@theholocron/astromech 5.0.0-alpha.6 → 5.0.0-alpha.8

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/index.d.mts CHANGED
@@ -546,6 +546,46 @@ declare const CI_ORDER: string[];
546
546
  /** Ordered (task contexts in {@link CI_ORDER}, then extras), de-duplicated. */
547
547
  declare function requiredChecks(config: TasksConfig): string[];
548
548
  //#endregion
549
+ //#region src/resolver.d.ts
550
+ /**
551
+ * Resolves the absolute path to a Bucket A tool's shared `@theholocron/*-config`
552
+ * entry point, installed in the consuming repo's own `node_modules` — and the
553
+ * CLI flag(s) to hand it via (`--config`, `--extends`, …). `run.ts` splices the
554
+ * result into the command it builds for a tool/detect runner or a linter-group
555
+ * entry, right after the tool's own args.
556
+ *
557
+ * Every mapped package's resolved entry point already carries a ready-to-use
558
+ * default export (a plain re-export for `prettier`/`commitlint`; an invoked,
559
+ * zero-arg preset for `eslint`/`vitest`/`tsdown` — verified end-to-end against
560
+ * each tool's real `--config`/`--extends` loader, not just the file's shape).
561
+ * A repo doesn't need this package installed at all — every lookup degrades to
562
+ * `[]` (no flag added, the tool falls back to its own auto-discovery of a
563
+ * local file, unchanged from today) rather than throwing, so this is additive
564
+ * and safe to run unconditionally.
565
+ *
566
+ * `semantic-release`, `devmoji`, `editorconfig-checker`, and `knip` are
567
+ * deliberately absent — `semantic-release-config`'s `defineConfig()` needs
568
+ * real per-repo data (branches, npm options) a static `--extends <path>` can't
569
+ * carry; `devmoji` runs through a git hook template, not `holocron run
570
+ * <task>`; `editorconfig-checker` has no shared-config package yet; `knip` is
571
+ * repo-specific by nature, never a shared-config candidate. See
572
+ * `.notes/tech-config-resolution.spec.md` (#676).
573
+ */
574
+ interface ResolverDeps {
575
+ readFile: (path: string) => string;
576
+ fileExists: (path: string) => boolean;
577
+ }
578
+ /** Every tool name the resolver knows how to point at a shared config. */
579
+ declare const RESOLVABLE_TOOLS: ReadonlySet<string>;
580
+ /**
581
+ * `<flag> <absolute path>` for `tool`, resolved against `cwd`'s
582
+ * `node_modules` — or `[]` when the tool isn't mapped, the package isn't
583
+ * installed, its `package.json` doesn't declare the export subpath this
584
+ * needs, or the resolved file doesn't actually exist on disk (a stale
585
+ * install, or a package version that predates the export existing).
586
+ */
587
+ declare function resolveToolConfig(tool: string, cwd: string, deps: ResolverDeps): string[];
588
+ //#endregion
549
589
  //#region src/reusable.d.ts
550
590
  /**
551
591
  * The **reusable** GitHub Actions surface pushed to `theholocron/.github` by
@@ -573,4 +613,73 @@ declare const WORKFLOW_TEMPLATE_PROPERTIES: Record<string, string>;
573
613
  */
574
614
  declare function reusableTemplates(): Map<string, string>;
575
615
  //#endregion
