@theholocron/cli 5.0.0-alpha.4 → 5.0.0-alpha.6
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/dist/cli.mjs +216 -8
- package/dist/cli.mjs.map +1 -1
- package/package.json +4 -4
package/dist/cli.mjs
CHANGED
|
@@ -3324,6 +3324,170 @@ async function runPublish(input = {}) {
|
|
|
3324
3324
|
packageNames
|
|
3325
3325
|
};
|
|
3326
3326
|
}
|
|
3327
|
+
/**
|
|
3328
|
+
* `holocron publish` (no `--initial`) — the steady-state publish
|
|
3329
|
+
* `exec.publishCmd` in `release.config.ts` should call, replacing each
|
|
3330
|
+
* repo's hand-typed `pnpm -r --filter=./packages/* publish ...` shell (which
|
|
3331
|
+
* differs, today, only in monorepo-vs-single-package, provenance on/off, and
|
|
3332
|
+
* whether a repo needs to skip already-published packages) with one uniform
|
|
3333
|
+
* invocation. Part of the config-resolution workstream, #676 — see
|
|
3334
|
+
* `.notes/tech-config-resolution.spec.md`.
|
|
3335
|
+
*
|
|
3336
|
+
* Deliberately does not do the `--initial` bootstrap dance (npm
|
|
3337
|
+
* login-if-needed, Trusted Publisher next-steps printout) — a steady-state
|
|
3338
|
+
* publish runs in CI, authenticated via OIDC automatically, no interactive
|
|
3339
|
+
* step involved. Reuses `hasPackagesDir()`'s monorepo-vs-single-package
|
|
3340
|
+
* detection rather than duplicating it.
|
|
3341
|
+
*/
|
|
3342
|
+
async function runSteadyPublish(input = {}) {
|
|
3343
|
+
const print = input.print ?? ((line) => console.log(line));
|
|
3344
|
+
const logger = input.logger ?? getLogger();
|
|
3345
|
+
const cwd = input.cwd ?? process.cwd();
|
|
3346
|
+
const tag = input.tag ?? "latest";
|
|
3347
|
+
const dryRun = input.dryRun ?? false;
|
|
3348
|
+
const otp = input.otp;
|
|
3349
|
+
const exec = input.exec ?? defaultExec$1;
|
|
3350
|
+
const provenance = input.provenance ?? true;
|
|
3351
|
+
const skipAlreadyPublished = input.skipAlreadyPublished ?? false;
|
|
3352
|
+
const entries = input.packages ?? discoverPublicPackageEntries(cwd);
|
|
3353
|
+
const packageNames = entries.map((e) => e.name);
|
|
3354
|
+
print(`Holocron publish${dryRun ? " (dry-run)" : ""}`);
|
|
3355
|
+
print(` cwd: ${cwd}`);
|
|
3356
|
+
print(` tag: ${tag}`);
|
|
3357
|
+
print(` provenance: ${provenance}`);
|
|
3358
|
+
if (skipAlreadyPublished) print(" skip-already-published: true");
|
|
3359
|
+
logger.info({
|
|
3360
|
+
tag,
|
|
3361
|
+
provenance,
|
|
3362
|
+
skipAlreadyPublished,
|
|
3363
|
+
dryRun: dryRun || void 0
|
|
3364
|
+
}, "publish: start");
|
|
3365
|
+
if (otp) print(` otp: <${otp.length} chars>`);
|
|
3366
|
+
print("");
|
|
3367
|
+
if (entries.length === 0) {
|
|
3368
|
+
const message = "nothing to publish (no packages/* and root package.json is private or unnamed)";
|
|
3369
|
+
print(` ✗ ${message}`);
|
|
3370
|
+
logger.warn({ reason: message }, "publish: done");
|
|
3371
|
+
return {
|
|
3372
|
+
status: "fail",
|
|
3373
|
+
message,
|
|
3374
|
+
packageNames
|
|
3375
|
+
};
|
|
3376
|
+
}
|
|
3377
|
+
const baseFlags = [
|
|
3378
|
+
"--access",
|
|
3379
|
+
"public",
|
|
3380
|
+
"--no-git-checks",
|
|
3381
|
+
"--tag",
|
|
3382
|
+
tag,
|
|
3383
|
+
...provenance ? ["--provenance"] : []
|
|
3384
|
+
];
|
|
3385
|
+
const otpFlags = otp ? ["--otp", otp] : [];
|
|
3386
|
+
if (dryRun) {
|
|
3387
|
+
print("");
|
|
3388
|
+
if (skipAlreadyPublished) for (const entry of entries) print(` … would check ${entry.name}@${entry.version}, publish if not already on npm`);
|
|
3389
|
+
else {
|
|
3390
|
+
const args = [
|
|
3391
|
+
...hasPackagesDir(cwd) ? ["-r", "--filter=./packages/*"] : [],
|
|
3392
|
+
"publish",
|
|
3393
|
+
...baseFlags,
|
|
3394
|
+
...otpFlags
|
|
3395
|
+
];
|
|
3396
|
+
print(` … (dry-run) skipping actual publish`);
|
|
3397
|
+
print(` would run: pnpm ${args.join(" ")}`);
|
|
3398
|
+
}
|
|
3399
|
+
logger.info({
|
|
3400
|
+
tag,
|
|
3401
|
+
status: "dry-run",
|
|
3402
|
+
packages: entries.length
|
|
3403
|
+
}, "publish: done");
|
|
3404
|
+
return {
|
|
3405
|
+
status: "dry-run",
|
|
3406
|
+
message: "dry-run — no publish executed",
|
|
3407
|
+
packageNames
|
|
3408
|
+
};
|
|
3409
|
+
}
|
|
3410
|
+
if (skipAlreadyPublished) {
|
|
3411
|
+
print(" → publishing packages not already on npm…");
|
|
3412
|
+
for (const entry of entries) {
|
|
3413
|
+
const view = await exec("npm", [
|
|
3414
|
+
"view",
|
|
3415
|
+
`${entry.name}@${entry.version}`,
|
|
3416
|
+
"version"
|
|
3417
|
+
], { cwd });
|
|
3418
|
+
if (view.exitCode === 0 && view.stdout.trim()) {
|
|
3419
|
+
print(` skip ${entry.name}@${entry.version} (already published)`);
|
|
3420
|
+
continue;
|
|
3421
|
+
}
|
|
3422
|
+
const publish = await exec("pnpm", [
|
|
3423
|
+
"--filter",
|
|
3424
|
+
entry.dir,
|
|
3425
|
+
"publish",
|
|
3426
|
+
...baseFlags,
|
|
3427
|
+
...otpFlags
|
|
3428
|
+
], { cwd });
|
|
3429
|
+
if (publish.exitCode !== 0) {
|
|
3430
|
+
const message = `publish failed for ${entry.name}@${entry.version} (exit ${publish.exitCode}): ${publish.stderr.trim() || publish.stdout.trim() || "no output"}`;
|
|
3431
|
+
print(` ✗ ${message}`);
|
|
3432
|
+
logger.warn({
|
|
3433
|
+
tag,
|
|
3434
|
+
package: entry.name,
|
|
3435
|
+
reason: message
|
|
3436
|
+
}, "publish: done");
|
|
3437
|
+
return {
|
|
3438
|
+
status: "fail",
|
|
3439
|
+
message,
|
|
3440
|
+
packageNames
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3443
|
+
print(` ✓ ${entry.name}@${entry.version}`);
|
|
3444
|
+
}
|
|
3445
|
+
logger.info({
|
|
3446
|
+
tag,
|
|
3447
|
+
status: "ok",
|
|
3448
|
+
packages: entries.length
|
|
3449
|
+
}, "publish: done");
|
|
3450
|
+
return {
|
|
3451
|
+
status: "ok",
|
|
3452
|
+
packageNames
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
print(" → publishing all public @theholocron/* packages…");
|
|
3456
|
+
const publish = await exec("pnpm", [
|
|
3457
|
+
...hasPackagesDir(cwd) ? ["-r", "--filter=./packages/*"] : [],
|
|
3458
|
+
"publish",
|
|
3459
|
+
...baseFlags,
|
|
3460
|
+
...otpFlags
|
|
3461
|
+
], { cwd });
|
|
3462
|
+
if (publish.exitCode !== 0) {
|
|
3463
|
+
const message = `publish failed (exit ${publish.exitCode}): ${publish.stderr.trim() || publish.stdout.trim() || "no output"}`;
|
|
3464
|
+
print(` ✗ ${message}`);
|
|
3465
|
+
if (publish.stdout.includes("EOTP") || publish.stderr.includes("EOTP")) {
|
|
3466
|
+
print("");
|
|
3467
|
+
print(" → hint: your npm account requires 2FA for writes. Re-run with `--otp <code>`:");
|
|
3468
|
+
print(` pnpm exec holocron publish --otp <6-digit-code>`);
|
|
3469
|
+
}
|
|
3470
|
+
logger.warn({
|
|
3471
|
+
tag,
|
|
3472
|
+
reason: message
|
|
3473
|
+
}, "publish: done");
|
|
3474
|
+
return {
|
|
3475
|
+
status: "fail",
|
|
3476
|
+
message,
|
|
3477
|
+
packageNames
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
print(" ✓ publish complete");
|
|
3481
|
+
logger.info({
|
|
3482
|
+
tag,
|
|
3483
|
+
status: "ok",
|
|
3484
|
+
packages: entries.length
|
|
3485
|
+
}, "publish: done");
|
|
3486
|
+
return {
|
|
3487
|
+
status: "ok",
|
|
3488
|
+
packageNames
|
|
3489
|
+
};
|
|
3490
|
+
}
|
|
3327
3491
|
function hasPackagesDir(cwd) {
|
|
3328
3492
|
return existsSync(join(cwd, "packages"));
|
|
3329
3493
|
}
|
|
@@ -3337,6 +3501,37 @@ function publicPackageName(pkgPath) {
|
|
|
3337
3501
|
return;
|
|
3338
3502
|
}
|
|
3339
3503
|
}
|
|
3504
|
+
/** Read a `package.json`'s `name` + `version`, when it's public (`!private && name`). */
|
|
3505
|
+
function publicPackageEntry(pkgPath, dir) {
|
|
3506
|
+
if (!existsSync(pkgPath)) return void 0;
|
|
3507
|
+
try {
|
|
3508
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
3509
|
+
return !pkg.private && pkg.name && pkg.version ? {
|
|
3510
|
+
name: pkg.name,
|
|
3511
|
+
version: pkg.version,
|
|
3512
|
+
dir
|
|
3513
|
+
} : void 0;
|
|
3514
|
+
} catch {
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
/**
|
|
3519
|
+
* Same repo-layout detection as {@link discoverPublicPackages}, but carrying
|
|
3520
|
+
* version + a `pnpm --filter`-ready path — {@link runSteadyPublish}'s
|
|
3521
|
+
* `skipAlreadyPublished` mode needs both to check `npm view <pkg>@<version>`
|
|
3522
|
+
* and publish one workspace at a time.
|
|
3523
|
+
*/
|
|
3524
|
+
function discoverPublicPackageEntries(cwd) {
|
|
3525
|
+
if (!hasPackagesDir(cwd)) {
|
|
3526
|
+
const entry = publicPackageEntry(join(cwd, "package.json"), ".");
|
|
3527
|
+
return entry ? [entry] : [];
|
|
3528
|
+
}
|
|
3529
|
+
const packagesDir = join(cwd, "packages");
|
|
3530
|
+
return readdirSync(packagesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).flatMap((e) => {
|
|
3531
|
+
const entry = publicPackageEntry(join(packagesDir, e.name, "package.json"), `./packages/${e.name}`);
|
|
3532
|
+
return entry ? [entry] : [];
|
|
3533
|
+
});
|
|
3534
|
+
}
|
|
3340
3535
|
/**
|
|
3341
3536
|
* Monorepo (`packages/` present): every public `packages/*` workspace.
|
|
3342
3537
|
* Single package (no `packages/`): the root `package.json`, if it's public —
|
|
@@ -7976,11 +8171,10 @@ try {
|
|
|
7976
8171
|
}).command("publish", "Publish @theholocron/* packages to npm", (y) => y.option("initial", {
|
|
7977
8172
|
type: "boolean",
|
|
7978
8173
|
default: false,
|
|
7979
|
-
describe: "One-shot bootstrap publish for trusted-publishing-eligible packages (npm needs the package to exist before Trusted Publishing can be configured for it).
|
|
8174
|
+
describe: "One-shot bootstrap publish for trusted-publishing-eligible packages (npm needs the package to exist before Trusted Publishing can be configured for it). Skips the steady-state OIDC path below — use only for a package's very first publish."
|
|
7980
8175
|
}).option("tag", {
|
|
7981
8176
|
type: "string",
|
|
7982
|
-
|
|
7983
|
-
describe: "npm distribution tag (defaults to alpha)"
|
|
8177
|
+
describe: "npm distribution tag. Defaults to alpha for --initial, latest for a steady-state publish."
|
|
7984
8178
|
}).option("otp", {
|
|
7985
8179
|
type: "string",
|
|
7986
8180
|
describe: "One-time password from your authenticator (required if npm needs 2FA for writes)"
|
|
@@ -7990,6 +8184,14 @@ try {
|
|
|
7990
8184
|
}).option("repo", {
|
|
7991
8185
|
type: "string",
|
|
7992
8186
|
describe: "owner/repo for Trusted Publisher config (--sync-trust). Defaults to theholocron/holocron."
|
|
8187
|
+
}).option("provenance", {
|
|
8188
|
+
type: "boolean",
|
|
8189
|
+
default: true,
|
|
8190
|
+
describe: "Publish with --provenance (steady-state only). Default on — free under CI/OIDC."
|
|
8191
|
+
}).option("skip-already-published", {
|
|
8192
|
+
type: "boolean",
|
|
8193
|
+
default: false,
|
|
8194
|
+
describe: "Steady-state only: check `npm view <pkg>@<version>` before publishing each package and skip it if that exact version already exists, instead of one bulk publish. For repos that ship many independently-versioned packages where a release doesn't bump every one."
|
|
7993
8195
|
}), async (argv) => {
|
|
7994
8196
|
if (argv.syncTrust) {
|
|
7995
8197
|
const [oldFile, newFile] = argv.syncTrust;
|
|
@@ -8007,15 +8209,21 @@ try {
|
|
|
8007
8209
|
})).status === "fail") process.exitCode = 1;
|
|
8008
8210
|
return;
|
|
8009
8211
|
}
|
|
8010
|
-
if (
|
|
8011
|
-
|
|
8012
|
-
|
|
8212
|
+
if (argv.initial) {
|
|
8213
|
+
if ((await runPublish({
|
|
8214
|
+
cwd: argv.cwd,
|
|
8215
|
+
dryRun: argv.dryRun,
|
|
8216
|
+
...argv.tag ? { tag: argv.tag } : {},
|
|
8217
|
+
...argv.otp ? { otp: argv.otp } : {}
|
|
8218
|
+
})).status === "fail") process.exitCode = 1;
|
|
8013
8219
|
return;
|
|
8014
8220
|
}
|
|
8015
|
-
if ((await
|
|
8221
|
+
if ((await runSteadyPublish({
|
|
8016
8222
|
cwd: argv.cwd,
|
|
8017
|
-
tag: argv.tag,
|
|
8018
8223
|
dryRun: argv.dryRun,
|
|
8224
|
+
provenance: argv.provenance,
|
|
8225
|
+
skipAlreadyPublished: argv.skipAlreadyPublished,
|
|
8226
|
+
...argv.tag ? { tag: argv.tag } : {},
|
|
8019
8227
|
...argv.otp ? { otp: argv.otp } : {}
|
|
8020
8228
|
})).status === "fail") process.exitCode = 1;
|
|
8021
8229
|
}).command("sync [steps..]", "Sync state from config to the provider and local files (labels, properties, teams, topics, keywords, description, homepage, readme, workflows, scripts, wiki)", (y) => y.positional("steps", {
|