@theholocron/cli 4.13.0 → 4.14.1
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 +247 -106
- 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,185 @@ 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` call site. A portable
|
|
5827
|
+
* `AnalyticsSink` adapter: usage, adoption, retention. It reads no environment
|
|
5828
|
+
* and ships no credentials — the constructor takes a resolved `key` + `host`;
|
|
5829
|
+
* the caller (the CLI's `telemetry.ts`, via `telemetry/resolve.ts`) owns the
|
|
5830
|
+
* env chain and the fallback. An empty `key` is the caller's signal to install
|
|
5831
|
+
* a `NoopAnalyticsSink` instead — it is never passed here.
|
|
5828
5832
|
*
|
|
5829
|
-
*
|
|
5830
|
-
*
|
|
5831
|
-
* A consumer repo with `SENTRY_DSN` set gets CLI errors routed to its own
|
|
5832
|
-
* project with no config. Opt out entirely with `HOLOCRON_TELEMETRY=false`.
|
|
5833
|
+
* Destined for `@theholocron/observability/analytics` (#635).
|
|
5833
5834
|
*/
|
|
5834
|
-
|
|
5835
|
-
|
|
5835
|
+
var PostHogSink = class {
|
|
5836
|
+
#client;
|
|
5837
|
+
constructor(config) {
|
|
5838
|
+
this.#client = new PostHog(config.key, { host: config.host });
|
|
5839
|
+
}
|
|
5840
|
+
identify(distinctId, props) {
|
|
5841
|
+
this.#client.identify({
|
|
5842
|
+
distinctId,
|
|
5843
|
+
properties: props
|
|
5844
|
+
});
|
|
5845
|
+
}
|
|
5846
|
+
capture(distinctId, event, props) {
|
|
5847
|
+
this.#client.capture({
|
|
5848
|
+
distinctId,
|
|
5849
|
+
event,
|
|
5850
|
+
properties: props
|
|
5851
|
+
});
|
|
5852
|
+
}
|
|
5853
|
+
async shutdown() {
|
|
5854
|
+
await this.#client.shutdown();
|
|
5855
|
+
}
|
|
5856
|
+
};
|
|
5857
|
+
//#endregion
|
|
5858
|
+
//#region src/telemetry/redact.ts
|
|
5859
|
+
/**
|
|
5860
|
+
* Token-shape scrubbing for telemetry payloads — a raw token never leaves the
|
|
5861
|
+
* process, even when a value is controlled. Regex-on-the-serialised-string
|
|
5862
|
+
* (matches a token shape *anywhere*), distinct from `@theholocron/logger`'s
|
|
5863
|
+
* field-path redaction. Shared by {@link scrubError} (Sentry `beforeSend`) and
|
|
5864
|
+
* the PostHog `event()` path.
|
|
5865
|
+
*/
|
|
5866
|
+
const TOKEN_RE = /\b(ghp_|ghs_|glpat-|xoxb-|xoxp-|npm_|sk-|[A-Z][A-Z0-9_]{2,}_TOKEN[=\s])[^\s"]*/g;
|
|
5867
|
+
/** Replace token-shaped substrings in a string with `[REDACTED]`. */
|
|
5868
|
+
function redact(raw) {
|
|
5869
|
+
return raw.replace(TOKEN_RE, "[REDACTED]");
|
|
5836
5870
|
}
|
|
5837
|
-
|
|
5838
|
-
|
|
5871
|
+
/** Deep-scrub every string in an object by round-tripping through {@link redact}. */
|
|
5872
|
+
function redactObject(value) {
|
|
5873
|
+
return JSON.parse(redact(JSON.stringify(value)));
|
|
5874
|
+
}
|
|
5875
|
+
//#endregion
|
|
5876
|
+
//#region src/telemetry/resolve.ts
|
|
5839
5877
|
/**
|
|
5840
|
-
*
|
|
5841
|
-
*
|
|
5878
|
+
* Holocron-specific credential resolution for the telemetry sinks — the env
|
|
5879
|
+
* chain plus Holocron's own shipped fallbacks. This is **CLI policy**, not part
|
|
5880
|
+
* of the portable adapter set: `sentry-sink.ts` / `posthog-sink.ts` take a
|
|
5881
|
+
* resolved DSN / key and never read the environment. When the adapters move to
|
|
5882
|
+
* `@theholocron/observability` (#635) this module stays here.
|
|
5842
5883
|
*
|
|
5843
|
-
*
|
|
5884
|
+
* errors: HOLOCRON_SENTRY_DSN → SENTRY_DSN → FALLBACK_DSN
|
|
5885
|
+
* analytics: HOLOCRON_POSTHOG_PROJECT_TOKEN → POSTHOG_PROJECT_TOKEN → FALLBACK_POSTHOG_PROJECT_TOKEN
|
|
5886
|
+
* host: HOLOCRON_POSTHOG_HOST → POSTHOG_HOST → DEFAULT_POSTHOG_HOST
|
|
5844
5887
|
*
|
|
5845
|
-
* `POSTHOG_PROJECT_TOKEN`
|
|
5846
|
-
*
|
|
5847
|
-
*
|
|
5888
|
+
* The `SENTRY_DSN` / `POSTHOG_PROJECT_TOKEN` fallbacks are the vendor-native
|
|
5889
|
+
* names a consumer repo sets to route CLI telemetry to its own project. The
|
|
5890
|
+
* built-in fallbacks are ingest-only keys — publishable by design, same risk
|
|
5891
|
+
* model as ADR-0007 / ADR-0008.
|
|
5848
5892
|
*/
|
|
5893
|
+
/** Holocron's own Sentry project — the fallback when no env var points elsewhere. */
|
|
5894
|
+
const FALLBACK_DSN = "https://95cbb72ad5636c94e119a5405ee8f55f@o4508238154104832.ingest.us.sentry.io/4511810950791168";
|
|
5895
|
+
/** Holocron's own PostHog project write key — ingest-only. */
|
|
5896
|
+
const FALLBACK_POSTHOG_PROJECT_TOKEN = "phc_AC4vFCvYzwnzmG7Vg5nEc3PZKZztoPyfKp4Lb9BbXcLK";
|
|
5897
|
+
const DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com";
|
|
5898
|
+
/** Resolve the Sentry DSN. `""` → error telemetry disabled. */
|
|
5899
|
+
function resolveDsn() {
|
|
5900
|
+
return env.get("HOLOCRON_SENTRY_DSN") ?? env.get("SENTRY_DSN") ?? FALLBACK_DSN;
|
|
5901
|
+
}
|
|
5902
|
+
/** Resolve the PostHog project key. `""` → usage analytics disabled. */
|
|
5849
5903
|
function resolvePostHogKey() {
|
|
5850
5904
|
return env.get("HOLOCRON_POSTHOG_PROJECT_TOKEN") ?? env.get("POSTHOG_PROJECT_TOKEN") ?? FALLBACK_POSTHOG_PROJECT_TOKEN;
|
|
5851
5905
|
}
|
|
5906
|
+
/** Resolve the PostHog ingest host. */
|
|
5852
5907
|
function resolvePostHogHost() {
|
|
5853
5908
|
return env.get("HOLOCRON_POSTHOG_HOST") ?? env.get("POSTHOG_HOST") ?? DEFAULT_POSTHOG_HOST;
|
|
5854
5909
|
}
|
|
5910
|
+
//#endregion
|
|
5911
|
+
//#region src/telemetry/sentry-sink.ts
|
|
5912
|
+
/** `beforeSend` — token-shaped strings never leave the process. */
|
|
5913
|
+
function scrubError(event, _hint) {
|
|
5914
|
+
return redactObject(event);
|
|
5915
|
+
}
|
|
5916
|
+
var SentrySink = class {
|
|
5917
|
+
init(ctx) {
|
|
5918
|
+
Sentry.init({
|
|
5919
|
+
dsn: ctx.dsn,
|
|
5920
|
+
release: ctx.release,
|
|
5921
|
+
environment: ctx.environment,
|
|
5922
|
+
tracesSampleRate: 1,
|
|
5923
|
+
beforeSend: scrubError
|
|
5924
|
+
});
|
|
5925
|
+
Sentry.startSession();
|
|
5926
|
+
for (const [key, value] of Object.entries(ctx.tags)) Sentry.setTag(key, value);
|
|
5927
|
+
}
|
|
5928
|
+
startSpan(name) {
|
|
5929
|
+
Sentry.setTag("command", name);
|
|
5930
|
+
const span = Sentry.startInactiveSpan({
|
|
5931
|
+
name,
|
|
5932
|
+
op: "holocron.command",
|
|
5933
|
+
forceTransaction: true
|
|
5934
|
+
});
|
|
5935
|
+
return {
|
|
5936
|
+
setStatus: (ok) => span.setStatus({ code: ok ? 1 : 2 }),
|
|
5937
|
+
end: () => span.end()
|
|
5938
|
+
};
|
|
5939
|
+
}
|
|
5940
|
+
captureException(err) {
|
|
5941
|
+
Sentry.captureException(err);
|
|
5942
|
+
}
|
|
5943
|
+
endSession() {
|
|
5944
|
+
Sentry.endSession();
|
|
5945
|
+
}
|
|
5946
|
+
async flush() {
|
|
5947
|
+
await Sentry.close(2e3);
|
|
5948
|
+
}
|
|
5949
|
+
};
|
|
5950
|
+
//#endregion
|
|
5951
|
+
//#region src/telemetry/sinks.ts
|
|
5952
|
+
/**
|
|
5953
|
+
* The disabled path made explicit — one shared no-op instead of a scatter of
|
|
5954
|
+
* `if (!enabled) return` guards. Used when telemetry is opted out
|
|
5955
|
+
* (`HOLOCRON_TELEMETRY=false`, `config.telemetry.enabled: false`) or no
|
|
5956
|
+
* credentials resolve.
|
|
5957
|
+
*/
|
|
5958
|
+
var NoopErrorSink = class {
|
|
5959
|
+
init() {}
|
|
5960
|
+
startSpan() {}
|
|
5961
|
+
captureException() {}
|
|
5962
|
+
endSession() {}
|
|
5963
|
+
async flush() {}
|
|
5964
|
+
};
|
|
5965
|
+
/** @see NoopErrorSink */
|
|
5966
|
+
var NoopAnalyticsSink = class {
|
|
5967
|
+
identify() {}
|
|
5968
|
+
capture() {}
|
|
5969
|
+
async shutdown() {}
|
|
5970
|
+
};
|
|
5971
|
+
//#endregion
|
|
5972
|
+
//#region src/telemetry.ts
|
|
5973
|
+
/**
|
|
5974
|
+
* CLI self-telemetry orchestration — errors (Sentry), usage analytics (PostHog).
|
|
5975
|
+
*
|
|
5976
|
+
* This module holds *no* vendor SDK import. Each concern sits behind a
|
|
5977
|
+
* Holocron-owned interface ({@link ErrorSink} / {@link AnalyticsSink}); the
|
|
5978
|
+
* concrete `SentrySink` / `PostHogSink` are the only `@sentry/node` /
|
|
5979
|
+
* `posthog-node` call sites, and a `Noop*Sink` is installed when telemetry is
|
|
5980
|
+
* off. Same seam `@theholocron/logger` uses for Pino.
|
|
5981
|
+
*
|
|
5982
|
+
* Activation is env-var + shipped-fallback, resolved here (`telemetry/resolve.ts`)
|
|
5983
|
+
* and passed to the sink — the adapters read no environment. The single kill
|
|
5984
|
+
* switch is `HOLOCRON_TELEMETRY=false` (legacy alias `NO_HOLOCRON_TELEMETRY`);
|
|
5985
|
+
* see ADR-0007 / ADR-0008.
|
|
5986
|
+
*/
|
|
5987
|
+
let errors = new NoopErrorSink();
|
|
5988
|
+
let analytics = new NoopAnalyticsSink();
|
|
5989
|
+
/**
|
|
5990
|
+
* The shared opt-out. `HOLOCRON_TELEMETRY=false` (going-forward) or
|
|
5991
|
+
* `NO_HOLOCRON_TELEMETRY` (any truthy value, back-compat) disables all sinks.
|
|
5992
|
+
*/
|
|
5855
5993
|
function isEnabled() {
|
|
5856
5994
|
return env.get("HOLOCRON_TELEMETRY") !== "false" && !env.get("NO_HOLOCRON_TELEMETRY");
|
|
5857
5995
|
}
|
|
5858
|
-
const sentryEnabled = () => isEnabled() && resolveDsn() !== "";
|
|
5859
|
-
const posthogEnabled = () => isEnabled() && resolvePostHogKey() !== "";
|
|
5860
|
-
let posthog;
|
|
5861
5996
|
let commandStartedAt = 0;
|
|
5862
5997
|
let currentCommand = "unknown";
|
|
5998
|
+
let lastErrorType;
|
|
5999
|
+
let currentSpan;
|
|
5863
6000
|
/**
|
|
5864
6001
|
* 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.
|
|
6002
|
+
* (ADR-0008). One-way: no raw hostname or username is ever transmitted.
|
|
5867
6003
|
*/
|
|
5868
6004
|
function machineId() {
|
|
5869
6005
|
try {
|
|
@@ -5872,7 +6008,7 @@ function machineId() {
|
|
|
5872
6008
|
return "unknown";
|
|
5873
6009
|
}
|
|
5874
6010
|
}
|
|
5875
|
-
/** Properties every event carries
|
|
6011
|
+
/** Properties every analytics event carries — pivot key to the Axiom trace. */
|
|
5876
6012
|
function baseProps() {
|
|
5877
6013
|
const runId = getRunId();
|
|
5878
6014
|
return {
|
|
@@ -5881,76 +6017,80 @@ function baseProps() {
|
|
|
5881
6017
|
...runId ? { runId } : {}
|
|
5882
6018
|
};
|
|
5883
6019
|
}
|
|
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
6020
|
function init(version) {
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
distinctId: machineId(),
|
|
5923
|
-
properties: {
|
|
6021
|
+
const dsn = resolveDsn();
|
|
6022
|
+
if (isEnabled() && dsn !== "") {
|
|
6023
|
+
errors = new SentrySink();
|
|
6024
|
+
errors.init({
|
|
6025
|
+
dsn,
|
|
6026
|
+
release: `holocron@${version}`,
|
|
6027
|
+
environment: env.get("CI") ? "ci" : "local",
|
|
6028
|
+
tags: {
|
|
6029
|
+
os: process.platform,
|
|
6030
|
+
node: process.version,
|
|
6031
|
+
ci: String(Boolean(env.get("CI")))
|
|
6032
|
+
}
|
|
6033
|
+
});
|
|
6034
|
+
}
|
|
6035
|
+
const posthogKey = resolvePostHogKey();
|
|
6036
|
+
if (isEnabled() && posthogKey !== "") {
|
|
6037
|
+
analytics = new PostHogSink({
|
|
6038
|
+
key: posthogKey,
|
|
6039
|
+
host: resolvePostHogHost()
|
|
6040
|
+
});
|
|
6041
|
+
analytics.identify(machineId(), {
|
|
5924
6042
|
ci: Boolean(env.get("CI")),
|
|
5925
6043
|
os: process.platform,
|
|
5926
6044
|
node: process.version,
|
|
5927
6045
|
cli: version,
|
|
5928
6046
|
...env.get("HOLOCRON_ORG") ? { org: env.get("HOLOCRON_ORG") } : {}
|
|
5929
|
-
}
|
|
5930
|
-
}
|
|
6047
|
+
});
|
|
6048
|
+
}
|
|
5931
6049
|
}
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
6050
|
+
/**
|
|
6051
|
+
* Apply the `telemetry` config block — the committed override layer, run once
|
|
6052
|
+
* per command *after* config loads (so it lands after {@link init}). Env still
|
|
6053
|
+
* wins: this only ever *narrows* what `init` turned on, never re-enables.
|
|
6054
|
+
*
|
|
6055
|
+
* - `enabled: false` → swap both sinks to no-ops for the rest of the run.
|
|
6056
|
+
* - `analytics: "none"` → drop usage analytics only; error reporting stays.
|
|
6057
|
+
*
|
|
6058
|
+
* A live sink is drained best-effort before it is dropped so an in-flight
|
|
6059
|
+
* Sentry session / PostHog queue still leaves the process cleanly.
|
|
6060
|
+
*/
|
|
6061
|
+
function applyConfig(cfg) {
|
|
6062
|
+
if (!cfg) return;
|
|
6063
|
+
const drain = (p) => {
|
|
6064
|
+
p.catch(() => {});
|
|
6065
|
+
};
|
|
6066
|
+
if (cfg.enabled === false) {
|
|
6067
|
+
drain(errors.flush());
|
|
6068
|
+
drain(analytics.shutdown());
|
|
6069
|
+
errors = new NoopErrorSink();
|
|
6070
|
+
analytics = new NoopAnalyticsSink();
|
|
6071
|
+
return;
|
|
6072
|
+
}
|
|
6073
|
+
if (cfg.analytics === "none") {
|
|
6074
|
+
drain(analytics.shutdown());
|
|
6075
|
+
analytics = new NoopAnalyticsSink();
|
|
6076
|
+
}
|
|
6077
|
+
}
|
|
6078
|
+
/** Capture a PostHog event. Every `properties` object runs through the scrubber. */
|
|
6079
|
+
function event(name, properties = {}) {
|
|
6080
|
+
analytics.capture(machineId(), name, redactObject({
|
|
6081
|
+
...baseProps(),
|
|
6082
|
+
...properties
|
|
6083
|
+
}));
|
|
5942
6084
|
}
|
|
5943
6085
|
function startCommand(name) {
|
|
5944
6086
|
currentCommand = name;
|
|
5945
6087
|
commandStartedAt = Date.now();
|
|
5946
6088
|
lastErrorType = void 0;
|
|
5947
|
-
|
|
6089
|
+
currentSpan = errors.startSpan(name);
|
|
5948
6090
|
event("command_started", { dry_run: process.argv.includes("--dry-run") });
|
|
5949
6091
|
return (ok) => {
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
span.end();
|
|
5953
|
-
}
|
|
6092
|
+
currentSpan?.setStatus(ok);
|
|
6093
|
+
currentSpan?.end();
|
|
5954
6094
|
event(ok ? "command_completed" : "command_failed", {
|
|
5955
6095
|
status: ok ? "ok" : "fail",
|
|
5956
6096
|
duration_ms: Date.now() - commandStartedAt,
|
|
@@ -5960,26 +6100,16 @@ function startCommand(name) {
|
|
|
5960
6100
|
}
|
|
5961
6101
|
function captureException(err) {
|
|
5962
6102
|
lastErrorType = err instanceof Error ? err.constructor.name : "Error";
|
|
5963
|
-
|
|
5964
|
-
Sentry.captureException(err);
|
|
6103
|
+
errors.captureException(err);
|
|
5965
6104
|
}
|
|
5966
6105
|
function endSession() {
|
|
5967
|
-
|
|
5968
|
-
Sentry.endSession();
|
|
6106
|
+
errors.endSession();
|
|
5969
6107
|
}
|
|
5970
6108
|
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)));
|
|
6109
|
+
await errors.flush();
|
|
6110
|
+
await analytics.shutdown();
|
|
6111
|
+
errors = new NoopErrorSink();
|
|
6112
|
+
analytics = new NoopAnalyticsSink();
|
|
5983
6113
|
}
|
|
5984
6114
|
//#endregion
|
|
5985
6115
|
//#region src/commands/sync-github.ts
|
|
@@ -6630,6 +6760,17 @@ function cliLoggerOpts(argv, resolved) {
|
|
|
6630
6760
|
org: resolveOrg(argv, resolved)
|
|
6631
6761
|
};
|
|
6632
6762
|
}
|
|
6763
|
+
/**
|
|
6764
|
+
* Fold a loaded `holocron.config` into the running process: reconfigure the
|
|
6765
|
+
* logger from `log:`, then apply the `telemetry:` override layer. Every handler
|
|
6766
|
+
* that has called `loadConfig` runs this, so a repo's `telemetry.enabled: false`
|
|
6767
|
+
* (or `analytics: "none"`) is honoured for that command. Env still wins —
|
|
6768
|
+
* `applyConfig` only narrows what `init` already turned on.
|
|
6769
|
+
*/
|
|
6770
|
+
function applyResolvedConfig(argv, resolved) {
|
|
6771
|
+
buildCliLogger(argv, cliLoggerOpts(argv, resolved));
|
|
6772
|
+
applyConfig(resolved.telemetry);
|
|
6773
|
+
}
|
|
6633
6774
|
/** Parses --token values and returns the context spread, or null on parse error (exits with code 1). */
|
|
6634
6775
|
function tokenContext(rawTokens) {
|
|
6635
6776
|
if (!rawTokens?.length) return {};
|
|
@@ -6716,7 +6857,7 @@ try {
|
|
|
6716
6857
|
const tokens = tokenContext(argv.token);
|
|
6717
6858
|
if (!tokens) return;
|
|
6718
6859
|
const loaded = await loadConfig(argv.cwd);
|
|
6719
|
-
|
|
6860
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6720
6861
|
if ((await runDoctor({
|
|
6721
6862
|
loaded,
|
|
6722
6863
|
context: {
|
|
@@ -6737,7 +6878,7 @@ try {
|
|
|
6737
6878
|
const tokens = tokenContext(argv.token);
|
|
6738
6879
|
if (!tokens) return;
|
|
6739
6880
|
const loaded = await loadConfig(argv.cwd);
|
|
6740
|
-
|
|
6881
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6741
6882
|
if ((await runSetup({
|
|
6742
6883
|
loaded,
|
|
6743
6884
|
context: {
|
|
@@ -6804,7 +6945,7 @@ try {
|
|
|
6804
6945
|
const scopeArg = argv.scope;
|
|
6805
6946
|
const scope = parseScope(scopeArg);
|
|
6806
6947
|
const loaded = await loadConfig(argv.cwd);
|
|
6807
|
-
|
|
6948
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6808
6949
|
if ((await runSecretSet({
|
|
6809
6950
|
loaded,
|
|
6810
6951
|
context: {
|
|
@@ -6834,7 +6975,7 @@ try {
|
|
|
6834
6975
|
const tokens = tokenContext(argv.token);
|
|
6835
6976
|
if (!tokens) return;
|
|
6836
6977
|
const loaded = await loadConfig(argv.cwd);
|
|
6837
|
-
|
|
6978
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6838
6979
|
if ((await runSecretsSync({
|
|
6839
6980
|
loaded,
|
|
6840
6981
|
context: {
|
|
@@ -6863,7 +7004,7 @@ try {
|
|
|
6863
7004
|
const tokens = tokenContext(argv.token);
|
|
6864
7005
|
if (!tokens) return;
|
|
6865
7006
|
const loaded = await loadConfig(argv.cwd);
|
|
6866
|
-
|
|
7007
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6867
7008
|
if ((await runDeploy({
|
|
6868
7009
|
loaded,
|
|
6869
7010
|
context: {
|
|
@@ -6891,7 +7032,7 @@ try {
|
|
|
6891
7032
|
const tokens = tokenContext(argv.token);
|
|
6892
7033
|
if (!tokens) return;
|
|
6893
7034
|
const loaded = await loadConfig(argv.cwd);
|
|
6894
|
-
|
|
7035
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6895
7036
|
if ((await runCleanupPreview({
|
|
6896
7037
|
loaded,
|
|
6897
7038
|
context: {
|
|
@@ -6939,7 +7080,7 @@ try {
|
|
|
6939
7080
|
const tokens = tokenContext(argv.token);
|
|
6940
7081
|
if (!tokens) return;
|
|
6941
7082
|
const loaded = await loadConfig(argv.cwd);
|
|
6942
|
-
|
|
7083
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
6943
7084
|
if ((await runSync({
|
|
6944
7085
|
loaded,
|
|
6945
7086
|
context: {
|
|
@@ -7048,7 +7189,7 @@ try {
|
|
|
7048
7189
|
default: false
|
|
7049
7190
|
}), async (argv) => {
|
|
7050
7191
|
const loaded = await loadConfig(argv.cwd);
|
|
7051
|
-
|
|
7192
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
7052
7193
|
if ((await runSyncReadme({
|
|
7053
7194
|
loaded,
|
|
7054
7195
|
context: {
|