@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 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(...)\`. Once methods are stubbed, add
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
- rest: ${clientClass};
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
- const token = resolveToken(options);
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: new ${clientClass}(restOpts),
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: .notes/ai-engineering-workflow.spec.md
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 { } from "${name}";`, "", "function App() {", ` return <></>;`, "}", "```");
5221
- else lines.push("```typescript", `import { } from "${name}";`, "```");
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: loaded.resolved.homepage ?? pkg.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.{json,js,ts}` file loader.
6702
+ * `holocron.config.{ts,js,mjs,cjs,json}` loader.
6690
6703
  *
6691
- * Search order: json → js → ts. JSON is parsed directly; JS is loaded
6692
- * via native dynamic import; TS is loaded via `tsImport` from tsx (a
6693
- * runtime dep) so operators can write typed configs with `defineConfig`
6694
- * without needing a separate build step.
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
- * All three forms are validated through the same `resolveConfig` path.
6697
- * Implements the lookup-order contract from issue #75 / #81.
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
- * Search order: json → js → ts. Throws `ConfigFileError` if nothing
6711
- * found, or `ConfigError` if the config is malformed / invalid.
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
- for (const filename of CANDIDATE_FILENAMES) {
6715
- const fullPath = join(cwd, filename);
6716
- if (await fileExists(fullPath)) {
6717
- if (filename.endsWith(".json")) return {
6718
- resolved: await loadJson(fullPath),
6719
- filepath: fullPath
6720
- };
6721
- if (filename.endsWith(".ts")) return {
6722
- resolved: await loadTs(fullPath),
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";