@theholocron/astromech 4.10.3 → 4.11.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/index.d.mts CHANGED
@@ -1,18 +1,26 @@
1
1
  import { r as TasksConfig } from "./schema-BegK4MsY.mjs";
2
2
  //#region src/run.d.ts
3
3
  /**
4
- * `holocron run <task> [-- <passthrough>]` — run a registry task locally.
4
+ * `holocron run <task> [job] [-- <passthrough>]` — run a registry task (or one
5
+ * of its sub-jobs) locally.
6
+ *
7
+ * A `job` argument only applies to tasks that declare `jobs` (today: `audit`);
8
+ * for any other task it is folded back into the passthrough (`holocron run
9
+ * build src/`). `holocron run audit performance` runs one job; `holocron run
10
+ * audit` runs every job in declared order.
5
11
  *
6
12
  * Resolution:
7
13
  *
8
- * 0. task === "lint" → the linter aggregate (see below)
14
+ * 0. task === "lint" (no job) → the linter aggregate (see below)
15
+ * J. job given → TASKS[task].jobs[job].local (unknown job → exit 1)
9
16
  * 1. turbo.json defines the task → `turbo run <task>`
10
17
  * 2. package.json has a `<task>` script → `<pm> run <task>`
11
18
  * (unless it's the `holocron run …` thin caller — that recurses)
12
19
  * 3. TASKS[task].local resolves → `<tool> <args> <org-flags> <passthrough>`
13
- * 4. TASKS[task].local === null → "enforced in CI" (skip, even with --required)
14
- * 4b. known task, nothing resolved → "no <task> task" (exit 0, or 1 with --required)
15
- * 5. unknown task → "unknown task" (exit 1)
20
+ * 4. TASKS[task].jobs has entries → run each job in declared order
21
+ * 5. TASKS[task].local === null → "enforced in CI" (skip, even with --required)
22
+ * 5b. known task, nothing resolved → "no <task> task" (exit 0, or 1 with --required)
23
+ * 6. unknown task → "unknown task" (exit 1)
16
24
  *
17
25
  * `lint` runs the resolved linter set (`config.tasks` `linters`, else
18
26
  * auto-detected): the eslint slot goes through the standard turbo / script /
@@ -49,6 +57,11 @@ interface RunDeps {
49
57
  interface RunTaskInput extends RunDeps {
50
58
  /** Registry task name, e.g. `"test"`. */
51
59
  task: string;
60
+ /**
61
+ * Sub-job within the task, e.g. `"performance"` in `holocron run audit
62
+ * performance`. Ignored (folded into `passthrough`) for tasks with no `jobs`.
63
+ */
64
+ job?: string;
52
65
  /** Directory to run in. */
53
66
  cwd: string;
54
67
  /** Args after `--` on the command line, forwarded to the tool / turbo / script. */
@@ -290,6 +303,12 @@ interface AstromechOptions {
290
303
  lookPath?: (cwd: string, bin: string) => string | null;
291
304
  }
