crumbtrail 0.1.0 → 0.2.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/LICENSE +21 -0
- package/dist/bug-widget-LGIRLS76-WB3MQGAH.js +199 -0
- package/dist/chunk-MZDASWQ7.js +1570 -0
- package/dist/cli.cjs +440 -6125
- package/dist/cli.d.cts +35 -3
- package/dist/cli.d.ts +35 -3
- package/dist/cli.js +149 -26
- package/dist/{executor-DKDrkhxM.d.cts → executor-CW6Q5mhH.d.cts} +10 -5
- package/dist/{executor-DKDrkhxM.d.ts → executor-CW6Q5mhH.d.ts} +10 -5
- package/dist/index.cjs +145 -5952
- package/dist/index.d.cts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +3 -1
- package/package.json +12 -8
- package/dist/chunk-UFJ2BODB.js +0 -7401
package/dist/cli.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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-
|
|
3
|
-
import { Stack } from 'crumbtrail-
|
|
2
|
+
import { R as Recipe, D as DetectResult, P as PackageManager, b as buildPlan, e as executePlan } from './executor-CW6Q5mhH.cjs';
|
|
3
|
+
import { Stack } from 'crumbtrail-core';
|
|
4
4
|
|
|
5
5
|
/** Output sink — swappable in tests to capture lines instead of writing stdout. */
|
|
6
6
|
interface Ui {
|
|
@@ -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,6 +1,6 @@
|
|
|
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-
|
|
3
|
-
import { Stack } from 'crumbtrail-
|
|
2
|
+
import { R as Recipe, D as DetectResult, P as PackageManager, b as buildPlan, e as executePlan } from './executor-CW6Q5mhH.js';
|
|
3
|
+
import { Stack } from 'crumbtrail-core';
|
|
4
4
|
|
|
5
5
|
/** Output sink — swappable in tests to capture lines instead of writing stdout. */
|
|
6
6
|
interface Ui {
|
|
@@ -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-MZDASWQ7.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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Stack } from 'crumbtrail-
|
|
1
|
+
import { Stack } from 'crumbtrail-core';
|
|
2
2
|
|
|
3
3
|
/** Injection recipes, ordered most-specific-first during detection. */
|
|
4
4
|
type Recipe = "tauri" | "next" | "sveltekit" | "nuxt" | "remix" | "astro" | "angular" | "vite-spa" | "nestjs" | "express" | "hono" | "fastify" | "react-native" | "node" | "otlp";
|
|
@@ -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. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Stack } from 'crumbtrail-
|
|
1
|
+
import { Stack } from 'crumbtrail-core';
|
|
2
2
|
|
|
3
3
|
/** Injection recipes, ordered most-specific-first during detection. */
|
|
4
4
|
type Recipe = "tauri" | "next" | "sveltekit" | "nuxt" | "remix" | "astro" | "angular" | "vite-spa" | "nestjs" | "express" | "hono" | "fastify" | "react-native" | "node" | "otlp";
|
|
@@ -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. */
|