@theholocron/cli 4.13.0 → 4.14.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 +22 -1
- package/dist/cli.mjs +223 -108
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +32 -1
- package/dist/index.mjs +2 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -338,7 +338,28 @@ The CLI reports on itself through three sinks: **Sentry** (errors), **Axiom**
|
|
|
338
338
|
duration, failure rates, keyed to a one-way `sha256(hostname + username)`
|
|
339
339
|
fingerprint). PostHog activates from `HOLOCRON_POSTHOG_PROJECT_TOKEN` → `POSTHOG_PROJECT_TOKEN`
|
|
340
340
|
→ a shipped ingest-only key (on by default). `HOLOCRON_TELEMETRY=false` disables
|
|
341
|
-
all three.
|
|
341
|
+
all three.
|
|
342
|
+
|
|
343
|
+
For a committed, repo-wide opt-out, add a `telemetry` block to `holocron.config`
|
|
344
|
+
— the version-controlled peer of `HOLOCRON_TELEMETRY=false` (the env var still
|
|
345
|
+
wins; this is an override layer applied after config loads):
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
import { defineConfig } from "@theholocron/cli";
|
|
349
|
+
|
|
350
|
+
export default defineConfig({
|
|
351
|
+
// …
|
|
352
|
+
telemetry: {
|
|
353
|
+
enabled: false, // no-ops Sentry + PostHog for every command
|
|
354
|
+
// analytics: "none", // alternative: PostHog off, Sentry error reporting kept
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Each SDK sits behind a Holocron-owned interface — `ErrorSink` (Sentry) /
|
|
360
|
+
`AnalyticsSink` (PostHog) in `src/telemetry/sinks.ts`; `@sentry/node` and
|
|
361
|
+
`posthog-node` are imported only from their adapter modules. See the
|
|
362
|
+
[telemetry guide](https://docs.theholocron.dev/holocron/telemetry/).
|
|
342
363
|
|
|
343
364
|
## What's in here
|
|
344
365
|
|
package/dist/cli.mjs
CHANGED
|
@@ -21,8 +21,8 @@ import { createHash } from "node:crypto";
|
|
|
21
21
|
import { generateReadme } from "@theholocron/components-doc/markdown";
|
|
22
22
|
import { getClients, getConfigs, getDocs, getPlugins, getSkills, getThemes, getUtils } from "@theholocron/registry-doc";
|
|
23
23
|
import { createGitHubClient } from "@theholocron/github-client";
|
|
24
|
-
import * as Sentry from "@sentry/node";
|
|
25
24
|
import { PostHog } from "posthog-node";
|
|
25
|
+
import * as Sentry from "@sentry/node";
|
|
26
26
|
import { promisify } from "node:util";
|
|
27
27
|
import { ConfigFileError, loadConfigFile } from "@theholocron/datapad";
|
|
28
28
|
//#region src/env.ts
|
|
@@ -292,7 +292,8 @@ function resolveConfig(raw) {
|
|
|
292
292
|
skills: raw.skills,
|
|
293
293
|
docs,
|
|
294
294
|
env: raw.env,
|
|
295
|
-
log: raw.log
|
|
295
|
+
log: raw.log,
|
|
296
|
+
telemetry: raw.telemetry
|
|
296
297
|
};
|
|
297
298
|
}
|
|
298
299
|
//#endregion
|
|
@@ -5820,50 +5821,165 @@ async function writeWorkflowFile(repoRoot, filename, content) {
|
|
|
5820
5821
|
await writeFile(join(dir, filename), content, "utf8");
|
|
5821
5822
|
}
|
|
5822
5823
|
//#endregion
|
|
5823
|
-
//#region src/telemetry.ts
|
|
5824
|
-
const FALLBACK_DSN = "https://95cbb72ad5636c94e119a5405ee8f55f@o4508238154104832.ingest.us.sentry.io/4511810950791168";
|
|
5824
|
+
//#region src/telemetry/posthog-sink.ts
|
|
5825
5825
|
/**
|
|
5826
|
-
*
|
|
5827
|
-
*
|
|
5826
|
+
* `PostHogSink` — the **only** `posthog-node` import in the CLI. Product
|
|
5827
|
+
* analytics (usage, adoption, retention), activated self-contained per ADR-0008:
|
|
5828
5828
|
*
|
|
5829
|
-
*
|
|
5829
|
+
* HOLOCRON_POSTHOG_PROJECT_TOKEN → POSTHOG_PROJECT_TOKEN → built-in fallback key
|
|
5830
|
+
* HOLOCRON_POSTHOG_HOST → POSTHOG_HOST → https://us.i.posthog.com
|
|
5830
5831
|
*
|
|
5831
|
-
*
|
|
5832
|
-
*
|
|
5832
|
+
* The built-in fallback is an ingest-only project write key (`phc_…`) — it can
|
|
5833
|
+
* capture events, never read data — so it ships in the published package, the
|
|
5834
|
+
* same risk model as the hard-coded Sentry DSN. {@link resolvePostHogKey}
|
|
5835
|
+
* returning `""` is how the caller decides to install a `NoopAnalyticsSink`.
|
|
5833
5836
|
*/
|
|
5834
|
-
function resolveDsn() {
|
|
5835
|
-
return env.get("HOLOCRON_SENTRY_DSN") ?? env.get("SENTRY_DSN") ?? FALLBACK_DSN;
|
|
5836
|
-
}
|
|
5837
5837
|
const FALLBACK_POSTHOG_PROJECT_TOKEN = "phc_AC4vFCvYzwnzmG7Vg5nEc3PZKZztoPyfKp4Lb9BbXcLK";
|
|
5838
5838
|
const DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com";
|
|
5839
|
-
/**
|
|
5840
|
-
* Resolve the PostHog project key — mirrors {@link resolveDsn} and the
|
|
5841
|
-
* `HOLOCRON_AXIOM_TOKEN` / `AXIOM_TOKEN` pattern, per ADR-0008:
|
|
5842
|
-
*
|
|
5843
|
-
* HOLOCRON_POSTHOG_PROJECT_TOKEN → POSTHOG_PROJECT_TOKEN → built-in fallback
|
|
5844
|
-
*
|
|
5845
|
-
* `POSTHOG_PROJECT_TOKEN` is the vendor-native name a consumer repo sets (as a
|
|
5846
|
-
* plain variable, not a secret — the `phc_…` key is publishable) to route CLI
|
|
5847
|
-
* usage to its own project.
|
|
5848
|
-
*/
|
|
5839
|
+
/** Resolve the PostHog project key. `""` → usage analytics disabled. */
|
|
5849
5840
|
function resolvePostHogKey() {
|
|
5850
5841
|
return env.get("HOLOCRON_POSTHOG_PROJECT_TOKEN") ?? env.get("POSTHOG_PROJECT_TOKEN") ?? FALLBACK_POSTHOG_PROJECT_TOKEN;
|
|
5851
5842
|
}
|
|
5852
5843
|
function resolvePostHogHost() {
|
|
5853
5844
|
return env.get("HOLOCRON_POSTHOG_HOST") ?? env.get("POSTHOG_HOST") ?? DEFAULT_POSTHOG_HOST;
|
|
5854
5845
|
}
|
|
5846
|
+
var PostHogSink = class {
|
|
5847
|
+
#client;
|
|
5848
|
+
constructor() {
|
|
5849
|
+
this.#client = new PostHog(resolvePostHogKey(), { host: resolvePostHogHost() });
|
|
5850
|
+
}
|
|
5851
|
+
identify(distinctId, props) {
|
|
5852
|
+
this.#client.identify({
|
|
5853
|
+
distinctId,
|
|
5854
|
+
properties: props
|
|
5855
|
+
});
|
|
5856
|
+
}
|
|
5857
|
+
capture(distinctId, event, props) {
|
|
5858
|
+
this.#client.capture({
|
|
5859
|
+
distinctId,
|
|
5860
|
+
event,
|
|
5861
|
+
properties: props
|
|
5862
|
+
});
|
|
5863
|
+
}
|
|
5864
|
+
async shutdown() {
|
|
5865
|
+
await this.#client.shutdown();
|
|
5866
|
+
}
|
|
5867
|
+
};
|
|
5868
|
+
//#endregion
|
|
5869
|
+
//#region src/telemetry/redact.ts
|
|
5870
|
+
/**
|
|
5871
|
+
* Token-shape scrubbing for telemetry payloads — a raw token never leaves the
|
|
5872
|
+
* process, even when a value is controlled. Regex-on-the-serialised-string
|
|
5873
|
+
* (matches a token shape *anywhere*), distinct from `@theholocron/logger`'s
|
|
5874
|
+
* field-path redaction. Shared by {@link scrubError} (Sentry `beforeSend`) and
|
|
5875
|
+
* the PostHog `event()` path.
|
|
5876
|
+
*/
|
|
5877
|
+
const TOKEN_RE = /\b(ghp_|ghs_|glpat-|xoxb-|xoxp-|npm_|sk-|[A-Z][A-Z0-9_]{2,}_TOKEN[=\s])[^\s"]*/g;
|
|
5878
|
+
/** Replace token-shaped substrings in a string with `[REDACTED]`. */
|
|
5879
|
+
function redact(raw) {
|
|
5880
|
+
return raw.replace(TOKEN_RE, "[REDACTED]");
|
|
5881
|
+
}
|
|
5882
|
+
/** Deep-scrub every string in an object by round-tripping through {@link redact}. */
|
|
5883
|
+
function redactObject(value) {
|
|
5884
|
+
return JSON.parse(redact(JSON.stringify(value)));
|
|
5885
|
+
}
|
|
5886
|
+
//#endregion
|
|
5887
|
+
//#region src/telemetry/sentry-sink.ts
|
|
5888
|
+
const FALLBACK_DSN = "https://95cbb72ad5636c94e119a5405ee8f55f@o4508238154104832.ingest.us.sentry.io/4511810950791168";
|
|
5889
|
+
/** Resolve the Sentry DSN. `""` → error telemetry disabled. */
|
|
5890
|
+
function resolveDsn() {
|
|
5891
|
+
return env.get("HOLOCRON_SENTRY_DSN") ?? env.get("SENTRY_DSN") ?? FALLBACK_DSN;
|
|
5892
|
+
}
|
|
5893
|
+
/** `beforeSend` — token-shaped strings never leave the process. */
|
|
5894
|
+
function scrubError(event, _hint) {
|
|
5895
|
+
return redactObject(event);
|
|
5896
|
+
}
|
|
5897
|
+
var SentrySink = class {
|
|
5898
|
+
init(ctx) {
|
|
5899
|
+
Sentry.init({
|
|
5900
|
+
dsn: resolveDsn(),
|
|
5901
|
+
release: ctx.release,
|
|
5902
|
+
environment: ctx.environment,
|
|
5903
|
+
tracesSampleRate: 1,
|
|
5904
|
+
beforeSend: scrubError
|
|
5905
|
+
});
|
|
5906
|
+
Sentry.startSession();
|
|
5907
|
+
for (const [key, value] of Object.entries(ctx.tags)) Sentry.setTag(key, value);
|
|
5908
|
+
}
|
|
5909
|
+
startSpan(name) {
|
|
5910
|
+
Sentry.setTag("command", name);
|
|
5911
|
+
const span = Sentry.startInactiveSpan({
|
|
5912
|
+
name,
|
|
5913
|
+
op: "holocron.command",
|
|
5914
|
+
forceTransaction: true
|
|
5915
|
+
});
|
|
5916
|
+
return {
|
|
5917
|
+
setStatus: (ok) => span.setStatus({ code: ok ? 1 : 2 }),
|
|
5918
|
+
end: () => span.end()
|
|
5919
|
+
};
|
|
5920
|
+
}
|
|
5921
|
+
captureException(err) {
|
|
5922
|
+
Sentry.captureException(err);
|
|
5923
|
+
}
|
|
5924
|
+
endSession() {
|
|
5925
|
+
Sentry.endSession();
|
|
5926
|
+
}
|
|
5927
|
+
async flush() {
|
|
5928
|
+
await Sentry.close(2e3);
|
|
5929
|
+
}
|
|
5930
|
+
};
|
|
5931
|
+
//#endregion
|
|
5932
|
+
//#region src/telemetry/sinks.ts
|
|
5933
|
+
/**
|
|
5934
|
+
* The disabled path made explicit — one shared no-op instead of a scatter of
|
|
5935
|
+
* `if (!enabled) return` guards. Used when telemetry is opted out
|
|
5936
|
+
* (`HOLOCRON_TELEMETRY=false`, `config.telemetry.enabled: false`) or no
|
|
5937
|
+
* credentials resolve.
|
|
5938
|
+
*/
|
|
5939
|
+
var NoopErrorSink = class {
|
|
5940
|
+
init() {}
|
|
5941
|
+
startSpan() {}
|
|
5942
|
+
captureException() {}
|
|
5943
|
+
endSession() {}
|
|
5944
|
+
async flush() {}
|
|
5945
|
+
};
|
|
5946
|
+
/** @see NoopErrorSink */
|
|
5947
|
+
var NoopAnalyticsSink = class {
|
|
5948
|
+
identify() {}
|
|
5949
|
+
capture() {}
|
|
5950
|
+
async shutdown() {}
|
|
5951
|
+
};
|
|
5952
|
+
//#endregion
|
|
5953
|
+
//#region src/telemetry.ts
|
|
5954
|
+
/**
|
|
5955
|
+
* CLI self-telemetry orchestration — errors (Sentry), usage analytics (PostHog).
|
|
5956
|
+
*
|
|
5957
|
+
* This module holds *no* vendor SDK import. Each concern sits behind a
|
|
5958
|
+
* Holocron-owned interface ({@link ErrorSink} / {@link AnalyticsSink}); the
|
|
5959
|
+
* concrete `SentrySink` / `PostHogSink` are the only `@sentry/node` /
|
|
5960
|
+
* `posthog-node` call sites, and a `Noop*Sink` is installed when telemetry is
|
|
5961
|
+
* off. Same seam `@theholocron/logger` uses for Pino.
|
|
5962
|
+
*
|
|
5963
|
+
* Activation is env-var + shipped-fallback, resolved inside each sink. The
|
|
5964
|
+
* single kill switch is `HOLOCRON_TELEMETRY=false` (legacy alias
|
|
5965
|
+
* `NO_HOLOCRON_TELEMETRY`); see ADR-0007 / ADR-0008.
|
|
5966
|
+
*/
|
|
5967
|
+
let errors = new NoopErrorSink();
|
|
5968
|
+
let analytics = new NoopAnalyticsSink();
|
|
5969
|
+
/**
|
|
5970
|
+
* The shared opt-out. `HOLOCRON_TELEMETRY=false` (going-forward) or
|
|
5971
|
+
* `NO_HOLOCRON_TELEMETRY` (any truthy value, back-compat) disables all sinks.
|
|
5972
|
+
*/
|
|
5855
5973
|
function isEnabled() {
|
|
5856
5974
|
return env.get("HOLOCRON_TELEMETRY") !== "false" && !env.get("NO_HOLOCRON_TELEMETRY");
|
|
5857
5975
|
}
|
|
5858
|
-
const sentryEnabled = () => isEnabled() && resolveDsn() !== "";
|
|
5859
|
-
const posthogEnabled = () => isEnabled() && resolvePostHogKey() !== "";
|
|
5860
|
-
let posthog;
|
|
5861
5976
|
let commandStartedAt = 0;
|
|
5862
5977
|
let currentCommand = "unknown";
|
|
5978
|
+
let lastErrorType;
|
|
5979
|
+
let currentSpan;
|
|
5863
5980
|
/**
|
|
5864
5981
|
* Anonymous, stable per-machine id — `sha256(hostname + username)` truncated
|
|
5865
|
-
* (ADR-0008).
|
|
5866
|
-
* transmitted, and it is not personal data under this project's rule.
|
|
5982
|
+
* (ADR-0008). One-way: no raw hostname or username is ever transmitted.
|
|
5867
5983
|
*/
|
|
5868
5984
|
function machineId() {
|
|
5869
5985
|
try {
|
|
@@ -5872,7 +5988,7 @@ function machineId() {
|
|
|
5872
5988
|
return "unknown";
|
|
5873
5989
|
}
|
|
5874
5990
|
}
|
|
5875
|
-
/** Properties every event carries
|
|
5991
|
+
/** Properties every analytics event carries — pivot key to the Axiom trace. */
|
|
5876
5992
|
function baseProps() {
|
|
5877
5993
|
const runId = getRunId();
|
|
5878
5994
|
return {
|
|
@@ -5881,76 +5997,74 @@ function baseProps() {
|
|
|
5881
5997
|
...runId ? { runId } : {}
|
|
5882
5998
|
};
|
|
5883
5999
|
}
|
|
5884
|
-
/**
|
|
5885
|
-
* Capture a PostHog event. Runs every `properties` object through the same
|
|
5886
|
-
* token scrubber Sentry uses (defence-in-depth — values are controlled).
|
|
5887
|
-
*/
|
|
5888
|
-
function event(name, properties = {}) {
|
|
5889
|
-
if (!posthogEnabled() || !posthog) return;
|
|
5890
|
-
const merged = {
|
|
5891
|
-
...baseProps(),
|
|
5892
|
-
...properties
|
|
5893
|
-
};
|
|
5894
|
-
posthog.capture({
|
|
5895
|
-
distinctId: machineId(),
|
|
5896
|
-
event: name,
|
|
5897
|
-
properties: JSON.parse(redact(JSON.stringify(merged)))
|
|
5898
|
-
});
|
|
5899
|
-
}
|
|
5900
6000
|
function init(version) {
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
}
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
Sentry.setTag("ci", String(Boolean(env.get("CI"))));
|
|
5917
|
-
}
|
|
5918
|
-
function initPostHog(version) {
|
|
5919
|
-
if (!posthogEnabled()) return;
|
|
5920
|
-
posthog = new PostHog(resolvePostHogKey(), { host: resolvePostHogHost() });
|
|
5921
|
-
posthog.identify({
|
|
5922
|
-
distinctId: machineId(),
|
|
5923
|
-
properties: {
|
|
6001
|
+
if (isEnabled() && resolveDsn() !== "") {
|
|
6002
|
+
errors = new SentrySink();
|
|
6003
|
+
errors.init({
|
|
6004
|
+
release: `holocron@${version}`,
|
|
6005
|
+
environment: env.get("CI") ? "ci" : "local",
|
|
6006
|
+
tags: {
|
|
6007
|
+
os: process.platform,
|
|
6008
|
+
node: process.version,
|
|
6009
|
+
ci: String(Boolean(env.get("CI")))
|
|
6010
|
+
}
|
|
6011
|
+
});
|
|
6012
|
+
}
|
|
6013
|
+
if (isEnabled() && resolvePostHogKey() !== "") {
|
|
6014
|
+
analytics = new PostHogSink();
|
|
6015
|
+
analytics.identify(machineId(), {
|
|
5924
6016
|
ci: Boolean(env.get("CI")),
|
|
5925
6017
|
os: process.platform,
|
|
5926
6018
|
node: process.version,
|
|
5927
6019
|
cli: version,
|
|
5928
6020
|
...env.get("HOLOCRON_ORG") ? { org: env.get("HOLOCRON_ORG") } : {}
|
|
5929
|
-
}
|
|
5930
|
-
}
|
|
6021
|
+
});
|
|
6022
|
+
}
|
|
5931
6023
|
}
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
6024
|
+
/**
|
|
6025
|
+
* Apply the `telemetry` config block — the committed override layer, run once
|
|
6026
|
+
* per command *after* config loads (so it lands after {@link init}). Env still
|
|
6027
|
+
* wins: this only ever *narrows* what `init` turned on, never re-enables.
|
|
6028
|
+
*
|
|
6029
|
+
* - `enabled: false` → swap both sinks to no-ops for the rest of the run.
|
|
6030
|
+
* - `analytics: "none"` → drop usage analytics only; error reporting stays.
|
|
6031
|
+
*
|
|
6032
|
+
* A live sink is drained best-effort before it is dropped so an in-flight
|
|
6033
|
+
* Sentry session / PostHog queue still leaves the process cleanly.
|
|
6034
|
+
*/
|
|
6035
|
+
function applyConfig(cfg) {
|
|
6036
|
+
if (!cfg) return;
|
|
6037
|
+
const drain = (p) => {
|
|
6038
|
+
p.catch(() => {});
|
|
6039
|
+
};
|
|
6040
|
+
if (cfg.enabled === false) {
|
|
6041
|
+
drain(errors.flush());
|
|
6042
|
+
drain(analytics.shutdown());
|
|
6043
|
+
errors = new NoopErrorSink();
|
|
6044
|
+
analytics = new NoopAnalyticsSink();
|
|
6045
|
+
return;
|
|
6046
|
+
}
|
|
6047
|
+
if (cfg.analytics === "none") {
|
|
6048
|
+
drain(analytics.shutdown());
|
|
6049
|
+
analytics = new NoopAnalyticsSink();
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
/** Capture a PostHog event. Every `properties` object runs through the scrubber. */
|
|
6053
|
+
function event(name, properties = {}) {
|
|
6054
|
+
analytics.capture(machineId(), name, redactObject({
|
|
6055
|
+
...baseProps(),
|
|
6056
|
+
...properties
|
|
6057
|
+
}));
|
|
5942
6058
|
}
|
|
5943
6059
|
function startCommand(name) {
|
|
5944
6060
|
currentCommand = name;
|
|
5945
6061
|
commandStartedAt = Date.now();
|
|
5946
6062
|
lastErrorType = void 0;
|
|
5947
|
-
|
|
6063
|
+
currentSpan = errors.startSpan(name);
|
|
5948
6064
|
event("command_started", { dry_run: process.argv.includes("--dry-run") });
|
|
5949
6065
|
return (ok) => {
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
span.end();
|
|
5953
|
-
}
|
|
6066
|
+
currentSpan?.setStatus(ok);
|
|
6067
|
+
currentSpan?.end();
|
|
5954
6068
|
event(ok ? "command_completed" : "command_failed", {
|
|
5955
6069
|
status: ok ? "ok" : "fail",
|
|
5956
6070
|
duration_ms: Date.now() - commandStartedAt,
|
|
@@ -5960,26 +6074,16 @@ function startCommand(name) {
|
|
|
5960
6074
|
}
|
|
5961
6075
|
function captureException(err) {
|
|
5962
6076
|
lastErrorType = err instanceof Error ? err.constructor.name : "Error";
|
|
5963
|
-
|
|
5964
|
-
Sentry.captureException(err);
|
|
6077
|
+
errors.captureException(err);
|
|
5965
6078
|
}
|
|
5966
6079
|
function endSession() {
|
|
5967
|
-
|
|
5968
|
-
Sentry.endSession();
|
|
6080
|
+
errors.endSession();
|
|
5969
6081
|
}
|
|
5970
6082
|
async function flush() {
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
}
|
|
5976
|
-
}
|
|
5977
|
-
const TOKEN_RE = /\b(ghp_|ghs_|glpat-|xoxb-|xoxp-|npm_|sk-|[A-Z][A-Z0-9_]{2,}_TOKEN[=\s])[^\s"]*/g;
|
|
5978
|
-
function redact(raw) {
|
|
5979
|
-
return raw.replace(TOKEN_RE, "[REDACTED]");
|
|
5980
|
-
}
|
|
5981
|
-
function scrubError(event, _hint) {
|
|
5982
|
-
return JSON.parse(redact(JSON.stringify(event)));
|
|
6083
|
+
await errors.flush();
|
|
6084
|
+
await analytics.shutdown();
|
|
6085
|
+
errors = new NoopErrorSink();
|
|
6086
|
+
analytics = new NoopAnalyticsSink();
|
|
5983
6087
|
}
|
|
5984
6088
|
//#endregion
|
|
5985
6089
|
//#region src/commands/sync-github.ts
|
|
@@ -6630,6 +6734,17 @@ function cliLoggerOpts(argv, resolved) {
|
|
|
6630
6734
|
org: resolveOrg(argv, resolved)
|
|
6631
6735
|
};
|
|
6632
6736
|
}
|
|
6737
|
+
/**
|
|
6738
|
+
* Fold a loaded `holocron.config` into the running process: reconfigure the
|
|
6739
|
+
* logger from `log:`, then apply the `telemetry:` override layer. Every handler
|
|
6740
|
+
* that has called `loadConfig` runs this, so a repo's `telemetry.enabled: false`
|
|
6741
|
+
* (or `analytics: "none"`) is honoured for that command. Env still wins —
|
|
6742
|
+
* `applyConfig` only narrows what `init` already turned on.
|
|
6743
|
+
*/
|
|
6744
|
+
function applyResolvedConfig(argv, resolved) {
|
|
6745
|
+
buildCliLogger(argv, cliLoggerOpts(argv, resolved));
|
|
6746
|
+
applyConfig(resolved.telemetry);
|
|
6747
|
+
}
|
|
6633
6748
|
/** Parses --token values and returns the context spread, or null on parse error (exits with code 1). */
|
|
6634
6749
|
function tokenContext(rawTokens) {
|
|
6635
6750
|
if (!rawTokens?.length) return {};
|
|
@@ -6716,7 +6831,7 @@ try {
|
|
|
6716
6831
|
const tokens = tokenContext(argv.token);
|
|
6717
6832
|
if (!tokens) return;
|
|
6718
6833
|
const loaded = await loadConfig(argv.cwd);
|
|
6719
|
-
|
|
6834
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6720
6835
|
if ((await runDoctor({
|
|
6721
6836
|
loaded,
|
|
6722
6837
|
context: {
|
|
@@ -6737,7 +6852,7 @@ try {
|
|
|
6737
6852
|
const tokens = tokenContext(argv.token);
|
|
6738
6853
|
if (!tokens) return;
|
|
6739
6854
|
const loaded = await loadConfig(argv.cwd);
|
|
6740
|
-
|
|
6855
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6741
6856
|
if ((await runSetup({
|
|
6742
6857
|
loaded,
|
|
6743
6858
|
context: {
|
|
@@ -6804,7 +6919,7 @@ try {
|
|
|
6804
6919
|
const scopeArg = argv.scope;
|
|
6805
6920
|
const scope = parseScope(scopeArg);
|
|
6806
6921
|
const loaded = await loadConfig(argv.cwd);
|
|
6807
|
-
|
|
6922
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6808
6923
|
if ((await runSecretSet({
|
|
6809
6924
|
loaded,
|
|
6810
6925
|
context: {
|
|
@@ -6834,7 +6949,7 @@ try {
|
|
|
6834
6949
|
const tokens = tokenContext(argv.token);
|
|
6835
6950
|
if (!tokens) return;
|
|
6836
6951
|
const loaded = await loadConfig(argv.cwd);
|
|
6837
|
-
|
|
6952
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6838
6953
|
if ((await runSecretsSync({
|
|
6839
6954
|
loaded,
|
|
6840
6955
|
context: {
|
|
@@ -6863,7 +6978,7 @@ try {
|
|
|
6863
6978
|
const tokens = tokenContext(argv.token);
|
|
6864
6979
|
if (!tokens) return;
|
|
6865
6980
|
const loaded = await loadConfig(argv.cwd);
|
|
6866
|
-
|
|
6981
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6867
6982
|
if ((await runDeploy({
|
|
6868
6983
|
loaded,
|
|
6869
6984
|
context: {
|
|
@@ -6891,7 +7006,7 @@ try {
|
|
|
6891
7006
|
const tokens = tokenContext(argv.token);
|
|
6892
7007
|
if (!tokens) return;
|
|
6893
7008
|
const loaded = await loadConfig(argv.cwd);
|
|
6894
|
-
|
|
7009
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6895
7010
|
if ((await runCleanupPreview({
|
|
6896
7011
|
loaded,
|
|
6897
7012
|
context: {
|
|
@@ -6939,7 +7054,7 @@ try {
|
|
|
6939
7054
|
const tokens = tokenContext(argv.token);
|
|
6940
7055
|
if (!tokens) return;
|
|
6941
7056
|
const loaded = await loadConfig(argv.cwd);
|
|
6942
|
-
|
|
7057
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6943
7058
|
if ((await runSync({
|
|
6944
7059
|
loaded,
|
|
6945
7060
|
context: {
|
|
@@ -7048,7 +7163,7 @@ try {
|
|
|
7048
7163
|
default: false
|
|
7049
7164
|
}), async (argv) => {
|
|
7050
7165
|
const loaded = await loadConfig(argv.cwd);
|
|
7051
|
-
|
|
7166
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
7052
7167
|
if ((await runSyncReadme({
|
|
7053
7168
|
loaded,
|
|
7054
7169
|
context: {
|