@theholocron/cli 3.63.0 → 3.65.0
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 +46 -82
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +3 -5
- package/dist/index.mjs +20 -69
- package/package.json +6 -5
package/dist/cli.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import { createGitHubClient } from "@theholocron/github-client";
|
|
|
22
22
|
import * as Sentry from "@sentry/node";
|
|
23
23
|
import { PostHog } from "posthog-node";
|
|
24
24
|
import { promisify } from "node:util";
|
|
25
|
+
import { ConfigFileError, loadConfigFile } from "@theholocron/datapad";
|
|
25
26
|
//#region src/env.ts
|
|
26
27
|
/** Singleton env for simple global lookups throughout the CLI. */
|
|
27
28
|
const env = createEnvLookup();
|
|
@@ -2035,12 +2036,13 @@ export class ${inputs.capabilityClass} {
|
|
|
2035
2036
|
readonly key = "${inputs.capability}" as const;
|
|
2036
2037
|
readonly providerName = "${inputs.slug}";
|
|
2037
2038
|
|
|
2038
|
-
constructor(private readonly rest: ${clientClass}) {}
|
|
2039
|
+
constructor(private readonly rest: () => ${clientClass}) {}
|
|
2039
2040
|
|
|
2040
2041
|
// TODO: implement the ${capabilityInterface} interface methods
|
|
2041
2042
|
// (see \`packages/cli/src/capabilities/index.ts\`). Each method
|
|
2042
2043
|
// should hit a specific ${inputs.vendorName} REST endpoint via
|
|
2043
|
-
// \`this.rest.request(...)
|
|
2044
|
+
// \`this.rest().request(...)\` — call the thunk so the token is
|
|
2045
|
+
// resolved lazily. Once methods are stubbed, add
|
|
2044
2046
|
// \`implements ${capabilityInterface}\` to the class declaration
|
|
2045
2047
|
// above and remove the \`as unknown as\` cast in src/index.ts.
|
|
2046
2048
|
}
|
|
@@ -2061,7 +2063,7 @@ import { stubFetch } from "./helpers.js";
|
|
|
2061
2063
|
function makeCapability() {
|
|
2062
2064
|
const stub = stubFetch([]);
|
|
2063
2065
|
const rest = new ${clientClass}({ token: "t", fetch: stub.fetch });
|
|
2064
|
-
return new ${inputs.capabilityClass}(rest);
|
|
2066
|
+
return new ${inputs.capabilityClass}(() => rest);
|
|
2065
2067
|
}
|
|
2066
2068
|
|
|
2067
2069
|
describe("${inputs.capabilityClass}", () => {
|
|
@@ -2254,17 +2256,21 @@ export interface ${inputs.vendorName}PluginOptions extends ResolveTokenInput {
|
|
|
2254
2256
|
|
|
2255
2257
|
export interface PluginContext {
|
|
2256
2258
|
options: ${inputs.vendorName}PluginOptions;
|
|
2257
|
-
|
|
2259
|
+
/** Memoized client — the token is resolved on first use, not at plugin load. */
|
|
2260
|
+
rest: () => ${clientClass};
|
|
2258
2261
|
}
|
|
2259
2262
|
|
|
2260
2263
|
export function createContext(options: ${inputs.vendorName}PluginOptions): PluginContext {
|
|
2261
|
-
|
|
2262
|
-
const restOpts: ConstructorParameters<typeof ${clientClass}>[0] = { token };
|
|
2263
|
-
if (options.baseUrl !== undefined) restOpts.baseUrl = options.baseUrl;
|
|
2264
|
-
if (options.fetch !== undefined) restOpts.fetch = options.fetch;
|
|
2264
|
+
let rest: ${clientClass} | undefined;
|
|
2265
2265
|
return {
|
|
2266
2266
|
options,
|
|
2267
|
-
rest:
|
|
2267
|
+
rest: () => {
|
|
2268
|
+
if (rest) return rest;
|
|
2269
|
+
const restOpts: ConstructorParameters<typeof ${clientClass}>[0] = { token: resolveToken(options) };
|
|
2270
|
+
if (options.baseUrl !== undefined) restOpts.baseUrl = options.baseUrl;
|
|
2271
|
+
if (options.fetch !== undefined) restOpts.fetch = options.fetch;
|
|
2272
|
+
return (rest = new ${clientClass}(restOpts));
|
|
2273
|
+
},
|
|
2268
2274
|
};
|
|
2269
2275
|
}
|
|
2270
2276
|
|
|
@@ -3751,7 +3757,7 @@ function deriveDeployPaths(raw) {
|
|
|
3751
3757
|
* configured. Paths are gitignored and regenerated on every setup run so the
|
|
3752
3758
|
* content always reflects the current CLI version.
|
|
3753
3759
|
*
|
|
3754
|
-
* Source:
|
|
3760
|
+
* Source: docs/wiki/specifications/ai-engineering-workflow.spec.md
|
|
3755
3761
|
*/
|
|
3756
3762
|
const DECISIONS_TEMPLATE = "---\nid: ADR-XXXX\ntitle: \"\"\nstatus: proposed\ndate: YYYY-MM-DD\nowners: []\nspecs: []\ndiscussion:\n github:\nsupersedes: []\nsuperseded-by: []\ntags: []\n---\n\n# [Short title of the decision]\n\n- Status: [proposed | accepted | rejected | deprecated | superseded by ADR-XXXX]\n- Date: YYYY-MM-DD\n\n## Context and Problem Statement\n\n2–3 sentences describing the situation that forced this decision.\n\n## Decision Drivers\n\n- [driver 1 — a constraint, goal, or value]\n- [driver 2]\n\n## Considered Options\n\n- [Option A]\n- [Option B]\n- [Option C — do nothing]\n\n## Decision Outcome\n\nChosen option: **[Option A]**, because [one-sentence justification].\n\n### Positive Consequences\n\n- …\n\n### Negative Consequences\n\n- …\n\n## Pros and Cons of the Options\n\n### [Option A]\n\n- Good, because [argument]\n- Bad, because [argument]\n\n### [Option B]\n\n- Good, because [argument]\n- Bad, because [argument]\n";
|
|
3757
3763
|
const AGENT_PROMPTS = {
|
|
@@ -5203,12 +5209,17 @@ const REGISTRY_MAP = {
|
|
|
5203
5209
|
"@theholocron/themes": getThemes,
|
|
5204
5210
|
"@theholocron/utils": getUtils
|
|
5205
5211
|
};
|
|
5206
|
-
function generateInstallBlock(pkg) {
|
|
5212
|
+
function generateInstallBlock(pkg, homepage = "") {
|
|
5207
5213
|
const { name = "", bin, peerDependencies = {} } = pkg;
|
|
5208
5214
|
const isCli = Boolean(bin);
|
|
5209
5215
|
const isReact = Boolean(peerDependencies["react"]);
|
|
5216
|
+
const isConsumable = isCli || !pkg.private || Boolean(pkg.main) || pkg.exports !== void 0;
|
|
5210
5217
|
const lines = [];
|
|
5211
5218
|
lines.push("## Installation", "");
|
|
5219
|
+
if (!isConsumable) {
|
|
5220
|
+
lines.push("This repository is a workspace root — it is not published. See the", "packages under [`packages/`](./packages) for the tools it ships.");
|
|
5221
|
+
return lines.join("\n");
|
|
5222
|
+
}
|
|
5212
5223
|
if (isCli) lines.push("```bash", `npm install --global ${name}`, "```");
|
|
5213
5224
|
else lines.push("```bash", `pnpm install ${name}`, "```");
|
|
5214
5225
|
lines.push("", "## Usage", "");
|
|
@@ -5217,8 +5228,9 @@ function generateInstallBlock(pkg) {
|
|
|
5217
5228
|
lines.push("```bash");
|
|
5218
5229
|
for (const cmd of commands) lines.push(`${cmd} --help`);
|
|
5219
5230
|
lines.push("```");
|
|
5220
|
-
} else if (isReact) lines.push("```tsx", `import {
|
|
5221
|
-
else lines.push(
|
|
5231
|
+
} else if (isReact) lines.push("```tsx", `import {} from "${name}";`, "", "function App() {", ` return <></>;`, "}", "```");
|
|
5232
|
+
else if (homepage) lines.push(`See the [documentation](${homepage}) for the API.`);
|
|
5233
|
+
else lines.push("See the package documentation for the API.");
|
|
5222
5234
|
return lines.join("\n");
|
|
5223
5235
|
}
|
|
5224
5236
|
/**
|
|
@@ -5300,9 +5312,10 @@ async function runSyncReadme(input) {
|
|
|
5300
5312
|
};
|
|
5301
5313
|
}
|
|
5302
5314
|
const description = loaded.resolved.description ?? "";
|
|
5315
|
+
const homepage = loaded.resolved.homepage ?? pkg.homepage ?? "";
|
|
5303
5316
|
const sections = generateReadme({
|
|
5304
5317
|
description,
|
|
5305
|
-
homepage
|
|
5318
|
+
homepage,
|
|
5306
5319
|
scripts: pkg.scripts ?? {},
|
|
5307
5320
|
packages: pkg.name ? REGISTRY_MAP[pkg.name]?.() : void 0
|
|
5308
5321
|
});
|
|
@@ -5311,7 +5324,7 @@ async function runSyncReadme(input) {
|
|
|
5311
5324
|
development: sections.development,
|
|
5312
5325
|
releases: sections.releases
|
|
5313
5326
|
};
|
|
5314
|
-
const updated = await updateReadme(repoRoot, generateInstallBlock(pkg), markerSections, dryRun, readFileFn, writeFileFn);
|
|
5327
|
+
const updated = await updateReadme(repoRoot, generateInstallBlock(pkg, homepage), markerSections, dryRun, readFileFn, writeFileFn);
|
|
5315
5328
|
if (!updated) {
|
|
5316
5329
|
print(style.warn("README.md not found or has no writable location for installation block"));
|
|
5317
5330
|
logger.warn({
|
|
@@ -6686,74 +6699,32 @@ async function runUpgradeNode(input) {
|
|
|
6686
6699
|
//#endregion
|
|
6687
6700
|
//#region src/config/load-config.ts
|
|
6688
6701
|
/**
|
|
6689
|
-
* `holocron.config.{
|
|
6702
|
+
* `holocron.config.{ts,js,mjs,cjs,json}` loader.
|
|
6690
6703
|
*
|
|
6691
|
-
*
|
|
6692
|
-
*
|
|
6693
|
-
*
|
|
6694
|
-
*
|
|
6704
|
+
* File discovery + module loading (typed configs via `tsx`, no build
|
|
6705
|
+
* step) is delegated to `@theholocron/datapad`. This module keeps the
|
|
6706
|
+
* holocron-specific parts: filling `name` / `repo.name` defaults and
|
|
6707
|
+
* running the config through `resolveConfig`.
|
|
6695
6708
|
*
|
|
6696
|
-
*
|
|
6697
|
-
*
|
|
6709
|
+
* Probe order is TS-first (`.ts` → `.js` → `.mjs` → `.cjs` → `.json`) —
|
|
6710
|
+
* see ADR-0010 / issue #75 / #81.
|
|
6698
6711
|
*/
|
|
6699
6712
|
const execFileAsync = promisify(execFile);
|
|
6700
|
-
const CANDIDATE_FILENAMES = [
|
|
6701
|
-
"holocron.config.json",
|
|
6702
|
-
"holocron.config.js",
|
|
6703
|
-
"holocron.config.ts"
|
|
6704
|
-
];
|
|
6705
|
-
var ConfigFileError = class extends Error {
|
|
6706
|
-
name = "ConfigFileError";
|
|
6707
|
-
};
|
|
6708
6713
|
/**
|
|
6709
6714
|
* Read + parse + resolve `holocron.config.*` from the given directory.
|
|
6710
|
-
*
|
|
6711
|
-
*
|
|
6715
|
+
* Throws {@link ConfigFileError} when nothing is found or a file cannot
|
|
6716
|
+
* be loaded, or `ConfigError` when the config is invalid.
|
|
6712
6717
|
*/
|
|
6713
6718
|
async function loadConfig(cwd) {
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
filepath: fullPath
|
|
6724
|
-
};
|
|
6725
|
-
return {
|
|
6726
|
-
resolved: await loadJs(fullPath),
|
|
6727
|
-
filepath: fullPath
|
|
6728
|
-
};
|
|
6729
|
-
}
|
|
6730
|
-
}
|
|
6731
|
-
throw new ConfigFileError(`no holocron.config.{json,js,ts} found in ${cwd}. Create one — see the README for the schema.`);
|
|
6732
|
-
}
|
|
6733
|
-
async function loadJson(filepath) {
|
|
6734
|
-
const raw = await readFile(filepath, "utf8");
|
|
6735
|
-
let parsed;
|
|
6736
|
-
try {
|
|
6737
|
-
parsed = JSON.parse(raw);
|
|
6738
|
-
} catch (err) {
|
|
6739
|
-
throw new ConfigError(`${filepath} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
6740
|
-
}
|
|
6741
|
-
return resolveConfig(await deriveDefaults(dirname(filepath), parsed));
|
|
6742
|
-
}
|
|
6743
|
-
async function loadJs(filepath) {
|
|
6744
|
-
const mod = await import(pathToFileURL(filepath).href);
|
|
6745
|
-
return resolveConfig(await deriveDefaults(dirname(filepath), extractRaw(filepath, mod)));
|
|
6746
|
-
}
|
|
6747
|
-
async function loadTs(filepath) {
|
|
6748
|
-
const { tsImport } = await import("tsx/esm/api");
|
|
6749
|
-
const mod = await tsImport(pathToFileURL(filepath).href, import.meta.url);
|
|
6750
|
-
return resolveConfig(await deriveDefaults(dirname(filepath), extractRaw(filepath, mod)));
|
|
6751
|
-
}
|
|
6752
|
-
function extractRaw(filepath, mod) {
|
|
6753
|
-
const outer = mod.default;
|
|
6754
|
-
const raw = outer?.__esModule === true ? outer.default : outer;
|
|
6755
|
-
if (raw === void 0 || raw === null) throw new ConfigFileError(`${filepath} must have a default export (use \`export default defineConfig({…})\`)`);
|
|
6756
|
-
return raw;
|
|
6719
|
+
const found = await loadConfigFile({
|
|
6720
|
+
cwd,
|
|
6721
|
+
name: "holocron"
|
|
6722
|
+
});
|
|
6723
|
+
if (!found) throw new ConfigFileError(`no holocron.config.{ts,js,mjs,cjs,json} found in ${cwd}. Create one — see the README for the schema.`);
|
|
6724
|
+
return {
|
|
6725
|
+
resolved: resolveConfig(await deriveDefaults(dirname(found.filepath), found.config)),
|
|
6726
|
+
filepath: found.filepath
|
|
6727
|
+
};
|
|
6757
6728
|
}
|
|
6758
6729
|
async function deriveDefaults(configDir, raw) {
|
|
6759
6730
|
const result = { ...raw };
|
|
@@ -6794,13 +6765,6 @@ function parseGitRemoteUrl(url) {
|
|
|
6794
6765
|
const sshMatch = url.match(/github\.com:([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
6795
6766
|
if (sshMatch) return sshMatch[1];
|
|
6796
6767
|
}
|
|
6797
|
-
async function fileExists(path) {
|
|
6798
|
-
try {
|
|
6799
|
-
return (await stat(path)).isFile();
|
|
6800
|
-
} catch {
|
|
6801
|
-
return false;
|
|
6802
|
-
}
|
|
6803
|
-
}
|
|
6804
6768
|
//#endregion
|
|
6805
6769
|
//#region src/update-notifier.ts
|
|
6806
6770
|
const PACKAGE_NAME = "@theholocron/cli";
|