@theholocron/cli 3.58.1 → 3.60.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/cli.mjs +213 -109
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -11,9 +11,9 @@ import { Entry, findCredentials } from "@napi-rs/keyring";
|
|
|
11
11
|
import { pathToFileURL } from "node:url";
|
|
12
12
|
import ora from "ora";
|
|
13
13
|
import chalk from "chalk";
|
|
14
|
+
import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/logger";
|
|
14
15
|
import { execFile, execFileSync, spawnSync } from "node:child_process";
|
|
15
16
|
import { homedir } from "node:os";
|
|
16
|
-
import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/logger";
|
|
17
17
|
import { access, copyFile, mkdir, readFile, readdir, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
18
18
|
import { createHash } from "node:crypto";
|
|
19
19
|
import { generateReadme } from "@theholocron/components-doc/markdown";
|
|
@@ -512,6 +512,94 @@ async function tryLoadHint(importer, packageName) {
|
|
|
512
512
|
}
|
|
513
513
|
}
|
|
514
514
|
//#endregion
|
|
515
|
+
//#region src/logger.ts
|
|
516
|
+
/**
|
|
517
|
+
* CLI-side wiring for `@theholocron/logger`.
|
|
518
|
+
*
|
|
519
|
+
* `logger` is the operational-output channel — internal state, debug
|
|
520
|
+
* traces, errors, structured context that routes to Axiom. It runs in
|
|
521
|
+
* parallel to `print` (user-facing UX output) and does not replace it.
|
|
522
|
+
*/
|
|
523
|
+
/**
|
|
524
|
+
* Resolve the explicit level to hand to `createLogger`, in priority order:
|
|
525
|
+
*
|
|
526
|
+
* 1. `--verbose` → `"debug"` 2. `--quiet` → `"error"`
|
|
527
|
+
* 3. `HOLOCRON_LOG_LEVEL` env var
|
|
528
|
+
* 4. `holocron.config` `log.level` (`configLevel`)
|
|
529
|
+
*
|
|
530
|
+
* Returns `undefined` when nothing applies — `createLogger` then defaults
|
|
531
|
+
* to `"info"`. Resolving the full chain here (rather than passing
|
|
532
|
+
* `configLevel` straight through) keeps config below the env var.
|
|
533
|
+
*/
|
|
534
|
+
function resolveLogLevel(argv, configLevel) {
|
|
535
|
+
if (argv.verbose) return "debug";
|
|
536
|
+
if (argv.quiet) return "error";
|
|
537
|
+
return parseLogLevel(env.get("HOLOCRON_LOG_LEVEL")) ?? configLevel;
|
|
538
|
+
}
|
|
539
|
+
let root;
|
|
540
|
+
let rootLevel;
|
|
541
|
+
let rootCommand;
|
|
542
|
+
let rootAxiomKey;
|
|
543
|
+
/**
|
|
544
|
+
* Resolve Axiom credentials for the CLI. Env vars win — same contract as
|
|
545
|
+
* `@theholocron/logger`'s `resolveAxiomFromEnv`. Failing that, the CLI-only
|
|
546
|
+
* bridge pairs the OS-keyring token (`axiom.<org>` then bare `axiom`) with a
|
|
547
|
+
* dataset from `HOLOCRON_AXIOM_DATASET` / `AXIOM_DATASET` or
|
|
548
|
+
* `holocron.config` `log.axiom.dataset`. Returns `undefined` unless both a
|
|
549
|
+
* token and a dataset are found.
|
|
550
|
+
*/
|
|
551
|
+
function resolveCliAxiom(opts) {
|
|
552
|
+
const fromEnv = resolveAxiomFromEnv();
|
|
553
|
+
if (fromEnv) return fromEnv;
|
|
554
|
+
const dataset = env.get("HOLOCRON_AXIOM_DATASET") || env.get("AXIOM_DATASET") || opts.configAxiomDataset;
|
|
555
|
+
if (!dataset) return void 0;
|
|
556
|
+
const org = opts.org ?? env.get("HOLOCRON_ORG");
|
|
557
|
+
const token = (org ? getToken(`axiom.${org}`) : null) ?? getToken("axiom");
|
|
558
|
+
return token ? {
|
|
559
|
+
dataset,
|
|
560
|
+
token
|
|
561
|
+
} : void 0;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* The process-wide root logger. Built once (from `cli.ts`'s middleware,
|
|
565
|
+
* with the command name + flags + env). Rebuilt at most once more when a
|
|
566
|
+
* command's handler supplies `holocron.config` context the flag/env-only
|
|
567
|
+
* first pass could not have known — `log.level` (unless `--verbose` /
|
|
568
|
+
* `--quiet` already fixed it) or `log.axiom.dataset` + the resolved org
|
|
569
|
+
* for a keyring-backed Axiom transport. That rebuild generates a fresh
|
|
570
|
+
* `runId`, which is harmless: nothing logs between the middleware and the
|
|
571
|
+
* handler.
|
|
572
|
+
*/
|
|
573
|
+
function buildCliLogger(argv, opts = {}) {
|
|
574
|
+
const { command, configLevel } = opts;
|
|
575
|
+
if (command) rootCommand = command;
|
|
576
|
+
const level = resolveLogLevel(argv, configLevel);
|
|
577
|
+
const axiom = resolveCliAxiom(opts);
|
|
578
|
+
const axiomKey = axiom?.dataset;
|
|
579
|
+
const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet;
|
|
580
|
+
if (!root || rebuildForConfig || axiomKey !== void 0 && axiomKey !== rootAxiomKey) {
|
|
581
|
+
const built = createLogger({
|
|
582
|
+
...level ? { level } : {},
|
|
583
|
+
...axiom ? { axiom } : {}
|
|
584
|
+
});
|
|
585
|
+
root = {
|
|
586
|
+
logger: rootCommand ? built.logger.child({ command: rootCommand }) : built.logger,
|
|
587
|
+
runId: built.runId
|
|
588
|
+
};
|
|
589
|
+
rootLevel = level;
|
|
590
|
+
rootAxiomKey = axiomKey;
|
|
591
|
+
}
|
|
592
|
+
return root;
|
|
593
|
+
}
|
|
594
|
+
/** Lazily-memoized `Logger` for module-level call sites with no `argv` in scope. */
|
|
595
|
+
function getLogger() {
|
|
596
|
+
return (root ??= createLogger()).logger;
|
|
597
|
+
}
|
|
598
|
+
/** The current root logger's correlation id, if a root has been built. */
|
|
599
|
+
function getRunId() {
|
|
600
|
+
return root?.runId;
|
|
601
|
+
}
|
|
602
|
+
//#endregion
|
|
515
603
|
//#region src/plugin/loader.ts
|
|
516
604
|
/**
|
|
517
605
|
* `PluginLoader` — loads provider plugins per the resolved config and
|
|
@@ -684,9 +772,14 @@ function prStateLabel(pr) {
|
|
|
684
772
|
}
|
|
685
773
|
async function runCleanupPreview(input) {
|
|
686
774
|
const print = input.print ?? ((line) => console.log(line));
|
|
775
|
+
const logger = input.logger ?? getLogger();
|
|
687
776
|
// c8 ignore next -- real PluginLoader construction is integration-level; unit tests always supply loader
|
|
688
777
|
const loader = input.loader ?? new PluginLoader(input.loaded.resolved, input.context);
|
|
689
778
|
await loader.load();
|
|
779
|
+
logger.info({
|
|
780
|
+
pr: input.prNumber,
|
|
781
|
+
project: input.project
|
|
782
|
+
}, "cleanup-preview: start");
|
|
690
783
|
if (!loader.has("source")) throw new Error("source capability is not configured — add a source provider to holocron.config.json");
|
|
691
784
|
const source = loader.get("source");
|
|
692
785
|
if (!source.getPullRequest) throw new Error(`${source.providerName} source provider does not support getPullRequest`);
|
|
@@ -716,6 +809,13 @@ async function runCleanupPreview(input) {
|
|
|
716
809
|
}
|
|
717
810
|
if (deployments.length === 0) {
|
|
718
811
|
print(style.dim(`No deployments found for branch ${branch}.`));
|
|
812
|
+
logger.info({
|
|
813
|
+
pr: pr.number,
|
|
814
|
+
branch,
|
|
815
|
+
found: 0,
|
|
816
|
+
deleted: 0,
|
|
817
|
+
status: "none"
|
|
818
|
+
}, "cleanup-preview: done");
|
|
719
819
|
return {
|
|
720
820
|
pr,
|
|
721
821
|
branch,
|
|
@@ -768,6 +868,13 @@ async function runCleanupPreview(input) {
|
|
|
768
868
|
try {
|
|
769
869
|
const count = await deploy.deletePreviewDeployments(input.project, selected);
|
|
770
870
|
print(style.success(`Deleted ${count} deployment${count === 1 ? "" : "s"}.`));
|
|
871
|
+
logger.info({
|
|
872
|
+
pr: pr.number,
|
|
873
|
+
branch,
|
|
874
|
+
found: deployments.length,
|
|
875
|
+
deleted: count,
|
|
876
|
+
status: "ok"
|
|
877
|
+
}, "cleanup-preview: done");
|
|
771
878
|
return {
|
|
772
879
|
pr,
|
|
773
880
|
branch,
|
|
@@ -778,6 +885,13 @@ async function runCleanupPreview(input) {
|
|
|
778
885
|
} catch (err) {
|
|
779
886
|
const message = err instanceof Error ? err.message : String(err);
|
|
780
887
|
print(style.fail(message));
|
|
888
|
+
logger.warn({
|
|
889
|
+
pr: pr.number,
|
|
890
|
+
branch,
|
|
891
|
+
found: deployments.length,
|
|
892
|
+
reason: message,
|
|
893
|
+
status: "fail"
|
|
894
|
+
}, "cleanup-preview: done");
|
|
781
895
|
return {
|
|
782
896
|
pr,
|
|
783
897
|
branch,
|
|
@@ -894,9 +1008,16 @@ async function runClone(input) {
|
|
|
894
1008
|
//#region src/commands/deploy.ts
|
|
895
1009
|
async function runDeploy(input) {
|
|
896
1010
|
const print = input.print ?? ((line) => console.log(line));
|
|
1011
|
+
const logger = input.logger ?? getLogger();
|
|
897
1012
|
const loader = input.loader ?? new PluginLoader(input.loaded.resolved, input.context);
|
|
898
1013
|
await loader.load();
|
|
899
1014
|
const dryRun = input.context.dryRun ?? false;
|
|
1015
|
+
logger.info({
|
|
1016
|
+
branch: input.branch,
|
|
1017
|
+
target: input.target ?? "preview",
|
|
1018
|
+
projectId: input.projectId,
|
|
1019
|
+
dryRun: dryRun || void 0
|
|
1020
|
+
}, "deploy: start");
|
|
900
1021
|
print(style.header(`Holocron deploy — branch=${input.branch}${input.target ? `, target=${input.target}` : " (preview)"}${dryRun ? " (dry-run)" : ""}`));
|
|
901
1022
|
if (!loader.has("deployment")) throw new Error("deployment capability is not configured — add a `deployment` provider to holocron.config.json");
|
|
902
1023
|
const deploy = loader.get("deployment");
|
|
@@ -916,6 +1037,11 @@ async function runDeploy(input) {
|
|
|
916
1037
|
...input.target ? { target: input.target } : {}
|
|
917
1038
|
}));
|
|
918
1039
|
print(` ${style.success(`${record.status} — ${record.url}`)}`);
|
|
1040
|
+
logger.info({
|
|
1041
|
+
status: record.status,
|
|
1042
|
+
url: record.url,
|
|
1043
|
+
id: record.id
|
|
1044
|
+
}, "deploy: triggered");
|
|
919
1045
|
return {
|
|
920
1046
|
deployment: record,
|
|
921
1047
|
status: "ok"
|
|
@@ -923,6 +1049,10 @@ async function runDeploy(input) {
|
|
|
923
1049
|
} catch (err) {
|
|
924
1050
|
const message = err instanceof Error ? err.message : String(err);
|
|
925
1051
|
print(` ${style.fail(message)}`);
|
|
1052
|
+
logger.warn({
|
|
1053
|
+
branch: input.branch,
|
|
1054
|
+
reason: message
|
|
1055
|
+
}, "deploy: failed");
|
|
926
1056
|
return {
|
|
927
1057
|
deployment: null,
|
|
928
1058
|
status: "fail",
|
|
@@ -931,94 +1061,6 @@ async function runDeploy(input) {
|
|
|
931
1061
|
}
|
|
932
1062
|
}
|
|
933
1063
|
//#endregion
|
|
934
|
-
//#region src/logger.ts
|
|
935
|
-
/**
|
|
936
|
-
* CLI-side wiring for `@theholocron/logger`.
|
|
937
|
-
*
|
|
938
|
-
* `logger` is the operational-output channel — internal state, debug
|
|
939
|
-
* traces, errors, structured context that routes to Axiom. It runs in
|
|
940
|
-
* parallel to `print` (user-facing UX output) and does not replace it.
|
|
941
|
-
*/
|
|
942
|
-
/**
|
|
943
|
-
* Resolve the explicit level to hand to `createLogger`, in priority order:
|
|
944
|
-
*
|
|
945
|
-
* 1. `--verbose` → `"debug"` 2. `--quiet` → `"error"`
|
|
946
|
-
* 3. `HOLOCRON_LOG_LEVEL` env var
|
|
947
|
-
* 4. `holocron.config` `log.level` (`configLevel`)
|
|
948
|
-
*
|
|
949
|
-
* Returns `undefined` when nothing applies — `createLogger` then defaults
|
|
950
|
-
* to `"info"`. Resolving the full chain here (rather than passing
|
|
951
|
-
* `configLevel` straight through) keeps config below the env var.
|
|
952
|
-
*/
|
|
953
|
-
function resolveLogLevel(argv, configLevel) {
|
|
954
|
-
if (argv.verbose) return "debug";
|
|
955
|
-
if (argv.quiet) return "error";
|
|
956
|
-
return parseLogLevel(env.get("HOLOCRON_LOG_LEVEL")) ?? configLevel;
|
|
957
|
-
}
|
|
958
|
-
let root;
|
|
959
|
-
let rootLevel;
|
|
960
|
-
let rootCommand;
|
|
961
|
-
let rootAxiomKey;
|
|
962
|
-
/**
|
|
963
|
-
* Resolve Axiom credentials for the CLI. Env vars win — same contract as
|
|
964
|
-
* `@theholocron/logger`'s `resolveAxiomFromEnv`. Failing that, the CLI-only
|
|
965
|
-
* bridge pairs the OS-keyring token (`axiom.<org>` then bare `axiom`) with a
|
|
966
|
-
* dataset from `HOLOCRON_AXIOM_DATASET` / `AXIOM_DATASET` or
|
|
967
|
-
* `holocron.config` `log.axiom.dataset`. Returns `undefined` unless both a
|
|
968
|
-
* token and a dataset are found.
|
|
969
|
-
*/
|
|
970
|
-
function resolveCliAxiom(opts) {
|
|
971
|
-
const fromEnv = resolveAxiomFromEnv();
|
|
972
|
-
if (fromEnv) return fromEnv;
|
|
973
|
-
const dataset = env.get("HOLOCRON_AXIOM_DATASET") || env.get("AXIOM_DATASET") || opts.configAxiomDataset;
|
|
974
|
-
if (!dataset) return void 0;
|
|
975
|
-
const org = opts.org ?? env.get("HOLOCRON_ORG");
|
|
976
|
-
const token = (org ? getToken(`axiom.${org}`) : null) ?? getToken("axiom");
|
|
977
|
-
return token ? {
|
|
978
|
-
dataset,
|
|
979
|
-
token
|
|
980
|
-
} : void 0;
|
|
981
|
-
}
|
|
982
|
-
/**
|
|
983
|
-
* The process-wide root logger. Built once (from `cli.ts`'s middleware,
|
|
984
|
-
* with the command name + flags + env). Rebuilt at most once more when a
|
|
985
|
-
* command's handler supplies `holocron.config` context the flag/env-only
|
|
986
|
-
* first pass could not have known — `log.level` (unless `--verbose` /
|
|
987
|
-
* `--quiet` already fixed it) or `log.axiom.dataset` + the resolved org
|
|
988
|
-
* for a keyring-backed Axiom transport. That rebuild generates a fresh
|
|
989
|
-
* `runId`, which is harmless: nothing logs between the middleware and the
|
|
990
|
-
* handler.
|
|
991
|
-
*/
|
|
992
|
-
function buildCliLogger(argv, opts = {}) {
|
|
993
|
-
const { command, configLevel } = opts;
|
|
994
|
-
if (command) rootCommand = command;
|
|
995
|
-
const level = resolveLogLevel(argv, configLevel);
|
|
996
|
-
const axiom = resolveCliAxiom(opts);
|
|
997
|
-
const axiomKey = axiom?.dataset;
|
|
998
|
-
const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet;
|
|
999
|
-
if (!root || rebuildForConfig || axiomKey !== void 0 && axiomKey !== rootAxiomKey) {
|
|
1000
|
-
const built = createLogger({
|
|
1001
|
-
...level ? { level } : {},
|
|
1002
|
-
...axiom ? { axiom } : {}
|
|
1003
|
-
});
|
|
1004
|
-
root = {
|
|
1005
|
-
logger: rootCommand ? built.logger.child({ command: rootCommand }) : built.logger,
|
|
1006
|
-
runId: built.runId
|
|
1007
|
-
};
|
|
1008
|
-
rootLevel = level;
|
|
1009
|
-
rootAxiomKey = axiomKey;
|
|
1010
|
-
}
|
|
1011
|
-
return root;
|
|
1012
|
-
}
|
|
1013
|
-
/** Lazily-memoized `Logger` for module-level call sites with no `argv` in scope. */
|
|
1014
|
-
function getLogger() {
|
|
1015
|
-
return (root ??= createLogger()).logger;
|
|
1016
|
-
}
|
|
1017
|
-
/** The current root logger's correlation id, if a root has been built. */
|
|
1018
|
-
function getRunId() {
|
|
1019
|
-
return root?.runId;
|
|
1020
|
-
}
|
|
1021
|
-
//#endregion
|
|
1022
1064
|
//#region src/commands/doctor.ts
|
|
1023
1065
|
async function runDoctor(input) {
|
|
1024
1066
|
const print = input.print ?? ((line) => console.log(line));
|
|
@@ -2871,8 +2913,13 @@ function describeScope(scope) {
|
|
|
2871
2913
|
//#region src/commands/secrets-sync.ts
|
|
2872
2914
|
async function runSecretsSync(input) {
|
|
2873
2915
|
const print = input.print ?? ((line) => console.log(line));
|
|
2916
|
+
const logger = input.logger ?? getLogger();
|
|
2874
2917
|
const loader = input.loader ?? new PluginLoader(input.loaded.resolved, input.context);
|
|
2875
2918
|
await withSpinner("Loading plugins…", () => loader.load());
|
|
2919
|
+
logger.info({
|
|
2920
|
+
environment: input.environmentId,
|
|
2921
|
+
dryRun: (input.context.dryRun ?? false) || void 0
|
|
2922
|
+
}, "secrets sync: start");
|
|
2876
2923
|
const dryRun = input.context.dryRun ?? false;
|
|
2877
2924
|
const targets = input.targets ?? ["production", "preview"];
|
|
2878
2925
|
print(style.header(`Holocron secrets sync — environment ${input.environmentId}${dryRun ? " (dry-run)" : ""}`));
|
|
@@ -2918,6 +2965,13 @@ async function runSecretsSync(input) {
|
|
|
2918
2965
|
}
|
|
2919
2966
|
}
|
|
2920
2967
|
}
|
|
2968
|
+
for (const r of rows) logger[r.status === "fail" ? "warn" : "info"]({
|
|
2969
|
+
destination: r.destination,
|
|
2970
|
+
scope: r.scope,
|
|
2971
|
+
key: r.key,
|
|
2972
|
+
status: r.status,
|
|
2973
|
+
...r.message ? { detail: r.message } : {}
|
|
2974
|
+
}, `secrets sync: ${r.key} → ${r.destination}`);
|
|
2921
2975
|
const summary = rows.reduce((acc, r) => {
|
|
2922
2976
|
if (r.status === "ok") acc.ok += 1;
|
|
2923
2977
|
else if (r.status === "fail") acc.fail += 1;
|
|
@@ -2930,6 +2984,10 @@ async function runSecretsSync(input) {
|
|
|
2930
2984
|
skip: 0,
|
|
2931
2985
|
dryRun: 0
|
|
2932
2986
|
});
|
|
2987
|
+
logger[summary.fail > 0 ? "warn" : "info"]({
|
|
2988
|
+
...summary,
|
|
2989
|
+
keys: rows.length
|
|
2990
|
+
}, "secrets sync: done");
|
|
2933
2991
|
print("");
|
|
2934
2992
|
const summaryLine = `${summary.ok} ok, ${summary.fail} fail, ${summary.skip} skipped${dryRun ? `, ${summary.dryRun} would-do` : ""}`;
|
|
2935
2993
|
print(summary.fail > 0 ? style.fail(summaryLine) : style.success(summaryLine));
|
|
@@ -5299,12 +5357,23 @@ const LOCAL_STEPS = /* @__PURE__ */ new Set([
|
|
|
5299
5357
|
]);
|
|
5300
5358
|
async function runSync(input) {
|
|
5301
5359
|
const print = input.print ?? ((line) => console.log(line));
|
|
5360
|
+
const logger = input.logger ?? getLogger();
|
|
5302
5361
|
const loader = input.loader ?? new PluginLoader(input.loaded.resolved, input.context);
|
|
5303
5362
|
const config = input.loaded.resolved;
|
|
5304
5363
|
const dryRun = input.context.dryRun ?? false;
|
|
5305
5364
|
const requestedSteps = input.steps;
|
|
5306
5365
|
const steps = [];
|
|
5366
|
+
logger.info({
|
|
5367
|
+
config: config.name,
|
|
5368
|
+
steps: requestedSteps ?? "all",
|
|
5369
|
+
dryRun: dryRun || void 0
|
|
5370
|
+
}, "sync: start");
|
|
5307
5371
|
await loader.load();
|
|
5372
|
+
for (const failure of loader.loadFailures()) logger.warn({
|
|
5373
|
+
capability: failure.key,
|
|
5374
|
+
provider: failure.provider,
|
|
5375
|
+
reason: failure.error.message
|
|
5376
|
+
}, `sync: plugin ${failure.provider} unavailable`);
|
|
5308
5377
|
print(`Holocron sync — ${config.name}${dryRun ? " (dry-run)" : ""}`);
|
|
5309
5378
|
print(` config: ${input.loaded.filepath}`);
|
|
5310
5379
|
print("");
|
|
@@ -5572,6 +5641,12 @@ async function runSync(input) {
|
|
|
5572
5641
|
print(formatSyncStep(result));
|
|
5573
5642
|
}
|
|
5574
5643
|
}
|
|
5644
|
+
for (const s of steps) logger[s.status === "fail" ? "warn" : "info"]({
|
|
5645
|
+
capability: s.capability,
|
|
5646
|
+
step: s.step,
|
|
5647
|
+
status: s.status,
|
|
5648
|
+
...s.message ? { detail: s.message } : {}
|
|
5649
|
+
}, `sync: ${s.step}`);
|
|
5575
5650
|
const summary = steps.reduce((acc, s) => {
|
|
5576
5651
|
if (s.status === "ok") acc.ok += 1;
|
|
5577
5652
|
else if (s.status === "fail") acc.fail += 1;
|
|
@@ -5584,6 +5659,7 @@ async function runSync(input) {
|
|
|
5584
5659
|
skip: 0,
|
|
5585
5660
|
dryRun: 0
|
|
5586
5661
|
});
|
|
5662
|
+
logger[summary.fail > 0 ? "warn" : "info"]({ ...summary }, "sync: done");
|
|
5587
5663
|
print("");
|
|
5588
5664
|
print(` ${summary.ok} ok, ${summary.fail} fail, ${summary.skip} skipped${dryRun ? `, ${summary.dryRun} would-do` : ""}`);
|
|
5589
5665
|
return {
|
|
@@ -5710,13 +5786,13 @@ var security_default = "name: Security\n\non: # yamllint disable-line rule:truth
|
|
|
5710
5786
|
var stale_default = "name: Stale\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n days-before-stale:\n description: Days of inactivity before an issue or PR is marked stale (applies to both unless type-specific value is set)\n type: number\n required: false\n default: 30\n days-before-close:\n description: Days after stale label before closing (applies to both unless type-specific value is set)\n type: number\n required: false\n default: 5\n days-before-issue-stale:\n description: Days of inactivity before an issue is marked stale; overrides days-before-stale when set to a non-negative value\n type: number\n required: false\n default: -1\n days-before-issue-close:\n description: Days after stale label before closing an issue; overrides days-before-close when set to a non-negative value\n type: number\n required: false\n default: -1\n days-before-pr-stale:\n description: Days of inactivity before a PR is marked stale; overrides days-before-stale when set to a non-negative value\n type: number\n required: false\n default: -1\n days-before-pr-close:\n description: Days after stale label before closing a PR; overrides days-before-close when set to a non-negative value\n type: number\n required: false\n default: -1\n exempt-issue-labels:\n description: Comma-separated labels that exempt an issue from being marked stale\n type: string\n required: false\n default: \"in-progress,wip\"\n exempt-pr-labels:\n description: Comma-separated labels that exempt a PR from being marked stale\n type: string\n required: false\n default: \"\"\n exempt-all-issue-milestones:\n description: Issues assigned to any milestone are never marked stale\n type: boolean\n required: false\n default: true\n exempt-all-pr-milestones:\n description: PRs assigned to any milestone are never marked stale\n type: boolean\n required: false\n default: true\n exempt-all-issue-projects:\n description: Issues assigned to any project are never marked stale\n type: boolean\n required: false\n default: true\n exempt-all-pr-projects:\n description: PRs assigned to any project are never marked stale\n type: boolean\n required: false\n default: true\n\njobs:\n stale:\n name: Mark stale issues and pull requests\n permissions:\n contents: write\n issues: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0\n name: Run Stale\n with:\n close-issue-message: >\n This issue was closed because it has been stalled for\n ${{ inputs.days-before-close }} days with no activity.\n days-before-close: ${{ inputs.days-before-close }}\n days-before-issue-close: ${{ inputs.days-before-issue-close }}\n days-before-issue-stale: ${{ inputs.days-before-issue-stale }}\n days-before-pr-close: ${{ inputs.days-before-pr-close }}\n days-before-pr-stale: ${{ inputs.days-before-pr-stale }}\n days-before-stale: ${{ inputs.days-before-stale }}\n exempt-all-issue-milestones: ${{ inputs.exempt-all-issue-milestones }}\n exempt-all-issue-projects: ${{ inputs.exempt-all-issue-projects }}\n exempt-all-pr-milestones: ${{ inputs.exempt-all-pr-milestones }}\n exempt-all-pr-projects: ${{ inputs.exempt-all-pr-projects }}\n exempt-issue-labels: ${{ inputs.exempt-issue-labels }}\n exempt-pr-labels: ${{ inputs.exempt-pr-labels }}\n stale-issue-label: wontfix\n stale-issue-message: >\n This issue is stale because it has been open ${{ inputs.days-before-stale }}\n days with no activity. Remove the stale label or comment, or this will be\n closed in ${{ inputs.days-before-close }} days.\n stale-pr-label: wontfix\n stale-pr-message: >\n This PR is stale because it has been open ${{ inputs.days-before-stale }}\n days with no activity. Remove the stale label or comment, or this will be\n closed in ${{ inputs.days-before-close }} days.\n";
|
|
5711
5787
|
//#endregion
|
|
5712
5788
|
//#region src/templates/workflows/sync.yml
|
|
5713
|
-
var sync_default = "name: Sync\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n steps:\n description: >\n Sync steps to run (default: all). Valid values:\n labels, properties, teams, topics, keywords, description, homepage, readme, workflows, wiki.\n Pass a space-separated list to run a subset, e.g. \"readme\" or \"readme wiki\".\n type: string\n required: false\n secrets:\n HOLOCRON_ADMIN_TOKEN:\n description: Fine-grained PAT with admin scopes (labels, properties, teams).\n required: false\n HOLOCRON_AXIOM_TOKEN:\n description: >\n Axiom API token. When set alongside the HOLOCRON_AXIOM_DATASET\n repo/org variable, the CLI ships this run's structured logs to Axiom.\n required: false\n HOLOCRON_DEPLOY_TOKEN:\n description: Fine-grained PAT for GitHub Pages configuration.\n required: false\n HOLOCRON_ISSUES_TOKEN:\n description: Fine-grained PAT for issue management.\n required: false\n HOLOCRON_ORG_TOKEN:\n description: Org-scoped fine-grained PAT for team sync and org properties.\n required: false\n HOLOCRON_READ_TOKEN:\n description: Fine-grained PAT for read-only GitHub API calls.\n required: false\n HOLOCRON_SYNC_TOKEN:\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for gh CLI calls. Used when\n HOLOCRON_SYNC_TOKEN is not set.\n required: false\n\njobs:\n sync:\n name: Sync repo from config\n runs-on: ubuntu-latest\n timeout-minutes: 10\n permissions:\n contents: write\n pull-requests: write\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n token: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm build\n name: Build CLI\n\n - name: Run holocron sync\n # Call the built entry directly — `pnpm exec holocron` relies on a\n # node_modules/.bin/holocron symlink that pnpm cannot create at install\n # time (dist/ does not exist yet), same as sync-github.yml.\n run: |\n if [ -n \"$STEPS\" ]; then\n # shellcheck disable=SC2086\n node packages/cli/dist/cli.mjs sync --steps $STEPS\n else\n node packages/cli/dist/cli.mjs sync\n fi\n env:\n HOLOCRON_ADMIN_TOKEN: ${{ secrets.HOLOCRON_ADMIN_TOKEN }}\n HOLOCRON_AXIOM_TOKEN: ${{ secrets.HOLOCRON_AXIOM_TOKEN }}\n HOLOCRON_AXIOM_DATASET: ${{ vars.HOLOCRON_AXIOM_DATASET }}\n HOLOCRON_DEPLOY_TOKEN: ${{ secrets.HOLOCRON_DEPLOY_TOKEN }}\n HOLOCRON_ISSUES_TOKEN: ${{ secrets.HOLOCRON_ISSUES_TOKEN }}\n HOLOCRON_ORG_TOKEN: ${{ secrets.HOLOCRON_ORG_TOKEN }}\n HOLOCRON_READ_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n STEPS: ${{ inputs.steps }}\n\n - name: Format generated files\n run: pnpm exec prettier --write README.md docs/src/content/docs/index.mdx 2>/dev/null || true\n\n - uses: theholocron/.github/.github/actions/auto-commit@main\n id: auto-commit\n name: Commit sync changes\n with:\n token: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n branch: chore/auto-sync\n commit-message: \"chore: sync from holocron.config\"\n commit-options: \"--no-verify\"\n\n - name: Open PR if changes were committed\n if: steps.auto-commit.outputs.changes-detected == 'true'\n run: |\n gh pr create \\\n --title \"chore: sync README and repo metadata\" \\\n --body \"Automated sync triggered by changes to config or package files. Merge to apply.\" \\\n --base main \\\n --head chore/auto-sync \\\n || echo \"PR already open — branch updated.\"\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - name: Broadcast wiki sync if navbar changed\n if: >-\n steps.auto-commit.outputs.changes-detected == 'true' &&\n github.event_name == 'push' &&\n (inputs.steps == '' || contains(inputs.steps, 'wiki'))\n run: |\n if git diff HEAD~1 --name-only | grep -q 'fern/docs.yml'; then\n gh workflow run sync-dispatch.yml \\\n --repo theholocron/.github \\\n --field \"steps=wiki\" \\\n || echo \"skipping broadcast — insufficient permissions\"\n fi\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n";
|
|
5789
|
+
var sync_default = "name: Sync\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n steps:\n description: >\n Sync steps to run (default: all). Valid values:\n labels, properties, teams, topics, keywords, description, homepage, readme, workflows, wiki.\n Pass a space-separated list to run a subset, e.g. \"readme\" or \"readme wiki\".\n type: string\n required: false\n secrets:\n HOLOCRON_ADMIN_TOKEN:\n description: Fine-grained PAT with admin scopes (labels, properties, teams).\n required: false\n HOLOCRON_AXIOM_TOKEN:\n description: >\n Axiom API token. When set alongside the HOLOCRON_AXIOM_DATASET\n repo/org variable, the CLI ships this run's structured logs to Axiom.\n Falls back to the vendor-native AXIOM_TOKEN secret.\n required: false\n AXIOM_TOKEN:\n description: Vendor-native fallback for HOLOCRON_AXIOM_TOKEN.\n required: false\n HOLOCRON_DEPLOY_TOKEN:\n description: Fine-grained PAT for GitHub Pages configuration.\n required: false\n HOLOCRON_ISSUES_TOKEN:\n description: Fine-grained PAT for issue management.\n required: false\n HOLOCRON_ORG_TOKEN:\n description: Org-scoped fine-grained PAT for team sync and org properties.\n required: false\n HOLOCRON_READ_TOKEN:\n description: Fine-grained PAT for read-only GitHub API calls.\n required: false\n HOLOCRON_SYNC_TOKEN:\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for gh CLI calls. Used when\n HOLOCRON_SYNC_TOKEN is not set.\n required: false\n\njobs:\n sync:\n name: Sync repo from config\n runs-on: ubuntu-latest\n timeout-minutes: 10\n permissions:\n contents: write\n pull-requests: write\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n token: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm build\n name: Build CLI\n\n - name: Run holocron sync\n # Call the built entry directly — `pnpm exec holocron` relies on a\n # node_modules/.bin/holocron symlink that pnpm cannot create at install\n # time (dist/ does not exist yet), same as sync-github.yml.\n run: |\n if [ -n \"$STEPS\" ]; then\n # shellcheck disable=SC2086\n node packages/cli/dist/cli.mjs sync --steps $STEPS\n else\n node packages/cli/dist/cli.mjs sync\n fi\n env:\n HOLOCRON_ADMIN_TOKEN: ${{ secrets.HOLOCRON_ADMIN_TOKEN }}\n HOLOCRON_AXIOM_TOKEN: ${{ secrets.HOLOCRON_AXIOM_TOKEN || secrets.AXIOM_TOKEN }}\n HOLOCRON_AXIOM_DATASET: ${{ vars.HOLOCRON_AXIOM_DATASET || vars.AXIOM_DATASET }}\n HOLOCRON_DEPLOY_TOKEN: ${{ secrets.HOLOCRON_DEPLOY_TOKEN }}\n HOLOCRON_ISSUES_TOKEN: ${{ secrets.HOLOCRON_ISSUES_TOKEN }}\n HOLOCRON_ORG_TOKEN: ${{ secrets.HOLOCRON_ORG_TOKEN }}\n HOLOCRON_READ_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n STEPS: ${{ inputs.steps }}\n\n - name: Format generated files\n run: |\n pnpm exec prettier --write README.md docs/src/content/docs/index.mdx 2>/dev/null || true\n # `holocron sync` writes package.json fields (keywords, description,\n # homepage) with a plain assignment, which appends new keys at the\n # end — re-apply the canonical order the lint config enforces.\n pnpm exec eslint --fix --no-warn-ignored package.json 2>/dev/null || true\n\n - uses: theholocron/.github/.github/actions/auto-commit@main\n id: auto-commit\n name: Commit sync changes\n with:\n token: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n branch: chore/auto-sync\n commit-message: \"chore: sync from holocron.config\"\n commit-options: \"--no-verify\"\n\n - name: Open PR if changes were committed\n if: steps.auto-commit.outputs.changes-detected == 'true'\n run: |\n gh pr create \\\n --title \"chore: sync README and repo metadata\" \\\n --body \"Automated sync triggered by changes to config or package files. Merge to apply.\" \\\n --base main \\\n --head chore/auto-sync \\\n || echo \"PR already open — branch updated.\"\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - name: Broadcast wiki sync if navbar changed\n if: >-\n steps.auto-commit.outputs.changes-detected == 'true' &&\n github.event_name == 'push' &&\n (inputs.steps == '' || contains(inputs.steps, 'wiki'))\n run: |\n if git diff HEAD~1 --name-only | grep -q 'fern/docs.yml'; then\n gh workflow run sync-dispatch.yml \\\n --repo theholocron/.github \\\n --field \"steps=wiki\" \\\n || echo \"skipping broadcast — insufficient permissions\"\n fi\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n";
|
|
5714
5790
|
//#endregion
|
|
5715
5791
|
//#region src/templates/workflows/sync-dispatch.yml
|
|
5716
5792
|
var sync_dispatch_default = "name: Sync Dispatch\n\non: # yamllint disable-line rule:truthy\n workflow_dispatch:\n inputs:\n steps:\n description: >\n Sync steps to pass to each repo's sync.yml. Default is \"readme\"\n (only README marker blocks are updated).\n type: string\n required: false\n default: readme\n\npermissions:\n contents: read\n\njobs:\n broadcast:\n name: Broadcast sync to all repos\n runs-on: ubuntu-latest\n timeout-minutes: 15\n steps:\n - name: Dispatch sync to all repos with sync.yml\n run: |\n gh api /orgs/theholocron/repos --paginate --jq '.[].name' \\\n | while IFS= read -r repo; do\n gh api \"/repos/theholocron/$repo/contents/.github/workflows/sync.yml\" --silent 2>/dev/null || continue\n echo \"Dispatching sync to theholocron/$repo\"\n gh workflow run sync.yml \\\n --repo \"theholocron/$repo\" \\\n --field \"steps=$STEPS\" \\\n || echo \"Warning: could not dispatch to theholocron/$repo — skipping\"\n done\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n STEPS: ${{ inputs.steps }}\n";
|
|
5717
5793
|
//#endregion
|
|
5718
5794
|
//#region src/templates/workflows/sync-github.yml
|
|
5719
|
-
var sync_github_default = "name: Sync workflow templates\n\n# Builds the holocron CLI from source and pushes updated workflow templates\n# and composite actions to downstream .github repos. Runs whenever the\n# template source files change on main or alpha.\n#\n# Secrets required:\n# HOLOCRON_SYNC_TOKEN — fine-grained PAT (resource owner: org) with:\n# Actions: Read and write (dispatch workflow runs via gh workflow run)\n# Contents: Read and write (git trees, blobs, refs)\n# Pull requests: Read and write (open sync PR)\n# Workflows: Read and write (write .github/workflows/*.yml)\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n primary-repo:\n description: >\n Primary .github repo — receives composite actions, reusable workflows,\n and thin-caller templates. Requires a PR (branch protection assumed).\n type: string\n required: false\n default: theholocron/.github\n secondary-repos:\n description: >\n Space-separated list of secondary repos (reusable workflows + thin\n callers only, no composite actions). Changes are delivered via pull\n request, same as the primary repo.\n type: string\n required: false\n default: \"\"\n sync-branch:\n description: Branch name used for the primary and secondary repo PRs\n type: string\n required: false\n default: chore/sync-templates\n secrets:\n HOLOCRON_SYNC_TOKEN:\n required: true\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to HOLOCRON_SYNC_TOKEN.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when neither\n HOLOCRON_READ_TOKEN nor HOLOCRON_SYNC_TOKEN is set.\n required: false\n HOLOCRON_AXIOM_TOKEN:\n description: >\n Axiom API token. When set alongside the HOLOCRON_AXIOM_DATASET\n repo/org variable, the CLI ships this run's structured logs to Axiom.\n required: false\n TURBO_TOKEN:\n required: false\n\njobs:\n sync:\n name: Sync templates\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n HOLOCRON_AXIOM_TOKEN: ${{ secrets.HOLOCRON_AXIOM_TOKEN }}\n HOLOCRON_AXIOM_DATASET: ${{ vars.HOLOCRON_AXIOM_DATASET }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm build\n name: Build CLI\n\n - name: Cache actionlint\n id: cache-actionlint\n uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0\n with:\n path: /tmp/actionlint\n key: actionlint-v1.7.7-linux-amd64\n\n - name: Download actionlint\n if: steps.cache-actionlint.outputs.cache-hit != 'true'\n run: |\n curl -fsSL https://github.com/rhysd/actionlint/releases/download/v1.7.7/actionlint_1.7.7_linux_amd64.tar.gz \\\n | tar -xz -C /tmp actionlint\n\n - name: Validate generated workflows\n run: |\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$PRIMARY_REPO\" \\\n --output-dir /tmp/sync-validate\n /tmp/actionlint /tmp/sync-validate/.github/workflows/*.yml\n env:\n PRIMARY_REPO: ${{ inputs.primary-repo }}\n\n - name: Sync primary repo (PR)\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n COMMIT_MSG=\"chore: sync from theholocron/holocron\"$'\\n\\n'\"Signed-off-by: $GIT_NAME <$GIT_EMAIL>\"\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$PRIMARY_REPO\" \\\n --branch \"$SYNC_BRANCH\" \\\n --pr \\\n --message \"$COMMIT_MSG\"\n GH_TOKEN=\"$HOLOCRON_SYNC_TOKEN\" gh pr merge --auto --squash \\\n --repo \"$PRIMARY_REPO\" \"$SYNC_BRANCH\" 2>/dev/null || true\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n PRIMARY_REPO: ${{ inputs.primary-repo }}\n SYNC_BRANCH: ${{ inputs.sync-branch }}\n\n - name: Sync secondary repos (PR)\n if: ${{ inputs.secondary-repos != '' }}\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n COMMIT_MSG=\"chore: sync from theholocron/holocron\"$'\\n\\n'\"Signed-off-by: $GIT_NAME <$GIT_EMAIL>\"\n for repo in $SECONDARY_REPOS; do\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$repo\" \\\n --branch \"$SYNC_BRANCH\" \\\n --pr \\\n --message \"$COMMIT_MSG\"\n GH_TOKEN=\"$HOLOCRON_SYNC_TOKEN\" gh pr merge --auto --squash \\\n --repo \"$repo\" \"$SYNC_BRANCH\" 2>/dev/null || true\n done\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n SECONDARY_REPOS: ${{ inputs.secondary-repos }}\n SYNC_BRANCH: ${{ inputs.sync-branch }}\n";
|
|
5795
|
+
var sync_github_default = "name: Sync workflow templates\n\n# Builds the holocron CLI from source and pushes updated workflow templates\n# and composite actions to downstream .github repos. Runs whenever the\n# template source files change on main or alpha.\n#\n# Secrets required:\n# HOLOCRON_SYNC_TOKEN — fine-grained PAT (resource owner: org) with:\n# Actions: Read and write (dispatch workflow runs via gh workflow run)\n# Contents: Read and write (git trees, blobs, refs)\n# Pull requests: Read and write (open sync PR)\n# Workflows: Read and write (write .github/workflows/*.yml)\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n primary-repo:\n description: >\n Primary .github repo — receives composite actions, reusable workflows,\n and thin-caller templates. Requires a PR (branch protection assumed).\n type: string\n required: false\n default: theholocron/.github\n secondary-repos:\n description: >\n Space-separated list of secondary repos (reusable workflows + thin\n callers only, no composite actions). Changes are delivered via pull\n request, same as the primary repo.\n type: string\n required: false\n default: \"\"\n sync-branch:\n description: Branch name used for the primary and secondary repo PRs\n type: string\n required: false\n default: chore/sync-templates\n secrets:\n HOLOCRON_SYNC_TOKEN:\n required: true\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to HOLOCRON_SYNC_TOKEN.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when neither\n HOLOCRON_READ_TOKEN nor HOLOCRON_SYNC_TOKEN is set.\n required: false\n HOLOCRON_AXIOM_TOKEN:\n description: >\n Axiom API token. When set alongside the HOLOCRON_AXIOM_DATASET\n repo/org variable, the CLI ships this run's structured logs to Axiom.\n Falls back to the vendor-native AXIOM_TOKEN secret.\n required: false\n AXIOM_TOKEN:\n description: Vendor-native fallback for HOLOCRON_AXIOM_TOKEN.\n required: false\n TURBO_TOKEN:\n required: false\n\njobs:\n sync:\n name: Sync templates\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n HOLOCRON_AXIOM_TOKEN: ${{ secrets.HOLOCRON_AXIOM_TOKEN || secrets.AXIOM_TOKEN }}\n HOLOCRON_AXIOM_DATASET: ${{ vars.HOLOCRON_AXIOM_DATASET || vars.AXIOM_DATASET }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm build\n name: Build CLI\n\n - name: Cache actionlint\n id: cache-actionlint\n uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0\n with:\n path: /tmp/actionlint\n key: actionlint-v1.7.7-linux-amd64\n\n - name: Download actionlint\n if: steps.cache-actionlint.outputs.cache-hit != 'true'\n run: |\n curl -fsSL https://github.com/rhysd/actionlint/releases/download/v1.7.7/actionlint_1.7.7_linux_amd64.tar.gz \\\n | tar -xz -C /tmp actionlint\n\n - name: Validate generated workflows\n run: |\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$PRIMARY_REPO\" \\\n --output-dir /tmp/sync-validate\n /tmp/actionlint /tmp/sync-validate/.github/workflows/*.yml\n env:\n PRIMARY_REPO: ${{ inputs.primary-repo }}\n\n - name: Sync primary repo (PR)\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n COMMIT_MSG=\"chore: sync from theholocron/holocron\"$'\\n\\n'\"Signed-off-by: $GIT_NAME <$GIT_EMAIL>\"\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$PRIMARY_REPO\" \\\n --branch \"$SYNC_BRANCH\" \\\n --pr \\\n --message \"$COMMIT_MSG\"\n GH_TOKEN=\"$HOLOCRON_SYNC_TOKEN\" gh pr merge --auto --squash \\\n --repo \"$PRIMARY_REPO\" \"$SYNC_BRANCH\" 2>/dev/null || true\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n PRIMARY_REPO: ${{ inputs.primary-repo }}\n SYNC_BRANCH: ${{ inputs.sync-branch }}\n\n - name: Sync secondary repos (PR)\n if: ${{ inputs.secondary-repos != '' }}\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n COMMIT_MSG=\"chore: sync from theholocron/holocron\"$'\\n\\n'\"Signed-off-by: $GIT_NAME <$GIT_EMAIL>\"\n for repo in $SECONDARY_REPOS; do\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$repo\" \\\n --branch \"$SYNC_BRANCH\" \\\n --pr \\\n --message \"$COMMIT_MSG\"\n GH_TOKEN=\"$HOLOCRON_SYNC_TOKEN\" gh pr merge --auto --squash \\\n --repo \"$repo\" \"$SYNC_BRANCH\" 2>/dev/null || true\n done\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n SECONDARY_REPOS: ${{ inputs.secondary-repos }}\n SYNC_BRANCH: ${{ inputs.sync-branch }}\n";
|
|
5720
5796
|
//#endregion
|
|
5721
5797
|
//#region src/templates/workflows/tag.yml
|
|
5722
5798
|
var tag_default = "name: Tag\n\n# Release Please — fully automated tag and GitHub Release from Conventional Commits.\n# No package.json or npm publishing required. Operates in \"simple\" mode by default:\n# analyzes commits since the last tag, maintains a rolling Release PR, and creates\n# a tag + GitHub Release when that PR is merged.\n#\n# The calling repo must have two files at the root:\n# release-please-config.json — declares packages and release-type\n# .release-please-manifest.json — tracks the current version\n\non: # yamllint disable-line rule:truthy\n # Self-trigger: when this workflow lives in theholocron/.github itself,\n # push to main runs Release Please for that repo's own releases.\n push:\n branches:\n - main\n workflow_call:\n inputs:\n release-type:\n description: Release Please release type (simple, node, python, etc.)\n type: string\n required: false\n default: simple\n config-file:\n description: Path to release-please-config.json\n type: string\n required: false\n default: release-please-config.json\n manifest-file:\n description: Path to .release-please-manifest.json\n type: string\n required: false\n default: .release-please-manifest.json\n\njobs:\n tag:\n name: Tag release\n permissions:\n contents: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: google-github-actions/release-please-action@e4dc86ba9405554aeba3c6bb2d169500e7d3b4ee # v4.1.1\n name: Run Release Please\n with:\n release-type: ${{ inputs.release-type }}\n config-file: ${{ inputs.config-file }}\n manifest-file: ${{ inputs.manifest-file }}\n";
|
|
@@ -5821,6 +5897,7 @@ function gitBlobSha(content) {
|
|
|
5821
5897
|
}
|
|
5822
5898
|
async function runSyncGithub(input) {
|
|
5823
5899
|
const print = input.print ?? ((line) => console.log(line));
|
|
5900
|
+
const logger = input.logger ?? getLogger();
|
|
5824
5901
|
const repo = input.repo ?? DEFAULT_REPO;
|
|
5825
5902
|
const { token, dryRun = false, branch, createPr = false } = input;
|
|
5826
5903
|
const message = input.message ?? `chore: sync from theholocron/holocron`;
|
|
@@ -5832,6 +5909,25 @@ async function runSyncGithub(input) {
|
|
|
5832
5909
|
print(` repo: ${repo}`);
|
|
5833
5910
|
if (branch) print(` branch: ${branch}`);
|
|
5834
5911
|
print("");
|
|
5912
|
+
logger.info({
|
|
5913
|
+
repo,
|
|
5914
|
+
branch,
|
|
5915
|
+
createPr: createPr || void 0,
|
|
5916
|
+
dryRun: dryRun || void 0
|
|
5917
|
+
}, "sync-github: start");
|
|
5918
|
+
/** Single exit point — one structured line per invocation, whatever the path. */
|
|
5919
|
+
const done = (r) => {
|
|
5920
|
+
logger[r.status === "fail" ? "warn" : "info"]({
|
|
5921
|
+
repo,
|
|
5922
|
+
status: r.status,
|
|
5923
|
+
created: r.created,
|
|
5924
|
+
updated: r.updated,
|
|
5925
|
+
unchanged: r.unchanged,
|
|
5926
|
+
...r.message ? { reason: r.message } : {},
|
|
5927
|
+
...r.prUrl ? { pr: r.prUrl } : {}
|
|
5928
|
+
}, "sync-github: done");
|
|
5929
|
+
return r;
|
|
5930
|
+
};
|
|
5835
5931
|
if (input.outputDir) {
|
|
5836
5932
|
const batch = buildBatch(repo);
|
|
5837
5933
|
for (const file of batch) {
|
|
@@ -5840,12 +5936,12 @@ async function runSyncGithub(input) {
|
|
|
5840
5936
|
writeFileSync(dest, file.content, "utf8");
|
|
5841
5937
|
}
|
|
5842
5938
|
print(` ${batch.length} files written to ${input.outputDir}`);
|
|
5843
|
-
return {
|
|
5939
|
+
return done({
|
|
5844
5940
|
status: "ok",
|
|
5845
5941
|
created: batch.length,
|
|
5846
5942
|
updated: 0,
|
|
5847
5943
|
unchanged: 0
|
|
5848
|
-
};
|
|
5944
|
+
});
|
|
5849
5945
|
}
|
|
5850
5946
|
let targetBranch = branch;
|
|
5851
5947
|
let defaultBranch;
|
|
@@ -5855,13 +5951,13 @@ async function runSyncGithub(input) {
|
|
|
5855
5951
|
} catch {
|
|
5856
5952
|
const msg = "failed to fetch repo metadata";
|
|
5857
5953
|
print(` ✗ ${msg}`);
|
|
5858
|
-
return {
|
|
5954
|
+
return done({
|
|
5859
5955
|
status: "fail",
|
|
5860
5956
|
created: 0,
|
|
5861
5957
|
updated: 0,
|
|
5862
5958
|
unchanged: 0,
|
|
5863
5959
|
message: msg
|
|
5864
|
-
};
|
|
5960
|
+
});
|
|
5865
5961
|
}
|
|
5866
5962
|
const baseBranch = createPr && defaultBranch ? defaultBranch : targetBranch;
|
|
5867
5963
|
let headSha;
|
|
@@ -5875,13 +5971,13 @@ async function runSyncGithub(input) {
|
|
|
5875
5971
|
} catch (err) {
|
|
5876
5972
|
const msg = err instanceof Error ? err.message : `Branch ${baseBranch} not found`;
|
|
5877
5973
|
print(` ✗ ${msg}`);
|
|
5878
|
-
return {
|
|
5974
|
+
return done({
|
|
5879
5975
|
status: "fail",
|
|
5880
5976
|
created: 0,
|
|
5881
5977
|
updated: 0,
|
|
5882
5978
|
unchanged: 0,
|
|
5883
5979
|
message: msg
|
|
5884
|
-
};
|
|
5980
|
+
});
|
|
5885
5981
|
}
|
|
5886
5982
|
const batch = buildBatch(repo);
|
|
5887
5983
|
let created = 0;
|
|
@@ -5896,22 +5992,30 @@ async function runSyncGithub(input) {
|
|
|
5896
5992
|
unchanged++;
|
|
5897
5993
|
} else if (existingSha) {
|
|
5898
5994
|
print(` ${dryRun ? "~" : "✓"} updated ${file.path}`);
|
|
5995
|
+
logger.debug({
|
|
5996
|
+
file: file.path,
|
|
5997
|
+
change: "updated"
|
|
5998
|
+
}, "sync-github: file");
|
|
5899
5999
|
updated++;
|
|
5900
6000
|
if (!dryRun) changedFiles.push(file);
|
|
5901
6001
|
} else {
|
|
5902
6002
|
print(` ${dryRun ? "~" : "✓"} created ${file.path}`);
|
|
6003
|
+
logger.debug({
|
|
6004
|
+
file: file.path,
|
|
6005
|
+
change: "created"
|
|
6006
|
+
}, "sync-github: file");
|
|
5903
6007
|
created++;
|
|
5904
6008
|
if (!dryRun) changedFiles.push(file);
|
|
5905
6009
|
}
|
|
5906
6010
|
}
|
|
5907
6011
|
print("");
|
|
5908
6012
|
print(` ${created} created, ${updated} updated, ${unchanged} unchanged`);
|
|
5909
|
-
if (dryRun || changedFiles.length === 0) return {
|
|
6013
|
+
if (dryRun || changedFiles.length === 0) return done({
|
|
5910
6014
|
status: dryRun ? "dry-run" : "ok",
|
|
5911
6015
|
created,
|
|
5912
6016
|
updated,
|
|
5913
6017
|
unchanged
|
|
5914
|
-
};
|
|
6018
|
+
});
|
|
5915
6019
|
const treeEntries = [];
|
|
5916
6020
|
for (const file of changedFiles) try {
|
|
5917
6021
|
const blob = await client.git.createBlob(repo, file.content);
|
|
@@ -5924,13 +6028,13 @@ async function runSyncGithub(input) {
|
|
|
5924
6028
|
} catch (err) {
|
|
5925
6029
|
const msg = `failed to create blob for ${file.path}: ${err instanceof Error ? err.message : String(err)}`;
|
|
5926
6030
|
print(` ✗ ${msg}`);
|
|
5927
|
-
return {
|
|
6031
|
+
return done({
|
|
5928
6032
|
status: "fail",
|
|
5929
6033
|
created,
|
|
5930
6034
|
updated,
|
|
5931
6035
|
unchanged,
|
|
5932
6036
|
message: msg
|
|
5933
|
-
};
|
|
6037
|
+
});
|
|
5934
6038
|
}
|
|
5935
6039
|
let newTreeSha;
|
|
5936
6040
|
try {
|
|
@@ -5938,13 +6042,13 @@ async function runSyncGithub(input) {
|
|
|
5938
6042
|
} catch (err) {
|
|
5939
6043
|
const msg = `failed to create tree: ${err instanceof Error ? err.message : String(err)}`;
|
|
5940
6044
|
print(` ✗ ${msg}`);
|
|
5941
|
-
return {
|
|
6045
|
+
return done({
|
|
5942
6046
|
status: "fail",
|
|
5943
6047
|
created,
|
|
5944
6048
|
updated,
|
|
5945
6049
|
unchanged,
|
|
5946
6050
|
message: msg
|
|
5947
|
-
};
|
|
6051
|
+
});
|
|
5948
6052
|
}
|
|
5949
6053
|
let newCommitSha;
|
|
5950
6054
|
try {
|
|
@@ -5952,13 +6056,13 @@ async function runSyncGithub(input) {
|
|
|
5952
6056
|
} catch (err) {
|
|
5953
6057
|
const msg = `failed to create commit: ${err instanceof Error ? err.message : String(err)}`;
|
|
5954
6058
|
print(` ✗ ${msg}`);
|
|
5955
|
-
return {
|
|
6059
|
+
return done({
|
|
5956
6060
|
status: "fail",
|
|
5957
6061
|
created,
|
|
5958
6062
|
updated,
|
|
5959
6063
|
unchanged,
|
|
5960
6064
|
message: msg
|
|
5961
|
-
};
|
|
6065
|
+
});
|
|
5962
6066
|
}
|
|
5963
6067
|
try {
|
|
5964
6068
|
if (createPr && branch) try {
|
|
@@ -5971,13 +6075,13 @@ async function runSyncGithub(input) {
|
|
|
5971
6075
|
} catch (err) {
|
|
5972
6076
|
const msg = `failed to update ref: ${err instanceof Error ? err.message : String(err)}`;
|
|
5973
6077
|
print(` ✗ ${msg}`);
|
|
5974
|
-
return {
|
|
6078
|
+
return done({
|
|
5975
6079
|
status: "fail",
|
|
5976
6080
|
created,
|
|
5977
6081
|
updated,
|
|
5978
6082
|
unchanged,
|
|
5979
6083
|
message: msg
|
|
5980
|
-
};
|
|
6084
|
+
});
|
|
5981
6085
|
}
|
|
5982
6086
|
let prUrl;
|
|
5983
6087
|
if (branch && createPr && !dryRun) try {
|
|
@@ -5992,13 +6096,13 @@ async function runSyncGithub(input) {
|
|
|
5992
6096
|
if (err instanceof ProviderApiError && err.status === 422 && String(err.details).includes("already exists")) print(` → PR already open for ${branch} — branch updated, ready to merge`);
|
|
5993
6097
|
else print(` ⚠ PR creation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5994
6098
|
}
|
|
5995
|
-
return {
|
|
6099
|
+
return done({
|
|
5996
6100
|
status: "ok",
|
|
5997
6101
|
created,
|
|
5998
6102
|
updated,
|
|
5999
6103
|
unchanged,
|
|
6000
6104
|
prUrl
|
|
6001
|
-
};
|
|
6105
|
+
});
|
|
6002
6106
|
}
|
|
6003
6107
|
//#endregion
|
|
6004
6108
|
//#region src/commands/upgrade-node.ts
|