@theholocron/cli 4.18.3 → 5.0.0-alpha.10
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 +554 -55
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +24 -11
- package/package.json +8 -9
package/dist/cli.mjs
CHANGED
|
@@ -2154,12 +2154,11 @@ function render$10(inputs) {
|
|
|
2154
2154
|
main: "./src/index.ts",
|
|
2155
2155
|
exports: { ".": "./src/index.ts" },
|
|
2156
2156
|
scripts: {
|
|
2157
|
-
build: "tsdown",
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2157
|
+
"delivery.build": "tsdown",
|
|
2158
|
+
"sourceQuality.staticAnalysis": "holocron run sourceQuality.staticAnalysis --",
|
|
2159
|
+
"verification.typeSafety": "holocron run verification.typeSafety --",
|
|
2160
|
+
"verification.unitTests": "holocron run verification.unitTests --",
|
|
2161
2161
|
"test:watch": "vitest",
|
|
2162
|
-
"test:coverage": "vitest run --coverage",
|
|
2163
2162
|
validate: "tsx scripts/validate.mjs"
|
|
2164
2163
|
},
|
|
2165
2164
|
peerDependencies: { "@theholocron/cli": "workspace:*" },
|
|
@@ -2879,7 +2878,7 @@ function runPluginCreate(input) {
|
|
|
2879
2878
|
execFn("pnpm", [
|
|
2880
2879
|
"--filter",
|
|
2881
2880
|
pkg,
|
|
2882
|
-
"
|
|
2881
|
+
"verification.typeSafety"
|
|
2883
2882
|
], {
|
|
2884
2883
|
cwd,
|
|
2885
2884
|
stdio: "inherit"
|
|
@@ -2887,7 +2886,7 @@ function runPluginCreate(input) {
|
|
|
2887
2886
|
execFn("pnpm", [
|
|
2888
2887
|
"--filter",
|
|
2889
2888
|
pkg,
|
|
2890
|
-
"
|
|
2889
|
+
"sourceQuality.staticAnalysis"
|
|
2891
2890
|
], {
|
|
2892
2891
|
cwd,
|
|
2893
2892
|
stdio: "inherit"
|
|
@@ -2895,7 +2894,7 @@ function runPluginCreate(input) {
|
|
|
2895
2894
|
execFn("pnpm", [
|
|
2896
2895
|
"--filter",
|
|
2897
2896
|
pkg,
|
|
2898
|
-
"
|
|
2897
|
+
"verification.unitTests"
|
|
2899
2898
|
], {
|
|
2900
2899
|
cwd,
|
|
2901
2900
|
stdio: "inherit"
|
|
@@ -3018,6 +3017,193 @@ function printNextSteps$1(print, inputs) {
|
|
|
3018
3017
|
* "revoke this token at <url>" reminder — same pattern as `rando vc
|
|
3019
3018
|
* setup` for the ephemeral GH admin PAT.
|
|
3020
3019
|
*/
|
|
3020
|
+
/**
|
|
3021
|
+
* `holocron publish --sync-trust <old-file> <new-file>` — bulk-migrates npm
|
|
3022
|
+
* Trusted Publisher (OIDC) config to a new workflow filename across every
|
|
3023
|
+
* public package. See issue #689 / `scripts/npm-trust-migrate.sh` (the
|
|
3024
|
+
* shell-script prototype this supersedes) for the full "why": npm ties
|
|
3025
|
+
* Trusted Publisher to a workflow FILENAME per package, so renaming the
|
|
3026
|
+
* release workflow breaks OIDC publish (`ENEEDAUTH`) until every package's
|
|
3027
|
+
* config is updated to match.
|
|
3028
|
+
*
|
|
3029
|
+
* Requires npm@11.15.0+ (`npm trust`) — always shells out through
|
|
3030
|
+
* `npx -y npm@latest` regardless of the locally-installed npm version.
|
|
3031
|
+
*
|
|
3032
|
+
* npm's `trust` mutations — and reads, once a short-lived OTP grant lapses —
|
|
3033
|
+
* require a fresh interactive browser 2FA approval (an `EOTP` error with an
|
|
3034
|
+
* `authUrl`). No token bypasses this (npm's own docs: Granular Access Tokens
|
|
3035
|
+
* with the bypass-2FA option are explicitly unsupported for trust commands).
|
|
3036
|
+
* This command can't drive that browser step itself, so on the first `EOTP`
|
|
3037
|
+
* it stops immediately, prints the `authUrl`, and returns — the operator
|
|
3038
|
+
* approves it and re-runs; already-migrated packages are skipped on the next
|
|
3039
|
+
* pass, so it's safe to resume from wherever it stopped.
|
|
3040
|
+
*/
|
|
3041
|
+
async function runSyncTrust(input) {
|
|
3042
|
+
const print = input.print ?? ((line) => console.log(line));
|
|
3043
|
+
const logger = input.logger ?? getLogger();
|
|
3044
|
+
const cwd = input.cwd ?? process.cwd();
|
|
3045
|
+
const repo = input.repo ?? "theholocron/holocron";
|
|
3046
|
+
const dryRun = input.dryRun ?? false;
|
|
3047
|
+
const exec = input.exec ?? defaultExec$1;
|
|
3048
|
+
const packageNames = input.packages ?? discoverPublicPackages(cwd);
|
|
3049
|
+
const npm = "npx";
|
|
3050
|
+
const npmArgs = ["-y", "npm@latest"];
|
|
3051
|
+
print(`Holocron publish --sync-trust${dryRun ? " (dry-run)" : ""}`);
|
|
3052
|
+
print(` ${input.oldFile} -> ${input.newFile} (repo: ${repo})`);
|
|
3053
|
+
print(` ${packageNames.length} public packages`);
|
|
3054
|
+
print("");
|
|
3055
|
+
logger.info({
|
|
3056
|
+
oldFile: input.oldFile,
|
|
3057
|
+
newFile: input.newFile,
|
|
3058
|
+
packages: packageNames.length
|
|
3059
|
+
}, "sync-trust: start");
|
|
3060
|
+
if (packageNames.length === 0) {
|
|
3061
|
+
const message = "nothing to sync (no packages/* and root package.json is private or unnamed)";
|
|
3062
|
+
print(` ✗ ${message}`);
|
|
3063
|
+
return {
|
|
3064
|
+
status: "fail",
|
|
3065
|
+
message,
|
|
3066
|
+
packages: []
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
if (dryRun) {
|
|
3070
|
+
for (const name of packageNames) print(` … would sync ${name}`);
|
|
3071
|
+
return {
|
|
3072
|
+
status: "dry-run",
|
|
3073
|
+
message: "dry-run — no npm calls made",
|
|
3074
|
+
packages: []
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
3077
|
+
const results = [];
|
|
3078
|
+
for (const name of packageNames) {
|
|
3079
|
+
print(`=== ${name} ===`);
|
|
3080
|
+
const parsed = parseJson((await exec(npm, [
|
|
3081
|
+
...npmArgs,
|
|
3082
|
+
"trust",
|
|
3083
|
+
"list",
|
|
3084
|
+
name,
|
|
3085
|
+
"--json"
|
|
3086
|
+
], { cwd })).stdout);
|
|
3087
|
+
if (isEotp(parsed)) {
|
|
3088
|
+
const authUrl = parsed.error?.authUrl ?? "";
|
|
3089
|
+
print(` needs fresh 2FA — open and approve: ${authUrl}`);
|
|
3090
|
+
print(" then re-run; already-migrated packages are skipped.");
|
|
3091
|
+
results.push({
|
|
3092
|
+
name,
|
|
3093
|
+
status: "needs-auth",
|
|
3094
|
+
message: authUrl
|
|
3095
|
+
});
|
|
3096
|
+
logger.warn({
|
|
3097
|
+
package: name,
|
|
3098
|
+
authUrl
|
|
3099
|
+
}, "sync-trust: needs auth, stopping");
|
|
3100
|
+
return {
|
|
3101
|
+
status: "fail",
|
|
3102
|
+
message: "npm 2FA grant needed — see printed authUrl",
|
|
3103
|
+
packages: results
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
const entry = parsed;
|
|
3107
|
+
if (entry.file === input.newFile) {
|
|
3108
|
+
print(` already on ${input.newFile}, skipping`);
|
|
3109
|
+
results.push({
|
|
3110
|
+
name,
|
|
3111
|
+
status: "skipped"
|
|
3112
|
+
});
|
|
3113
|
+
continue;
|
|
3114
|
+
}
|
|
3115
|
+
if (entry.id) {
|
|
3116
|
+
print(` revoking existing trust (file=${entry.file ?? "?"}, id=${entry.id})`);
|
|
3117
|
+
const revoke = await exec(npm, [
|
|
3118
|
+
...npmArgs,
|
|
3119
|
+
"trust",
|
|
3120
|
+
"revoke",
|
|
3121
|
+
name,
|
|
3122
|
+
"--id",
|
|
3123
|
+
entry.id,
|
|
3124
|
+
"-y"
|
|
3125
|
+
], { cwd });
|
|
3126
|
+
if (revoke.exitCode !== 0 && isEotp(parseJson(revoke.stdout))) {
|
|
3127
|
+
const authUrl = parseJson(revoke.stdout).error?.authUrl ?? "";
|
|
3128
|
+
print(` needs fresh 2FA — open and approve: ${authUrl}`);
|
|
3129
|
+
results.push({
|
|
3130
|
+
name,
|
|
3131
|
+
status: "needs-auth",
|
|
3132
|
+
message: authUrl
|
|
3133
|
+
});
|
|
3134
|
+
return {
|
|
3135
|
+
status: "fail",
|
|
3136
|
+
message: "npm 2FA grant needed — see printed authUrl",
|
|
3137
|
+
packages: results
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
} else print(" no existing trust config found");
|
|
3141
|
+
print(` creating trust: ${input.newFile}`);
|
|
3142
|
+
const create = await exec(npm, [
|
|
3143
|
+
...npmArgs,
|
|
3144
|
+
"trust",
|
|
3145
|
+
"github",
|
|
3146
|
+
name,
|
|
3147
|
+
"--repo",
|
|
3148
|
+
repo,
|
|
3149
|
+
"--file",
|
|
3150
|
+
input.newFile,
|
|
3151
|
+
"--allow-publish",
|
|
3152
|
+
"--allow-stage-publish",
|
|
3153
|
+
"-y"
|
|
3154
|
+
], { cwd });
|
|
3155
|
+
if (create.exitCode === 0) {
|
|
3156
|
+
print(" ✓ ok");
|
|
3157
|
+
results.push({
|
|
3158
|
+
name,
|
|
3159
|
+
status: "ok"
|
|
3160
|
+
});
|
|
3161
|
+
} else {
|
|
3162
|
+
const createParsed = parseJson(create.stdout);
|
|
3163
|
+
if (isEotp(createParsed)) {
|
|
3164
|
+
const authUrl = createParsed.error?.authUrl ?? "";
|
|
3165
|
+
print(` needs fresh 2FA — open and approve: ${authUrl}`);
|
|
3166
|
+
results.push({
|
|
3167
|
+
name,
|
|
3168
|
+
status: "needs-auth",
|
|
3169
|
+
message: authUrl
|
|
3170
|
+
});
|
|
3171
|
+
return {
|
|
3172
|
+
status: "fail",
|
|
3173
|
+
message: "npm 2FA grant needed — see printed authUrl",
|
|
3174
|
+
packages: results
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
const message = create.stderr.trim() || create.stdout.trim() || `exit ${create.exitCode}`;
|
|
3178
|
+
print(` ✗ FAILED: ${message}`);
|
|
3179
|
+
results.push({
|
|
3180
|
+
name,
|
|
3181
|
+
status: "fail",
|
|
3182
|
+
message
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
const failed = results.filter((r) => r.status === "fail");
|
|
3187
|
+
logger.info({
|
|
3188
|
+
ok: results.filter((r) => r.status === "ok").length,
|
|
3189
|
+
failed: failed.length
|
|
3190
|
+
}, "sync-trust: done");
|
|
3191
|
+
return {
|
|
3192
|
+
status: failed.length > 0 ? "fail" : "ok",
|
|
3193
|
+
...failed.length > 0 ? { message: `${failed.length} package(s) failed` } : {},
|
|
3194
|
+
packages: results
|
|
3195
|
+
};
|
|
3196
|
+
}
|
|
3197
|
+
function parseJson(raw) {
|
|
3198
|
+
try {
|
|
3199
|
+
return JSON.parse(raw);
|
|
3200
|
+
} catch {
|
|
3201
|
+
return;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
function isEotp(parsed) {
|
|
3205
|
+
return typeof parsed === "object" && parsed !== null && "error" in parsed && parsed.error?.code === "EOTP";
|
|
3206
|
+
}
|
|
3021
3207
|
async function runPublish(input = {}) {
|
|
3022
3208
|
const print = input.print ?? ((line) => console.log(line));
|
|
3023
3209
|
const logger = input.logger ?? getLogger();
|
|
@@ -3138,6 +3324,170 @@ async function runPublish(input = {}) {
|
|
|
3138
3324
|
packageNames
|
|
3139
3325
|
};
|
|
3140
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
|
+
}
|
|
3141
3491
|
function hasPackagesDir(cwd) {
|
|
3142
3492
|
return existsSync(join(cwd, "packages"));
|
|
3143
3493
|
}
|
|
@@ -3151,6 +3501,37 @@ function publicPackageName(pkgPath) {
|
|
|
3151
3501
|
return;
|
|
3152
3502
|
}
|
|
3153
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
|
+
}
|
|
3154
3535
|
/**
|
|
3155
3536
|
* Monorepo (`packages/` present): every public `packages/*` workspace.
|
|
3156
3537
|
* Single package (no `packages/`): the root `package.json`, if it's public —
|
|
@@ -3180,7 +3561,7 @@ function printNextSteps(print, env, packageNames, repoName) {
|
|
|
3180
3561
|
print("");
|
|
3181
3562
|
print(" → next: configure Trusted Publisher for each package on npm:");
|
|
3182
3563
|
for (const name of packageNames) print(` https://www.npmjs.com/package/${name}/access`);
|
|
3183
|
-
print(` Publisher: GitHub Actions Org: theholocron Repo: ${repoName} Workflow:
|
|
3564
|
+
print(` Publisher: GitHub Actions Org: theholocron Repo: ${repoName} Workflow: delivery.publish.yml`);
|
|
3184
3565
|
if (env.get("NPM_TOKEN")) {
|
|
3185
3566
|
print("");
|
|
3186
3567
|
print(" → cleanup: $NPM_TOKEN was used. Revoke it now (no API for self-revoke; UI-only):");
|
|
@@ -3563,19 +3944,28 @@ var alexrc_default = { allow: [
|
|
|
3563
3944
|
] };
|
|
3564
3945
|
//#endregion
|
|
3565
3946
|
//#region src/templates/configs/alexjs/create-config.ts
|
|
3566
|
-
const { workflowHeader: workflowHeader$
|
|
3947
|
+
const { workflowHeader: workflowHeader$9 } = createHeader({ source: "packages/cli/src/templates/configs/alexjs/create-config.ts" });
|
|
3567
3948
|
function createRcConfig() {
|
|
3568
3949
|
return JSON.stringify(alexrc_default, null, 2) + "\n";
|
|
3569
3950
|
}
|
|
3570
|
-
function createIgnoreConfig() {
|
|
3571
|
-
return `${workflowHeader$
|
|
3951
|
+
function createIgnoreConfig$1() {
|
|
3952
|
+
return `${workflowHeader$9()}${alexignore_default}`;
|
|
3953
|
+
}
|
|
3954
|
+
//#endregion
|
|
3955
|
+
//#region src/templates/configs/commit-msg/commit-msg
|
|
3956
|
+
var commit_msg_default = "# --config <resolved shared path> (config-resolution workstream, #676) — falls\n# back to commitlint's own auto-discovery of a local commitlint.config.ts when\n# the shared package's built dist/ isn't present (not installed, or a stale\n# install predating this file).\nCOMMITLINT_CONFIG=\"node_modules/@theholocron/commitlint-config/dist/index.js\"\nif [ -f \"$COMMITLINT_CONFIG\" ]; then\n npx --no -- commitlint --config \"$COMMITLINT_CONFIG\" --edit \"$1\"\nelse\n npx --no -- commitlint --edit \"$1\"\nfi\n";
|
|
3957
|
+
//#endregion
|
|
3958
|
+
//#region src/templates/configs/commit-msg/create-config.ts
|
|
3959
|
+
const { workflowHeader: workflowHeader$8 } = createHeader({ source: "packages/cli/src/templates/configs/commit-msg/create-config.ts" });
|
|
3960
|
+
function createConfig$7() {
|
|
3961
|
+
return `${workflowHeader$8("shebang")}${commit_msg_default}`;
|
|
3572
3962
|
}
|
|
3573
3963
|
//#endregion
|
|
3574
3964
|
//#region src/templates/configs/devmoji/create-config.ts
|
|
3575
|
-
const { workflowHeader: workflowHeader$
|
|
3576
|
-
function createConfig$
|
|
3965
|
+
const { workflowHeader: workflowHeader$7 } = createHeader({ source: "packages/cli/src/templates/configs/devmoji/create-config.ts" });
|
|
3966
|
+
function createConfig$6() {
|
|
3577
3967
|
return [
|
|
3578
|
-
workflowHeader$
|
|
3968
|
+
workflowHeader$7("cjs"),
|
|
3579
3969
|
`/* eslint-disable */`,
|
|
3580
3970
|
`const { defineConfig } = require("@theholocron/devmoji-config");`,
|
|
3581
3971
|
`module.exports = defineConfig();`,
|
|
@@ -3587,9 +3977,9 @@ function createConfig$5() {
|
|
|
3587
3977
|
var editorconfig_default = "root = true\n\n[*]\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\nindent_style = tab\nindent_size = 4\n\n[.gitattributes]\nindent_style = space\nindent_size = 2\n\n[*.{json,yml,yaml}]\nindent_style = space\nindent_size = 2\n\n[*.{md,mdx}]\ntrim_trailing_whitespace = false\n\n[.*{rc,ignore}]\nindent_style = space\nindent_size = 2\n";
|
|
3588
3978
|
//#endregion
|
|
3589
3979
|
//#region src/templates/configs/editorconfig/create-config.ts
|
|
3590
|
-
const { workflowHeader: workflowHeader$
|
|
3591
|
-
function createConfig$
|
|
3592
|
-
return `${workflowHeader$
|
|
3980
|
+
const { workflowHeader: workflowHeader$6 } = createHeader({ source: "packages/cli/src/templates/configs/editorconfig/create-config.ts" });
|
|
3981
|
+
function createConfig$5() {
|
|
3982
|
+
return `${workflowHeader$6()}${editorconfig_default}`;
|
|
3593
3983
|
}
|
|
3594
3984
|
//#endregion
|
|
3595
3985
|
//#region src/templates/configs/editorconfig-checker/editorconfig-checker.json
|
|
@@ -3630,7 +4020,7 @@ var editorconfig_checker_default = {
|
|
|
3630
4020
|
* options here (`Exclude`, `Disable`) are stable across releases, so pinning
|
|
3631
4021
|
* buys nothing.
|
|
3632
4022
|
*/
|
|
3633
|
-
function createConfig$
|
|
4023
|
+
function createConfig$4() {
|
|
3634
4024
|
return JSON.stringify(editorconfig_checker_default, null, 2) + "\n";
|
|
3635
4025
|
}
|
|
3636
4026
|
//#endregion
|
|
@@ -3638,7 +4028,7 @@ function createConfig$3() {
|
|
|
3638
4028
|
var pre_commit_default = "# Run GitLeaks against staged changes only — a full `gitleaks git` history\n# scan re-flags every pre-existing (already-merged) finding on every commit,\n# permanently blocking the repo once any false positive lands in history.\n# CI's super-linter GITLEAKS validator is likewise scoped to changed files.\ngitleaks protect --staged --no-banner\n\n# Exit with non-zero if secrets are detected by GitLeaks\nif [ $? -ne 0 ]; then\n echo \"Secrets detected by GitLeaks, aborting commit!\"\n exit 1\nfi\n\n# Run lint-staged with a custom config\nnpx lint-staged --config @theholocron/lint-staged-config\n";
|
|
3639
4029
|
//#endregion
|
|
3640
4030
|
//#region src/templates/configs/pre-commit/create-config.ts
|
|
3641
|
-
const { workflowHeader: workflowHeader$
|
|
4031
|
+
const { workflowHeader: workflowHeader$5 } = createHeader({ source: "packages/cli/src/templates/configs/pre-commit/create-config.ts" });
|
|
3642
4032
|
/**
|
|
3643
4033
|
* `.husky/pre-commit` — runs GitLeaks (scoped to staged changes) and
|
|
3644
4034
|
* lint-staged before every commit. Installed by `holocron setup` when hooks
|
|
@@ -3651,15 +4041,15 @@ const { workflowHeader: workflowHeader$4 } = createHeader({ source: "packages/cl
|
|
|
3651
4041
|
* doc — every future commit is permanently blocked, regardless of what's
|
|
3652
4042
|
* actually staged.
|
|
3653
4043
|
*/
|
|
3654
|
-
function createConfig$
|
|
3655
|
-
return `${workflowHeader$
|
|
4044
|
+
function createConfig$3() {
|
|
4045
|
+
return `${workflowHeader$5("shebang")}${pre_commit_default}`;
|
|
3656
4046
|
}
|
|
3657
4047
|
//#endregion
|
|
3658
4048
|
//#region src/templates/configs/pre-push/pre-push
|
|
3659
4049
|
var pre_push_default = "# Run the merge-gating checks locally before pushing — the same set CI runs.\n# Bypass once with: git push --no-verify\n\n# CI runners (semantic-release, super-linter auto-commit, bots) push without a\n# built dev environment — the hook has nothing to add there.\n[ -n \"$CI\" ] && exit 0\n\n__HOLOCRON_SCRIPT__ ci\n";
|
|
3660
4050
|
//#endregion
|
|
3661
4051
|
//#region src/templates/configs/pre-push/create-config.ts
|
|
3662
|
-
const { workflowHeader: workflowHeader$
|
|
4052
|
+
const { workflowHeader: workflowHeader$4 } = createHeader({ source: "packages/cli/src/templates/configs/pre-push/create-config.ts" });
|
|
3663
4053
|
/**
|
|
3664
4054
|
* `.husky/pre-push` — runs `holocron ci` (the merge-gating checks, in CI order)
|
|
3665
4055
|
* before every push. Installed by `holocron setup` when hooks are enabled
|
|
@@ -3668,17 +4058,32 @@ const { workflowHeader: workflowHeader$3 } = createHeader({ source: "packages/cl
|
|
|
3668
4058
|
* @param holocronScript - the command that invokes the CLI. Defaults to
|
|
3669
4059
|
* `pnpm exec holocron`; the source repo passes `node packages/cli/dist/cli.mjs`.
|
|
3670
4060
|
*/
|
|
3671
|
-
function createConfig$
|
|
3672
|
-
return `${workflowHeader$
|
|
4061
|
+
function createConfig$2(holocronScript = "pnpm exec holocron") {
|
|
4062
|
+
return `${workflowHeader$4("shebang")}${pre_push_default.replace("__HOLOCRON_SCRIPT__", holocronScript)}`;
|
|
3673
4063
|
}
|
|
3674
4064
|
//#endregion
|
|
3675
4065
|
//#region src/templates/configs/prepare-commit-msg/prepare-commit-msg
|
|
3676
|
-
var prepare_commit_msg_default = "NAME=$(git config user.name)\nEMAIL=$(git config user.email)\n\nif [ -z \"$NAME\" ]; then\n echo \"empty git config user.name\"\n exit 1\nfi\n\nif [ -z \"$EMAIL\" ]; then\n echo \"empty git config user.email\"\n exit 1\nfi\n\ngit interpret-trailers --if-exists doNothing --trailer \\\n \"Signed-off-by: $NAME <$EMAIL>\" \\\n --in-place \"$1\"\n\
|
|
4066
|
+
var prepare_commit_msg_default = "NAME=$(git config user.name)\nEMAIL=$(git config user.email)\n\nif [ -z \"$NAME\" ]; then\n echo \"empty git config user.name\"\n exit 1\nfi\n\nif [ -z \"$EMAIL\" ]; then\n echo \"empty git config user.email\"\n exit 1\nfi\n\ngit interpret-trailers --if-exists doNothing --trailer \\\n \"Signed-off-by: $NAME <$EMAIL>\" \\\n --in-place \"$1\"\n\n# --config <resolved shared path> (config-resolution workstream, #676) — falls\n# back to devmoji's own auto-discovery of a local devmoji.config.cjs when the\n# shared package's built dist/ isn't present (not installed, or a stale\n# install predating this file).\nDEVMOJI_CONFIG=\"node_modules/@theholocron/devmoji-config/dist/index.cjs\"\nif [ -f \"$DEVMOJI_CONFIG\" ]; then\n npx devmoji --config \"$DEVMOJI_CONFIG\" -e\nelse\n npx devmoji -e\nfi\n";
|
|
3677
4067
|
//#endregion
|
|
3678
4068
|
//#region src/templates/configs/prepare-commit-msg/create-config.ts
|
|
3679
|
-
const { workflowHeader: workflowHeader$
|
|
4069
|
+
const { workflowHeader: workflowHeader$3 } = createHeader({ source: "packages/cli/src/templates/configs/prepare-commit-msg/create-config.ts" });
|
|
4070
|
+
function createConfig$1() {
|
|
4071
|
+
return `${workflowHeader$3("shebang")}${prepare_commit_msg_default}`;
|
|
4072
|
+
}
|
|
4073
|
+
//#endregion
|
|
4074
|
+
//#region src/templates/configs/yamllint/.yamlignore
|
|
4075
|
+
var _yamlignore_default = "pnpm-lock.yaml\n";
|
|
4076
|
+
//#endregion
|
|
4077
|
+
//#region src/templates/configs/yamllint/.yamllint.yml
|
|
4078
|
+
var _yamllint_default = "---\nextends: default\n\nignore-from-file:\n - .gitignore\n - .yamlignore\n\nrules:\n comments:\n min-spaces-from-content: 1\n require-starting-space: true\n\n comments-indentation: {}\n\n document-start:\n ignore: |\n .github/*.yml\n\n indentation:\n spaces: consistent\n indent-sequences: consistent\n\n line-length: disable\n";
|
|
4079
|
+
//#endregion
|
|
4080
|
+
//#region src/templates/configs/yamllint/create-config.ts
|
|
4081
|
+
const { workflowHeader: workflowHeader$2 } = createHeader({ source: "packages/cli/src/templates/configs/yamllint/create-config.ts" });
|
|
3680
4082
|
function createConfig() {
|
|
3681
|
-
return `${workflowHeader$2(
|
|
4083
|
+
return `${workflowHeader$2()}${_yamllint_default}`;
|
|
4084
|
+
}
|
|
4085
|
+
function createIgnoreConfig() {
|
|
4086
|
+
return `${workflowHeader$2()}${_yamlignore_default}`;
|
|
3682
4087
|
}
|
|
3683
4088
|
//#endregion
|
|
3684
4089
|
//#region src/templates/dco.yml
|
|
@@ -3688,7 +4093,7 @@ var dco_default = "allowRemediationCommits:\n individual: true\n";
|
|
|
3688
4093
|
var dependabot_default = "version: 2\nupdates:\n - package-ecosystem: npm\n directory: /\n schedule:\n interval: weekly\n commit-message:\n prefix: \"chore(deps)\"\n prefix-development: \"chore(deps-dev)\"\n groups:\n security-patches:\n applies-to: security-updates\n patterns:\n - \"*\"\n all-dependencies:\n update-types:\n - minor\n - patch\n\n - package-ecosystem: github-actions\n directory: /\n schedule:\n interval: weekly\n commit-message:\n prefix: \"chore(deps)\"\n groups:\n all-actions:\n patterns:\n - \"*\"\n";
|
|
3689
4094
|
//#endregion
|
|
3690
4095
|
//#region src/templates/labeler.yml
|
|
3691
|
-
var labeler_default = "bug:\n -
|
|
4096
|
+
var labeler_default = "bug:\n - \"^fix\"\n\nchore:\n - '^chore(?!\\(deps)'\n\nci:\n - \"^ci\"\n\ndependencies:\n - '^chore\\(deps'\n\ndocumentation:\n - \"^docs\"\n\nenhancement:\n - \"^feat\"\n\nperformance:\n - \"^perf\"\n\nrefactor:\n - \"^refactor\"\n\ntest:\n - \"^test\"\n";
|
|
3692
4097
|
//#endregion
|
|
3693
4098
|
//#region src/commands/setup/agent-prompts-data.ts
|
|
3694
4099
|
/**
|
|
@@ -4503,6 +4908,10 @@ const { workflowHeader: workflowHeader$1 } = createHeader({
|
|
|
4503
4908
|
source: "packages/cli/src/commands/setup/run-setup.ts",
|
|
4504
4909
|
tool: "holocron setup"
|
|
4505
4910
|
});
|
|
4911
|
+
const { workflowHeader: thinCallerHeader } = createHeader({
|
|
4912
|
+
source: "packages/astromech/src/thin-callers.ts",
|
|
4913
|
+
tool: "holocron setup"
|
|
4914
|
+
});
|
|
4506
4915
|
async function runSetup(input) {
|
|
4507
4916
|
const print = input.print ?? ((line) => console.log(line));
|
|
4508
4917
|
const logger = getLogger();
|
|
@@ -4537,7 +4946,7 @@ async function runSetup(input) {
|
|
|
4537
4946
|
}, { skipCodes: SECURITY_SKIP_CODES[method] }));
|
|
4538
4947
|
print(formatStep(steps[steps.length - 1]));
|
|
4539
4948
|
}
|
|
4540
|
-
const usesAdvancedCodeQL = (config.tasks ?? []).map((e) => typeof e === "string" ? e : e.name).includes("
|
|
4949
|
+
const usesAdvancedCodeQL = (config.tasks ?? []).map((e) => typeof e === "string" ? e : e.name).includes("security.codeScanning");
|
|
4541
4950
|
steps.push(await runStep("source", usesAdvancedCodeQL ? "disableDefaultCodeScanning" : "enableCodeScanning", dryRun, async () => {
|
|
4542
4951
|
if (usesAdvancedCodeQL) await source.disableDefaultCodeScanning();
|
|
4543
4952
|
else return await source.enableCodeScanning();
|
|
@@ -4574,7 +4983,8 @@ async function runSetup(input) {
|
|
|
4574
4983
|
}).thinCallers();
|
|
4575
4984
|
for (const entry of tasks) {
|
|
4576
4985
|
const name = typeof entry === "string" ? entry : entry.name;
|
|
4577
|
-
|
|
4986
|
+
const isCombinedContentOnly = name === "knowledge.docs" || name === "knowledge.components";
|
|
4987
|
+
if (!KNOWN_WORKFLOWS.has(name) && !isCombinedContentOnly) {
|
|
4578
4988
|
steps.push({
|
|
4579
4989
|
capability: "source",
|
|
4580
4990
|
step: `write workflow ${name}`,
|
|
@@ -4584,12 +4994,12 @@ async function runSetup(input) {
|
|
|
4584
4994
|
print(formatStep(steps[steps.length - 1]));
|
|
4585
4995
|
continue;
|
|
4586
4996
|
}
|
|
4587
|
-
const filename =
|
|
4997
|
+
const filename = `${name}.yml`;
|
|
4588
4998
|
const content = files.get(filename);
|
|
4589
4999
|
if (content === void 0) continue;
|
|
4590
|
-
const step =
|
|
5000
|
+
const step = content.includes("workflows/preview.yml@main") ? `write workflow ${name} (with preview)` : `write workflow ${name}`;
|
|
4591
5001
|
steps.push(await runStep("source", step, dryRun, async () => {
|
|
4592
|
-
await source.writeWorkflowFile(filename, `${
|
|
5002
|
+
await source.writeWorkflowFile(filename, `${thinCallerHeader()}${content}`);
|
|
4593
5003
|
}));
|
|
4594
5004
|
print(formatStep(steps[steps.length - 1]));
|
|
4595
5005
|
}
|
|
@@ -4623,33 +5033,45 @@ async function runSetup(input) {
|
|
|
4623
5033
|
}));
|
|
4624
5034
|
print(formatStep(steps[steps.length - 1]));
|
|
4625
5035
|
steps.push(await runStep("source", "write .alexignore", dryRun, async () => {
|
|
4626
|
-
await source.writeRepoFile(".alexignore", createIgnoreConfig());
|
|
5036
|
+
await source.writeRepoFile(".alexignore", createIgnoreConfig$1());
|
|
5037
|
+
}));
|
|
5038
|
+
print(formatStep(steps[steps.length - 1]));
|
|
5039
|
+
steps.push(await runStep("source", "write .yamllint.yml", dryRun, async () => {
|
|
5040
|
+
await source.writeRepoFile(".yamllint.yml", createConfig());
|
|
5041
|
+
}));
|
|
5042
|
+
print(formatStep(steps[steps.length - 1]));
|
|
5043
|
+
steps.push(await runStep("source", "write .yamlignore", dryRun, async () => {
|
|
5044
|
+
await source.writeRepoFile(".yamlignore", createIgnoreConfig());
|
|
4627
5045
|
}));
|
|
4628
5046
|
print(formatStep(steps[steps.length - 1]));
|
|
4629
5047
|
steps.push(await runStep("source", "write .editorconfig", dryRun, async () => {
|
|
4630
|
-
await source.writeRepoFile(".editorconfig", createConfig$
|
|
5048
|
+
await source.writeRepoFile(".editorconfig", createConfig$5());
|
|
4631
5049
|
}));
|
|
4632
5050
|
print(formatStep(steps[steps.length - 1]));
|
|
4633
5051
|
steps.push(await runStep("source", "write .editorconfig-checker.json", dryRun, async () => {
|
|
4634
|
-
await source.writeRepoFile(".editorconfig-checker.json", createConfig$
|
|
5052
|
+
await source.writeRepoFile(".editorconfig-checker.json", createConfig$4());
|
|
4635
5053
|
}));
|
|
4636
5054
|
print(formatStep(steps[steps.length - 1]));
|
|
4637
5055
|
steps.push(await runStep("source", "write devmoji.config.cjs", dryRun, async () => {
|
|
4638
|
-
await source.writeRepoFile("devmoji.config.cjs", createConfig$
|
|
5056
|
+
await source.writeRepoFile("devmoji.config.cjs", createConfig$6());
|
|
4639
5057
|
}));
|
|
4640
5058
|
print(formatStep(steps[steps.length - 1]));
|
|
4641
5059
|
steps.push(await runStep("source", "write .husky/prepare-commit-msg", dryRun, async () => {
|
|
4642
|
-
await source.writeRepoFile(".husky/prepare-commit-msg", createConfig());
|
|
5060
|
+
await source.writeRepoFile(".husky/prepare-commit-msg", createConfig$1());
|
|
5061
|
+
}));
|
|
5062
|
+
print(formatStep(steps[steps.length - 1]));
|
|
5063
|
+
steps.push(await runStep("source", "write .husky/commit-msg", dryRun, async () => {
|
|
5064
|
+
await source.writeRepoFile(".husky/commit-msg", createConfig$7());
|
|
4643
5065
|
}));
|
|
4644
5066
|
print(formatStep(steps[steps.length - 1]));
|
|
4645
5067
|
if (hooksEnabled) {
|
|
4646
5068
|
steps.push(await runStep("source", "write .husky/pre-push", dryRun, async () => {
|
|
4647
|
-
await source.writeRepoFile(".husky/pre-push", createConfig$
|
|
5069
|
+
await source.writeRepoFile(".husky/pre-push", createConfig$2(config.holocronScript));
|
|
4648
5070
|
return "runs `holocron ci` before push — bypass with `git push --no-verify`";
|
|
4649
5071
|
}));
|
|
4650
5072
|
print(formatStep(steps[steps.length - 1]));
|
|
4651
5073
|
steps.push(await runStep("source", "write .husky/pre-commit", dryRun, async () => {
|
|
4652
|
-
await source.writeRepoFile(".husky/pre-commit", createConfig$
|
|
5074
|
+
await source.writeRepoFile(".husky/pre-commit", createConfig$3());
|
|
4653
5075
|
return "runs GitLeaks (staged-only) + lint-staged before commit";
|
|
4654
5076
|
}));
|
|
4655
5077
|
print(formatStep(steps[steps.length - 1]));
|
|
@@ -4676,7 +5098,7 @@ async function runSetup(input) {
|
|
|
4676
5098
|
print(formatStep(steps[steps.length - 1]));
|
|
4677
5099
|
}
|
|
4678
5100
|
{
|
|
4679
|
-
const hasTestWorkflow = (config.tasks ?? []).map((e) => typeof e === "string" ? e : e.name).includes("
|
|
5101
|
+
const hasTestWorkflow = (config.tasks ?? []).map((e) => typeof e === "string" ? e : e.name).includes("verification.unitTests");
|
|
4680
5102
|
const existing = await readFile(join(input.context.repoRoot, "codecov.yml"), "utf8").catch(() => null);
|
|
4681
5103
|
if (!hasTestWorkflow && existing == null) steps.push({
|
|
4682
5104
|
capability: "source",
|
|
@@ -4695,6 +5117,23 @@ async function runSetup(input) {
|
|
|
4695
5117
|
}));
|
|
4696
5118
|
print(formatStep(steps[steps.length - 1]));
|
|
4697
5119
|
}
|
|
5120
|
+
{
|
|
5121
|
+
const content = createAstromech({
|
|
5122
|
+
cwd: input.context.repoRoot,
|
|
5123
|
+
config: { tasks: config.tasks },
|
|
5124
|
+
logger
|
|
5125
|
+
}).turboConfig();
|
|
5126
|
+
if (content === null) steps.push({
|
|
5127
|
+
capability: "source",
|
|
5128
|
+
step: "write turbo.json",
|
|
5129
|
+
status: "skip",
|
|
5130
|
+
message: "no fan-out-eligible tasks configured"
|
|
5131
|
+
});
|
|
5132
|
+
else steps.push(await runStep("source", "write turbo.json", dryRun, async () => {
|
|
5133
|
+
await source.writeRepoFile("turbo.json", content);
|
|
5134
|
+
}));
|
|
5135
|
+
print(formatStep(steps[steps.length - 1]));
|
|
5136
|
+
}
|
|
4698
5137
|
if (source.syncLabels) {
|
|
4699
5138
|
steps.push(await runStep("source", "sync labels", dryRun, async () => {
|
|
4700
5139
|
return source.syncLabels(CANONICAL_LABELS, STALE_LABELS);
|
|
@@ -4833,7 +5272,7 @@ async function runSetup(input) {
|
|
|
4833
5272
|
if (loader.has("deployment")) {
|
|
4834
5273
|
const deploy = loader.get("deployment");
|
|
4835
5274
|
print(style.step("deployment"));
|
|
4836
|
-
const deployEntry = (config.tasks ?? []).map((e) => typeof e === "string" ? { name: e } : e).find((e) => e.name === "deploy");
|
|
5275
|
+
const deployEntry = (config.tasks ?? []).map((e) => typeof e === "string" ? { name: e } : e).find((e) => e.name === "delivery.deploy" || e.name === "knowledge.docs" || e.name === "knowledge.components");
|
|
4837
5276
|
const previewCfg = deployEntry?.with ? extractPreviewConfig(deployEntry.with, {
|
|
4838
5277
|
org: config.org,
|
|
4839
5278
|
domain: config.domain
|
|
@@ -5557,7 +5996,7 @@ async function runSyncWiki(input) {
|
|
|
5557
5996
|
//#endregion
|
|
5558
5997
|
//#region src/commands/sync.ts
|
|
5559
5998
|
const { workflowHeader } = createHeader({
|
|
5560
|
-
source: "packages/
|
|
5999
|
+
source: "packages/astromech/src/thin-callers.ts",
|
|
5561
6000
|
tool: "holocron sync"
|
|
5562
6001
|
});
|
|
5563
6002
|
const SYNC_STEPS = [
|
|
@@ -5571,6 +6010,7 @@ const SYNC_STEPS = [
|
|
|
5571
6010
|
"readme",
|
|
5572
6011
|
"workflows",
|
|
5573
6012
|
"scripts",
|
|
6013
|
+
"turbo",
|
|
5574
6014
|
"wiki"
|
|
5575
6015
|
];
|
|
5576
6016
|
const LOCAL_STEPS = /* @__PURE__ */ new Set([
|
|
@@ -5580,6 +6020,7 @@ const LOCAL_STEPS = /* @__PURE__ */ new Set([
|
|
|
5580
6020
|
"readme",
|
|
5581
6021
|
"workflows",
|
|
5582
6022
|
"scripts",
|
|
6023
|
+
"turbo",
|
|
5583
6024
|
"wiki"
|
|
5584
6025
|
]);
|
|
5585
6026
|
async function runSync(input) {
|
|
@@ -5728,6 +6169,7 @@ async function runSync(input) {
|
|
|
5728
6169
|
"readme",
|
|
5729
6170
|
"workflows",
|
|
5730
6171
|
"scripts",
|
|
6172
|
+
"turbo",
|
|
5731
6173
|
"wiki"
|
|
5732
6174
|
]) {
|
|
5733
6175
|
if (requestedSteps !== void 0 && !requestedSteps.includes(stepName)) continue;
|
|
@@ -5832,7 +6274,8 @@ async function runSync(input) {
|
|
|
5832
6274
|
}).thinCallers();
|
|
5833
6275
|
for (const entry of taskEntries) {
|
|
5834
6276
|
const name = typeof entry === "string" ? entry : entry.name;
|
|
5835
|
-
|
|
6277
|
+
const isCombinedContentOnly = name === "knowledge.docs" || name === "knowledge.components";
|
|
6278
|
+
if (!KNOWN_WORKFLOWS.has(name) && !isCombinedContentOnly) {
|
|
5836
6279
|
steps.push({
|
|
5837
6280
|
capability: "local",
|
|
5838
6281
|
step: `sync workflow ${name}`,
|
|
@@ -5842,10 +6285,10 @@ async function runSync(input) {
|
|
|
5842
6285
|
print(formatSyncStep(steps[steps.length - 1]));
|
|
5843
6286
|
continue;
|
|
5844
6287
|
}
|
|
5845
|
-
const filename =
|
|
6288
|
+
const filename = `${name}.yml`;
|
|
5846
6289
|
const content = files.get(filename);
|
|
5847
6290
|
if (content === void 0) continue;
|
|
5848
|
-
const step = `sync workflow ${name}${
|
|
6291
|
+
const step = `sync workflow ${name}${content.includes("workflows/preview.yml@main") ? " (with preview)" : ""}`;
|
|
5849
6292
|
steps.push(await runSyncStep("local", step, dryRun, async () => {
|
|
5850
6293
|
await writeWorkflowFile(input.context.repoRoot, filename, `${workflowHeader()}${content}`);
|
|
5851
6294
|
}));
|
|
@@ -5877,6 +6320,27 @@ async function runSync(input) {
|
|
|
5877
6320
|
}));
|
|
5878
6321
|
print(formatSyncStep(steps[steps.length - 1]));
|
|
5879
6322
|
}
|
|
6323
|
+
if (stepName === "turbo") {
|
|
6324
|
+
const content = createAstromech({
|
|
6325
|
+
cwd: input.context.repoRoot,
|
|
6326
|
+
config: { tasks: config.tasks },
|
|
6327
|
+
logger
|
|
6328
|
+
}).turboConfig();
|
|
6329
|
+
if (content === null) {
|
|
6330
|
+
steps.push({
|
|
6331
|
+
capability: "local",
|
|
6332
|
+
step: "sync turbo.json",
|
|
6333
|
+
status: "skip",
|
|
6334
|
+
message: "no fan-out-eligible tasks configured"
|
|
6335
|
+
});
|
|
6336
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
6337
|
+
} else {
|
|
6338
|
+
steps.push(await runSyncStep("local", "sync turbo.json", dryRun, async () => {
|
|
6339
|
+
await writeFile(join(input.context.repoRoot, "turbo.json"), content, "utf8");
|
|
6340
|
+
}));
|
|
6341
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
5880
6344
|
if (stepName === "wiki") {
|
|
5881
6345
|
const result = await runSyncWiki({
|
|
5882
6346
|
loaded: input.loaded,
|
|
@@ -7720,24 +8184,59 @@ try {
|
|
|
7720
8184
|
}).command("publish", "Publish @theholocron/* packages to npm", (y) => y.option("initial", {
|
|
7721
8185
|
type: "boolean",
|
|
7722
8186
|
default: false,
|
|
7723
|
-
describe: "One-shot bootstrap publish for trusted-publishing-eligible packages (npm needs the package to exist before Trusted Publishing can be configured for it).
|
|
8187
|
+
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."
|
|
7724
8188
|
}).option("tag", {
|
|
7725
8189
|
type: "string",
|
|
7726
|
-
|
|
7727
|
-
describe: "npm distribution tag (defaults to alpha)"
|
|
8190
|
+
describe: "npm distribution tag. Defaults to alpha for --initial, latest for a steady-state publish."
|
|
7728
8191
|
}).option("otp", {
|
|
7729
8192
|
type: "string",
|
|
7730
8193
|
describe: "One-time password from your authenticator (required if npm needs 2FA for writes)"
|
|
8194
|
+
}).option("sync-trust", {
|
|
8195
|
+
type: "array",
|
|
8196
|
+
describe: "Migrate npm Trusted Publisher config to a new workflow filename across every public package: --sync-trust <old-file> <new-file>. See issue #689 — npm requires fresh interactive 2FA per package; re-run after approving the printed authUrl if it stops early."
|
|
8197
|
+
}).option("repo", {
|
|
8198
|
+
type: "string",
|
|
8199
|
+
describe: "owner/repo for Trusted Publisher config (--sync-trust). Defaults to theholocron/holocron."
|
|
8200
|
+
}).option("provenance", {
|
|
8201
|
+
type: "boolean",
|
|
8202
|
+
default: true,
|
|
8203
|
+
describe: "Publish with --provenance (steady-state only). Default on — free under CI/OIDC."
|
|
8204
|
+
}).option("skip-already-published", {
|
|
8205
|
+
type: "boolean",
|
|
8206
|
+
default: false,
|
|
8207
|
+
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."
|
|
7731
8208
|
}), async (argv) => {
|
|
7732
|
-
if (
|
|
7733
|
-
|
|
7734
|
-
|
|
8209
|
+
if (argv.syncTrust) {
|
|
8210
|
+
const [oldFile, newFile] = argv.syncTrust;
|
|
8211
|
+
if (!oldFile || !newFile) {
|
|
8212
|
+
getLogger().error("publish --sync-trust requires exactly two values: <old-file> <new-file>");
|
|
8213
|
+
process.exitCode = 1;
|
|
8214
|
+
return;
|
|
8215
|
+
}
|
|
8216
|
+
if ((await runSyncTrust({
|
|
8217
|
+
cwd: argv.cwd,
|
|
8218
|
+
oldFile,
|
|
8219
|
+
newFile,
|
|
8220
|
+
dryRun: argv.dryRun,
|
|
8221
|
+
...argv.repo ? { repo: argv.repo } : {}
|
|
8222
|
+
})).status === "fail") process.exitCode = 1;
|
|
8223
|
+
return;
|
|
8224
|
+
}
|
|
8225
|
+
if (argv.initial) {
|
|
8226
|
+
if ((await runPublish({
|
|
8227
|
+
cwd: argv.cwd,
|
|
8228
|
+
dryRun: argv.dryRun,
|
|
8229
|
+
...argv.tag ? { tag: argv.tag } : {},
|
|
8230
|
+
...argv.otp ? { otp: argv.otp } : {}
|
|
8231
|
+
})).status === "fail") process.exitCode = 1;
|
|
7735
8232
|
return;
|
|
7736
8233
|
}
|
|
7737
|
-
if ((await
|
|
8234
|
+
if ((await runSteadyPublish({
|
|
7738
8235
|
cwd: argv.cwd,
|
|
7739
|
-
tag: argv.tag,
|
|
7740
8236
|
dryRun: argv.dryRun,
|
|
8237
|
+
provenance: argv.provenance,
|
|
8238
|
+
skipAlreadyPublished: argv.skipAlreadyPublished,
|
|
8239
|
+
...argv.tag ? { tag: argv.tag } : {},
|
|
7741
8240
|
...argv.otp ? { otp: argv.otp } : {}
|
|
7742
8241
|
})).status === "fail") process.exitCode = 1;
|
|
7743
8242
|
}).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", {
|