292
305
  interface RunOptions {
306
+ /**
307
+ * Sub-job within the task — `performance` in `holocron run audit
308
+ * performance`. Only meaningful for tasks that declare `jobs` (`audit`);
309
+ * ignored otherwise. Omit to run every job the task has.
310
+ */
311
+ job?: string;
293
312
  /** Args after `--`, forwarded to the tool / turbo / script. */
294
313
  passthrough?: string[];
295
314
  /** Print the resolved command without running it. */
@@ -300,7 +319,7 @@ interface RunOptions {
300
319
  filter?: string;
301
320
  }
302
321
  interface Astromech {
303
- /** Run one task locally. */
322
+ /** Run one task — or one of its sub-jobs (`opts.job`) — locally. */
304
323
  run(task: string, opts?: RunOptions): RunTaskReport;
305
324
  /**
306
325
  * Run the merge-gating checks locally, in CI order — "will CI pass?".
@@ -439,6 +458,18 @@ interface LocalRunner {
439
458
  /** The task is already a holocron subcommand (`sync`, `sync-wiki`). */
440
459
  command?: string;
441
460
  }
461
+ /** One sub-job of a task — `performance` in `holocron run audit performance`. */
462
+ interface JobDef {
463
+ /** How the job runs locally; `null` → no local equivalent (enforced in CI). */
464
+ local: LocalRunner | null;
465
+ /**
466
+ * The CI status-check context this job reports as (`audit / Knip`) — every
467
+ * sub-job is a CI job. `holocron run audit` and `holocron ci` label each job
468
+ * line with it; the task-level `… / Conclusion` context lives in
469
+ * `WORKFLOW_CHECK_CONTEXTS`.
470
+ */
471
+ checkContext: string;
472
+ }
442
473
  interface TaskDef {
443
474
  /**
444
475
  * `null` — the registry has no built-in runner (CodeQL, deploys, audit's
@@ -448,10 +479,11 @@ interface TaskDef {
448
479
  * is `required` (a CI-only check isn't a local one).
449
480
  */
450
481
  local: LocalRunner | null;
451
- /** Sub-jobs, keyed by slug — `holocron run audit performance`. */
452
- jobs?: Record<string, {
453
- local: LocalRunner | null;
454
- }>;
482
+ /**
483
+ * Sub-jobs, keyed by slug — `holocron run audit performance`. Declared order
484
+ * is run order: `holocron run audit` (no job) runs each in turn.
485
+ */
486
+ jobs?: Record<string, JobDef>;
455
487
  /** Org-default flags injected by tool name. Removed by a repo override. */
456
488
  flags?: Record<string, string[]>;
457
489
  /**
@@ -503,4 +535,4 @@ declare const WORKFLOW_TEMPLATE_PROPERTIES: Record<string, string>;
503
535
  */
504
536
  declare function reusableTemplates(): Map<string, string>;
505
537
  //#endregion
506
- export { type Astromech, type AstromechOptions, CI_ORDER, type CiJobReport, type CiOptions, type CiReport, type ExecFn, 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, type SuperLinterConfig, TASKS, type TaskDef, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, WORKFLOW_TEMPLATE_PROPERTIES, baselineSuperLinterEnv, createAstromech, deriveDeployPaths, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, lintThinCallerWith, normalizeWorkflowWith, requiredChecks, resolveLinters, reusableTemplates, runCi, runTask, superLinterConfig };
538
+ 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, type SuperLinterConfig, TASKS, type TaskDef, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, WORKFLOW_TEMPLATE_PROPERTIES, baselineSuperLinterEnv, createAstromech, deriveDeployPaths, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, lintThinCallerWith, normalizeWorkflowWith, requiredChecks, resolveLinters, reusableTemplates, runCi, runTask, superLinterConfig };
package/dist/index.mjs CHANGED
@@ -45,7 +45,31 @@ const TASKS = {
45
45
  ] } },
46
46
  sync: { local: { command: "sync" } },
47
47
  wiki: { local: { command: "sync-wiki" } },
48
- audit: { local: null },
48
+ audit: {
49
+ local: null,
50
+ jobs: {
51
+ "bundle-size": {
52
+ local: null,
53
+ checkContext: "audit / Audit the bundle size"
54
+ },
55
+ knip: {
56
+ local: { tool: "knip" },
57
+ checkContext: "audit / Knip"
58
+ },
59
+ performance: {
60
+ local: { detect: [{
61
+ when: /^lighthouse\.config\.(cjs|js|mjs|ts)$/,
62
+ tool: "lhci",
63
+ args: ["autorun"]
64
+ }, {
65
+ when: /^lighthouserc\.(cjs|js|mjs|json|yml|yaml)$/,
66
+ tool: "lhci",
67
+ args: ["autorun"]
68
+ }] },
69
+ checkContext: "audit / Audit the performance"
70
+ }
71
+ }
72
+ },
49
73
  codeql: { local: null },
50
74
  deploy: { local: null }
51
75
  };
