@reticlehq/init 3.2.0 → 3.4.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/detect/capabilities.d.ts +4 -1
- package/dist/detect/capabilities.js +45 -9
- package/dist/detect/detect.d.ts +58 -4
- package/dist/detect/detect.js +117 -7
- package/dist/detect/dev-script.d.ts +26 -2
- package/dist/detect/dev-script.js +80 -7
- package/dist/detect/non-js-project.d.ts +1 -1
- package/dist/detect/non-js-project.js +6 -1
- package/dist/detect/workspace-apps.d.ts +21 -0
- package/dist/detect/workspace-apps.js +111 -12
- package/dist/detect/workspace-redirect.d.ts +2 -1
- package/dist/detect/workspace-redirect.js +28 -2
- package/dist/diagnose/containerised-dev-server.d.ts +12 -4
- package/dist/diagnose/containerised-dev-server.js +36 -4
- package/dist/diagnose/csp-check.d.ts +12 -6
- package/dist/diagnose/csp-check.js +61 -13
- package/dist/diagnose/csp-doctor.d.ts +33 -2
- package/dist/diagnose/csp-doctor.js +103 -6
- package/dist/diagnose/csp-step.d.ts +27 -0
- package/dist/diagnose/csp-step.js +86 -0
- package/dist/diagnose/desktop-doctor.d.ts +1 -1
- package/dist/diagnose/desktop-doctor.js +8 -2
- package/dist/diagnose/install-hint.d.ts +9 -1
- package/dist/diagnose/install-hint.js +17 -7
- package/dist/diagnose/install-retries.d.ts +18 -2
- package/dist/diagnose/install-retries.js +50 -3
- package/dist/diagnose/terminal-wrap.d.ts +18 -0
- package/dist/diagnose/terminal-wrap.js +63 -0
- package/dist/host.d.ts +6 -0
- package/dist/host.js +3 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +8 -3
- package/dist/no-package-json.d.ts +10 -0
- package/dist/no-package-json.js +121 -0
- package/dist/node-io.d.ts +9 -5
- package/dist/node-io.js +25 -7
- package/dist/patch/angular.d.ts +68 -0
- package/dist/patch/angular.js +216 -0
- package/dist/patch/astro-patch.d.ts +48 -13
- package/dist/patch/astro-patch.js +151 -43
- package/dist/patch/cra.d.ts +0 -2
- package/dist/patch/cra.js +0 -2
- package/dist/patch/electron-patch.d.ts +10 -1
- package/dist/patch/electron-patch.js +79 -27
- package/dist/patch/electron-vite-patch.js +1 -1
- package/dist/patch/nuxt-patch.d.ts +1 -1
- package/dist/patch/nuxt-patch.js +2 -2
- package/dist/patch/nuxt-snippets.d.ts +44 -0
- package/dist/patch/nuxt-snippets.js +135 -0
- package/dist/patch/remix.d.ts +24 -0
- package/dist/patch/remix.js +59 -0
- package/dist/patch/snippets.d.ts +58 -57
- package/dist/patch/snippets.js +181 -202
- package/dist/patch/static-page.d.ts +39 -0
- package/dist/patch/static-page.js +102 -0
- package/dist/patch/tanstack-start.d.ts +36 -8
- package/dist/patch/tanstack-start.js +118 -29
- package/dist/patch/vite-config.d.ts +5 -0
- package/dist/patch/vite-config.js +8 -0
- package/dist/patch/vite-env-types.d.ts +9 -0
- package/dist/patch/vite-env-types.js +19 -0
- package/dist/patch/vite-owning-config.d.ts +47 -0
- package/dist/patch/vite-owning-config.js +104 -19
- package/dist/plan/connect-steps.d.ts +10 -3
- package/dist/plan/connect-steps.js +22 -3
- package/dist/plan/framework-adapter.d.ts +5 -6
- package/dist/plan/framework-adapter.js +105 -11
- package/dist/plan/notices.js +4 -3
- package/dist/plan/plan-angular.d.ts +6 -0
- package/dist/plan/plan-angular.js +89 -0
- package/dist/plan/plan-electron-vite.d.ts +6 -3
- package/dist/plan/plan-electron-vite.js +39 -8
- package/dist/plan/plan-framework.d.ts +9 -47
- package/dist/plan/plan-framework.js +114 -323
- package/dist/plan/plan-types.d.ts +47 -2
- package/dist/plan/plan-vite.d.ts +67 -0
- package/dist/plan/plan-vite.js +256 -0
- package/dist/plan/plan.js +155 -69
- package/dist/plan/port-steps.d.ts +45 -0
- package/dist/plan/port-steps.js +119 -0
- package/dist/plan/preflight.d.ts +17 -3
- package/dist/plan/preflight.js +53 -23
- package/dist/plan/stop-hook-step.d.ts +4 -0
- package/dist/plan/stop-hook-step.js +49 -0
- package/dist/project/agent-rules.js +2 -0
- package/dist/register/mcp-clients.d.ts +13 -0
- package/dist/register/mcp-clients.js +22 -0
- package/dist/register/mcp.d.ts +26 -5
- package/dist/register/mcp.js +64 -8
- package/dist/run-types.d.ts +10 -0
- package/dist/run.js +132 -78
- package/package.json +3 -4
- package/NOTICE +0 -5
|
@@ -114,6 +114,13 @@ export interface PlanInput {
|
|
|
114
114
|
claudeCli: boolean;
|
|
115
115
|
/** Whether an `reticle` MCP server is already registered with Claude (any scope) — idempotency. */
|
|
116
116
|
mcpExists: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Whether `init` is running inside Claude Code itself, which is the one case where writing its
|
|
119
|
+
* project-scope `.mcp.json` is known to be wanted. See `CLAUDE_PROJECT_SPEC`.
|
|
120
|
+
*/
|
|
121
|
+
insideClaudeCode?: boolean | undefined;
|
|
122
|
+
/** The project `.mcp.json` at the agent's root, read so the merge stays idempotent. */
|
|
123
|
+
claudeProjectConfig?: string | null | undefined;
|
|
117
124
|
/** `process.platform`. Injected so this module stays pure. Windows is the only branch. */
|
|
118
125
|
platform?: string;
|
|
119
126
|
/**
|
|
@@ -175,10 +182,15 @@ export interface PlanInput {
|
|
|
175
182
|
source: string;
|
|
176
183
|
} | null | undefined;
|
|
177
184
|
/**
|
|
178
|
-
* Existing `src/env.d.ts` content, when present — where the
|
|
179
|
-
* so `astro check` can see `__RETICLE_TOKEN__` / `__RETICLE_ROOT__` (#677).
|
|
185
|
+
* Existing `src/env.d.ts` content, when present — where the window ambient declarations go
|
|
186
|
+
* so `astro check` can see `__RETICLE_TOKEN__` / `__RETICLE_ROOT__` (#677, #1008).
|
|
180
187
|
*/
|
|
181
188
|
astroEnvDts?: string | null | undefined;
|
|
189
|
+
/**
|
|
190
|
+
* Existing `src/components/ReticleDev.ts` content, when present — the local module whose
|
|
191
|
+
* static SDK import is what actually dials the daemon.
|
|
192
|
+
*/
|
|
193
|
+
astroReticleDev?: string | null | undefined;
|
|
182
194
|
/** Discovered Next config filename (e.g. 'next.config.mjs'), or null. */
|
|
183
195
|
nextConfigFile: string | null;
|
|
184
196
|
/** Source of that Next config, so the export can be wrapped in withReticle. */
|
|
@@ -214,6 +226,8 @@ export interface PlanInput {
|
|
|
214
226
|
viteDevModuleExists?: boolean | undefined;
|
|
215
227
|
/** Whether src/hooks.client.ts already exists (SvelteKit idempotency). */
|
|
216
228
|
svelteKitHooksExists?: boolean;
|
|
229
|
+
/** Its content, so a re-run with a new `--port` can move the URL baked into it. */
|
|
230
|
+
svelteKitHooksSource?: string | null | undefined;
|
|
217
231
|
/** Whether app/entry.client.tsx already exists — it decides whether init writes one or patches it. */
|
|
218
232
|
reactRouterEntryExists?: boolean;
|
|
219
233
|
/**
|
|
@@ -224,6 +238,8 @@ export interface PlanInput {
|
|
|
224
238
|
* added to whatever the app already put in it. Existence alone could only ever print a recipe.
|
|
225
239
|
*/
|
|
226
240
|
reactRouterEntrySource?: string | null | undefined;
|
|
241
|
+
/** The installed Vite's major, for the configs that own their Vite (see `installedViteMajor`). */
|
|
242
|
+
viteMajor?: number | null | undefined;
|
|
227
243
|
/** Discovered Nuxt config: its path + source, or null. It is where the pairing token is inlined. */
|
|
228
244
|
nuxtConfig?: {
|
|
229
245
|
path: string;
|
|
@@ -233,6 +249,8 @@ export interface PlanInput {
|
|
|
233
249
|
nuxtHasAppDir?: boolean | undefined;
|
|
234
250
|
/** Whether the Nuxt client plugin already exists (idempotency — the file is the owner's to edit). */
|
|
235
251
|
nuxtPluginExists?: boolean | undefined;
|
|
252
|
+
/** Its content, so a re-run with a new `--port` can move the URL baked into it. */
|
|
253
|
+
nuxtPluginSource?: string | null | undefined;
|
|
236
254
|
/**
|
|
237
255
|
* TanStack Start's document module, when found (`src/routes/__root.tsx` or `app/routes/__root.tsx`).
|
|
238
256
|
*
|
|
@@ -240,6 +258,29 @@ export interface PlanInput {
|
|
|
240
258
|
* 500s — but the path has to be the one that actually exists, not a guess.
|
|
241
259
|
*/
|
|
242
260
|
tanstackStartRoot?: string | undefined;
|
|
261
|
+
/** Its content, so `init` can render the connect component in it rather than print a recipe. */
|
|
262
|
+
tanstackStartRootSource?: string | null | undefined;
|
|
263
|
+
/** Whether the connect component `init` writes beside `routes/` already exists (it is the owner's). */
|
|
264
|
+
tanstackStartConnectExists?: boolean | undefined;
|
|
265
|
+
/** Its content, so a re-run with a new `--port` can move the URL baked into it. */
|
|
266
|
+
tanstackStartConnectSource?: string | null | undefined;
|
|
267
|
+
/** `angular.json`, read so the serve target can be pointed at the token route. */
|
|
268
|
+
angularWorkspace?: {
|
|
269
|
+
path: string;
|
|
270
|
+
source: string;
|
|
271
|
+
} | null | undefined;
|
|
272
|
+
/** The browser entry `angular.json` names (`src/main.ts` by default) — where the connect goes. */
|
|
273
|
+
angularEntry?: {
|
|
274
|
+
path: string;
|
|
275
|
+
source: string;
|
|
276
|
+
} | null | undefined;
|
|
277
|
+
/** The generated `reticle.proxy.mjs`, when a previous run left one (idempotency). */
|
|
278
|
+
angularProxySource?: string | null | undefined;
|
|
279
|
+
/**
|
|
280
|
+
* The plain-HTML app's `index.html`, when it has one — so a re-run over a page that already
|
|
281
|
+
* carries the connect snippet reports it as done instead of "will NOT connect".
|
|
282
|
+
*/
|
|
283
|
+
htmlIndexSource?: string | null | undefined;
|
|
243
284
|
/** CRA's bundled entry (src/index.tsx or .js) — where the connect import has to go. */
|
|
244
285
|
craEntry?: {
|
|
245
286
|
path: string;
|
|
@@ -283,6 +324,10 @@ export interface PlanInput {
|
|
|
283
324
|
claudeCommandContent?: string | null | undefined;
|
|
284
325
|
/** Current .cursor/commands/reticle.md content, or null when absent. */
|
|
285
326
|
cursorCommandContent?: string | null | undefined;
|
|
327
|
+
/** `--hooks`: install the print-only Claude Code Stop hook. Off unless asked. */
|
|
328
|
+
hooks?: boolean | undefined;
|
|
329
|
+
/** Current .claude/settings.json content, or null when absent (the hook is MERGED into it). */
|
|
330
|
+
claudeSettingsContent?: string | null | undefined;
|
|
286
331
|
/**
|
|
287
332
|
* Absolute directory the AGENT runs in, when that is not the app's directory.
|
|
288
333
|
*
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Vite half of the plan: the `reticle()` plugin in a Vite config, and the dev module that
|
|
3
|
+
* carries `registerCapabilities` / `registerStore`.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `plan-framework.ts`, which had grown to the edge of the line cap. It is its own
|
|
6
|
+
* module because every Vite-based stack (plain Vite, SvelteKit, React Router, Remix, TanStack
|
|
7
|
+
* Start, the Electron renderers) composes these steps, while the rest of `plan-framework.ts` is one
|
|
8
|
+
* builder per framework that has its own files to write.
|
|
9
|
+
*/
|
|
10
|
+
import { type PlanInput, type Step } from './plan-types.js';
|
|
11
|
+
/** What adding `reticle()` to a Vite config buys, which differs by framework. */
|
|
12
|
+
export declare const VITE_PLUGIN_DETAIL: {
|
|
13
|
+
/** A plain Vite app gets both halves from the plugin. */
|
|
14
|
+
readonly VITE: "add reticle() to plugins (also injects connect())";
|
|
15
|
+
/**
|
|
16
|
+
* SvelteKit renders through app.html, so the plugin's HTML injection never fires and connect()
|
|
17
|
+
* comes from the client hook instead. The plugin is still required: it is what stamps
|
|
18
|
+
* data-reticle-source into .svelte components, and without it every verdict on a SvelteKit app
|
|
19
|
+
* comes back with no file:line at all.
|
|
20
|
+
*/
|
|
21
|
+
readonly SVELTEKIT: "add reticle({ inject: false }) to plugins (stamps data-reticle-source in .svelte components; the client hook connects)";
|
|
22
|
+
/**
|
|
23
|
+
* React Router framework mode renders through its own request handler, so the plugin's HTML
|
|
24
|
+
* injection never fires and connect() comes from the client entry instead. The plugin is still
|
|
25
|
+
* required for the same reason it is under SvelteKit: without it every verdict on the app comes
|
|
26
|
+
* back with no file:line at all.
|
|
27
|
+
*/
|
|
28
|
+
readonly REACT_ROUTER: "add reticle() to plugins (stamps data-reticle-source in .tsx components)";
|
|
29
|
+
/**
|
|
30
|
+
* TanStack Start SSRs its own HTML, so the plugin's HTML injection never fires and connect()
|
|
31
|
+
* comes from a client effect instead. `inject: false` is load-bearing honesty: leaving the
|
|
32
|
+
* default would keep reporting "also injects connect()" for a transform that never runs.
|
|
33
|
+
* The plugin is still required for the same reason it is under SvelteKit: without it every
|
|
34
|
+
* verdict on the app comes back with no file:line at all.
|
|
35
|
+
*/
|
|
36
|
+
readonly TANSTACK_START: "add reticle({ inject: false }) to plugins (stamps data-reticle-source; Start SSRs its own HTML so connect() cannot come from the plugin)";
|
|
37
|
+
};
|
|
38
|
+
export declare function capabilitiesTodo(path: string, stores: readonly string[]): string;
|
|
39
|
+
/**
|
|
40
|
+
* The dev module carrying `registerCapabilities` / `registerStore`.
|
|
41
|
+
*
|
|
42
|
+
* Without it every app came up `hasCapabilities: false` with a `reticle_state` holding nothing but
|
|
43
|
+
* `__reticle_renders` — the state-truth read was unavailable on every app out of the box. Written
|
|
44
|
+
* only when absent, because it is the one generated file a user is expected to EDIT.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Is there state left that ONLY the app can hand over?
|
|
48
|
+
*
|
|
49
|
+
* This used to ask a much bigger question — "does the generated file register anything at all?" —
|
|
50
|
+
* and fired on almost every install, because almost every install generated a file whose testids
|
|
51
|
+
* and stores were both empty. The answer was homework: go read the source, uncomment a line, add
|
|
52
|
+
* data-testid attributes, then drive to prove it. Several turns, on every onboarding.
|
|
53
|
+
*
|
|
54
|
+
* Two of those three are no longer anyone's homework. Testids are read from the live DOM, and a
|
|
55
|
+
* store passed through a React context provider (Redux, TanStack Query) is discovered and
|
|
56
|
+
* registered on the first commit. What is left is the genuinely unreachable case: a module-scope
|
|
57
|
+
* store, or one needing an argument only the source supplies — Zustand, Jotai atoms, an XState
|
|
58
|
+
* actor. Nothing in the running app points at those, so the notice still has to fire, and it now
|
|
59
|
+
* fires ONLY there.
|
|
60
|
+
*
|
|
61
|
+
* `wired` are stores init resolved and wrote a live `registerStore` call for. Hints are not
|
|
62
|
+
* registrations: they land in the file as a commented line, and counting one as a registration
|
|
63
|
+
* silences the notice whose whole purpose is to say "act on the hint".
|
|
64
|
+
*/
|
|
65
|
+
export declare function needsManualStore(hints: readonly string[], wired: readonly unknown[]): boolean;
|
|
66
|
+
export declare function capabilitiesStep(input: PlanInput, path?: string): Step[];
|
|
67
|
+
export declare function viteSteps(input: PlanInput, detail?: string, inject?: boolean): Step[];
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Vite half of the plan: the `reticle()` plugin in a Vite config, and the dev module that
|
|
3
|
+
* carries `registerCapabilities` / `registerStore`.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `plan-framework.ts`, which had grown to the edge of the line cap. It is its own
|
|
6
|
+
* module because every Vite-based stack (plain Vite, SvelteKit, React Router, Remix, TanStack
|
|
7
|
+
* Start, the Electron renderers) composes these steps, while the rest of `plan-framework.ts` is one
|
|
8
|
+
* builder per framework that has its own files to write.
|
|
9
|
+
*/
|
|
10
|
+
import { newViteConfig, patchViteConfig, VitePatchKind, VITE_IMPORT } from '../patch/vite-config.js';
|
|
11
|
+
import { viteManual, viteDevModuleFile, VITE_DEV_MODULE_PATH } from '../patch/snippets.js';
|
|
12
|
+
import { hasOptOut, OPT_OUT_MARKER } from '../detect/declared/init-opt-out.js';
|
|
13
|
+
import { Framework } from '../detect/detect.js';
|
|
14
|
+
import { StepStatus } from './plan-types.js';
|
|
15
|
+
import { StepTitle } from './connect-steps.js';
|
|
16
|
+
import { alreadyOrMovedPort } from './port-steps.js';
|
|
17
|
+
/** What adding `reticle()` to a Vite config buys, which differs by framework. */
|
|
18
|
+
export const VITE_PLUGIN_DETAIL = {
|
|
19
|
+
/** A plain Vite app gets both halves from the plugin. */
|
|
20
|
+
VITE: 'add reticle() to plugins (also injects connect())',
|
|
21
|
+
/**
|
|
22
|
+
* SvelteKit renders through app.html, so the plugin's HTML injection never fires and connect()
|
|
23
|
+
* comes from the client hook instead. The plugin is still required: it is what stamps
|
|
24
|
+
* data-reticle-source into .svelte components, and without it every verdict on a SvelteKit app
|
|
25
|
+
* comes back with no file:line at all.
|
|
26
|
+
*/
|
|
27
|
+
SVELTEKIT: 'add reticle({ inject: false }) to plugins (stamps data-reticle-source in .svelte components; the client hook connects)',
|
|
28
|
+
/**
|
|
29
|
+
* React Router framework mode renders through its own request handler, so the plugin's HTML
|
|
30
|
+
* injection never fires and connect() comes from the client entry instead. The plugin is still
|
|
31
|
+
* required for the same reason it is under SvelteKit: without it every verdict on the app comes
|
|
32
|
+
* back with no file:line at all.
|
|
33
|
+
*/
|
|
34
|
+
REACT_ROUTER: 'add reticle() to plugins (stamps data-reticle-source in .tsx components)',
|
|
35
|
+
/**
|
|
36
|
+
* TanStack Start SSRs its own HTML, so the plugin's HTML injection never fires and connect()
|
|
37
|
+
* comes from a client effect instead. `inject: false` is load-bearing honesty: leaving the
|
|
38
|
+
* default would keep reporting "also injects connect()" for a transform that never runs.
|
|
39
|
+
* The plugin is still required for the same reason it is under SvelteKit: without it every
|
|
40
|
+
* verdict on the app comes back with no file:line at all.
|
|
41
|
+
*/
|
|
42
|
+
TANSTACK_START: 'add reticle({ inject: false }) to plugins (stamps data-reticle-source; Start SSRs its own HTML so connect() cannot come from the plugin)',
|
|
43
|
+
};
|
|
44
|
+
const CAPABILITIES_TITLE = StepTitle.CAPABILITIES;
|
|
45
|
+
/**
|
|
46
|
+
* The one step `init` genuinely cannot finish, addressed to the AGENT reading this report.
|
|
47
|
+
*
|
|
48
|
+
* It used to read "Capabilities are empty until you edit that file" and describe the consequence to
|
|
49
|
+
* a human — homework, in a report a human mostly does not read, naming no file to open and no thing
|
|
50
|
+
* to look for. The agent that ran `init` is right there, has the codebase, and can finish this in
|
|
51
|
+
* the same minute; what it needed was the specific instruction, not the rationale.
|
|
52
|
+
*
|
|
53
|
+
* What is left is exactly what could not be PROVEN from source: which atoms/actor/queryClient an
|
|
54
|
+
* adapter-wrapped store should be built from, and which elements matter enough to name. `init`
|
|
55
|
+
* emits nothing it cannot prove, because a wrong import here throws on every dev page load — worse
|
|
56
|
+
* than an empty file.
|
|
57
|
+
*/
|
|
58
|
+
const CAPABILITIES_TODO_TITLE = StepTitle.CAPABILITIES_TODO;
|
|
59
|
+
export function capabilitiesTodo(path, stores) {
|
|
60
|
+
return (`This app's state lives in a library nothing in the running app points at, so it is the one ` +
|
|
61
|
+
`thing Reticle cannot find for itself — reticle_state will not see it until you say where it ` +
|
|
62
|
+
`is.\n` +
|
|
63
|
+
`Do this now, before driving anything:\n` +
|
|
64
|
+
` 1. In ${path}, uncomment the suggested line and complete it: ${stores.join('; ')}.\n` +
|
|
65
|
+
` 2. Prove it: drive one flow and check reticle_state returns your key. An empty state read ` +
|
|
66
|
+
`means the line is still not doing anything, whatever else went green.`);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The dev module carrying `registerCapabilities` / `registerStore`.
|
|
70
|
+
*
|
|
71
|
+
* Without it every app came up `hasCapabilities: false` with a `reticle_state` holding nothing but
|
|
72
|
+
* `__reticle_renders` — the state-truth read was unavailable on every app out of the box. Written
|
|
73
|
+
* only when absent, because it is the one generated file a user is expected to EDIT.
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* Is there state left that ONLY the app can hand over?
|
|
77
|
+
*
|
|
78
|
+
* This used to ask a much bigger question — "does the generated file register anything at all?" —
|
|
79
|
+
* and fired on almost every install, because almost every install generated a file whose testids
|
|
80
|
+
* and stores were both empty. The answer was homework: go read the source, uncomment a line, add
|
|
81
|
+
* data-testid attributes, then drive to prove it. Several turns, on every onboarding.
|
|
82
|
+
*
|
|
83
|
+
* Two of those three are no longer anyone's homework. Testids are read from the live DOM, and a
|
|
84
|
+
* store passed through a React context provider (Redux, TanStack Query) is discovered and
|
|
85
|
+
* registered on the first commit. What is left is the genuinely unreachable case: a module-scope
|
|
86
|
+
* store, or one needing an argument only the source supplies — Zustand, Jotai atoms, an XState
|
|
87
|
+
* actor. Nothing in the running app points at those, so the notice still has to fire, and it now
|
|
88
|
+
* fires ONLY there.
|
|
89
|
+
*
|
|
90
|
+
* `wired` are stores init resolved and wrote a live `registerStore` call for. Hints are not
|
|
91
|
+
* registrations: they land in the file as a commented line, and counting one as a registration
|
|
92
|
+
* silences the notice whose whole purpose is to say "act on the hint".
|
|
93
|
+
*/
|
|
94
|
+
export function needsManualStore(hints, wired) {
|
|
95
|
+
return hints.length > 0 && 0 === wired.length;
|
|
96
|
+
}
|
|
97
|
+
export function capabilitiesStep(input, path = VITE_DEV_MODULE_PATH) {
|
|
98
|
+
if (true === input.viteDevModuleExists) {
|
|
99
|
+
return [
|
|
100
|
+
{
|
|
101
|
+
title: CAPABILITIES_TITLE,
|
|
102
|
+
target: path,
|
|
103
|
+
status: StepStatus.ALREADY,
|
|
104
|
+
detail: 'file exists, left alone, it is yours to edit',
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
const testids = input.testids ?? [];
|
|
109
|
+
const stores = input.storeHints ?? [];
|
|
110
|
+
const wired = input.foundStores ?? [];
|
|
111
|
+
// Testids no longer need counting here. They are read from the live DOM at announce time, so a
|
|
112
|
+
// number printed at install time would be a stale claim about a codebase that is about to change —
|
|
113
|
+
// which is exactly what "no data-testid values yet" used to be on an app whose testids arrive with
|
|
114
|
+
// a lazy route.
|
|
115
|
+
const found = 'testids read from the live DOM';
|
|
116
|
+
// A store we found and WIRED is a registration, so the notice must not fire. A store HINT is not:
|
|
117
|
+
// `stores` holds suggestions, written into the file as a commented line of the form
|
|
118
|
+
// `// import your store, then: registerStore(...)`. Counting a suggestion as a registration let
|
|
119
|
+
// the hint silence the notice whose entire job is to say "act on the hint".
|
|
120
|
+
//
|
|
121
|
+
// Measured against a real product UI (rowy — 70+ deps, jotai, a whole src/atoms tree): init
|
|
122
|
+
// detected jotai, offered one commented line, emitted NO notice, and wrote
|
|
123
|
+
// `registerCapabilities({ testids: [], signals: [], stores: [] })`. So `hasCapabilities` stayed
|
|
124
|
+
// false, `reticle_state` was empty forever, and the install gate reported "connected: 1,
|
|
125
|
+
// manual ⚠: none". Every check green, state observability zero.
|
|
126
|
+
//
|
|
127
|
+
// jotai is still exactly that case. A Redux or TanStack app is not, any more: its store rides a
|
|
128
|
+
// context provider and the React adapter registers it on the first commit, so `storeHints` no
|
|
129
|
+
// longer names either one and this notice no longer fires for them.
|
|
130
|
+
const nothingToRegister = needsManualStore(stores, wired);
|
|
131
|
+
return [
|
|
132
|
+
{
|
|
133
|
+
title: CAPABILITIES_TITLE,
|
|
134
|
+
target: path,
|
|
135
|
+
status: StepStatus.APPLY,
|
|
136
|
+
detail: `${found}; ${wired.length > 0
|
|
137
|
+
? `registered ${wired.map((s) => `'${s.key}'`).join(', ')} from your source`
|
|
138
|
+
: stores.length > 0
|
|
139
|
+
? `store: uncomment the ${String(stores.length)} suggested line(s)`
|
|
140
|
+
: 'no state library detected'}`,
|
|
141
|
+
write: {
|
|
142
|
+
path,
|
|
143
|
+
content: viteDevModuleFile(testids, stores, wired, input.detection.uiLibrary, input.detection.framework),
|
|
144
|
+
},
|
|
145
|
+
dependsOnInstall: true,
|
|
146
|
+
},
|
|
147
|
+
// The write is real; what it registers is not. `registerCapabilities({ testids: [], signals: [],
|
|
148
|
+
// stores: [] })` registers nothing, so `hasCapabilities` stays false — correctly — and the ✓
|
|
149
|
+
// above reads as if the feature is on. Reported as exactly that confusion: "hasCapabilities false
|
|
150
|
+
// on every session while init said ✓ Capabilities + store".
|
|
151
|
+
//
|
|
152
|
+
// Beside the write rather than replacing it, for two reasons: only APPLY steps are written
|
|
153
|
+
// (run.ts), and SKILL.md tells the reader to skip ✓ lines — so a caveat carried on the ✓ is a
|
|
154
|
+
// caveat nobody reads.
|
|
155
|
+
...(nothingToRegister
|
|
156
|
+
? [
|
|
157
|
+
{
|
|
158
|
+
title: CAPABILITIES_TODO_TITLE,
|
|
159
|
+
target: path,
|
|
160
|
+
status: StepStatus.NOTICE,
|
|
161
|
+
detail: capabilitiesTodo(path, stores),
|
|
162
|
+
},
|
|
163
|
+
]
|
|
164
|
+
: []),
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
export function viteSteps(input, detail = VITE_PLUGIN_DETAIL.VITE, inject = true) {
|
|
168
|
+
// Capabilities are independent of whether the config needed patching. Attaching them to the APPLY
|
|
169
|
+
// branch meant a re-run on an already-wired app silently never created the module.
|
|
170
|
+
return [...viteConfigSteps(input, detail, inject), ...capabilitiesStep(input)];
|
|
171
|
+
}
|
|
172
|
+
function viteConfigSteps(input, detail, inject = true) {
|
|
173
|
+
const cfg = input.viteConfig;
|
|
174
|
+
const port = input.options.port;
|
|
175
|
+
// Stamp `data-reticle-source` unless this app renders through a non-DOM React reconciler, where a
|
|
176
|
+
// lowercase JSX tag is not an element and the stamp crashes the app at commit time. See
|
|
177
|
+
// `Detection.customReconciler`.
|
|
178
|
+
const stampSource = true !== input.detection?.customReconciler;
|
|
179
|
+
// An explicit "not here" is not an invitation. `init` runs unattended in a repo it has just met,
|
|
180
|
+
// and it was reported adding the plugin to an app whose config said Reticle was deliberately
|
|
181
|
+
// excluded. A NOTICE rather than a ⚠: opting out is a decision, not something to go and fix.
|
|
182
|
+
if (cfg !== null && hasOptOut(cfg.source)) {
|
|
183
|
+
return [
|
|
184
|
+
{
|
|
185
|
+
title: StepTitle.VITE_PLUGIN,
|
|
186
|
+
target: cfg.path,
|
|
187
|
+
status: StepStatus.NOTICE,
|
|
188
|
+
detail: `left alone: this config carries ${OPT_OUT_MARKER}. Remove that marker to instrument this app.`,
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
// Plain Vite runs happily with no config file (`npm create vite`'s vanilla template has none), so
|
|
193
|
+
// make one. Only there: a Vite-based framework's missing config also lost the framework's own
|
|
194
|
+
// plugin, and a file carrying ours alone would not boot the app.
|
|
195
|
+
const plainVite = Framework.VITE === input.detection.framework;
|
|
196
|
+
if (null === cfg) {
|
|
197
|
+
if (!plainVite) {
|
|
198
|
+
return [
|
|
199
|
+
{
|
|
200
|
+
title: StepTitle.VITE_PLUGIN,
|
|
201
|
+
target: 'vite.config',
|
|
202
|
+
status: StepStatus.MANUAL,
|
|
203
|
+
detail: viteManual(port, input.detection.uiLibrary, inject, stampSource),
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
const path = input.detection.typescript ? 'vite.config.ts' : 'vite.config.mjs';
|
|
208
|
+
return [
|
|
209
|
+
{
|
|
210
|
+
title: StepTitle.VITE_PLUGIN,
|
|
211
|
+
target: path,
|
|
212
|
+
status: StepStatus.APPLY,
|
|
213
|
+
detail,
|
|
214
|
+
write: {
|
|
215
|
+
path,
|
|
216
|
+
content: newViteConfig(port, true === input.captureBodies, inject, stampSource),
|
|
217
|
+
expect: [VITE_IMPORT, 'reticle('],
|
|
218
|
+
},
|
|
219
|
+
dependsOnInstall: true,
|
|
220
|
+
},
|
|
221
|
+
];
|
|
222
|
+
}
|
|
223
|
+
const patch = patchViteConfig(cfg.source, port, true === input.captureBodies, inject, stampSource);
|
|
224
|
+
if (patch.kind === VitePatchKind.ALREADY) {
|
|
225
|
+
return [
|
|
226
|
+
alreadyOrMovedPort({
|
|
227
|
+
title: StepTitle.VITE_PLUGIN,
|
|
228
|
+
target: cfg.path,
|
|
229
|
+
status: StepStatus.ALREADY,
|
|
230
|
+
detail: 'reticle() already in plugins',
|
|
231
|
+
}, cfg.source, port),
|
|
232
|
+
];
|
|
233
|
+
}
|
|
234
|
+
if (patch.kind === VitePatchKind.MANUAL) {
|
|
235
|
+
return [
|
|
236
|
+
{
|
|
237
|
+
title: StepTitle.VITE_PLUGIN,
|
|
238
|
+
target: cfg.path,
|
|
239
|
+
status: StepStatus.MANUAL,
|
|
240
|
+
detail: `${patch.reason}\n\n${viteManual(port, input.detection.uiLibrary, inject, stampSource)}`,
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
return [
|
|
245
|
+
{
|
|
246
|
+
title: StepTitle.VITE_PLUGIN,
|
|
247
|
+
target: cfg.path,
|
|
248
|
+
status: StepStatus.APPLY,
|
|
249
|
+
detail,
|
|
250
|
+
// Both halves, because either alone is a config that does not wire: the import without the
|
|
251
|
+
// call leaves `plugins` untouched, and the call without the import does not build.
|
|
252
|
+
write: { path: cfg.path, content: patch.code, expect: [VITE_IMPORT, 'reticle('] },
|
|
253
|
+
dependsOnInstall: true,
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
}
|