crumbtrail 0.1.0 → 0.1.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/dist/{chunk-UFJ2BODB.js → chunk-HAP4DPWC.js} +24 -2
- package/dist/cli.cjs +171 -26
- package/dist/cli.d.cts +34 -2
- package/dist/cli.d.ts +34 -2
- package/dist/cli.js +149 -26
- package/dist/{executor-DKDrkhxM.d.cts → executor-BKiOvIvD.d.cts} +9 -4
- package/dist/{executor-DKDrkhxM.d.ts → executor-BKiOvIvD.d.ts} +9 -4
- package/dist/index.cjs +25 -2
- package/dist/index.d.cts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +3 -1
- package/package.json +7 -7
|
@@ -2333,6 +2333,9 @@ function resolveReactNativeEntry(cwd) {
|
|
|
2333
2333
|
path.join("app", "_layout.tsx"),
|
|
2334
2334
|
path.join("app", "_layout.jsx"),
|
|
2335
2335
|
path.join("app", "_layout.js"),
|
|
2336
|
+
path.join("src", "app", "_layout.tsx"),
|
|
2337
|
+
path.join("src", "app", "_layout.jsx"),
|
|
2338
|
+
path.join("src", "app", "_layout.js"),
|
|
2336
2339
|
"App.tsx",
|
|
2337
2340
|
"App.jsx",
|
|
2338
2341
|
"App.ts",
|
|
@@ -2812,6 +2815,20 @@ function nodeInitSnippet(endpoint) {
|
|
|
2812
2815
|
" .catch(() => {});"
|
|
2813
2816
|
].join("\n");
|
|
2814
2817
|
}
|
|
2818
|
+
function singleQuoted(value) {
|
|
2819
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
2820
|
+
}
|
|
2821
|
+
function nestInitSnippet(endpoint) {
|
|
2822
|
+
return [
|
|
2823
|
+
"// Crumbtrail \u2014 auto-captures uncaught exceptions, unhandled rejections, and",
|
|
2824
|
+
"// console.error. Key is read from process.env.CRUMBTRAIL_KEY, which autoCapture",
|
|
2825
|
+
"// loads from your .env (written by the CLI). Express apps can also add",
|
|
2826
|
+
"// createCrumbtrailExpressMiddleware for per-request capture.",
|
|
2827
|
+
"import('crumbtrail-node')",
|
|
2828
|
+
` .then(({ autoCapture }) => autoCapture({ endpoint: ${singleQuoted(endpoint)} }))`,
|
|
2829
|
+
" .catch(() => {});"
|
|
2830
|
+
].join("\n");
|
|
2831
|
+
}
|
|
2815
2832
|
function reactNativeInitSnippet(endpoint, apiKey) {
|
|
2816
2833
|
return [
|
|
2817
2834
|
'import { createReactNativeCrumbtrail } from "crumbtrail-react-native";',
|
|
@@ -2843,10 +2860,14 @@ import path2 from "path";
|
|
|
2843
2860
|
function realGitStatus(cwd, target) {
|
|
2844
2861
|
let out;
|
|
2845
2862
|
try {
|
|
2863
|
+
const env = Object.fromEntries(
|
|
2864
|
+
Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_"))
|
|
2865
|
+
);
|
|
2846
2866
|
out = execFileSync("git", ["status", "--porcelain", "--", target], {
|
|
2847
2867
|
cwd,
|
|
2848
2868
|
encoding: "utf8",
|
|
2849
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2869
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2870
|
+
env
|
|
2850
2871
|
});
|
|
2851
2872
|
} catch {
|
|
2852
2873
|
return { isRepo: false, tracked: false, dirty: false };
|
|
@@ -7027,7 +7048,7 @@ function planVite(input, io) {
|
|
|
7027
7048
|
return prependWithPreflight(input, io, input.entryFile, block);
|
|
7028
7049
|
}
|
|
7029
7050
|
function planNode(input, io) {
|
|
7030
|
-
const block = nodeInitSnippet(input.endpoint);
|
|
7051
|
+
const block = input.recipe === "nestjs" ? nestInitSnippet(input.endpoint) : nodeInitSnippet(input.endpoint);
|
|
7031
7052
|
const { action, warning } = buildEnvAction(input.cwd, input.apiKey, io);
|
|
7032
7053
|
const envWarnings = warning ? [warning] : [];
|
|
7033
7054
|
if (!input.entryFile) {
|
|
@@ -7320,6 +7341,7 @@ export {
|
|
|
7320
7341
|
clientInitSnippet,
|
|
7321
7342
|
nuxtPluginSnippet,
|
|
7322
7343
|
nodeInitSnippet,
|
|
7344
|
+
nestInitSnippet,
|
|
7323
7345
|
reactNativeInitSnippet,
|
|
7324
7346
|
tauriInitSnippet,
|
|
7325
7347
|
envKeyLine,
|
package/dist/cli.cjs
CHANGED
|
@@ -2182,6 +2182,7 @@ __export(cli_exports, {
|
|
|
2182
2182
|
isCliEntrypoint: () => isCliEntrypoint,
|
|
2183
2183
|
parseArgs: () => parseArgs,
|
|
2184
2184
|
resolveSelection: () => resolveSelection,
|
|
2185
|
+
resolveWorkspaceDir: () => resolveWorkspaceDir,
|
|
2185
2186
|
runBatchWizard: () => runBatchWizard,
|
|
2186
2187
|
runCli: () => runCli,
|
|
2187
2188
|
runWizard: () => runWizard
|
|
@@ -2357,6 +2358,9 @@ function resolveReactNativeEntry(cwd) {
|
|
|
2357
2358
|
import_node_path.default.join("app", "_layout.tsx"),
|
|
2358
2359
|
import_node_path.default.join("app", "_layout.jsx"),
|
|
2359
2360
|
import_node_path.default.join("app", "_layout.js"),
|
|
2361
|
+
import_node_path.default.join("src", "app", "_layout.tsx"),
|
|
2362
|
+
import_node_path.default.join("src", "app", "_layout.jsx"),
|
|
2363
|
+
import_node_path.default.join("src", "app", "_layout.js"),
|
|
2360
2364
|
"App.tsx",
|
|
2361
2365
|
"App.jsx",
|
|
2362
2366
|
"App.ts",
|
|
@@ -2836,6 +2840,20 @@ function nodeInitSnippet(endpoint) {
|
|
|
2836
2840
|
" .catch(() => {});"
|
|
2837
2841
|
].join("\n");
|
|
2838
2842
|
}
|
|
2843
|
+
function singleQuoted(value) {
|
|
2844
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
2845
|
+
}
|
|
2846
|
+
function nestInitSnippet(endpoint) {
|
|
2847
|
+
return [
|
|
2848
|
+
"// Crumbtrail \u2014 auto-captures uncaught exceptions, unhandled rejections, and",
|
|
2849
|
+
"// console.error. Key is read from process.env.CRUMBTRAIL_KEY, which autoCapture",
|
|
2850
|
+
"// loads from your .env (written by the CLI). Express apps can also add",
|
|
2851
|
+
"// createCrumbtrailExpressMiddleware for per-request capture.",
|
|
2852
|
+
"import('crumbtrail-node')",
|
|
2853
|
+
` .then(({ autoCapture }) => autoCapture({ endpoint: ${singleQuoted(endpoint)} }))`,
|
|
2854
|
+
" .catch(() => {});"
|
|
2855
|
+
].join("\n");
|
|
2856
|
+
}
|
|
2839
2857
|
function reactNativeInitSnippet(endpoint, apiKey) {
|
|
2840
2858
|
return [
|
|
2841
2859
|
'import { createReactNativeCrumbtrail } from "crumbtrail-react-native";',
|
|
@@ -2867,10 +2885,14 @@ var import_node_path2 = __toESM(require("path"), 1);
|
|
|
2867
2885
|
function realGitStatus(cwd, target) {
|
|
2868
2886
|
let out;
|
|
2869
2887
|
try {
|
|
2888
|
+
const env = Object.fromEntries(
|
|
2889
|
+
Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_"))
|
|
2890
|
+
);
|
|
2870
2891
|
out = (0, import_node_child_process.execFileSync)("git", ["status", "--porcelain", "--", target], {
|
|
2871
2892
|
cwd,
|
|
2872
2893
|
encoding: "utf8",
|
|
2873
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2894
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2895
|
+
env
|
|
2874
2896
|
});
|
|
2875
2897
|
} catch {
|
|
2876
2898
|
return { isRepo: false, tracked: false, dirty: false };
|
|
@@ -7051,7 +7073,7 @@ function planVite(input, io) {
|
|
|
7051
7073
|
return prependWithPreflight(input, io, input.entryFile, block);
|
|
7052
7074
|
}
|
|
7053
7075
|
function planNode(input, io) {
|
|
7054
|
-
const block = nodeInitSnippet(input.endpoint);
|
|
7076
|
+
const block = input.recipe === "nestjs" ? nestInitSnippet(input.endpoint) : nodeInitSnippet(input.endpoint);
|
|
7055
7077
|
const { action, warning } = buildEnvAction(input.cwd, input.apiKey, io);
|
|
7056
7078
|
const envWarnings = warning ? [warning] : [];
|
|
7057
7079
|
if (!input.entryFile) {
|
|
@@ -7325,7 +7347,7 @@ var import_node_os = __toESM(require("os"), 1);
|
|
|
7325
7347
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
7326
7348
|
|
|
7327
7349
|
// src/net.ts
|
|
7328
|
-
var DEFAULT_ENDPOINT = "https://crumbtrail
|
|
7350
|
+
var DEFAULT_ENDPOINT = "https://app.crumbtrail.dev";
|
|
7329
7351
|
function normalizeBase(base2) {
|
|
7330
7352
|
return base2.replace(/\/+$/, "");
|
|
7331
7353
|
}
|
|
@@ -7780,6 +7802,7 @@ async function validateToken(base2, token, fetchImpl) {
|
|
|
7780
7802
|
throw err;
|
|
7781
7803
|
}
|
|
7782
7804
|
}
|
|
7805
|
+
var TOKEN_ENV_VAR = "CRUMBTRAIL_TOKEN";
|
|
7783
7806
|
var sleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
|
|
7784
7807
|
async function loginBrowser(opts) {
|
|
7785
7808
|
const { verifier, challenge } = pkcePair();
|
|
@@ -7876,6 +7899,17 @@ async function login(opts) {
|
|
|
7876
7899
|
}
|
|
7877
7900
|
async function ensureToken(opts) {
|
|
7878
7901
|
const env = opts.env ?? process.env;
|
|
7902
|
+
const envToken = env[TOKEN_ENV_VAR]?.trim();
|
|
7903
|
+
if (envToken) {
|
|
7904
|
+
const state = await validateToken(opts.base, envToken, opts.fetchImpl);
|
|
7905
|
+
if (state === "valid") {
|
|
7906
|
+
opts.ui.out(color.dim(`Using ${TOKEN_ENV_VAR} from the environment.`));
|
|
7907
|
+
return envToken;
|
|
7908
|
+
}
|
|
7909
|
+
throw new Error(
|
|
7910
|
+
`${TOKEN_ENV_VAR} was set but ${opts.base} rejected it (401). Check the token value (create one in the dashboard), or point at the right deployment with --endpoint <url>.`
|
|
7911
|
+
);
|
|
7912
|
+
}
|
|
7879
7913
|
const stored = loadAuth(env);
|
|
7880
7914
|
if (stored && stored.token && stored.endpoint === opts.base) {
|
|
7881
7915
|
const state = await validateToken(opts.base, stored.token, opts.fetchImpl);
|
|
@@ -7886,6 +7920,11 @@ async function ensureToken(opts) {
|
|
|
7886
7920
|
clearAuth(env);
|
|
7887
7921
|
opts.ui.out(color.dim("Saved login expired \u2014 signing in again."));
|
|
7888
7922
|
}
|
|
7923
|
+
if (opts.allowInteractiveLogin === false) {
|
|
7924
|
+
throw new Error(
|
|
7925
|
+
`No Crumbtrail login available and this shell isn't interactive. Set ${TOKEN_ENV_VAR}=<your CLI token> (create one in the dashboard) to run in CI, or run the wizard in an interactive terminal. Add --endpoint <url> if you point at a self-hosted Crumbtrail.`
|
|
7926
|
+
);
|
|
7927
|
+
}
|
|
7889
7928
|
const minted = await login(opts);
|
|
7890
7929
|
saveAuth(
|
|
7891
7930
|
{ token: minted.token, expiresAt: minted.expiresAt, endpoint: opts.base },
|
|
@@ -8097,13 +8136,17 @@ var CLI_CHECK_PREFIX = "cli-check-";
|
|
|
8097
8136
|
function syntheticSessionId() {
|
|
8098
8137
|
return `${CLI_CHECK_PREFIX}${(0, import_node_crypto2.randomBytes)(8).toString("hex")}`;
|
|
8099
8138
|
}
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8139
|
+
var POLL_SKEW_TOLERANCE_MS = 2 * 60 * 1e3;
|
|
8140
|
+
function isRealNewSession(s, wizardStart, guard) {
|
|
8141
|
+
if (s.id.startsWith(CLI_CHECK_PREFIX)) return false;
|
|
8142
|
+
if (guard?.baselineIds) return !guard.baselineIds.has(s.id);
|
|
8143
|
+
if (wizardStart == null) return true;
|
|
8144
|
+
const started = s.startedAt ? Date.parse(s.startedAt) : NaN;
|
|
8145
|
+
const tolerance = Math.max(0, guard?.skewToleranceMs ?? 0);
|
|
8146
|
+
return Number.isFinite(started) && started >= wizardStart - tolerance;
|
|
8147
|
+
}
|
|
8148
|
+
function firstRealSession(sessions, wizardStart, guard) {
|
|
8149
|
+
return sessions.find((s) => isRealNewSession(s, wizardStart, guard));
|
|
8107
8150
|
}
|
|
8108
8151
|
async function syntheticCheck(base2, apiKey, fetchImpl) {
|
|
8109
8152
|
const sessionId = syntheticSessionId();
|
|
@@ -8165,6 +8208,26 @@ async function pollForRealEvent(opts) {
|
|
|
8165
8208
|
color.dim("Waiting for the first real event\u2026 (Ctrl-C to skip)")
|
|
8166
8209
|
);
|
|
8167
8210
|
}
|
|
8211
|
+
if (opts.signal?.aborted) {
|
|
8212
|
+
opts.ui.status?.();
|
|
8213
|
+
return { outcome: "cancelled" };
|
|
8214
|
+
}
|
|
8215
|
+
let baselineIds;
|
|
8216
|
+
try {
|
|
8217
|
+
const existing = await fetchSessions(
|
|
8218
|
+
opts.base,
|
|
8219
|
+
opts.token,
|
|
8220
|
+
opts.projectId,
|
|
8221
|
+
opts.fetchImpl
|
|
8222
|
+
);
|
|
8223
|
+
baselineIds = new Set(existing.map((s) => s.id));
|
|
8224
|
+
} catch {
|
|
8225
|
+
baselineIds = void 0;
|
|
8226
|
+
}
|
|
8227
|
+
const guard = {
|
|
8228
|
+
baselineIds,
|
|
8229
|
+
skewToleranceMs: POLL_SKEW_TOLERANCE_MS
|
|
8230
|
+
};
|
|
8168
8231
|
let state = initialIngestPollState();
|
|
8169
8232
|
let sessionId;
|
|
8170
8233
|
while (state.status === "waiting") {
|
|
@@ -8187,7 +8250,7 @@ async function pollForRealEvent(opts) {
|
|
|
8187
8250
|
opts.projectId,
|
|
8188
8251
|
opts.fetchImpl
|
|
8189
8252
|
);
|
|
8190
|
-
const real = firstRealSession(sessions, opts.wizardStart);
|
|
8253
|
+
const real = firstRealSession(sessions, opts.wizardStart, guard);
|
|
8191
8254
|
found = real !== void 0;
|
|
8192
8255
|
if (real) sessionId = real.id;
|
|
8193
8256
|
} catch {
|
|
@@ -8198,15 +8261,11 @@ async function pollForRealEvent(opts) {
|
|
|
8198
8261
|
opts.ui.status?.();
|
|
8199
8262
|
return state.status === "found" ? { outcome: "found", sessionId } : { outcome: "timedout" };
|
|
8200
8263
|
}
|
|
8201
|
-
function realSessionsByService(sessions, wizardStart) {
|
|
8264
|
+
function realSessionsByService(sessions, wizardStart, guard) {
|
|
8202
8265
|
const found = /* @__PURE__ */ new Map();
|
|
8203
8266
|
for (const s of [...sessions].reverse()) {
|
|
8204
8267
|
if (!s.serviceId || found.has(s.serviceId)) continue;
|
|
8205
|
-
if (s
|
|
8206
|
-
if (wizardStart != null) {
|
|
8207
|
-
const started = s.startedAt ? Date.parse(s.startedAt) : NaN;
|
|
8208
|
-
if (!Number.isFinite(started) || started < wizardStart) continue;
|
|
8209
|
-
}
|
|
8268
|
+
if (!isRealNewSession(s, wizardStart, guard)) continue;
|
|
8210
8269
|
found.set(s.serviceId, s.id);
|
|
8211
8270
|
}
|
|
8212
8271
|
return found;
|
|
@@ -8482,6 +8541,9 @@ function parseArgs(argv) {
|
|
|
8482
8541
|
case "--only":
|
|
8483
8542
|
(parsed.only ??= []).push(args[++i]);
|
|
8484
8543
|
break;
|
|
8544
|
+
case "--workspace":
|
|
8545
|
+
parsed.workspace = args[++i];
|
|
8546
|
+
break;
|
|
8485
8547
|
default:
|
|
8486
8548
|
if (a.startsWith("--project=")) {
|
|
8487
8549
|
parsed.project = a.slice("--project=".length);
|
|
@@ -8489,6 +8551,8 @@ function parseArgs(argv) {
|
|
|
8489
8551
|
parsed.endpoint = a.slice("--endpoint=".length);
|
|
8490
8552
|
} else if (a.startsWith("--only=")) {
|
|
8491
8553
|
(parsed.only ??= []).push(a.slice("--only=".length));
|
|
8554
|
+
} else if (a.startsWith("--workspace=")) {
|
|
8555
|
+
parsed.workspace = a.slice("--workspace=".length);
|
|
8492
8556
|
} else if (!commandSet && (a === "login" || a === "logout")) {
|
|
8493
8557
|
parsed.command = a;
|
|
8494
8558
|
commandSet = true;
|
|
@@ -8516,6 +8580,8 @@ Options:
|
|
|
8516
8580
|
--project <id> Attach to an existing project (skip creation)
|
|
8517
8581
|
--only <name> Monorepo: wire only this service (repeatable)
|
|
8518
8582
|
--all Monorepo: wire every service it can, no prompt
|
|
8583
|
+
--workspace <dir> Target one package dir (relative to cwd) instead of
|
|
8584
|
+
the repo root \u2014 wires just that package
|
|
8519
8585
|
--no-browser Use the device-code login flow
|
|
8520
8586
|
--skip-verify Don't wait for the first event
|
|
8521
8587
|
--endpoint <url> Cloud endpoint (else $CRUMBTRAIL_BASE_URL, else default)
|
|
@@ -8639,6 +8705,34 @@ function readPkgName(dir) {
|
|
|
8639
8705
|
return null;
|
|
8640
8706
|
}
|
|
8641
8707
|
}
|
|
8708
|
+
var defaultWorkspaceIO = {
|
|
8709
|
+
isDir: (p) => {
|
|
8710
|
+
try {
|
|
8711
|
+
return (0, import_node_fs6.statSync)(p).isDirectory();
|
|
8712
|
+
} catch {
|
|
8713
|
+
return false;
|
|
8714
|
+
}
|
|
8715
|
+
},
|
|
8716
|
+
isFile: (p) => {
|
|
8717
|
+
try {
|
|
8718
|
+
return (0, import_node_fs6.statSync)(p).isFile();
|
|
8719
|
+
} catch {
|
|
8720
|
+
return false;
|
|
8721
|
+
}
|
|
8722
|
+
}
|
|
8723
|
+
};
|
|
8724
|
+
function resolveWorkspaceDir(baseCwd, workspace, io = defaultWorkspaceIO) {
|
|
8725
|
+
const dir = import_node_path8.default.resolve(baseCwd, workspace);
|
|
8726
|
+
if (!io.isDir(dir)) {
|
|
8727
|
+
return { error: `--workspace ${workspace}: no such directory (${dir}).` };
|
|
8728
|
+
}
|
|
8729
|
+
if (!io.isFile(import_node_path8.default.join(dir, "package.json"))) {
|
|
8730
|
+
return {
|
|
8731
|
+
error: `--workspace ${workspace}: ${dir} has no package.json \u2014 point it at a package directory.`
|
|
8732
|
+
};
|
|
8733
|
+
}
|
|
8734
|
+
return { dir };
|
|
8735
|
+
}
|
|
8642
8736
|
async function runWizard(parsed, deps) {
|
|
8643
8737
|
const { ui } = deps;
|
|
8644
8738
|
const base2 = resolveEndpoint(parsed.endpoint, deps.env);
|
|
@@ -8646,7 +8740,18 @@ async function runWizard(parsed, deps) {
|
|
|
8646
8740
|
ui.out(color.bold("\nCrumbtrail setup"));
|
|
8647
8741
|
ui.out(color.dim(`Endpoint: ${base2}
|
|
8648
8742
|
`));
|
|
8649
|
-
|
|
8743
|
+
let cwd = deps.cwd;
|
|
8744
|
+
if (parsed.workspace) {
|
|
8745
|
+
const resolved = resolveWorkspaceDir(deps.cwd, parsed.workspace);
|
|
8746
|
+
if ("error" in resolved) {
|
|
8747
|
+
ui.err(color.red(resolved.error));
|
|
8748
|
+
return 1;
|
|
8749
|
+
}
|
|
8750
|
+
cwd = resolved.dir;
|
|
8751
|
+
ui.out(
|
|
8752
|
+
color.dim(`Targeting workspace: ${import_node_path8.default.relative(deps.cwd, cwd) || cwd}`)
|
|
8753
|
+
);
|
|
8754
|
+
}
|
|
8650
8755
|
const result = deps.detect(cwd);
|
|
8651
8756
|
if (result.isMonorepo) {
|
|
8652
8757
|
return runBatchWizard(parsed, deps, { base: base2, wizardStart, root: result });
|
|
@@ -8680,7 +8785,8 @@ async function runWizard(parsed, deps) {
|
|
|
8680
8785
|
ui,
|
|
8681
8786
|
noBrowser: parsed.noBrowser,
|
|
8682
8787
|
fetchImpl: deps.fetchImpl,
|
|
8683
|
-
env: deps.env
|
|
8788
|
+
env: deps.env,
|
|
8789
|
+
allowInteractiveLogin: deps.isTTY
|
|
8684
8790
|
});
|
|
8685
8791
|
} catch (err) {
|
|
8686
8792
|
ui.err(color.red(`Login failed: ${errMessage(err)}`));
|
|
@@ -8737,7 +8843,10 @@ async function runWizard(parsed, deps) {
|
|
|
8737
8843
|
} else if (install.note) {
|
|
8738
8844
|
ui.out(color.yellow(`! ${install.note}`));
|
|
8739
8845
|
}
|
|
8740
|
-
const inject = await applyInjection(plan, parsed, deps
|
|
8846
|
+
const inject = await applyInjection(plan, parsed, deps, {
|
|
8847
|
+
installed: install.installed,
|
|
8848
|
+
packages: install.packages
|
|
8849
|
+
});
|
|
8741
8850
|
const notes = [];
|
|
8742
8851
|
if (!install.installed && install.note) notes.push(install.note);
|
|
8743
8852
|
notes.push(...inject.notes);
|
|
@@ -8780,10 +8889,27 @@ async function runWizard(parsed, deps) {
|
|
|
8780
8889
|
} else {
|
|
8781
8890
|
notes.push("No event yet \u2014 start your app and check the dashboard.");
|
|
8782
8891
|
}
|
|
8892
|
+
printEvidenceSourcesPointer(ui, base2);
|
|
8783
8893
|
}
|
|
8784
8894
|
printSummary(ui, base2, provisioned, inject.filesTouched, notes, sessionUrl);
|
|
8785
8895
|
return 0;
|
|
8786
8896
|
}
|
|
8897
|
+
function printEvidenceSourcesPointer(ui, base2) {
|
|
8898
|
+
ui.out("");
|
|
8899
|
+
ui.out(color.bold("Next: make each ticket's evidence more complete."));
|
|
8900
|
+
ui.out(
|
|
8901
|
+
color.dim(
|
|
8902
|
+
"Crumbtrail's SDK stands alone, but it can also fold in evidence from tools"
|
|
8903
|
+
)
|
|
8904
|
+
);
|
|
8905
|
+
ui.out(
|
|
8906
|
+
color.dim(
|
|
8907
|
+
"you already run \u2014 Sentry, CloudWatch, Splunk, Datadog, PostHog, Cloudflare \u2014"
|
|
8908
|
+
)
|
|
8909
|
+
);
|
|
8910
|
+
ui.out(color.dim("queried at incident time and added to each bug's bundle."));
|
|
8911
|
+
ui.out(` Evidence sources: ${color.cyan(`${base2}/settings`)}`);
|
|
8912
|
+
}
|
|
8787
8913
|
function stackLabel2(c) {
|
|
8788
8914
|
if (c.recipe == null) return "\u2014";
|
|
8789
8915
|
if (c.recipe === "otlp") return c.detected.otlpStack ?? "otlp";
|
|
@@ -8895,7 +9021,8 @@ async function runBatchWizard(parsed, deps, ctx) {
|
|
|
8895
9021
|
ui,
|
|
8896
9022
|
noBrowser: parsed.noBrowser,
|
|
8897
9023
|
fetchImpl: deps.fetchImpl,
|
|
8898
|
-
env: deps.env
|
|
9024
|
+
env: deps.env,
|
|
9025
|
+
allowInteractiveLogin: deps.isTTY
|
|
8899
9026
|
});
|
|
8900
9027
|
} catch (err) {
|
|
8901
9028
|
ui.err(color.red(`Login failed: ${errMessage(err)}`));
|
|
@@ -8990,7 +9117,11 @@ async function runBatchWizard(parsed, deps, ctx) {
|
|
|
8990
9117
|
const applied = await applyBatchInjection(plan, c, svc.serviceName, {
|
|
8991
9118
|
parsed,
|
|
8992
9119
|
deps,
|
|
8993
|
-
base: base2
|
|
9120
|
+
base: base2,
|
|
9121
|
+
sdkInstall: {
|
|
9122
|
+
installed: install.installed,
|
|
9123
|
+
packages: install.packages
|
|
9124
|
+
}
|
|
8994
9125
|
});
|
|
8995
9126
|
outcomes.push({
|
|
8996
9127
|
name: svc.serviceName,
|
|
@@ -9065,6 +9196,9 @@ async function runBatchWizard(parsed, deps, ctx) {
|
|
|
9065
9196
|
}
|
|
9066
9197
|
}
|
|
9067
9198
|
}
|
|
9199
|
+
if (!parsed.skipVerify && reporting.length > 0) {
|
|
9200
|
+
printEvidenceSourcesPointer(ui, base2);
|
|
9201
|
+
}
|
|
9068
9202
|
printBatchSummary(ui, base2, root, project.name, outcomes, batchNotes);
|
|
9069
9203
|
const attempted = outcomes.filter(
|
|
9070
9204
|
(o) => o.status !== "skipped-already-wired"
|
|
@@ -9095,7 +9229,7 @@ async function applyBatchInjection(plan, candidate, serviceName, ctx) {
|
|
|
9095
9229
|
notes: ["add the OTLP exporter from the guide file to start reporting."]
|
|
9096
9230
|
};
|
|
9097
9231
|
}
|
|
9098
|
-
const applied = await applyInjection(plan, parsed, deps);
|
|
9232
|
+
const applied = await applyInjection(plan, parsed, deps, ctx.sdkInstall);
|
|
9099
9233
|
const status = plan.kind === "fallback-ai" ? "guidance" : applied.filesTouched.length > 0 ? "wired" : "skipped-already-wired";
|
|
9100
9234
|
return { status, filesTouched: applied.filesTouched, notes: applied.notes };
|
|
9101
9235
|
}
|
|
@@ -9139,7 +9273,7 @@ function printBatchSummary(ui, base2, root, projectName, outcomes, batchNotes =
|
|
|
9139
9273
|
for (const n of notes) ui.out(color.dim(` note: ${n}`));
|
|
9140
9274
|
}
|
|
9141
9275
|
}
|
|
9142
|
-
async function applyInjection(plan, parsed, deps) {
|
|
9276
|
+
async function applyInjection(plan, parsed, deps, sdkInstall) {
|
|
9143
9277
|
const { ui } = deps;
|
|
9144
9278
|
const filesTouched = [];
|
|
9145
9279
|
const notes = [];
|
|
@@ -9186,7 +9320,16 @@ async function applyInjection(plan, parsed, deps) {
|
|
|
9186
9320
|
color.yellow("Left your file untouched. Add this to the top yourself:")
|
|
9187
9321
|
);
|
|
9188
9322
|
if (plan.content) ui.out(plan.content);
|
|
9189
|
-
notes.push(
|
|
9323
|
+
notes.push(
|
|
9324
|
+
`Skipped editing ${plan.targetPath} (uncommitted changes) \u2014 paste the snippet above into it manually.`
|
|
9325
|
+
);
|
|
9326
|
+
if (sdkInstall?.installed && sdkInstall.packages.length > 0) {
|
|
9327
|
+
const pkgs = sdkInstall.packages.join(", ");
|
|
9328
|
+
const were = sdkInstall.packages.length > 1 ? "were" : "was";
|
|
9329
|
+
notes.push(
|
|
9330
|
+
`${pkgs} ${were} already installed, so your package.json is already updated \u2014 only the code import above is still manual.`
|
|
9331
|
+
);
|
|
9332
|
+
}
|
|
9190
9333
|
return { filesTouched, notes };
|
|
9191
9334
|
}
|
|
9192
9335
|
const res2 = deps.executePlan(plan, void 0, { confirmDirty: true });
|
|
@@ -9275,7 +9418,8 @@ async function runLogin(parsed, deps) {
|
|
|
9275
9418
|
ui: deps.ui,
|
|
9276
9419
|
noBrowser: parsed.noBrowser,
|
|
9277
9420
|
fetchImpl: deps.fetchImpl,
|
|
9278
|
-
env: deps.env
|
|
9421
|
+
env: deps.env,
|
|
9422
|
+
allowInteractiveLogin: deps.isTTY
|
|
9279
9423
|
});
|
|
9280
9424
|
return 0;
|
|
9281
9425
|
} catch (err) {
|
|
@@ -9338,6 +9482,7 @@ if (isCliEntrypoint(process.argv[1])) {
|
|
|
9338
9482
|
isCliEntrypoint,
|
|
9339
9483
|
parseArgs,
|
|
9340
9484
|
resolveSelection,
|
|
9485
|
+
resolveWorkspaceDir,
|
|
9341
9486
|
runBatchWizard,
|
|
9342
9487
|
runCli,
|
|
9343
9488
|
runWizard
|
package/dist/cli.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { R as Recipe, D as DetectResult, P as PackageManager, b as buildPlan, e as executePlan } from './executor-
|
|
2
|
+
import { R as Recipe, D as DetectResult, P as PackageManager, b as buildPlan, e as executePlan } from './executor-BKiOvIvD.cjs';
|
|
3
3
|
import { Stack } from 'crumbtrail-design-system';
|
|
4
4
|
|
|
5
5
|
/** Output sink — swappable in tests to capture lines instead of writing stdout. */
|
|
@@ -56,6 +56,14 @@ interface LoginOptions {
|
|
|
56
56
|
pollIntervalMs?: number;
|
|
57
57
|
/** Browser hand-off deadline in ms (default 5 min); overridable in tests. */
|
|
58
58
|
browserDeadlineMs?: number;
|
|
59
|
+
/**
|
|
60
|
+
* False in a non-TTY shell: refuse to START an interactive login (browser
|
|
61
|
+
* hand-off / device code) that would block on input nobody can give, and throw
|
|
62
|
+
* an actionable error instead of hanging. Undefined/true keeps the interactive
|
|
63
|
+
* flow (default) so a normal terminal is unaffected. A valid CRUMBTRAIL_TOKEN or
|
|
64
|
+
* a cached token is honored regardless of this flag.
|
|
65
|
+
*/
|
|
66
|
+
allowInteractiveLogin?: boolean;
|
|
59
67
|
}
|
|
60
68
|
/**
|
|
61
69
|
* Resolve a usable CLI token for `base`: reuse a stored token (validated by a
|
|
@@ -260,6 +268,12 @@ interface ParsedArgs {
|
|
|
260
268
|
only?: string[];
|
|
261
269
|
/** Monorepo root only: select every wireable service, no prompt. */
|
|
262
270
|
all: boolean;
|
|
271
|
+
/**
|
|
272
|
+
* Target a specific package directory instead of the detected repo root. In a
|
|
273
|
+
* monorepo this bypasses the batch scan and wires exactly this one package
|
|
274
|
+
* (resolved relative to cwd; must exist and hold a package.json).
|
|
275
|
+
*/
|
|
276
|
+
workspace?: string;
|
|
263
277
|
/** Non-flag/subcommand leftover — an unknown token triggers usage help. */
|
|
264
278
|
unknown?: string;
|
|
265
279
|
}
|
|
@@ -315,6 +329,24 @@ interface WizardDeps {
|
|
|
315
329
|
fetchImpl?: typeof fetch;
|
|
316
330
|
}
|
|
317
331
|
declare function defaultDeps(): WizardDeps;
|
|
332
|
+
/** Filesystem probes for --workspace validation; injectable so the resolver is
|
|
333
|
+
* unit-testable without touching real directories. */
|
|
334
|
+
interface WorkspaceIO {
|
|
335
|
+
isDir: (p: string) => boolean;
|
|
336
|
+
isFile: (p: string) => boolean;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Resolve `--workspace <dir>` to an absolute package directory. The dir is taken
|
|
340
|
+
* relative to the wizard's cwd, must exist, and must hold a package.json (the
|
|
341
|
+
* workspace-package manifest) — otherwise detect() would run against nothing
|
|
342
|
+
* useful, so we refuse with a concrete message rather than proceed. Pure aside
|
|
343
|
+
* from the injected probes.
|
|
344
|
+
*/
|
|
345
|
+
declare function resolveWorkspaceDir(baseCwd: string, workspace: string, io?: WorkspaceIO): {
|
|
346
|
+
dir: string;
|
|
347
|
+
} | {
|
|
348
|
+
error: string;
|
|
349
|
+
};
|
|
318
350
|
declare function runWizard(parsed: ParsedArgs, deps: WizardDeps): Promise<number>;
|
|
319
351
|
type ServiceStatus = "wired" | "guidance" | "skipped-already-wired" | "failed";
|
|
320
352
|
interface ServiceOutcome {
|
|
@@ -355,4 +387,4 @@ declare function runBatchWizard(parsed: ParsedArgs, deps: WizardDeps, ctx: Batch
|
|
|
355
387
|
declare function runCli(argv: string[], deps?: WizardDeps): Promise<number>;
|
|
356
388
|
declare function isCliEntrypoint(argv1: string | undefined): boolean;
|
|
357
389
|
|
|
358
|
-
export { type Command, type InstallSdkInput, type InstallSdkResult, type ParsedArgs, type ServiceOutcome, type ServiceStatus, type WizardDeps, defaultDeps, installSdk, isCliEntrypoint, parseArgs, resolveSelection, runBatchWizard, runCli, runWizard };
|
|
390
|
+
export { type Command, type InstallSdkInput, type InstallSdkResult, type ParsedArgs, type ServiceOutcome, type ServiceStatus, type WizardDeps, type WorkspaceIO, defaultDeps, installSdk, isCliEntrypoint, parseArgs, resolveSelection, resolveWorkspaceDir, runBatchWizard, runCli, runWizard };
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { R as Recipe, D as DetectResult, P as PackageManager, b as buildPlan, e as executePlan } from './executor-
|
|
2
|
+
import { R as Recipe, D as DetectResult, P as PackageManager, b as buildPlan, e as executePlan } from './executor-BKiOvIvD.js';
|
|
3
3
|
import { Stack } from 'crumbtrail-design-system';
|
|
4
4
|
|
|
5
5
|
/** Output sink — swappable in tests to capture lines instead of writing stdout. */
|
|
@@ -56,6 +56,14 @@ interface LoginOptions {
|
|
|
56
56
|
pollIntervalMs?: number;
|
|
57
57
|
/** Browser hand-off deadline in ms (default 5 min); overridable in tests. */
|
|
58
58
|
browserDeadlineMs?: number;
|
|
59
|
+
/**
|
|
60
|
+
* False in a non-TTY shell: refuse to START an interactive login (browser
|
|
61
|
+
* hand-off / device code) that would block on input nobody can give, and throw
|
|
62
|
+
* an actionable error instead of hanging. Undefined/true keeps the interactive
|
|
63
|
+
* flow (default) so a normal terminal is unaffected. A valid CRUMBTRAIL_TOKEN or
|
|
64
|
+
* a cached token is honored regardless of this flag.
|
|
65
|
+
*/
|
|
66
|
+
allowInteractiveLogin?: boolean;
|
|
59
67
|
}
|
|
60
68
|
/**
|
|
61
69
|
* Resolve a usable CLI token for `base`: reuse a stored token (validated by a
|
|
@@ -260,6 +268,12 @@ interface ParsedArgs {
|
|
|
260
268
|
only?: string[];
|
|
261
269
|
/** Monorepo root only: select every wireable service, no prompt. */
|
|
262
270
|
all: boolean;
|
|
271
|
+
/**
|
|
272
|
+
* Target a specific package directory instead of the detected repo root. In a
|
|
273
|
+
* monorepo this bypasses the batch scan and wires exactly this one package
|
|
274
|
+
* (resolved relative to cwd; must exist and hold a package.json).
|
|
275
|
+
*/
|
|
276
|
+
workspace?: string;
|
|
263
277
|
/** Non-flag/subcommand leftover — an unknown token triggers usage help. */
|
|
264
278
|
unknown?: string;
|
|
265
279
|
}
|
|
@@ -315,6 +329,24 @@ interface WizardDeps {
|
|
|
315
329
|
fetchImpl?: typeof fetch;
|
|
316
330
|
}
|
|
317
331
|
declare function defaultDeps(): WizardDeps;
|
|
332
|
+
/** Filesystem probes for --workspace validation; injectable so the resolver is
|
|
333
|
+
* unit-testable without touching real directories. */
|
|
334
|
+
interface WorkspaceIO {
|
|
335
|
+
isDir: (p: string) => boolean;
|
|
336
|
+
isFile: (p: string) => boolean;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Resolve `--workspace <dir>` to an absolute package directory. The dir is taken
|
|
340
|
+
* relative to the wizard's cwd, must exist, and must hold a package.json (the
|
|
341
|
+
* workspace-package manifest) — otherwise detect() would run against nothing
|
|
342
|
+
* useful, so we refuse with a concrete message rather than proceed. Pure aside
|
|
343
|
+
* from the injected probes.
|
|
344
|
+
*/
|
|
345
|
+
declare function resolveWorkspaceDir(baseCwd: string, workspace: string, io?: WorkspaceIO): {
|
|
346
|
+
dir: string;
|
|
347
|
+
} | {
|
|
348
|
+
error: string;
|
|
349
|
+
};
|
|
318
350
|
declare function runWizard(parsed: ParsedArgs, deps: WizardDeps): Promise<number>;
|
|
319
351
|
type ServiceStatus = "wired" | "guidance" | "skipped-already-wired" | "failed";
|
|
320
352
|
interface ServiceOutcome {
|
|
@@ -355,4 +387,4 @@ declare function runBatchWizard(parsed: ParsedArgs, deps: WizardDeps, ctx: Batch
|
|
|
355
387
|
declare function runCli(argv: string[], deps?: WizardDeps): Promise<number>;
|
|
356
388
|
declare function isCliEntrypoint(argv1: string | undefined): boolean;
|
|
357
389
|
|
|
358
|
-
export { type Command, type InstallSdkInput, type InstallSdkResult, type ParsedArgs, type ServiceOutcome, type ServiceStatus, type WizardDeps, defaultDeps, installSdk, isCliEntrypoint, parseArgs, resolveSelection, runBatchWizard, runCli, runWizard };
|
|
390
|
+
export { type Command, type InstallSdkInput, type InstallSdkResult, type ParsedArgs, type ServiceOutcome, type ServiceStatus, type WizardDeps, type WorkspaceIO, defaultDeps, installSdk, isCliEntrypoint, parseArgs, resolveSelection, resolveWorkspaceDir, runBatchWizard, runCli, runWizard };
|
package/dist/cli.js
CHANGED
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
detect,
|
|
8
8
|
executePlan,
|
|
9
9
|
projectAlreadyWired
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-HAP4DPWC.js";
|
|
11
11
|
|
|
12
12
|
// src/cli.ts
|
|
13
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
13
|
+
import { readFileSync as readFileSync2, statSync } from "fs";
|
|
14
14
|
import path4 from "path";
|
|
15
15
|
import { spawnSync } from "child_process";
|
|
16
16
|
|
|
@@ -30,7 +30,7 @@ import os from "os";
|
|
|
30
30
|
import path from "path";
|
|
31
31
|
|
|
32
32
|
// src/net.ts
|
|
33
|
-
var DEFAULT_ENDPOINT = "https://crumbtrail
|
|
33
|
+
var DEFAULT_ENDPOINT = "https://app.crumbtrail.dev";
|
|
34
34
|
function normalizeBase(base) {
|
|
35
35
|
return base.replace(/\/+$/, "");
|
|
36
36
|
}
|
|
@@ -485,6 +485,7 @@ async function validateToken(base, token, fetchImpl) {
|
|
|
485
485
|
throw err;
|
|
486
486
|
}
|
|
487
487
|
}
|
|
488
|
+
var TOKEN_ENV_VAR = "CRUMBTRAIL_TOKEN";
|
|
488
489
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
489
490
|
async function loginBrowser(opts) {
|
|
490
491
|
const { verifier, challenge } = pkcePair();
|
|
@@ -581,6 +582,17 @@ async function login(opts) {
|
|
|
581
582
|
}
|
|
582
583
|
async function ensureToken(opts) {
|
|
583
584
|
const env = opts.env ?? process.env;
|
|
585
|
+
const envToken = env[TOKEN_ENV_VAR]?.trim();
|
|
586
|
+
if (envToken) {
|
|
587
|
+
const state = await validateToken(opts.base, envToken, opts.fetchImpl);
|
|
588
|
+
if (state === "valid") {
|
|
589
|
+
opts.ui.out(color.dim(`Using ${TOKEN_ENV_VAR} from the environment.`));
|
|
590
|
+
return envToken;
|
|
591
|
+
}
|
|
592
|
+
throw new Error(
|
|
593
|
+
`${TOKEN_ENV_VAR} was set but ${opts.base} rejected it (401). Check the token value (create one in the dashboard), or point at the right deployment with --endpoint <url>.`
|
|
594
|
+
);
|
|
595
|
+
}
|
|
584
596
|
const stored = loadAuth(env);
|
|
585
597
|
if (stored && stored.token && stored.endpoint === opts.base) {
|
|
586
598
|
const state = await validateToken(opts.base, stored.token, opts.fetchImpl);
|
|
@@ -591,6 +603,11 @@ async function ensureToken(opts) {
|
|
|
591
603
|
clearAuth(env);
|
|
592
604
|
opts.ui.out(color.dim("Saved login expired \u2014 signing in again."));
|
|
593
605
|
}
|
|
606
|
+
if (opts.allowInteractiveLogin === false) {
|
|
607
|
+
throw new Error(
|
|
608
|
+
`No Crumbtrail login available and this shell isn't interactive. Set ${TOKEN_ENV_VAR}=<your CLI token> (create one in the dashboard) to run in CI, or run the wizard in an interactive terminal. Add --endpoint <url> if you point at a self-hosted Crumbtrail.`
|
|
609
|
+
);
|
|
610
|
+
}
|
|
594
611
|
const minted = await login(opts);
|
|
595
612
|
saveAuth(
|
|
596
613
|
{ token: minted.token, expiresAt: minted.expiresAt, endpoint: opts.base },
|
|
@@ -802,13 +819,17 @@ var CLI_CHECK_PREFIX = "cli-check-";
|
|
|
802
819
|
function syntheticSessionId() {
|
|
803
820
|
return `${CLI_CHECK_PREFIX}${randomBytes2(8).toString("hex")}`;
|
|
804
821
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
822
|
+
var POLL_SKEW_TOLERANCE_MS = 2 * 60 * 1e3;
|
|
823
|
+
function isRealNewSession(s, wizardStart, guard) {
|
|
824
|
+
if (s.id.startsWith(CLI_CHECK_PREFIX)) return false;
|
|
825
|
+
if (guard?.baselineIds) return !guard.baselineIds.has(s.id);
|
|
826
|
+
if (wizardStart == null) return true;
|
|
827
|
+
const started = s.startedAt ? Date.parse(s.startedAt) : NaN;
|
|
828
|
+
const tolerance = Math.max(0, guard?.skewToleranceMs ?? 0);
|
|
829
|
+
return Number.isFinite(started) && started >= wizardStart - tolerance;
|
|
830
|
+
}
|
|
831
|
+
function firstRealSession(sessions, wizardStart, guard) {
|
|
832
|
+
return sessions.find((s) => isRealNewSession(s, wizardStart, guard));
|
|
812
833
|
}
|
|
813
834
|
async function syntheticCheck(base, apiKey, fetchImpl) {
|
|
814
835
|
const sessionId = syntheticSessionId();
|
|
@@ -870,6 +891,26 @@ async function pollForRealEvent(opts) {
|
|
|
870
891
|
color.dim("Waiting for the first real event\u2026 (Ctrl-C to skip)")
|
|
871
892
|
);
|
|
872
893
|
}
|
|
894
|
+
if (opts.signal?.aborted) {
|
|
895
|
+
opts.ui.status?.();
|
|
896
|
+
return { outcome: "cancelled" };
|
|
897
|
+
}
|
|
898
|
+
let baselineIds;
|
|
899
|
+
try {
|
|
900
|
+
const existing = await fetchSessions(
|
|
901
|
+
opts.base,
|
|
902
|
+
opts.token,
|
|
903
|
+
opts.projectId,
|
|
904
|
+
opts.fetchImpl
|
|
905
|
+
);
|
|
906
|
+
baselineIds = new Set(existing.map((s) => s.id));
|
|
907
|
+
} catch {
|
|
908
|
+
baselineIds = void 0;
|
|
909
|
+
}
|
|
910
|
+
const guard = {
|
|
911
|
+
baselineIds,
|
|
912
|
+
skewToleranceMs: POLL_SKEW_TOLERANCE_MS
|
|
913
|
+
};
|
|
873
914
|
let state = initialIngestPollState();
|
|
874
915
|
let sessionId;
|
|
875
916
|
while (state.status === "waiting") {
|
|
@@ -892,7 +933,7 @@ async function pollForRealEvent(opts) {
|
|
|
892
933
|
opts.projectId,
|
|
893
934
|
opts.fetchImpl
|
|
894
935
|
);
|
|
895
|
-
const real = firstRealSession(sessions, opts.wizardStart);
|
|
936
|
+
const real = firstRealSession(sessions, opts.wizardStart, guard);
|
|
896
937
|
found = real !== void 0;
|
|
897
938
|
if (real) sessionId = real.id;
|
|
898
939
|
} catch {
|
|
@@ -903,15 +944,11 @@ async function pollForRealEvent(opts) {
|
|
|
903
944
|
opts.ui.status?.();
|
|
904
945
|
return state.status === "found" ? { outcome: "found", sessionId } : { outcome: "timedout" };
|
|
905
946
|
}
|
|
906
|
-
function realSessionsByService(sessions, wizardStart) {
|
|
947
|
+
function realSessionsByService(sessions, wizardStart, guard) {
|
|
907
948
|
const found = /* @__PURE__ */ new Map();
|
|
908
949
|
for (const s of [...sessions].reverse()) {
|
|
909
950
|
if (!s.serviceId || found.has(s.serviceId)) continue;
|
|
910
|
-
if (s
|
|
911
|
-
if (wizardStart != null) {
|
|
912
|
-
const started = s.startedAt ? Date.parse(s.startedAt) : NaN;
|
|
913
|
-
if (!Number.isFinite(started) || started < wizardStart) continue;
|
|
914
|
-
}
|
|
951
|
+
if (!isRealNewSession(s, wizardStart, guard)) continue;
|
|
915
952
|
found.set(s.serviceId, s.id);
|
|
916
953
|
}
|
|
917
954
|
return found;
|
|
@@ -1187,6 +1224,9 @@ function parseArgs(argv) {
|
|
|
1187
1224
|
case "--only":
|
|
1188
1225
|
(parsed.only ??= []).push(args[++i]);
|
|
1189
1226
|
break;
|
|
1227
|
+
case "--workspace":
|
|
1228
|
+
parsed.workspace = args[++i];
|
|
1229
|
+
break;
|
|
1190
1230
|
default:
|
|
1191
1231
|
if (a.startsWith("--project=")) {
|
|
1192
1232
|
parsed.project = a.slice("--project=".length);
|
|
@@ -1194,6 +1234,8 @@ function parseArgs(argv) {
|
|
|
1194
1234
|
parsed.endpoint = a.slice("--endpoint=".length);
|
|
1195
1235
|
} else if (a.startsWith("--only=")) {
|
|
1196
1236
|
(parsed.only ??= []).push(a.slice("--only=".length));
|
|
1237
|
+
} else if (a.startsWith("--workspace=")) {
|
|
1238
|
+
parsed.workspace = a.slice("--workspace=".length);
|
|
1197
1239
|
} else if (!commandSet && (a === "login" || a === "logout")) {
|
|
1198
1240
|
parsed.command = a;
|
|
1199
1241
|
commandSet = true;
|
|
@@ -1221,6 +1263,8 @@ Options:
|
|
|
1221
1263
|
--project <id> Attach to an existing project (skip creation)
|
|
1222
1264
|
--only <name> Monorepo: wire only this service (repeatable)
|
|
1223
1265
|
--all Monorepo: wire every service it can, no prompt
|
|
1266
|
+
--workspace <dir> Target one package dir (relative to cwd) instead of
|
|
1267
|
+
the repo root \u2014 wires just that package
|
|
1224
1268
|
--no-browser Use the device-code login flow
|
|
1225
1269
|
--skip-verify Don't wait for the first event
|
|
1226
1270
|
--endpoint <url> Cloud endpoint (else $CRUMBTRAIL_BASE_URL, else default)
|
|
@@ -1344,6 +1388,34 @@ function readPkgName(dir) {
|
|
|
1344
1388
|
return null;
|
|
1345
1389
|
}
|
|
1346
1390
|
}
|
|
1391
|
+
var defaultWorkspaceIO = {
|
|
1392
|
+
isDir: (p) => {
|
|
1393
|
+
try {
|
|
1394
|
+
return statSync(p).isDirectory();
|
|
1395
|
+
} catch {
|
|
1396
|
+
return false;
|
|
1397
|
+
}
|
|
1398
|
+
},
|
|
1399
|
+
isFile: (p) => {
|
|
1400
|
+
try {
|
|
1401
|
+
return statSync(p).isFile();
|
|
1402
|
+
} catch {
|
|
1403
|
+
return false;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
function resolveWorkspaceDir(baseCwd, workspace, io = defaultWorkspaceIO) {
|
|
1408
|
+
const dir = path4.resolve(baseCwd, workspace);
|
|
1409
|
+
if (!io.isDir(dir)) {
|
|
1410
|
+
return { error: `--workspace ${workspace}: no such directory (${dir}).` };
|
|
1411
|
+
}
|
|
1412
|
+
if (!io.isFile(path4.join(dir, "package.json"))) {
|
|
1413
|
+
return {
|
|
1414
|
+
error: `--workspace ${workspace}: ${dir} has no package.json \u2014 point it at a package directory.`
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
return { dir };
|
|
1418
|
+
}
|
|
1347
1419
|
async function runWizard(parsed, deps) {
|
|
1348
1420
|
const { ui } = deps;
|
|
1349
1421
|
const base = resolveEndpoint(parsed.endpoint, deps.env);
|
|
@@ -1351,7 +1423,18 @@ async function runWizard(parsed, deps) {
|
|
|
1351
1423
|
ui.out(color.bold("\nCrumbtrail setup"));
|
|
1352
1424
|
ui.out(color.dim(`Endpoint: ${base}
|
|
1353
1425
|
`));
|
|
1354
|
-
|
|
1426
|
+
let cwd = deps.cwd;
|
|
1427
|
+
if (parsed.workspace) {
|
|
1428
|
+
const resolved = resolveWorkspaceDir(deps.cwd, parsed.workspace);
|
|
1429
|
+
if ("error" in resolved) {
|
|
1430
|
+
ui.err(color.red(resolved.error));
|
|
1431
|
+
return 1;
|
|
1432
|
+
}
|
|
1433
|
+
cwd = resolved.dir;
|
|
1434
|
+
ui.out(
|
|
1435
|
+
color.dim(`Targeting workspace: ${path4.relative(deps.cwd, cwd) || cwd}`)
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1355
1438
|
const result = deps.detect(cwd);
|
|
1356
1439
|
if (result.isMonorepo) {
|
|
1357
1440
|
return runBatchWizard(parsed, deps, { base, wizardStart, root: result });
|
|
@@ -1385,7 +1468,8 @@ async function runWizard(parsed, deps) {
|
|
|
1385
1468
|
ui,
|
|
1386
1469
|
noBrowser: parsed.noBrowser,
|
|
1387
1470
|
fetchImpl: deps.fetchImpl,
|
|
1388
|
-
env: deps.env
|
|
1471
|
+
env: deps.env,
|
|
1472
|
+
allowInteractiveLogin: deps.isTTY
|
|
1389
1473
|
});
|
|
1390
1474
|
} catch (err) {
|
|
1391
1475
|
ui.err(color.red(`Login failed: ${errMessage(err)}`));
|
|
@@ -1442,7 +1526,10 @@ async function runWizard(parsed, deps) {
|
|
|
1442
1526
|
} else if (install.note) {
|
|
1443
1527
|
ui.out(color.yellow(`! ${install.note}`));
|
|
1444
1528
|
}
|
|
1445
|
-
const inject = await applyInjection(plan, parsed, deps
|
|
1529
|
+
const inject = await applyInjection(plan, parsed, deps, {
|
|
1530
|
+
installed: install.installed,
|
|
1531
|
+
packages: install.packages
|
|
1532
|
+
});
|
|
1446
1533
|
const notes = [];
|
|
1447
1534
|
if (!install.installed && install.note) notes.push(install.note);
|
|
1448
1535
|
notes.push(...inject.notes);
|
|
@@ -1485,10 +1572,27 @@ async function runWizard(parsed, deps) {
|
|
|
1485
1572
|
} else {
|
|
1486
1573
|
notes.push("No event yet \u2014 start your app and check the dashboard.");
|
|
1487
1574
|
}
|
|
1575
|
+
printEvidenceSourcesPointer(ui, base);
|
|
1488
1576
|
}
|
|
1489
1577
|
printSummary(ui, base, provisioned, inject.filesTouched, notes, sessionUrl);
|
|
1490
1578
|
return 0;
|
|
1491
1579
|
}
|
|
1580
|
+
function printEvidenceSourcesPointer(ui, base) {
|
|
1581
|
+
ui.out("");
|
|
1582
|
+
ui.out(color.bold("Next: make each ticket's evidence more complete."));
|
|
1583
|
+
ui.out(
|
|
1584
|
+
color.dim(
|
|
1585
|
+
"Crumbtrail's SDK stands alone, but it can also fold in evidence from tools"
|
|
1586
|
+
)
|
|
1587
|
+
);
|
|
1588
|
+
ui.out(
|
|
1589
|
+
color.dim(
|
|
1590
|
+
"you already run \u2014 Sentry, CloudWatch, Splunk, Datadog, PostHog, Cloudflare \u2014"
|
|
1591
|
+
)
|
|
1592
|
+
);
|
|
1593
|
+
ui.out(color.dim("queried at incident time and added to each bug's bundle."));
|
|
1594
|
+
ui.out(` Evidence sources: ${color.cyan(`${base}/settings`)}`);
|
|
1595
|
+
}
|
|
1492
1596
|
function stackLabel(c) {
|
|
1493
1597
|
if (c.recipe == null) return "\u2014";
|
|
1494
1598
|
if (c.recipe === "otlp") return c.detected.otlpStack ?? "otlp";
|
|
@@ -1600,7 +1704,8 @@ async function runBatchWizard(parsed, deps, ctx) {
|
|
|
1600
1704
|
ui,
|
|
1601
1705
|
noBrowser: parsed.noBrowser,
|
|
1602
1706
|
fetchImpl: deps.fetchImpl,
|
|
1603
|
-
env: deps.env
|
|
1707
|
+
env: deps.env,
|
|
1708
|
+
allowInteractiveLogin: deps.isTTY
|
|
1604
1709
|
});
|
|
1605
1710
|
} catch (err) {
|
|
1606
1711
|
ui.err(color.red(`Login failed: ${errMessage(err)}`));
|
|
@@ -1695,7 +1800,11 @@ async function runBatchWizard(parsed, deps, ctx) {
|
|
|
1695
1800
|
const applied = await applyBatchInjection(plan, c, svc.serviceName, {
|
|
1696
1801
|
parsed,
|
|
1697
1802
|
deps,
|
|
1698
|
-
base
|
|
1803
|
+
base,
|
|
1804
|
+
sdkInstall: {
|
|
1805
|
+
installed: install.installed,
|
|
1806
|
+
packages: install.packages
|
|
1807
|
+
}
|
|
1699
1808
|
});
|
|
1700
1809
|
outcomes.push({
|
|
1701
1810
|
name: svc.serviceName,
|
|
@@ -1770,6 +1879,9 @@ async function runBatchWizard(parsed, deps, ctx) {
|
|
|
1770
1879
|
}
|
|
1771
1880
|
}
|
|
1772
1881
|
}
|
|
1882
|
+
if (!parsed.skipVerify && reporting.length > 0) {
|
|
1883
|
+
printEvidenceSourcesPointer(ui, base);
|
|
1884
|
+
}
|
|
1773
1885
|
printBatchSummary(ui, base, root, project.name, outcomes, batchNotes);
|
|
1774
1886
|
const attempted = outcomes.filter(
|
|
1775
1887
|
(o) => o.status !== "skipped-already-wired"
|
|
@@ -1800,7 +1912,7 @@ async function applyBatchInjection(plan, candidate, serviceName, ctx) {
|
|
|
1800
1912
|
notes: ["add the OTLP exporter from the guide file to start reporting."]
|
|
1801
1913
|
};
|
|
1802
1914
|
}
|
|
1803
|
-
const applied = await applyInjection(plan, parsed, deps);
|
|
1915
|
+
const applied = await applyInjection(plan, parsed, deps, ctx.sdkInstall);
|
|
1804
1916
|
const status = plan.kind === "fallback-ai" ? "guidance" : applied.filesTouched.length > 0 ? "wired" : "skipped-already-wired";
|
|
1805
1917
|
return { status, filesTouched: applied.filesTouched, notes: applied.notes };
|
|
1806
1918
|
}
|
|
@@ -1844,7 +1956,7 @@ function printBatchSummary(ui, base, root, projectName, outcomes, batchNotes = [
|
|
|
1844
1956
|
for (const n of notes) ui.out(color.dim(` note: ${n}`));
|
|
1845
1957
|
}
|
|
1846
1958
|
}
|
|
1847
|
-
async function applyInjection(plan, parsed, deps) {
|
|
1959
|
+
async function applyInjection(plan, parsed, deps, sdkInstall) {
|
|
1848
1960
|
const { ui } = deps;
|
|
1849
1961
|
const filesTouched = [];
|
|
1850
1962
|
const notes = [];
|
|
@@ -1891,7 +2003,16 @@ async function applyInjection(plan, parsed, deps) {
|
|
|
1891
2003
|
color.yellow("Left your file untouched. Add this to the top yourself:")
|
|
1892
2004
|
);
|
|
1893
2005
|
if (plan.content) ui.out(plan.content);
|
|
1894
|
-
notes.push(
|
|
2006
|
+
notes.push(
|
|
2007
|
+
`Skipped editing ${plan.targetPath} (uncommitted changes) \u2014 paste the snippet above into it manually.`
|
|
2008
|
+
);
|
|
2009
|
+
if (sdkInstall?.installed && sdkInstall.packages.length > 0) {
|
|
2010
|
+
const pkgs = sdkInstall.packages.join(", ");
|
|
2011
|
+
const were = sdkInstall.packages.length > 1 ? "were" : "was";
|
|
2012
|
+
notes.push(
|
|
2013
|
+
`${pkgs} ${were} already installed, so your package.json is already updated \u2014 only the code import above is still manual.`
|
|
2014
|
+
);
|
|
2015
|
+
}
|
|
1895
2016
|
return { filesTouched, notes };
|
|
1896
2017
|
}
|
|
1897
2018
|
const res2 = deps.executePlan(plan, void 0, { confirmDirty: true });
|
|
@@ -1980,7 +2101,8 @@ async function runLogin(parsed, deps) {
|
|
|
1980
2101
|
ui: deps.ui,
|
|
1981
2102
|
noBrowser: parsed.noBrowser,
|
|
1982
2103
|
fetchImpl: deps.fetchImpl,
|
|
1983
|
-
env: deps.env
|
|
2104
|
+
env: deps.env,
|
|
2105
|
+
allowInteractiveLogin: deps.isTTY
|
|
1984
2106
|
});
|
|
1985
2107
|
return 0;
|
|
1986
2108
|
} catch (err) {
|
|
@@ -2042,6 +2164,7 @@ export {
|
|
|
2042
2164
|
isCliEntrypoint,
|
|
2043
2165
|
parseArgs,
|
|
2044
2166
|
resolveSelection,
|
|
2167
|
+
resolveWorkspaceDir,
|
|
2045
2168
|
runBatchWizard,
|
|
2046
2169
|
runCli,
|
|
2047
2170
|
runWizard
|
|
@@ -69,10 +69,15 @@ declare function resolveViteEntry(cwd: string): string | null;
|
|
|
69
69
|
/**
|
|
70
70
|
* Resolve the React Native / Expo injection entry. Prefers, in order:
|
|
71
71
|
* 1. `app/_layout.{tsx,jsx,js}` — the expo-router root layout,
|
|
72
|
-
* 2. `
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
72
|
+
* 2. `src/app/_layout.{tsx,jsx,js}` — the same root layout under a `src/` dir,
|
|
73
|
+
* which is where create-expo-app's current DEFAULT template puts it,
|
|
74
|
+
* 3. `App.{tsx,jsx,ts,js}` — the classic Expo / bare-RN root component,
|
|
75
|
+
* 4. `index.{js,ts}` — the bare-RN `AppRegistry` entry.
|
|
76
|
+
* First existing file wins. The root `app/` layout keeps precedence over the
|
|
77
|
+
* `src/app/` one so existing projects resolve exactly as before; `src/app/` is a
|
|
78
|
+
* new fallback checked ahead of `App.*`. Returns null when none exist
|
|
79
|
+
* (→ ambiguous), so the resolver never points at an `expo/AppEntry` path inside
|
|
80
|
+
* node_modules.
|
|
76
81
|
*/
|
|
77
82
|
declare function resolveReactNativeEntry(cwd: string): string | null;
|
|
78
83
|
/** Pull a node script/module path out of a `start`-style command. */
|
|
@@ -69,10 +69,15 @@ declare function resolveViteEntry(cwd: string): string | null;
|
|
|
69
69
|
/**
|
|
70
70
|
* Resolve the React Native / Expo injection entry. Prefers, in order:
|
|
71
71
|
* 1. `app/_layout.{tsx,jsx,js}` — the expo-router root layout,
|
|
72
|
-
* 2. `
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
72
|
+
* 2. `src/app/_layout.{tsx,jsx,js}` — the same root layout under a `src/` dir,
|
|
73
|
+
* which is where create-expo-app's current DEFAULT template puts it,
|
|
74
|
+
* 3. `App.{tsx,jsx,ts,js}` — the classic Expo / bare-RN root component,
|
|
75
|
+
* 4. `index.{js,ts}` — the bare-RN `AppRegistry` entry.
|
|
76
|
+
* First existing file wins. The root `app/` layout keeps precedence over the
|
|
77
|
+
* `src/app/` one so existing projects resolve exactly as before; `src/app/` is a
|
|
78
|
+
* new fallback checked ahead of `App.*`. Returns null when none exist
|
|
79
|
+
* (→ ambiguous), so the resolver never points at an `expo/AppEntry` path inside
|
|
80
|
+
* node_modules.
|
|
76
81
|
*/
|
|
77
82
|
declare function resolveReactNativeEntry(cwd: string): string | null;
|
|
78
83
|
/** Pull a node script/module path out of a `start`-style command. */
|
package/dist/index.cjs
CHANGED
|
@@ -2188,6 +2188,7 @@ __export(index_exports, {
|
|
|
2188
2188
|
envKeyLine: () => envKeyLine,
|
|
2189
2189
|
executePlan: () => executePlan,
|
|
2190
2190
|
matchRecipe: () => matchRecipe,
|
|
2191
|
+
nestInitSnippet: () => nestInitSnippet,
|
|
2191
2192
|
nodeInitSnippet: () => nodeInitSnippet,
|
|
2192
2193
|
nuxtPluginSnippet: () => nuxtPluginSnippet,
|
|
2193
2194
|
parseNodeInvocation: () => parseNodeInvocation,
|
|
@@ -2375,6 +2376,9 @@ function resolveReactNativeEntry(cwd) {
|
|
|
2375
2376
|
import_node_path.default.join("app", "_layout.tsx"),
|
|
2376
2377
|
import_node_path.default.join("app", "_layout.jsx"),
|
|
2377
2378
|
import_node_path.default.join("app", "_layout.js"),
|
|
2379
|
+
import_node_path.default.join("src", "app", "_layout.tsx"),
|
|
2380
|
+
import_node_path.default.join("src", "app", "_layout.jsx"),
|
|
2381
|
+
import_node_path.default.join("src", "app", "_layout.js"),
|
|
2378
2382
|
"App.tsx",
|
|
2379
2383
|
"App.jsx",
|
|
2380
2384
|
"App.ts",
|
|
@@ -2854,6 +2858,20 @@ function nodeInitSnippet(endpoint) {
|
|
|
2854
2858
|
" .catch(() => {});"
|
|
2855
2859
|
].join("\n");
|
|
2856
2860
|
}
|
|
2861
|
+
function singleQuoted(value) {
|
|
2862
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
2863
|
+
}
|
|
2864
|
+
function nestInitSnippet(endpoint) {
|
|
2865
|
+
return [
|
|
2866
|
+
"// Crumbtrail \u2014 auto-captures uncaught exceptions, unhandled rejections, and",
|
|
2867
|
+
"// console.error. Key is read from process.env.CRUMBTRAIL_KEY, which autoCapture",
|
|
2868
|
+
"// loads from your .env (written by the CLI). Express apps can also add",
|
|
2869
|
+
"// createCrumbtrailExpressMiddleware for per-request capture.",
|
|
2870
|
+
"import('crumbtrail-node')",
|
|
2871
|
+
` .then(({ autoCapture }) => autoCapture({ endpoint: ${singleQuoted(endpoint)} }))`,
|
|
2872
|
+
" .catch(() => {});"
|
|
2873
|
+
].join("\n");
|
|
2874
|
+
}
|
|
2857
2875
|
function reactNativeInitSnippet(endpoint, apiKey) {
|
|
2858
2876
|
return [
|
|
2859
2877
|
'import { createReactNativeCrumbtrail } from "crumbtrail-react-native";',
|
|
@@ -2885,10 +2903,14 @@ var import_node_path2 = __toESM(require("path"), 1);
|
|
|
2885
2903
|
function realGitStatus(cwd, target) {
|
|
2886
2904
|
let out;
|
|
2887
2905
|
try {
|
|
2906
|
+
const env = Object.fromEntries(
|
|
2907
|
+
Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_"))
|
|
2908
|
+
);
|
|
2888
2909
|
out = (0, import_node_child_process.execFileSync)("git", ["status", "--porcelain", "--", target], {
|
|
2889
2910
|
cwd,
|
|
2890
2911
|
encoding: "utf8",
|
|
2891
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2912
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2913
|
+
env
|
|
2892
2914
|
});
|
|
2893
2915
|
} catch {
|
|
2894
2916
|
return { isRepo: false, tracked: false, dirty: false };
|
|
@@ -7069,7 +7091,7 @@ function planVite(input, io) {
|
|
|
7069
7091
|
return prependWithPreflight(input, io, input.entryFile, block);
|
|
7070
7092
|
}
|
|
7071
7093
|
function planNode(input, io) {
|
|
7072
|
-
const block = nodeInitSnippet(input.endpoint);
|
|
7094
|
+
const block = input.recipe === "nestjs" ? nestInitSnippet(input.endpoint) : nodeInitSnippet(input.endpoint);
|
|
7073
7095
|
const { action, warning } = buildEnvAction(input.cwd, input.apiKey, io);
|
|
7074
7096
|
const envWarnings = warning ? [warning] : [];
|
|
7075
7097
|
if (!input.entryFile) {
|
|
@@ -7347,6 +7369,7 @@ function executePlan(plan, io = defaultExecutorIO, options = {}) {
|
|
|
7347
7369
|
envKeyLine,
|
|
7348
7370
|
executePlan,
|
|
7349
7371
|
matchRecipe,
|
|
7372
|
+
nestInitSnippet,
|
|
7350
7373
|
nodeInitSnippet,
|
|
7351
7374
|
nuxtPluginSnippet,
|
|
7352
7375
|
parseNodeInvocation,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BuildPlanInput, a as BuildPlanOptions, c as DENO_UNSUPPORTED_REASON, d as DOCKER_COMING_SOON_NOTE, D as DetectResult, E as EnvAction, f as ExecuteOptions, g as ExecuteResult, h as ExecutorIO, G as GitTargetStatus, I as InjectIO, P as PackageManager, i as Plan, j as PlanKind, R as Recipe, W as WorkspacePackage, b as buildPlan, k as defaultExecutorIO, l as defaultInjectIO, m as detect, n as detectPackageManager, e as executePlan, o as matchRecipe, p as parseNodeInvocation, q as parsePnpmWorkspace, r as projectAlreadyWired, s as resolveAngularEntry, t as resolveNestEntry, u as resolveOtlpStack, v as resolveReactNativeEntry, w as resolveRemixEntry, x as resolveViteEntry, y as supportsInstrumentationClient } from './executor-
|
|
1
|
+
export { B as BuildPlanInput, a as BuildPlanOptions, c as DENO_UNSUPPORTED_REASON, d as DOCKER_COMING_SOON_NOTE, D as DetectResult, E as EnvAction, f as ExecuteOptions, g as ExecuteResult, h as ExecutorIO, G as GitTargetStatus, I as InjectIO, P as PackageManager, i as Plan, j as PlanKind, R as Recipe, W as WorkspacePackage, b as buildPlan, k as defaultExecutorIO, l as defaultInjectIO, m as detect, n as detectPackageManager, e as executePlan, o as matchRecipe, p as parseNodeInvocation, q as parsePnpmWorkspace, r as projectAlreadyWired, s as resolveAngularEntry, t as resolveNestEntry, u as resolveOtlpStack, v as resolveReactNativeEntry, w as resolveRemixEntry, x as resolveViteEntry, y as supportsInstrumentationClient } from './executor-BKiOvIvD.cjs';
|
|
2
2
|
import 'crumbtrail-design-system';
|
|
3
3
|
|
|
4
4
|
interface SourceShape {
|
|
@@ -55,6 +55,17 @@ declare function nuxtPluginSnippet(endpoint: string, apiKey: string): string;
|
|
|
55
55
|
* crumbtrail-node's README).
|
|
56
56
|
*/
|
|
57
57
|
declare function nodeInitSnippet(endpoint: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* NestJS server init. Byte-for-byte the same wiring as `nodeInitSnippet` — a
|
|
60
|
+
* dynamically-imported `autoCapture` prepended into `src/main.ts` — but emitted
|
|
61
|
+
* with SINGLE quotes to match Nest scaffolds' Prettier default
|
|
62
|
+
* (`singleQuote: true`). Nest is the only backend-JS recipe that gets its own
|
|
63
|
+
* snippet: its generator ships a `.prettierrc` with single quotes, so the
|
|
64
|
+
* double-quoted `nodeInitSnippet` produces cosmetic diff/lint noise on the very
|
|
65
|
+
* first commit. Every other backend-JS recipe (express/hono/fastify/node) keeps
|
|
66
|
+
* the double-quoted snippet, which matches Prettier's own default.
|
|
67
|
+
*/
|
|
68
|
+
declare function nestInitSnippet(endpoint: string): string;
|
|
58
69
|
/**
|
|
59
70
|
* React Native / Expo init block. Imperative + prepend-safe: it calls
|
|
60
71
|
* `createReactNativeCrumbtrail` (which runs `Crumbtrail.init` and installs the
|
|
@@ -75,4 +86,4 @@ declare function tauriInitSnippet(): string;
|
|
|
75
86
|
/** The single line the CLI writes into `.env` for the Node recipe. */
|
|
76
87
|
declare function envKeyLine(apiKey: string): string;
|
|
77
88
|
|
|
78
|
-
export { type SourceShape, analyzeSource, clientInitSnippet, envKeyLine, nodeInitSnippet, nuxtPluginSnippet, prependIntoSource, prologueEnd, reactNativeInitSnippet, referencesCrumbtrail, tauriInitSnippet, withTrailingNewline };
|
|
89
|
+
export { type SourceShape, analyzeSource, clientInitSnippet, envKeyLine, nestInitSnippet, nodeInitSnippet, nuxtPluginSnippet, prependIntoSource, prologueEnd, reactNativeInitSnippet, referencesCrumbtrail, tauriInitSnippet, withTrailingNewline };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BuildPlanInput, a as BuildPlanOptions, c as DENO_UNSUPPORTED_REASON, d as DOCKER_COMING_SOON_NOTE, D as DetectResult, E as EnvAction, f as ExecuteOptions, g as ExecuteResult, h as ExecutorIO, G as GitTargetStatus, I as InjectIO, P as PackageManager, i as Plan, j as PlanKind, R as Recipe, W as WorkspacePackage, b as buildPlan, k as defaultExecutorIO, l as defaultInjectIO, m as detect, n as detectPackageManager, e as executePlan, o as matchRecipe, p as parseNodeInvocation, q as parsePnpmWorkspace, r as projectAlreadyWired, s as resolveAngularEntry, t as resolveNestEntry, u as resolveOtlpStack, v as resolveReactNativeEntry, w as resolveRemixEntry, x as resolveViteEntry, y as supportsInstrumentationClient } from './executor-
|
|
1
|
+
export { B as BuildPlanInput, a as BuildPlanOptions, c as DENO_UNSUPPORTED_REASON, d as DOCKER_COMING_SOON_NOTE, D as DetectResult, E as EnvAction, f as ExecuteOptions, g as ExecuteResult, h as ExecutorIO, G as GitTargetStatus, I as InjectIO, P as PackageManager, i as Plan, j as PlanKind, R as Recipe, W as WorkspacePackage, b as buildPlan, k as defaultExecutorIO, l as defaultInjectIO, m as detect, n as detectPackageManager, e as executePlan, o as matchRecipe, p as parseNodeInvocation, q as parsePnpmWorkspace, r as projectAlreadyWired, s as resolveAngularEntry, t as resolveNestEntry, u as resolveOtlpStack, v as resolveReactNativeEntry, w as resolveRemixEntry, x as resolveViteEntry, y as supportsInstrumentationClient } from './executor-BKiOvIvD.js';
|
|
2
2
|
import 'crumbtrail-design-system';
|
|
3
3
|
|
|
4
4
|
interface SourceShape {
|
|
@@ -55,6 +55,17 @@ declare function nuxtPluginSnippet(endpoint: string, apiKey: string): string;
|
|
|
55
55
|
* crumbtrail-node's README).
|
|
56
56
|
*/
|
|
57
57
|
declare function nodeInitSnippet(endpoint: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* NestJS server init. Byte-for-byte the same wiring as `nodeInitSnippet` — a
|
|
60
|
+
* dynamically-imported `autoCapture` prepended into `src/main.ts` — but emitted
|
|
61
|
+
* with SINGLE quotes to match Nest scaffolds' Prettier default
|
|
62
|
+
* (`singleQuote: true`). Nest is the only backend-JS recipe that gets its own
|
|
63
|
+
* snippet: its generator ships a `.prettierrc` with single quotes, so the
|
|
64
|
+
* double-quoted `nodeInitSnippet` produces cosmetic diff/lint noise on the very
|
|
65
|
+
* first commit. Every other backend-JS recipe (express/hono/fastify/node) keeps
|
|
66
|
+
* the double-quoted snippet, which matches Prettier's own default.
|
|
67
|
+
*/
|
|
68
|
+
declare function nestInitSnippet(endpoint: string): string;
|
|
58
69
|
/**
|
|
59
70
|
* React Native / Expo init block. Imperative + prepend-safe: it calls
|
|
60
71
|
* `createReactNativeCrumbtrail` (which runs `Crumbtrail.init` and installs the
|
|
@@ -75,4 +86,4 @@ declare function tauriInitSnippet(): string;
|
|
|
75
86
|
/** The single line the CLI writes into `.env` for the Node recipe. */
|
|
76
87
|
declare function envKeyLine(apiKey: string): string;
|
|
77
88
|
|
|
78
|
-
export { type SourceShape, analyzeSource, clientInitSnippet, envKeyLine, nodeInitSnippet, nuxtPluginSnippet, prependIntoSource, prologueEnd, reactNativeInitSnippet, referencesCrumbtrail, tauriInitSnippet, withTrailingNewline };
|
|
89
|
+
export { type SourceShape, analyzeSource, clientInitSnippet, envKeyLine, nestInitSnippet, nodeInitSnippet, nuxtPluginSnippet, prependIntoSource, prologueEnd, reactNativeInitSnippet, referencesCrumbtrail, tauriInitSnippet, withTrailingNewline };
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
envKeyLine,
|
|
12
12
|
executePlan,
|
|
13
13
|
matchRecipe,
|
|
14
|
+
nestInitSnippet,
|
|
14
15
|
nodeInitSnippet,
|
|
15
16
|
nuxtPluginSnippet,
|
|
16
17
|
parseNodeInvocation,
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
29
30
|
supportsInstrumentationClient,
|
|
30
31
|
tauriInitSnippet,
|
|
31
32
|
withTrailingNewline
|
|
32
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-HAP4DPWC.js";
|
|
33
34
|
export {
|
|
34
35
|
DENO_UNSUPPORTED_REASON,
|
|
35
36
|
DOCKER_COMING_SOON_NOTE,
|
|
@@ -43,6 +44,7 @@ export {
|
|
|
43
44
|
envKeyLine,
|
|
44
45
|
executePlan,
|
|
45
46
|
matchRecipe,
|
|
47
|
+
nestInitSnippet,
|
|
46
48
|
nodeInitSnippet,
|
|
47
49
|
nuxtPluginSnippet,
|
|
48
50
|
parseNodeInvocation,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crumbtrail",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Crumbtrail setup CLI — detect your framework and wire the SDK into your app in one step.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -39,6 +39,11 @@
|
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^25.5.0",
|
|
44
|
+
"crumbtrail-design-system": "0.0.0",
|
|
45
|
+
"crumbtrail-install-shared": "0.1.0"
|
|
46
|
+
},
|
|
42
47
|
"scripts": {
|
|
43
48
|
"build": "tsup",
|
|
44
49
|
"test": "vitest run",
|
|
@@ -46,10 +51,5 @@
|
|
|
46
51
|
"test:watch": "vitest",
|
|
47
52
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
48
53
|
"clean": "rm -rf dist"
|
|
49
|
-
},
|
|
50
|
-
"devDependencies": {
|
|
51
|
-
"@types/node": "^25.5.0",
|
|
52
|
-
"crumbtrail-design-system": "workspace:*",
|
|
53
|
-
"crumbtrail-install-shared": "workspace:*"
|
|
54
54
|
}
|
|
55
|
-
}
|
|
55
|
+
}
|