session-orchestrator 3.17.0 → 3.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/030-wave-execution.mdc +17 -1
- package/CHANGELOG.md +185 -412
- package/README.md +12 -9
- package/SECURITY.md +190 -27
- package/agents/AGENTS.md +20 -3
- package/agents/code-implementer.md +6 -6
- package/agents/db-specialist.md +1 -1
- package/agents/qa-strategist.md +31 -6
- package/agents/schemas/qa-strategist.schema.json +27 -0
- package/agents/schemas/test-writer.schema.json +60 -2
- package/agents/security-reviewer.md +1 -1
- package/agents/session-reviewer.md +1 -1
- package/agents/test-writer.md +29 -10
- package/agents/ui-developer.md +1 -1
- package/commands/contract-version-bump.md +28 -0
- package/commands/portfolio.md +1 -1
- package/commands/session.md +6 -2
- package/docs/USER-GUIDE.md +9 -4
- package/docs/ci-setup.md +121 -7
- package/docs/codex-setup.md +1 -1
- package/docs/components.md +6 -6
- package/docs/cursor-setup.md +22 -9
- package/docs/events-schema.md +5 -1
- package/docs/instruction-delivery.md +794 -0
- package/docs/rule-authoring.md +58 -9
- package/docs/session-config-reference.md +245 -50
- package/docs/session-config-template.md +39 -26
- package/hooks/_lib/guard-source-loader.mjs +680 -0
- package/hooks/_lib/lock-bootstrap.mjs +21 -0
- package/hooks/_lib/vcs-create-matcher.mjs +119 -0
- package/hooks/config-protection.mjs +0 -0
- package/hooks/enforce-commands.mjs +226 -19
- package/hooks/enforce-scope.mjs +133 -9
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +11 -2
- package/hooks/hooks-pi.json +10 -0
- package/hooks/hooks.json +21 -1
- package/hooks/on-session-end.mjs +178 -18
- package/hooks/on-session-start.mjs +30 -4
- package/hooks/post-bash-write-verify.mjs +977 -0
- package/hooks/post-subagent-discovery-validator.mjs +256 -41
- package/hooks/pre-bash-destructive-guard.mjs +616 -164
- package/hooks/pre-bash-issue-budget.mjs +167 -0
- package/hooks/pre-bash-sessions-ledger-guard.mjs +1054 -0
- package/hooks/pre-bash-templates-first.mjs +96 -63
- package/hooks/subagent-telemetry.mjs +527 -37
- package/package.json +6 -3
- package/pi/prompts/contract-version-bump.md +12 -0
- package/rules/README.md +32 -0
- package/scripts/archive-closed-prds.mjs +12 -22
- package/scripts/autopilot-multi.mjs +103 -20
- package/scripts/backfill-abandoned-sessions.mjs +160 -4
- package/scripts/backfill-learnings-from-vault.mjs +967 -0
- package/scripts/check-doc-consistency.sh +17 -1
- package/scripts/emit-session.mjs +3 -40
- package/scripts/eval-session.mjs +50 -9
- package/scripts/fleet-instruction-scan.mjs +141 -0
- package/scripts/lib/autopilot/mr-draft.mjs +31 -1
- package/scripts/lib/autopilot/worktree-pipeline.mjs +113 -5
- package/scripts/lib/backlog-scan.mjs +39 -6
- package/scripts/lib/blocked-commands-policy.mjs +340 -0
- package/scripts/lib/ci-status-banner.mjs +75 -12
- package/scripts/lib/claude-md-budget-lint.mjs +283 -34
- package/scripts/lib/command-blocker.mjs +1273 -58
- package/scripts/lib/config/config-protection.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +9 -1
- package/scripts/lib/config/gitlab-portfolio.mjs +1 -1
- package/scripts/lib/config/issue-budget.mjs +123 -0
- package/scripts/lib/config/reconcile.mjs +21 -0
- package/scripts/lib/config/section-extractor.mjs +121 -1
- package/scripts/lib/config-schema.mjs +23 -3
- package/scripts/lib/config.mjs +17 -0
- package/scripts/lib/convergence-monitor.mjs +49 -3
- package/scripts/lib/description-surface.mjs +535 -0
- package/scripts/lib/dispatcher/enumerate.mjs +26 -40
- package/scripts/lib/ecosystem-wizard/config-writer.mjs +26 -24
- package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +1 -1
- package/scripts/lib/eval/engine.mjs +47 -5
- package/scripts/lib/events.mjs +59 -7
- package/scripts/lib/gates/gate-full.mjs +15 -3
- package/scripts/lib/gates/gate-helpers.mjs +132 -6
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +28 -8
- package/scripts/lib/gitlab-portfolio/aggregator.mjs +8 -2
- package/scripts/lib/gitlab-portfolio/cli.mjs +1 -1
- package/scripts/lib/handover-gate.mjs +7 -3
- package/scripts/lib/hardening.mjs +9 -9
- package/scripts/lib/harness-audit/categories/category4.mjs +9 -3
- package/scripts/lib/instruction-budget-guard.mjs +402 -51
- package/scripts/lib/io.mjs +345 -10
- package/scripts/lib/issue-budget.mjs +269 -0
- package/scripts/lib/issue-close-strip-labels.mjs +39 -9
- package/scripts/lib/label-scope.mjs +47 -0
- package/scripts/lib/learnings/affinity.mjs +434 -0
- package/scripts/lib/learnings/candidates.mjs +736 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +408 -53
- package/scripts/lib/learnings/judgment.mjs +782 -0
- package/scripts/lib/learnings/kebab.mjs +128 -0
- package/scripts/lib/learnings/schema.mjs +43 -3
- package/scripts/lib/learnings/select.mjs +550 -0
- package/scripts/lib/lock-reaper.mjs +1 -2
- package/scripts/lib/memory-proposals/schema.mjs +36 -1
- package/scripts/lib/peer-discovery.mjs +645 -0
- package/scripts/lib/pi-hook-bridge.mjs +146 -17
- package/scripts/lib/product-repo-detect.mjs +9 -8
- package/scripts/lib/project-hygiene.mjs +432 -0
- package/scripts/lib/quality-gate.mjs +167 -0
- package/scripts/lib/recommendations-v0.mjs +1 -1
- package/scripts/lib/reconcile/eligibility.mjs +1 -1
- package/scripts/lib/reconcile/emitter.mjs +128 -24
- package/scripts/lib/reconcile/engine.mjs +156 -54
- package/scripts/lib/reconcile/idempotency.mjs +114 -14
- package/scripts/lib/reconcile/renderer.mjs +141 -25
- package/scripts/lib/reconcile/sanitize.mjs +518 -0
- package/scripts/lib/reconcile/writer.mjs +95 -1
- package/scripts/lib/reconcile-nudge-banner.mjs +65 -9
- package/scripts/lib/resource-probe/evaluate.mjs +70 -4
- package/scripts/lib/resource-probe.mjs +19 -0
- package/scripts/lib/rule-loader.mjs +6 -0
- package/scripts/lib/scope-baseline.mjs +564 -0
- package/scripts/lib/scope-gate.mjs +568 -145
- package/scripts/lib/session-close-backfill.mjs +63 -8
- package/scripts/lib/session-end/phase-skip.mjs +1 -0
- package/scripts/lib/session-id.mjs +221 -41
- package/scripts/lib/session-lock.mjs +304 -6
- package/scripts/lib/session-record-repair.mjs +551 -0
- package/scripts/lib/session-schema/constants.mjs +22 -3
- package/scripts/lib/session-schema/serializer.mjs +54 -0
- package/scripts/lib/session-schema/validator.mjs +16 -0
- package/scripts/lib/session-schema.mjs +1 -0
- package/scripts/lib/session-token-rollup.mjs +68 -6
- package/scripts/lib/sessions-integrity-banner.mjs +294 -0
- package/scripts/lib/sessions-staleness-banner.mjs +121 -12
- package/scripts/lib/skill-evolution/idempotency.mjs +135 -16
- package/scripts/lib/skill-evolution/mr-opener.mjs +9 -1
- package/scripts/lib/soul-resolve.mjs +12 -0
- package/scripts/lib/spiral-carryover.mjs +142 -30
- package/scripts/lib/state-md/mission-status.mjs +53 -3
- package/scripts/lib/subagents-schema.mjs +43 -9
- package/scripts/lib/test-runner/issue-reconcile.mjs +53 -13
- package/scripts/lib/tests-src-ratio.mjs +484 -0
- package/scripts/lib/tmux-layout/telemetry.mjs +43 -10
- package/scripts/lib/validate/check-agents.mjs +56 -0
- package/scripts/lib/validate/check-banner-parity.mjs +376 -0
- package/scripts/lib/validate/check-guard-requires-parity.mjs +1148 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +244 -10
- package/scripts/lib/validate/check-learning-provenance.mjs +511 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +3 -3
- package/scripts/lib/validate/check-rules.mjs +244 -36
- package/scripts/lib/validate/check-test-value-bans.mjs +782 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-unwired-features.mjs +549 -0
- package/scripts/lib/validate-vendored-rules.mjs +10 -2
- package/scripts/lib/vault-archive.mjs +17 -2
- package/scripts/lib/vault-backfill/glab.mjs +8 -0
- package/scripts/lib/vault-mirror/process.mjs +30 -0
- package/scripts/lib/vault-mirror/render-sessions.mjs +293 -36
- package/scripts/lib/vcs-repo-spec.mjs +362 -0
- package/scripts/lib/wave-resource-gate.mjs +115 -11
- package/scripts/lib/worktree/listing.mjs +44 -7
- package/scripts/mcp-server.sh +17 -3
- package/scripts/measure-context-overhead.sh +151 -0
- package/scripts/memory-propose.mjs +72 -9
- package/scripts/print-applicable-rules.mjs +218 -16
- package/scripts/print-learnings-index.mjs +474 -0
- package/scripts/release.mjs +534 -0
- package/scripts/repair-invalid-sessions.mjs +209 -0
- package/scripts/run-quality-gate.mjs +123 -5
- package/scripts/sweep-expired-learnings.mjs +192 -32
- package/scripts/validate-plugin.mjs +21 -0
- package/scripts/validate-wave-scope.mjs +182 -17
- package/scripts/vault-integration-watcher.mjs +32 -10
- package/skills/_shared/config-reading.md +2 -2
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/brainstorm/soul.md +47 -1
- package/skills/claude-md-drift-check/checker.mjs +145 -28
- package/skills/contract-version-bump/SKILL.md +219 -0
- package/skills/discovery/SKILL.md +4 -4
- package/skills/discovery/issue-templates.md +11 -11
- package/skills/discovery/probes-audit.md +1 -1
- package/skills/discovery/probes-feature.md +1 -1
- package/skills/discovery/probes-session.md +26 -5
- package/skills/ecosystem-health/SKILL.md +1 -1
- package/skills/ecosystem-health/wizard.md +4 -4
- package/skills/evolve/SKILL.md +117 -18
- package/skills/gitlab-ops/SKILL.md +25 -12
- package/skills/gitlab-portfolio/SKILL.md +2 -2
- package/skills/grill/soul.md +44 -1
- package/skills/hook-development/SKILL.md +1 -1
- package/skills/mode-selector/SKILL.md +1 -1
- package/skills/npm-publish/SKILL.md +17 -1
- package/skills/plan/SKILL.md +5 -5
- package/skills/plan/mode-feature.md +4 -4
- package/skills/plan/mode-new.md +10 -10
- package/skills/plan/mode-retro.md +1 -1
- package/skills/plan/soul.md +46 -3
- package/skills/quality-gates/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +21 -4
- package/skills/session-end/SKILL.md +34 -36
- package/skills/session-end/discovery-scan.md +4 -2
- package/skills/session-end/drift-operations.md +4 -4
- package/skills/session-end/metrics-collection.md +13 -0
- package/skills/session-end/phase-3-2-docs-verification.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +32 -2
- package/skills/session-end/plan-verification.md +6 -7
- package/skills/session-end/session-metrics-write.md +2 -0
- package/skills/session-end/vault-operations.md +1 -1
- package/skills/session-end/verification-checklist.md +1 -1
- package/skills/session-plan/SKILL.md +6 -2
- package/skills/session-plan/wave-template.md +2 -0
- package/skills/session-start/SKILL.md +75 -7
- package/skills/session-start/phase-4-5-resource-health.md +15 -2
- package/skills/session-start/soul.md +41 -1
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/vault-sync/validator.mjs +108 -7
- package/skills/wave-executor/SKILL.md +6 -7
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/wave-loop.md +198 -80
- package/templates/_shared/loop.md +4 -4
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/release.mjs
|
|
3
|
+
//
|
|
4
|
+
// Release als ein Dispatch — the local half of issue #978.
|
|
5
|
+
//
|
|
6
|
+
// WHY THIS EXISTS (the incident class):
|
|
7
|
+
// Version, tag, npm publish, README badge, CHANGELOG entry, site copy and
|
|
8
|
+
// the two plugin manifests were SEVEN independent manual acts. Every past
|
|
9
|
+
// release forgot at least one: v3.18.0 shipped without its tag for 65
|
|
10
|
+
// commits (retro-tagged 2026-07-31), the marketplace pin froze ~588 commits
|
|
11
|
+
// behind HEAD (#851). The defense is a single surfaces table that both the
|
|
12
|
+
// rewrite and the check share — a version literal that exists outside the
|
|
13
|
+
// table is found by the drift sweep, and a table pattern that stops
|
|
14
|
+
// matching its file is a hard error, never a silent pass.
|
|
15
|
+
//
|
|
16
|
+
// PHASES:
|
|
17
|
+
// --set-version X.Y.Z Mechanically rewrite every version surface, then
|
|
18
|
+
// sync package-lock.json via `npm install
|
|
19
|
+
// --package-lock-only`. Editorial surfaces (CHANGELOG
|
|
20
|
+
// entry, README highlights prose) are NOT written —
|
|
21
|
+
// they are enforced by --check instead.
|
|
22
|
+
// --check Preflight: surface parity, CHANGELOG entry present
|
|
23
|
+
// + Unreleased folded, tag collision (local, origin,
|
|
24
|
+
// github), npm registry collision, CI green on HEAD,
|
|
25
|
+
// leakage gate over `npm pack --dry-run`.
|
|
26
|
+
// --publish Runs --check first, then: token publish via temp
|
|
27
|
+
// userconfig (NPM_TOKEN from .env.local), registry
|
|
28
|
+
// verify, annotated tag AFTER successful publish
|
|
29
|
+
// (never before — eliminates "tagged but unpublished"),
|
|
30
|
+
// push main + tag to origin AND the github mirror,
|
|
31
|
+
// then print the post-release checklist (site deploy,
|
|
32
|
+
// token rotation).
|
|
33
|
+
//
|
|
34
|
+
// USAGE:
|
|
35
|
+
// node scripts/release.mjs --check [--json]
|
|
36
|
+
// node scripts/release.mjs --set-version 3.19.0
|
|
37
|
+
// node scripts/release.mjs --publish [--json]
|
|
38
|
+
//
|
|
39
|
+
// EXIT CODES:
|
|
40
|
+
// 0 success
|
|
41
|
+
// 1 check failure (stale surface, missing CHANGELOG entry, tag/registry
|
|
42
|
+
// collision, CI not green, leakage-gate hit)
|
|
43
|
+
// 2 system/usage error (git/npm spawn failure, missing NPM_TOKEN,
|
|
44
|
+
// unknown flag)
|
|
45
|
+
//
|
|
46
|
+
// SECURITY INVARIANTS (from skills/npm-publish/SKILL.md):
|
|
47
|
+
// - NPM_TOKEN only from gitignored .env.local; never logged, never persisted.
|
|
48
|
+
// - Temp userconfig chmod 600, removed in a finally block.
|
|
49
|
+
// - Leakage gate runs before EVERY publish, not only the first.
|
|
50
|
+
|
|
51
|
+
import {
|
|
52
|
+
readFileSync,
|
|
53
|
+
writeFileSync,
|
|
54
|
+
existsSync,
|
|
55
|
+
mkdtempSync,
|
|
56
|
+
chmodSync,
|
|
57
|
+
rmSync,
|
|
58
|
+
realpathSync,
|
|
59
|
+
} from 'node:fs';
|
|
60
|
+
import { join } from 'node:path';
|
|
61
|
+
import { tmpdir } from 'node:os';
|
|
62
|
+
import { spawnSync } from 'node:child_process';
|
|
63
|
+
import { parseArgs } from 'node:util';
|
|
64
|
+
import { fileURLToPath } from 'node:url';
|
|
65
|
+
|
|
66
|
+
const PACKAGE_NAME = 'session-orchestrator';
|
|
67
|
+
const SPAWN_OPTS = { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 };
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Surfaces table — the SSOT both the scan and the rewrite share.
|
|
71
|
+
//
|
|
72
|
+
// Every entry: { file, patterns: [RegExp] }. Each pattern has exactly one
|
|
73
|
+
// capture group holding the version. Matching ZERO occurrences is a hard
|
|
74
|
+
// failure ("pattern-dead") — that is the guard against a surface silently
|
|
75
|
+
// falling out of the check after a file refactor. All captured versions must
|
|
76
|
+
// equal the target.
|
|
77
|
+
//
|
|
78
|
+
// CHANGELOG.md is deliberately NOT here: it carries version HISTORY, so a
|
|
79
|
+
// replace-all would corrupt it. It has its own editorial check below.
|
|
80
|
+
// package-lock.json is also special-cased (thousands of dep "version" keys).
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
export const SURFACES = [
|
|
83
|
+
{
|
|
84
|
+
file: 'package.json',
|
|
85
|
+
patterns: [/"version":\s*"(\d+\.\d+\.\d+)"/],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
file: '.claude-plugin/plugin.json',
|
|
89
|
+
patterns: [/"version":\s*"(\d+\.\d+\.\d+)"/],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
file: '.claude-plugin/marketplace.json',
|
|
93
|
+
patterns: [/"version":\s*"(\d+\.\d+\.\d+)"/g],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
// Codex manifest: version is '<base>+codex.<YYYYMMDDHHmmss>' (see
|
|
97
|
+
// scripts/lib/codex/plugin-contract.mjs). The base must equal the target;
|
|
98
|
+
// applyVersion additionally rotates the cachebuster timestamp. This
|
|
99
|
+
// surface was the first drift-sweep catch: a plain `rg` census missed it
|
|
100
|
+
// because ripgrep skips hidden directories by default — only `git grep`
|
|
101
|
+
// (and the validate-plugin base-version check) saw it.
|
|
102
|
+
file: '.codex-plugin/plugin.json',
|
|
103
|
+
patterns: [/"version":\s*"(\d+\.\d+\.\d+)\+codex\./],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
file: 'hooks/hooks.json',
|
|
107
|
+
patterns: [/Session Orchestrator v(\d+\.\d+\.\d+)/],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
file: 'hooks/hooks-codex.json',
|
|
111
|
+
patterns: [/Session Orchestrator v(\d+\.\d+\.\d+)/],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
file: 'README.md',
|
|
115
|
+
patterns: [
|
|
116
|
+
/version-(\d+\.\d+\.\d+)-blue\.svg/,
|
|
117
|
+
/^## Recent highlights \(v(\d+\.\d+\.\d+)\)/m,
|
|
118
|
+
/Highlights of the v(\d+\.\d+\.\d+) line:/,
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
file: 'site/index.html',
|
|
123
|
+
patterns: [/"softwareVersion":\s*"(\d+\.\d+\.\d+)"/, /v(\d+\.\d+\.\d+)\b/g],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
file: 'site/llms.txt',
|
|
127
|
+
patterns: [/Version:\s*(\d+\.\d+\.\d+)/],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
file: 'site/llms-full.txt',
|
|
131
|
+
patterns: [/Version\s+(\d+\.\d+\.\d+)/g],
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Scan every surface against the target version.
|
|
137
|
+
* Pure over the filesystem — no git/network. Returns one row per surface:
|
|
138
|
+
* { file, ok, problems: string[] }.
|
|
139
|
+
*/
|
|
140
|
+
export function scanSurfaces(repoRoot, target) {
|
|
141
|
+
const rows = [];
|
|
142
|
+
for (const surface of SURFACES) {
|
|
143
|
+
const abs = join(repoRoot, surface.file);
|
|
144
|
+
const problems = [];
|
|
145
|
+
if (!existsSync(abs)) {
|
|
146
|
+
rows.push({ file: surface.file, ok: false, problems: ['file missing'] });
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const text = readFileSync(abs, 'utf8');
|
|
150
|
+
for (const pattern of surface.patterns) {
|
|
151
|
+
const re = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g');
|
|
152
|
+
const found = [...text.matchAll(re)].map((m) => m[1]);
|
|
153
|
+
if (found.length === 0) {
|
|
154
|
+
problems.push(`pattern-dead: /${pattern.source}/ matches nothing`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const stale = found.filter((v) => v !== target);
|
|
158
|
+
if (stale.length > 0) {
|
|
159
|
+
problems.push(`/${pattern.source}/ found ${stale.join(', ')} (want ${target})`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
rows.push({ file: surface.file, ok: problems.length === 0, problems });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// package-lock.json — parse, don't pattern-match (dep versions everywhere).
|
|
166
|
+
const lockPath = join(repoRoot, 'package-lock.json');
|
|
167
|
+
if (!existsSync(lockPath)) {
|
|
168
|
+
rows.push({ file: 'package-lock.json', ok: false, problems: ['file missing'] });
|
|
169
|
+
} else {
|
|
170
|
+
const problems = [];
|
|
171
|
+
try {
|
|
172
|
+
const lock = JSON.parse(readFileSync(lockPath, 'utf8'));
|
|
173
|
+
if (lock.version !== target) problems.push(`root version is ${lock.version} (want ${target})`);
|
|
174
|
+
const rootPkg = lock.packages?.[''];
|
|
175
|
+
if (rootPkg && rootPkg.version !== target) {
|
|
176
|
+
problems.push(`packages[""].version is ${rootPkg.version} (want ${target})`);
|
|
177
|
+
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
problems.push(`unparseable: ${err.message}`);
|
|
180
|
+
}
|
|
181
|
+
rows.push({ file: 'package-lock.json', ok: problems.length === 0, problems });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return rows;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Mechanically rewrite every surface to the target version by replacing the
|
|
189
|
+
* captured version in each pattern match. Idempotent. Does NOT touch
|
|
190
|
+
* CHANGELOG.md or package-lock.json (the caller syncs the lock via npm).
|
|
191
|
+
* Returns the list of files actually changed.
|
|
192
|
+
*/
|
|
193
|
+
export function applyVersion(repoRoot, target) {
|
|
194
|
+
const changed = [];
|
|
195
|
+
for (const surface of SURFACES) {
|
|
196
|
+
const abs = join(repoRoot, surface.file);
|
|
197
|
+
if (!existsSync(abs)) continue;
|
|
198
|
+
const before = readFileSync(abs, 'utf8');
|
|
199
|
+
let after = before;
|
|
200
|
+
for (const pattern of surface.patterns) {
|
|
201
|
+
const re = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g');
|
|
202
|
+
after = after.replace(re, (full, captured) => full.replace(captured, target));
|
|
203
|
+
}
|
|
204
|
+
if (surface.file === '.codex-plugin/plugin.json') {
|
|
205
|
+
const stamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
|
|
206
|
+
after = after.replace(/(\+codex\.)\d{14}/, `$1${stamp}`);
|
|
207
|
+
}
|
|
208
|
+
if (after !== before) {
|
|
209
|
+
writeFileSync(abs, after);
|
|
210
|
+
changed.push(surface.file);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return changed;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Editorial gate: the CHANGELOG must carry a dated entry for the target as
|
|
218
|
+
* its topmost release, and [Unreleased] must be folded (empty) — an
|
|
219
|
+
* Unreleased section with content means the release notes are incomplete.
|
|
220
|
+
*/
|
|
221
|
+
export function checkChangelogEntry(text, target) {
|
|
222
|
+
const problems = [];
|
|
223
|
+
const entryRe = new RegExp(`^## \\[${target.replace(/\./g, '\\.')}\\] - \\d{4}-\\d{2}-\\d{2}`, 'm');
|
|
224
|
+
if (!entryRe.test(text)) {
|
|
225
|
+
problems.push(`no "## [${target}] - YYYY-MM-DD" entry`);
|
|
226
|
+
}
|
|
227
|
+
const headings = [...text.matchAll(/^## \[([^\]]+)\]/gm)].map((m) => m[1]);
|
|
228
|
+
const firstRelease = headings.find((h) => h.toLowerCase() !== 'unreleased');
|
|
229
|
+
if (firstRelease && firstRelease !== target) {
|
|
230
|
+
problems.push(`topmost release entry is [${firstRelease}], not [${target}]`);
|
|
231
|
+
}
|
|
232
|
+
const unreleasedMatch = text.match(/^## \[Unreleased\]([\s\S]*?)(?=^## \[|$(?![\s\S]))/m);
|
|
233
|
+
if (unreleasedMatch && unreleasedMatch[1].trim() !== '') {
|
|
234
|
+
problems.push('[Unreleased] still has content — fold it into the release entry');
|
|
235
|
+
}
|
|
236
|
+
return { ok: problems.length === 0, problems };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// The seven leakage patterns from skills/npm-publish/SKILL.md, applied to
|
|
240
|
+
// `npm pack --dry-run` output lines. Any hit blocks the publish.
|
|
241
|
+
export const LEAKAGE_PATTERNS = [
|
|
242
|
+
{ name: 'tests/', re: /npm notice.* tests\// },
|
|
243
|
+
{ name: '.orchestrator/', re: /npm notice.*\.orchestrator\// },
|
|
244
|
+
{ name: '.claude/', re: /npm notice.*\s\.claude\// },
|
|
245
|
+
{ name: '.github/', re: /npm notice.*\.github\// },
|
|
246
|
+
{ name: 'node_modules', re: /node_modules/ },
|
|
247
|
+
{ name: '.env', re: /npm notice.*\.env/i },
|
|
248
|
+
{ name: 'owner.yaml', re: /owner\.yaml/i },
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
/** Pure check over pack-output lines. Returns violations: {name, line}[]. */
|
|
252
|
+
export function checkLeakage(lines) {
|
|
253
|
+
const violations = [];
|
|
254
|
+
for (const line of lines) {
|
|
255
|
+
for (const { name, re } of LEAKAGE_PATTERNS) {
|
|
256
|
+
if (re.test(line)) violations.push({ name, line: line.trim() });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return violations;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
// Impure orchestration below — git/npm/network. Not unit-tested; exercised
|
|
264
|
+
// by the release runs themselves.
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
function run(cmd, args, opts = {}) {
|
|
268
|
+
const res = spawnSync(cmd, args, { ...SPAWN_OPTS, ...opts });
|
|
269
|
+
if (res.error) throw new Error(`${cmd} ${args.join(' ')}: ${res.error.message}`);
|
|
270
|
+
return res;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function mustRun(cmd, args, opts = {}) {
|
|
274
|
+
const res = run(cmd, args, opts);
|
|
275
|
+
if (res.status !== 0) {
|
|
276
|
+
throw new Error(`${cmd} ${args.join(' ')} exited ${res.status}: ${(res.stderr || res.stdout || '').slice(0, 500)}`);
|
|
277
|
+
}
|
|
278
|
+
return res;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function readPackageVersion(repoRoot) {
|
|
282
|
+
return JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')).version;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function preflight(repoRoot, target, { skipCi = false } = {}) {
|
|
286
|
+
const checks = [];
|
|
287
|
+
const add = (name, ok, detail = '') => checks.push({ name, ok, detail });
|
|
288
|
+
|
|
289
|
+
// 1. Git state: on main, clean tree, HEAD pushed.
|
|
290
|
+
const branch = run('git', ['branch', '--show-current'], { cwd: repoRoot }).stdout.trim();
|
|
291
|
+
add('branch-is-main', branch === 'main', branch);
|
|
292
|
+
const dirty = run('git', ['status', '--porcelain'], { cwd: repoRoot }).stdout.trim();
|
|
293
|
+
add('working-tree-clean', dirty === '', dirty ? `${dirty.split('\n').length} dirty path(s)` : '');
|
|
294
|
+
run('git', ['fetch', 'origin', 'main', '--quiet'], { cwd: repoRoot });
|
|
295
|
+
const head = run('git', ['rev-parse', 'HEAD'], { cwd: repoRoot }).stdout.trim();
|
|
296
|
+
const originMain = run('git', ['rev-parse', 'origin/main'], { cwd: repoRoot }).stdout.trim();
|
|
297
|
+
add('head-pushed', head === originMain, head === originMain ? head.slice(0, 8) : `HEAD ${head.slice(0, 8)} != origin/main ${originMain.slice(0, 8)}`);
|
|
298
|
+
|
|
299
|
+
// 2. Surface parity.
|
|
300
|
+
const surfaceRows = scanSurfaces(repoRoot, target);
|
|
301
|
+
for (const row of surfaceRows) {
|
|
302
|
+
add(`surface:${row.file}`, row.ok, row.problems.join('; '));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 3. CHANGELOG editorial gate.
|
|
306
|
+
const changelog = checkChangelogEntry(readFileSync(join(repoRoot, 'CHANGELOG.md'), 'utf8'), target);
|
|
307
|
+
add('changelog-entry', changelog.ok, changelog.problems.join('; '));
|
|
308
|
+
|
|
309
|
+
// 3b. Drift sweep: no TRACKED file outside the surfaces table + allowlist
|
|
310
|
+
// may still carry the previous release's version literal. `git grep` (not
|
|
311
|
+
// rg) on purpose — it searches every tracked file including hidden
|
|
312
|
+
// directories, which is exactly how the forgotten .codex-plugin manifest
|
|
313
|
+
// was invisible to a plain rg census. Allowlisted: files that legitimately
|
|
314
|
+
// carry version HISTORY.
|
|
315
|
+
const prevTag = run('git', ['tag', '-l', 'v*', '--sort=-v:refname'], { cwd: repoRoot })
|
|
316
|
+
.stdout.split('\n').map((t) => t.trim().replace(/^v/, ''))
|
|
317
|
+
.filter((t) => /^\d+\.\d+\.\d+$/.test(t) && t !== target)[0];
|
|
318
|
+
if (prevTag) {
|
|
319
|
+
const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|docs\/|tests\/|skills\/npm-publish\/|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json)/;
|
|
320
|
+
const grep = run('git', ['grep', '-l', '--fixed-strings', prevTag, '--', '.'], { cwd: repoRoot });
|
|
321
|
+
const hits = grep.stdout.split('\n').filter(Boolean).filter((f) => !HISTORY_ALLOWLIST.test(f));
|
|
322
|
+
add('drift-sweep', hits.length === 0, hits.length ? `still carry ${prevTag}: ${hits.slice(0, 5).join(', ')}` : `no tracked file outside the allowlist still carries ${prevTag}`);
|
|
323
|
+
} else {
|
|
324
|
+
add('drift-sweep', true, 'no previous tag to sweep against');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 4. Tag collision — local, origin, github mirror.
|
|
328
|
+
const tag = `v${target}`;
|
|
329
|
+
const localTag = run('git', ['tag', '-l', tag], { cwd: repoRoot }).stdout.trim();
|
|
330
|
+
add('tag-free-local', localTag === '', localTag && `${tag} already exists locally`);
|
|
331
|
+
for (const remote of ['origin', 'github']) {
|
|
332
|
+
const ls = run('git', ['ls-remote', '--tags', remote, `refs/tags/${tag}`], { cwd: repoRoot });
|
|
333
|
+
const collision = ls.status === 0 && ls.stdout.trim() !== '';
|
|
334
|
+
add(`tag-free-${remote}`, ls.status === 0 && !collision, collision ? `${tag} already on ${remote}` : ls.status !== 0 ? `ls-remote ${remote} failed` : '');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 5. npm registry collision (E404 = name free = fine for a first publish).
|
|
338
|
+
const view = run('npm', ['view', PACKAGE_NAME, 'versions', '--json'], { cwd: repoRoot });
|
|
339
|
+
if (view.status === 0) {
|
|
340
|
+
let published = [];
|
|
341
|
+
try {
|
|
342
|
+
const parsed = JSON.parse(view.stdout);
|
|
343
|
+
published = Array.isArray(parsed) ? parsed : [parsed];
|
|
344
|
+
} catch {
|
|
345
|
+
/* unparseable view output → treat as unknown, fail below */
|
|
346
|
+
}
|
|
347
|
+
add('registry-version-free', !published.includes(target), published.includes(target) ? `${target} already published` : `latest: ${published[published.length - 1] ?? '?'}`);
|
|
348
|
+
} else {
|
|
349
|
+
add('registry-version-free', /E404/.test(view.stderr || ''), /E404/.test(view.stderr || '') ? 'package not yet on registry (first publish)' : `npm view failed: ${(view.stderr || '').slice(0, 200)}`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// 6. CI green on HEAD (the repo's iron session-start rule applies to
|
|
353
|
+
// releases doubly: local green is not evidence — see .claude/rules).
|
|
354
|
+
if (skipCi) {
|
|
355
|
+
add('ci-green-on-head', true, 'SKIPPED via --skip-ci');
|
|
356
|
+
} else {
|
|
357
|
+
const { checkCiStatus } = await import('./lib/ci-status-banner.mjs');
|
|
358
|
+
const ci = await checkCiStatus({ repoRoot, timeoutMs: 15000 });
|
|
359
|
+
const green = ci !== null && ci.status === 'green';
|
|
360
|
+
add('ci-green-on-head', green, ci === null ? 'CI status unavailable' : `status: ${ci.status}${ci.failingJobName ? ` (${ci.failingJobName})` : ''}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// 7. Leakage gate over the actual pack file list.
|
|
364
|
+
const pack = run('npm', ['pack', '--dry-run'], { cwd: repoRoot });
|
|
365
|
+
const lines = `${pack.stdout}\n${pack.stderr}`.split('\n');
|
|
366
|
+
const violations = checkLeakage(lines);
|
|
367
|
+
add('leakage-gate', pack.status === 0 && violations.length === 0, violations.length ? violations.map((v) => `${v.name}: ${v.line}`).slice(0, 5).join(' | ') : pack.status !== 0 ? 'npm pack failed' : `${lines.filter((l) => /npm notice.*[0-9]+B /.test(l)).length} packed entries, 0 leaks`);
|
|
368
|
+
|
|
369
|
+
return checks;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function changelogExcerpt(repoRoot, target) {
|
|
373
|
+
const text = readFileSync(join(repoRoot, 'CHANGELOG.md'), 'utf8');
|
|
374
|
+
const re = new RegExp(`^## \\[${target.replace(/\./g, '\\.')}\\][^\\n]*\\n([\\s\\S]*?)(?=^## \\[|$(?![\\s\\S]))`, 'm');
|
|
375
|
+
const m = text.match(re);
|
|
376
|
+
return m ? m[1].trim().split('\n').slice(0, 40).join('\n') : '';
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function publish(repoRoot, target) {
|
|
380
|
+
// Token: only from gitignored .env.local (verify the ignore before reading).
|
|
381
|
+
const ignored = run('git', ['check-ignore', '.env.local'], { cwd: repoRoot });
|
|
382
|
+
if (ignored.status !== 0) throw new Error('.env.local is NOT gitignored — refusing to read a token from it');
|
|
383
|
+
const envLocal = readFileSync(join(repoRoot, '.env.local'), 'utf8');
|
|
384
|
+
const tokenMatch = envLocal.match(/^NPM_TOKEN=(.+)$/m);
|
|
385
|
+
if (!tokenMatch) throw new Error('NPM_TOKEN not found in .env.local');
|
|
386
|
+
const token = tokenMatch[1].trim();
|
|
387
|
+
|
|
388
|
+
const tmpDir = mkdtempSync(join(tmpdir(), 'release-npmrc-'));
|
|
389
|
+
const tmpRc = join(tmpDir, 'npmrc');
|
|
390
|
+
try {
|
|
391
|
+
writeFileSync(tmpRc, `//registry.npmjs.org/:_authToken=${token}\n`);
|
|
392
|
+
chmodSync(tmpRc, 0o600);
|
|
393
|
+
const res = run('npm', ['publish', '--access', 'public', '--userconfig', tmpRc], { cwd: repoRoot });
|
|
394
|
+
const out = `${res.stdout}\n${res.stderr}`;
|
|
395
|
+
if (res.status !== 0 || !out.includes(`+ ${PACKAGE_NAME}@${target}`)) {
|
|
396
|
+
// Never echo the raw output wholesale into logs beyond the error slice —
|
|
397
|
+
// it cannot contain the token (npm masks userconfig), but stay frugal.
|
|
398
|
+
throw new Error(`npm publish failed (exit ${res.status}): ${out.slice(0, 800)}`);
|
|
399
|
+
}
|
|
400
|
+
} finally {
|
|
401
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Registry verify with propagation retries.
|
|
405
|
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
406
|
+
const view = run('npm', ['view', PACKAGE_NAME, 'version'], { cwd: repoRoot });
|
|
407
|
+
if (view.status === 0 && view.stdout.trim() === target) return;
|
|
408
|
+
if (attempt < 5) spawnSync('sleep', ['3']);
|
|
409
|
+
}
|
|
410
|
+
throw new Error(`registry verify failed: npm view does not report ${target} after 5 attempts`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function tagAndPush(repoRoot, target) {
|
|
414
|
+
const tag = `v${target}`;
|
|
415
|
+
const excerpt = changelogExcerpt(repoRoot, target);
|
|
416
|
+
const msgDir = mkdtempSync(join(tmpdir(), 'release-tagmsg-'));
|
|
417
|
+
const msgFile = join(msgDir, 'msg');
|
|
418
|
+
try {
|
|
419
|
+
writeFileSync(msgFile, `${tag}\n\n${excerpt}\n`);
|
|
420
|
+
mustRun('git', ['tag', '-a', tag, '-F', msgFile], { cwd: repoRoot });
|
|
421
|
+
} finally {
|
|
422
|
+
rmSync(msgDir, { recursive: true, force: true });
|
|
423
|
+
}
|
|
424
|
+
const pushed = [];
|
|
425
|
+
for (const remote of ['origin', 'github']) {
|
|
426
|
+
mustRun('git', ['push', remote, 'main'], { cwd: repoRoot });
|
|
427
|
+
mustRun('git', ['push', remote, tag], { cwd: repoRoot });
|
|
428
|
+
pushed.push(remote);
|
|
429
|
+
}
|
|
430
|
+
return { tag, pushed };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function printChecks(checks, asJson, version) {
|
|
434
|
+
const ok = checks.every((c) => c.ok);
|
|
435
|
+
if (asJson) {
|
|
436
|
+
console.log(JSON.stringify({ ok, version, checks }, null, 2));
|
|
437
|
+
} else {
|
|
438
|
+
for (const c of checks) {
|
|
439
|
+
console.log(`${c.ok ? ' ok ' : 'FAIL '} ${c.name}${c.detail ? ` — ${c.detail}` : ''}`);
|
|
440
|
+
}
|
|
441
|
+
console.log(ok ? `\nAll ${checks.length} checks green for v${version}.` : `\n${checks.filter((c) => !c.ok).length} of ${checks.length} checks FAILED for v${version}.`);
|
|
442
|
+
}
|
|
443
|
+
return ok;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async function main() {
|
|
447
|
+
const { values } = parseArgs({
|
|
448
|
+
options: {
|
|
449
|
+
'set-version': { type: 'string' },
|
|
450
|
+
check: { type: 'boolean', default: false },
|
|
451
|
+
publish: { type: 'boolean', default: false },
|
|
452
|
+
'skip-ci': { type: 'boolean', default: false },
|
|
453
|
+
json: { type: 'boolean', default: false },
|
|
454
|
+
help: { type: 'boolean', default: false },
|
|
455
|
+
version: { type: 'boolean', default: false },
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
if (values.help) {
|
|
460
|
+
console.log('Usage: node scripts/release.mjs [--set-version X.Y.Z | --check | --publish] [--skip-ci] [--json]');
|
|
461
|
+
console.log('Release als ein Dispatch: surface sync, preflight checks, token publish, tag AFTER publish.');
|
|
462
|
+
console.log('Exit codes: 0 success, 1 check failure, 2 system/usage error.');
|
|
463
|
+
return 0;
|
|
464
|
+
}
|
|
465
|
+
if (values.version) {
|
|
466
|
+
console.log(readPackageVersion(repoRootOf()));
|
|
467
|
+
return 0;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const repoRoot = repoRootOf();
|
|
471
|
+
|
|
472
|
+
if (values['set-version']) {
|
|
473
|
+
const target = values['set-version'];
|
|
474
|
+
if (!/^\d+\.\d+\.\d+$/.test(target)) {
|
|
475
|
+
console.error(`invalid version: ${target}`);
|
|
476
|
+
return 2;
|
|
477
|
+
}
|
|
478
|
+
const changed = applyVersion(repoRoot, target);
|
|
479
|
+
mustRun('npm', ['install', '--package-lock-only', '--ignore-scripts', '--no-audit', '--no-fund'], { cwd: repoRoot });
|
|
480
|
+
console.log(`Rewrote ${changed.length} surface file(s) to ${target}:`);
|
|
481
|
+
for (const f of changed) console.log(` ${f}`);
|
|
482
|
+
console.log(' package-lock.json (via npm install --package-lock-only)');
|
|
483
|
+
console.log('\nEditorial TODOs (enforced by --check):');
|
|
484
|
+
console.log(` 1. CHANGELOG.md — write the "## [${target}] - YYYY-MM-DD" entry, fold [Unreleased].`);
|
|
485
|
+
console.log(' 2. README.md — rewrite the "Recent highlights" section content.');
|
|
486
|
+
return 0;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (values.check || values.publish) {
|
|
490
|
+
const target = readPackageVersion(repoRoot);
|
|
491
|
+
const checks = await preflight(repoRoot, target, { skipCi: values['skip-ci'] });
|
|
492
|
+
const ok = printChecks(checks, values.json && !values.publish, target);
|
|
493
|
+
if (!ok) return 1;
|
|
494
|
+
if (!values.publish) return 0;
|
|
495
|
+
|
|
496
|
+
console.log(`\nPublishing ${PACKAGE_NAME}@${target} ...`);
|
|
497
|
+
publish(repoRoot, target);
|
|
498
|
+
console.log(` + ${PACKAGE_NAME}@${target} — registry verified.`);
|
|
499
|
+
const { tag, pushed } = tagAndPush(repoRoot, target);
|
|
500
|
+
console.log(` tagged ${tag} (AFTER publish) and pushed main+tag to: ${pushed.join(', ')}.`);
|
|
501
|
+
console.log('\nPost-release checklist (manual):');
|
|
502
|
+
console.log(' 1. Site deploy: cd site && vercel --prod');
|
|
503
|
+
console.log(' 2. Rotate/delete the npm token: https://www.npmjs.com/settings/<user>/tokens');
|
|
504
|
+
console.log(' 3. pi.dev gallery indexes asynchronously — do not block on it.');
|
|
505
|
+
return 0;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
console.error('Nothing to do — pass --check, --publish, or --set-version X.Y.Z (see --help).');
|
|
509
|
+
return 2;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function repoRootOf() {
|
|
513
|
+
const res = spawnSync('git', ['rev-parse', '--show-toplevel'], SPAWN_OPTS);
|
|
514
|
+
if (res.status !== 0) throw new Error('not inside a git repository');
|
|
515
|
+
return res.stdout.trim();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const isMain = (() => {
|
|
519
|
+
try {
|
|
520
|
+
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
521
|
+
} catch {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
})();
|
|
525
|
+
|
|
526
|
+
if (isMain) {
|
|
527
|
+
main().then(
|
|
528
|
+
(code) => process.exit(code),
|
|
529
|
+
(err) => {
|
|
530
|
+
console.error(`release.mjs: ${err.message}`);
|
|
531
|
+
process.exit(2);
|
|
532
|
+
},
|
|
533
|
+
);
|
|
534
|
+
}
|