@theholocron/astromech 5.0.0-alpha.77 → 5.0.0-alpha.79
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/README.md +6 -4
- package/dist/index.d.mts +22 -8
- package/dist/index.mjs +54 -10
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ const report = astromech.run("verification.unitTests", { passthrough: ["--watch"
|
|
|
33
33
|
`holocron run verification.unitTests` runs your tests — you don't tell it
|
|
34
34
|
turbo vs pnpm vs npm, or which runner:
|
|
35
35
|
|
|
36
|
-
```
|
|
36
|
+
```text
|
|
37
37
|
1. turbo.json defines the task → turbo run <task>
|
|
38
38
|
2. package.json has a <task> script → <detected pm> run <task>
|
|
39
39
|
(a "holocron run …" thin caller is skipped — no recursion)
|
|
@@ -83,10 +83,12 @@ publish/deploy-shaped task can carry, toggled via that task's `with:`.
|
|
|
83
83
|
|
|
84
84
|
**`linterGroup` tasks** (`sourceQuality.staticAnalysis`,
|
|
85
85
|
`sourceQuality.formatting`) bundle more than one tool under a single
|
|
86
|
-
required check. Each tool is still gated by its own detection rule
|
|
87
|
-
`eslint`
|
|
86
|
+
required check. Each tool is still gated by its own detection rule — e.g.
|
|
87
|
+
`eslint` runs when either a local `eslint.config.*` is present, or the
|
|
88
|
+
package has no local file but `@theholocron/eslint-config` resolves
|
|
89
|
+
(the same shared-config splice the `tool`/`detect` runner path uses) — the
|
|
88
90
|
`tool name → detection rule → local binary` mapping lives in one place,
|
|
89
|
-
`src/linters.ts`. A repo's choice of which of these run is
|
|
91
|
+
`src/linters.ts`. A repo's choice of which of these run is based on which
|
|
90
92
|
tasks it includes in `tasks: [...]`, same as any other task.
|
|
91
93
|
|
|
92
94
|
**No super-linter.** Each task above runs its own tool directly, through
|
package/dist/index.d.mts
CHANGED
|
@@ -332,10 +332,14 @@ interface Astromech {
|
|
|
332
332
|
* (not just an orchestrator) silently vanishes from `turbo run <task>`.
|
|
333
333
|
* Pass `pnpm-workspace.yaml`'s current content (or `""` if none exists —
|
|
334
334
|
* a no-op either way, since no `packages:` key means single-package mode
|
|
335
|
-
* already) and root `package.json`'s own `scripts`
|
|
336
|
-
*
|
|
335
|
+
* already) and root `package.json`'s own `scripts` object — values and
|
|
336
|
+
* all, not just keys, so a task-name-matching script that's already the
|
|
337
|
+
* generated `holocron run <task> --` wrapper (predating this call, not
|
|
338
|
+
* written by it) is correctly excluded rather than treated as proof root
|
|
339
|
+
* is directly buildable (#846). `changed: false` means don't write
|
|
340
|
+
* anything — the file is already correct.
|
|
337
341
|
*/
|
|
338
|
-
ensureRootWorkspaceMember(workspaceYaml: string, rootScripts:
|
|
342
|
+
ensureRootWorkspaceMember(workspaceYaml: string, rootScripts: Readonly<Record<string, string>>): EnsureRootWorkspaceMemberResult;
|
|
339
343
|
/**
|
|
340
344
|
* `turboConfig()` writes a `turbo.json` but never touches `package.json`
|
|
341
345
|
* — a repo with no local `turbo` devDependency falls back to whatever
|
|
@@ -448,11 +452,20 @@ declare const LINTER_NAMES: ReadonlySet<string>;
|
|
|
448
452
|
* Resolve the linter set for a repo. An `explicit` list (from `config.tasks`)
|
|
449
453
|
* picks the candidate set; otherwise the candidates are every `always` linter
|
|
450
454
|
* plus every `detect`-gated linter whose filenames are present at the repo
|
|
451
|
-
* root
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
455
|
+
* root, OR whose mapped `@theholocron/*-config` package resolves per
|
|
456
|
+
* {@link sharedConfigAvailable} — a repo can't run eslint with genuinely no
|
|
457
|
+
* config anywhere (super-linter FATALs if you ask it to,
|
|
458
|
+
* theholocron/holocron#654), but a *shared* one it has installed counts
|
|
459
|
+
* (holocron#795: proving `clients`' eslint.config.ts files reduce to zero
|
|
460
|
+
* remaining content is only half the story — the file itself was still load-
|
|
461
|
+
* bearing here as the sole "eslint is even configured" signal until this
|
|
462
|
+
* fallback existed). `always` linters are unconditional. Result is ordered
|
|
463
|
+
* by {@link LINTERS} declaration order.
|
|
464
|
+
*
|
|
465
|
+
* `sharedConfigAvailable` is caller-computed (`run.ts`'s `runLinterGroup`
|
|
466
|
+
* already resolves each linter's `resolveToolConfig()` per member to build
|
|
467
|
+
* its actual command — reusing that instead of duplicating the package-
|
|
468
|
+
* resolution logic here keeps this function itself pure and file-I/O-free).
|
|
456
469
|
*
|
|
457
470
|
* @throws when an `explicit` name is not in the registry — a typo is a
|
|
458
471
|
* config bug, not a linter to silently skip.
|
|
@@ -460,6 +473,7 @@ declare const LINTER_NAMES: ReadonlySet<string>;
|
|
|
460
473
|
declare function resolveLinters(opts: {
|
|
461
474
|
explicit?: string[];
|
|
462
475
|
rootFiles: string[];
|
|
476
|
+
sharedConfigAvailable?: ReadonlySet<string>;
|
|
463
477
|
}): Array<{
|
|
464
478
|
name: string;
|
|
465
479
|
def: LinterDef;
|
package/dist/index.mjs
CHANGED
|
@@ -299,11 +299,20 @@ const LINTER_NAMES = new Set(Object.keys(LINTERS));
|
|
|
299
299
|
* Resolve the linter set for a repo. An `explicit` list (from `config.tasks`)
|
|
300
300
|
* picks the candidate set; otherwise the candidates are every `always` linter
|
|
301
301
|
* plus every `detect`-gated linter whose filenames are present at the repo
|
|
302
|
-
* root
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
302
|
+
* root, OR whose mapped `@theholocron/*-config` package resolves per
|
|
303
|
+
* {@link sharedConfigAvailable} — a repo can't run eslint with genuinely no
|
|
304
|
+
* config anywhere (super-linter FATALs if you ask it to,
|
|
305
|
+
* theholocron/holocron#654), but a *shared* one it has installed counts
|
|
306
|
+
* (holocron#795: proving `clients`' eslint.config.ts files reduce to zero
|
|
307
|
+
* remaining content is only half the story — the file itself was still load-
|
|
308
|
+
* bearing here as the sole "eslint is even configured" signal until this
|
|
309
|
+
* fallback existed). `always` linters are unconditional. Result is ordered
|
|
310
|
+
* by {@link LINTERS} declaration order.
|
|
311
|
+
*
|
|
312
|
+
* `sharedConfigAvailable` is caller-computed (`run.ts`'s `runLinterGroup`
|
|
313
|
+
* already resolves each linter's `resolveToolConfig()` per member to build
|
|
314
|
+
* its actual command — reusing that instead of duplicating the package-
|
|
315
|
+
* resolution logic here keeps this function itself pure and file-I/O-free).
|
|
307
316
|
*
|
|
308
317
|
* @throws when an `explicit` name is not in the registry — a typo is a
|
|
309
318
|
* config bug, not a linter to silently skip.
|
|
@@ -311,17 +320,18 @@ const LINTER_NAMES = new Set(Object.keys(LINTERS));
|
|
|
311
320
|
function resolveLinters(opts) {
|
|
312
321
|
const order = Object.keys(LINTERS);
|
|
313
322
|
const present = new Set(opts.rootFiles);
|
|
314
|
-
const
|
|
323
|
+
const shared = opts.sharedConfigAvailable ?? /* @__PURE__ */ new Set();
|
|
324
|
+
const configPresent = (name, def) => def.always === true || (def.detect ?? []).some((f) => present.has(f)) || shared.has(name);
|
|
315
325
|
if (opts.explicit && opts.explicit.length > 0) {
|
|
316
326
|
const unknown = opts.explicit.filter((n) => !LINTER_NAMES.has(n));
|
|
317
327
|
if (unknown.length > 0) throw new Error(`unknown linter${unknown.length > 1 ? "s" : ""} ${unknown.map((n) => `"${n}"`).join(", ")} — known: ${order.join(", ")}`);
|
|
318
328
|
const wanted = new Set(opts.explicit);
|
|
319
|
-
return order.filter((n) => wanted.has(n) && configPresent(LINTERS[n])).map((name) => ({
|
|
329
|
+
return order.filter((n) => wanted.has(n) && configPresent(n, LINTERS[n])).map((name) => ({
|
|
320
330
|
name,
|
|
321
331
|
def: LINTERS[name]
|
|
322
332
|
}));
|
|
323
333
|
}
|
|
324
|
-
return order.filter((name) => configPresent(LINTERS[name])).map((name) => ({
|
|
334
|
+
return order.filter((name) => configPresent(name, LINTERS[name])).map((name) => ({
|
|
325
335
|
name,
|
|
326
336
|
def: LINTERS[name]
|
|
327
337
|
}));
|
|
@@ -731,9 +741,14 @@ function runLinterGroup(input, names, passthrough) {
|
|
|
731
741
|
} catch {
|
|
732
742
|
rootFiles = [];
|
|
733
743
|
}
|
|
744
|
+
const sharedConfigAvailable = new Set(names.filter((name) => resolveToolConfig(name, cwd, {
|
|
745
|
+
readFile,
|
|
746
|
+
fileExists
|
|
747
|
+
}).length > 0));
|
|
734
748
|
const resolved = resolveLinters({
|
|
735
749
|
explicit: names,
|
|
736
|
-
rootFiles
|
|
750
|
+
rootFiles,
|
|
751
|
+
sharedConfigAvailable
|
|
737
752
|
});
|
|
738
753
|
const reports = [];
|
|
739
754
|
let anyHasLocalBin = false;
|
|
@@ -1544,6 +1559,17 @@ function turboConfig(config) {
|
|
|
1544
1559
|
* (`Packages in scope: docs, root-lib`) without disturbing the sibling
|
|
1545
1560
|
* package's own caching.
|
|
1546
1561
|
*
|
|
1562
|
+
* A matching script key alone isn't enough — its *value* has to be a real
|
|
1563
|
+
* command, not the generated `holocron run <task> --` wrapper (#846, the
|
|
1564
|
+
* counterpart `mergePackageJsonScripts()` already had to learn for #747/
|
|
1565
|
+
* #748). A repo whose root predates this tooling can already have that exact
|
|
1566
|
+
* wrapper sitting in a task-name-matching script with nothing ever having
|
|
1567
|
+
* generated it this session; adding `.` there fires #747's recursion
|
|
1568
|
+
* immediately (`turbo run <task>` → root's own `<task>` script → the wrapper
|
|
1569
|
+
* → `holocron run <task>` sees `turbo.json` still defines it → `turbo run
|
|
1570
|
+
* <task>` again, forever). Only a matching script whose value is *not* that
|
|
1571
|
+
* wrapper counts as "root is genuinely a directly-buildable package".
|
|
1572
|
+
*
|
|
1547
1573
|
* A targeted line-based edit, not a full YAML parse/reserialize — same
|
|
1548
1574
|
* pattern `codecov.ts`'s `mergeCodecovComponents()` already uses for editing
|
|
1549
1575
|
* an existing generated file. `pnpm-workspace.yaml` routinely carries a
|
|
@@ -1555,7 +1581,10 @@ function turboConfig(config) {
|
|
|
1555
1581
|
* every repo checked actually uses.
|
|
1556
1582
|
*/
|
|
1557
1583
|
function ensureRootWorkspaceMember(workspaceYaml, rootScripts, taskNames) {
|
|
1558
|
-
if (!taskNames.some((t) =>
|
|
1584
|
+
if (!taskNames.some((t) => {
|
|
1585
|
+
const script = rootScripts[t];
|
|
1586
|
+
return script !== void 0 && !isGeneratedRunWrapper(script, t);
|
|
1587
|
+
})) return {
|
|
1559
1588
|
content: workspaceYaml,
|
|
1560
1589
|
changed: false
|
|
1561
1590
|
};
|
|
@@ -1589,6 +1618,21 @@ function ensureRootWorkspaceMember(workspaceYaml, rootScripts, taskNames) {
|
|
|
1589
1618
|
};
|
|
1590
1619
|
}
|
|
1591
1620
|
/**
|
|
1621
|
+
* Matches the exact shape `packageScripts()` generates for `taskName` —
|
|
1622
|
+
* `<bin> run <taskName> --`, `<bin>` normally `holocron` but substitutable
|
|
1623
|
+
* (`holocronScript`, itself allowed to be multi-word, e.g. `"node
|
|
1624
|
+
* packages/cli/dist/cli.mjs"` — see its own test). Checking the *suffix*
|
|
1625
|
+
* rather than the whole string means an old wrapper survives a
|
|
1626
|
+
* `holocronScript` rename intact — it's still recognized as a wrapper (safe
|
|
1627
|
+
* to update to the new one), not mistaken for a real command. Mirrors
|
|
1628
|
+
* `sync.ts`'s private `isGeneratedRunWrapper()` — same predicate, needed on
|
|
1629
|
+
* both sides of #692/#747: whether to *write* the wrapper, and here, whether
|
|
1630
|
+
* an *existing* one should count as "root is directly buildable".
|
|
1631
|
+
*/
|
|
1632
|
+
function isGeneratedRunWrapper(command, taskName) {
|
|
1633
|
+
return command.endsWith(` run ${taskName} --`);
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1592
1636
|
* Fallback pin when a repo's own `pnpm-workspace.yaml` has no `turbo`
|
|
1593
1637
|
* catalog entry yet — matches this repo's own catalog version (the
|
|
1594
1638
|
* canonical source-of-truth repo for the whole org's tooling).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/astromech",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.79",
|
|
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.
|
|
40
|
+
"@theholocron/datapad": "5.0.0-alpha.79"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@theholocron/eslint-config": "^8.4.5",
|
|
@@ -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.
|
|
56
|
+
"@theholocron/rollup-plugin-transform-template": "5.0.0-alpha.79"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=22"
|