build: enforce complete private release workflow
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ai",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/authz",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/authz — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
+11
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/cli — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -28,16 +28,16 @@
|
||||
"wrnexus": "./dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.17",
|
||||
"@wrnexus/router": "^0.2.17",
|
||||
"@wrnexus/csr": "^0.2.17",
|
||||
"@wrnexus/compiler": "^0.2.17",
|
||||
"@wrnexus/styles": "^0.2.17",
|
||||
"@wrnexus/dev-server": "^0.2.17",
|
||||
"@wrnexus/ui": "^0.2.17",
|
||||
"@wrnexus/validation": "^0.2.17",
|
||||
"@wrnexus/i18n": "^0.2.17",
|
||||
"@wrnexus/db": "^0.2.17"
|
||||
"@wrnexus/core": "^0.2.18",
|
||||
"@wrnexus/router": "^0.2.18",
|
||||
"@wrnexus/csr": "^0.2.18",
|
||||
"@wrnexus/compiler": "^0.2.18",
|
||||
"@wrnexus/styles": "^0.2.18",
|
||||
"@wrnexus/dev-server": "^0.2.18",
|
||||
"@wrnexus/ui": "^0.2.18",
|
||||
"@wrnexus/validation": "^0.2.18",
|
||||
"@wrnexus/i18n": "^0.2.18",
|
||||
"@wrnexus/db": "^0.2.18"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/compiler — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/core",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/core — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/csr — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.17"
|
||||
"@wrnexus/core": "^0.2.18"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/db — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/dev-server — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -25,18 +25,18 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.17",
|
||||
"@wrnexus/router": "^0.2.17",
|
||||
"@wrnexus/ssr": "^0.2.17",
|
||||
"@wrnexus/csr": "^0.2.17",
|
||||
"@wrnexus/compiler": "^0.2.17",
|
||||
"@wrnexus/styles": "^0.2.17",
|
||||
"@wrnexus/ui": "^0.2.17",
|
||||
"@wrnexus/validation": "^0.2.17",
|
||||
"@wrnexus/i18n": "^0.2.17",
|
||||
"@wrnexus/db": "^0.2.17",
|
||||
"@wrnexus/pubsub": "^0.2.17",
|
||||
"@wrnexus/uploader": "^0.2.17"
|
||||
"@wrnexus/core": "^0.2.18",
|
||||
"@wrnexus/router": "^0.2.18",
|
||||
"@wrnexus/ssr": "^0.2.18",
|
||||
"@wrnexus/csr": "^0.2.18",
|
||||
"@wrnexus/compiler": "^0.2.18",
|
||||
"@wrnexus/styles": "^0.2.18",
|
||||
"@wrnexus/ui": "^0.2.18",
|
||||
"@wrnexus/validation": "^0.2.18",
|
||||
"@wrnexus/i18n": "^0.2.18",
|
||||
"@wrnexus/db": "^0.2.18",
|
||||
"@wrnexus/pubsub": "^0.2.18",
|
||||
"@wrnexus/uploader": "^0.2.18"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/encryption",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/encryption — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/helpers",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.17"
|
||||
"@wrnexus/core": "^0.2.18"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/i18n",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/i18n — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.17"
|
||||
"@wrnexus/core": "^0.2.18"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/jwt",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/jwt — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/mobile",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/mobile — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/native": "^0.2.17"
|
||||
"@wrnexus/native": "^0.2.18"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/native",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/native — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/oauth — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/pubsub",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/pubsub — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/queue",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/queue — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/reactive",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/reactive — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/router",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/router — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,8 +21,8 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/compiler": "^0.2.17",
|
||||
"@wrnexus/core": "^0.2.17"
|
||||
"@wrnexus/compiler": "^0.2.18",
|
||||
"@wrnexus/core": "^0.2.18"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/ssr — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.17"
|
||||
"@wrnexus/core": "^0.2.18"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/styles — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/uploader": "^0.2.17"
|
||||
"@wrnexus/uploader": "^0.2.18"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/test",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/test — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/tracking — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/ui — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -22,7 +22,7 @@
|
||||
"./ui.css": "./ui.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.17"
|
||||
"@wrnexus/core": "^0.2.18"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/uploader — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.17"
|
||||
"@wrnexus/core": "^0.2.18"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/validation — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
+60
-8
@@ -21,7 +21,58 @@ The repo `.npmrc` points the scope at public npm:
|
||||
@wrnexus:registry=https://registry.npmjs.org/
|
||||
```
|
||||
|
||||
## Publish
|
||||
## Mandatory release workflow
|
||||
|
||||
Do not publish packages with ad-hoc `npm publish` commands during a normal release. The
|
||||
repository release commands enforce the complete framework + migration + documentation
|
||||
workflow.
|
||||
|
||||
Every release must have an entry in `packages/cli/src/update.ts`'s `MIGRATIONS` list. The
|
||||
entry must be idempotent and conservative. If a release requires no project file change,
|
||||
add a documented no-op migration entry so that the decision is explicit and reviewable.
|
||||
|
||||
### 1. Prepare
|
||||
|
||||
Align every `packages/*/package.json` version, add the update migration and tests, then run:
|
||||
|
||||
```bash
|
||||
bun run release:prepare
|
||||
```
|
||||
|
||||
This command refuses to prepare a release when versions differ or migration coverage is
|
||||
missing. It runs the complete framework check, builds all publish artifacts, and verifies
|
||||
that every staged manifest uses the aligned version and `restricted` access.
|
||||
|
||||
Review the generated `.publish` changes, then commit and push the framework repository.
|
||||
The publish step will refuse to run from an uncommitted or unpushed framework checkout.
|
||||
|
||||
### 2. Publish privately and update documentation
|
||||
|
||||
```bash
|
||||
bun run release:private
|
||||
```
|
||||
|
||||
The command performs the remaining release as one guarded workflow:
|
||||
|
||||
1. Rebuild and validate all private package artifacts from the pushed commit.
|
||||
2. Publish every missing `@wrnexus/*` package in dependency order. Already-published
|
||||
packages are verified and skipped, so an interrupted release can be resumed safely.
|
||||
3. Confirm every package is private and run the newly published CLI updater, including its
|
||||
migrations, against `D:\Company\wrnexusjs`.
|
||||
4. Align every docs-site dependency, regenerate package pages twice, update
|
||||
`public/llms.txt`, `public/llms-full.txt`, the sitemap, and the documentation index.
|
||||
5. Run the docs-site check and production build, verify that `public/llms.txt` contains the
|
||||
current version and every framework package, then commit and push the docs repository.
|
||||
|
||||
If the documentation checkout lives elsewhere, set `WRNEXUS_DOCS_ROOT` before running the
|
||||
publish command. Both repositories must start clean and synchronized with their active
|
||||
remote branches.
|
||||
|
||||
Only use the individual commands printed by `scripts/publish-packages.ts` for diagnosed
|
||||
recovery. Finish recovery by rerunning `bun run release:private`; it is the authoritative
|
||||
completion check.
|
||||
|
||||
## Publishing implementation
|
||||
|
||||
The packages ship raw TypeScript in the repo (`main`/`exports` → `src/*.ts`), so
|
||||
they are **built** for publishing. `scripts/publish-packages.ts` builds each
|
||||
@@ -36,13 +87,14 @@ package with [tsup](https://tsup.egoist.dev) (bundled JS + a single `.d.ts`) and
|
||||
The dev `package.json` (pointing at `src/`) is **never modified**, so in-repo dev
|
||||
and tests keep working.
|
||||
|
||||
### 1. Private-first (test before the world sees it)
|
||||
### Private staging details
|
||||
|
||||
By default the script stages packages as **`restricted`** (private). Requires the
|
||||
`wrnexus` org on a paid Teams plan.
|
||||
By default the lower-level staging script creates **`restricted`** (private) manifests.
|
||||
It is called by the mandatory release commands and requires the `wrnexus` org on a paid
|
||||
Teams plan.
|
||||
|
||||
```bash
|
||||
# Build + stage all packages (access: restricted)
|
||||
# Lower-level recovery/debug command only
|
||||
bun run scripts/publish-packages.ts
|
||||
|
||||
# Publish (deps first). The script prints the exact order + --access flag; e.g.
|
||||
@@ -52,8 +104,8 @@ npm publish .publish/compiler --access restricted
|
||||
npm publish .publish/cli --access restricted
|
||||
```
|
||||
|
||||
Test the private packages (see below). When satisfied, **flip every package to
|
||||
public** (no republish needed):
|
||||
When an approved release is ready to become public, **flip every package to public** (no
|
||||
republish needed):
|
||||
|
||||
```bash
|
||||
npm access public @wrnexus/core
|
||||
@@ -61,7 +113,7 @@ npm access public @wrnexus/compiler
|
||||
# … the script prints the full list …
|
||||
```
|
||||
|
||||
### 2. Public directly
|
||||
### Public staging for an explicitly approved public release
|
||||
|
||||
To stage public manifests from the start (free org is enough):
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
"test": "bun test packages",
|
||||
"test:coverage": "bun test --coverage packages",
|
||||
"security:audit": "bun audit",
|
||||
"release:prepare": "bun run scripts/release.ts prepare",
|
||||
"release:private": "bun run scripts/release.ts publish",
|
||||
"test:db:live": "bash scripts/test-live-db.sh",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint .",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ai",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/authz",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -156,8 +156,29 @@ const MIGRATIONS: Migration[] = [
|
||||
if (!ctx.dryRun) writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
||||
},
|
||||
},
|
||||
{
|
||||
version: "0.2.18",
|
||||
id: "add-helpers-package",
|
||||
description: "Add @wrnexus/helpers to existing runnable applications",
|
||||
apply(ctx) {
|
||||
// Workspace roots do not consume application Context helpers directly.
|
||||
if (!existsSync(join(ctx.appRoot, "app", "pages"))) return;
|
||||
const file = join(ctx.appRoot, "package.json");
|
||||
const pkg = JSON.parse(readFileSync(file, "utf8")) as Record<string, any>;
|
||||
const dependencies = (pkg.dependencies ??= {} as Record<string, string>);
|
||||
if (dependencies["@wrnexus/helpers"]) return;
|
||||
dependencies["@wrnexus/helpers"] = `^${ctx.to}`;
|
||||
ctx.log(`+ @wrnexus/helpers ^${ctx.to}`);
|
||||
if (!ctx.dryRun) writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/** Release tooling uses this to require an explicit migration entry per version. */
|
||||
export function updateMigrationVersions(): string[] {
|
||||
return [...new Set(MIGRATIONS.map((migration) => migration.version))];
|
||||
}
|
||||
|
||||
/** Bump every `@wrnexus/*` range to `^target`. Returns the human-readable changes. */
|
||||
function bumpDeps(pkg: Record<string, unknown>, target: string): string[] {
|
||||
const changed: string[] = [];
|
||||
|
||||
@@ -36,3 +36,28 @@ test("updateApp migrates project files without marking an unverified update comp
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("0.2.18 migration adds helpers to existing apps and is idempotent", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrnexus-update-"));
|
||||
mkdirSync(join(root, "app", "pages"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(root, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "existing-app",
|
||||
dependencies: { "@wrnexus/core": "^0.2.17" },
|
||||
wrnexus: { version: "0.2.17" },
|
||||
}),
|
||||
);
|
||||
|
||||
try {
|
||||
updateApp(root, "0.2.18", false);
|
||||
updateApp(root, "0.2.18", false);
|
||||
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
||||
expect(pkg.dependencies["@wrnexus/helpers"]).toBe("^0.2.18");
|
||||
expect(
|
||||
Object.keys(pkg.dependencies).filter((name) => name === "@wrnexus/helpers"),
|
||||
).toHaveLength(1);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/core",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/encryption",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/helpers",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/i18n",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/jwt",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/mobile",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/native",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/pubsub",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/queue",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/reactive",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/router",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/test",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.2.17",
|
||||
"version": "0.2.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* Mandatory WRNexusJS private release workflow.
|
||||
*
|
||||
* bun run release:prepare # verify, enforce migration coverage, stage packages
|
||||
* # review, commit, and push the framework repository
|
||||
* bun run release:private # publish/resume, update docs, verify llms, commit + push docs
|
||||
*
|
||||
* Set WRNEXUS_DOCS_ROOT when the docs repository is not D:\\Company\\wrnexusjs.
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { updateMigrationVersions } from "../packages/cli/src/update.ts";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const docsRoot = resolve(process.env.WRNEXUS_DOCS_ROOT ?? "D:\\Company\\wrnexusjs");
|
||||
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
const mode = process.argv[2];
|
||||
|
||||
interface PackageInfo {
|
||||
dir: string;
|
||||
name: string;
|
||||
version: string;
|
||||
dependencies: Record<string, string>;
|
||||
}
|
||||
|
||||
function run(
|
||||
command: string,
|
||||
args: string[],
|
||||
cwd = root,
|
||||
options: { capture?: boolean; allowFailure?: boolean } = {},
|
||||
) {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd,
|
||||
encoding: options.capture ? "utf8" : undefined,
|
||||
stdio: options.capture ? "pipe" : "inherit",
|
||||
env: process.env,
|
||||
});
|
||||
if (result.error) throw result.error;
|
||||
if (result.status !== 0 && !options.allowFailure) {
|
||||
const detail = options.capture ? `\n${result.stderr || result.stdout || ""}` : "";
|
||||
throw new Error(`${command} ${args.join(" ")} failed with ${result.status}.${detail}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function output(command: string, args: string[], cwd = root): string {
|
||||
return String(run(command, args, cwd, { capture: true }).stdout ?? "").trim();
|
||||
}
|
||||
|
||||
function packages(): PackageInfo[] {
|
||||
return readdirSync(join(root, "packages"), { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => {
|
||||
const file = join(root, "packages", entry.name, "package.json");
|
||||
if (!existsSync(file)) return null;
|
||||
const manifest = JSON.parse(readFileSync(file, "utf8"));
|
||||
if (!manifest.name?.startsWith("@wrnexus/")) return null;
|
||||
return {
|
||||
dir: entry.name,
|
||||
name: manifest.name as string,
|
||||
version: manifest.version as string,
|
||||
dependencies: (manifest.dependencies ?? {}) as Record<string, string>,
|
||||
};
|
||||
})
|
||||
.filter((value): value is PackageInfo => value !== null);
|
||||
}
|
||||
|
||||
function alignedVersion(all: PackageInfo[]): string {
|
||||
const versions = new Set(all.map((pkg) => pkg.version));
|
||||
if (versions.size !== 1) {
|
||||
throw new Error(`Package versions are not aligned: ${[...versions].join(", ")}`);
|
||||
}
|
||||
const version = all[0]?.version;
|
||||
if (!version || !/^\d+\.\d+\.\d+$/.test(version)) {
|
||||
throw new Error(`Invalid release version: ${version ?? "missing"}`);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
function sortedPackages(all: PackageInfo[]): PackageInfo[] {
|
||||
const byName = new Map(all.map((pkg) => [pkg.name, pkg]));
|
||||
const seen = new Set<string>();
|
||||
const result: PackageInfo[] = [];
|
||||
const visit = (pkg: PackageInfo) => {
|
||||
if (seen.has(pkg.name)) return;
|
||||
seen.add(pkg.name);
|
||||
for (const dependency of Object.keys(pkg.dependencies)) {
|
||||
const internal = byName.get(dependency);
|
||||
if (internal) visit(internal);
|
||||
}
|
||||
result.push(pkg);
|
||||
};
|
||||
all.forEach(visit);
|
||||
return result;
|
||||
}
|
||||
|
||||
function requireMigration(version: string) {
|
||||
if (!updateMigrationVersions().includes(version)) {
|
||||
throw new Error(
|
||||
`Release ${version} has no update migration entry. Add an idempotent migration (a documented no-op is acceptable) to packages/cli/src/update.ts.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateStaging(all: PackageInfo[], version: string) {
|
||||
for (const pkg of all) {
|
||||
const stage = join(root, ".publish", pkg.dir);
|
||||
const manifestPath = join(stage, "package.json");
|
||||
const dist = join(stage, "dist", "index.js");
|
||||
if (!existsSync(manifestPath) || !existsSync(dist)) {
|
||||
throw new Error(`Missing staged artifact for ${pkg.name}; run release:prepare again.`);
|
||||
}
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
||||
if (manifest.version !== version || manifest.publishConfig?.access !== "restricted") {
|
||||
throw new Error(`${pkg.name} staging is not restricted ${version}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function requireCleanAndPushed(repo: string, label: string) {
|
||||
if (output("git", ["status", "--porcelain"], repo)) {
|
||||
throw new Error(`${label} repository has uncommitted changes.`);
|
||||
}
|
||||
run("git", ["fetch", "origin"], repo);
|
||||
const branch = output("git", ["branch", "--show-current"], repo);
|
||||
const head = output("git", ["rev-parse", "HEAD"], repo);
|
||||
const remote = output("git", ["rev-parse", `origin/${branch}`], repo);
|
||||
if (head !== remote) throw new Error(`${label} HEAD is not synchronized with origin/${branch}.`);
|
||||
}
|
||||
|
||||
function registryHas(pkg: PackageInfo, version: string): boolean {
|
||||
const result = run(npm, ["view", `${pkg.name}@${version}`, "version", "--json"], root, {
|
||||
capture: true,
|
||||
allowFailure: true,
|
||||
});
|
||||
return result.status === 0 && String(result.stdout).includes(version);
|
||||
}
|
||||
|
||||
function verifyPrivate(pkg: PackageInfo, version: string) {
|
||||
const status = output(npm, ["access", "get", "status", pkg.name]);
|
||||
if (!status.endsWith(": private")) throw new Error(`${pkg.name}@${version} is not private.`);
|
||||
}
|
||||
|
||||
function syncDocsManifest(all: PackageInfo[], version: string) {
|
||||
const file = join(docsRoot, "package.json");
|
||||
const manifest = JSON.parse(readFileSync(file, "utf8"));
|
||||
manifest.dependencies ??= {};
|
||||
manifest.devDependencies ??= {};
|
||||
for (const pkg of all) {
|
||||
if (pkg.name === "@wrnexus/cli") {
|
||||
manifest.devDependencies[pkg.name] = `^${version}`;
|
||||
delete manifest.dependencies[pkg.name];
|
||||
} else {
|
||||
manifest.dependencies[pkg.name] = `^${version}`;
|
||||
delete manifest.devDependencies[pkg.name];
|
||||
}
|
||||
}
|
||||
manifest.wrnexus = { ...(manifest.wrnexus ?? {}), version };
|
||||
writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n");
|
||||
}
|
||||
|
||||
function verifyDocs(all: PackageInfo[], version: string) {
|
||||
const manifest = JSON.parse(readFileSync(join(docsRoot, "package.json"), "utf8"));
|
||||
if (manifest.wrnexus?.version !== version) throw new Error("Docs version marker is stale.");
|
||||
const llms = readFileSync(join(docsRoot, "public", "llms.txt"), "utf8");
|
||||
if (!llms.includes(`# WRNexusJS documentation ${version}`)) {
|
||||
throw new Error("Docs public/llms.txt has a stale or missing release header.");
|
||||
}
|
||||
for (const pkg of all) {
|
||||
if (!llms.includes(`## ${pkg.name}`)) {
|
||||
throw new Error(`Docs public/llms.txt is missing ${pkg.name}.`);
|
||||
}
|
||||
}
|
||||
const full = readFileSync(join(docsRoot, "public", "llms-full.txt"), "utf8");
|
||||
if (!full.includes(`WRNexusJS ${version}`) || !full.includes("/packages/helpers")) {
|
||||
throw new Error("Docs public/llms-full.txt is stale or incomplete.");
|
||||
}
|
||||
}
|
||||
|
||||
function prepare(all: PackageInfo[], version: string) {
|
||||
requireMigration(version);
|
||||
console.log(`\nPreparing WRNexusJS ${version} (${all.length} packages)…\n`);
|
||||
run(process.execPath, ["run", "check"]);
|
||||
run(process.execPath, ["run", "scripts/publish-packages.ts"]);
|
||||
validateStaging(all, version);
|
||||
console.log(
|
||||
`\n✓ Release ${version} is prepared. Review changes, commit and push the framework repository, then run: bun run release:private\n`,
|
||||
);
|
||||
}
|
||||
|
||||
function publish(all: PackageInfo[], version: string) {
|
||||
if (!existsSync(docsRoot)) throw new Error(`Docs repository not found: ${docsRoot}`);
|
||||
requireMigration(version);
|
||||
requireCleanAndPushed(root, "Framework");
|
||||
requireCleanAndPushed(docsRoot, "Docs");
|
||||
|
||||
// Rebuild from the committed source and refuse publication if staging drifts.
|
||||
run(process.execPath, ["run", "scripts/publish-packages.ts"]);
|
||||
validateStaging(all, version);
|
||||
requireCleanAndPushed(root, "Framework after staging");
|
||||
output(npm, ["whoami"]);
|
||||
|
||||
for (const pkg of sortedPackages(all)) {
|
||||
if (registryHas(pkg, version)) {
|
||||
console.log(` = ${pkg.name}@${version} already published; verifying`);
|
||||
} else {
|
||||
console.log(` + publishing ${pkg.name}@${version}`);
|
||||
run(npm, ["publish", join(root, ".publish", pkg.dir), "--access", "restricted"]);
|
||||
}
|
||||
verifyPrivate(pkg, version);
|
||||
}
|
||||
|
||||
console.log(`\nUpdating docs repository at ${docsRoot}…\n`);
|
||||
run(
|
||||
process.execPath,
|
||||
["x", "--package", `@wrnexus/cli@${version}`, "wrnexus", "update", `--version=${version}`],
|
||||
docsRoot,
|
||||
);
|
||||
syncDocsManifest(all, version);
|
||||
run(process.execPath, ["install"], docsRoot);
|
||||
run(npm, ["install", "--package-lock-only", "--ignore-scripts"], docsRoot);
|
||||
run(process.execPath, ["run", "docs:generate"], docsRoot);
|
||||
run(process.execPath, ["run", "docs:generate"], docsRoot);
|
||||
run(process.execPath, ["run", "check"], docsRoot);
|
||||
run(process.execPath, ["run", "build"], docsRoot);
|
||||
verifyDocs(all, version);
|
||||
|
||||
run("git", ["add", "--all"], docsRoot);
|
||||
if (output("git", ["status", "--porcelain"], docsRoot)) {
|
||||
run("git", ["commit", "-m", `docs: update portal for WRNexusJS ${version}`], docsRoot);
|
||||
}
|
||||
run("git", ["push", "origin", "HEAD"], docsRoot);
|
||||
requireCleanAndPushed(docsRoot, "Docs");
|
||||
console.log(`\n✓ WRNexusJS ${version} published privately and docs are updated and pushed.\n`);
|
||||
}
|
||||
|
||||
const all = packages();
|
||||
const version = alignedVersion(all);
|
||||
if (mode === "prepare") prepare(all, version);
|
||||
else if (mode === "publish") publish(all, version);
|
||||
else {
|
||||
throw new Error("Usage: bun scripts/release.ts prepare | publish");
|
||||
}
|
||||
Reference in New Issue
Block a user