crumbtrail 0.1.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/dist/chunk-UFJ2BODB.js +7401 -0
- package/dist/cli.cjs +9412 -0
- package/dist/cli.d.cts +358 -0
- package/dist/cli.d.ts +358 -0
- package/dist/cli.js +2048 -0
- package/dist/executor-DKDrkhxM.d.cts +279 -0
- package/dist/executor-DKDrkhxM.d.ts +279 -0
- package/dist/index.cjs +7436 -0
- package/dist/index.d.cts +78 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +64 -0
- package/package.json +55 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { Stack } from 'crumbtrail-design-system';
|
|
2
|
+
|
|
3
|
+
/** Injection recipes, ordered most-specific-first during detection. */
|
|
4
|
+
type Recipe = "tauri" | "next" | "sveltekit" | "nuxt" | "remix" | "astro" | "angular" | "vite-spa" | "nestjs" | "express" | "hono" | "fastify" | "react-native" | "node" | "otlp";
|
|
5
|
+
type PackageManager = "pnpm" | "yarn" | "bun" | "npm";
|
|
6
|
+
interface WorkspacePackage {
|
|
7
|
+
/** package.json `name`, falling back to the directory basename. */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Absolute path to the workspace package directory. */
|
|
10
|
+
dir: string;
|
|
11
|
+
}
|
|
12
|
+
interface DetectResult {
|
|
13
|
+
cwd: string;
|
|
14
|
+
packageJsonPath: string | null;
|
|
15
|
+
/** Winning recipe, or null when nothing matched. */
|
|
16
|
+
recipe: Recipe | null;
|
|
17
|
+
packageManager: PackageManager | null;
|
|
18
|
+
/**
|
|
19
|
+
* Absolute path to the file the recipe would edit (vite-spa / node), when it
|
|
20
|
+
* could be resolved with confidence. null for create-a-new-file recipes and
|
|
21
|
+
* whenever resolution was ambiguous.
|
|
22
|
+
*/
|
|
23
|
+
entryFile: string | null;
|
|
24
|
+
/** Raw `next` version range from package.json, when the recipe is `next`. */
|
|
25
|
+
nextVersion: string | null;
|
|
26
|
+
/**
|
|
27
|
+
* The non-JS backend Stack resolved from filesystem markers when `recipe` is
|
|
28
|
+
* `"otlp"` (django/flask/fastapi/go/rails/dotnet). null for every other recipe.
|
|
29
|
+
* This is the ONE recipe that carries a variable Stack; call sites must prefer
|
|
30
|
+
* it over the static `RECIPE_REGISTRY["otlp"].stack` placeholder.
|
|
31
|
+
*/
|
|
32
|
+
otlpStack: Stack | null;
|
|
33
|
+
isMonorepo: boolean;
|
|
34
|
+
/** Workspace packages when `isMonorepo` — the caller picks which app to wire. */
|
|
35
|
+
workspaces: WorkspacePackage[];
|
|
36
|
+
/**
|
|
37
|
+
* True when the caller must resolve a choice before injecting: monorepo root,
|
|
38
|
+
* no recipe matched, or the recipe's entry file could not be resolved.
|
|
39
|
+
*/
|
|
40
|
+
ambiguous: boolean;
|
|
41
|
+
/** Human-readable trail of why detection landed where it did. */
|
|
42
|
+
reasons: string[];
|
|
43
|
+
/**
|
|
44
|
+
* Non-blocking, informational messages about the project that do NOT affect
|
|
45
|
+
* `recipe`/`ambiguous`/`isMonorepo`/`entryFile` — e.g. a "Docker files found,
|
|
46
|
+
* infra evidence sources coming soon" note. Kept separate from `reasons` (the
|
|
47
|
+
* detection trail); the caller surfaces these on both success and no-recipe
|
|
48
|
+
* paths without changing any outcome.
|
|
49
|
+
*/
|
|
50
|
+
notes: string[];
|
|
51
|
+
}
|
|
52
|
+
interface PackageJson {
|
|
53
|
+
name?: string;
|
|
54
|
+
main?: string;
|
|
55
|
+
bin?: string | Record<string, string>;
|
|
56
|
+
scripts?: Record<string, string>;
|
|
57
|
+
dependencies?: Record<string, string>;
|
|
58
|
+
devDependencies?: Record<string, string>;
|
|
59
|
+
workspaces?: string[] | {
|
|
60
|
+
packages?: string[];
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Walk up from `startDir` to the filesystem root looking for a known lockfile. */
|
|
64
|
+
declare function detectPackageManager(startDir: string): PackageManager | null;
|
|
65
|
+
/** Extract the `packages:` list from a pnpm-workspace.yaml without a YAML dep. */
|
|
66
|
+
declare function parsePnpmWorkspace(text: string): string[];
|
|
67
|
+
/** Resolve the Vite entry from index.html's `<script type="module" src>` tag. */
|
|
68
|
+
declare function resolveViteEntry(cwd: string): string | null;
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the React Native / Expo injection entry. Prefers, in order:
|
|
71
|
+
* 1. `app/_layout.{tsx,jsx,js}` — the expo-router root layout,
|
|
72
|
+
* 2. `App.{tsx,jsx,ts,js}` — the classic Expo / bare-RN root component,
|
|
73
|
+
* 3. `index.{js,ts}` — the bare-RN `AppRegistry` entry.
|
|
74
|
+
* First existing file wins. Returns null when none exist (→ ambiguous), so the
|
|
75
|
+
* resolver never points at an `expo/AppEntry` path inside node_modules.
|
|
76
|
+
*/
|
|
77
|
+
declare function resolveReactNativeEntry(cwd: string): string | null;
|
|
78
|
+
/** Pull a node script/module path out of a `start`-style command. */
|
|
79
|
+
declare function parseNodeInvocation(script: string): string | null;
|
|
80
|
+
/**
|
|
81
|
+
* Resolve the NestJS injection entry. Nest's bootstrap lives at `src/main.ts`
|
|
82
|
+
* (or `src/main.js`); `resolveNodeEntry` cannot find it because the `start`
|
|
83
|
+
* script is `nest start` (not a bare node invocation) and `main` points at
|
|
84
|
+
* `dist/`. First existing file wins; null when neither exists (→ ambiguous).
|
|
85
|
+
*/
|
|
86
|
+
declare function resolveNestEntry(cwd: string): string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Resolve the Remix / React Router v7 hydration entry. Prefers
|
|
89
|
+
* `app/entry.client.{tsx,jsx,js}` in that order. Returns null when none exist —
|
|
90
|
+
* the recipe then FALLS BACK rather than creating one, because a bare init-only
|
|
91
|
+
* entry.client would omit hydrateRoot/<RemixBrowser> and break the app.
|
|
92
|
+
*/
|
|
93
|
+
declare function resolveRemixEntry(cwd: string): string | null;
|
|
94
|
+
/**
|
|
95
|
+
* Resolve the Angular bootstrap entry — deterministically `src/main.ts`. Null
|
|
96
|
+
* when absent (→ ambiguous). Prepending an import + `Crumbtrail.init` above
|
|
97
|
+
* Angular's `bootstrapApplication`/`platformBrowserDynamic` call is safe.
|
|
98
|
+
*/
|
|
99
|
+
declare function resolveAngularEntry(cwd: string): string | null;
|
|
100
|
+
/**
|
|
101
|
+
* Resolve a non-JS backend from filesystem markers — the `otlp` recipe's Stack.
|
|
102
|
+
* Filesystem-only: no code is executed; Python/Ruby manifests are parsed purely
|
|
103
|
+
* as text. Precedence is deliberate and documented:
|
|
104
|
+
* 1. `manage.py` → django
|
|
105
|
+
* 2. pyproject.toml/requirements.txt dep → fastapi (wins over flask if BOTH
|
|
106
|
+
* tokens appear) else flask
|
|
107
|
+
* 3. `go.mod` → go
|
|
108
|
+
* 4. `Gemfile` referencing rails → rails
|
|
109
|
+
* 5. any `*.csproj` in the root → dotnet
|
|
110
|
+
* Returns null when no marker matches. Works with NO package.json present.
|
|
111
|
+
*/
|
|
112
|
+
declare function resolveOtlpStack(root: string): {
|
|
113
|
+
stack: Stack;
|
|
114
|
+
reason: string;
|
|
115
|
+
} | null;
|
|
116
|
+
/**
|
|
117
|
+
* Distinct `reasons` string pushed when a Deno project is detected (deno.json /
|
|
118
|
+
* deno.jsonc with no package.json). Exported so the wizard can recognize the
|
|
119
|
+
* signal and print a Deno-specific line instead of the generic unsupported hint.
|
|
120
|
+
*/
|
|
121
|
+
declare const DENO_UNSUPPORTED_REASON = "Deno projects aren't supported yet";
|
|
122
|
+
/**
|
|
123
|
+
* Informational, non-blocking note emitted when Docker/Compose files are present.
|
|
124
|
+
* References that infra evidence sources (e.g. the `docker` stack in
|
|
125
|
+
* install-shared's INFRA_STACKS) are coming soon. NEVER affects recipe/ambiguity.
|
|
126
|
+
*/
|
|
127
|
+
declare const DOCKER_COMING_SOON_NOTE = "Docker/Compose files found \u2014 infra evidence sources (e.g. docker) are coming soon.";
|
|
128
|
+
/** Inputs shared by every recipe matcher — filesystem-derived facts only. */
|
|
129
|
+
interface MatchContext {
|
|
130
|
+
root: string;
|
|
131
|
+
deps: Record<string, string>;
|
|
132
|
+
pkg: PackageJson | null;
|
|
133
|
+
/** Matchers push their user-facing detection reasons here (side effect). */
|
|
134
|
+
reasons: string[];
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Evaluate the ordered matcher ladder, returning the first match. When nothing
|
|
138
|
+
* matches, recipe is null with no entry/version. Reasons are recorded as a side
|
|
139
|
+
* effect on `ctx.reasons`, exactly as the original if/else chain did.
|
|
140
|
+
*/
|
|
141
|
+
declare function matchRecipe(ctx: MatchContext): {
|
|
142
|
+
recipe: Recipe | null;
|
|
143
|
+
entryFile: string | null;
|
|
144
|
+
nextVersion: string | null;
|
|
145
|
+
otlpStack: Stack | null;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Classify a single package directory. For a monorepo root this reports the
|
|
149
|
+
* workspace list and marks the result ambiguous — the caller picks a workspace
|
|
150
|
+
* and re-runs `detect` inside it.
|
|
151
|
+
*/
|
|
152
|
+
declare function detect(cwd: string): DetectResult;
|
|
153
|
+
|
|
154
|
+
/** The shape of a plan the executor knows how to (or refuses to) apply. */
|
|
155
|
+
type PlanKind = "create" | "prepend" | "skip-already-wired" | "needs-confirm-dirty" | "fallback-ai" | "otlp-guidance";
|
|
156
|
+
/** A create/append to `.env` carried alongside the Node recipe's plan. */
|
|
157
|
+
interface EnvAction {
|
|
158
|
+
/** Absolute path to the `.env` file. */
|
|
159
|
+
targetPath: string;
|
|
160
|
+
/** The `CRUMBTRAIL_KEY=...` line to ensure is present. */
|
|
161
|
+
line: string;
|
|
162
|
+
/** Set when `.env` is not covered by `.gitignore` (key could get committed). */
|
|
163
|
+
gitignoreWarning?: string;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* A fully-resolved, side-effect-free description of what injection would do.
|
|
167
|
+
* The executor turns this into filesystem writes; nothing here performs I/O.
|
|
168
|
+
*/
|
|
169
|
+
interface Plan {
|
|
170
|
+
recipe: Recipe;
|
|
171
|
+
kind: PlanKind;
|
|
172
|
+
/** Absolute path of the file to create/edit. null for skip/fallback plans. */
|
|
173
|
+
targetPath: string | null;
|
|
174
|
+
/**
|
|
175
|
+
* For `create`: the full file body. For `prepend`/`needs-confirm-dirty`: the
|
|
176
|
+
* block to prepend. null for skip/fallback plans.
|
|
177
|
+
*/
|
|
178
|
+
content: string | null;
|
|
179
|
+
/** Non-fatal notes to surface to the user. */
|
|
180
|
+
warnings: string[];
|
|
181
|
+
/** fallback-ai: the ready-to-paste code snippet, key already filled in. */
|
|
182
|
+
snippet?: string;
|
|
183
|
+
/** fallback-ai: the `buildAgentPrompt` output for a coding agent. */
|
|
184
|
+
agentPrompt?: string;
|
|
185
|
+
/** Node recipe: the `.env` write to perform alongside the entry edit. */
|
|
186
|
+
envAction?: EnvAction;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface GitTargetStatus {
|
|
190
|
+
/** false when `cwd` is not inside a git work tree. */
|
|
191
|
+
isRepo: boolean;
|
|
192
|
+
/** false for an untracked (never-committed) file. */
|
|
193
|
+
tracked: boolean;
|
|
194
|
+
/** true when the target has uncommitted changes or is untracked. */
|
|
195
|
+
dirty: boolean;
|
|
196
|
+
}
|
|
197
|
+
/** Read-only inspection surface consumed by the injection plan-builders. */
|
|
198
|
+
interface InjectIO {
|
|
199
|
+
exists(p: string): boolean;
|
|
200
|
+
/** File contents, or null when it does not exist / cannot be read. */
|
|
201
|
+
readFile(p: string): string | null;
|
|
202
|
+
/** git porcelain status for a single target path. */
|
|
203
|
+
gitStatus(cwd: string, target: string): GitTargetStatus;
|
|
204
|
+
/** `.gitignore` contents at `cwd`, or null when absent. */
|
|
205
|
+
readGitignore(cwd: string): string | null;
|
|
206
|
+
}
|
|
207
|
+
/** The default real-filesystem + git implementation. */
|
|
208
|
+
declare const defaultInjectIO: InjectIO;
|
|
209
|
+
|
|
210
|
+
interface BuildPlanOptions {
|
|
211
|
+
/** Prepend into a dirty (uncommitted) target instead of asking to confirm. */
|
|
212
|
+
force?: boolean;
|
|
213
|
+
}
|
|
214
|
+
interface BuildPlanInput {
|
|
215
|
+
cwd: string;
|
|
216
|
+
recipe: Recipe;
|
|
217
|
+
endpoint: string;
|
|
218
|
+
apiKey: string;
|
|
219
|
+
/** Absolute entry path for vite-spa / node (from detection). */
|
|
220
|
+
entryFile?: string | null;
|
|
221
|
+
/** Raw `next` version range from detection (drives new-file vs legacy-prepend). */
|
|
222
|
+
nextVersion?: string | null;
|
|
223
|
+
/**
|
|
224
|
+
* The detected non-JS backend Stack for the `otlp` recipe (from
|
|
225
|
+
* `DetectResult.otlpStack`). Drives the guidance agent prompt; ignored for
|
|
226
|
+
* every other recipe.
|
|
227
|
+
*/
|
|
228
|
+
stack?: Stack;
|
|
229
|
+
options?: BuildPlanOptions;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* True when this package already depends on a Crumbtrail SDK. Load-bearing for
|
|
233
|
+
* the batch installer, which must decide whether to provision a service BEFORE
|
|
234
|
+
* it builds a plan — `buildPlan` uses this to self-cancel into
|
|
235
|
+
* `skip-already-wired`, so a re-run must not mint a second key for a service
|
|
236
|
+
* that is going to be skipped anyway.
|
|
237
|
+
*/
|
|
238
|
+
declare function projectAlreadyWired(cwd: string, io: InjectIO): boolean;
|
|
239
|
+
/**
|
|
240
|
+
* `instrumentation-client.ts` is auto-loaded from Next 15.3+. Parse the leading
|
|
241
|
+
* numeric range; treat non-numeric ranges ("latest", "canary", workspace:) as
|
|
242
|
+
* new-enough.
|
|
243
|
+
*/
|
|
244
|
+
declare function supportsInstrumentationClient(version: string | null | undefined): boolean;
|
|
245
|
+
/**
|
|
246
|
+
* Build the injection Plan for a detected recipe. Reads only (via `io`); the
|
|
247
|
+
* returned Plan is plain data the executor applies all-or-nothing.
|
|
248
|
+
*/
|
|
249
|
+
declare function buildPlan(input: BuildPlanInput, io?: InjectIO): Plan;
|
|
250
|
+
|
|
251
|
+
/** Write boundary for the executor — swappable in tests. */
|
|
252
|
+
interface ExecutorIO {
|
|
253
|
+
exists(p: string): boolean;
|
|
254
|
+
readFile(p: string): string | null;
|
|
255
|
+
writeFile(p: string, content: string): void;
|
|
256
|
+
mkdirp(dir: string): void;
|
|
257
|
+
remove(p: string): void;
|
|
258
|
+
}
|
|
259
|
+
declare const defaultExecutorIO: ExecutorIO;
|
|
260
|
+
interface ExecuteOptions {
|
|
261
|
+
/** Apply a `needs-confirm-dirty` plan (the user confirmed / passed --force). */
|
|
262
|
+
confirmDirty?: boolean;
|
|
263
|
+
}
|
|
264
|
+
interface ExecuteResult {
|
|
265
|
+
kind: Plan["kind"];
|
|
266
|
+
/** Absolute paths actually written. */
|
|
267
|
+
written: string[];
|
|
268
|
+
/** True when nothing was written (skip / fallback / unconfirmed-dirty). */
|
|
269
|
+
skipped: boolean;
|
|
270
|
+
message: string;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Execute a Plan. skip/fallback plans (and unconfirmed dirty plans) perform no
|
|
274
|
+
* writes. create/prepend plans — plus any Node `.env` action — are applied
|
|
275
|
+
* all-or-nothing.
|
|
276
|
+
*/
|
|
277
|
+
declare function executePlan(plan: Plan, io?: ExecutorIO, options?: ExecuteOptions): ExecuteResult;
|
|
278
|
+
|
|
279
|
+
export { type BuildPlanInput as B, type DetectResult as D, type EnvAction as E, type GitTargetStatus as G, type InjectIO as I, type PackageManager as P, type Recipe as R, type WorkspacePackage as W, type BuildPlanOptions as a, buildPlan as b, DENO_UNSUPPORTED_REASON as c, DOCKER_COMING_SOON_NOTE as d, executePlan as e, type ExecuteOptions as f, type ExecuteResult as g, type ExecutorIO as h, type Plan as i, type PlanKind as j, defaultExecutorIO as k, defaultInjectIO as l, detect as m, detectPackageManager as n, matchRecipe as o, parseNodeInvocation as p, parsePnpmWorkspace as q, projectAlreadyWired as r, resolveAngularEntry as s, resolveNestEntry as t, resolveOtlpStack as u, resolveReactNativeEntry as v, resolveRemixEntry as w, resolveViteEntry as x, supportsInstrumentationClient as y };
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { Stack } from 'crumbtrail-design-system';
|
|
2
|
+
|
|
3
|
+
/** Injection recipes, ordered most-specific-first during detection. */
|
|
4
|
+
type Recipe = "tauri" | "next" | "sveltekit" | "nuxt" | "remix" | "astro" | "angular" | "vite-spa" | "nestjs" | "express" | "hono" | "fastify" | "react-native" | "node" | "otlp";
|
|
5
|
+
type PackageManager = "pnpm" | "yarn" | "bun" | "npm";
|
|
6
|
+
interface WorkspacePackage {
|
|
7
|
+
/** package.json `name`, falling back to the directory basename. */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Absolute path to the workspace package directory. */
|
|
10
|
+
dir: string;
|
|
11
|
+
}
|
|
12
|
+
interface DetectResult {
|
|
13
|
+
cwd: string;
|
|
14
|
+
packageJsonPath: string | null;
|
|
15
|
+
/** Winning recipe, or null when nothing matched. */
|
|
16
|
+
recipe: Recipe | null;
|
|
17
|
+
packageManager: PackageManager | null;
|
|
18
|
+
/**
|
|
19
|
+
* Absolute path to the file the recipe would edit (vite-spa / node), when it
|
|
20
|
+
* could be resolved with confidence. null for create-a-new-file recipes and
|
|
21
|
+
* whenever resolution was ambiguous.
|
|
22
|
+
*/
|
|
23
|
+
entryFile: string | null;
|
|
24
|
+
/** Raw `next` version range from package.json, when the recipe is `next`. */
|
|
25
|
+
nextVersion: string | null;
|
|
26
|
+
/**
|
|
27
|
+
* The non-JS backend Stack resolved from filesystem markers when `recipe` is
|
|
28
|
+
* `"otlp"` (django/flask/fastapi/go/rails/dotnet). null for every other recipe.
|
|
29
|
+
* This is the ONE recipe that carries a variable Stack; call sites must prefer
|
|
30
|
+
* it over the static `RECIPE_REGISTRY["otlp"].stack` placeholder.
|
|
31
|
+
*/
|
|
32
|
+
otlpStack: Stack | null;
|
|
33
|
+
isMonorepo: boolean;
|
|
34
|
+
/** Workspace packages when `isMonorepo` — the caller picks which app to wire. */
|
|
35
|
+
workspaces: WorkspacePackage[];
|
|
36
|
+
/**
|
|
37
|
+
* True when the caller must resolve a choice before injecting: monorepo root,
|
|
38
|
+
* no recipe matched, or the recipe's entry file could not be resolved.
|
|
39
|
+
*/
|
|
40
|
+
ambiguous: boolean;
|
|
41
|
+
/** Human-readable trail of why detection landed where it did. */
|
|
42
|
+
reasons: string[];
|
|
43
|
+
/**
|
|
44
|
+
* Non-blocking, informational messages about the project that do NOT affect
|
|
45
|
+
* `recipe`/`ambiguous`/`isMonorepo`/`entryFile` — e.g. a "Docker files found,
|
|
46
|
+
* infra evidence sources coming soon" note. Kept separate from `reasons` (the
|
|
47
|
+
* detection trail); the caller surfaces these on both success and no-recipe
|
|
48
|
+
* paths without changing any outcome.
|
|
49
|
+
*/
|
|
50
|
+
notes: string[];
|
|
51
|
+
}
|
|
52
|
+
interface PackageJson {
|
|
53
|
+
name?: string;
|
|
54
|
+
main?: string;
|
|
55
|
+
bin?: string | Record<string, string>;
|
|
56
|
+
scripts?: Record<string, string>;
|
|
57
|
+
dependencies?: Record<string, string>;
|
|
58
|
+
devDependencies?: Record<string, string>;
|
|
59
|
+
workspaces?: string[] | {
|
|
60
|
+
packages?: string[];
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Walk up from `startDir` to the filesystem root looking for a known lockfile. */
|
|
64
|
+
declare function detectPackageManager(startDir: string): PackageManager | null;
|
|
65
|
+
/** Extract the `packages:` list from a pnpm-workspace.yaml without a YAML dep. */
|
|
66
|
+
declare function parsePnpmWorkspace(text: string): string[];
|
|
67
|
+
/** Resolve the Vite entry from index.html's `<script type="module" src>` tag. */
|
|
68
|
+
declare function resolveViteEntry(cwd: string): string | null;
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the React Native / Expo injection entry. Prefers, in order:
|
|
71
|
+
* 1. `app/_layout.{tsx,jsx,js}` — the expo-router root layout,
|
|
72
|
+
* 2. `App.{tsx,jsx,ts,js}` — the classic Expo / bare-RN root component,
|
|
73
|
+
* 3. `index.{js,ts}` — the bare-RN `AppRegistry` entry.
|
|
74
|
+
* First existing file wins. Returns null when none exist (→ ambiguous), so the
|
|
75
|
+
* resolver never points at an `expo/AppEntry` path inside node_modules.
|
|
76
|
+
*/
|
|
77
|
+
declare function resolveReactNativeEntry(cwd: string): string | null;
|
|
78
|
+
/** Pull a node script/module path out of a `start`-style command. */
|
|
79
|
+
declare function parseNodeInvocation(script: string): string | null;
|
|
80
|
+
/**
|
|
81
|
+
* Resolve the NestJS injection entry. Nest's bootstrap lives at `src/main.ts`
|
|
82
|
+
* (or `src/main.js`); `resolveNodeEntry` cannot find it because the `start`
|
|
83
|
+
* script is `nest start` (not a bare node invocation) and `main` points at
|
|
84
|
+
* `dist/`. First existing file wins; null when neither exists (→ ambiguous).
|
|
85
|
+
*/
|
|
86
|
+
declare function resolveNestEntry(cwd: string): string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Resolve the Remix / React Router v7 hydration entry. Prefers
|
|
89
|
+
* `app/entry.client.{tsx,jsx,js}` in that order. Returns null when none exist —
|
|
90
|
+
* the recipe then FALLS BACK rather than creating one, because a bare init-only
|
|
91
|
+
* entry.client would omit hydrateRoot/<RemixBrowser> and break the app.
|
|
92
|
+
*/
|
|
93
|
+
declare function resolveRemixEntry(cwd: string): string | null;
|
|
94
|
+
/**
|
|
95
|
+
* Resolve the Angular bootstrap entry — deterministically `src/main.ts`. Null
|
|
96
|
+
* when absent (→ ambiguous). Prepending an import + `Crumbtrail.init` above
|
|
97
|
+
* Angular's `bootstrapApplication`/`platformBrowserDynamic` call is safe.
|
|
98
|
+
*/
|
|
99
|
+
declare function resolveAngularEntry(cwd: string): string | null;
|
|
100
|
+
/**
|
|
101
|
+
* Resolve a non-JS backend from filesystem markers — the `otlp` recipe's Stack.
|
|
102
|
+
* Filesystem-only: no code is executed; Python/Ruby manifests are parsed purely
|
|
103
|
+
* as text. Precedence is deliberate and documented:
|
|
104
|
+
* 1. `manage.py` → django
|
|
105
|
+
* 2. pyproject.toml/requirements.txt dep → fastapi (wins over flask if BOTH
|
|
106
|
+
* tokens appear) else flask
|
|
107
|
+
* 3. `go.mod` → go
|
|
108
|
+
* 4. `Gemfile` referencing rails → rails
|
|
109
|
+
* 5. any `*.csproj` in the root → dotnet
|
|
110
|
+
* Returns null when no marker matches. Works with NO package.json present.
|
|
111
|
+
*/
|
|
112
|
+
declare function resolveOtlpStack(root: string): {
|
|
113
|
+
stack: Stack;
|
|
114
|
+
reason: string;
|
|
115
|
+
} | null;
|
|
116
|
+
/**
|
|
117
|
+
* Distinct `reasons` string pushed when a Deno project is detected (deno.json /
|
|
118
|
+
* deno.jsonc with no package.json). Exported so the wizard can recognize the
|
|
119
|
+
* signal and print a Deno-specific line instead of the generic unsupported hint.
|
|
120
|
+
*/
|
|
121
|
+
declare const DENO_UNSUPPORTED_REASON = "Deno projects aren't supported yet";
|
|
122
|
+
/**
|
|
123
|
+
* Informational, non-blocking note emitted when Docker/Compose files are present.
|
|
124
|
+
* References that infra evidence sources (e.g. the `docker` stack in
|
|
125
|
+
* install-shared's INFRA_STACKS) are coming soon. NEVER affects recipe/ambiguity.
|
|
126
|
+
*/
|
|
127
|
+
declare const DOCKER_COMING_SOON_NOTE = "Docker/Compose files found \u2014 infra evidence sources (e.g. docker) are coming soon.";
|
|
128
|
+
/** Inputs shared by every recipe matcher — filesystem-derived facts only. */
|
|
129
|
+
interface MatchContext {
|
|
130
|
+
root: string;
|
|
131
|
+
deps: Record<string, string>;
|
|
132
|
+
pkg: PackageJson | null;
|
|
133
|
+
/** Matchers push their user-facing detection reasons here (side effect). */
|
|
134
|
+
reasons: string[];
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Evaluate the ordered matcher ladder, returning the first match. When nothing
|
|
138
|
+
* matches, recipe is null with no entry/version. Reasons are recorded as a side
|
|
139
|
+
* effect on `ctx.reasons`, exactly as the original if/else chain did.
|
|
140
|
+
*/
|
|
141
|
+
declare function matchRecipe(ctx: MatchContext): {
|
|
142
|
+
recipe: Recipe | null;
|
|
143
|
+
entryFile: string | null;
|
|
144
|
+
nextVersion: string | null;
|
|
145
|
+
otlpStack: Stack | null;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Classify a single package directory. For a monorepo root this reports the
|
|
149
|
+
* workspace list and marks the result ambiguous — the caller picks a workspace
|
|
150
|
+
* and re-runs `detect` inside it.
|
|
151
|
+
*/
|
|
152
|
+
declare function detect(cwd: string): DetectResult;
|
|
153
|
+
|
|
154
|
+
/** The shape of a plan the executor knows how to (or refuses to) apply. */
|
|
155
|
+
type PlanKind = "create" | "prepend" | "skip-already-wired" | "needs-confirm-dirty" | "fallback-ai" | "otlp-guidance";
|
|
156
|
+
/** A create/append to `.env` carried alongside the Node recipe's plan. */
|
|
157
|
+
interface EnvAction {
|
|
158
|
+
/** Absolute path to the `.env` file. */
|
|
159
|
+
targetPath: string;
|
|
160
|
+
/** The `CRUMBTRAIL_KEY=...` line to ensure is present. */
|
|
161
|
+
line: string;
|
|
162
|
+
/** Set when `.env` is not covered by `.gitignore` (key could get committed). */
|
|
163
|
+
gitignoreWarning?: string;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* A fully-resolved, side-effect-free description of what injection would do.
|
|
167
|
+
* The executor turns this into filesystem writes; nothing here performs I/O.
|
|
168
|
+
*/
|
|
169
|
+
interface Plan {
|
|
170
|
+
recipe: Recipe;
|
|
171
|
+
kind: PlanKind;
|
|
172
|
+
/** Absolute path of the file to create/edit. null for skip/fallback plans. */
|
|
173
|
+
targetPath: string | null;
|
|
174
|
+
/**
|
|
175
|
+
* For `create`: the full file body. For `prepend`/`needs-confirm-dirty`: the
|
|
176
|
+
* block to prepend. null for skip/fallback plans.
|
|
177
|
+
*/
|
|
178
|
+
content: string | null;
|
|
179
|
+
/** Non-fatal notes to surface to the user. */
|
|
180
|
+
warnings: string[];
|
|
181
|
+
/** fallback-ai: the ready-to-paste code snippet, key already filled in. */
|
|
182
|
+
snippet?: string;
|
|
183
|
+
/** fallback-ai: the `buildAgentPrompt` output for a coding agent. */
|
|
184
|
+
agentPrompt?: string;
|
|
185
|
+
/** Node recipe: the `.env` write to perform alongside the entry edit. */
|
|
186
|
+
envAction?: EnvAction;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface GitTargetStatus {
|
|
190
|
+
/** false when `cwd` is not inside a git work tree. */
|
|
191
|
+
isRepo: boolean;
|
|
192
|
+
/** false for an untracked (never-committed) file. */
|
|
193
|
+
tracked: boolean;
|
|
194
|
+
/** true when the target has uncommitted changes or is untracked. */
|
|
195
|
+
dirty: boolean;
|
|
196
|
+
}
|
|
197
|
+
/** Read-only inspection surface consumed by the injection plan-builders. */
|
|
198
|
+
interface InjectIO {
|
|
199
|
+
exists(p: string): boolean;
|
|
200
|
+
/** File contents, or null when it does not exist / cannot be read. */
|
|
201
|
+
readFile(p: string): string | null;
|
|
202
|
+
/** git porcelain status for a single target path. */
|
|
203
|
+
gitStatus(cwd: string, target: string): GitTargetStatus;
|
|
204
|
+
/** `.gitignore` contents at `cwd`, or null when absent. */
|
|
205
|
+
readGitignore(cwd: string): string | null;
|
|
206
|
+
}
|
|
207
|
+
/** The default real-filesystem + git implementation. */
|
|
208
|
+
declare const defaultInjectIO: InjectIO;
|
|
209
|
+
|
|
210
|
+
interface BuildPlanOptions {
|
|
211
|
+
/** Prepend into a dirty (uncommitted) target instead of asking to confirm. */
|
|
212
|
+
force?: boolean;
|
|
213
|
+
}
|
|
214
|
+
interface BuildPlanInput {
|
|
215
|
+
cwd: string;
|
|
216
|
+
recipe: Recipe;
|
|
217
|
+
endpoint: string;
|
|
218
|
+
apiKey: string;
|
|
219
|
+
/** Absolute entry path for vite-spa / node (from detection). */
|
|
220
|
+
entryFile?: string | null;
|
|
221
|
+
/** Raw `next` version range from detection (drives new-file vs legacy-prepend). */
|
|
222
|
+
nextVersion?: string | null;
|
|
223
|
+
/**
|
|
224
|
+
* The detected non-JS backend Stack for the `otlp` recipe (from
|
|
225
|
+
* `DetectResult.otlpStack`). Drives the guidance agent prompt; ignored for
|
|
226
|
+
* every other recipe.
|
|
227
|
+
*/
|
|
228
|
+
stack?: Stack;
|
|
229
|
+
options?: BuildPlanOptions;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* True when this package already depends on a Crumbtrail SDK. Load-bearing for
|
|
233
|
+
* the batch installer, which must decide whether to provision a service BEFORE
|
|
234
|
+
* it builds a plan — `buildPlan` uses this to self-cancel into
|
|
235
|
+
* `skip-already-wired`, so a re-run must not mint a second key for a service
|
|
236
|
+
* that is going to be skipped anyway.
|
|
237
|
+
*/
|
|
238
|
+
declare function projectAlreadyWired(cwd: string, io: InjectIO): boolean;
|
|
239
|
+
/**
|
|
240
|
+
* `instrumentation-client.ts` is auto-loaded from Next 15.3+. Parse the leading
|
|
241
|
+
* numeric range; treat non-numeric ranges ("latest", "canary", workspace:) as
|
|
242
|
+
* new-enough.
|
|
243
|
+
*/
|
|
244
|
+
declare function supportsInstrumentationClient(version: string | null | undefined): boolean;
|
|
245
|
+
/**
|
|
246
|
+
* Build the injection Plan for a detected recipe. Reads only (via `io`); the
|
|
247
|
+
* returned Plan is plain data the executor applies all-or-nothing.
|
|
248
|
+
*/
|
|
249
|
+
declare function buildPlan(input: BuildPlanInput, io?: InjectIO): Plan;
|
|
250
|
+
|
|
251
|
+
/** Write boundary for the executor — swappable in tests. */
|
|
252
|
+
interface ExecutorIO {
|
|
253
|
+
exists(p: string): boolean;
|
|
254
|
+
readFile(p: string): string | null;
|
|
255
|
+
writeFile(p: string, content: string): void;
|
|
256
|
+
mkdirp(dir: string): void;
|
|
257
|
+
remove(p: string): void;
|
|
258
|
+
}
|
|
259
|
+
declare const defaultExecutorIO: ExecutorIO;
|
|
260
|
+
interface ExecuteOptions {
|
|
261
|
+
/** Apply a `needs-confirm-dirty` plan (the user confirmed / passed --force). */
|
|
262
|
+
confirmDirty?: boolean;
|
|
263
|
+
}
|
|
264
|
+
interface ExecuteResult {
|
|
265
|
+
kind: Plan["kind"];
|
|
266
|
+
/** Absolute paths actually written. */
|
|
267
|
+
written: string[];
|
|
268
|
+
/** True when nothing was written (skip / fallback / unconfirmed-dirty). */
|
|
269
|
+
skipped: boolean;
|
|
270
|
+
message: string;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Execute a Plan. skip/fallback plans (and unconfirmed dirty plans) perform no
|
|
274
|
+
* writes. create/prepend plans — plus any Node `.env` action — are applied
|
|
275
|
+
* all-or-nothing.
|
|
276
|
+
*/
|
|
277
|
+
declare function executePlan(plan: Plan, io?: ExecutorIO, options?: ExecuteOptions): ExecuteResult;
|
|
278
|
+
|
|
279
|
+
export { type BuildPlanInput as B, type DetectResult as D, type EnvAction as E, type GitTargetStatus as G, type InjectIO as I, type PackageManager as P, type Recipe as R, type WorkspacePackage as W, type BuildPlanOptions as a, buildPlan as b, DENO_UNSUPPORTED_REASON as c, DOCKER_COMING_SOON_NOTE as d, executePlan as e, type ExecuteOptions as f, type ExecuteResult as g, type ExecutorIO as h, type Plan as i, type PlanKind as j, defaultExecutorIO as k, defaultInjectIO as l, detect as m, detectPackageManager as n, matchRecipe as o, parseNodeInvocation as p, parsePnpmWorkspace as q, projectAlreadyWired as r, resolveAngularEntry as s, resolveNestEntry as t, resolveOtlpStack as u, resolveReactNativeEntry as v, resolveRemixEntry as w, resolveViteEntry as x, supportsInstrumentationClient as y };
|