576
- export { type Astromech, type AstromechOptions, CI_ORDER, type CiJobReport, type CiOptions, type CiReport, type ExecFn, type JobDef, KNOWN_TASKS, KNOWN_WORKFLOWS, LINTERS, LINTER_NAMES, type LinterDef, type LocalRunner, type OrgContext, type PreviewConfig, REUSABLE_ACTIONS, REUSABLE_WORKFLOWS, type RunLogger, type RunOptions, type RunTaskInput, type RunTaskReport, TASKS, type TaskDef, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, WORKFLOW_TEMPLATE_PROPERTIES, type WorkspacePackage, codecovComponentBlock, codecovConfig, createAstromech, createCodecovConfig, deriveDeployPaths, ensureIfNotFound, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, mergeCodecovComponents, normalizeWorkflowWith, readWorkspacePackages, requiredChecks, resolveLinters, reusableTemplates, runCi, runTask };
616
+ //#region src/tsconfig.d.ts
617
+ /**
618
+ * `tsconfig.json` generation — Bucket B (config-resolution workstream, #676):
619
+ * the file must stay committed (the TypeScript language server discovers it
620
+ * directly from disk, no `--config` override mechanism to hand it a path
621
+ * instead), but its content is fully derivable, so it's generated rather
622
+ * than hand-authored — the same category as `.editorconfig`
623
+ * ({@link "./templates/configs/editorconfig/create-config.js" createConfig})
624
+ * and {@link codecovConfig}.
625
+ *
626
+ * Surveyed every package-level `tsconfig.json` across the org (`holocron`,
627
+ * `clients`, `utils` — 20+ packages). `extends: "@theholocron/tsconfig/
628
+ * node-lts"`, `compilerOptions.baseUrl: "./"`, `compilerOptions.outDir:
629
+ * "./dist"`, `include: ["src/**\/*.ts"]`, and `exclude: ["node_modules",
630
+ * "dist"]` are uniform in every one — safe defaults here. `paths` (a `@/*`
631
+ * → `./src/*` import alias) is a real per-package choice, present in
632
+ * roughly 40% of packages checked — an opt-in parameter, not a default.
633
+ *
634
+ * Two fields checked and deliberately *not* absorbed as defaults, unlike
635
+ * `eslint-config`'s `tsconfigRootDir`/`settings.node` (which turned out
636
+ * redundant with the shared package's own behavior, `configs`#461):
637
+ * `compilerOptions.module`/`moduleResolution` (only `@theholocron/cli`
638
+ * overrides these, to `esnext`/`bundler` — a genuine deviation from the
639
+ * shared `node-lts` preset's `nodenext`/`nodenext`, needed for its bundler
640
+ * tooling, not something every package should inherit) and
641
+ * `compilerOptions.rootDir` (present in exactly two packages, always the
642
+ * same value `./src` that `include` already implies — an unnecessary
643
+ * override wherever it appears, not a pattern worth generalizing).
644
+ *
645
+ * Monorepo *root* `tsconfig.json` (a TS project-references "solution
646
+ * file" — `holocron`'s own root is `{ files: [], references: [...] }`,
647
+ * listing which packages to build) is a structurally different, genuinely
648
+ * per-repo document — out of scope here, same as Bucket C content.
649
+ *
650
+ * Not yet wired into `holocron setup`'s per-package write loop (every
651
+ * other Bucket B file there is a single repo-root file, safely
652
+ * overwritten every run — `tsconfig.json` is per-package, and most
653
+ * packages' files still carry real hand-authored content today, not yet
654
+ * migrated to a fully-generated state). That per-package iteration +
655
+ * safe-migration design is `#680`'s job, not duplicated here — this is
656
+ * the generator itself, ready for it to call.
657
+ */
658
+ interface TsconfigOptions {
659
+ /** Human-readable name for the `display` field — the package's own name is the usual choice. */
660
+ display: string;
661
+ /**
662
+ * `@theholocron/tsconfig` variant. Defaults to `"node-lts"` — every
663
+ * checked package uses it; the other three (`astro`, `nextjs`, `react`)
664
+ * exist for a template repo's app-shaped packages, not the plain
665
+ * Node.js library packages this org's currently-migrated repos ship.
666
+ */
667
+ variant?: "astro" | "nextjs" | "node-lts" | "react";
668
+ /**
669
+ * Add a `@/*` → `./src/*` path alias. A real per-package choice, not a
670
+ * default — roughly 40% of packages checked use one, the rest don't.
671
+ */
672
+ paths?: boolean;
673
+ }
674
+ /**
675
+ * A package-level `tsconfig.json` — the uniform shape confirmed across
676
+ * every package checked, parameterized only by the two fields that
677
+ * genuinely vary (`display`, `paths`). No scaffold/workflow header (matches
678
+ * `.alexrc.json`'s existing precedent for a strict-JSON Bucket B file —
679
+ * JSON has no comment syntax to carry one, and TypeScript's own tolerance
680
+ * for JSONC comments in `tsconfig.json` isn't worth relying on for a file
681
+ * this thin).
682
+ */
683
+ declare function createTsconfig(options: TsconfigOptions): string;
684
+ //#endregion
685
+ export { type Astromech, type AstromechOptions, CI_ORDER, type CiJobReport, type CiOptions, type CiReport, type ExecFn, type JobDef, KNOWN_TASKS, KNOWN_WORKFLOWS, LINTERS, LINTER_NAMES, type LinterDef, type LocalRunner, type OrgContext, type PreviewConfig, RESOLVABLE_TOOLS, REUSABLE_ACTIONS, REUSABLE_WORKFLOWS, type ResolverDeps, type RunLogger, type RunOptions, type RunTaskInput, type RunTaskReport, TASKS, type TaskDef, type TsconfigOptions, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, WORKFLOW_TEMPLATE_PROPERTIES, type WorkspacePackage, codecovComponentBlock, codecovConfig, createAstromech, createCodecovConfig, createTsconfig, deriveDeployPaths, ensureIfNotFound, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, mergeCodecovComponents, normalizeWorkflowWith, readWorkspacePackages, requiredChecks, resolveLinters, resolveToolConfig, reusableTemplates, runCi, runTask };
package/dist/index.mjs CHANGED
@@ -327,6 +327,98 @@ function resolveLinters(opts) {
327
327
  }));
