@lifeaitools/clauth 1.30.2 → 1.30.3
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/.clauth-skill/SKILL.md +111 -111
- package/cli/api.classify.test.js +75 -75
- package/cli/assets/codevelop/launcher-active.cmd.template +20 -20
- package/cli/assets/codevelop/launcher-static.cmd.template +7 -7
- package/cli/assets/codevelop/windows-terminal.profiles.json +48 -48
- package/cli/assets/watchdog.ps1 +42 -42
- package/cli/commands/agent-cron.js +396 -396
- package/cli/commands/codevelop.js +1190 -1190
- package/cli/commands/doctor.js +302 -302
- package/cli/commands/install.js +10 -10
- package/cli/commands/invite.js +175 -175
- package/cli/commands/join.js +179 -179
- package/cli/commands/npm.js +182 -182
- package/cli/commands/scrub.js +327 -327
- package/cli/commands/scrub.test.js +115 -115
- package/cli/commands/serve/tools/fs.js +1055 -1055
- package/cli/commands/watchdog.js +209 -209
- package/cli/conf-path.js +21 -21
- package/cli/index.js +1089 -1089
- package/cli/lib/fs-git.js +282 -282
- package/cli/recovery.js +101 -101
- package/cli/studio-debug.js +1095 -1087
- package/cli/watchdog-registry.js +209 -209
- package/cli/watchdog-registry.test.js +89 -89
- package/install.ps1 +21 -21
- package/package.json +68 -68
- package/supabase/migrations/001_clauth_schema.sql +12 -12
- package/supabase/migrations/003_clauth_config.sql +13 -13
- package/supabase/migrations/003_machine_enrollments.sql +39 -39
package/cli/commands/npm.js
CHANGED
|
@@ -1,182 +1,182 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { spawnSync } from "child_process";
|
|
5
|
-
|
|
6
|
-
const CLAUTH_NPM_URL = "http://127.0.0.1:52437/v/npm";
|
|
7
|
-
|
|
8
|
-
function run(cmd, args, opts = {}) {
|
|
9
|
-
const useCmd = process.platform === "win32" && ["npm", "gh"].includes(cmd);
|
|
10
|
-
const executable = useCmd ? "cmd.exe" : cmd;
|
|
11
|
-
const finalArgs = useCmd ? ["/d", "/s", "/c", cmd, ...args] : args;
|
|
12
|
-
const result = spawnSync(executable, finalArgs, {
|
|
13
|
-
encoding: "utf8",
|
|
14
|
-
...opts,
|
|
15
|
-
env: { ...process.env, ...(opts.env || {}) }
|
|
16
|
-
});
|
|
17
|
-
if (result.error) throw result.error;
|
|
18
|
-
return result;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function redactNpmTokenList(text) {
|
|
22
|
-
return String(text || "").replace(/npm_[A-Za-z0-9]+/g, token => `${token.slice(0, 9)}...${token.slice(-4)}`);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function fetchNpmToken() {
|
|
26
|
-
const response = await fetch(CLAUTH_NPM_URL);
|
|
27
|
-
if (!response.ok) throw new Error(`failed to fetch npm token from clauth daemon: HTTP ${response.status}`);
|
|
28
|
-
const token = (await response.text()).trim();
|
|
29
|
-
if (!token) throw new Error("clauth npm token is empty");
|
|
30
|
-
if (!token.startsWith("npm_")) throw new Error("clauth npm token does not look like an npm token");
|
|
31
|
-
return token;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function withNpmAuth(token, fn) {
|
|
35
|
-
const npmrc = path.join(os.tmpdir(), `clauth-npm-${process.pid}-${Date.now()}.npmrc`);
|
|
36
|
-
try {
|
|
37
|
-
fs.writeFileSync(npmrc, `//registry.npmjs.org/:_authToken=${token}`, { encoding: "utf8", mode: 0o600 });
|
|
38
|
-
return fn(npmrc);
|
|
39
|
-
} finally {
|
|
40
|
-
try { fs.rmSync(npmrc, { force: true }); } catch {}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function runNpmWithToken(token, args) {
|
|
45
|
-
return withNpmAuth(token, npmrc => run("npm", ["--userconfig", npmrc, ...args]));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function printResult(result, { redact = true } = {}) {
|
|
49
|
-
const stdout = redact ? redactNpmTokenList(result.stdout) : result.stdout;
|
|
50
|
-
const stderr = redact ? redactNpmTokenList(result.stderr) : result.stderr;
|
|
51
|
-
if (stdout) process.stdout.write(stdout);
|
|
52
|
-
if (stderr) process.stderr.write(stderr);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function usage() {
|
|
56
|
-
console.log(`clauth npm <action>
|
|
57
|
-
|
|
58
|
-
Actions:
|
|
59
|
-
whoami Verify the clauth npm token identity
|
|
60
|
-
tokens List npm token metadata with token strings redacted
|
|
61
|
-
set-local Write the clauth npm token to the user npm config
|
|
62
|
-
sync-github-secret <repo> Set repo secret NPM_TOKEN from clauth, e.g. LIFEAI/rdc-skills
|
|
63
|
-
rerun <run-id> --repo <repo> Rerun a failed GitHub Actions workflow
|
|
64
|
-
`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export async function runNpm(action = "help", opts = {}) {
|
|
68
|
-
if (action === "help") {
|
|
69
|
-
usage();
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const token = await fetchNpmToken();
|
|
74
|
-
|
|
75
|
-
if (action === "whoami") {
|
|
76
|
-
const result = runNpmWithToken(token, ["whoami", "--registry=https://registry.npmjs.org/"]);
|
|
77
|
-
printResult(result);
|
|
78
|
-
if (result.status !== 0) process.exitCode = result.status;
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (action === "tokens") {
|
|
83
|
-
const result = runNpmWithToken(token, ["token", "list", "--json", "--registry=https://registry.npmjs.org/"]);
|
|
84
|
-
printResult(result);
|
|
85
|
-
if (result.status !== 0) process.exitCode = result.status;
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (action === "set-local") {
|
|
90
|
-
const result = run("npm", ["config", "set", "//registry.npmjs.org/:_authToken", token, "--location=user"]);
|
|
91
|
-
if (result.status !== 0) {
|
|
92
|
-
printResult(result);
|
|
93
|
-
process.exitCode = result.status;
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
console.log("local npm auth updated from clauth service 'npm'");
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (action === "sync-github-secret") {
|
|
101
|
-
const repo = opts.repo || opts.args?.[0];
|
|
102
|
-
if (!repo) throw new Error("repo is required, e.g. clauth npm sync-github-secret LIFEAI/rdc-skills");
|
|
103
|
-
const result = run("gh", ["secret", "set", "NPM_TOKEN", "--repo", repo], { input: token });
|
|
104
|
-
printResult(result);
|
|
105
|
-
if (result.status !== 0) process.exitCode = result.status;
|
|
106
|
-
else console.log(`GitHub secret NPM_TOKEN updated for ${repo}`);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (action === "rerun") {
|
|
111
|
-
const runId = opts.args?.[0];
|
|
112
|
-
const repo = opts.repo;
|
|
113
|
-
if (!runId || !repo) throw new Error("usage: clauth npm rerun <run-id> --repo LIFEAI/rdc-skills");
|
|
114
|
-
const result = run("gh", ["run", "rerun", runId, "--repo", repo, "--failed"]);
|
|
115
|
-
printResult(result);
|
|
116
|
-
if (result.status !== 0) process.exitCode = result.status;
|
|
117
|
-
else console.log(`rerun requested for ${repo} run ${runId}`);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
usage();
|
|
122
|
-
throw new Error(`unknown clauth npm action: ${action}`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ── Guarded publish ──────────────────────────────────────────────────────────
|
|
126
|
-
// Generic, package-agnostic publish that REFUSES to publish unless the package
|
|
127
|
-
// is already committed and pushed to GitHub — so the npm tarball can never be
|
|
128
|
-
// built from uncommitted "dev" code that isn't on the repo. Works for standalone
|
|
129
|
-
// repos and monorepo subpackages (clean-check is scoped to the package's dir).
|
|
130
|
-
export async function runPublish(target, opts = {}) {
|
|
131
|
-
const pkgDir = path.resolve(target || process.cwd());
|
|
132
|
-
const pkgJsonPath = path.join(pkgDir, "package.json");
|
|
133
|
-
if (!fs.existsSync(pkgJsonPath)) throw new Error(`No package.json found at ${pkgDir}`);
|
|
134
|
-
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
|
|
135
|
-
if (!pkg.name || !pkg.version) throw new Error(`package.json at ${pkgDir} is missing name or version`);
|
|
136
|
-
if (pkg.private === true) throw new Error(`${pkg.name} is marked "private": true — refusing to publish`);
|
|
137
|
-
const access = opts.access || pkg.publishConfig?.access || (pkg.name.startsWith("@") ? "public" : undefined);
|
|
138
|
-
|
|
139
|
-
const gitRoot = run("git", ["rev-parse", "--show-toplevel"], { cwd: pkgDir }).stdout?.trim();
|
|
140
|
-
if (!gitRoot) throw new Error(`${pkgDir} is not inside a git repository`);
|
|
141
|
-
const rel = path.relative(gitRoot, pkgDir) || ".";
|
|
142
|
-
|
|
143
|
-
// Guard 1 — nothing uncommitted in the package (else we'd pack dev code).
|
|
144
|
-
const dirty = run("git", ["status", "--porcelain", "--", rel], { cwd: gitRoot }).stdout?.trim();
|
|
145
|
-
if (dirty && !opts.allowDirty) {
|
|
146
|
-
throw new Error(`Refusing to publish ${pkg.name}@${pkg.version}: uncommitted changes in ${rel} would be packed but are NOT on GitHub:\n${dirty}\n\nCommit + push first (or pass --allow-dirty to override).`);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Guard 2 — HEAD is on a remote (actually pushed to GitHub).
|
|
150
|
-
const head = run("git", ["rev-parse", "HEAD"], { cwd: gitRoot }).stdout?.trim();
|
|
151
|
-
const onRemote = run("git", ["branch", "-r", "--contains", head], { cwd: gitRoot }).stdout?.trim();
|
|
152
|
-
if (!onRemote && !opts.allowUnpushed) {
|
|
153
|
-
throw new Error(`Refusing to publish ${pkg.name}@${pkg.version}: HEAD ${head.slice(0, 9)} is not on any remote branch — push to GitHub first (or pass --allow-unpushed to override).`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Traceability — warn (don't block) if there's no v<version> tag at HEAD.
|
|
157
|
-
const tags = (run("git", ["tag", "--points-at", "HEAD"], { cwd: gitRoot }).stdout || "").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
158
|
-
if (!tags.includes(`v${pkg.version}`)) {
|
|
159
|
-
console.log(`⚠ No git tag v${pkg.version} at HEAD (traceability only). Tags here: ${tags.join(", ") || "none"}`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
console.log(`${opts.dryRun ? "[dry-run] " : ""}Publishing ${pkg.name}@${pkg.version} from ${rel} @ ${head.slice(0, 9)} (pushed${access ? `, access=${access}` : ""})`);
|
|
163
|
-
|
|
164
|
-
const token = await fetchNpmToken();
|
|
165
|
-
const args = ["publish"];
|
|
166
|
-
if (access) args.push("--access", access);
|
|
167
|
-
if (opts.dryRun) args.push("--dry-run");
|
|
168
|
-
const result = withNpmAuth(token, (npmrc) => run("npm", ["--userconfig", npmrc, ...args], { cwd: pkgDir }));
|
|
169
|
-
printResult(result, { redact: true });
|
|
170
|
-
if (result.status !== 0) { process.exitCode = result.status; throw new Error(`npm publish failed for ${pkg.name}`); }
|
|
171
|
-
if (opts.dryRun) { console.log("Dry run complete — nothing published."); return; }
|
|
172
|
-
|
|
173
|
-
// Verify on the registry directly (bypasses npm's local cache lag).
|
|
174
|
-
try {
|
|
175
|
-
const reg = await fetch(`https://registry.npmjs.org/${pkg.name}`);
|
|
176
|
-
const meta = await reg.json();
|
|
177
|
-
if (meta.versions?.[pkg.version]) console.log(`✓ Verified ${pkg.name}@${pkg.version} on the registry (latest: ${meta["dist-tags"]?.latest}).`);
|
|
178
|
-
else console.log(`⚠ ${pkg.name}@${pkg.version} not visible on the registry yet (propagation lag) — re-check shortly.`);
|
|
179
|
-
} catch (e) {
|
|
180
|
-
console.log(`(could not verify on registry: ${e.message})`);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
5
|
+
|
|
6
|
+
const CLAUTH_NPM_URL = "http://127.0.0.1:52437/v/npm";
|
|
7
|
+
|
|
8
|
+
function run(cmd, args, opts = {}) {
|
|
9
|
+
const useCmd = process.platform === "win32" && ["npm", "gh"].includes(cmd);
|
|
10
|
+
const executable = useCmd ? "cmd.exe" : cmd;
|
|
11
|
+
const finalArgs = useCmd ? ["/d", "/s", "/c", cmd, ...args] : args;
|
|
12
|
+
const result = spawnSync(executable, finalArgs, {
|
|
13
|
+
encoding: "utf8",
|
|
14
|
+
...opts,
|
|
15
|
+
env: { ...process.env, ...(opts.env || {}) }
|
|
16
|
+
});
|
|
17
|
+
if (result.error) throw result.error;
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function redactNpmTokenList(text) {
|
|
22
|
+
return String(text || "").replace(/npm_[A-Za-z0-9]+/g, token => `${token.slice(0, 9)}...${token.slice(-4)}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function fetchNpmToken() {
|
|
26
|
+
const response = await fetch(CLAUTH_NPM_URL);
|
|
27
|
+
if (!response.ok) throw new Error(`failed to fetch npm token from clauth daemon: HTTP ${response.status}`);
|
|
28
|
+
const token = (await response.text()).trim();
|
|
29
|
+
if (!token) throw new Error("clauth npm token is empty");
|
|
30
|
+
if (!token.startsWith("npm_")) throw new Error("clauth npm token does not look like an npm token");
|
|
31
|
+
return token;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function withNpmAuth(token, fn) {
|
|
35
|
+
const npmrc = path.join(os.tmpdir(), `clauth-npm-${process.pid}-${Date.now()}.npmrc`);
|
|
36
|
+
try {
|
|
37
|
+
fs.writeFileSync(npmrc, `//registry.npmjs.org/:_authToken=${token}`, { encoding: "utf8", mode: 0o600 });
|
|
38
|
+
return fn(npmrc);
|
|
39
|
+
} finally {
|
|
40
|
+
try { fs.rmSync(npmrc, { force: true }); } catch {}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function runNpmWithToken(token, args) {
|
|
45
|
+
return withNpmAuth(token, npmrc => run("npm", ["--userconfig", npmrc, ...args]));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function printResult(result, { redact = true } = {}) {
|
|
49
|
+
const stdout = redact ? redactNpmTokenList(result.stdout) : result.stdout;
|
|
50
|
+
const stderr = redact ? redactNpmTokenList(result.stderr) : result.stderr;
|
|
51
|
+
if (stdout) process.stdout.write(stdout);
|
|
52
|
+
if (stderr) process.stderr.write(stderr);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function usage() {
|
|
56
|
+
console.log(`clauth npm <action>
|
|
57
|
+
|
|
58
|
+
Actions:
|
|
59
|
+
whoami Verify the clauth npm token identity
|
|
60
|
+
tokens List npm token metadata with token strings redacted
|
|
61
|
+
set-local Write the clauth npm token to the user npm config
|
|
62
|
+
sync-github-secret <repo> Set repo secret NPM_TOKEN from clauth, e.g. LIFEAI/rdc-skills
|
|
63
|
+
rerun <run-id> --repo <repo> Rerun a failed GitHub Actions workflow
|
|
64
|
+
`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function runNpm(action = "help", opts = {}) {
|
|
68
|
+
if (action === "help") {
|
|
69
|
+
usage();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const token = await fetchNpmToken();
|
|
74
|
+
|
|
75
|
+
if (action === "whoami") {
|
|
76
|
+
const result = runNpmWithToken(token, ["whoami", "--registry=https://registry.npmjs.org/"]);
|
|
77
|
+
printResult(result);
|
|
78
|
+
if (result.status !== 0) process.exitCode = result.status;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (action === "tokens") {
|
|
83
|
+
const result = runNpmWithToken(token, ["token", "list", "--json", "--registry=https://registry.npmjs.org/"]);
|
|
84
|
+
printResult(result);
|
|
85
|
+
if (result.status !== 0) process.exitCode = result.status;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (action === "set-local") {
|
|
90
|
+
const result = run("npm", ["config", "set", "//registry.npmjs.org/:_authToken", token, "--location=user"]);
|
|
91
|
+
if (result.status !== 0) {
|
|
92
|
+
printResult(result);
|
|
93
|
+
process.exitCode = result.status;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
console.log("local npm auth updated from clauth service 'npm'");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (action === "sync-github-secret") {
|
|
101
|
+
const repo = opts.repo || opts.args?.[0];
|
|
102
|
+
if (!repo) throw new Error("repo is required, e.g. clauth npm sync-github-secret LIFEAI/rdc-skills");
|
|
103
|
+
const result = run("gh", ["secret", "set", "NPM_TOKEN", "--repo", repo], { input: token });
|
|
104
|
+
printResult(result);
|
|
105
|
+
if (result.status !== 0) process.exitCode = result.status;
|
|
106
|
+
else console.log(`GitHub secret NPM_TOKEN updated for ${repo}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (action === "rerun") {
|
|
111
|
+
const runId = opts.args?.[0];
|
|
112
|
+
const repo = opts.repo;
|
|
113
|
+
if (!runId || !repo) throw new Error("usage: clauth npm rerun <run-id> --repo LIFEAI/rdc-skills");
|
|
114
|
+
const result = run("gh", ["run", "rerun", runId, "--repo", repo, "--failed"]);
|
|
115
|
+
printResult(result);
|
|
116
|
+
if (result.status !== 0) process.exitCode = result.status;
|
|
117
|
+
else console.log(`rerun requested for ${repo} run ${runId}`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
usage();
|
|
122
|
+
throw new Error(`unknown clauth npm action: ${action}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Guarded publish ──────────────────────────────────────────────────────────
|
|
126
|
+
// Generic, package-agnostic publish that REFUSES to publish unless the package
|
|
127
|
+
// is already committed and pushed to GitHub — so the npm tarball can never be
|
|
128
|
+
// built from uncommitted "dev" code that isn't on the repo. Works for standalone
|
|
129
|
+
// repos and monorepo subpackages (clean-check is scoped to the package's dir).
|
|
130
|
+
export async function runPublish(target, opts = {}) {
|
|
131
|
+
const pkgDir = path.resolve(target || process.cwd());
|
|
132
|
+
const pkgJsonPath = path.join(pkgDir, "package.json");
|
|
133
|
+
if (!fs.existsSync(pkgJsonPath)) throw new Error(`No package.json found at ${pkgDir}`);
|
|
134
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
|
|
135
|
+
if (!pkg.name || !pkg.version) throw new Error(`package.json at ${pkgDir} is missing name or version`);
|
|
136
|
+
if (pkg.private === true) throw new Error(`${pkg.name} is marked "private": true — refusing to publish`);
|
|
137
|
+
const access = opts.access || pkg.publishConfig?.access || (pkg.name.startsWith("@") ? "public" : undefined);
|
|
138
|
+
|
|
139
|
+
const gitRoot = run("git", ["rev-parse", "--show-toplevel"], { cwd: pkgDir }).stdout?.trim();
|
|
140
|
+
if (!gitRoot) throw new Error(`${pkgDir} is not inside a git repository`);
|
|
141
|
+
const rel = path.relative(gitRoot, pkgDir) || ".";
|
|
142
|
+
|
|
143
|
+
// Guard 1 — nothing uncommitted in the package (else we'd pack dev code).
|
|
144
|
+
const dirty = run("git", ["status", "--porcelain", "--", rel], { cwd: gitRoot }).stdout?.trim();
|
|
145
|
+
if (dirty && !opts.allowDirty) {
|
|
146
|
+
throw new Error(`Refusing to publish ${pkg.name}@${pkg.version}: uncommitted changes in ${rel} would be packed but are NOT on GitHub:\n${dirty}\n\nCommit + push first (or pass --allow-dirty to override).`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Guard 2 — HEAD is on a remote (actually pushed to GitHub).
|
|
150
|
+
const head = run("git", ["rev-parse", "HEAD"], { cwd: gitRoot }).stdout?.trim();
|
|
151
|
+
const onRemote = run("git", ["branch", "-r", "--contains", head], { cwd: gitRoot }).stdout?.trim();
|
|
152
|
+
if (!onRemote && !opts.allowUnpushed) {
|
|
153
|
+
throw new Error(`Refusing to publish ${pkg.name}@${pkg.version}: HEAD ${head.slice(0, 9)} is not on any remote branch — push to GitHub first (or pass --allow-unpushed to override).`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Traceability — warn (don't block) if there's no v<version> tag at HEAD.
|
|
157
|
+
const tags = (run("git", ["tag", "--points-at", "HEAD"], { cwd: gitRoot }).stdout || "").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
158
|
+
if (!tags.includes(`v${pkg.version}`)) {
|
|
159
|
+
console.log(`⚠ No git tag v${pkg.version} at HEAD (traceability only). Tags here: ${tags.join(", ") || "none"}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log(`${opts.dryRun ? "[dry-run] " : ""}Publishing ${pkg.name}@${pkg.version} from ${rel} @ ${head.slice(0, 9)} (pushed${access ? `, access=${access}` : ""})`);
|
|
163
|
+
|
|
164
|
+
const token = await fetchNpmToken();
|
|
165
|
+
const args = ["publish"];
|
|
166
|
+
if (access) args.push("--access", access);
|
|
167
|
+
if (opts.dryRun) args.push("--dry-run");
|
|
168
|
+
const result = withNpmAuth(token, (npmrc) => run("npm", ["--userconfig", npmrc, ...args], { cwd: pkgDir }));
|
|
169
|
+
printResult(result, { redact: true });
|
|
170
|
+
if (result.status !== 0) { process.exitCode = result.status; throw new Error(`npm publish failed for ${pkg.name}`); }
|
|
171
|
+
if (opts.dryRun) { console.log("Dry run complete — nothing published."); return; }
|
|
172
|
+
|
|
173
|
+
// Verify on the registry directly (bypasses npm's local cache lag).
|
|
174
|
+
try {
|
|
175
|
+
const reg = await fetch(`https://registry.npmjs.org/${pkg.name}`);
|
|
176
|
+
const meta = await reg.json();
|
|
177
|
+
if (meta.versions?.[pkg.version]) console.log(`✓ Verified ${pkg.name}@${pkg.version} on the registry (latest: ${meta["dist-tags"]?.latest}).`);
|
|
178
|
+
else console.log(`⚠ ${pkg.name}@${pkg.version} not visible on the registry yet (propagation lag) — re-check shortly.`);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.log(`(could not verify on registry: ${e.message})`);
|
|
181
|
+
}
|
|
182
|
+
}
|