first commit
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* Build every @wrnexus/* package (JS + .d.ts via tsup) and STAGE a publishable
|
||||
* copy under .publish/<name>/ with a rewritten manifest:
|
||||
* - main/module/types/exports/bin point at ./dist/*
|
||||
* - workspace:* deps → ^<version>
|
||||
* - `private` removed, publishConfig.registry set
|
||||
* - runtime assets (ui components/, ui.css) copied alongside dist
|
||||
*
|
||||
* The dev package.json (pointing at src/) is never modified, so in-repo dev and
|
||||
* tests keep working.
|
||||
*
|
||||
* bun run scripts/publish-packages.ts # build + stage all
|
||||
* bun run scripts/publish-packages.ts core db # build + stage only these
|
||||
*
|
||||
* Then publish from the staging dirs (see printed order), or dry-run with npm pack.
|
||||
*/
|
||||
import { build } from "tsup";
|
||||
import {
|
||||
cpSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const packagesDir = join(repoRoot, "packages");
|
||||
const stageRoot = join(repoRoot, ".publish");
|
||||
const REGISTRY = "https://registry.npmjs.org/";
|
||||
// First release is published PRIVATE ("restricted") so we can test before the
|
||||
// world sees it; flip to public with `npm access public @wrnexus/<name>` (or set
|
||||
// WRNEXUS_NPM_ACCESS=public to stage public manifests). Requires the `wrnexus`
|
||||
// npm org to exist and be on a paid plan (private scoped packages need Teams).
|
||||
const ACCESS = process.env.WRNEXUS_NPM_ACCESS === "public" ? "public" : "restricted";
|
||||
const LICENSE = "MIT";
|
||||
|
||||
/** Extra (non-code) files each package must ship, relative to the package root. */
|
||||
const ASSETS: Record<string, string[]> = {
|
||||
"@wrnexus/ui": ["components", "ui.css"],
|
||||
};
|
||||
|
||||
interface Pkg {
|
||||
dir: string;
|
||||
name: string;
|
||||
manifest: Record<string, any>;
|
||||
}
|
||||
|
||||
function readPackages(filter: string[]): Pkg[] {
|
||||
const out: Pkg[] = [];
|
||||
for (const entry of readdirSync(packagesDir)) {
|
||||
const dir = join(packagesDir, entry);
|
||||
const pj = join(dir, "package.json");
|
||||
if (!existsSync(pj)) continue;
|
||||
const manifest = JSON.parse(readFileSync(pj, "utf8"));
|
||||
if (!manifest.name?.startsWith("@wrnexus/")) continue;
|
||||
if (filter.length && !filter.includes(entry) && !filter.includes(manifest.name)) continue;
|
||||
out.push({ dir, name: manifest.name, manifest });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Topologically sort by @wrnexus deps so dependencies publish first. */
|
||||
function topoSort(pkgs: Pkg[]): Pkg[] {
|
||||
const byName = new Map(pkgs.map((p) => [p.name, p]));
|
||||
const sorted: Pkg[] = [];
|
||||
const seen = new Set<string>();
|
||||
const visit = (p: Pkg) => {
|
||||
if (seen.has(p.name)) return;
|
||||
seen.add(p.name);
|
||||
for (const dep of Object.keys(p.manifest.dependencies ?? {})) {
|
||||
const d = byName.get(dep);
|
||||
if (d) visit(d);
|
||||
}
|
||||
sorted.push(p);
|
||||
};
|
||||
pkgs.forEach(visit);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a tsup entry map { outName: absSourcePath } from every `./x.ts` target
|
||||
* referenced by main/bin/exports. Object entries (unlike arrays) are NOT glob-
|
||||
* expanded by tsup, so absolute Windows paths work. `outName` is the source path
|
||||
* relative to `src/` without extension, matching the js()/dts() manifest mapping.
|
||||
*/
|
||||
function entriesOf(dir: string, m: Record<string, any>): Record<string, string> {
|
||||
const map: Record<string, string> = {};
|
||||
const add = (v: unknown) => {
|
||||
if (typeof v !== "string" || !v.endsWith(".ts")) return;
|
||||
const rel = v.replace(/^\.?\/?src\//, "").replace(/\.ts$/, "");
|
||||
map[rel] = join(dir, v.replace(/^\.\//, "")).replace(/\\/g, "/");
|
||||
};
|
||||
add(m.main);
|
||||
if (m.bin) Object.values(m.bin).forEach(add);
|
||||
if (m.exports) for (const v of Object.values(m.exports)) add(v);
|
||||
return map;
|
||||
}
|
||||
|
||||
/** Rewrite the manifest for publishing. */
|
||||
function publishManifest(m: Record<string, any>, version: string): Record<string, any> {
|
||||
const js = (ts: string) => "./dist/" + ts.replace(/^\.?\/?src\//, "").replace(/\.ts$/, ".js");
|
||||
const dts = (ts: string) => "./dist/" + ts.replace(/^\.?\/?src\//, "").replace(/\.ts$/, ".d.ts");
|
||||
|
||||
const out: Record<string, any> = {
|
||||
name: m.name,
|
||||
version,
|
||||
type: "module",
|
||||
description: m.description ?? `${m.name} — part of the WrNexus framework.`,
|
||||
license: LICENSE,
|
||||
main: m.main ? js(m.main) : "./dist/index.js",
|
||||
module: m.main ? js(m.main) : "./dist/index.js",
|
||||
types: m.main ? dts(m.main) : "./dist/index.d.ts",
|
||||
engines: { bun: ">=1.1.0" },
|
||||
publishConfig: { registry: REGISTRY, access: ACCESS },
|
||||
};
|
||||
|
||||
// exports: rewrite .ts targets to { types, import }; copy non-.ts (assets) as-is.
|
||||
if (m.exports) {
|
||||
const ex: Record<string, any> = {};
|
||||
for (const [key, val] of Object.entries(m.exports)) {
|
||||
if (typeof val === "string" && val.endsWith(".ts")) {
|
||||
ex[key] = { types: dts(val), import: js(val) };
|
||||
} else {
|
||||
ex[key] = val; // e.g. "./ui.css"
|
||||
}
|
||||
}
|
||||
out.exports = ex;
|
||||
} else {
|
||||
out.exports = { ".": { types: out.types, import: out.main } };
|
||||
}
|
||||
|
||||
if (m.bin) {
|
||||
out.bin = {};
|
||||
for (const [k, v] of Object.entries(m.bin)) out.bin[k] = js(v as string);
|
||||
}
|
||||
|
||||
if (m.dependencies) {
|
||||
out.dependencies = {};
|
||||
for (const [dep, range] of Object.entries(m.dependencies)) {
|
||||
out.dependencies[dep] = String(range).startsWith("workspace:") ? `^${version}` : range;
|
||||
}
|
||||
}
|
||||
|
||||
const files = new Set(["dist"]);
|
||||
for (const a of ASSETS[m.name] ?? []) files.add(a);
|
||||
out.files = [...files];
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const filter = process.argv.slice(2);
|
||||
const pkgs = topoSort(readPackages(filter));
|
||||
if (!pkgs.length) {
|
||||
console.error("No @wrnexus packages matched.");
|
||||
process.exit(1);
|
||||
}
|
||||
rmSync(stageRoot, { recursive: true, force: true });
|
||||
mkdirSync(stageRoot, { recursive: true });
|
||||
|
||||
const order: string[] = [];
|
||||
for (const p of pkgs) {
|
||||
const entries = entriesOf(p.dir, p.manifest);
|
||||
process.stdout.write(`▸ ${p.name} … `);
|
||||
|
||||
await build({
|
||||
entry: entries,
|
||||
outDir: join(p.dir, "dist"),
|
||||
format: ["esm"],
|
||||
dts: true,
|
||||
external: [/^@wrnexus\//, /^bun:/, /^node:/],
|
||||
tsconfig: join(repoRoot, "tsconfig.json"),
|
||||
clean: true,
|
||||
splitting: false,
|
||||
sourcemap: false,
|
||||
shims: false,
|
||||
silent: true,
|
||||
target: "esnext",
|
||||
platform: "node",
|
||||
});
|
||||
|
||||
// tsup's dts pass can occasionally leak .d.ts files next to source (the
|
||||
// jsxImportSource files). Source is all .ts, so any .d.ts under src/ is a
|
||||
// stray artifact — remove it so it never pollutes the tree.
|
||||
for (const f of readdirSync(join(p.dir, "src"), { recursive: true }) as string[]) {
|
||||
if (typeof f === "string" && f.endsWith(".d.ts")) {
|
||||
rmSync(join(p.dir, "src", f), { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
// Stage: dist + assets + manifest + README.
|
||||
const stage = join(stageRoot, p.name.replace("@wrnexus/", ""));
|
||||
mkdirSync(stage, { recursive: true });
|
||||
cpSync(join(p.dir, "dist"), join(stage, "dist"), { recursive: true });
|
||||
for (const a of ASSETS[p.name] ?? []) {
|
||||
if (existsSync(join(p.dir, a))) cpSync(join(p.dir, a), join(stage, a), { recursive: true });
|
||||
}
|
||||
const manifest = publishManifest(p.manifest, p.manifest.version);
|
||||
writeFileSync(join(stage, "package.json"), JSON.stringify(manifest, null, 2) + "\n");
|
||||
// Ship the package's own README.md if it has one; else a minimal placeholder.
|
||||
const srcReadme = join(p.dir, "README.md");
|
||||
if (existsSync(srcReadme)) {
|
||||
cpSync(srcReadme, join(stage, "README.md"));
|
||||
} else {
|
||||
writeFileSync(
|
||||
join(stage, "README.md"),
|
||||
`# ${p.name}\n\n${manifest.description}\n\nPart of the WrNexus framework.\n`,
|
||||
);
|
||||
}
|
||||
|
||||
order.push(p.name);
|
||||
console.log("staged");
|
||||
}
|
||||
|
||||
console.log(`\n✓ Staged ${order.length} package(s) under .publish/ (access: ${ACCESS})`);
|
||||
console.log("Publish order (deps first):");
|
||||
for (const n of order) {
|
||||
console.log(` npm publish .publish/${n.replace("@wrnexus/", "")} --access ${ACCESS}`);
|
||||
}
|
||||
if (ACCESS === "restricted") {
|
||||
console.log("\nAfter testing, flip every package to public with:");
|
||||
for (const n of order) console.log(` npm access public ${n}`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# Spin up Postgres + MySQL, run the live DB integration tests, tear down.
|
||||
# Requires Docker. From the repo root: bun run test:db:live
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
COMPOSE="docker compose -f docker-compose.yml"
|
||||
|
||||
cleanup() { $COMPOSE down -v >/dev/null 2>&1 || true; }
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "Starting Postgres + MySQL…"
|
||||
$COMPOSE up -d
|
||||
|
||||
echo "Waiting for databases to become healthy…"
|
||||
for _ in $(seq 1 60); do
|
||||
pg=$(docker inspect --format '{{.State.Health.Status}}' "$($COMPOSE ps -q postgres)" 2>/dev/null || echo starting)
|
||||
my=$(docker inspect --format '{{.State.Health.Status}}' "$($COMPOSE ps -q mysql)" 2>/dev/null || echo starting)
|
||||
[ "$pg" = healthy ] && [ "$my" = healthy ] && break
|
||||
sleep 2
|
||||
done
|
||||
|
||||
export WIREFW_PG_URL="postgres://wire:wire@localhost:5433/wire_test"
|
||||
export WIREFW_MYSQL_URL="mysql://wire:wire@localhost:3307/wire_test"
|
||||
|
||||
echo "Running live DB tests…"
|
||||
bun test packages/db/test/live-db.test.ts
|
||||
Reference in New Issue
Block a user