328
328
  }
329
329
  //#endregion
330
+ //#region src/resolver.ts
331
+ /**
332
+ * `library` is every mapped tool's org-wide default variant today — every
333
+ * currently-migrated repo (`holocron`, `clients`, `utils`, `configs`,
334
+ * `themes`, `observability`, `skills`) is a publishable library package, and
335
+ * `eslint-config`/`vitest-config`/`tsdown-config` all name their
336
+ * general-purpose preset exactly that. Picking a different variant per repo
337
+ * (an app-type template, say) needs new `holocron.config.ts` surface this
338
+ * doesn't add yet — deliberately out of scope until a real consumer needs it.
339
+ */
340
+ const TOOL_CONFIGS = {
341
+ eslint: {
342
+ package: "@theholocron/eslint-config",
343
+ exportPath: "./bundles/library",
344
+ flag: ["--config"]
345
+ },
346
+ prettier: {
347
+ package: "@theholocron/prettier-config",
348
+ exportPath: ".",
349
+ flag: ["--config"]
350
+ },
351
+ vitest: {
352
+ package: "@theholocron/vitest-config",
353
+ exportPath: "./bundles/library",
354
+ flag: ["--config"]
355
+ },
356
+ tsdown: {
357
+ package: "@theholocron/tsdown-config",
358
+ exportPath: "./presets/library",
359
+ flag: ["--config"]
360
+ },
361
+ commitlint: {
362
+ package: "@theholocron/commitlint-config",
363
+ exportPath: ".",
364
+ flag: ["--config"]
365
+ }
366
+ };
367
+ /** Every tool name the resolver knows how to point at a shared config. */
368
+ const RESOLVABLE_TOOLS = new Set(Object.keys(TOOL_CONFIGS));
369
+ /**
370
+ * `<flag> <absolute path>` for `tool`, resolved against `cwd`'s
371
+ * `node_modules` — or `[]` when the tool isn't mapped, the package isn't
372
+ * installed, its `package.json` doesn't declare the export subpath this
373
+ * needs, or the resolved file doesn't actually exist on disk (a stale
374
+ * install, or a package version that predates the export existing).
375
+ */
376
+ function resolveToolConfig(tool, cwd, deps) {
377
+ const spec = TOOL_CONFIGS[tool];
378
+ if (!spec) return [];
379
+ const pkgDir = joinPath(cwd, "node_modules", ...spec.package.split("/"));
380
+ const pkgJsonPath = joinPath(pkgDir, "package.json");
381
+ if (!deps.fileExists(pkgJsonPath)) return [];
382
+ let pkg;
383
+ try {
384
+ pkg = JSON.parse(deps.readFile(pkgJsonPath));
385
+ } catch {
386
+ return [];
387
+ }
388
+ const relative = resolveExportEntry(pkg.exports?.[spec.exportPath]);
389
+ if (!relative) return [];
390
+ const absolute = joinPath(pkgDir, relative.replace(/^\.\//, ""));
391
+ if (!deps.fileExists(absolute)) return [];
392
+ return [...spec.flag, absolute];
393
+ }
394
+ /**
395
+ * Unwrap a `package.json#exports` subpath entry down to its file path —
396
+ * either a bare string, or an object with condition keys (`import`,
397
+ * `default`, `require`, …) that may themselves nest one level (`{ import: {
398
+ * types, default } }`, the shape every `@theholocron/*-config` package
399
+ * actually uses). Prefers `import` (this org ships ESM), then `default`,
400
+ * then `require` as a last resort.
401
+ */
402
+ function resolveExportEntry(entry) {
403
+ if (typeof entry === "string") return entry;
404
+ if (entry === null || typeof entry !== "object") return void 0;
405
+ const conditions = entry;
406
+ const value = conditions.import ?? conditions.default ?? conditions.require;
407
+ if (typeof value === "string") return value;
408
+ if (typeof value === "object" && value !== null) return resolveExportEntry(value);
409
+ }
410
+ /**
411
+ * A minimal, POSIX-safe `path.join` — avoids importing `node:path` just for
412
+ * this. Every path in play here is either already POSIX (a `package.json`
413
+ * `exports` value, always forward-slashed regardless of platform) or built
414
+ * from platform-neutral segments (`cwd`, `"node_modules"`, a scoped package
415
+ * name split on `/`), so a manual join is safe and keeps this module
416
+ * dependency-free like the rest of `run.ts`'s helpers.
417
+ */
418
+ function joinPath(...segments) {
419
+ return segments.map((s, i) => i === 0 ? s.replace(/\/+$/, "") : s.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
420
+ }
421
+ //#endregion
330
422
  //#region src/run.ts
331
423
  /**
332
424
  * `holocron run <task> [job] [-- <passthrough>]` — run a registry task (or one
@@ -448,8 +540,13 @@ function runTask(input) {
448
540
  const runner = resolveRunner(def.local, cwd, listDir);
449
541
  if (runner) {
450
542
  const flags = (def.flags?.[runner.tool] ?? []).filter((f) => !passthrough.includes(f));
543
+ const configFlags = resolveToolConfig(runner.tool, cwd, {
544
+ readFile,
545
+ fileExists
546
+ });
451
547
  return run(resolveBin(cwd, runner.tool, fileExists), [
452
548
  ...runner.args,
549
+ ...configFlags,
453
550
  ...flags,
454
551
  ...passthrough
455
552
  ]);
@@ -582,7 +679,7 @@ function runAllJobs(input, jobs, passthrough) {
582
679
  * turbo/a package script would have already caught in steps 1–2.
583
680
  */
584
681
  function runLinterGroup(input, names, passthrough) {
585
- const { print, logger, listDir, lookPath, cwd, task } = input;
682
+ const { print, logger, listDir, lookPath, cwd, task, readFile, fileExists } = input;
586
683
  const dryRun = input.dryRun ?? false;
587
684
  const runOne = makeRunOne(input);
588
685
  let rootFiles;
@@ -609,7 +706,15 @@ function runLinterGroup(input, names, passthrough) {
609
706
  print(`! ${name} — ${bin} not on PATH${def.installHint ? `. ${def.installHint}` : ""} (enforced in CI)`);
610
707
  continue;
611
708
  }
612
- reports.push(runOne(found, [...def.localArgs ?? [], ...passthrough]));
709
+ const configFlags = resolveToolConfig(name, cwd, {
710
+ readFile,
711
+ fileExists
712
+ });
713
+ reports.push(runOne(found, [
714
+ ...configFlags,
715
+ ...def.localArgs ?? [],
716
+ ...passthrough
717
+ ]));
613
718
  }
614
719
  if (reports.length === 0) {
615
720
  const required = input.required && anyHasLocalBin;
@@ -1445,4 +1550,31 @@ function createAstromech(options) {
1445
1550
  };
1446
1551
  }
1447
1552
  //#endregion
1448
- export { CI_ORDER, KNOWN_TASKS, KNOWN_WORKFLOWS, LINTERS, LINTER_NAMES, REUSABLE_ACTIONS, REUSABLE_WORKFLOWS, TASKS, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, WORKFLOW_TEMPLATE_PROPERTIES, codecovComponentBlock, codecovConfig, createAstromech, createCodecovConfig, deriveDeployPaths, ensureIfNotFound, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, mergeCodecovComponents, normalizeWorkflowWith, readWorkspacePackages, requiredChecks, resolveLinters, reusableTemplates, runCi, runTask };
1553
+ //#region src/tsconfig.ts
1554
+ /**
1555
+ * A package-level `tsconfig.json` — the uniform shape confirmed across
1556
+ * every package checked, parameterized only by the two fields that
1557
+ * genuinely vary (`display`, `paths`). No scaffold/workflow header (matches
1558
+ * `.alexrc.json`'s existing precedent for a strict-JSON Bucket B file —
1559
+ * JSON has no comment syntax to carry one, and TypeScript's own tolerance
1560
+ * for JSONC comments in `tsconfig.json` isn't worth relying on for a file
1561
+ * this thin).
1562
+ */
1563
+ function createTsconfig(options) {
1564
+ const variant = options.variant ?? "node-lts";
1565
+ const compilerOptions = {
1566
+ baseUrl: "./",
1567
+ outDir: "./dist"
1568
+ };
1569
+ if (options.paths) compilerOptions.paths = { "@/*": ["./src/*"] };
1570
+ const config = {
1571
+ display: options.display,
1572
+ extends: `@theholocron/tsconfig/${variant}`,
1573
+ compilerOptions,
1574
+ include: ["src/**/*.ts"],
1575
+ exclude: ["node_modules", "dist"]
1576
+ };
1577
+ return JSON.stringify(config, null, 2) + "\n";
1578
+ }
1579
+ //#endregion
1580
+ export { CI_ORDER, KNOWN_TASKS, KNOWN_WORKFLOWS, LINTERS, LINTER_NAMES, RESOLVABLE_TOOLS, REUSABLE_ACTIONS, REUSABLE_WORKFLOWS, TASKS, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, WORKFLOW_TEMPLATE_PROPERTIES, codecovComponentBlock, codecovConfig, createAstromech, createCodecovConfig, createTsconfig, deriveDeployPaths, ensureIfNotFound, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, mergeCodecovComponents, normalizeWorkflowWith, readWorkspacePackages, requiredChecks, resolveLinters, resolveToolConfig, reusableTemplates, runCi, runTask };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/astromech",
3
- "version": "5.0.0-alpha.6",
3
+ "version": "5.0.0-alpha.8",
4
4
  "description": "The Holocron task runner — one task manifest drives `holocron run`, `holocron ci`, the CI workflows, package.json scripts, linters, and required checks.",
5
5
  "keywords": [
6
6
  "ci",
@@ -37,7 +37,7 @@
37
37
  "dist"
38
38
  ],
39
39
  "dependencies": {
40
- "@theholocron/datapad": "5.0.0-alpha.6"
40
+ "@theholocron/datapad": "5.0.0-alpha.8"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@theholocron/eslint-config": "^8.0.0",
@@ -53,7 +53,7 @@
53
53
  "tsdown": "^0.22.14",
54
54
  "typescript": "^5.9.3",
55
55
  "vitest": "^4.1.11",
56
- "@theholocron/rollup-plugin-transform-template": "5.0.0-alpha.6"
56
+ "@theholocron/rollup-plugin-transform-template": "5.0.0-alpha.8"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">=22"