@theholocron/cli 3.62.1 → 3.64.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 +10 -2
- package/dist/cli.mjs +203 -84
- package/dist/cli.mjs.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -192,8 +192,16 @@ Axiom shipping activates when a token (`HOLOCRON_AXIOM_TOKEN` / `AXIOM_TOKEN`
|
|
|
192
192
|
→ OS keyring `axiom.<org>` → `axiom`) **and** a dataset
|
|
193
193
|
(`HOLOCRON_AXIOM_DATASET` / `AXIOM_DATASET` → `log.axiom.dataset` in
|
|
194
194
|
`holocron.config`) both resolve; env vars win. The token is never read from a
|
|
195
|
-
config file.
|
|
196
|
-
|
|
195
|
+
config file. See the [logging guide](https://docs.theholocron.dev/holocron/logging/).
|
|
196
|
+
|
|
197
|
+
### Telemetry
|
|
198
|
+
|
|
199
|
+
The CLI reports on itself through three sinks: **Sentry** (errors), **Axiom**
|
|
200
|
+
(logs), and **PostHog** (anonymous usage analytics — command frequency,
|
|
201
|
+
duration, failure rates, keyed to a one-way `sha256(hostname + username)`
|
|
202
|
+
fingerprint). PostHog activates from `HOLOCRON_POSTHOG_PROJECT_TOKEN` → `POSTHOG_PROJECT_TOKEN`
|
|
203
|
+
→ a shipped ingest-only key (on by default). `HOLOCRON_TELEMETRY=false` disables
|
|
204
|
+
all three. See the [telemetry guide](https://docs.theholocron.dev/holocron/telemetry/).
|
|
197
205
|
|
|
198
206
|
## What's in here
|
|
199
207
|
|
package/dist/cli.mjs
CHANGED
|
@@ -13,14 +13,15 @@ import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/l
|
|
|
13
13
|
import ora from "ora";
|
|
14
14
|
import chalk from "chalk";
|
|
15
15
|
import { execFile, execFileSync, spawnSync } from "node:child_process";
|
|
16
|
-
import { homedir } from "node:os";
|
|
16
|
+
import { homedir, hostname, userInfo } from "node:os";
|
|
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";
|
|
20
20
|
import { getClients, getConfigs, getDocs, getPlugins, getSkills, getThemes, getUtils } from "@theholocron/registry-doc";
|
|
21
21
|
import { createGitHubClient } from "@theholocron/github-client";
|
|
22
|
-
import { promisify } from "node:util";
|
|
23
22
|
import * as Sentry from "@sentry/node";
|
|
23
|
+
import { PostHog } from "posthog-node";
|
|
24
|
+
import { promisify } from "node:util";
|
|
24
25
|
//#region src/env.ts
|
|
25
26
|
/** Singleton env for simple global lookups throughout the CLI. */
|
|
26
27
|
const env = createEnvLookup();
|
|
@@ -2034,12 +2035,13 @@ export class ${inputs.capabilityClass} {
|
|
|
2034
2035
|
readonly key = "${inputs.capability}" as const;
|
|
2035
2036
|
readonly providerName = "${inputs.slug}";
|
|
2036
2037
|
|
|
2037
|
-
constructor(private readonly rest: ${clientClass}) {}
|
|
2038
|
+
constructor(private readonly rest: () => ${clientClass}) {}
|
|
2038
2039
|
|
|
2039
2040
|
// TODO: implement the ${capabilityInterface} interface methods
|
|
2040
2041
|
// (see \`packages/cli/src/capabilities/index.ts\`). Each method
|
|
2041
2042
|
// should hit a specific ${inputs.vendorName} REST endpoint via
|
|
2042
|
-
// \`this.rest.request(...)
|
|
2043
|
+
// \`this.rest().request(...)\` — call the thunk so the token is
|
|
2044
|
+
// resolved lazily. Once methods are stubbed, add
|
|
2043
2045
|
// \`implements ${capabilityInterface}\` to the class declaration
|
|
2044
2046
|
// above and remove the \`as unknown as\` cast in src/index.ts.
|
|
2045
2047
|
}
|
|
@@ -2060,7 +2062,7 @@ import { stubFetch } from "./helpers.js";
|
|
|
2060
2062
|
function makeCapability() {
|
|
2061
2063
|
const stub = stubFetch([]);
|
|
2062
2064
|
const rest = new ${clientClass}({ token: "t", fetch: stub.fetch });
|
|
2063
|
-
return new ${inputs.capabilityClass}(rest);
|
|
2065
|
+
return new ${inputs.capabilityClass}(() => rest);
|
|
2064
2066
|
}
|
|
2065
2067
|
|
|
2066
2068
|
describe("${inputs.capabilityClass}", () => {
|
|
@@ -2253,17 +2255,21 @@ export interface ${inputs.vendorName}PluginOptions extends ResolveTokenInput {
|
|
|
2253
2255
|
|
|
2254
2256
|
export interface PluginContext {
|
|
2255
2257
|
options: ${inputs.vendorName}PluginOptions;
|
|
2256
|
-
|
|
2258
|
+
/** Memoized client — the token is resolved on first use, not at plugin load. */
|
|
2259
|
+
rest: () => ${clientClass};
|
|
2257
2260
|
}
|
|
2258
2261
|
|
|
2259
2262
|
export function createContext(options: ${inputs.vendorName}PluginOptions): PluginContext {
|
|
2260
|
-
|
|
2261
|
-
const restOpts: ConstructorParameters<typeof ${clientClass}>[0] = { token };
|
|
2262
|
-
if (options.baseUrl !== undefined) restOpts.baseUrl = options.baseUrl;
|
|
2263
|
-
if (options.fetch !== undefined) restOpts.fetch = options.fetch;
|
|
2263
|
+
let rest: ${clientClass} | undefined;
|
|
2264
2264
|
return {
|
|
2265
2265
|
options,
|
|
2266
|
-
rest:
|
|
2266
|
+
rest: () => {
|
|
2267
|
+
if (rest) return rest;
|
|
2268
|
+
const restOpts: ConstructorParameters<typeof ${clientClass}>[0] = { token: resolveToken(options) };
|
|
2269
|
+
if (options.baseUrl !== undefined) restOpts.baseUrl = options.baseUrl;
|
|
2270
|
+
if (options.fetch !== undefined) restOpts.fetch = options.fetch;
|
|
2271
|
+
return (rest = new ${clientClass}(restOpts));
|
|
2272
|
+
},
|
|
2267
2273
|
};
|
|
2268
2274
|
}
|
|
2269
2275
|
|
|
@@ -3750,7 +3756,7 @@ function deriveDeployPaths(raw) {
|
|
|
3750
3756
|
* configured. Paths are gitignored and regenerated on every setup run so the
|
|
3751
3757
|
* content always reflects the current CLI version.
|
|
3752
3758
|
*
|
|
3753
|
-
* Source:
|
|
3759
|
+
* Source: docs/wiki/specifications/ai-engineering-workflow.spec.md
|
|
3754
3760
|
*/
|
|
3755
3761
|
const DECISIONS_TEMPLATE = "---\nid: ADR-XXXX\ntitle: \"\"\nstatus: proposed\ndate: YYYY-MM-DD\nowners: []\nspecs: []\ndiscussion:\n github:\nsupersedes: []\nsuperseded-by: []\ntags: []\n---\n\n# [Short title of the decision]\n\n- Status: [proposed | accepted | rejected | deprecated | superseded by ADR-XXXX]\n- Date: YYYY-MM-DD\n\n## Context and Problem Statement\n\n2–3 sentences describing the situation that forced this decision.\n\n## Decision Drivers\n\n- [driver 1 — a constraint, goal, or value]\n- [driver 2]\n\n## Considered Options\n\n- [Option A]\n- [Option B]\n- [Option C — do nothing]\n\n## Decision Outcome\n\nChosen option: **[Option A]**, because [one-sentence justification].\n\n### Positive Consequences\n\n- …\n\n### Negative Consequences\n\n- …\n\n## Pros and Cons of the Options\n\n### [Option A]\n\n- Good, because [argument]\n- Bad, because [argument]\n\n### [Option B]\n\n- Good, because [argument]\n- Bad, because [argument]\n";
|
|
3756
3762
|
const AGENT_PROMPTS = {
|
|
@@ -5202,12 +5208,17 @@ const REGISTRY_MAP = {
|
|
|
5202
5208
|
"@theholocron/themes": getThemes,
|
|
5203
5209
|
"@theholocron/utils": getUtils
|
|
5204
5210
|
};
|
|
5205
|
-
function generateInstallBlock(pkg) {
|
|
5211
|
+
function generateInstallBlock(pkg, homepage = "") {
|
|
5206
5212
|
const { name = "", bin, peerDependencies = {} } = pkg;
|
|
5207
5213
|
const isCli = Boolean(bin);
|
|
5208
5214
|
const isReact = Boolean(peerDependencies["react"]);
|
|
5215
|
+
const isConsumable = isCli || !pkg.private || Boolean(pkg.main) || pkg.exports !== void 0;
|
|
5209
5216
|
const lines = [];
|
|
5210
5217
|
lines.push("## Installation", "");
|
|
5218
|
+
if (!isConsumable) {
|
|
5219
|
+
lines.push("This repository is a workspace root — it is not published. See the", "packages under [`packages/`](./packages) for the tools it ships.");
|
|
5220
|
+
return lines.join("\n");
|
|
5221
|
+
}
|
|
5211
5222
|
if (isCli) lines.push("```bash", `npm install --global ${name}`, "```");
|
|
5212
5223
|
else lines.push("```bash", `pnpm install ${name}`, "```");
|
|
5213
5224
|
lines.push("", "## Usage", "");
|
|
@@ -5216,8 +5227,9 @@ function generateInstallBlock(pkg) {
|
|
|
5216
5227
|
lines.push("```bash");
|
|
5217
5228
|
for (const cmd of commands) lines.push(`${cmd} --help`);
|
|
5218
5229
|
lines.push("```");
|
|
5219
|
-
} else if (isReact) lines.push("```tsx", `import {
|
|
5220
|
-
else lines.push(
|
|
5230
|
+
} else if (isReact) lines.push("```tsx", `import {} from "${name}";`, "", "function App() {", ` return <></>;`, "}", "```");
|
|
5231
|
+
else if (homepage) lines.push(`See the [documentation](${homepage}) for the API.`);
|
|
5232
|
+
else lines.push("See the package documentation for the API.");
|
|
5221
5233
|
return lines.join("\n");
|
|
5222
5234
|
}
|
|
5223
5235
|
/**
|
|
@@ -5299,9 +5311,10 @@ async function runSyncReadme(input) {
|
|
|
5299
5311
|
};
|
|
5300
5312
|
}
|
|
5301
5313
|
const description = loaded.resolved.description ?? "";
|
|
5314
|
+
const homepage = loaded.resolved.homepage ?? pkg.homepage ?? "";
|
|
5302
5315
|
const sections = generateReadme({
|
|
5303
5316
|
description,
|
|
5304
|
-
homepage
|
|
5317
|
+
homepage,
|
|
5305
5318
|
scripts: pkg.scripts ?? {},
|
|
5306
5319
|
packages: pkg.name ? REGISTRY_MAP[pkg.name]?.() : void 0
|
|
5307
5320
|
});
|
|
@@ -5310,7 +5323,7 @@ async function runSyncReadme(input) {
|
|
|
5310
5323
|
development: sections.development,
|
|
5311
5324
|
releases: sections.releases
|
|
5312
5325
|
};
|
|
5313
|
-
const updated = await updateReadme(repoRoot, generateInstallBlock(pkg), markerSections, dryRun, readFileFn, writeFileFn);
|
|
5326
|
+
const updated = await updateReadme(repoRoot, generateInstallBlock(pkg, homepage), markerSections, dryRun, readFileFn, writeFileFn);
|
|
5314
5327
|
if (!updated) {
|
|
5315
5328
|
print(style.warn("README.md not found or has no writable location for installation block"));
|
|
5316
5329
|
logger.warn({
|
|
@@ -5960,6 +5973,168 @@ async function writeWorkflowFile(repoRoot, filename, content) {
|
|
|
5960
5973
|
await writeFile(join(dir, filename), content, "utf8");
|
|
5961
5974
|
}
|
|
5962
5975
|
//#endregion
|
|
5976
|
+
//#region src/telemetry.ts
|
|
5977
|
+
const FALLBACK_DSN = "https://95cbb72ad5636c94e119a5405ee8f55f@o4508238154104832.ingest.us.sentry.io/4511810950791168";
|
|
5978
|
+
/**
|
|
5979
|
+
* Resolve the Sentry DSN — the `errors` capability's self-contained
|
|
5980
|
+
* activation, per ADR-0007:
|
|
5981
|
+
*
|
|
5982
|
+
* HOLOCRON_SENTRY_DSN → SENTRY_DSN → built-in fallback
|
|
5983
|
+
*
|
|
5984
|
+
* A consumer repo with `SENTRY_DSN` set gets CLI errors routed to its own
|
|
5985
|
+
* project with no config. Opt out entirely with `HOLOCRON_TELEMETRY=false`.
|
|
5986
|
+
*/
|
|
5987
|
+
function resolveDsn() {
|
|
5988
|
+
return env.get("HOLOCRON_SENTRY_DSN") ?? env.get("SENTRY_DSN") ?? FALLBACK_DSN;
|
|
5989
|
+
}
|
|
5990
|
+
const FALLBACK_POSTHOG_PROJECT_TOKEN = "phc_AC4vFCvYzwnzmG7Vg5nEc3PZKZztoPyfKp4Lb9BbXcLK";
|
|
5991
|
+
const DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com";
|
|
5992
|
+
/**
|
|
5993
|
+
* Resolve the PostHog project key — mirrors {@link resolveDsn} and the
|
|
5994
|
+
* `HOLOCRON_AXIOM_TOKEN` / `AXIOM_TOKEN` pattern, per ADR-0008:
|
|
5995
|
+
*
|
|
5996
|
+
* HOLOCRON_POSTHOG_PROJECT_TOKEN → POSTHOG_PROJECT_TOKEN → built-in fallback
|
|
5997
|
+
*
|
|
5998
|
+
* `POSTHOG_PROJECT_TOKEN` is the vendor-native name a consumer repo sets (as a
|
|
5999
|
+
* plain variable, not a secret — the `phc_…` key is publishable) to route CLI
|
|
6000
|
+
* usage to its own project.
|
|
6001
|
+
*/
|
|
6002
|
+
function resolvePostHogKey() {
|
|
6003
|
+
return env.get("HOLOCRON_POSTHOG_PROJECT_TOKEN") ?? env.get("POSTHOG_PROJECT_TOKEN") ?? FALLBACK_POSTHOG_PROJECT_TOKEN;
|
|
6004
|
+
}
|
|
6005
|
+
function resolvePostHogHost() {
|
|
6006
|
+
return env.get("HOLOCRON_POSTHOG_HOST") ?? env.get("POSTHOG_HOST") ?? DEFAULT_POSTHOG_HOST;
|
|
6007
|
+
}
|
|
6008
|
+
function isEnabled() {
|
|
6009
|
+
return env.get("HOLOCRON_TELEMETRY") !== "false" && !env.get("NO_HOLOCRON_TELEMETRY");
|
|
6010
|
+
}
|
|
6011
|
+
const sentryEnabled = () => isEnabled() && resolveDsn() !== "";
|
|
6012
|
+
const posthogEnabled = () => isEnabled() && resolvePostHogKey() !== "";
|
|
6013
|
+
let posthog;
|
|
6014
|
+
let commandStartedAt = 0;
|
|
6015
|
+
let currentCommand = "unknown";
|
|
6016
|
+
/**
|
|
6017
|
+
* Anonymous, stable per-machine id — `sha256(hostname + username)` truncated
|
|
6018
|
+
* (ADR-0008). A one-way fingerprint: no raw hostname or username is ever
|
|
6019
|
+
* transmitted, and it is not personal data under this project's rule.
|
|
6020
|
+
*/
|
|
6021
|
+
function machineId() {
|
|
6022
|
+
try {
|
|
6023
|
+
return createHash("sha256").update(`${hostname()} ${userInfo().username}`).digest("hex").slice(0, 32);
|
|
6024
|
+
} catch {
|
|
6025
|
+
return "unknown";
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
/** Properties every event carries, so any event can be pivoted to its Axiom trace. */
|
|
6029
|
+
function baseProps() {
|
|
6030
|
+
const runId = getRunId();
|
|
6031
|
+
return {
|
|
6032
|
+
command: currentCommand,
|
|
6033
|
+
ci: Boolean(env.get("CI")),
|
|
6034
|
+
...runId ? { runId } : {}
|
|
6035
|
+
};
|
|
6036
|
+
}
|
|
6037
|
+
/**
|
|
6038
|
+
* Capture a PostHog event. Runs every `properties` object through the same
|
|
6039
|
+
* token scrubber Sentry uses (defence-in-depth — values are controlled).
|
|
6040
|
+
*/
|
|
6041
|
+
function event(name, properties = {}) {
|
|
6042
|
+
if (!posthogEnabled() || !posthog) return;
|
|
6043
|
+
const merged = {
|
|
6044
|
+
...baseProps(),
|
|
6045
|
+
...properties
|
|
6046
|
+
};
|
|
6047
|
+
posthog.capture({
|
|
6048
|
+
distinctId: machineId(),
|
|
6049
|
+
event: name,
|
|
6050
|
+
properties: JSON.parse(redact(JSON.stringify(merged)))
|
|
6051
|
+
});
|
|
6052
|
+
}
|
|
6053
|
+
function init(version) {
|
|
6054
|
+
initSentry(version);
|
|
6055
|
+
initPostHog(version);
|
|
6056
|
+
}
|
|
6057
|
+
function initSentry(version) {
|
|
6058
|
+
if (!sentryEnabled()) return;
|
|
6059
|
+
Sentry.init({
|
|
6060
|
+
dsn: resolveDsn(),
|
|
6061
|
+
release: `holocron@${version}`,
|
|
6062
|
+
environment: env.get("CI") ? "ci" : "local",
|
|
6063
|
+
tracesSampleRate: 1,
|
|
6064
|
+
beforeSend: scrubError
|
|
6065
|
+
});
|
|
6066
|
+
Sentry.startSession();
|
|
6067
|
+
Sentry.setTag("os", process.platform);
|
|
6068
|
+
Sentry.setTag("node", process.version);
|
|
6069
|
+
Sentry.setTag("ci", String(Boolean(env.get("CI"))));
|
|
6070
|
+
}
|
|
6071
|
+
function initPostHog(version) {
|
|
6072
|
+
if (!posthogEnabled()) return;
|
|
6073
|
+
posthog = new PostHog(resolvePostHogKey(), { host: resolvePostHogHost() });
|
|
6074
|
+
posthog.identify({
|
|
6075
|
+
distinctId: machineId(),
|
|
6076
|
+
properties: {
|
|
6077
|
+
ci: Boolean(env.get("CI")),
|
|
6078
|
+
os: process.platform,
|
|
6079
|
+
node: process.version,
|
|
6080
|
+
cli: version,
|
|
6081
|
+
...env.get("HOLOCRON_ORG") ? { org: env.get("HOLOCRON_ORG") } : {}
|
|
6082
|
+
}
|
|
6083
|
+
});
|
|
6084
|
+
}
|
|
6085
|
+
let lastErrorType;
|
|
6086
|
+
/** Start a Sentry command span, or `undefined` when Sentry is off. */
|
|
6087
|
+
function startSpan(name) {
|
|
6088
|
+
if (!sentryEnabled()) return void 0;
|
|
6089
|
+
Sentry.setTag("command", name);
|
|
6090
|
+
return Sentry.startInactiveSpan({
|
|
6091
|
+
name,
|
|
6092
|
+
op: "holocron.command",
|
|
6093
|
+
forceTransaction: true
|
|
6094
|
+
});
|
|
6095
|
+
}
|
|
6096
|
+
function startCommand(name) {
|
|
6097
|
+
currentCommand = name;
|
|
6098
|
+
commandStartedAt = Date.now();
|
|
6099
|
+
lastErrorType = void 0;
|
|
6100
|
+
const span = startSpan(name);
|
|
6101
|
+
event("command_started", { dry_run: process.argv.includes("--dry-run") });
|
|
6102
|
+
return (ok) => {
|
|
6103
|
+
if (span) {
|
|
6104
|
+
span.setStatus({ code: ok ? 1 : 2 });
|
|
6105
|
+
span.end();
|
|
6106
|
+
}
|
|
6107
|
+
event(ok ? "command_completed" : "command_failed", {
|
|
6108
|
+
status: ok ? "ok" : "fail",
|
|
6109
|
+
duration_ms: Date.now() - commandStartedAt,
|
|
6110
|
+
...ok || !lastErrorType ? {} : { error_type: lastErrorType }
|
|
6111
|
+
});
|
|
6112
|
+
};
|
|
6113
|
+
}
|
|
6114
|
+
function captureException(err) {
|
|
6115
|
+
lastErrorType = err instanceof Error ? err.constructor.name : "Error";
|
|
6116
|
+
if (!sentryEnabled()) return;
|
|
6117
|
+
Sentry.captureException(err);
|
|
6118
|
+
}
|
|
6119
|
+
function endSession() {
|
|
6120
|
+
if (!sentryEnabled()) return;
|
|
6121
|
+
Sentry.endSession();
|
|
6122
|
+
}
|
|
6123
|
+
async function flush() {
|
|
6124
|
+
if (sentryEnabled()) await Sentry.close(2e3);
|
|
6125
|
+
if (posthog) {
|
|
6126
|
+
await posthog.shutdown();
|
|
6127
|
+
posthog = void 0;
|
|
6128
|
+
}
|
|
6129
|
+
}
|
|
6130
|
+
const TOKEN_RE = /\b(ghp_|ghs_|glpat-|xoxb-|xoxp-|npm_|sk-|[A-Z][A-Z0-9_]{2,}_TOKEN[=\s])[^\s"]*/g;
|
|
6131
|
+
function redact(raw) {
|
|
6132
|
+
return raw.replace(TOKEN_RE, "[REDACTED]");
|
|
6133
|
+
}
|
|
6134
|
+
function scrubError(event, _hint) {
|
|
6135
|
+
return JSON.parse(redact(JSON.stringify(event)));
|
|
6136
|
+
}
|
|
6137
|
+
//#endregion
|
|
5963
6138
|
//#region src/templates/actions/auto-commit.yml
|
|
5964
6139
|
var auto_commit_default = "name: Auto Commit\ndescription: >\n Commit and push any file changes using stefanzweifel/git-auto-commit-action.\n If a branch name is provided and differs from the current branch, a new branch\n is created automatically.\n\ninputs:\n token:\n description: >\n GitHub token with push access to the target branch. Used to authenticate\n the git remote so the commit can be pushed.\n required: true\n branch:\n description: >\n Branch to commit to. Leave empty to commit to the current branch (useful\n in PR contexts). Provide an explicit name (e.g. chore/auto-sync) to push\n to a separate branch.\n required: false\n default: \"\"\n commit-message:\n description: Commit message.\n required: true\n commit-options:\n description: >\n Additional options passed to git commit (e.g. --no-verify).\n required: false\n default: \"\"\n commit-user-name:\n description: Git user.name for the commit.\n required: false\n default: \"github-actions[bot]\"\n commit-user-email:\n description: Git user.email for the commit.\n required: false\n default: \"41898282+github-actions[bot]@users.noreply.github.com\"\n commit-author:\n description: >\n Git author string in \"Name <email>\" format.\n required: false\n default: \"github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>\"\n\noutputs:\n changes-detected:\n description: \"'true' if any files were committed, 'false' otherwise.\"\n value: ${{ steps.commit.outputs.changes_detected }}\n\nruns:\n using: composite\n steps:\n - name: Configure git remote with token\n shell: bash\n run: |\n REMOTE_URL=$(git remote get-url origin)\n AUTHED_URL=$(echo \"$REMOTE_URL\" | sed \"s|https://|https://x-access-token:${TOKEN}@|\")\n git remote set-url origin \"$AUTHED_URL\"\n env:\n TOKEN: ${{ inputs.token }}\n\n - name: Reset branch to current HEAD\n if: ${{ inputs.branch != '' }}\n shell: bash\n run: git checkout -B \"$BRANCH\"\n env:\n BRANCH: ${{ inputs.branch }}\n\n - name: Build signed commit message\n id: msg\n shell: bash\n run: |\n TRAILER=\"Signed-off-by: ${COMMIT_USER_NAME} <${COMMIT_USER_EMAIL}>\"\n if echo \"$COMMIT_MESSAGE\" | grep -qF \"$TRAILER\"; then\n FINAL=\"$COMMIT_MESSAGE\"\n else\n FINAL=\"${COMMIT_MESSAGE}\"$'\\n\\n'\"${TRAILER}\"\n fi\n {\n echo \"value<<EOF_TRAILER\"\n printf '%s' \"$FINAL\"\n echo \"\"\n echo \"EOF_TRAILER\"\n } >> \"$GITHUB_OUTPUT\"\n env:\n COMMIT_MESSAGE: ${{ inputs.commit-message }}\n COMMIT_USER_NAME: ${{ inputs.commit-user-name }}\n COMMIT_USER_EMAIL: ${{ inputs.commit-user-email }}\n\n - uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0\n id: commit\n with:\n branch: ${{ inputs.branch }}\n commit_message: ${{ steps.msg.outputs.value }}\n commit_options: ${{ inputs.commit-options }}\n commit_user_name: ${{ inputs.commit-user-name }}\n commit_user_email: ${{ inputs.commit-user-email }}\n commit_author: ${{ inputs.commit-author }}\n";
|
|
5965
6140
|
//#endregion
|
|
@@ -6006,13 +6181,13 @@ var security_default = "name: Security\n\non: # yamllint disable-line rule:truth
|
|
|
6006
6181
|
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";
|
|
6007
6182
|
//#endregion
|
|
6008
6183
|
//#region src/templates/workflows/sync.yml
|
|
6009
|
-
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";
|
|
6184
|
+
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 POSTHOG_PROJECT_TOKEN: ${{ vars.POSTHOG_PROJECT_TOKEN }}\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";
|
|
6010
6185
|
//#endregion
|
|
6011
6186
|
//#region src/templates/workflows/sync-dispatch.yml
|
|
6012
6187
|
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";
|
|
6013
6188
|
//#endregion
|
|
6014
6189
|
//#region src/templates/workflows/sync-github.yml
|
|
6015
|
-
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";
|
|
6190
|
+
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 POSTHOG_PROJECT_TOKEN: ${{ vars.POSTHOG_PROJECT_TOKEN }}\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";
|
|
6016
6191
|
//#endregion
|
|
6017
6192
|
//#region src/templates/workflows/tag.yml
|
|
6018
6193
|
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";
|
|
@@ -6146,6 +6321,14 @@ async function runSyncGithub(input) {
|
|
|
6146
6321
|
...r.message ? { reason: r.message } : {},
|
|
6147
6322
|
...r.prUrl ? { pr: r.prUrl } : {}
|
|
6148
6323
|
}, "sync-github: done");
|
|
6324
|
+
event("sync_github_run", {
|
|
6325
|
+
repo,
|
|
6326
|
+
status: r.status,
|
|
6327
|
+
...branch ? { branch } : {},
|
|
6328
|
+
repos_targeted: 1,
|
|
6329
|
+
files_changed: r.created + r.updated,
|
|
6330
|
+
pr_opened: Boolean(r.prUrl)
|
|
6331
|
+
});
|
|
6149
6332
|
return r;
|
|
6150
6333
|
};
|
|
6151
6334
|
if (input.outputDir) {
|
|
@@ -6631,70 +6814,6 @@ async function fileExists(path) {
|
|
|
6631
6814
|
}
|
|
6632
6815
|
}
|
|
6633
6816
|
//#endregion
|
|
6634
|
-
//#region src/telemetry.ts
|
|
6635
|
-
const FALLBACK_DSN = "https://95cbb72ad5636c94e119a5405ee8f55f@o4508238154104832.ingest.us.sentry.io/4511810950791168";
|
|
6636
|
-
/**
|
|
6637
|
-
* Resolve the Sentry DSN — the `errors` capability's self-contained
|
|
6638
|
-
* activation, per ADR-0007:
|
|
6639
|
-
*
|
|
6640
|
-
* HOLOCRON_SENTRY_DSN → SENTRY_DSN → built-in fallback
|
|
6641
|
-
*
|
|
6642
|
-
* A consumer repo with `SENTRY_DSN` set gets CLI errors routed to its own
|
|
6643
|
-
* project with no config. Opt out entirely with `HOLOCRON_TELEMETRY=false`.
|
|
6644
|
-
*/
|
|
6645
|
-
function resolveDsn() {
|
|
6646
|
-
return env.get("HOLOCRON_SENTRY_DSN") ?? env.get("SENTRY_DSN") ?? FALLBACK_DSN;
|
|
6647
|
-
}
|
|
6648
|
-
function isEnabled() {
|
|
6649
|
-
return !(env.get("HOLOCRON_TELEMETRY") === "false" || Boolean(env.get("NO_HOLOCRON_TELEMETRY"))) && resolveDsn() !== "";
|
|
6650
|
-
}
|
|
6651
|
-
function init(version) {
|
|
6652
|
-
if (!isEnabled()) return;
|
|
6653
|
-
Sentry.init({
|
|
6654
|
-
dsn: resolveDsn(),
|
|
6655
|
-
release: `holocron@${version}`,
|
|
6656
|
-
environment: env.get("CI") ? "ci" : "local",
|
|
6657
|
-
tracesSampleRate: 1,
|
|
6658
|
-
beforeSend: scrubError
|
|
6659
|
-
});
|
|
6660
|
-
Sentry.startSession();
|
|
6661
|
-
Sentry.setTag("os", process.platform);
|
|
6662
|
-
Sentry.setTag("node", process.version);
|
|
6663
|
-
Sentry.setTag("ci", String(Boolean(env.get("CI"))));
|
|
6664
|
-
}
|
|
6665
|
-
function startCommand(name) {
|
|
6666
|
-
if (!isEnabled()) return () => {};
|
|
6667
|
-
Sentry.setTag("command", name);
|
|
6668
|
-
const span = Sentry.startInactiveSpan({
|
|
6669
|
-
name,
|
|
6670
|
-
op: "holocron.command",
|
|
6671
|
-
forceTransaction: true
|
|
6672
|
-
});
|
|
6673
|
-
return (ok) => {
|
|
6674
|
-
span.setStatus({ code: ok ? 1 : 2 });
|
|
6675
|
-
span.end();
|
|
6676
|
-
};
|
|
6677
|
-
}
|
|
6678
|
-
function captureException(err) {
|
|
6679
|
-
if (!isEnabled()) return;
|
|
6680
|
-
Sentry.captureException(err);
|
|
6681
|
-
}
|
|
6682
|
-
function endSession() {
|
|
6683
|
-
if (!isEnabled()) return;
|
|
6684
|
-
Sentry.endSession();
|
|
6685
|
-
}
|
|
6686
|
-
async function flush() {
|
|
6687
|
-
if (!isEnabled()) return;
|
|
6688
|
-
await Sentry.close(2e3);
|
|
6689
|
-
}
|
|
6690
|
-
const TOKEN_RE = /\b(ghp_|ghs_|glpat-|xoxb-|xoxp-|npm_|sk-|[A-Z][A-Z0-9_]{2,}_TOKEN[=\s])[^\s"]*/g;
|
|
6691
|
-
function redact(raw) {
|
|
6692
|
-
return raw.replace(TOKEN_RE, "[REDACTED]");
|
|
6693
|
-
}
|
|
6694
|
-
function scrubError(event, _hint) {
|
|
6695
|
-
return JSON.parse(redact(JSON.stringify(event)));
|
|
6696
|
-
}
|
|
6697
|
-
//#endregion
|
|
6698
6817
|
//#region src/update-notifier.ts
|
|
6699
6818
|
const PACKAGE_NAME = "@theholocron/cli";
|
|
6700
6819
|
const CACHE_TTL_MS = 1440 * 60 * 1e3;
|
|
@@ -6887,12 +7006,12 @@ try {
|
|
|
6887
7006
|
describe: "Set the log level to error — suppress info and warn."
|
|
6888
7007
|
}).middleware((argv) => {
|
|
6889
7008
|
const name = argv._.slice(0, 2).join(" ") || "unknown";
|
|
6890
|
-
finishCommand = startCommand(name);
|
|
6891
7009
|
printRunId = Boolean(argv.debug || argv.verbose);
|
|
6892
7010
|
buildCliLogger(argv, {
|
|
6893
7011
|
command: name,
|
|
6894
7012
|
org: argv.org
|
|
6895
7013
|
});
|
|
7014
|
+
finishCommand = startCommand(name);
|
|
6896
7015
|
}).command("version", "Print the CLI version", () => {}, () => {
|
|
6897
7016
|
console.log(`holocron ${CLI_VERSION}`);
|
|
6898
7017
|
}).command("clone", "Clone all repos in a GitHub org as siblings under a single directory", (y) => y.option("org", {
|