@theholocron/cli 4.14.1 → 4.16.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/README.md +24 -8
- package/dist/cli.mjs +243 -129
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -275,6 +275,20 @@ export default acmeConfig;
|
|
|
275
275
|
|
|
276
276
|
```
|
|
277
277
|
|
|
278
|
+
## Upgrading
|
|
279
|
+
|
|
280
|
+
- **`holocron upgrade node <to>`** — patches every Node.js version pin
|
|
281
|
+
(`engines.node`, `.nvmrc`, `.node-version`, `.github/workflows/*`,
|
|
282
|
+
`Dockerfile`, `.tool-versions`) to a new major.
|
|
283
|
+
- **`holocron upgrade deps`** — bumps every `@theholocron/*` pin in
|
|
284
|
+
`pnpm-workspace.yaml` (`catalog:` and every named `catalogs.*`) to its latest
|
|
285
|
+
published version, then migrates `holocron.config.ts` from the 7.x preset
|
|
286
|
+
shape (`const { repo, workflows } = node()` + `repo.requiredChecks` +
|
|
287
|
+
`workflows:`) to the current composed-preset API (`const preset = node()` +
|
|
288
|
+
`...preset` + `tasks:` + `extraRequiredChecks:`). Mechanical transforms only —
|
|
289
|
+
review the result, then `pnpm install && holocron ci`. `--pins-only` skips
|
|
290
|
+
the config migration; `--dry-run` prints without writing.
|
|
291
|
+
|
|
278
292
|
## Auth — fine-grained tokens
|
|
279
293
|
|
|
280
294
|
Each GitHub capability resolves its own fine-grained PAT so a compromised
|
|
@@ -307,8 +321,8 @@ holocron auth list # show all stored providers
|
|
|
307
321
|
|
|
308
322
|
## Logging
|
|
309
323
|
|
|
310
|
-
Operational output goes through
|
|
311
|
-
the user-facing `print` surface. Global flags:
|
|
324
|
+
Operational output goes through `@theholocron/observability/logger` — separate
|
|
325
|
+
from the user-facing `print` surface. Global flags:
|
|
312
326
|
|
|
313
327
|
```sh
|
|
314
328
|
holocron doctor --verbose # log level → debug (full structured output)
|
|
@@ -356,9 +370,11 @@ export default defineConfig({
|
|
|
356
370
|
});
|
|
357
371
|
```
|
|
358
372
|
|
|
359
|
-
Each SDK sits behind
|
|
360
|
-
`AnalyticsSink` (PostHog)
|
|
361
|
-
`posthog-node`
|
|
373
|
+
Each SDK sits behind an interface from `@theholocron/observability` —
|
|
374
|
+
`ErrorSink` (Sentry) / `AnalyticsSink` (PostHog); the adapters, and the only
|
|
375
|
+
`@sentry/node` / `posthog-node` imports, live in that package. `telemetry.ts`
|
|
376
|
+
holds the orchestration + the Holocron-specific credential resolution
|
|
377
|
+
(`telemetry/resolve.ts`). See the
|
|
362
378
|
[telemetry guide](https://docs.theholocron.dev/holocron/telemetry/).
|
|
363
379
|
|
|
364
380
|
## What's in here
|
|
@@ -370,8 +386,8 @@ Each SDK sits behind a Holocron-owned interface — `ErrorSink` (Sentry) /
|
|
|
370
386
|
- `src/config/load-config.ts` — `loadConfig` — reads `holocron.config.*`
|
|
371
387
|
(file discovery via [`@theholocron/datapad`](../datapad))
|
|
372
388
|
- `src/define-config.ts` — `defineConfig` typed pass-through
|
|
373
|
-
- `src/logger.ts` — CLI-side `@theholocron/logger` wiring
|
|
374
|
-
`resolveLogLevel`)
|
|
389
|
+
- `src/logger.ts` — CLI-side `@theholocron/observability/logger` wiring
|
|
390
|
+
(`buildCliLogger`, `resolveLogLevel`)
|
|
375
391
|
- `src/loader.ts` — `PluginLoader` — dynamic-imports plugins, resolves
|
|
376
392
|
capability config packages, builds the capability registry
|
|
377
393
|
- `src/cli.ts` — yargs entry, dispatches subcommands. `holocron run` / `ci` are
|
|
@@ -383,7 +399,7 @@ Each SDK sits behind a Holocron-owned interface — `ErrorSink` (Sentry) /
|
|
|
383
399
|
never hand-edit its `.github/workflows/*`).
|
|
384
400
|
- `src/commands/` — `setup`, `sync`, `doctor`, `deploy`, `secret set`,
|
|
385
401
|
`secrets sync`, `npm publish-initial`, `sync-github`, `upgrade node`,
|
|
386
|
-
`plugin create`, `auth`
|
|
402
|
+
`upgrade deps`, `plugin create`, `auth`
|
|
387
403
|
|
|
388
404
|
## Status
|
|
389
405
|
|
package/dist/cli.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { AuthError, ProviderApiError, ProviderApiError as ProviderApiError$1, cr
|
|
|
11
11
|
import { createEnvLookup } from "@theholocron/env-utils";
|
|
12
12
|
import { Entry, findCredentials } from "@napi-rs/keyring";
|
|
13
13
|
import { pathToFileURL } from "node:url";
|
|
14
|
-
import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/logger";
|
|
14
|
+
import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/observability/logger";
|
|
15
15
|
import ora from "ora";
|
|
16
16
|
import chalk from "chalk";
|
|
17
17
|
import { execFile, execFileSync, spawnSync } from "node:child_process";
|
|
@@ -21,8 +21,9 @@ import { createHash } from "node:crypto";
|
|
|
21
21
|
import { generateReadme } from "@theholocron/components-doc/markdown";
|
|
22
22
|
import { getClients, getConfigs, getDocs, getPlugins, getSkills, getThemes, getUtils } from "@theholocron/registry-doc";
|
|
23
23
|
import { createGitHubClient } from "@theholocron/github-client";
|
|
24
|
-
import {
|
|
25
|
-
import
|
|
24
|
+
import { PostHogSink } from "@theholocron/observability/analytics";
|
|
25
|
+
import { NoopAnalyticsSink, NoopErrorSink, redactObject } from "@theholocron/observability/core";
|
|
26
|
+
import { SentrySink } from "@theholocron/observability/errors";
|
|
26
27
|
import { promisify } from "node:util";
|
|
27
28
|
import { ConfigFileError, loadConfigFile } from "@theholocron/datapad";
|
|
28
29
|
//#region src/env.ts
|
|
@@ -299,13 +300,6 @@ function resolveConfig(raw) {
|
|
|
299
300
|
//#endregion
|
|
300
301
|
//#region src/logger.ts
|
|
301
302
|
/**
|
|
302
|
-
* CLI-side wiring for `@theholocron/logger`.
|
|
303
|
-
*
|
|
304
|
-
* `logger` is the operational-output channel — internal state, debug
|
|
305
|
-
* traces, errors, structured context that routes to Axiom. It runs in
|
|
306
|
-
* parallel to `print` (user-facing UX output) and does not replace it.
|
|
307
|
-
*/
|
|
308
|
-
/**
|
|
309
303
|
* Resolve the explicit level to hand to `createLogger`, in priority order:
|
|
310
304
|
*
|
|
311
305
|
* 1. `--verbose` → `"debug"` 2. `--quiet` → `"error"`
|
|
@@ -327,7 +321,7 @@ let rootCommand;
|
|
|
327
321
|
let rootAxiomKey;
|
|
328
322
|
/**
|
|
329
323
|
* Resolve Axiom credentials for the CLI. Env vars win — same contract as
|
|
330
|
-
* `@theholocron/
|
|
324
|
+
* `@theholocron/observability`'s `resolveAxiomFromEnv`. Failing that, the CLI-only
|
|
331
325
|
* bridge pairs the OS-keyring token (`axiom.<org>` then bare `axiom`) with a
|
|
332
326
|
* dataset from `HOLOCRON_AXIOM_DATASET` / `AXIOM_DATASET` or
|
|
333
327
|
* `holocron.config` `log.axiom.dataset`. Returns `undefined` unless both a
|
|
@@ -5821,58 +5815,6 @@ async function writeWorkflowFile(repoRoot, filename, content) {
|
|
|
5821
5815
|
await writeFile(join(dir, filename), content, "utf8");
|
|
5822
5816
|
}
|
|
5823
5817
|
//#endregion
|
|
5824
|
-
//#region src/telemetry/posthog-sink.ts
|
|
5825
|
-
/**
|
|
5826
|
-
* `PostHogSink` — the **only** `posthog-node` call site. A portable
|
|
5827
|
-
* `AnalyticsSink` adapter: usage, adoption, retention. It reads no environment
|
|
5828
|
-
* and ships no credentials — the constructor takes a resolved `key` + `host`;
|
|
5829
|
-
* the caller (the CLI's `telemetry.ts`, via `telemetry/resolve.ts`) owns the
|
|
5830
|
-
* env chain and the fallback. An empty `key` is the caller's signal to install
|
|
5831
|
-
* a `NoopAnalyticsSink` instead — it is never passed here.
|
|
5832
|
-
*
|
|
5833
|
-
* Destined for `@theholocron/observability/analytics` (#635).
|
|
5834
|
-
*/
|
|
5835
|
-
var PostHogSink = class {
|
|
5836
|
-
#client;
|
|
5837
|
-
constructor(config) {
|
|
5838
|
-
this.#client = new PostHog(config.key, { host: config.host });
|
|
5839
|
-
}
|
|
5840
|
-
identify(distinctId, props) {
|
|
5841
|
-
this.#client.identify({
|
|
5842
|
-
distinctId,
|
|
5843
|
-
properties: props
|
|
5844
|
-
});
|
|
5845
|
-
}
|
|
5846
|
-
capture(distinctId, event, props) {
|
|
5847
|
-
this.#client.capture({
|
|
5848
|
-
distinctId,
|
|
5849
|
-
event,
|
|
5850
|
-
properties: props
|
|
5851
|
-
});
|
|
5852
|
-
}
|
|
5853
|
-
async shutdown() {
|
|
5854
|
-
await this.#client.shutdown();
|
|
5855
|
-
}
|
|
5856
|
-
};
|
|
5857
|
-
//#endregion
|
|
5858
|
-
//#region src/telemetry/redact.ts
|
|
5859
|
-
/**
|
|
5860
|
-
* Token-shape scrubbing for telemetry payloads — a raw token never leaves the
|
|
5861
|
-
* process, even when a value is controlled. Regex-on-the-serialised-string
|
|
5862
|
-
* (matches a token shape *anywhere*), distinct from `@theholocron/logger`'s
|
|
5863
|
-
* field-path redaction. Shared by {@link scrubError} (Sentry `beforeSend`) and
|
|
5864
|
-
* the PostHog `event()` path.
|
|
5865
|
-
*/
|
|
5866
|
-
const TOKEN_RE = /\b(ghp_|ghs_|glpat-|xoxb-|xoxp-|npm_|sk-|[A-Z][A-Z0-9_]{2,}_TOKEN[=\s])[^\s"]*/g;
|
|
5867
|
-
/** Replace token-shaped substrings in a string with `[REDACTED]`. */
|
|
5868
|
-
function redact(raw) {
|
|
5869
|
-
return raw.replace(TOKEN_RE, "[REDACTED]");
|
|
5870
|
-
}
|
|
5871
|
-
/** Deep-scrub every string in an object by round-tripping through {@link redact}. */
|
|
5872
|
-
function redactObject(value) {
|
|
5873
|
-
return JSON.parse(redact(JSON.stringify(value)));
|
|
5874
|
-
}
|
|
5875
|
-
//#endregion
|
|
5876
5818
|
//#region src/telemetry/resolve.ts
|
|
5877
5819
|
/**
|
|
5878
5820
|
* Holocron-specific credential resolution for the telemetry sinks — the env
|
|
@@ -5908,76 +5850,16 @@ function resolvePostHogHost() {
|
|
|
5908
5850
|
return env.get("HOLOCRON_POSTHOG_HOST") ?? env.get("POSTHOG_HOST") ?? DEFAULT_POSTHOG_HOST;
|
|
5909
5851
|
}
|
|
5910
5852
|
//#endregion
|
|
5911
|
-
//#region src/telemetry/sentry-sink.ts
|
|
5912
|
-
/** `beforeSend` — token-shaped strings never leave the process. */
|
|
5913
|
-
function scrubError(event, _hint) {
|
|
5914
|
-
return redactObject(event);
|
|
5915
|
-
}
|
|
5916
|
-
var SentrySink = class {
|
|
5917
|
-
init(ctx) {
|
|
5918
|
-
Sentry.init({
|
|
5919
|
-
dsn: ctx.dsn,
|
|
5920
|
-
release: ctx.release,
|
|
5921
|
-
environment: ctx.environment,
|
|
5922
|
-
tracesSampleRate: 1,
|
|
5923
|
-
beforeSend: scrubError
|
|
5924
|
-
});
|
|
5925
|
-
Sentry.startSession();
|
|
5926
|
-
for (const [key, value] of Object.entries(ctx.tags)) Sentry.setTag(key, value);
|
|
5927
|
-
}
|
|
5928
|
-
startSpan(name) {
|
|
5929
|
-
Sentry.setTag("command", name);
|
|
5930
|
-
const span = Sentry.startInactiveSpan({
|
|
5931
|
-
name,
|
|
5932
|
-
op: "holocron.command",
|
|
5933
|
-
forceTransaction: true
|
|
5934
|
-
});
|
|
5935
|
-
return {
|
|
5936
|
-
setStatus: (ok) => span.setStatus({ code: ok ? 1 : 2 }),
|
|
5937
|
-
end: () => span.end()
|
|
5938
|
-
};
|
|
5939
|
-
}
|
|
5940
|
-
captureException(err) {
|
|
5941
|
-
Sentry.captureException(err);
|
|
5942
|
-
}
|
|
5943
|
-
endSession() {
|
|
5944
|
-
Sentry.endSession();
|
|
5945
|
-
}
|
|
5946
|
-
async flush() {
|
|
5947
|
-
await Sentry.close(2e3);
|
|
5948
|
-
}
|
|
5949
|
-
};
|
|
5950
|
-
//#endregion
|
|
5951
|
-
//#region src/telemetry/sinks.ts
|
|
5952
|
-
/**
|
|
5953
|
-
* The disabled path made explicit — one shared no-op instead of a scatter of
|
|
5954
|
-
* `if (!enabled) return` guards. Used when telemetry is opted out
|
|
5955
|
-
* (`HOLOCRON_TELEMETRY=false`, `config.telemetry.enabled: false`) or no
|
|
5956
|
-
* credentials resolve.
|
|
5957
|
-
*/
|
|
5958
|
-
var NoopErrorSink = class {
|
|
5959
|
-
init() {}
|
|
5960
|
-
startSpan() {}
|
|
5961
|
-
captureException() {}
|
|
5962
|
-
endSession() {}
|
|
5963
|
-
async flush() {}
|
|
5964
|
-
};
|
|
5965
|
-
/** @see NoopErrorSink */
|
|
5966
|
-
var NoopAnalyticsSink = class {
|
|
5967
|
-
identify() {}
|
|
5968
|
-
capture() {}
|
|
5969
|
-
async shutdown() {}
|
|
5970
|
-
};
|
|
5971
|
-
//#endregion
|
|
5972
5853
|
//#region src/telemetry.ts
|
|
5973
5854
|
/**
|
|
5974
5855
|
* CLI self-telemetry orchestration — errors (Sentry), usage analytics (PostHog).
|
|
5975
5856
|
*
|
|
5976
|
-
* This module holds *no* vendor SDK import. Each concern sits behind
|
|
5977
|
-
*
|
|
5978
|
-
*
|
|
5979
|
-
* `posthog-node` call sites, and a `Noop*Sink` is
|
|
5980
|
-
* off. Same seam
|
|
5857
|
+
* This module holds *no* vendor SDK import. Each concern sits behind an
|
|
5858
|
+
* interface from `@theholocron/observability` ({@link ErrorSink} /
|
|
5859
|
+
* {@link AnalyticsSink}); the `SentrySink` / `PostHogSink` adapters there are
|
|
5860
|
+
* the only `@sentry/node` / `posthog-node` call sites, and a `Noop*Sink` is
|
|
5861
|
+
* installed when telemetry is off. Same seam the package's `Logger` uses for
|
|
5862
|
+
* Pino.
|
|
5981
5863
|
*
|
|
5982
5864
|
* Activation is env-var + shipped-fallback, resolved here (`telemetry/resolve.ts`)
|
|
5983
5865
|
* and passed to the sink — the adapters read no environment. The single kill
|
|
@@ -6352,6 +6234,224 @@ async function runSyncGithub(input) {
|
|
|
6352
6234
|
});
|
|
6353
6235
|
}
|
|
6354
6236
|
//#endregion
|
|
6237
|
+
//#region src/commands/upgrade-deps.ts
|
|
6238
|
+
/**
|
|
6239
|
+
* `holocron upgrade deps` — bring a repo's `@theholocron/*` dependency pins and
|
|
6240
|
+
* its `holocron.config.ts` up to the current major.
|
|
6241
|
+
*
|
|
6242
|
+
* Two concerns:
|
|
6243
|
+
*
|
|
6244
|
+
* 1. **Catalog pins.** Every `@theholocron/*` entry in `pnpm-workspace.yaml`
|
|
6245
|
+
* (`catalog:` and every named `catalogs.*`) is bumped to the latest published
|
|
6246
|
+
* version, preserving the range prefix (`^` / `~`). Never downgrades.
|
|
6247
|
+
* 2. **`holocron.config.ts` migration.** `@theholocron/holocron-config@8`
|
|
6248
|
+
* dropped the 7.x preset shape (`const { repo, workflows, providers } =
|
|
6249
|
+
* node()` + `repo.requiredChecks` + `workflows:`) for a composed-preset
|
|
6250
|
+
* (`const preset = node()` + `...preset` + `tasks:` + `extraRequiredChecks:`).
|
|
6251
|
+
* This applies the mechanical transforms and flags anything that needs a
|
|
6252
|
+
* human eye.
|
|
6253
|
+
*
|
|
6254
|
+
* After running, `pnpm install && holocron ci`.
|
|
6255
|
+
*/
|
|
6256
|
+
const FETCH_TIMEOUT_MS$1 = 4e3;
|
|
6257
|
+
/** Resolve a package's `latest` dist-tag from the npm registry. */
|
|
6258
|
+
const fetchLatestFromNpm = async (pkg) => {
|
|
6259
|
+
try {
|
|
6260
|
+
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(pkg)}`, {
|
|
6261
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS$1),
|
|
6262
|
+
headers: { accept: "application/vnd.npm.install-v1+json" }
|
|
6263
|
+
});
|
|
6264
|
+
if (!res.ok) return null;
|
|
6265
|
+
return (await res.json())["dist-tags"]?.["latest"] ?? null;
|
|
6266
|
+
} catch {
|
|
6267
|
+
return null;
|
|
6268
|
+
}
|
|
6269
|
+
};
|
|
6270
|
+
/** `a > b` for `x.y.z` release versions (prerelease tags are ignored). */
|
|
6271
|
+
function gt(a, b) {
|
|
6272
|
+
const pa = a.split(".").map((n) => parseInt(n, 10));
|
|
6273
|
+
const pb = b.split(".").map((n) => parseInt(n, 10));
|
|
6274
|
+
for (let i = 0; i < 3; i++) {
|
|
6275
|
+
const x = pa[i] ?? 0;
|
|
6276
|
+
const y = pb[i] ?? 0;
|
|
6277
|
+
if (x !== y) return x > y;
|
|
6278
|
+
}
|
|
6279
|
+
return false;
|
|
6280
|
+
}
|
|
6281
|
+
const CATALOG_LINE = /^(\s*['"]?)(@theholocron\/[a-z0-9-]+)(['"]?\s*:\s*['"]?)([~^]?)(\d+\.\d+\.\d+[\w.-]*)(['"]?\s*)$/;
|
|
6282
|
+
/**
|
|
6283
|
+
* Rewrite every `@theholocron/*` pin in a `pnpm-workspace.yaml` to `latest`,
|
|
6284
|
+
* keeping the range prefix. `latest` is looked up once per package.
|
|
6285
|
+
*/
|
|
6286
|
+
async function bumpCatalogs(content, fetchLatest) {
|
|
6287
|
+
const lines = content.split("\n");
|
|
6288
|
+
const wanted = /* @__PURE__ */ new Set();
|
|
6289
|
+
for (const line of lines) {
|
|
6290
|
+
const m = CATALOG_LINE.exec(line);
|
|
6291
|
+
if (m) wanted.add(m[2]);
|
|
6292
|
+
}
|
|
6293
|
+
const latest = /* @__PURE__ */ new Map();
|
|
6294
|
+
await Promise.all([...wanted].map(async (pkg) => {
|
|
6295
|
+
latest.set(pkg, await fetchLatest(pkg));
|
|
6296
|
+
}));
|
|
6297
|
+
const bumps = [];
|
|
6298
|
+
return {
|
|
6299
|
+
content: lines.map((line) => {
|
|
6300
|
+
const m = CATALOG_LINE.exec(line);
|
|
6301
|
+
if (!m) return line;
|
|
6302
|
+
const [, pre, pkg, mid, prefix, current, post] = m;
|
|
6303
|
+
const next = latest.get(pkg);
|
|
6304
|
+
if (!next || next === current || !gt(next, current)) return line;
|
|
6305
|
+
bumps.push({
|
|
6306
|
+
pkg,
|
|
6307
|
+
from: current,
|
|
6308
|
+
to: next
|
|
6309
|
+
});
|
|
6310
|
+
return `${pre}${pkg}${mid}${prefix}${next}${post}`;
|
|
6311
|
+
}).join("\n"),
|
|
6312
|
+
bumps
|
|
6313
|
+
};
|
|
6314
|
+
}
|
|
6315
|
+
/** True if the file still uses the 7.x preset shape. */
|
|
6316
|
+
function isLegacyConfig(content) {
|
|
6317
|
+
return /const\s*\{[^}]*\b(repo|workflows|providers)\b[^}]*\}\s*=\s*(compose\(|[a-zA-Z]+\(\))/.test(content) || /\.\.\.repo\.requiredChecks/.test(content) || /^\s*workflows\s*:/m.test(content);
|
|
6318
|
+
}
|
|
6319
|
+
/**
|
|
6320
|
+
* Apply the mechanical 7.x → 8.x transforms to a `holocron.config.ts`. Best
|
|
6321
|
+
* effort — the result should always be reviewed and run through `holocron ci`.
|
|
6322
|
+
*/
|
|
6323
|
+
function migrateConfig(content) {
|
|
6324
|
+
const transforms = [];
|
|
6325
|
+
const warnings = [];
|
|
6326
|
+
let s = content;
|
|
6327
|
+
if (!isLegacyConfig(s)) return {
|
|
6328
|
+
content,
|
|
6329
|
+
changed: false,
|
|
6330
|
+
transforms,
|
|
6331
|
+
warnings
|
|
6332
|
+
};
|
|
6333
|
+
s = s.replace(/const\s*\{[^}]*\}\s*=\s*((?:compose\([\s\S]*?\)|[a-zA-Z]+\([\s\S]*?\)))\s*;/, (_m, expr) => {
|
|
6334
|
+
transforms.push("destructured preset → `const preset = …`");
|
|
6335
|
+
return `const preset = ${expr.trim()};`;
|
|
6336
|
+
});
|
|
6337
|
+
if (/const preset =/.test(s) && !/\.\.\.preset\b/.test(s)) {
|
|
6338
|
+
s = s.replace(/(defineConfig\(\{\s*\n)/, `$1\t...preset,\n`);
|
|
6339
|
+
transforms.push("spread `...preset` into defineConfig()");
|
|
6340
|
+
}
|
|
6341
|
+
s = s.replace(/^\torg,\n/m, "");
|
|
6342
|
+
s = s.replace(/^\tdomain,\n/m, "");
|
|
6343
|
+
s = s.replace(/^\tdocs,\n/m, "");
|
|
6344
|
+
if (/\.\.\.repo\.properties/.test(s)) {
|
|
6345
|
+
s = s.replace(/\.\.\.repo\.properties/g, "...preset.repo?.properties");
|
|
6346
|
+
transforms.push("`...repo.properties` → `...preset.repo?.properties`");
|
|
6347
|
+
}
|
|
6348
|
+
if (/([{[,]\s*)\.\.\.repo\b(?!\.)/.test(s)) {
|
|
6349
|
+
s = s.replace(/([{[,]\s*)\.\.\.repo\b(?!\.)/g, "$1...preset.repo");
|
|
6350
|
+
transforms.push("`...repo` → `...preset.repo`");
|
|
6351
|
+
}
|
|
6352
|
+
if (/\.\.\.providers\b/.test(s)) {
|
|
6353
|
+
s = s.replace(/\.\.\.providers\b/g, "...preset.providers");
|
|
6354
|
+
transforms.push("`...providers` → `...preset.providers`");
|
|
6355
|
+
}
|
|
6356
|
+
const rc = /(\n\s*)requiredChecks\s*:\s*\[\s*\n?\s*\.\.\.repo\.requiredChecks\s*,?\s*([\s\S]*?)\n\s*\]\s*,/.exec(s);
|
|
6357
|
+
if (rc) {
|
|
6358
|
+
const custom = rc[2].trim().replace(/,\s*$/, "");
|
|
6359
|
+
s = s.replace(rc[0], "");
|
|
6360
|
+
if (custom) {
|
|
6361
|
+
const block = `\textraRequiredChecks: [\n\t\t${custom.split("\n").map((l) => l.trim()).filter(Boolean).join("\n ")}\n\t],\n`;
|
|
6362
|
+
s = s.replace(/(defineConfig\(\{\s*\n(?:\t\.\.\.preset,\n)?)/, `$1${block}`);
|
|
6363
|
+
transforms.push("`repo.requiredChecks` custom entries → top-level `extraRequiredChecks`");
|
|
6364
|
+
warnings.push("check the moved `extraRequiredChecks` — the preset already contributes lint/test/typecheck");
|
|
6365
|
+
} else {
|
|
6366
|
+
s = s.replace(/,(\s*)\.\.\.repo\.requiredChecks/g, "$1");
|
|
6367
|
+
transforms.push("dropped `...repo.requiredChecks`");
|
|
6368
|
+
}
|
|
6369
|
+
}
|
|
6370
|
+
if (/^\s*workflows\s*:/m.test(s)) {
|
|
6371
|
+
s = s.replace(/(\n\s*)workflows(\s*:\s*\[)/, "$1tasks$2");
|
|
6372
|
+
s = s.replace(/\.\.\.workflows\b/g, "...preset.tasks");
|
|
6373
|
+
transforms.push("`workflows:` → `tasks:` (`...workflows` → `...preset.tasks`)");
|
|
6374
|
+
warnings.push("review the `tasks` array — a bare workflow name that should gate merges now needs `{ name, required: true }`");
|
|
6375
|
+
}
|
|
6376
|
+
return {
|
|
6377
|
+
content: s,
|
|
6378
|
+
changed: s !== content,
|
|
6379
|
+
transforms,
|
|
6380
|
+
warnings
|
|
6381
|
+
};
|
|
6382
|
+
}
|
|
6383
|
+
async function runUpgradeDeps(input = {}) {
|
|
6384
|
+
const print = input.print ?? ((line) => console.log(line));
|
|
6385
|
+
const logger = input.logger ?? getLogger();
|
|
6386
|
+
const cwd = input.cwd ?? process.cwd();
|
|
6387
|
+
const dryRun = input.dryRun ?? false;
|
|
6388
|
+
const pinsOnly = input.pinsOnly ?? false;
|
|
6389
|
+
const _readFile = input.readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
6390
|
+
const _writeFile = input.writeFile ?? ((p, c) => writeFileSync(p, c));
|
|
6391
|
+
const fetchLatest = input.fetchLatest ?? fetchLatestFromNpm;
|
|
6392
|
+
logger.info({
|
|
6393
|
+
dryRun: dryRun || void 0,
|
|
6394
|
+
pinsOnly: pinsOnly || void 0
|
|
6395
|
+
}, "upgrade deps: start");
|
|
6396
|
+
print(`Upgrading @theholocron/* dependencies${dryRun ? " (dry-run)" : ""}…`);
|
|
6397
|
+
const wsPath = join(cwd, "pnpm-workspace.yaml");
|
|
6398
|
+
let bumps = [];
|
|
6399
|
+
try {
|
|
6400
|
+
const result = await bumpCatalogs(_readFile(wsPath), fetchLatest);
|
|
6401
|
+
bumps = result.bumps;
|
|
6402
|
+
if (bumps.length > 0) {
|
|
6403
|
+
if (!dryRun) _writeFile(wsPath, result.content);
|
|
6404
|
+
print("");
|
|
6405
|
+
print(` pnpm-workspace.yaml — ${bumps.length} pin${bumps.length === 1 ? "" : "s"}`);
|
|
6406
|
+
for (const b of bumps) print(` ${dryRun ? "~" : "✓"} ${b.pkg} ${b.from} → ${b.to}`);
|
|
6407
|
+
} else print(" · pnpm-workspace.yaml — @theholocron/* pins already current");
|
|
6408
|
+
} catch {
|
|
6409
|
+
print(" · no pnpm-workspace.yaml — skipping catalog pins");
|
|
6410
|
+
}
|
|
6411
|
+
let configMigrated = false;
|
|
6412
|
+
let configWarnings = [];
|
|
6413
|
+
if (!pinsOnly) for (const name of ["holocron.config.ts", "holocron.config.js"]) {
|
|
6414
|
+
let cfg;
|
|
6415
|
+
try {
|
|
6416
|
+
cfg = _readFile(join(cwd, name));
|
|
6417
|
+
} catch {
|
|
6418
|
+
continue;
|
|
6419
|
+
}
|
|
6420
|
+
const m = migrateConfig(cfg);
|
|
6421
|
+
if (!m.changed) {
|
|
6422
|
+
print(` · ${name} — already on the current preset API`);
|
|
6423
|
+
break;
|
|
6424
|
+
}
|
|
6425
|
+
if (!dryRun) _writeFile(join(cwd, name), m.content);
|
|
6426
|
+
configMigrated = true;
|
|
6427
|
+
configWarnings = m.warnings;
|
|
6428
|
+
print("");
|
|
6429
|
+
print(` ${name} — ${dryRun ? "would migrate" : "migrated"} to the 8.x preset API`);
|
|
6430
|
+
for (const t of m.transforms) print(` ${dryRun ? "~" : "✓"} ${t}`);
|
|
6431
|
+
for (const w of m.warnings) print(` ⚠ ${w}`);
|
|
6432
|
+
break;
|
|
6433
|
+
}
|
|
6434
|
+
print("");
|
|
6435
|
+
if (bumps.length === 0 && !configMigrated) print("Nothing to do — already up to date.");
|
|
6436
|
+
else {
|
|
6437
|
+
print(`Next: ${dryRun ? "run without --dry-run, then " : ""}\`pnpm install && holocron ci\``);
|
|
6438
|
+
if (configMigrated) print(" review holocron.config.ts before committing.");
|
|
6439
|
+
}
|
|
6440
|
+
const status = dryRun ? "dry-run" : "ok";
|
|
6441
|
+
logger.info({
|
|
6442
|
+
bumps: bumps.length,
|
|
6443
|
+
configMigrated,
|
|
6444
|
+
warnings: configWarnings.length,
|
|
6445
|
+
status
|
|
6446
|
+
}, "upgrade deps: done");
|
|
6447
|
+
return {
|
|
6448
|
+
status,
|
|
6449
|
+
bumps,
|
|
6450
|
+
configMigrated,
|
|
6451
|
+
configWarnings
|
|
6452
|
+
};
|
|
6453
|
+
}
|
|
6454
|
+
//#endregion
|
|
6355
6455
|
//#region src/commands/upgrade-node.ts
|
|
6356
6456
|
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
6357
6457
|
"node_modules",
|
|
@@ -7551,6 +7651,20 @@ try {
|
|
|
7551
7651
|
if (report.message) getLogger().error(`upgrade node: ${report.message}`);
|
|
7552
7652
|
process.exitCode = 1;
|
|
7553
7653
|
}
|
|
7654
|
+
}).command("deps", "Bump every @theholocron/* pin to latest and migrate holocron.config.ts to the current preset API", (yy) => yy.option("pins-only", {
|
|
7655
|
+
type: "boolean",
|
|
7656
|
+
default: false,
|
|
7657
|
+
describe: "Only bump the catalog pins — skip the holocron.config.ts migration"
|
|
7658
|
+
}), async (argv) => {
|
|
7659
|
+
const report = await runUpgradeDeps({
|
|
7660
|
+
cwd: argv.cwd,
|
|
7661
|
+
dryRun: argv.dryRun,
|
|
7662
|
+
pinsOnly: argv.pinsOnly
|
|
7663
|
+
});
|
|
7664
|
+
if (report.status === "fail") {
|
|
7665
|
+
if (report.message) getLogger().error(`upgrade deps: ${report.message}`);
|
|
7666
|
+
process.exitCode = 1;
|
|
7667
|
+
}
|
|
7554
7668
|
}).demandCommand(1, "Run `holocron upgrade --help` to see available upgrade subcommands."), () => {}).command("auth <subcommand>", "Manage bootstrap credentials in the OS keyring", (y) => y.command("set <provider> [value]", "Verify + store a bootstrap token for a provider", (yy) => yy.positional("provider", {
|
|
7555
7669
|
type: "string",
|
|
7556
7670
|
demandOption: true
|