@theholocron/cli 5.0.0-alpha.144 → 5.0.0-alpha.145
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 +96 -16
- package/dist/cli.mjs.map +1 -1
- package/package.json +4 -4
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { homedir, hostname, tmpdir, userInfo } from "node:os";
|
|
5
5
|
import path, { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { checkbox, confirm, input, select } from "@inquirer/prompts";
|
|
@@ -18,8 +18,8 @@ import { createLogger, isCI, parseLogLevel, resolveAxiomFromEnv } from "@theholo
|
|
|
18
18
|
import chalk from "chalk";
|
|
19
19
|
import ora from "ora";
|
|
20
20
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
21
|
-
import { access, copyFile, mkdir, readFile, readdir, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
22
|
-
import { createHash } from "node:crypto";
|
|
21
|
+
import { access, copyFile, mkdir, readFile, readdir, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
22
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
23
23
|
import { generateReadme } from "@theholocron/components-doc/markdown";
|
|
24
24
|
import { getClients, getConfigs, getDocs, getPlugins, getSkills, getThemes, getUtils } from "@theholocron/registry-doc";
|
|
25
25
|
import { createGitHubClient } from "@theholocron/github-client";
|
|
@@ -6462,6 +6462,55 @@ function runSkillsUpdate(input) {
|
|
|
6462
6462
|
return { status: exitCode === 0 ? "ok" : "fail" };
|
|
6463
6463
|
}
|
|
6464
6464
|
//#endregion
|
|
6465
|
+
//#region src/utils/find-git-root.ts
|
|
6466
|
+
/**
|
|
6467
|
+
* Walks up from `startDir` looking for a `.git` entry — a directory for a
|
|
6468
|
+
* normal checkout, a file for a worktree/submodule (contains a `gitdir:`
|
|
6469
|
+
* pointer, never followed here; its presence is enough to mark the root).
|
|
6470
|
+
*
|
|
6471
|
+
* `--cwd` picks which `holocron.config.ts` to load, which is NOT
|
|
6472
|
+
* necessarily the git repository root — a monorepo subpackage (e.g.
|
|
6473
|
+
* `packages/sentinel`) has its own config but no `.git` of its own.
|
|
6474
|
+
* Anything inherently repo-wide (`.github/workflows`, `.github/CODEOWNERS`,
|
|
6475
|
+
* `turbo.json`, "is this repo a monorepo") must resolve against the real
|
|
6476
|
+
* root, not `--cwd`, or it silently writes into a GitHub-invisible nested
|
|
6477
|
+
* `.github/` instead (holocron#1014).
|
|
6478
|
+
*
|
|
6479
|
+
* Falls back to the resolved start dir when no `.git` is found walking all
|
|
6480
|
+
* the way to the filesystem root — soft-skip, not a hard failure; every
|
|
6481
|
+
* caller already treats `--cwd` itself as the root today, so an
|
|
6482
|
+
* unexpected non-git checkout just keeps today's behavior.
|
|
6483
|
+
*
|
|
6484
|
+
* Resolves `startDir` to an absolute, symlink-free path before walking:
|
|
6485
|
+
* a relative `--cwd` (e.g. `"packages/sentinel"`) would otherwise hit
|
|
6486
|
+
* `dirname`'s fixed point at `"."` after only two steps, far short of the
|
|
6487
|
+
* real root, and a `--cwd` reached through a symlink would walk the
|
|
6488
|
+
* symlink's own lexical ancestors instead of the physical directory
|
|
6489
|
+
* tree its target actually lives in — in both cases landing on the
|
|
6490
|
+
* wrong directory, not genuinely "no `.git` found anywhere."
|
|
6491
|
+
*
|
|
6492
|
+
* `realpathSync` itself throws when `startDir` doesn't exist on disk at
|
|
6493
|
+
* all (a real possibility in tests, which often use a synthetic path
|
|
6494
|
+
* like `"/tmp/test"` that's never actually created) — caught and treated
|
|
6495
|
+
* the same as "no `.git` found," since this function's whole contract is
|
|
6496
|
+
* soft-skip, never a hard failure.
|
|
6497
|
+
*/
|
|
6498
|
+
function findGitRoot(startDir) {
|
|
6499
|
+
let resolvedStart;
|
|
6500
|
+
try {
|
|
6501
|
+
resolvedStart = realpathSync(startDir);
|
|
6502
|
+
} catch {
|
|
6503
|
+
return startDir;
|
|
6504
|
+
}
|
|
6505
|
+
let dir = resolvedStart;
|
|
6506
|
+
for (;;) {
|
|
6507
|
+
if (existsSync(`${dir}/.git`)) return dir;
|
|
6508
|
+
const parent = dirname(dir);
|
|
6509
|
+
if (parent === dir) return resolvedStart;
|
|
6510
|
+
dir = parent;
|
|
6511
|
+
}
|
|
6512
|
+
}
|
|
6513
|
+
//#endregion
|
|
6465
6514
|
//#region src/commands/sync-readme.ts
|
|
6466
6515
|
const MARKER_INSTALL_START = "<!-- holocron:installation -->";
|
|
6467
6516
|
const MARKER_INSTALL_END = "<!-- /holocron:installation -->";
|
|
@@ -6868,6 +6917,13 @@ async function runSync(input) {
|
|
|
6868
6917
|
const dryRun = input.context.dryRun ?? false;
|
|
6869
6918
|
const requestedSteps = input.steps;
|
|
6870
6919
|
const steps = [];
|
|
6920
|
+
const gitRoot = findGitRoot(input.context.repoRoot);
|
|
6921
|
+
let resolvedRepoRoot;
|
|
6922
|
+
try {
|
|
6923
|
+
resolvedRepoRoot = realpathSync(input.context.repoRoot);
|
|
6924
|
+
} catch {
|
|
6925
|
+
resolvedRepoRoot = input.context.repoRoot;
|
|
6926
|
+
}
|
|
6871
6927
|
logger.info({
|
|
6872
6928
|
config: config.name,
|
|
6873
6929
|
steps: requestedSteps ?? "all",
|
|
@@ -6908,7 +6964,7 @@ async function runSync(input) {
|
|
|
6908
6964
|
const properties = {};
|
|
6909
6965
|
const effectivePreset = repo?.protection;
|
|
6910
6966
|
if (effectivePreset && effectivePreset !== "none") properties["holocron_branch_protection_level"] = effectivePreset;
|
|
6911
|
-
const isMonorepo = await access(join(
|
|
6967
|
+
const isMonorepo = await access(join(gitRoot, "pnpm-workspace.yaml")).then(() => true).catch(() => false);
|
|
6912
6968
|
properties["monorepo"] = String(isMonorepo);
|
|
6913
6969
|
const manual = repo?.properties ?? {};
|
|
6914
6970
|
if (manual.lifecycle) properties["lifecycle"] = manual.lifecycle;
|
|
@@ -6916,12 +6972,11 @@ async function runSync(input) {
|
|
|
6916
6972
|
if (manual.runtime_environment) properties["runtime_environment"] = manual.runtime_environment;
|
|
6917
6973
|
if (manual.uses_external_packages !== void 0) properties["uses_external_packages"] = String(manual.uses_external_packages);
|
|
6918
6974
|
{
|
|
6919
|
-
const rootPackageJson = await readFile(join(
|
|
6920
|
-
const
|
|
6921
|
-
const workspacePackageJsons = await readWorkspacePackageJsons(input.context.repoRoot, workspacePackages);
|
|
6975
|
+
const rootPackageJson = await readFile(join(gitRoot, "package.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => null);
|
|
6976
|
+
const workspacePackageJsons = await readWorkspacePackageJsons(gitRoot, readWorkspacePackages(gitRoot));
|
|
6922
6977
|
properties["holocron_profile"] = deriveProfile({
|
|
6923
6978
|
rootPackageJson,
|
|
6924
|
-
repoName: basename(
|
|
6979
|
+
repoName: basename(gitRoot),
|
|
6925
6980
|
isMonorepo,
|
|
6926
6981
|
workspacePackageJsons
|
|
6927
6982
|
});
|
|
@@ -7146,7 +7201,7 @@ async function runSync(input) {
|
|
|
7146
7201
|
if (content === void 0) continue;
|
|
7147
7202
|
const step = `sync workflow ${name}${content.includes("workflows/preview.yml@main") ? " (with preview)" : ""}`;
|
|
7148
7203
|
steps.push(await runSyncStep("local", step, dryRun, async () => {
|
|
7149
|
-
await writeWorkflowFile(
|
|
7204
|
+
await writeWorkflowFile(gitRoot, filename, `${workflowHeader()}${content}`);
|
|
7150
7205
|
}));
|
|
7151
7206
|
print(formatSyncStep(steps[steps.length - 1]));
|
|
7152
7207
|
}
|
|
@@ -7182,6 +7237,16 @@ async function runSync(input) {
|
|
|
7182
7237
|
}
|
|
7183
7238
|
}
|
|
7184
7239
|
if (stepName === "turbo") {
|
|
7240
|
+
if (resolvedRepoRoot !== gitRoot) {
|
|
7241
|
+
steps.push({
|
|
7242
|
+
capability: "local",
|
|
7243
|
+
step: "sync turbo.json",
|
|
7244
|
+
status: "skip",
|
|
7245
|
+
message: "--cwd is a monorepo subpackage, not the git root — run from the root to sync turbo.json"
|
|
7246
|
+
});
|
|
7247
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
7248
|
+
continue;
|
|
7249
|
+
}
|
|
7185
7250
|
const astromechTurbo = createAstromech({
|
|
7186
7251
|
cwd: input.context.repoRoot,
|
|
7187
7252
|
config: { tasks: config.tasks },
|
|
@@ -7198,17 +7263,17 @@ async function runSync(input) {
|
|
|
7198
7263
|
print(formatSyncStep(steps[steps.length - 1]));
|
|
7199
7264
|
} else {
|
|
7200
7265
|
steps.push(await runSyncStep("local", "sync turbo.json", dryRun, async () => {
|
|
7201
|
-
await writeFile(join(
|
|
7266
|
+
await writeFile(join(gitRoot, "turbo.json"), content, "utf8");
|
|
7202
7267
|
}));
|
|
7203
7268
|
print(formatSyncStep(steps[steps.length - 1]));
|
|
7204
|
-
const rootPkgRaw = await readFile(join(
|
|
7269
|
+
const rootPkgRaw = await readFile(join(gitRoot, "package.json"), "utf8").catch(() => null);
|
|
7205
7270
|
const rootPkg = rootPkgRaw ? JSON.parse(rootPkgRaw) : null;
|
|
7206
|
-
const workspaceYaml = await readFile(join(
|
|
7271
|
+
const workspaceYaml = await readFile(join(gitRoot, "pnpm-workspace.yaml"), "utf8").catch(() => "");
|
|
7207
7272
|
const rootScripts = rootPkg?.scripts ?? {};
|
|
7208
7273
|
const workspaceFix = astromechTurbo.ensureRootWorkspaceMember(workspaceYaml, rootScripts);
|
|
7209
7274
|
if (workspaceFix.changed) {
|
|
7210
7275
|
steps.push(await runSyncStep("local", "fix pnpm-workspace.yaml (root workspace member, #692)", dryRun, async () => {
|
|
7211
|
-
await writeFile(join(
|
|
7276
|
+
await writeFile(join(gitRoot, "pnpm-workspace.yaml"), workspaceFix.content, "utf8");
|
|
7212
7277
|
}));
|
|
7213
7278
|
print(formatSyncStep(steps[steps.length - 1]));
|
|
7214
7279
|
}
|
|
@@ -7216,7 +7281,7 @@ async function runSync(input) {
|
|
|
7216
7281
|
const depFix = astromechTurbo.ensureTurboDependency(rootPkgRaw, workspaceFix.content);
|
|
7217
7282
|
if (depFix.changed) {
|
|
7218
7283
|
steps.push(await runSyncStep("local", "add turbo devDependency", dryRun, async () => {
|
|
7219
|
-
await writeFile(join(
|
|
7284
|
+
await writeFile(join(gitRoot, "package.json"), depFix.content, "utf8");
|
|
7220
7285
|
}));
|
|
7221
7286
|
print(formatSyncStep(steps[steps.length - 1]));
|
|
7222
7287
|
}
|
|
@@ -7226,7 +7291,10 @@ async function runSync(input) {
|
|
|
7226
7291
|
if (stepName === "wiki") {
|
|
7227
7292
|
const result = await runSyncWiki({
|
|
7228
7293
|
loaded: input.loaded,
|
|
7229
|
-
context:
|
|
7294
|
+
context: {
|
|
7295
|
+
...input.context,
|
|
7296
|
+
repoRoot: gitRoot
|
|
7297
|
+
}
|
|
7230
7298
|
});
|
|
7231
7299
|
steps.push(result);
|
|
7232
7300
|
print(formatSyncStep(result));
|
|
@@ -7379,10 +7447,22 @@ async function updateReadmeDescription(repoRoot, description) {
|
|
|
7379
7447
|
await writeFile(readmePath, lines.join("\n"), "utf8");
|
|
7380
7448
|
return true;
|
|
7381
7449
|
}
|
|
7450
|
+
/**
|
|
7451
|
+
* Writes via a temp file + rename in the same directory, not a direct
|
|
7452
|
+
* `writeFile` — a monorepo subpackage sync can target the same root-level
|
|
7453
|
+
* file a root sync (or another subpackage's sync) also writes, and a
|
|
7454
|
+
* process killed mid-write would otherwise leave the live GitHub Actions
|
|
7455
|
+
* workflow truncated until some later sync happens to restore it. A same-
|
|
7456
|
+
* directory rename is atomic on POSIX filesystems, so readers only ever
|
|
7457
|
+
* see the old complete content or the new complete content, never a
|
|
7458
|
+
* partial write.
|
|
7459
|
+
*/
|
|
7382
7460
|
async function writeWorkflowFile(repoRoot, filename, content) {
|
|
7383
7461
|
const dir = join(repoRoot, ".github", "workflows");
|
|
7384
7462
|
await mkdir(dir, { recursive: true });
|
|
7385
|
-
|
|
7463
|
+
const tmpPath = join(dir, `.${filename}.${randomUUID()}.tmp`);
|
|
7464
|
+
await writeFile(tmpPath, content, "utf8");
|
|
7465
|
+
await rename(tmpPath, join(dir, filename));
|
|
7386
7466
|
}
|
|
7387
7467
|
//#endregion
|
|
7388
7468
|
//#region src/telemetry/resolve.ts
|