@theholocron/cli 5.0.0-alpha.166 → 5.0.0-alpha.168
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 +102 -5
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -5217,6 +5217,50 @@ const STALE_LABELS = [
|
|
|
5217
5217
|
//#region src/templates/dco.yml
|
|
5218
5218
|
var dco_default = "allowRemediationCommits:\n individual: true\n";
|
|
5219
5219
|
//#endregion
|
|
5220
|
+
//#region src/commands/workflow-source-marker/workflow-source-marker.ts
|
|
5221
|
+
/** `# Source package: <path>` line at the head of a generated thin-caller workflow — see {@link resolvePkgRelPath}. */
|
|
5222
|
+
const SOURCE_PACKAGE_MARKER = /^# Source package: (.+)$/m;
|
|
5223
|
+
/** Strip control characters (including ANSI escapes) before a value reaches a printed error message. */
|
|
5224
|
+
function sanitizeForDisplay(value) {
|
|
5225
|
+
return value.replace(/[\x00-\x1f\x7f]/g, "");
|
|
5226
|
+
}
|
|
5227
|
+
/**
|
|
5228
|
+
* Repo-root-relative path of the PACKAGE a workflow-writing command run is
|
|
5229
|
+
* scoped to — the directory holding the discovered `holocron.config.*`
|
|
5230
|
+
* (`configFilepath`), not necessarily `--cwd` itself. `loadConfig()` walks
|
|
5231
|
+
* up to find the config from any subdirectory ("every command is usable
|
|
5232
|
+
* from any subdirectory of a repo, not only its exact root"), so a `--cwd`
|
|
5233
|
+
* one level too deep would otherwise stamp a different marker than a later
|
|
5234
|
+
* run from the package's own root and misfire the collision check this
|
|
5235
|
+
* feeds (holocron#1026).
|
|
5236
|
+
*
|
|
5237
|
+
* "." for a root invocation, "apps/sentinel" for a subpackage. Normalized
|
|
5238
|
+
* to forward slashes so the same package produces the same marker on
|
|
5239
|
+
* Windows and POSIX. Throws if the result contains a line break — a path
|
|
5240
|
+
* containing one could otherwise break out of the `# Source package: `
|
|
5241
|
+
* comment line and inject arbitrary workflow YAML when the generated
|
|
5242
|
+
* content is later written. Not reachable through this CLI's own flows
|
|
5243
|
+
* (no code path writes a config file whose directory name contains a line
|
|
5244
|
+
* break), but cheap to reject outright rather than trust the filesystem.
|
|
5245
|
+
*/
|
|
5246
|
+
function resolvePkgRelPath(gitRoot, configFilepath) {
|
|
5247
|
+
let root;
|
|
5248
|
+
try {
|
|
5249
|
+
root = realpathSync(gitRoot);
|
|
5250
|
+
} catch {
|
|
5251
|
+
root = gitRoot;
|
|
5252
|
+
}
|
|
5253
|
+
let configDir;
|
|
5254
|
+
try {
|
|
5255
|
+
configDir = realpathSync(dirname(configFilepath));
|
|
5256
|
+
} catch {
|
|
5257
|
+
configDir = dirname(configFilepath);
|
|
5258
|
+
}
|
|
5259
|
+
const pkgRelPath = (relative(root, configDir) || ".").split(sep).join("/");
|
|
5260
|
+
if (/[\r\n]/.test(pkgRelPath)) throw new Error(`refusing to use a package path containing a line break: ${JSON.stringify(pkgRelPath)}`);
|
|
5261
|
+
return pkgRelPath;
|
|
5262
|
+
}
|
|
5263
|
+
//#endregion
|
|
5220
5264
|
//#region src/commands/setup/agent-prompts-data.ts
|
|
5221
5265
|
/**
|
|
5222
5266
|
* Canonical AI engineering workflow role prompts.
|
|
@@ -5877,14 +5921,35 @@ async function installSkills({ agent, skills, repoRoot }) {
|
|
|
5877
5921
|
try {
|
|
5878
5922
|
skillsRoot = dirname(require.resolve("@theholocron/skills/package.json"));
|
|
5879
5923
|
} catch {
|
|
5880
|
-
|
|
5924
|
+
const atWorkspaceRoot = await isWorkspaceRoot(repoRoot);
|
|
5925
|
+
let result = spawnSync("pnpm", [
|
|
5881
5926
|
"add",
|
|
5882
5927
|
"-D",
|
|
5928
|
+
...atWorkspaceRoot ? ["-w"] : [],
|
|
5883
5929
|
"@theholocron/skills"
|
|
5884
5930
|
], {
|
|
5885
5931
|
cwd: repoRoot,
|
|
5886
|
-
stdio:
|
|
5887
|
-
|
|
5932
|
+
stdio: [
|
|
5933
|
+
"inherit",
|
|
5934
|
+
"inherit",
|
|
5935
|
+
"pipe"
|
|
5936
|
+
],
|
|
5937
|
+
encoding: "utf8"
|
|
5938
|
+
});
|
|
5939
|
+
if (result.status !== 0 && !atWorkspaceRoot && result.stderr?.includes("ERR_PNPM_ADDING_TO_ROOT")) {
|
|
5940
|
+
process.stderr.write(result.stderr);
|
|
5941
|
+
result = spawnSync("pnpm", [
|
|
5942
|
+
"add",
|
|
5943
|
+
"-D",
|
|
5944
|
+
"-w",
|
|
5945
|
+
"@theholocron/skills"
|
|
5946
|
+
], {
|
|
5947
|
+
cwd: repoRoot,
|
|
5948
|
+
stdio: "inherit",
|
|
5949
|
+
encoding: "utf8"
|
|
5950
|
+
});
|
|
5951
|
+
} else if (result.status !== 0 && result.stderr) process.stderr.write(result.stderr);
|
|
5952
|
+
if (result.status !== 0) throw new Error("failed to auto-install @theholocron/skills");
|
|
5888
5953
|
try {
|
|
5889
5954
|
skillsRoot = dirname(require.resolve("@theholocron/skills/package.json"));
|
|
5890
5955
|
} catch {
|
|
@@ -6040,6 +6105,10 @@ async function runSetup(input) {
|
|
|
6040
6105
|
const steps = [];
|
|
6041
6106
|
const repo = config.repo;
|
|
6042
6107
|
const effectivePreset = repo?.protection;
|
|
6108
|
+
const setupPkgRelPath = resolvePkgRelPath(findRootSync(input.context.repoRoot, ".git", {
|
|
6109
|
+
fallback: "start",
|
|
6110
|
+
realpath: true
|
|
6111
|
+
}), input.loaded.filepath);
|
|
6043
6112
|
const hooksEnabled = input.hooks ?? (typeof config.hooks === "boolean" ? config.hooks : typeof config.hooks === "object" ? config.hooks.prePush !== false : effectivePreset === "strict");
|
|
6044
6113
|
print(style.header(`Holocron setup — ${config.name}${dryRun ? " (dry-run)" : ""}`));
|
|
6045
6114
|
print(style.dim(` config: ${input.loaded.filepath}`));
|
|
@@ -6119,7 +6188,7 @@ async function runSetup(input) {
|
|
|
6119
6188
|
if (content === void 0) continue;
|
|
6120
6189
|
const step = content.includes("workflows/preview.yml@main") ? `write workflow ${name} (with preview)` : `write workflow ${name}`;
|
|
6121
6190
|
steps.push(await runStep("source", step, dryRun, async () => {
|
|
6122
|
-
await source.writeWorkflowFile(filename, `${thinCallerHeader()}${content}`);
|
|
6191
|
+
await source.writeWorkflowFile(filename, `${thinCallerHeader()}# Source package: ${setupPkgRelPath}\n\n${content}`);
|
|
6123
6192
|
}));
|
|
6124
6193
|
print(formatStep(steps[steps.length - 1]));
|
|
6125
6194
|
}
|
|
@@ -7222,6 +7291,7 @@ async function runSync(input) {
|
|
|
7222
7291
|
} catch {
|
|
7223
7292
|
resolvedRepoRoot = input.context.repoRoot;
|
|
7224
7293
|
}
|
|
7294
|
+
const pkgRelPath = resolvePkgRelPath(gitRoot, input.loaded.filepath);
|
|
7225
7295
|
logger.info({
|
|
7226
7296
|
config: config.name,
|
|
7227
7297
|
steps: requestedSteps ?? "all",
|
|
@@ -7499,7 +7569,8 @@ async function runSync(input) {
|
|
|
7499
7569
|
if (content === void 0) continue;
|
|
7500
7570
|
const step = `sync workflow ${name}${content.includes("workflows/preview.yml@main") ? " (with preview)" : ""}`;
|
|
7501
7571
|
steps.push(await runSyncStep("local", step, dryRun, async () => {
|
|
7502
|
-
await
|
|
7572
|
+
await assertNoPackageCollision(gitRoot, filename, name, pkgRelPath);
|
|
7573
|
+
await writeWorkflowFile(gitRoot, filename, `${workflowHeader()}# Source package: ${pkgRelPath}\n\n${content}`);
|
|
7503
7574
|
}));
|
|
7504
7575
|
print(formatSyncStep(steps[steps.length - 1]));
|
|
7505
7576
|
}
|
|
@@ -7762,6 +7833,32 @@ async function writeWorkflowFile(repoRoot, filename, content) {
|
|
|
7762
7833
|
await writeFile(tmpPath, content, "utf8");
|
|
7763
7834
|
await rename(tmpPath, join(dir, filename));
|
|
7764
7835
|
}
|
|
7836
|
+
/**
|
|
7837
|
+
* Two different monorepo packages opting into the same generic task name
|
|
7838
|
+
* (e.g. both `apps/sentinel` and some future `apps/other` configuring
|
|
7839
|
+
* `delivery.previewDeploy`) would otherwise overwrite each other's
|
|
7840
|
+
* `.github/workflows/<task>.yml` on whichever package's `holocron repo sync`
|
|
7841
|
+
* ran last — only one package's behavior would ever actually run in CI,
|
|
7842
|
+
* with no signal that the other's was silently dropped (holocron#1026).
|
|
7843
|
+
*
|
|
7844
|
+
* A file with no marker at all predates this check (or was written before
|
|
7845
|
+
* #1026) and is never flagged — only a marker that's present AND disagrees
|
|
7846
|
+
* with the current run's own package is a real collision. Any read error
|
|
7847
|
+
* other than "file doesn't exist" propagates rather than being treated as
|
|
7848
|
+
* "nothing to collide with" — the file may genuinely be unreadable, not
|
|
7849
|
+
* missing.
|
|
7850
|
+
*/
|
|
7851
|
+
async function assertNoPackageCollision(repoRoot, filename, taskName, pkgRelPath) {
|
|
7852
|
+
let existing;
|
|
7853
|
+
try {
|
|
7854
|
+
existing = await readFile(join(repoRoot, ".github", "workflows", filename), "utf8");
|
|
7855
|
+
} catch (err) {
|
|
7856
|
+
if (err.code === "ENOENT") return;
|
|
7857
|
+
throw err;
|
|
7858
|
+
}
|
|
7859
|
+
const previousPkg = SOURCE_PACKAGE_MARKER.exec(existing)?.[1];
|
|
7860
|
+
if (previousPkg !== void 0 && previousPkg !== pkgRelPath) throw new Error(`task "${taskName}" would overwrite .github/workflows/${filename}, already generated for package "${sanitizeForDisplay(previousPkg)}" — the same task name can't be used by two different packages (holocron#1026). If "${sanitizeForDisplay(previousPkg)}" moved or was renamed to this path, delete .github/workflows/${filename} first and re-run sync; otherwise rename one or give it a different thin-caller filename.`);
|
|
7861
|
+
}
|
|
7765
7862
|
//#endregion
|
|
7766
7863
|
//#region src/observability/resolve-telemetry/resolve-telemetry.ts
|
|
7767
7864
|
/**
|