@theholocron/cli 4.17.0 → 4.17.2
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/README.md +1 -1
- package/dist/cli.mjs +252 -265
- package/dist/cli.mjs.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -424,7 +424,7 @@ holds the orchestration + the Holocron-specific credential resolution
|
|
|
424
424
|
`astromech.reusableTemplates()` to `theholocron/.github` (a pure sync target —
|
|
425
425
|
never hand-edit its `.github/workflows/*`).
|
|
426
426
|
- `src/commands/` — `setup`, `sync`, `doctor`, `deploy`, `secret set`,
|
|
427
|
-
`secrets sync`, `
|
|
427
|
+
`secrets sync`, `publish`, `bump-versions`, `sync-github`, `upgrade node`,
|
|
428
428
|
`upgrade deps`, `plugin create`, `auth`
|
|
429
429
|
|
|
430
430
|
## Status
|
package/dist/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import path, { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { checkbox, input, select } from "@inquirer/prompts";
|
|
6
|
-
import { KNOWN_WORKFLOWS, createAstromech, extractPreviewConfig, reusableTemplates } from "@theholocron/astromech";
|
|
6
|
+
import { KNOWN_WORKFLOWS, createAstromech, extractPreviewConfig, readWorkspacePackages, reusableTemplates } from "@theholocron/astromech";
|
|
7
7
|
import { loadTasksConfig } from "@theholocron/astromech/config";
|
|
8
8
|
import yargs from "yargs";
|
|
9
9
|
import { hideBin } from "yargs/helpers";
|
|
@@ -1208,8 +1208,8 @@ const COMMAND_CONTEXTS = {
|
|
|
1208
1208
|
"auth unset": "global",
|
|
1209
1209
|
"auth list": "global",
|
|
1210
1210
|
"auth check": "global",
|
|
1211
|
-
"
|
|
1212
|
-
|
|
1211
|
+
"bump-versions": "global",
|
|
1212
|
+
publish: "global",
|
|
1213
1213
|
"skills install": "global",
|
|
1214
1214
|
"skills remove": "global",
|
|
1215
1215
|
"skills update": "global",
|
|
@@ -1894,177 +1894,6 @@ async function runNpmBumpVersions(input) {
|
|
|
1894
1894
|
};
|
|
1895
1895
|
}
|
|
1896
1896
|
//#endregion
|
|
1897
|
-
//#region src/commands/npm-publish-initial.ts
|
|
1898
|
-
/**
|
|
1899
|
-
* `holocron npm publish-initial` — bottles up the chicken-and-egg
|
|
1900
|
-
* bootstrap that every new npm-published holocron monorepo hits.
|
|
1901
|
-
*
|
|
1902
|
-
* npm requires a package to exist before Trusted Publishing can be
|
|
1903
|
-
* configured on it. So the first publish has to happen outside the
|
|
1904
|
-
* OIDC flow — using either a browser-auth session (`npm login
|
|
1905
|
-
* --auth-type=web`) or an ephemeral automation token. This command
|
|
1906
|
-
* runs the publish step + tells you exactly what to do next.
|
|
1907
|
-
*
|
|
1908
|
-
* Workflow:
|
|
1909
|
-
*
|
|
1910
|
-
* $ npm login --auth-type=web # one-time, browser-based
|
|
1911
|
-
* $ pnpm install --frozen-lockfile
|
|
1912
|
-
* $ pnpm build
|
|
1913
|
-
* $ pnpm exec tsx packages/cli/src/cli.ts npm publish-initial
|
|
1914
|
-
*
|
|
1915
|
-
* The command itself only handles the publish step + the post-publish
|
|
1916
|
-
* Trusted Publisher setup reminder. `pnpm install` + `pnpm build`
|
|
1917
|
-
* stay outside the command (no pnpm-inside-pnpm).
|
|
1918
|
-
*
|
|
1919
|
-
* If `NPM_TOKEN` is detected in env, the command prints a final
|
|
1920
|
-
* "revoke this token at <url>" reminder — same pattern as `rando vc
|
|
1921
|
-
* setup` for the ephemeral GH admin PAT.
|
|
1922
|
-
*/
|
|
1923
|
-
async function runNpmPublishInitial(input = {}) {
|
|
1924
|
-
const print = input.print ?? ((line) => console.log(line));
|
|
1925
|
-
const logger = input.logger ?? getLogger();
|
|
1926
|
-
const cwd = input.cwd ?? process.cwd();
|
|
1927
|
-
const tag = input.tag ?? "alpha";
|
|
1928
|
-
const dryRun = input.dryRun ?? false;
|
|
1929
|
-
const otp = input.otp;
|
|
1930
|
-
const env = makeEnv(input.env);
|
|
1931
|
-
const exec = input.exec ?? defaultExec$2;
|
|
1932
|
-
const publishArgs = [
|
|
1933
|
-
"-r",
|
|
1934
|
-
"--filter=./packages/*",
|
|
1935
|
-
"publish",
|
|
1936
|
-
"--access",
|
|
1937
|
-
"public",
|
|
1938
|
-
"--no-git-checks",
|
|
1939
|
-
"--tag",
|
|
1940
|
-
tag,
|
|
1941
|
-
...otp ? ["--otp", otp] : []
|
|
1942
|
-
];
|
|
1943
|
-
print(`Holocron npm publish-initial${dryRun ? " (dry-run)" : ""}`);
|
|
1944
|
-
print(` cwd: ${cwd}`);
|
|
1945
|
-
print(` tag: ${tag}`);
|
|
1946
|
-
logger.info({
|
|
1947
|
-
tag,
|
|
1948
|
-
dryRun: dryRun || void 0
|
|
1949
|
-
}, "npm publish-initial: start");
|
|
1950
|
-
if (otp) print(` otp: <${otp.length} chars>`);
|
|
1951
|
-
print("");
|
|
1952
|
-
print(" → verifying npm auth (`npm whoami`)…");
|
|
1953
|
-
const whoami = await exec("npm", ["whoami"], { cwd });
|
|
1954
|
-
if (whoami.exitCode !== 0) {
|
|
1955
|
-
const message = "npm is not authenticated. Run `npm login --auth-type=web` (browser flow, no token stored) or `npm login`, then re-run this command.";
|
|
1956
|
-
print(` ✗ ${message}`);
|
|
1957
|
-
const packageNames = input.packages ?? discoverPublicPackages(cwd);
|
|
1958
|
-
logger.warn({ reason: "npm not authenticated" }, "npm publish-initial: done");
|
|
1959
|
-
return {
|
|
1960
|
-
status: "fail",
|
|
1961
|
-
message,
|
|
1962
|
-
packageNames
|
|
1963
|
-
};
|
|
1964
|
-
}
|
|
1965
|
-
print(` ✓ authed as ${whoami.stdout.trim() || "<unknown>"}`);
|
|
1966
|
-
const packageNames = input.packages ?? discoverPublicPackages(cwd);
|
|
1967
|
-
const repoName = input.repoName ?? await resolveRepoName(cwd, exec);
|
|
1968
|
-
if (dryRun) {
|
|
1969
|
-
print("");
|
|
1970
|
-
print(" … (dry-run) skipping actual publish");
|
|
1971
|
-
print(` would run: pnpm ${publishArgs.join(" ")}`);
|
|
1972
|
-
printNextSteps$1(print, env, packageNames, repoName);
|
|
1973
|
-
logger.info({
|
|
1974
|
-
tag,
|
|
1975
|
-
status: "dry-run",
|
|
1976
|
-
packages: packageNames.length
|
|
1977
|
-
}, "npm publish-initial: done");
|
|
1978
|
-
return {
|
|
1979
|
-
status: "dry-run",
|
|
1980
|
-
message: "dry-run — no publish executed",
|
|
1981
|
-
packageNames
|
|
1982
|
-
};
|
|
1983
|
-
}
|
|
1984
|
-
print("");
|
|
1985
|
-
print(" → publishing all public @theholocron/* packages…");
|
|
1986
|
-
const publish = await exec("pnpm", publishArgs, { cwd });
|
|
1987
|
-
if (publish.exitCode !== 0) {
|
|
1988
|
-
const message = `publish failed (exit ${publish.exitCode}): ${publish.stderr.trim() || publish.stdout.trim() || "no output"}`;
|
|
1989
|
-
print(` ✗ ${message}`);
|
|
1990
|
-
if (publish.stdout.includes("EOTP") || publish.stderr.includes("EOTP")) {
|
|
1991
|
-
print("");
|
|
1992
|
-
print(" → hint: your npm account requires 2FA for writes. Re-run with `--otp <code>`:");
|
|
1993
|
-
print(` pnpm exec tsx packages/cli/src/cli.ts npm publish-initial --otp <6-digit-code>`);
|
|
1994
|
-
}
|
|
1995
|
-
logger.warn({
|
|
1996
|
-
tag,
|
|
1997
|
-
reason: message
|
|
1998
|
-
}, "npm publish-initial: done");
|
|
1999
|
-
return {
|
|
2000
|
-
status: "fail",
|
|
2001
|
-
message,
|
|
2002
|
-
packageNames
|
|
2003
|
-
};
|
|
2004
|
-
}
|
|
2005
|
-
print(" ✓ publish complete");
|
|
2006
|
-
printNextSteps$1(print, env, packageNames, repoName);
|
|
2007
|
-
logger.info({
|
|
2008
|
-
tag,
|
|
2009
|
-
status: "ok",
|
|
2010
|
-
packages: packageNames.length
|
|
2011
|
-
}, "npm publish-initial: done");
|
|
2012
|
-
return {
|
|
2013
|
-
status: "ok",
|
|
2014
|
-
packageNames
|
|
2015
|
-
};
|
|
2016
|
-
}
|
|
2017
|
-
function discoverPublicPackages(cwd) {
|
|
2018
|
-
const packagesDir = join(cwd, "packages");
|
|
2019
|
-
if (!existsSync(packagesDir)) return [];
|
|
2020
|
-
return readdirSync(packagesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).flatMap((e) => {
|
|
2021
|
-
const pkgPath = join(packagesDir, e.name, "package.json");
|
|
2022
|
-
if (!existsSync(pkgPath)) return [];
|
|
2023
|
-
try {
|
|
2024
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
2025
|
-
return !pkg.private && pkg.name ? [pkg.name] : [];
|
|
2026
|
-
} catch {
|
|
2027
|
-
return [];
|
|
2028
|
-
}
|
|
2029
|
-
});
|
|
2030
|
-
}
|
|
2031
|
-
async function resolveRepoName(cwd, exec) {
|
|
2032
|
-
const result = await exec("git", [
|
|
2033
|
-
"remote",
|
|
2034
|
-
"get-url",
|
|
2035
|
-
"origin"
|
|
2036
|
-
], { cwd });
|
|
2037
|
-
if (result.exitCode !== 0) return "unknown";
|
|
2038
|
-
return /[/:]([^/:]+?)(?:\.git)?$/.exec(result.stdout.trim())?.[1] ?? "unknown";
|
|
2039
|
-
}
|
|
2040
|
-
function printNextSteps$1(print, env, packageNames, repoName) {
|
|
2041
|
-
print("");
|
|
2042
|
-
print(" → next: configure Trusted Publisher for each package on npm:");
|
|
2043
|
-
for (const name of packageNames) print(` https://www.npmjs.com/package/${name}/access`);
|
|
2044
|
-
print(` Publisher: GitHub Actions Org: theholocron Repo: ${repoName} Workflow: release.yml`);
|
|
2045
|
-
if (env.get("NPM_TOKEN")) {
|
|
2046
|
-
print("");
|
|
2047
|
-
print(" → cleanup: $NPM_TOKEN was used. Revoke it now (no API for self-revoke; UI-only):");
|
|
2048
|
-
print(" https://www.npmjs.com/settings/~/tokens");
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
const defaultExec$2 = async (cmd, args, opts) => {
|
|
2052
|
-
const result = spawnSync(cmd, args, {
|
|
2053
|
-
cwd: opts.cwd,
|
|
2054
|
-
encoding: "utf8",
|
|
2055
|
-
stdio: [
|
|
2056
|
-
"inherit",
|
|
2057
|
-
"pipe",
|
|
2058
|
-
"pipe"
|
|
2059
|
-
]
|
|
2060
|
-
});
|
|
2061
|
-
return {
|
|
2062
|
-
exitCode: result.status ?? -1,
|
|
2063
|
-
stdout: result.stdout ?? "",
|
|
2064
|
-
stderr: result.stderr ?? ""
|
|
2065
|
-
};
|
|
2066
|
-
};
|
|
2067
|
-
//#endregion
|
|
2068
1897
|
//#region src/commands/plugin-create/template-inputs.ts
|
|
2069
1898
|
/** Derive the standard defaults from a slug + vendor name. */
|
|
2070
1899
|
function deriveDefaults$1(input) {
|
|
@@ -3037,7 +2866,7 @@ function runPluginCreate(input) {
|
|
|
3037
2866
|
filesWritten.push(resolvedPath);
|
|
3038
2867
|
}
|
|
3039
2868
|
if (!input.dryRun && !input.noVerify) {
|
|
3040
|
-
const execFn = input.exec ?? defaultExec$
|
|
2869
|
+
const execFn = input.exec ?? defaultExec$2;
|
|
3041
2870
|
const pkg = `@theholocron/holocron-plugin-${inputs.slug}`;
|
|
3042
2871
|
print("");
|
|
3043
2872
|
print(" Verifying scaffold…");
|
|
@@ -3088,7 +2917,7 @@ function runPluginCreate(input) {
|
|
|
3088
2917
|
};
|
|
3089
2918
|
}
|
|
3090
2919
|
}
|
|
3091
|
-
if (!input.dryRun) printNextSteps(print, inputs);
|
|
2920
|
+
if (!input.dryRun) printNextSteps$1(print, inputs);
|
|
3092
2921
|
logger.info({
|
|
3093
2922
|
slug: inputs.slug,
|
|
3094
2923
|
capability: inputs.capability,
|
|
@@ -3137,13 +2966,13 @@ function defaultWrite(filepath, content) {
|
|
|
3137
2966
|
mkdirSync(path.dirname(filepath), { recursive: true });
|
|
3138
2967
|
writeFileSync(filepath, content, "utf8");
|
|
3139
2968
|
}
|
|
3140
|
-
function defaultExec$
|
|
2969
|
+
function defaultExec$2(cmd, args, opts) {
|
|
3141
2970
|
execFileSync(cmd, args, {
|
|
3142
2971
|
cwd: opts.cwd,
|
|
3143
2972
|
stdio: opts.stdio
|
|
3144
2973
|
});
|
|
3145
2974
|
}
|
|
3146
|
-
function printNextSteps(print, inputs) {
|
|
2975
|
+
function printNextSteps$1(print, inputs) {
|
|
3147
2976
|
print("");
|
|
3148
2977
|
print(` Scaffolded @theholocron/holocron-plugin-${inputs.slug} (18 files).`);
|
|
3149
2978
|
print("");
|
|
@@ -3157,6 +2986,235 @@ function printNextSteps(print, inputs) {
|
|
|
3157
2986
|
print(" 7. Commit + push when capability is functionally complete.");
|
|
3158
2987
|
}
|
|
3159
2988
|
//#endregion
|
|
2989
|
+
//#region src/commands/publish.ts
|
|
2990
|
+
/**
|
|
2991
|
+
* `holocron publish --initial` — bottles up the chicken-and-egg bootstrap
|
|
2992
|
+
* that every new npm-published holocron monorepo hits.
|
|
2993
|
+
*
|
|
2994
|
+
* npm requires a package to exist before Trusted Publishing can be
|
|
2995
|
+
* configured on it. So the first publish has to happen outside the OIDC
|
|
2996
|
+
* flow — a browser-auth session (`npm login --auth-type=web`) or an
|
|
2997
|
+
* ephemeral automation token. This command drives the login step itself
|
|
2998
|
+
* (when `npm whoami` shows you're not authenticated), runs the publish
|
|
2999
|
+
* step, and tells you exactly what to do next.
|
|
3000
|
+
*
|
|
3001
|
+
* Workflow:
|
|
3002
|
+
*
|
|
3003
|
+
* $ pnpm install --frozen-lockfile
|
|
3004
|
+
* $ pnpm build
|
|
3005
|
+
* $ pnpm exec tsx packages/cli/src/cli.ts publish --initial
|
|
3006
|
+
*
|
|
3007
|
+
* `npm login --auth-type=web` runs automatically the first time — no
|
|
3008
|
+
* separate manual step. `pnpm install` + `pnpm build` stay outside the
|
|
3009
|
+
* command (no pnpm-inside-pnpm).
|
|
3010
|
+
*
|
|
3011
|
+
* `--initial` is required today — this command only implements the
|
|
3012
|
+
* bootstrap publish. A non-initial `holocron publish` (for a manual publish
|
|
3013
|
+
* outside the semantic-release/OIDC steady state) isn't built yet; the flag
|
|
3014
|
+
* exists so the surface doesn't need another rename when it is.
|
|
3015
|
+
*
|
|
3016
|
+
* If `NPM_TOKEN` is detected in env, the command prints a final
|
|
3017
|
+
* "revoke this token at <url>" reminder — same pattern as `rando vc
|
|
3018
|
+
* setup` for the ephemeral GH admin PAT.
|
|
3019
|
+
*/
|
|
3020
|
+
async function runPublish(input = {}) {
|
|
3021
|
+
const print = input.print ?? ((line) => console.log(line));
|
|
3022
|
+
const logger = input.logger ?? getLogger();
|
|
3023
|
+
const cwd = input.cwd ?? process.cwd();
|
|
3024
|
+
const tag = input.tag ?? "alpha";
|
|
3025
|
+
const dryRun = input.dryRun ?? false;
|
|
3026
|
+
const otp = input.otp;
|
|
3027
|
+
const env = makeEnv(input.env);
|
|
3028
|
+
const exec = input.exec ?? defaultExec$1;
|
|
3029
|
+
const login = input.login ?? defaultLogin;
|
|
3030
|
+
const publishArgs = [
|
|
3031
|
+
...hasPackagesDir(cwd) ? ["-r", "--filter=./packages/*"] : [],
|
|
3032
|
+
"publish",
|
|
3033
|
+
"--access",
|
|
3034
|
+
"public",
|
|
3035
|
+
"--no-git-checks",
|
|
3036
|
+
"--tag",
|
|
3037
|
+
tag,
|
|
3038
|
+
...otp ? ["--otp", otp] : []
|
|
3039
|
+
];
|
|
3040
|
+
print(`Holocron publish --initial${dryRun ? " (dry-run)" : ""}`);
|
|
3041
|
+
print(` cwd: ${cwd}`);
|
|
3042
|
+
print(` tag: ${tag}`);
|
|
3043
|
+
logger.info({
|
|
3044
|
+
tag,
|
|
3045
|
+
dryRun: dryRun || void 0
|
|
3046
|
+
}, "publish --initial: start");
|
|
3047
|
+
if (otp) print(` otp: <${otp.length} chars>`);
|
|
3048
|
+
print("");
|
|
3049
|
+
print(" → verifying npm auth (`npm whoami`)…");
|
|
3050
|
+
let whoami = await exec("npm", ["whoami"], { cwd });
|
|
3051
|
+
if (whoami.exitCode !== 0) {
|
|
3052
|
+
print(" not authenticated — running `npm login --auth-type=web`…");
|
|
3053
|
+
logger.info({}, "publish --initial: npm login");
|
|
3054
|
+
if ((await login(cwd)).exitCode !== 0) {
|
|
3055
|
+
const message = "npm login failed. Run `npm login --auth-type=web` (or `npm login`) manually, then re-run.";
|
|
3056
|
+
print(` ✗ ${message}`);
|
|
3057
|
+
const packageNames = input.packages ?? discoverPublicPackages(cwd);
|
|
3058
|
+
logger.warn({ reason: "npm login failed" }, "publish --initial: done");
|
|
3059
|
+
return {
|
|
3060
|
+
status: "fail",
|
|
3061
|
+
message,
|
|
3062
|
+
packageNames
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
whoami = await exec("npm", ["whoami"], { cwd });
|
|
3066
|
+
if (whoami.exitCode !== 0) {
|
|
3067
|
+
const message = "npm login completed but `npm whoami` still fails — check your npm account.";
|
|
3068
|
+
print(` ✗ ${message}`);
|
|
3069
|
+
const packageNames = input.packages ?? discoverPublicPackages(cwd);
|
|
3070
|
+
logger.warn({ reason: "npm whoami still fails after login" }, "publish --initial: done");
|
|
3071
|
+
return {
|
|
3072
|
+
status: "fail",
|
|
3073
|
+
message,
|
|
3074
|
+
packageNames
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
print(` ✓ authed as ${whoami.stdout.trim() || "<unknown>"}`);
|
|
3079
|
+
const packageNames = input.packages ?? discoverPublicPackages(cwd);
|
|
3080
|
+
const repoName = input.repoName ?? await resolveRepoName(cwd, exec);
|
|
3081
|
+
if (packageNames.length === 0) {
|
|
3082
|
+
const message = "nothing to publish (no packages/* and root package.json is private or unnamed)";
|
|
3083
|
+
print(` ✗ ${message}`);
|
|
3084
|
+
logger.warn({ reason: message }, "publish --initial: done");
|
|
3085
|
+
return {
|
|
3086
|
+
status: "fail",
|
|
3087
|
+
message,
|
|
3088
|
+
packageNames
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
if (dryRun) {
|
|
3092
|
+
print("");
|
|
3093
|
+
print(" … (dry-run) skipping actual publish");
|
|
3094
|
+
print(` would run: pnpm ${publishArgs.join(" ")}`);
|
|
3095
|
+
printNextSteps(print, env, packageNames, repoName);
|
|
3096
|
+
logger.info({
|
|
3097
|
+
tag,
|
|
3098
|
+
status: "dry-run",
|
|
3099
|
+
packages: packageNames.length
|
|
3100
|
+
}, "publish --initial: done");
|
|
3101
|
+
return {
|
|
3102
|
+
status: "dry-run",
|
|
3103
|
+
message: "dry-run — no publish executed",
|
|
3104
|
+
packageNames
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3107
|
+
print("");
|
|
3108
|
+
print(" → publishing all public @theholocron/* packages…");
|
|
3109
|
+
const publish = await exec("pnpm", publishArgs, { cwd });
|
|
3110
|
+
if (publish.exitCode !== 0) {
|
|
3111
|
+
const message = `publish failed (exit ${publish.exitCode}): ${publish.stderr.trim() || publish.stdout.trim() || "no output"}`;
|
|
3112
|
+
print(` ✗ ${message}`);
|
|
3113
|
+
if (publish.stdout.includes("EOTP") || publish.stderr.includes("EOTP")) {
|
|
3114
|
+
print("");
|
|
3115
|
+
print(" → hint: your npm account requires 2FA for writes. Re-run with `--otp <code>`:");
|
|
3116
|
+
print(` pnpm exec tsx packages/cli/src/cli.ts publish --initial --otp <6-digit-code>`);
|
|
3117
|
+
}
|
|
3118
|
+
logger.warn({
|
|
3119
|
+
tag,
|
|
3120
|
+
reason: message
|
|
3121
|
+
}, "publish --initial: done");
|
|
3122
|
+
return {
|
|
3123
|
+
status: "fail",
|
|
3124
|
+
message,
|
|
3125
|
+
packageNames
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
print(" ✓ publish complete");
|
|
3129
|
+
printNextSteps(print, env, packageNames, repoName);
|
|
3130
|
+
logger.info({
|
|
3131
|
+
tag,
|
|
3132
|
+
status: "ok",
|
|
3133
|
+
packages: packageNames.length
|
|
3134
|
+
}, "publish --initial: done");
|
|
3135
|
+
return {
|
|
3136
|
+
status: "ok",
|
|
3137
|
+
packageNames
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
function hasPackagesDir(cwd) {
|
|
3141
|
+
return existsSync(join(cwd, "packages"));
|
|
3142
|
+
}
|
|
3143
|
+
/** Read a `package.json`'s `name`, when it's public (`!private && name`). */
|
|
3144
|
+
function publicPackageName(pkgPath) {
|
|
3145
|
+
if (!existsSync(pkgPath)) return void 0;
|
|
3146
|
+
try {
|
|
3147
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
3148
|
+
return !pkg.private && pkg.name ? pkg.name : void 0;
|
|
3149
|
+
} catch {
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
/**
|
|
3154
|
+
* Monorepo (`packages/` present): every public `packages/*` workspace.
|
|
3155
|
+
* Single package (no `packages/`): the root `package.json`, if it's public —
|
|
3156
|
+
* most `node-template` scaffolds are one package at the repo root.
|
|
3157
|
+
*/
|
|
3158
|
+
function discoverPublicPackages(cwd) {
|
|
3159
|
+
if (!hasPackagesDir(cwd)) {
|
|
3160
|
+
const name = publicPackageName(join(cwd, "package.json"));
|
|
3161
|
+
return name ? [name] : [];
|
|
3162
|
+
}
|
|
3163
|
+
const packagesDir = join(cwd, "packages");
|
|
3164
|
+
return readdirSync(packagesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).flatMap((e) => {
|
|
3165
|
+
const name = publicPackageName(join(packagesDir, e.name, "package.json"));
|
|
3166
|
+
return name ? [name] : [];
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
async function resolveRepoName(cwd, exec) {
|
|
3170
|
+
const result = await exec("git", [
|
|
3171
|
+
"remote",
|
|
3172
|
+
"get-url",
|
|
3173
|
+
"origin"
|
|
3174
|
+
], { cwd });
|
|
3175
|
+
if (result.exitCode !== 0) return "unknown";
|
|
3176
|
+
return /[/:]([^/:]+?)(?:\.git)?$/.exec(result.stdout.trim())?.[1] ?? "unknown";
|
|
3177
|
+
}
|
|
3178
|
+
function printNextSteps(print, env, packageNames, repoName) {
|
|
3179
|
+
print("");
|
|
3180
|
+
print(" → next: configure Trusted Publisher for each package on npm:");
|
|
3181
|
+
for (const name of packageNames) print(` https://www.npmjs.com/package/${name}/access`);
|
|
3182
|
+
print(` Publisher: GitHub Actions Org: theholocron Repo: ${repoName} Workflow: release.yml`);
|
|
3183
|
+
if (env.get("NPM_TOKEN")) {
|
|
3184
|
+
print("");
|
|
3185
|
+
print(" → cleanup: $NPM_TOKEN was used. Revoke it now (no API for self-revoke; UI-only):");
|
|
3186
|
+
print(" https://www.npmjs.com/settings/~/tokens");
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
const defaultExec$1 = async (cmd, args, opts) => {
|
|
3190
|
+
const result = spawnSync(cmd, args, {
|
|
3191
|
+
cwd: opts.cwd,
|
|
3192
|
+
encoding: "utf8",
|
|
3193
|
+
stdio: [
|
|
3194
|
+
"inherit",
|
|
3195
|
+
"pipe",
|
|
3196
|
+
"pipe"
|
|
3197
|
+
]
|
|
3198
|
+
});
|
|
3199
|
+
return {
|
|
3200
|
+
exitCode: result.status ?? -1,
|
|
3201
|
+
stdout: result.stdout ?? "",
|
|
3202
|
+
stderr: result.stderr ?? ""
|
|
3203
|
+
};
|
|
3204
|
+
};
|
|
3205
|
+
/**
|
|
3206
|
+
* Fully interactive — `npm login --auth-type=web` prints a URL and waits for
|
|
3207
|
+
* the browser flow to complete; the operator needs to see that live, so all
|
|
3208
|
+
* three stdio streams are inherited (unlike `defaultExec`, which pipes
|
|
3209
|
+
* stdout/stderr for capture).
|
|
3210
|
+
*/
|
|
3211
|
+
const defaultLogin = async (cwd) => {
|
|
3212
|
+
return { exitCode: spawnSync("npm", ["login", "--auth-type=web"], {
|
|
3213
|
+
cwd,
|
|
3214
|
+
stdio: "inherit"
|
|
3215
|
+
}).status ?? -1 };
|
|
3216
|
+
};
|
|
3217
|
+
//#endregion
|
|
3160
3218
|
//#region src/commands/secret-set.ts
|
|
3161
3219
|
async function runSecretSet(input) {
|
|
3162
3220
|
const print = input.print ?? ((line) => console.log(line));
|
|
@@ -3512,86 +3570,6 @@ function createIgnoreConfig() {
|
|
|
3512
3570
|
return `${workflowHeader$6()}${alexignore_default}`;
|
|
3513
3571
|
}
|
|
3514
3572
|
//#endregion
|
|
3515
|
-
//#region src/templates/configs/codecov/codecov.yml
|
|
3516
|
-
var codecov_default = "codecov:\n require_ci_to_pass: true\n\ncoverage:\n precision: 2\n round: down\n status:\n project:\n default:\n target: auto\n threshold: 2%\n # Infra-only PRs (dep bumps, config, lockfile) touch no coverable code,\n # so codecov produces no report to compare. Pass instead of blocking\n # forever on a required check that will never post.\n if_not_found: success\n patch:\n default:\n target: 80%\n if_not_found: success\n\ncomment:\n layout: \"reach,diff,flags,components\"\n behavior: default\n require_changes: true\n\ncomponent_management:\n default_rules:\n statuses:\n - type: patch\n target: 80%\n if_not_found: success\n individual_components:\n";
|
|
3517
|
-
//#endregion
|
|
3518
|
-
//#region src/templates/configs/codecov/utils.ts
|
|
3519
|
-
const INDIVIDUAL_COMPONENTS_MARKER = " individual_components:";
|
|
3520
|
-
function codecovComponentBlock(packages) {
|
|
3521
|
-
if (packages.length === 0) return "\n []\n";
|
|
3522
|
-
return "\n" + packages.flatMap(({ slug }) => [
|
|
3523
|
-
` - component_id: ${slug}`,
|
|
3524
|
-
` name: "${slug}"`,
|
|
3525
|
-
` paths:`,
|
|
3526
|
-
` - packages/${slug}/**`,
|
|
3527
|
-
``
|
|
3528
|
-
]).join("\n");
|
|
3529
|
-
}
|
|
3530
|
-
/**
|
|
3531
|
-
* Ensure every codecov status default carries `if_not_found: success`, so an
|
|
3532
|
-
* infra-only PR (dep bump, config, lockfile — no coverable change) passes
|
|
3533
|
-
* instead of hanging forever on a required check codecov never posts.
|
|
3534
|
-
*
|
|
3535
|
-
* Injects the key on the line after each `target:` that sits inside a status
|
|
3536
|
-
* block (`coverage.status.*.default` or a `statuses:` list item — indent ≥ 8,
|
|
3537
|
-
* or ≥ 8 for a `- type:` list entry), when the immediate sibling lines do not
|
|
3538
|
-
* already declare it.
|
|
3539
|
-
*/
|
|
3540
|
-
function ensureIfNotFound(yaml) {
|
|
3541
|
-
const lines = yaml.split("\n");
|
|
3542
|
-
const out = [];
|
|
3543
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3544
|
-
const line = lines[i];
|
|
3545
|
-
out.push(line);
|
|
3546
|
-
const m = /^(\s{8,})target:\s+\S+\s*$/.exec(line);
|
|
3547
|
-
if (!m) continue;
|
|
3548
|
-
const indent = m[1];
|
|
3549
|
-
let alreadySet = false;
|
|
3550
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
3551
|
-
const sib = lines[j];
|
|
3552
|
-
if (sib.trim() === "") continue;
|
|
3553
|
-
const sibIndent = sib.match(/^\s*/)[0].length;
|
|
3554
|
-
if (sibIndent < indent.length) break;
|
|
3555
|
-
if (sibIndent === indent.length && /^\s*if_not_found\s*:/.test(sib)) {
|
|
3556
|
-
alreadySet = true;
|
|
3557
|
-
break;
|
|
3558
|
-
}
|
|
3559
|
-
if (sibIndent === indent.length && /^\s*(- |\w)/.test(sib) && !/^\s*(threshold|flags|paths|branches)\s*:/.test(sib)) break;
|
|
3560
|
-
}
|
|
3561
|
-
if (!alreadySet) out.push(`${indent}if_not_found: success`);
|
|
3562
|
-
}
|
|
3563
|
-
return out.join("\n");
|
|
3564
|
-
}
|
|
3565
|
-
function mergeCodecovComponents(existing, packages) {
|
|
3566
|
-
const idx = existing.indexOf(INDIVIDUAL_COMPONENTS_MARKER);
|
|
3567
|
-
if (idx === -1) return ensureIfNotFound(existing);
|
|
3568
|
-
return ensureIfNotFound(existing.slice(0, idx + 24)) + codecovComponentBlock(packages);
|
|
3569
|
-
}
|
|
3570
|
-
async function readWorkspacePackages(repoRoot) {
|
|
3571
|
-
const packagesDir = join(repoRoot, "packages");
|
|
3572
|
-
const entries = await readdir(packagesDir, { withFileTypes: true }).catch(() => null);
|
|
3573
|
-
if (!entries) return [];
|
|
3574
|
-
const packages = [];
|
|
3575
|
-
for (const entry of entries) {
|
|
3576
|
-
if (!entry.isDirectory()) continue;
|
|
3577
|
-
try {
|
|
3578
|
-
const raw = await readFile(join(packagesDir, entry.name, "package.json"), "utf8");
|
|
3579
|
-
const pkg = JSON.parse(raw);
|
|
3580
|
-
if (typeof pkg.name === "string") packages.push({
|
|
3581
|
-
slug: entry.name,
|
|
3582
|
-
name: pkg.name
|
|
3583
|
-
});
|
|
3584
|
-
} catch {}
|
|
3585
|
-
}
|
|
3586
|
-
return packages.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
3587
|
-
}
|
|
3588
|
-
//#endregion
|
|
3589
|
-
//#region src/templates/configs/codecov/create-config.ts
|
|
3590
|
-
const { scaffoldHeader } = createHeader({ source: "packages/cli/src/templates/configs/codecov/create-config.ts" });
|
|
3591
|
-
function createConfig$5(packages) {
|
|
3592
|
-
return `${scaffoldHeader()}${codecov_default.trimEnd()}${codecovComponentBlock(packages)}`;
|
|
3593
|
-
}
|
|
3594
|
-
//#endregion
|
|
3595
3573
|
//#region src/templates/configs/devmoji/create-config.ts
|
|
3596
3574
|
const { workflowHeader: workflowHeader$5 } = createHeader({ source: "packages/cli/src/templates/configs/devmoji/create-config.ts" });
|
|
3597
3575
|
function createConfig$4() {
|
|
@@ -4662,7 +4640,6 @@ async function runSetup(input) {
|
|
|
4662
4640
|
}
|
|
4663
4641
|
{
|
|
4664
4642
|
const hasTestWorkflow = (config.tasks ?? []).map((e) => typeof e === "string" ? e : e.name).includes("test");
|
|
4665
|
-
const packages = await readWorkspacePackages(input.context.repoRoot);
|
|
4666
4643
|
const existing = await readFile(join(input.context.repoRoot, "codecov.yml"), "utf8").catch(() => null);
|
|
4667
4644
|
if (!hasTestWorkflow && existing == null) steps.push({
|
|
4668
4645
|
capability: "source",
|
|
@@ -4671,9 +4648,10 @@ async function runSetup(input) {
|
|
|
4671
4648
|
message: "no test workflow configured"
|
|
4672
4649
|
});
|
|
4673
4650
|
else steps.push(await runStep("source", "write codecov.yml", dryRun, async () => {
|
|
4674
|
-
const content =
|
|
4651
|
+
const content = createAstromech({ cwd: input.context.repoRoot }).codecovConfig(existing);
|
|
4675
4652
|
await source.writeRepoFile("codecov.yml", content);
|
|
4676
|
-
|
|
4653
|
+
const packageCount = readWorkspacePackages(input.context.repoRoot).length;
|
|
4654
|
+
return packageCount > 0 ? `${packageCount} components` : "no components";
|
|
4677
4655
|
}));
|
|
4678
4656
|
print(formatStep(steps[steps.length - 1]));
|
|
4679
4657
|
}
|
|
@@ -7337,7 +7315,7 @@ try {
|
|
|
7337
7315
|
project: argv.project,
|
|
7338
7316
|
...argv.repo ? { repo: argv.repo } : {}
|
|
7339
7317
|
})).status === "fail") process.exitCode = 1;
|
|
7340
|
-
}).command("
|
|
7318
|
+
}).command("bump-versions <new-version>", "Bump all non-private package versions in lockstep (semantic-release prepareCmd)", (y) => y.positional("new-version", {
|
|
7341
7319
|
type: "string",
|
|
7342
7320
|
demandOption: true,
|
|
7343
7321
|
describe: "Version to set (e.g., 4.2.0 or 2.0.0-alpha.1)"
|
|
@@ -7347,7 +7325,11 @@ try {
|
|
|
7347
7325
|
cwd: argv.cwd,
|
|
7348
7326
|
dryRun: argv.dryRun
|
|
7349
7327
|
})).status === "fail") process.exitCode = 1;
|
|
7350
|
-
}).command("publish
|
|
7328
|
+
}).command("publish", "Publish @theholocron/* packages to npm", (y) => y.option("initial", {
|
|
7329
|
+
type: "boolean",
|
|
7330
|
+
default: false,
|
|
7331
|
+
describe: "One-shot bootstrap publish for trusted-publishing-eligible packages (npm needs the package to exist before Trusted Publishing can be configured for it). Required today — a non-initial `publish` isn't implemented yet."
|
|
7332
|
+
}).option("tag", {
|
|
7351
7333
|
type: "string",
|
|
7352
7334
|
default: "alpha",
|
|
7353
7335
|
describe: "npm distribution tag (defaults to alpha)"
|
|
@@ -7355,13 +7337,18 @@ try {
|
|
|
7355
7337
|
type: "string",
|
|
7356
7338
|
describe: "One-time password from your authenticator (required if npm needs 2FA for writes)"
|
|
7357
7339
|
}), async (argv) => {
|
|
7358
|
-
if (
|
|
7340
|
+
if (!argv.initial) {
|
|
7341
|
+
getLogger().error("publish: only `--initial` is supported today. Run `holocron publish --initial`.");
|
|
7342
|
+
process.exitCode = 1;
|
|
7343
|
+
return;
|
|
7344
|
+
}
|
|
7345
|
+
if ((await runPublish({
|
|
7359
7346
|
cwd: argv.cwd,
|
|
7360
7347
|
tag: argv.tag,
|
|
7361
7348
|
dryRun: argv.dryRun,
|
|
7362
7349
|
...argv.otp ? { otp: argv.otp } : {}
|
|
7363
7350
|
})).status === "fail") process.exitCode = 1;
|
|
7364
|
-
}).
|
|
7351
|
+
}).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", {
|
|
7365
7352
|
type: "string",
|
|
7366
7353
|
array: true,
|
|
7367
7354
|
describe: "Steps to run: labels, properties, teams, topics, keywords, description, homepage, readme, workflows, scripts, wiki (default: all)"
|