@theholocron/astromech 5.0.0-alpha.146 → 5.0.0-alpha.148
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/config/index.d.mts +24 -8
- package/dist/config/index.mjs +1 -1
- package/dist/index.d.mts +140 -201
- package/dist/index.mjs +341 -243
- package/dist/{load-ytspboBj.mjs → load-BqpxDjMJ.mjs} +27 -14
- package/dist/{schema-BDld3xq1.d.mts → schema.type-BUiSrwS5.d.mts} +2 -12
- package/package.json +6 -3
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { r as normalizeTaskEntry, t as loadTasksConfig } from "./load-
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
1
|
+
import { r as normalizeTaskEntry, t as loadTasksConfig } from "./load-BqpxDjMJ.mjs";
|
|
3
2
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
3
|
import { join, relative } from "node:path";
|
|
4
|
+
import { stringifyJson } from "@theholocron/json-utils";
|
|
5
|
+
import { dependencyNames, detectPackageManager, readPackageJsonSync } from "@theholocron/pkg-utils";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { findExecutableSync } from "@theholocron/fs-utils";
|
|
5
8
|
//#region src/templates/codecov/codecov.yml
|
|
6
9
|
var codecov_default = "codecov:\n require_ci_to_pass: true\n\ncoverage:\n precision: 2\n round: down\n status:\n project:\n default:\n target: auto\n threshold: 2%\n # Infra-only PRs (dep bumps, config, lockfile) touch no coverable code,\n # so codecov produces no report to compare. Pass instead of blocking\n # forever on a required check that will never post.\n if_not_found: success\n patch:\n default:\n target: 80%\n if_not_found: success\n\ncomment:\n layout: \"reach,diff,flags,components\"\n behavior: default\n require_changes: true\n\ncomponent_management:\n default_rules:\n statuses:\n - type: patch\n target: 80%\n if_not_found: success\n individual_components:\n";
|
|
7
10
|
//#endregion
|
|
@@ -607,6 +610,27 @@ function reusableTemplates() {
|
|
|
607
610
|
}
|
|
608
611
|
//#endregion
|
|
609
612
|
//#region src/registry.ts
|
|
613
|
+
/**
|
|
614
|
+
* The task registry — how each task runs *locally*, without GitHub
|
|
615
|
+
* Actions. `holocron run <task>` and `holocron run ci` resolve against this;
|
|
616
|
+
* adding a task here gives every repo that task.
|
|
617
|
+
*
|
|
618
|
+
* Keyed identically to the workflow templates — a task IS a workflow. This
|
|
619
|
+
* is the canonical vocabulary table (epic #672, D11): every other artifact
|
|
620
|
+
* (`KNOWN_TASKS`, `thin-callers.ts`'s `KNOWN_WORKFLOWS`/`WORKFLOW_CHECK_CONTEXTS`,
|
|
621
|
+
* `CI_ORDER`) derives from these keys rather than hand-duplicating them —
|
|
622
|
+
* including the future GitHub App (#679), which imports this same table for
|
|
623
|
+
* config-schema validation instead of reimplementing its own copy.
|
|
624
|
+
*
|
|
625
|
+
* Task names are an intent-facing vocabulary (`verification.*`,
|
|
626
|
+
* `sourceQuality.*`, `security.*`, `delivery.*`, `platform.*`,
|
|
627
|
+
* `knowledge.*`), not tool names — `eslint`/`vitest`/`tsdown`/… stay
|
|
628
|
+
* internal to this file and `theholocron/configs`. See
|
|
629
|
+
* `docs/wiki/specifications/tech-vocabulary-rename.spec.md` (#675) for the full mapping and
|
|
630
|
+
* the reasoning behind each namespace and decomposition.
|
|
631
|
+
*
|
|
632
|
+
* Spec: `docs/wiki/specifications/tech-astromech-task-runner.spec.md` (epic #581).
|
|
633
|
+
*/
|
|
610
634
|
const TASKS = {
|
|
611
635
|
"verification.unitTests": {
|
|
612
636
|
local: {
|
|
@@ -869,11 +893,11 @@ function turboConfig(config) {
|
|
|
869
893
|
} : turbo;
|
|
870
894
|
}
|
|
871
895
|
if (Object.keys(tasks).length === 0) return null;
|
|
872
|
-
return
|
|
896
|
+
return stringifyJson({
|
|
873
897
|
$schema: "https://turborepo.org/schema.json",
|
|
874
898
|
globalDependencies: GLOBAL_DEPENDENCIES,
|
|
875
899
|
tasks
|
|
876
|
-
}
|
|
900
|
+
});
|
|
877
901
|
}
|
|
878
902
|
/**
|
|
879
903
|
* Fixes #692: `turboConfig()` writes a `turbo.json` the moment *any* task in
|
|
@@ -1014,13 +1038,22 @@ function ensureTurboDependency(packageJson, workspaceYaml) {
|
|
|
1014
1038
|
devDependencies
|
|
1015
1039
|
};
|
|
1016
1040
|
return {
|
|
1017
|
-
content:
|
|
1041
|
+
content: stringifyJson(updated, { like: packageJson }),
|
|
1018
1042
|
changed: true
|
|
1019
1043
|
};
|
|
1020
1044
|
}
|
|
1021
1045
|
//#endregion
|
|
1022
1046
|
//#region src/tasks/run/linters.ts
|
|
1023
1047
|
/**
|
|
1048
|
+
* The linter registry — one list of linter names (from `config.tasks`'
|
|
1049
|
+
* `{ name: "lint", linters: [...] }`, or auto-detected) maps to both the
|
|
1050
|
+
* CI super-linter `VALIDATE_*` env and the native local run. This table is
|
|
1051
|
+
* the single source of truth for that mapping; {@link superLinterConfig}
|
|
1052
|
+
* (CI) and the `holocron run lint` aggregate (local) both read it.
|
|
1053
|
+
*
|
|
1054
|
+
* Spec: `docs/wiki/specifications/tech-astromech-task-runner.spec.md` "Lint parity".
|
|
1055
|
+
*/
|
|
1056
|
+
/**
|
|
1024
1057
|
* Known linters, in execution order. `always` entries are the current
|
|
1025
1058
|
* hard-coded super-linter baseline; `prettier` is always-on because the org
|
|
1026
1059
|
* applies it universally (super-linter only lints files that exist).
|
|
@@ -1164,7 +1197,79 @@ function resolveLinters(opts) {
|
|
|
1164
1197
|
}));
|
|
1165
1198
|
}
|
|
1166
1199
|
//#endregion
|
|
1167
|
-
//#region src/tasks/run/
|
|
1200
|
+
//#region src/tasks/run/resolve-bin.ts
|
|
1201
|
+
/** `<cwd>/node_modules/.bin/<tool>` when it exists, else the bare `tool` name (left to `PATH`). */
|
|
1202
|
+
function resolveBin(cwd, tool, fileExists) {
|
|
1203
|
+
const local = join(cwd, "node_modules", ".bin", tool);
|
|
1204
|
+
return fileExists(local) ? local : tool;
|
|
1205
|
+
}
|
|
1206
|
+
//#endregion
|
|
1207
|
+
//#region src/tasks/run/resolve-runner.ts
|
|
1208
|
+
const mkRunner = (tool, args = []) => ({
|
|
1209
|
+
tool,
|
|
1210
|
+
args
|
|
1211
|
+
});
|
|
1212
|
+
/**
|
|
1213
|
+
* Resolve `{ tool, args }` for a runner, applying `detect[]` against repo
|
|
1214
|
+
* files, falling back (#750) to each candidate's `tool` name against
|
|
1215
|
+
* `package.json`'s own declared dependencies when neither matches by
|
|
1216
|
+
* filename. Only called for `tool` / `detect` runners — the caller handles
|
|
1217
|
+
* `command` runners itself, so `local.detect` is present whenever
|
|
1218
|
+
* `local.tool` is not.
|
|
1219
|
+
*
|
|
1220
|
+
* The dependency fallback matters once a Bucket A migration (#680) deletes
|
|
1221
|
+
* a build tool's own config file (`tsdown.config.ts`, `vite.config.ts`, …):
|
|
1222
|
+
* that file was the *only* signal `detect[]` had to offer, so removing it
|
|
1223
|
+
* used to make `delivery.build` report "no task for this repo" and silently
|
|
1224
|
+
* skip the build, even though the tool is still very much in use — a
|
|
1225
|
+
* package can't actually build with `tsdown` without it in
|
|
1226
|
+
* `dependencies`/`devDependencies`, so that's a signal at least as reliable
|
|
1227
|
+
* as the config file, and one deleting the file doesn't remove.
|
|
1228
|
+
*
|
|
1229
|
+
* **Per-candidate, not two full passes** (#750 follow-up — the first
|
|
1230
|
+
* shipped version got this wrong): `delivery.build`'s own `detect[]` ends
|
|
1231
|
+
* with `{ when: /^tsconfig\.json$/, tool: "tsc" }` as its last-resort
|
|
1232
|
+
* fallback, and `tsconfig.json` exists in essentially every package
|
|
1233
|
+
* regardless of build tool. A "check every candidate's filename, *then*
|
|
1234
|
+
* check every candidate's dependency" two-pass design would let that
|
|
1235
|
+
* always-present fallback file win over a real `tsdown`/`vite`/`rollup`
|
|
1236
|
+
* dependency match, the moment the earlier candidates' own config files are
|
|
1237
|
+
* gone — exactly backwards. Checking `(file match) || (dependency match)`
|
|
1238
|
+
* together for each candidate before moving to the next preserves the
|
|
1239
|
+
* intended precedence: a real signal for an *earlier* candidate, by either
|
|
1240
|
+
* means, still wins over a later candidate's mere filename presence.
|
|
1241
|
+
*/
|
|
1242
|
+
function resolveRunner(local, cwd, listDir, readFile) {
|
|
1243
|
+
if (local.tool) return mkRunner(local.tool, local.args);
|
|
1244
|
+
let files;
|
|
1245
|
+
try {
|
|
1246
|
+
files = listDir(cwd);
|
|
1247
|
+
} catch {
|
|
1248
|
+
files = [];
|
|
1249
|
+
}
|
|
1250
|
+
const deps = dependencyNames(readPackageJsonSync(cwd, { readFile }));
|
|
1251
|
+
for (const candidate of local.detect) if (files.some((f) => candidate.when.test(f)) || deps.has(candidate.tool)) return mkRunner(candidate.tool, candidate.args);
|
|
1252
|
+
}
|
|
1253
|
+
//#endregion
|
|
1254
|
+
//#region src/tasks/run/resolve-export-entry.ts
|
|
1255
|
+
/**
|
|
1256
|
+
* Unwrap a `package.json#exports` subpath entry down to its file path —
|
|
1257
|
+
* either a bare string, or an object with condition keys (`import`,
|
|
1258
|
+
* `default`, `require`, …) that may themselves nest one level (`{ import: {
|
|
1259
|
+
* types, default } }`, the shape every `@theholocron/*-config` package
|
|
1260
|
+
* actually uses). Prefers `import` (this org ships ESM), then `default`,
|
|
1261
|
+
* then `require` as a last resort.
|
|
1262
|
+
*/
|
|
1263
|
+
function resolveExportEntry(entry) {
|
|
1264
|
+
if (typeof entry === "string") return entry;
|
|
1265
|
+
if (entry === null || typeof entry !== "object") return void 0;
|
|
1266
|
+
const conditions = entry;
|
|
1267
|
+
const value = conditions.import ?? conditions.default ?? conditions.require;
|
|
1268
|
+
if (typeof value === "string") return value;
|
|
1269
|
+
if (typeof value === "object" && value !== null) return resolveExportEntry(value);
|
|
1270
|
+
}
|
|
1271
|
+
//#endregion
|
|
1272
|
+
//#region src/tasks/run/tool-configs.ts
|
|
1168
1273
|
/**
|
|
1169
1274
|
* `library` is every mapped tool's org-wide default variant today — every
|
|
1170
1275
|
* currently-migrated repo (`holocron`, `clients`, `utils`, `configs`,
|
|
@@ -1241,7 +1346,42 @@ const LOCAL_CONFIG_FILES = {
|
|
|
1241
1346
|
"commitlint.config.cjs"
|
|
1242
1347
|
]
|
|
1243
1348
|
};
|
|
1244
|
-
|
|
1349
|
+
//#endregion
|
|
1350
|
+
//#region src/tasks/run/resolve-tool-config.ts
|
|
1351
|
+
/**
|
|
1352
|
+
* Resolves the absolute path to a Bucket A tool's shared `@theholocron/*-config`
|
|
1353
|
+
* entry point, installed in the consuming repo's own `node_modules` — and the
|
|
1354
|
+
* CLI flag(s) to hand it via (`--config`, `--extends`, …). `run.ts` splices the
|
|
1355
|
+
* result into the command it builds for a tool/detect runner or a linter-group
|
|
1356
|
+
* entry, right after the tool's own args.
|
|
1357
|
+
*
|
|
1358
|
+
* Every mapped package's resolved entry point already carries a ready-to-use
|
|
1359
|
+
* default export (a plain re-export for `prettier`/`commitlint`; an invoked,
|
|
1360
|
+
* zero-arg preset for `eslint`/`vitest`/`tsdown` — verified end-to-end against
|
|
1361
|
+
* each tool's real `--config`/`--extends` loader, not just the file's shape).
|
|
1362
|
+
* A repo doesn't need this package installed at all — every lookup degrades to
|
|
1363
|
+
* `[]` (no flag added, the tool falls back to its own auto-discovery of a
|
|
1364
|
+
* local file, unchanged from today) rather than throwing.
|
|
1365
|
+
*
|
|
1366
|
+
* **Local file wins.** A `<tool>.config.*` sitting in `cwd` (whether it's
|
|
1367
|
+
* pure boilerplate or has real per-package customization on top of the
|
|
1368
|
+
* shared bundle, e.g. `mergeConfig(base, { test: { coverage: { exclude }
|
|
1369
|
+
* } })`) always skips the splice, even when the shared package is also
|
|
1370
|
+
* installed — an explicit `--config <shared path>` flag beats a tool's own
|
|
1371
|
+
* auto-discovery every time, so splicing it unconditionally would silently
|
|
1372
|
+
* discard that customization the file is still sitting right there on disk.
|
|
1373
|
+
* The shared bundle is only ever the fallback for a package with *no* local
|
|
1374
|
+
* file at all — that's still the "delete the file to go fully shared" path
|
|
1375
|
+
* this was designed for (#676); it just isn't unconditional (#749).
|
|
1376
|
+
*
|
|
1377
|
+
* `semantic-release`, `devmoji`, `editorconfig-checker`, and `knip` are
|
|
1378
|
+
* deliberately absent — `semantic-release-config`'s `defineConfig()` needs
|
|
1379
|
+
* real per-repo data (branches, npm options) a static `--extends <path>` can't
|
|
1380
|
+
* carry; `devmoji` runs through a git hook template, not `holocron run
|
|
1381
|
+
* <task>`; `editorconfig-checker` has no shared-config package yet; `knip` is
|
|
1382
|
+
* repo-specific by nature, never a shared-config candidate. See
|
|
1383
|
+
* `.notes/tech-config-resolution.spec.md` (#676).
|
|
1384
|
+
*/
|
|
1245
1385
|
/**
|
|
1246
1386
|
* `<flag> <absolute path>` for `tool`, resolved against `cwd`'s
|
|
1247
1387
|
* `node_modules` — or `[]` when the tool isn't mapped, `cwd` already has its
|
|
@@ -1254,9 +1394,9 @@ new Set(Object.keys(TOOL_CONFIGS));
|
|
|
1254
1394
|
function resolveToolConfig(tool, cwd, deps) {
|
|
1255
1395
|
const spec = TOOL_CONFIGS[tool];
|
|
1256
1396
|
if (!spec) return [];
|
|
1257
|
-
if ((LOCAL_CONFIG_FILES[tool] ?? []).some((name) => deps.fileExists(
|
|
1258
|
-
const pkgDir =
|
|
1259
|
-
const pkgJsonPath =
|
|
1397
|
+
if ((LOCAL_CONFIG_FILES[tool] ?? []).some((name) => deps.fileExists(join(cwd, name)))) return [];
|
|
1398
|
+
const pkgDir = join(cwd, "node_modules", ...spec.package.split("/"));
|
|
1399
|
+
const pkgJsonPath = join(pkgDir, "package.json");
|
|
1260
1400
|
if (!deps.fileExists(pkgJsonPath)) return [];
|
|
1261
1401
|
let pkg;
|
|
1262
1402
|
try {
|
|
@@ -1266,36 +1406,21 @@ function resolveToolConfig(tool, cwd, deps) {
|
|
|
1266
1406
|
}
|
|
1267
1407
|
const relative = resolveExportEntry(pkg.exports?.[spec.exportPath]);
|
|
1268
1408
|
if (!relative) return [];
|
|
1269
|
-
const absolute =
|
|
1409
|
+
const absolute = join(pkgDir, relative);
|
|
1270
1410
|
if (!deps.fileExists(absolute)) return [];
|
|
1271
1411
|
return [...spec.flag, absolute];
|
|
1272
1412
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
const conditions = entry;
|
|
1285
|
-
const value = conditions.import ?? conditions.default ?? conditions.require;
|
|
1286
|
-
if (typeof value === "string") return value;
|
|
1287
|
-
if (typeof value === "object" && value !== null) return resolveExportEntry(value);
|
|
1288
|
-
}
|
|
1289
|
-
/**
|
|
1290
|
-
* A minimal, POSIX-safe `path.join` — avoids importing `node:path` just for
|
|
1291
|
-
* this. Every path in play here is either already POSIX (a `package.json`
|
|
1292
|
-
* `exports` value, always forward-slashed regardless of platform) or built
|
|
1293
|
-
* from platform-neutral segments (`cwd`, `"node_modules"`, a scoped package
|
|
1294
|
-
* name split on `/`), so a manual join is safe and keeps this module
|
|
1295
|
-
* dependency-free like the rest of `run.ts`'s helpers.
|
|
1296
|
-
*/
|
|
1297
|
-
function joinPath(...segments) {
|
|
1298
|
-
return segments.map((s, i) => i === 0 ? s.replace(/\/+$/, "") : s.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
|
|
1413
|
+
//#endregion
|
|
1414
|
+
//#region src/tasks/run/turbo-defines-task.ts
|
|
1415
|
+
/** `true` when `<cwd>/turbo.json` declares `task` (under `tasks`, or turbo 1.x's `pipeline`). */
|
|
1416
|
+
function turboDefinesTask(cwd, task, readFile, fileExists) {
|
|
1417
|
+
if (!fileExists(join(cwd, "turbo.json"))) return false;
|
|
1418
|
+
try {
|
|
1419
|
+
const turbo = JSON.parse(readFile(join(cwd, "turbo.json")));
|
|
1420
|
+
return Boolean(turbo.tasks?.[task] ?? turbo.pipeline?.[task]);
|
|
1421
|
+
} catch {
|
|
1422
|
+
return false;
|
|
1423
|
+
}
|
|
1299
1424
|
}
|
|
1300
1425
|
//#endregion
|
|
1301
1426
|
//#region src/tasks/run/run.ts
|
|
@@ -1404,8 +1529,11 @@ function runTask(input) {
|
|
|
1404
1529
|
];
|
|
1405
1530
|
return run(resolveBin(cwd, "turbo", fileExists), args);
|
|
1406
1531
|
}
|
|
1407
|
-
const script =
|
|
1408
|
-
if (script && !/^holocron run\b/.test(script.trim())) return run(
|
|
1532
|
+
const script = readPackageJsonSync(cwd, { readFile })?.scripts?.[task];
|
|
1533
|
+
if (script && !/^holocron run\b/.test(script.trim())) return run(detectPackageManager(cwd, {
|
|
1534
|
+
readFile,
|
|
1535
|
+
fileExists
|
|
1536
|
+
}), [
|
|
1409
1537
|
"run",
|
|
1410
1538
|
task,
|
|
1411
1539
|
...passthrough.length ? ["--", ...passthrough] : []
|
|
@@ -1627,92 +1755,6 @@ function runLinterGroup(input, names, passthrough) {
|
|
|
1627
1755
|
command
|
|
1628
1756
|
};
|
|
1629
1757
|
}
|
|
1630
|
-
const mkRunner = (tool, args = []) => ({
|
|
1631
|
-
tool,
|
|
1632
|
-
args
|
|
1633
|
-
});
|
|
1634
|
-
/**
|
|
1635
|
-
* Resolve `{ tool, args }` for a runner, applying `detect[]` against repo
|
|
1636
|
-
* files, falling back (#750) to each candidate's `tool` name against
|
|
1637
|
-
* `package.json`'s own declared dependencies when neither matches by
|
|
1638
|
-
* filename. Only called for `tool` / `detect` runners — the caller handles
|
|
1639
|
-
* `command` runners itself, so `local.detect` is present whenever
|
|
1640
|
-
* `local.tool` is not.
|
|
1641
|
-
*
|
|
1642
|
-
* The dependency fallback matters once a Bucket A migration (#680) deletes
|
|
1643
|
-
* a build tool's own config file (`tsdown.config.ts`, `vite.config.ts`, …):
|
|
1644
|
-
* that file was the *only* signal `detect[]` had to offer, so removing it
|
|
1645
|
-
* used to make `delivery.build` report "no task for this repo" and silently
|
|
1646
|
-
* skip the build, even though the tool is still very much in use — a
|
|
1647
|
-
* package can't actually build with `tsdown` without it in
|
|
1648
|
-
* `dependencies`/`devDependencies`, so that's a signal at least as reliable
|
|
1649
|
-
* as the config file, and one deleting the file doesn't remove.
|
|
1650
|
-
*
|
|
1651
|
-
* **Per-candidate, not two full passes** (#750 follow-up — the first
|
|
1652
|
-
* shipped version got this wrong): `delivery.build`'s own `detect[]` ends
|
|
1653
|
-
* with `{ when: /^tsconfig\.json$/, tool: "tsc" }` as its last-resort
|
|
1654
|
-
* fallback, and `tsconfig.json` exists in essentially every package
|
|
1655
|
-
* regardless of build tool. A "check every candidate's filename, *then*
|
|
1656
|
-
* check every candidate's dependency" two-pass design would let that
|
|
1657
|
-
* always-present fallback file win over a real `tsdown`/`vite`/`rollup`
|
|
1658
|
-
* dependency match, the moment the earlier candidates' own config files are
|
|
1659
|
-
* gone — exactly backwards. Checking `(file match) || (dependency match)`
|
|
1660
|
-
* together for each candidate before moving to the next preserves the
|
|
1661
|
-
* intended precedence: a real signal for an *earlier* candidate, by either
|
|
1662
|
-
* means, still wins over a later candidate's mere filename presence.
|
|
1663
|
-
*/
|
|
1664
|
-
function resolveRunner(local, cwd, listDir, readFile) {
|
|
1665
|
-
if (local.tool) return mkRunner(local.tool, local.args);
|
|
1666
|
-
let files;
|
|
1667
|
-
try {
|
|
1668
|
-
files = listDir(cwd);
|
|
1669
|
-
} catch {
|
|
1670
|
-
files = [];
|
|
1671
|
-
}
|
|
1672
|
-
const deps = packageJsonDependencies(cwd, readFile);
|
|
1673
|
-
for (const candidate of local.detect) if (files.some((f) => candidate.when.test(f)) || deps.has(candidate.tool)) return mkRunner(candidate.tool, candidate.args);
|
|
1674
|
-
}
|
|
1675
|
-
/** Every key from `package.json`'s `dependencies` + `devDependencies`, or an empty set on any read/parse failure. */
|
|
1676
|
-
function packageJsonDependencies(cwd, readFile) {
|
|
1677
|
-
try {
|
|
1678
|
-
const pkg = JSON.parse(readFile(join(cwd, "package.json")));
|
|
1679
|
-
return /* @__PURE__ */ new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})]);
|
|
1680
|
-
} catch {
|
|
1681
|
-
return /* @__PURE__ */ new Set();
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
function resolveBin(cwd, tool, fileExists) {
|
|
1685
|
-
const local = join(cwd, "node_modules", ".bin", tool);
|
|
1686
|
-
return fileExists(local) ? local : tool;
|
|
1687
|
-
}
|
|
1688
|
-
function packageManager(cwd, readFile, fileExists) {
|
|
1689
|
-
try {
|
|
1690
|
-
const pkg = JSON.parse(readFile(join(cwd, "package.json")));
|
|
1691
|
-
if (typeof pkg.packageManager === "string") return pkg.packageManager.split("@")[0];
|
|
1692
|
-
} catch {}
|
|
1693
|
-
if (fileExists(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
1694
|
-
if (fileExists(join(cwd, "bun.lockb"))) return "bun";
|
|
1695
|
-
if (fileExists(join(cwd, "yarn.lock"))) return "yarn";
|
|
1696
|
-
if (fileExists(join(cwd, "package-lock.json"))) return "npm";
|
|
1697
|
-
return "pnpm";
|
|
1698
|
-
}
|
|
1699
|
-
function turboDefinesTask(cwd, task, readFile, fileExists) {
|
|
1700
|
-
if (!fileExists(join(cwd, "turbo.json"))) return false;
|
|
1701
|
-
try {
|
|
1702
|
-
const turbo = JSON.parse(readFile(join(cwd, "turbo.json")));
|
|
1703
|
-
return Boolean(turbo.tasks?.[task] ?? turbo.pipeline?.[task]);
|
|
1704
|
-
} catch {
|
|
1705
|
-
return false;
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
function packageJsonScript(cwd, task, readFile, fileExists) {
|
|
1709
|
-
if (!fileExists(join(cwd, "package.json"))) return void 0;
|
|
1710
|
-
try {
|
|
1711
|
-
return JSON.parse(readFile(join(cwd, "package.json"))).scripts?.[task];
|
|
1712
|
-
} catch {
|
|
1713
|
-
return;
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
1758
|
//#endregion
|
|
1717
1759
|
//#region src/tasks/ci.ts
|
|
1718
1760
|
/**
|
|
@@ -1784,19 +1826,7 @@ function taskName(item) {
|
|
|
1784
1826
|
return typeof item === "string" ? item : item.name;
|
|
1785
1827
|
}
|
|
1786
1828
|
//#endregion
|
|
1787
|
-
//#region src/tasks/
|
|
1788
|
-
/**
|
|
1789
|
-
* `holocron run <namespace>` — run every configured task under one
|
|
1790
|
-
* namespace prefix (`sourceQuality.*`, `verification.*`, …) in sequence.
|
|
1791
|
-
* `Astromech.run()` falls back here whenever the given name isn't an exact
|
|
1792
|
-
* `TASKS` entry but matches the prefix of one or more configured tasks —
|
|
1793
|
-
* same "aggregate several real tasks into one report" shape `runCi` already
|
|
1794
|
-
* established, scoped to one namespace instead of the merge-gating set.
|
|
1795
|
-
*
|
|
1796
|
-
* Every task in the namespace always runs — one task's failure never skips
|
|
1797
|
-
* the rest, same as `runCi`'s own loop — so a single invocation surfaces
|
|
1798
|
-
* every problem in the namespace at once instead of stopping at the first.
|
|
1799
|
-
*/
|
|
1829
|
+
//#region src/tasks/match-namespace.ts
|
|
1800
1830
|
/** Every configured task whose name starts with `"<namespace>."`, in manifest declaration order. */
|
|
1801
1831
|
function matchNamespace(config, namespace) {
|
|
1802
1832
|
const prefix = `${namespace}.`;
|
|
@@ -1805,6 +1835,8 @@ function matchNamespace(config, namespace) {
|
|
|
1805
1835
|
return names;
|
|
1806
1836
|
}, []);
|
|
1807
1837
|
}
|
|
1838
|
+
//#endregion
|
|
1839
|
+
//#region src/tasks/group.ts
|
|
1808
1840
|
function runGroup(input) {
|
|
1809
1841
|
const { print, namespace } = input;
|
|
1810
1842
|
const names = matchNamespace(input.config, namespace);
|
|
@@ -1843,29 +1875,47 @@ function runGroup(input) {
|
|
|
1843
1875
|
};
|
|
1844
1876
|
}
|
|
1845
1877
|
//#endregion
|
|
1846
|
-
//#region src/
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1878
|
+
//#region src/utils/default-list-dirs.ts
|
|
1879
|
+
const defaultListDirs = (path) => readdirSync(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
1880
|
+
//#endregion
|
|
1881
|
+
//#region src/utils/default-read-json.ts
|
|
1882
|
+
const defaultReadJson = (path) => JSON.parse(readFileSync(path, "utf8"));
|
|
1883
|
+
//#endregion
|
|
1884
|
+
//#region src/utils/default-run.ts
|
|
1885
|
+
const defaultRun = (command, args, opts) => spawnSync(command, args, {
|
|
1886
|
+
encoding: "utf8",
|
|
1887
|
+
...opts
|
|
1888
|
+
});
|
|
1889
|
+
//#endregion
|
|
1890
|
+
//#region src/utils/default-sleep.ts
|
|
1891
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1892
|
+
//#endregion
|
|
1893
|
+
//#region src/utils/exec.ts
|
|
1894
|
+
/** Runs `cmd` with the parent's stdio; a signal-killed or unspawnable process reports `-1`. */
|
|
1856
1895
|
const realExec = (cmd, args, opts) => {
|
|
1857
1896
|
return { exitCode: spawnSync(cmd, args, {
|
|
1858
1897
|
cwd: opts.cwd,
|
|
1859
1898
|
stdio: "inherit"
|
|
1860
1899
|
}).status ?? -1 };
|
|
1861
1900
|
};
|
|
1901
|
+
//#endregion
|
|
1902
|
+
//#region src/utils/look-path.ts
|
|
1862
1903
|
/** `node_modules/.bin/<bin>`, else the first `PATH` entry that has it, else `null`. */
|
|
1863
|
-
const realLookPath = (cwd, bin) => {
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1904
|
+
const realLookPath = (cwd, bin) => findExecutableSync(bin, { path: join(cwd, "node_modules", ".bin") }) ?? findExecutableSync(bin) ?? null;
|
|
1905
|
+
//#endregion
|
|
1906
|
+
//#region src/utils/noop-logger.ts
|
|
1907
|
+
/** The `RunLogger` used when the caller injects none: drops every record. */
|
|
1908
|
+
const noopLogger = {
|
|
1909
|
+
debug() {},
|
|
1910
|
+
warn() {}
|
|
1868
1911
|
};
|
|
1912
|
+
//#endregion
|
|
1913
|
+
//#region src/astromech.ts
|
|
1914
|
+
/**
|
|
1915
|
+
* `createAstromech(options)` — the self-contained task runner.
|
|
1916
|
+
* `@theholocron/cli` instantiates it once and delegates the `run` /
|
|
1917
|
+
* `ci` / workflow-generation commands to it (like `@theholocron/observability`).
|
|
1918
|
+
*/
|
|
1869
1919
|
function createAstromech(options) {
|
|
1870
1920
|
const deps = {
|
|
1871
1921
|
print: options.print ?? ((line) => console.log(line)),
|
|
@@ -1980,6 +2030,47 @@ function createAstromech(options) {
|
|
|
1980
2030
|
//#endregion
|
|
1981
2031
|
//#region src/generators/tsconfig.ts
|
|
1982
2032
|
/**
|
|
2033
|
+
* `tsconfig.json` generation — Bucket B (config-resolution workstream, #676):
|
|
2034
|
+
* the file must stay committed (the TypeScript language server discovers it
|
|
2035
|
+
* directly from disk, no `--config` override mechanism to hand it a path
|
|
2036
|
+
* instead), but its content is fully derivable, so it's generated rather
|
|
2037
|
+
* than hand-authored — the same category as `.editorconfig`
|
|
2038
|
+
* ({@link "./templates/configs/editorconfig/create-config.js" createConfig})
|
|
2039
|
+
* and {@link codecovConfig}.
|
|
2040
|
+
*
|
|
2041
|
+
* Surveyed every package-level `tsconfig.json` across the org (`holocron`,
|
|
2042
|
+
* `clients`, `utils` — 20+ packages). `extends: "@theholocron/tsconfig/
|
|
2043
|
+
* node-lts"`, `compilerOptions.baseUrl: "./"`, `compilerOptions.outDir:
|
|
2044
|
+
* "./dist"`, `include: ["src/**\/*.ts"]`, and `exclude: ["node_modules",
|
|
2045
|
+
* "dist"]` are uniform in every one — safe defaults here. `paths` (a `@/*`
|
|
2046
|
+
* → `./src/*` import alias) is a real per-package choice, present in
|
|
2047
|
+
* roughly 40% of packages checked — an opt-in parameter, not a default.
|
|
2048
|
+
*
|
|
2049
|
+
* Two fields checked and deliberately *not* absorbed as defaults, unlike
|
|
2050
|
+
* `eslint-config`'s `tsconfigRootDir`/`settings.node` (which turned out
|
|
2051
|
+
* redundant with the shared package's own behavior, `configs`#461):
|
|
2052
|
+
* `compilerOptions.module`/`moduleResolution` (only `@theholocron/cli`
|
|
2053
|
+
* overrides these, to `esnext`/`bundler` — a genuine deviation from the
|
|
2054
|
+
* shared `node-lts` preset's `nodenext`/`nodenext`, needed for its bundler
|
|
2055
|
+
* tooling, not something every package should inherit) and
|
|
2056
|
+
* `compilerOptions.rootDir` (present in exactly two packages, always the
|
|
2057
|
+
* same value `./src` that `include` already implies — an unnecessary
|
|
2058
|
+
* override wherever it appears, not a pattern worth generalizing).
|
|
2059
|
+
*
|
|
2060
|
+
* Monorepo *root* `tsconfig.json` (a TS project-references "solution
|
|
2061
|
+
* file" — `holocron`'s own root is `{ files: [], references: [...] }`,
|
|
2062
|
+
* listing which packages to build) is a structurally different, genuinely
|
|
2063
|
+
* per-repo document — out of scope here, same as Bucket C content.
|
|
2064
|
+
*
|
|
2065
|
+
* Not yet wired into `holocron repo setup`'s per-package write loop (every
|
|
2066
|
+
* other Bucket B file there is a single repo-root file, safely
|
|
2067
|
+
* overwritten every run — `tsconfig.json` is per-package, and most
|
|
2068
|
+
* packages' files still carry real hand-authored content today, not yet
|
|
2069
|
+
* migrated to a fully-generated state). That per-package iteration +
|
|
2070
|
+
* safe-migration design is `#680`'s job, not duplicated here — this is
|
|
2071
|
+
* the generator itself, ready for it to call.
|
|
2072
|
+
*/
|
|
2073
|
+
/**
|
|
1983
2074
|
* A package-level `tsconfig.json` — the uniform shape confirmed across
|
|
1984
2075
|
* every package checked, parameterized only by the two fields that
|
|
1985
2076
|
* genuinely vary (`display`, `paths`). No scaffold/workflow header (matches
|
|
@@ -2002,81 +2093,25 @@ function createTsconfig(options) {
|
|
|
2002
2093
|
include: ["src/**/*.ts"],
|
|
2003
2094
|
exclude: ["node_modules", "dist"]
|
|
2004
2095
|
};
|
|
2005
|
-
return
|
|
2096
|
+
return stringifyJson(config);
|
|
2006
2097
|
}
|
|
2007
2098
|
//#endregion
|
|
2008
|
-
//#region src/tasks/deploy-on-release.ts
|
|
2009
|
-
/**
|
|
2010
|
-
* Deploy workspace packages once a release has published (holocron#930).
|
|
2011
|
-
*
|
|
2012
|
-
* A package opts in with a `delivery.deploy` task in its own `holocron.config`
|
|
2013
|
-
* whose `with.on` is `"release"`:
|
|
2014
|
-
*
|
|
2015
|
-
* ```ts
|
|
2016
|
-
* // packages/<app>/holocron.config.ts
|
|
2017
|
-
* tasks: [{ name: "delivery.deploy", with: { on: "release", channel: "alpha" } }];
|
|
2018
|
-
* ```
|
|
2019
|
-
*
|
|
2020
|
-
* `channel` defaults to `main` (a stable release); set a prerelease identifier
|
|
2021
|
-
* such as `alpha` to deploy those releases instead.
|
|
2022
|
-
*
|
|
2023
|
-
* The shared `delivery.publish` workflow runs this (`holocron deploy on-release`)
|
|
2024
|
-
* as its own `deploy` job after the release job, so a deploy is a separate,
|
|
2025
|
-
* re-runnable check rather than a step buried in the release log. Each package
|
|
2026
|
-
* with such a task deploys when the release is on its `channel` and changes a file under its paths: the package itself plus its
|
|
2027
|
-
* `workspace:*` dependencies ({@link defaultPaths}), or an explicit
|
|
2028
|
-
* `with.paths`. It first waits for npm to serve the release's new versions of
|
|
2029
|
-
* those dependencies ({@link waitForPublished}) — the registry can lag a version
|
|
2030
|
-
* it has just accepted, and the deploy installs them — then runs
|
|
2031
|
-
* `pnpm --filter <package> delivery.deploy`.
|
|
2032
|
-
*
|
|
2033
|
-
* The release is already published by then, so a failed deploy can't undo it;
|
|
2034
|
-
* it fails this job instead, loudly and re-runnable on its own. A task is
|
|
2035
|
-
* `skip`ped (not failed) only when it doesn't apply to this release.
|
|
2036
|
-
*
|
|
2037
|
-
* A `delivery.deploy` task with `on: "release"` generates no CI workflow.
|
|
2038
|
-
*/
|
|
2099
|
+
//#region src/tasks/deploy-on-release/constants.ts
|
|
2039
2100
|
/** A stable release (no prerelease identifier) is on `main`, the default channel for a task. */
|
|
2040
2101
|
const MAIN_CHANNEL = "main";
|
|
2041
2102
|
const DEPLOY_TASK = "delivery.deploy";
|
|
2042
2103
|
/** How long to wait for npm to serve the release's versions, and how often to ask. */
|
|
2043
2104
|
const WAIT_TIMEOUT_MS = 3e5;
|
|
2044
2105
|
const WAIT_INTERVAL_MS = 1e4;
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
try {
|
|
2048
|
-
folders = listDirs(dirPath);
|
|
2049
|
-
} catch {
|
|
2050
|
-
return [];
|
|
2051
|
-
}
|
|
2052
|
-
return folders.flatMap((folder) => {
|
|
2053
|
-
try {
|
|
2054
|
-
const manifest = readJson(join(dirPath, folder, "package.json"));
|
|
2055
|
-
if (!manifest.name) return [];
|
|
2056
|
-
return [{
|
|
2057
|
-
name: manifest.name,
|
|
2058
|
-
dir: join(dirPath, folder),
|
|
2059
|
-
version: manifest.version,
|
|
2060
|
-
dependencies: manifest.dependencies ?? {}
|
|
2061
|
-
}];
|
|
2062
|
-
} catch {
|
|
2063
|
-
return [];
|
|
2064
|
-
}
|
|
2065
|
-
});
|
|
2066
|
-
}
|
|
2067
|
-
/**
|
|
2068
|
-
* Every package under `<repoRoot>/packages/*` and `<repoRoot>/plugins/*`
|
|
2069
|
-
* with a readable, named `package.json` — `holocron`'s own layout splits
|
|
2070
|
-
* vendor plugin packages into `plugins/*`, sibling to `packages/*`.
|
|
2071
|
-
*/
|
|
2072
|
-
function workspacePackages(repoRoot, tree) {
|
|
2073
|
-
return [...scanWorkspaceDir(join(repoRoot, "packages"), tree), ...scanWorkspaceDir(join(repoRoot, "plugins"), tree)];
|
|
2074
|
-
}
|
|
2106
|
+
//#endregion
|
|
2107
|
+
//#region src/tasks/deploy-on-release/workspace-dependencies.ts
|
|
2075
2108
|
/** The `workspace:*` dependencies of `pkg`, as workspace packages (with their current version). */
|
|
2076
2109
|
function workspaceDependencies(pkg, all) {
|
|
2077
2110
|
const names = new Set(Object.entries(pkg.dependencies).filter(([, spec]) => String(spec).startsWith("workspace:")).map(([name]) => name));
|
|
2078
2111
|
return all.filter((candidate) => names.has(candidate.name));
|
|
2079
2112
|
}
|
|
2113
|
+
//#endregion
|
|
2114
|
+
//#region src/tasks/deploy-on-release/default-paths.ts
|
|
2080
2115
|
/**
|
|
2081
2116
|
* The paths a release must touch to redeploy `pkg` when its manifest doesn't
|
|
2082
2117
|
* list them: its own folder plus the folder of every
|
|
@@ -2088,6 +2123,8 @@ function workspaceDependencies(pkg, all) {
|
|
|
2088
2123
|
function defaultPaths(repoRoot, pkg, all) {
|
|
2089
2124
|
return [pkg, ...workspaceDependencies(pkg, all)].map(({ dir }) => `${relative(repoRoot, dir)}/`);
|
|
2090
2125
|
}
|
|
2126
|
+
//#endregion
|
|
2127
|
+
//#region src/tasks/deploy-on-release/deploy-targets.ts
|
|
2091
2128
|
/**
|
|
2092
2129
|
* The packages whose manifest declares a release-time `delivery.deploy`
|
|
2093
2130
|
* (`with.on === "release"`), with the task's options resolved.
|
|
@@ -2095,7 +2132,7 @@ function defaultPaths(repoRoot, pkg, all) {
|
|
|
2095
2132
|
function deployTargets(packages, manifests) {
|
|
2096
2133
|
return packages.flatMap((pkg) => (manifests.get(pkg.dir)?.tasks ?? []).flatMap((item) => {
|
|
2097
2134
|
const entry = normalizeTaskEntry(item);
|
|
2098
|
-
if (entry.name !==
|
|
2135
|
+
if (entry.name !== "delivery.deploy" || entry.with?.["on"] !== "release") return [];
|
|
2099
2136
|
const { channel, paths } = entry.with;
|
|
2100
2137
|
return [{
|
|
2101
2138
|
pkg,
|
|
@@ -2104,6 +2141,32 @@ function deployTargets(packages, manifests) {
|
|
|
2104
2141
|
}];
|
|
2105
2142
|
}));
|
|
2106
2143
|
}
|
|
2144
|
+
//#endregion
|
|
2145
|
+
//#region src/tasks/deploy-on-release/should-deploy.ts
|
|
2146
|
+
/**
|
|
2147
|
+
* @param channel The release's channel: its prerelease identifier (`alpha`), or `main` for a stable release.
|
|
2148
|
+
* @param changedFiles `undefined` when there's no previous release to diff against.
|
|
2149
|
+
*/
|
|
2150
|
+
function shouldDeploy({ channel, configuredChannel, changedFiles, paths }) {
|
|
2151
|
+
if (channel !== configuredChannel) return {
|
|
2152
|
+
deploy: false,
|
|
2153
|
+
reason: `channel "${channel}" isn't "${configuredChannel}"`
|
|
2154
|
+
};
|
|
2155
|
+
if (changedFiles === void 0) return {
|
|
2156
|
+
deploy: true,
|
|
2157
|
+
reason: "no previous release to diff against"
|
|
2158
|
+
};
|
|
2159
|
+
const touched = changedFiles.find((file) => paths.some((path) => file.startsWith(path)));
|
|
2160
|
+
return touched ? {
|
|
2161
|
+
deploy: true,
|
|
2162
|
+
reason: `the release changes ${touched}`
|
|
2163
|
+
} : {
|
|
2164
|
+
deploy: false,
|
|
2165
|
+
reason: `the release changes nothing under ${paths.join(", ")}`
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
//#endregion
|
|
2169
|
+
//#region src/tasks/deploy-on-release/wait-for-published.ts
|
|
2107
2170
|
/**
|
|
2108
2171
|
* Waits until npm serves every `name@version` in `packages`, asking with
|
|
2109
2172
|
* `npm view --prefer-online` (so npm's local cache can't answer for the
|
|
@@ -2129,35 +2192,70 @@ async function waitForPublished({ packages, run, sleep, now, cwd, timeoutMs = WA
|
|
|
2129
2192
|
await sleep(intervalMs);
|
|
2130
2193
|
}
|
|
2131
2194
|
}
|
|
2195
|
+
//#endregion
|
|
2196
|
+
//#region src/tasks/deploy-on-release/workspace-packages.ts
|
|
2197
|
+
function scanWorkspaceDir(dirPath, { readJson, listDirs }) {
|
|
2198
|
+
let folders;
|
|
2199
|
+
try {
|
|
2200
|
+
folders = listDirs(dirPath);
|
|
2201
|
+
} catch {
|
|
2202
|
+
return [];
|
|
2203
|
+
}
|
|
2204
|
+
return folders.flatMap((folder) => {
|
|
2205
|
+
try {
|
|
2206
|
+
const manifest = readJson(join(dirPath, folder, "package.json"));
|
|
2207
|
+
if (!manifest.name) return [];
|
|
2208
|
+
return [{
|
|
2209
|
+
name: manifest.name,
|
|
2210
|
+
dir: join(dirPath, folder),
|
|
2211
|
+
version: manifest.version,
|
|
2212
|
+
dependencies: manifest.dependencies ?? {}
|
|
2213
|
+
}];
|
|
2214
|
+
} catch {
|
|
2215
|
+
return [];
|
|
2216
|
+
}
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2132
2219
|
/**
|
|
2133
|
-
*
|
|
2134
|
-
*
|
|
2220
|
+
* Every package under `<repoRoot>/packages/*` and `<repoRoot>/plugins/*`
|
|
2221
|
+
* with a readable, named `package.json` — `holocron`'s own layout splits
|
|
2222
|
+
* vendor plugin packages into `plugins/*`, sibling to `packages/*`.
|
|
2135
2223
|
*/
|
|
2136
|
-
function
|
|
2137
|
-
|
|
2138
|
-
deploy: false,
|
|
2139
|
-
reason: `channel "${channel}" isn't "${configuredChannel}"`
|
|
2140
|
-
};
|
|
2141
|
-
if (changedFiles === void 0) return {
|
|
2142
|
-
deploy: true,
|
|
2143
|
-
reason: "no previous release to diff against"
|
|
2144
|
-
};
|
|
2145
|
-
const touched = changedFiles.find((file) => paths.some((path) => file.startsWith(path)));
|
|
2146
|
-
return touched ? {
|
|
2147
|
-
deploy: true,
|
|
2148
|
-
reason: `the release changes ${touched}`
|
|
2149
|
-
} : {
|
|
2150
|
-
deploy: false,
|
|
2151
|
-
reason: `the release changes nothing under ${paths.join(", ")}`
|
|
2152
|
-
};
|
|
2224
|
+
function workspacePackages(repoRoot, tree) {
|
|
2225
|
+
return [...scanWorkspaceDir(join(repoRoot, "packages"), tree), ...scanWorkspaceDir(join(repoRoot, "plugins"), tree)];
|
|
2153
2226
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2227
|
+
//#endregion
|
|
2228
|
+
//#region src/tasks/deploy-on-release/deploy-on-release.ts
|
|
2229
|
+
/**
|
|
2230
|
+
* Deploy workspace packages once a release has published (holocron#930).
|
|
2231
|
+
*
|
|
2232
|
+
* A package opts in with a `delivery.deploy` task in its own `holocron.config`
|
|
2233
|
+
* whose `with.on` is `"release"`:
|
|
2234
|
+
*
|
|
2235
|
+
* ```ts
|
|
2236
|
+
* // packages/<app>/holocron.config.ts
|
|
2237
|
+
* tasks: [{ name: "delivery.deploy", with: { on: "release", channel: "alpha" } }];
|
|
2238
|
+
* ```
|
|
2239
|
+
*
|
|
2240
|
+
* `channel` defaults to `main` (a stable release); set a prerelease identifier
|
|
2241
|
+
* such as `alpha` to deploy those releases instead.
|
|
2242
|
+
*
|
|
2243
|
+
* The shared `delivery.publish` workflow runs this (`holocron deploy on-release`)
|
|
2244
|
+
* as its own `deploy` job after the release job, so a deploy is a separate,
|
|
2245
|
+
* re-runnable check rather than a step buried in the release log. Each package
|
|
2246
|
+
* with such a task deploys when the release is on its `channel` and changes a file under its paths: the package itself plus its
|
|
2247
|
+
* `workspace:*` dependencies ({@link defaultPaths}), or an explicit
|
|
2248
|
+
* `with.paths`. It first waits for npm to serve the release's new versions of
|
|
2249
|
+
* those dependencies ({@link waitForPublished}) — the registry can lag a version
|
|
2250
|
+
* it has just accepted, and the deploy installs them — then runs
|
|
2251
|
+
* `pnpm --filter <package> delivery.deploy`.
|
|
2252
|
+
*
|
|
2253
|
+
* The release is already published by then, so a failed deploy can't undo it;
|
|
2254
|
+
* it fails this job instead, loudly and re-runnable on its own. A task is
|
|
2255
|
+
* `skip`ped (not failed) only when it doesn't apply to this release.
|
|
2256
|
+
*
|
|
2257
|
+
* A `delivery.deploy` task with `on: "release"` generates no CI workflow.
|
|
2258
|
+
*/
|
|
2161
2259
|
/**
|
|
2162
2260
|
* Deploys every package whose manifest declares a release-time deploy that
|
|
2163
2261
|
* applies to this release. Never throws for a deploy problem: each package's
|
|
@@ -2165,7 +2263,7 @@ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
2165
2263
|
*/
|
|
2166
2264
|
async function deployOnRelease(options, { run = defaultRun, readJson = defaultReadJson, listDirs = defaultListDirs, sleep = defaultSleep, now = Date.now, loadTasks = loadTasksConfig } = {}) {
|
|
2167
2265
|
const { cwd, from, print } = options;
|
|
2168
|
-
const channel = options.channel ||
|
|
2266
|
+
const channel = options.channel || "main";
|
|
2169
2267
|
const to = options.to ?? "HEAD";
|
|
2170
2268
|
const packages = workspacePackages(cwd, {
|
|
2171
2269
|
readJson,
|