@@ -197,18 +221,26 @@ function resolveLinters(opts) {
197
221
  //#endregion
198
222
  //#region src/run.ts
199
223
  /**
200
- * `holocron run <task> [-- <passthrough>]` — run a registry task locally.
224
+ * `holocron run <task> [job] [-- <passthrough>]` — run a registry task (or one
225
+ * of its sub-jobs) locally.
226
+ *
227
+ * A `job` argument only applies to tasks that declare `jobs` (today: `audit`);
228
+ * for any other task it is folded back into the passthrough (`holocron run
229
+ * build src/`). `holocron run audit performance` runs one job; `holocron run
230
+ * audit` runs every job in declared order.
201
231
  *
202
232
  * Resolution:
203
233
  *
204
- * 0. task === "lint" → the linter aggregate (see below)
234
+ * 0. task === "lint" (no job) → the linter aggregate (see below)
235
+ * J. job given → TASKS[task].jobs[job].local (unknown job → exit 1)
205
236
  * 1. turbo.json defines the task → `turbo run <task>`
206
237
  * 2. package.json has a `<task>` script → `<pm> run <task>`
207
238
  * (unless it's the `holocron run …` thin caller — that recurses)
208
239
  * 3. TASKS[task].local resolves → `<tool> <args> <org-flags> <passthrough>`
209
- * 4. TASKS[task].local === null → "enforced in CI" (skip, even with --required)
210
- * 4b. known task, nothing resolved → "no <task> task" (exit 0, or 1 with --required)
211
- * 5. unknown task → "unknown task" (exit 1)
240
+ * 4. TASKS[task].jobs has entries → run each job in declared order
241
+ * 5. TASKS[task].local === null → "enforced in CI" (skip, even with --required)
242
+ * 5b. known task, nothing resolved → "no <task> task" (exit 0, or 1 with --required)
243
+ * 6. unknown task → "unknown task" (exit 1)
212
244
  *
213
245
  * `lint` runs the resolved linter set (`config.tasks` `linters`, else
214
246
  * auto-detected): the eslint slot goes through the standard turbo / script /
@@ -252,9 +284,36 @@ function makeRunOne(input) {
252
284
  }
253
285
  function runTask(input) {
254
286
  const { print, logger, readFile, fileExists, listDir, task, cwd } = input;
255
- const passthrough = input.passthrough ?? [];
287
+ const def = TASKS[task];
288
+ let job = input.job;
289
+ let passthrough = input.passthrough ?? [];
290
+ if (job !== void 0 && !def?.jobs) {
291
+ passthrough = [job, ...passthrough];
292
+ job = void 0;
293
+ }
294
+ if (job !== void 0) {
295
+ const jobDef = def.jobs[job];
296
+ if (!jobDef) {
297
+ const known = Object.keys(def.jobs).join(", ");
298
+ print(`✗ unknown job "${job}" for "${task}" (known: ${known})`);
299
+ logger.warn({
300
+ task,
301
+ job,
302
+ status: "unknown"
303
+ }, `run: ${task} ${job}`);
304
+ return {
305
+ status: "unknown",
306
+ message: `unknown job "${task} ${job}"`
307
+ };
308
+ }
309
+ const label = `${task} ${job}`;
310
+ return runUnit(input, label, jobDef.local, makeRunOne({
311
+ ...input,
312
+ task: label
313
+ }), passthrough);
314
+ }
256
315
  const run = makeRunOne(input);
257
- if (task === "lint") return runLintAggregate(input);
316
+ if (task === "lint") return runLintAggregate(input, passthrough);
258
317
  if (turboDefinesTask(cwd, task, readFile, fileExists)) {
259
318
  const args = [
260
319
  "run",
@@ -264,7 +323,6 @@ function runTask(input) {
264
323
  ];
265
324
  return run(resolveBin(cwd, "turbo", fileExists), args);
266
325
  }
267
- const def = TASKS[task];
268
326
  const script = packageJsonScript(cwd, task, readFile, fileExists);
269
327
  if (script && !/^holocron run\b/.test(script.trim())) return run(packageManager(cwd, readFile, fileExists), [
270
328
  "run",
@@ -287,6 +345,7 @@ function runTask(input) {
287
345
  ]);
288
346
  }
289
347
  }
348
+ if (def?.jobs && Object.keys(def.jobs).length > 0) return runAllJobs(input, def.jobs, passthrough);
290
349
  if (def?.local === null) {
291
350
  const msg = `no local equivalent for ${task} — enforced in CI`;
292
351
  print(`· ${msg}`);
@@ -322,13 +381,94 @@ function runTask(input) {
322
381
  };
323
382
  }
324
383
  /**
384
+ * Run one registry unit — a task's own runner or a single sub-job. Resolution
385
+ * is registry-only (`turbo` / `package.json` scripts are keyed by task, not
386
+ * `task/job`). `label` names the unit in messages (`audit performance`).
387
+ *
388
+ * `local: null` → CI-only by design, always a skip (never a failure, even with
389
+ * `--required`). A declared runner with no matching repo file, or whose tool
390
+ * isn't installed locally → a skip (`--required` turns it into a failure — the
391
+ * repo claims a check it can't back). Mirrors the lint aggregate: run what's
392
+ * here, flag the rest, let CI enforce.
393
+ */
394
+ function runUnit(input, label, local, run, passthrough) {
395
+ const { print, logger, listDir, lookPath, cwd } = input;
396
+ const skip = (msg) => {
397
+ print(input.required ? `✗ ${msg} (required)` : `· ${msg}`);
398
+ logger[input.required ? "warn" : "debug"]({
399
+ task: label,
400
+ status: input.required ? "fail" : "skip"
401
+ }, `run: ${label}`);
402
+ return {
403
+ status: input.required ? "fail" : "skip",
404
+ message: msg
405
+ };
406
+ };
407
+ if (local) {
408
+ const runner = resolveRunner(local, cwd, listDir);
409
+ if (!runner) return skip(`no ${label} runner for this repo`);
410
+ const found = lookPath(cwd, runner.tool);
411
+ if (!found) return skip(`${runner.tool} not installed locally for ${label} — enforced in CI`);
412
+ return run(found, [...runner.args, ...passthrough]);
413
+ }
414
+ const msg = `no local equivalent for ${label} — enforced in CI`;
415
+ print(`· ${msg}`);
416
+ logger.debug({
417
+ task: label,
418
+ status: "skip"
419
+ }, `run: ${label}`);
420
+ return {
421
+ status: "skip",
422
+ message: msg
423
+ };
424
+ }
425
+ /**
426
+ * `holocron run <task>` for a task that is a container of sub-jobs (`audit`) —
427
+ * run each job in declared order. Status precedence: any `fail` → `fail`; else
428
+ * any `ok` → `ok`; else any `dry-run` → `dry-run`; else `skip` (nothing ran).
429
+ */
430
+ function runAllJobs(input, jobs, passthrough) {
431
+ const { print, task } = input;
432
+ const perJob = {
433
+ ...input,
434
+ required: false
435
+ };
436
+ const reports = [];
437
+ for (const [name, jobDef] of Object.entries(jobs)) {
438
+ const label = `${task} ${name}`;
439
+ print(`▶ ${jobDef.checkContext}`);
440
+ reports.push(runUnit(perJob, label, jobDef.local, makeRunOne({
441
+ ...perJob,
442
+ task: label
443
+ }), passthrough));
444
+ }
445
+ const command = reports.map((r) => r.command).filter((c) => Boolean(c)).join(" && ");
446
+ const statuses = new Set(reports.map((r) => r.status));
447
+ if (statuses.has("fail")) return {
448
+ status: "fail",
449
+ command,
450
+ message: `one or more ${task} jobs failed`
451
+ };
452
+ if (statuses.has("ok")) return {
453
+ status: "ok",
454
+ command
455
+ };
456
+ if (statuses.has("dry-run")) return {
457
+ status: "dry-run",
458
+ command
459
+ };
460
+ return {
461
+ status: "skip",
462
+ message: `no ${task} job has a local equivalent — enforced in CI`
463
+ };
464
+ }
465
+ /**
325
466
  * `holocron run lint` — the resolved linter set, run natively. The eslint
326
467
  * slot reuses turbo / the `lint` script / `eslint .`; the rest run their
327
468
  * `localBin` when it resolves on PATH. Worst exit code wins.
328
469
  */
329
- function runLintAggregate(input) {
470
+ function runLintAggregate(input, passthrough) {
330
471
  const { print, logger, readFile, fileExists, listDir, lookPath, cwd } = input;
331
- const passthrough = input.passthrough ?? [];
332
472
  const dryRun = input.dryRun ?? false;
333
473
  const runOne = makeRunOne(input);
334
474
  const pass = passthrough.length ? ["--", ...passthrough] : [];
@@ -1026,11 +1166,12 @@ function createAstromech(options) {
1026
1166
  ...deps,
1027
1167
  task,
1028
1168
  cwd: options.cwd,
1169
+ ...opts.job !== void 0 ? { job: opts.job } : {},
1029
1170
  passthrough: opts.passthrough ?? [],
1030
1171
  dryRun: opts.dryRun ?? false,
1031
1172
  required: opts.required ?? false,
1032
1173
  ...opts.filter ? { filter: opts.filter } : {},
1033
- ...task === "lint" ? { linters: lintEntry()?.linters } : {}
1174
+ ...task === "lint" && opts.job === void 0 ? { linters: lintEntry()?.linters } : {}
1034
1175
  }),
1035
1176
  ci: (opts = {}) => runCi({
1036
1177
  ...deps,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/astromech",
3
- "version": "4.10.3",
3
+ "version": "4.11.0",
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": "4.10.3"
40
+ "@theholocron/datapad": "4.11.0"
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": "4.10.3"
56
+ "@theholocron/rollup-plugin-transform-template": "4.11.0"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">=22"