@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
|
@@ -1,242 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* How each framework gets wired: the
|
|
3
|
-
* `plan.ts` is the plan's SHAPE (statuses, ordering, the agent/MCP steps); this is the
|
|
4
|
-
* detail, and they grow for different reasons.
|
|
2
|
+
* How each framework gets wired: the three Next files, the SvelteKit client hook, CRA, Nuxt, Astro
|
|
3
|
+
* and the rest. `plan.ts` is the plan's SHAPE (statuses, ordering, the agent/MCP steps); this is the
|
|
4
|
+
* per-framework detail, and they grow for different reasons. The Vite plugin and the capabilities
|
|
5
|
+
* module every Vite-based stack shares live in `plan-vite.ts`.
|
|
5
6
|
*/
|
|
6
7
|
import { bridgeWsUrl } from '@reticlehq/core';
|
|
7
|
-
import { patchViteConfig, VitePatchKind, VITE_IMPORT } from '../patch/vite-config.js';
|
|
8
8
|
import { patchNextConfig, patchRootLayout, patchPagesApp } from '../patch/next-patch.js';
|
|
9
|
-
import { ASTRO_ENV_DTS_PATH, patchAstroConfig, patchAstroEnvDts, patchAstroLayout, } from '../patch/astro-patch.js';
|
|
9
|
+
import { ASTRO_ENV_DTS_PATH, ASTRO_RETICLE_DEV_PATH, astroReticleDevFile, patchAstroConfig, patchAstroEnvDts, patchAstroLayout, } from '../patch/astro-patch.js';
|
|
10
10
|
import { CRA_DEV_MODULE_IMPORT, CRA_ENV_PATH, CRA_TOKEN_PER_MACHINE_NOTICE, TOKEN_VAR, craDevModuleFile, craDevModulePath, craEnvPatch, craImportPatch, } from '../patch/cra.js';
|
|
11
11
|
import { PatchKind } from '../patch/patch-kind.js';
|
|
12
|
-
import {
|
|
13
|
-
import { hasOptOut, OPT_OUT_MARKER } from '../detect/declared/init-opt-out.js';
|
|
12
|
+
import { NEXT_LAYOUT_MANUAL, NEXT_LAYOUT_PATH, nextReticleDevFile, NEXT_RETICLE_DEV_PATH, nextConfigManual, svelteKitHooksFile, SVELTEKIT_HOOKS_PATH, SVELTEKIT_SETUP_GATED_NOTE, astroManual, nuxtManual, nuxtPluginFile, nuxtPluginPath, NUXT_PLUGIN_NOTICE, webpack4TranspileNote, WEBPACK4_REACT_SCRIPTS_MAJOR, reactRouterEntryFile, reactRouterEntryPatch, REACT_ROUTER_ENTRY_PATH, UNVERIFIED_TANSTACK_START_NOTE, htmlManual, } from '../patch/snippets.js';
|
|
14
13
|
import { StepStatus } from './plan-types.js';
|
|
15
|
-
import { RETICLE_DEFAULT_PORT } from '@reticlehq/core';
|
|
16
|
-
import { CSP_STEP_TITLE } from '../diagnose/csp-check.js';
|
|
17
14
|
import { StepTitle } from './connect-steps.js';
|
|
18
|
-
import { tanstackStartManual, TANSTACK_START_ROOT_PATH } from '../patch/tanstack-start.js';
|
|
19
|
-
import { diagnoseWebCsp } from '../diagnose/csp-doctor.js';
|
|
15
|
+
import { patchTanstackStartRoot, tanstackStartConnectFile, tanstackStartConnectPath, tanstackStartManual, TANSTACK_START_ROOT_PATH, } from '../patch/tanstack-start.js';
|
|
20
16
|
import { patchNuxtConfig } from '../patch/nuxt-patch.js';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
VITE: 'add reticle() to plugins (also injects connect())',
|
|
25
|
-
/**
|
|
26
|
-
* SvelteKit renders through app.html, so the plugin's HTML injection never fires and connect()
|
|
27
|
-
* comes from the client hook instead. The plugin is still required: it is what stamps
|
|
28
|
-
* data-reticle-source into .svelte components, and without it every verdict on a SvelteKit app
|
|
29
|
-
* comes back with no file:line at all.
|
|
30
|
-
*/
|
|
31
|
-
SVELTEKIT: 'add reticle() to plugins (stamps data-reticle-source in .svelte components)',
|
|
32
|
-
/**
|
|
33
|
-
* React Router framework mode renders through its own request handler, so the plugin's HTML
|
|
34
|
-
* injection never fires and connect() comes from the client entry instead. The plugin is still
|
|
35
|
-
* required for the same reason it is under SvelteKit: without it every verdict on the app comes
|
|
36
|
-
* back with no file:line at all.
|
|
37
|
-
*/
|
|
38
|
-
REACT_ROUTER: 'add reticle() to plugins (stamps data-reticle-source in .tsx components)',
|
|
39
|
-
/**
|
|
40
|
-
* TanStack Start SSRs its own HTML, so the plugin's HTML injection never fires and connect()
|
|
41
|
-
* comes from a client effect instead. `inject: false` is load-bearing honesty: leaving the
|
|
42
|
-
* default would keep reporting "also injects connect()" for a transform that never runs.
|
|
43
|
-
* The plugin is still required for the same reason it is under SvelteKit: without it every
|
|
44
|
-
* verdict on the app comes back with no file:line at all.
|
|
45
|
-
*/
|
|
46
|
-
TANSTACK_START: 'add reticle({ inject: false }) to plugins (stamps data-reticle-source; Start SSRs its own HTML so connect() cannot come from the plugin)',
|
|
47
|
-
};
|
|
48
|
-
const CAPABILITIES_TITLE = StepTitle.CAPABILITIES;
|
|
49
|
-
/**
|
|
50
|
-
* The one step `init` genuinely cannot finish, addressed to the AGENT reading this report.
|
|
51
|
-
*
|
|
52
|
-
* It used to read "Capabilities are empty until you edit that file" and describe the consequence to
|
|
53
|
-
* a human — homework, in a report a human mostly does not read, naming no file to open and no thing
|
|
54
|
-
* to look for. The agent that ran `init` is right there, has the codebase, and can finish this in
|
|
55
|
-
* the same minute; what it needed was the specific instruction, not the rationale.
|
|
56
|
-
*
|
|
57
|
-
* What is left is exactly what could not be PROVEN from source: which atoms/actor/queryClient an
|
|
58
|
-
* adapter-wrapped store should be built from, and which elements matter enough to name. `init`
|
|
59
|
-
* emits nothing it cannot prove, because a wrong import here throws on every dev page load — worse
|
|
60
|
-
* than an empty file.
|
|
61
|
-
*/
|
|
62
|
-
const CAPABILITIES_TODO_TITLE = StepTitle.CAPABILITIES_TODO;
|
|
63
|
-
function capabilitiesTodo(path, stores) {
|
|
64
|
-
return (`This app's state lives in a library nothing in the running app points at, so it is the one ` +
|
|
65
|
-
`thing Reticle cannot find for itself — reticle_state will not see it until you say where it ` +
|
|
66
|
-
`is.\n` +
|
|
67
|
-
`Do this now, before driving anything:\n` +
|
|
68
|
-
` 1. In ${path}, uncomment the suggested line and complete it: ${stores.join('; ')}.\n` +
|
|
69
|
-
` 2. Prove it: drive one flow and check reticle_state returns your key. An empty state read ` +
|
|
70
|
-
`means the line is still not doing anything, whatever else went green.`);
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* The dev module carrying `registerCapabilities` / `registerStore`.
|
|
74
|
-
*
|
|
75
|
-
* Without it every app came up `hasCapabilities: false` with a `reticle_state` holding nothing but
|
|
76
|
-
* `__reticle_renders` — the state-truth read was unavailable on every app out of the box. Written
|
|
77
|
-
* only when absent, because it is the one generated file a user is expected to EDIT.
|
|
78
|
-
*/
|
|
79
|
-
/**
|
|
80
|
-
* Is there state left that ONLY the app can hand over?
|
|
81
|
-
*
|
|
82
|
-
* This used to ask a much bigger question — "does the generated file register anything at all?" —
|
|
83
|
-
* and fired on almost every install, because almost every install generated a file whose testids
|
|
84
|
-
* and stores were both empty. The answer was homework: go read the source, uncomment a line, add
|
|
85
|
-
* data-testid attributes, then drive to prove it. Several turns, on every onboarding.
|
|
86
|
-
*
|
|
87
|
-
* Two of those three are no longer anyone's homework. Testids are read from the live DOM, and a
|
|
88
|
-
* store passed through a React context provider (Redux, TanStack Query) is discovered and
|
|
89
|
-
* registered on the first commit. What is left is the genuinely unreachable case: a module-scope
|
|
90
|
-
* store, or one needing an argument only the source supplies — Zustand, Jotai atoms, an XState
|
|
91
|
-
* actor. Nothing in the running app points at those, so the notice still has to fire, and it now
|
|
92
|
-
* fires ONLY there.
|
|
93
|
-
*
|
|
94
|
-
* `wired` are stores init resolved and wrote a live `registerStore` call for. Hints are not
|
|
95
|
-
* registrations: they land in the file as a commented line, and counting one as a registration
|
|
96
|
-
* silences the notice whose whole purpose is to say "act on the hint".
|
|
97
|
-
*/
|
|
98
|
-
function needsManualStore(hints, wired) {
|
|
99
|
-
return hints.length > 0 && 0 === wired.length;
|
|
100
|
-
}
|
|
101
|
-
export function capabilitiesStep(input, path = VITE_DEV_MODULE_PATH) {
|
|
102
|
-
if (true === input.viteDevModuleExists) {
|
|
103
|
-
return [
|
|
104
|
-
{
|
|
105
|
-
title: CAPABILITIES_TITLE,
|
|
106
|
-
target: path,
|
|
107
|
-
status: StepStatus.ALREADY,
|
|
108
|
-
detail: 'file exists, left alone, it is yours to edit',
|
|
109
|
-
},
|
|
110
|
-
];
|
|
111
|
-
}
|
|
112
|
-
const testids = input.testids ?? [];
|
|
113
|
-
const stores = input.storeHints ?? [];
|
|
114
|
-
const wired = input.foundStores ?? [];
|
|
115
|
-
// Testids no longer need counting here. They are read from the live DOM at announce time, so a
|
|
116
|
-
// number printed at install time would be a stale claim about a codebase that is about to change —
|
|
117
|
-
// which is exactly what "no data-testid values yet" used to be on an app whose testids arrive with
|
|
118
|
-
// a lazy route.
|
|
119
|
-
const found = 'testids read from the live DOM';
|
|
120
|
-
// A store we found and WIRED is a registration, so the notice must not fire. A store HINT is not:
|
|
121
|
-
// `stores` holds suggestions, written into the file as a commented line of the form
|
|
122
|
-
// `// import your store, then: registerStore(...)`. Counting a suggestion as a registration let
|
|
123
|
-
// the hint silence the notice whose entire job is to say "act on the hint".
|
|
124
|
-
//
|
|
125
|
-
// Measured against a real product UI (rowy — 70+ deps, jotai, a whole src/atoms tree): init
|
|
126
|
-
// detected jotai, offered one commented line, emitted NO notice, and wrote
|
|
127
|
-
// `registerCapabilities({ testids: [], signals: [], stores: [] })`. So `hasCapabilities` stayed
|
|
128
|
-
// false, `reticle_state` was empty forever, and the install gate reported "connected: 1,
|
|
129
|
-
// manual ⚠: none". Every check green, state observability zero.
|
|
130
|
-
//
|
|
131
|
-
// jotai is still exactly that case. A Redux or TanStack app is not, any more: its store rides a
|
|
132
|
-
// context provider and the React adapter registers it on the first commit, so `storeHints` no
|
|
133
|
-
// longer names either one and this notice no longer fires for them.
|
|
134
|
-
const nothingToRegister = needsManualStore(stores, wired);
|
|
135
|
-
return [
|
|
136
|
-
{
|
|
137
|
-
title: CAPABILITIES_TITLE,
|
|
138
|
-
target: path,
|
|
139
|
-
status: StepStatus.APPLY,
|
|
140
|
-
detail: `${found}; ${wired.length > 0
|
|
141
|
-
? `registered ${wired.map((s) => `'${s.key}'`).join(', ')} from your source`
|
|
142
|
-
: stores.length > 0
|
|
143
|
-
? `store: uncomment the ${String(stores.length)} suggested line(s)`
|
|
144
|
-
: 'no state library detected'}`,
|
|
145
|
-
write: {
|
|
146
|
-
path,
|
|
147
|
-
content: viteDevModuleFile(testids, stores, wired, input.detection.uiLibrary),
|
|
148
|
-
},
|
|
149
|
-
dependsOnInstall: true,
|
|
150
|
-
},
|
|
151
|
-
// The write is real; what it registers is not. `registerCapabilities({ testids: [], signals: [],
|
|
152
|
-
// stores: [] })` registers nothing, so `hasCapabilities` stays false — correctly — and the ✓
|
|
153
|
-
// above reads as if the feature is on. Reported as exactly that confusion: "hasCapabilities false
|
|
154
|
-
// on every session while init said ✓ Capabilities + store".
|
|
155
|
-
//
|
|
156
|
-
// Beside the write rather than replacing it, for two reasons: only APPLY steps are written
|
|
157
|
-
// (run.ts), and SKILL.md tells the reader to skip ✓ lines — so a caveat carried on the ✓ is a
|
|
158
|
-
// caveat nobody reads.
|
|
159
|
-
...(nothingToRegister
|
|
160
|
-
? [
|
|
161
|
-
{
|
|
162
|
-
title: CAPABILITIES_TODO_TITLE,
|
|
163
|
-
target: path,
|
|
164
|
-
status: StepStatus.NOTICE,
|
|
165
|
-
detail: capabilitiesTodo(path, stores),
|
|
166
|
-
},
|
|
167
|
-
]
|
|
168
|
-
: []),
|
|
169
|
-
];
|
|
170
|
-
}
|
|
171
|
-
export function viteSteps(input, detail = VITE_PLUGIN_DETAIL.VITE, inject = true) {
|
|
172
|
-
// Capabilities are independent of whether the config needed patching. Attaching them to the APPLY
|
|
173
|
-
// branch meant a re-run on an already-wired app silently never created the module.
|
|
174
|
-
return [...viteConfigSteps(input, detail, inject), ...capabilitiesStep(input)];
|
|
175
|
-
}
|
|
176
|
-
function viteConfigSteps(input, detail, inject = true) {
|
|
177
|
-
const cfg = input.viteConfig;
|
|
178
|
-
const port = input.options.port;
|
|
179
|
-
// Stamp `data-reticle-source` unless this app renders through a non-DOM React reconciler, where a
|
|
180
|
-
// lowercase JSX tag is not an element and the stamp crashes the app at commit time. See
|
|
181
|
-
// `Detection.customReconciler`.
|
|
182
|
-
const stampSource = true !== input.detection?.customReconciler;
|
|
183
|
-
// An explicit "not here" is not an invitation. `init` runs unattended in a repo it has just met,
|
|
184
|
-
// and it was reported adding the plugin to an app whose config said Reticle was deliberately
|
|
185
|
-
// excluded. A NOTICE rather than a ⚠: opting out is a decision, not something to go and fix.
|
|
186
|
-
if (cfg !== null && hasOptOut(cfg.source)) {
|
|
187
|
-
return [
|
|
188
|
-
{
|
|
189
|
-
title: StepTitle.VITE_PLUGIN,
|
|
190
|
-
target: cfg.path,
|
|
191
|
-
status: StepStatus.NOTICE,
|
|
192
|
-
detail: `left alone: this config carries ${OPT_OUT_MARKER}. Remove that marker to instrument this app.`,
|
|
193
|
-
},
|
|
194
|
-
];
|
|
195
|
-
}
|
|
196
|
-
if (null === cfg) {
|
|
197
|
-
return [
|
|
198
|
-
{
|
|
199
|
-
title: StepTitle.VITE_PLUGIN,
|
|
200
|
-
target: 'vite.config',
|
|
201
|
-
status: StepStatus.MANUAL,
|
|
202
|
-
detail: viteManual(port, input.detection.uiLibrary, inject, stampSource),
|
|
203
|
-
},
|
|
204
|
-
];
|
|
205
|
-
}
|
|
206
|
-
const patch = patchViteConfig(cfg.source, port, true === input.captureBodies, inject, stampSource);
|
|
207
|
-
if (patch.kind === VitePatchKind.ALREADY) {
|
|
208
|
-
return [
|
|
209
|
-
{
|
|
210
|
-
title: StepTitle.VITE_PLUGIN,
|
|
211
|
-
target: cfg.path,
|
|
212
|
-
status: StepStatus.ALREADY,
|
|
213
|
-
detail: 'reticle() already in plugins',
|
|
214
|
-
},
|
|
215
|
-
];
|
|
216
|
-
}
|
|
217
|
-
if (patch.kind === VitePatchKind.MANUAL) {
|
|
218
|
-
return [
|
|
219
|
-
{
|
|
220
|
-
title: StepTitle.VITE_PLUGIN,
|
|
221
|
-
target: cfg.path,
|
|
222
|
-
status: StepStatus.MANUAL,
|
|
223
|
-
detail: `${patch.reason}\n\n${viteManual(port, input.detection.uiLibrary, inject, stampSource)}`,
|
|
224
|
-
},
|
|
225
|
-
];
|
|
226
|
-
}
|
|
227
|
-
return [
|
|
228
|
-
{
|
|
229
|
-
title: StepTitle.VITE_PLUGIN,
|
|
230
|
-
target: cfg.path,
|
|
231
|
-
status: StepStatus.APPLY,
|
|
232
|
-
detail,
|
|
233
|
-
// Both halves, because either alone is a config that does not wire: the import without the
|
|
234
|
-
// call leaves `plugins` untouched, and the call without the import does not build.
|
|
235
|
-
write: { path: cfg.path, content: patch.code, expect: [VITE_IMPORT, 'reticle('] },
|
|
236
|
-
dependsOnInstall: true,
|
|
237
|
-
},
|
|
238
|
-
];
|
|
239
|
-
}
|
|
17
|
+
import { alreadyOrMovedPort } from './port-steps.js';
|
|
18
|
+
import { capabilitiesTodo, needsManualStore } from './plan-vite.js';
|
|
19
|
+
import { HTML_INDEX_PATH, hasStaticSnippet } from '../patch/static-page.js';
|
|
240
20
|
/**
|
|
241
21
|
* Turn a conservative source patch into a step: applied when it patched, already when the wiring is
|
|
242
22
|
* there, and the hand-edit instructions when the file shape wasn't one we recognise.
|
|
@@ -299,12 +79,12 @@ export function nextSteps(input) {
|
|
|
299
79
|
status: StepStatus.MANUAL,
|
|
300
80
|
detail: NEXT_DEV_FILE_STALE_DETAIL,
|
|
301
81
|
}
|
|
302
|
-
: {
|
|
82
|
+
: alreadyOrMovedPort({
|
|
303
83
|
title: StepTitle.RETICLE_DEV_COMPONENT,
|
|
304
84
|
target: devPath,
|
|
305
85
|
status: StepStatus.ALREADY,
|
|
306
86
|
detail: 'file exists',
|
|
307
|
-
}
|
|
87
|
+
}, input.nextReticleDevSource, input.options.port)
|
|
308
88
|
: {
|
|
309
89
|
title: StepTitle.RETICLE_DEV_COMPONENT,
|
|
310
90
|
target: devPath,
|
|
@@ -337,7 +117,7 @@ export function nextSteps(input) {
|
|
|
337
117
|
needsManualStore(input.storeHints ?? [], input.nextFoundStores ?? [])
|
|
338
118
|
? [
|
|
339
119
|
{
|
|
340
|
-
title:
|
|
120
|
+
title: StepTitle.CAPABILITIES_TODO,
|
|
341
121
|
target: devPath,
|
|
342
122
|
status: StepStatus.NOTICE,
|
|
343
123
|
detail: capabilitiesTodo(devPath, input.storeHints ?? []),
|
|
@@ -494,7 +274,7 @@ export function nuxtSteps(input) {
|
|
|
494
274
|
];
|
|
495
275
|
if (null === config)
|
|
496
276
|
return manual;
|
|
497
|
-
const configPatch = patchNuxtConfig(config.source);
|
|
277
|
+
const configPatch = patchNuxtConfig(config.source, input.viteMajor ?? null);
|
|
498
278
|
if (configPatch.kind === PatchKind.MANUAL)
|
|
499
279
|
return manual;
|
|
500
280
|
const notice = {
|
|
@@ -506,12 +286,12 @@ export function nuxtSteps(input) {
|
|
|
506
286
|
// Never overwritten: this file is the one an app owner edits — their registered stores, their own
|
|
507
287
|
// capabilities. Rewriting it would take those with it.
|
|
508
288
|
const plugin = true === input.nuxtPluginExists
|
|
509
|
-
? {
|
|
289
|
+
? alreadyOrMovedPort({
|
|
510
290
|
title: StepTitle.CONNECT_SNIPPET_NUXT,
|
|
511
291
|
target: pluginPath,
|
|
512
292
|
status: StepStatus.ALREADY,
|
|
513
293
|
detail: 'file exists',
|
|
514
|
-
}
|
|
294
|
+
}, input.nuxtPluginSource, input.options.port)
|
|
515
295
|
: {
|
|
516
296
|
title: StepTitle.CONNECT_SNIPPET_NUXT,
|
|
517
297
|
target: pluginPath,
|
|
@@ -526,7 +306,11 @@ export function nuxtSteps(input) {
|
|
|
526
306
|
return [
|
|
527
307
|
plugin,
|
|
528
308
|
patchStep(StepTitle.NUXT_CONFIG, config.path, configPatch, 'inline the pairing token and keep the journal out of the watcher', nuxtManual(input.options.port, input.options.projectId)),
|
|
529
|
-
|
|
309
|
+
// Only when this run changed something Nuxt reads at startup. A re-run that found everything in
|
|
310
|
+
// place, attached to the running server and connected was still told to restart it.
|
|
311
|
+
...(StepStatus.ALREADY === plugin.status && PatchKind.ALREADY === configPatch.kind
|
|
312
|
+
? []
|
|
313
|
+
: [notice]),
|
|
530
314
|
];
|
|
531
315
|
}
|
|
532
316
|
/**
|
|
@@ -542,18 +326,21 @@ export function nuxtSteps(input) {
|
|
|
542
326
|
* would replace that default with one that never hydrates. When it is present, one line is added to
|
|
543
327
|
* it and nothing else is touched.
|
|
544
328
|
*/
|
|
545
|
-
export function reactRouterSteps(input
|
|
329
|
+
export function reactRouterSteps(input,
|
|
330
|
+
// Remix v2 is the same framework under its old name and takes the same entry, with its own
|
|
331
|
+
// default hydration (`RemixBrowser`) and its own title — see plan-remix.ts.
|
|
332
|
+
title = StepTitle.CONNECT_SNIPPET_REACT_ROUTER, newEntry = reactRouterEntryFile) {
|
|
546
333
|
const detail = 'connect from the client entry — framework mode renders HTML through its own request handler, ' +
|
|
547
334
|
"so the Vite plugin's index.html injection never fires";
|
|
548
335
|
const existing = input.reactRouterEntrySource ?? null;
|
|
549
336
|
if (true !== input.reactRouterEntryExists || null === existing) {
|
|
550
337
|
return [
|
|
551
338
|
{
|
|
552
|
-
title
|
|
339
|
+
title,
|
|
553
340
|
target: REACT_ROUTER_ENTRY_PATH,
|
|
554
341
|
status: StepStatus.APPLY,
|
|
555
342
|
detail: `create the dev-only ${detail}`,
|
|
556
|
-
write: { path: REACT_ROUTER_ENTRY_PATH, content:
|
|
343
|
+
write: { path: REACT_ROUTER_ENTRY_PATH, content: newEntry() },
|
|
557
344
|
dependsOnInstall: true,
|
|
558
345
|
},
|
|
559
346
|
];
|
|
@@ -562,13 +349,13 @@ export function reactRouterSteps(input) {
|
|
|
562
349
|
return [
|
|
563
350
|
null === patched
|
|
564
351
|
? {
|
|
565
|
-
title
|
|
352
|
+
title,
|
|
566
353
|
target: REACT_ROUTER_ENTRY_PATH,
|
|
567
354
|
status: StepStatus.ALREADY,
|
|
568
355
|
detail: 'already imported',
|
|
569
356
|
}
|
|
570
357
|
: {
|
|
571
|
-
title
|
|
358
|
+
title,
|
|
572
359
|
target: REACT_ROUTER_ENTRY_PATH,
|
|
573
360
|
status: StepStatus.APPLY,
|
|
574
361
|
detail: `add one line to ${detail}`,
|
|
@@ -578,46 +365,62 @@ export function reactRouterSteps(input) {
|
|
|
578
365
|
];
|
|
579
366
|
}
|
|
580
367
|
/**
|
|
581
|
-
* TanStack Start:
|
|
368
|
+
* TanStack Start: a client connect component, written, and rendered in the root document.
|
|
582
369
|
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
370
|
+
* ATOMIC, like Astro: the component on disk with nothing rendering it is a guaranteed
|
|
371
|
+
* non-connection, so when the root cannot be patched BOTH halves go back to the recipe.
|
|
585
372
|
*/
|
|
586
373
|
export function tanstackStartSteps(input) {
|
|
587
374
|
const root = input.tanstackStartRoot ?? TANSTACK_START_ROOT_PATH;
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
375
|
+
const unverified = {
|
|
376
|
+
title: StepTitle.TANSTACK_START_UNVERIFIED,
|
|
377
|
+
target: root,
|
|
378
|
+
status: StepStatus.NOTICE,
|
|
379
|
+
detail: UNVERIFIED_TANSTACK_START_NOTE,
|
|
380
|
+
};
|
|
381
|
+
const recipe = tanstackStartManual(input.options.port, input.options.projectId, root);
|
|
382
|
+
const source = input.tanstackStartRootSource ?? null;
|
|
383
|
+
const patch = null === source
|
|
384
|
+
? { kind: PatchKind.MANUAL, reason: `could not read ${root}` }
|
|
385
|
+
: patchTanstackStartRoot(source, root);
|
|
386
|
+
const rendered = patchStep(StepTitle.CONNECT_SNIPPET_TANSTACK_START, root, patch, 'render <ReticleConnect /> before <Scripts /> in the root document', recipe);
|
|
387
|
+
if (PatchKind.MANUAL === patch.kind)
|
|
388
|
+
return [unverified, rendered];
|
|
389
|
+
const title = StepTitle.TANSTACK_START_CONNECT_COMPONENT;
|
|
390
|
+
const path = tanstackStartConnectPath(root);
|
|
391
|
+
const content = tanstackStartConnectFile(input.options.port, input.options.projectId, root);
|
|
392
|
+
const component = true === input.tanstackStartConnectExists
|
|
393
|
+
? // "file exists" used to end it without reading the port inside, so a re-run with a new
|
|
394
|
+
// `--port` left the page dialling the daemon that had moved.
|
|
395
|
+
alreadyOrMovedPort({ title, target: path, status: StepStatus.ALREADY, detail: 'file exists' }, input.tanstackStartConnectSource, input.options.port)
|
|
396
|
+
: {
|
|
397
|
+
title,
|
|
398
|
+
target: path,
|
|
399
|
+
status: StepStatus.APPLY,
|
|
400
|
+
detail: 'create the dev-only client connect (Start SSRs its own HTML)',
|
|
401
|
+
write: { path, content },
|
|
402
|
+
dependsOnInstall: true,
|
|
403
|
+
};
|
|
404
|
+
return [unverified, component, rendered];
|
|
602
405
|
}
|
|
603
406
|
export function svelteKitSteps(input) {
|
|
604
407
|
const unverified = {
|
|
605
|
-
title: StepTitle.
|
|
408
|
+
title: StepTitle.SVELTEKIT_SETUP_GATED,
|
|
606
409
|
target: SVELTEKIT_HOOKS_PATH,
|
|
607
410
|
status: StepStatus.NOTICE,
|
|
608
|
-
detail:
|
|
411
|
+
detail: SVELTEKIT_SETUP_GATED_NOTE,
|
|
609
412
|
};
|
|
610
413
|
// SvelteKit can't use the Vite-plugin injection (it renders via app.html) — wire a client hook
|
|
611
414
|
// that SvelteKit runs on startup, which is the path that can register a session at all.
|
|
612
415
|
if (true === input.svelteKitHooksExists) {
|
|
613
416
|
return [
|
|
614
417
|
unverified,
|
|
615
|
-
{
|
|
418
|
+
alreadyOrMovedPort({
|
|
616
419
|
title: StepTitle.CLIENT_HOOK,
|
|
617
420
|
target: SVELTEKIT_HOOKS_PATH,
|
|
618
421
|
status: StepStatus.ALREADY,
|
|
619
422
|
detail: 'file exists',
|
|
620
|
-
},
|
|
423
|
+
}, input.svelteKitHooksSource, input.options.port),
|
|
621
424
|
];
|
|
622
425
|
}
|
|
623
426
|
return [
|
|
@@ -636,7 +439,7 @@ export function svelteKitSteps(input) {
|
|
|
636
439
|
];
|
|
637
440
|
}
|
|
638
441
|
/**
|
|
639
|
-
* Astro: the config
|
|
442
|
+
* Astro: the config build target, and the connect script (with the pairing token) in ONE layout.
|
|
640
443
|
*
|
|
641
444
|
* Astro was the last gated stack left printing a recipe it did not apply. It still falls back to the
|
|
642
445
|
* printed one whenever the choice is not obvious — no config, no single layout, or a shape the
|
|
@@ -659,14 +462,15 @@ export function astroSteps(input) {
|
|
|
659
462
|
},
|
|
660
463
|
];
|
|
661
464
|
}
|
|
662
|
-
// ATOMIC. The connect snippet is useless without the config:
|
|
663
|
-
//
|
|
664
|
-
//
|
|
665
|
-
//
|
|
666
|
-
//
|
|
465
|
+
// ATOMIC. The connect snippet is useless without the config: without `build.target: 'es2022'`
|
|
466
|
+
// Astro down-levels the SDK and the dynamic import dies, and without `optimizeDeps.include` the
|
|
467
|
+
// first load 404s the hashed module. The token itself now lives in the layout frontmatter (#1008),
|
|
468
|
+
// but a layout patched on its own still cannot connect. Measured on a real fixture — config ⚠,
|
|
469
|
+
// layout ✓ — which reads as one step done and one caveat when it is actually a guaranteed
|
|
470
|
+
// non-connection. If either half cannot be applied, BOTH go manual with the single recipe.
|
|
667
471
|
const manualWithLayout = astroManual(input.options.port, input.options.projectId, layout.path);
|
|
668
|
-
const configPatch = patchAstroConfig(config.source);
|
|
669
|
-
const layoutPatch = patchAstroLayout(layout.source,
|
|
472
|
+
const configPatch = patchAstroConfig(config.source, input.viteMajor ?? null);
|
|
473
|
+
const layoutPatch = patchAstroLayout(layout.source, layout.path);
|
|
670
474
|
if (configPatch.kind === PatchKind.MANUAL || layoutPatch.kind === PatchKind.MANUAL) {
|
|
671
475
|
return [
|
|
672
476
|
{
|
|
@@ -678,60 +482,34 @@ export function astroSteps(input) {
|
|
|
678
482
|
];
|
|
679
483
|
}
|
|
680
484
|
const envPatch = patchAstroEnvDts(input.astroEnvDts ?? null);
|
|
485
|
+
const existingDev = input.astroReticleDev ?? null;
|
|
486
|
+
const devAlready = 'string' === typeof existingDev && existingDev.includes('reticle.connect');
|
|
487
|
+
const devStep = devAlready
|
|
488
|
+
? {
|
|
489
|
+
title: StepTitle.ASTRO_RETICLE_DEV,
|
|
490
|
+
target: ASTRO_RETICLE_DEV_PATH,
|
|
491
|
+
status: StepStatus.ALREADY,
|
|
492
|
+
detail: 'file exists, left alone',
|
|
493
|
+
}
|
|
494
|
+
: {
|
|
495
|
+
title: StepTitle.ASTRO_RETICLE_DEV,
|
|
496
|
+
target: ASTRO_RETICLE_DEV_PATH,
|
|
497
|
+
status: StepStatus.APPLY,
|
|
498
|
+
detail: 'write the local connect module (static SDK import, token from <meta>)',
|
|
499
|
+
write: {
|
|
500
|
+
path: ASTRO_RETICLE_DEV_PATH,
|
|
501
|
+
content: astroReticleDevFile(input.options.port, input.options.projectId, input.detection.uiLibrary, input.testids ?? []),
|
|
502
|
+
},
|
|
503
|
+
dependsOnInstall: true,
|
|
504
|
+
};
|
|
681
505
|
return [
|
|
682
|
-
patchStep(StepTitle.ASTRO_CONFIG, config.path, configPatch, '
|
|
683
|
-
patchStep(StepTitle.CONNECT_SNIPPET_ASTRO, layout.path, layoutPatch, 'add the
|
|
506
|
+
patchStep(StepTitle.ASTRO_CONFIG, config.path, configPatch, 'raise build.target to es2022 and keep .reticle/ out of the watcher', manualWithLayout),
|
|
507
|
+
patchStep(StepTitle.CONNECT_SNIPPET_ASTRO, layout.path, layoutPatch, 'add the pairing-token <meta> and a script that statically imports ReticleDev', manualWithLayout),
|
|
508
|
+
devStep,
|
|
684
509
|
// Declares the Vite define names so `astro check` (create-astro's default build) can see them
|
|
685
510
|
// (#677). Independent of the two halves above: even an ALREADY config/layout still needs this
|
|
686
511
|
// when the env file was never written.
|
|
687
|
-
patchStep(StepTitle.ASTRO_ENV_DTS, ASTRO_ENV_DTS_PATH, envPatch, 'declare __RETICLE_TOKEN__ / __RETICLE_ROOT__ for astro check', manualWithLayout),
|
|
688
|
-
];
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* A Content-Security-Policy whose `connect-src` excludes the bridge, said out loud at install time.
|
|
692
|
-
*
|
|
693
|
-
* Both reported cases were Next apps where `init` printed success for every step and the app then
|
|
694
|
-
* never connected: the browser blocked the WebSocket and reported it in its own console, which
|
|
695
|
-
* nothing on this side reads. A NOTICE rather than a step, because editing someone's security policy
|
|
696
|
-
* is theirs to do — but it carries the exact text to paste, which is the difference between a
|
|
697
|
-
* warning and a fix.
|
|
698
|
-
*/
|
|
699
|
-
export function cspStep(input) {
|
|
700
|
-
// Reads the SAME list `reticle doctor` reads. It used to read a hand-written pair —
|
|
701
|
-
// `[nextConfigSource, nextLayout?.source]` — while csp-doctor.ts already carried the full set
|
|
702
|
-
// including `index.html`, which is where every Vite and Electron app declares its policy. So the
|
|
703
|
-
// command you run BEFORE anything works checked less than the one you run after it has failed.
|
|
704
|
-
//
|
|
705
|
-
// Measured on MarkText, a production Electron editor: its renderer sets `default-src 'self'` with
|
|
706
|
-
// no `connect-src`, the browser blocked the bridge WebSocket, and `init` printed a clean plan. The
|
|
707
|
-
// daemon cannot see a dial that never left the page, so nothing anywhere said why — and the check
|
|
708
|
-
// that would have said it was sitting one import away.
|
|
709
|
-
// The pre-read Next sources are folded in ON TOP of the shared list, not replaced by it: init
|
|
710
|
-
// resolves `nextConfigFile` across more spellings than CSP_FILES names (`.mts`, for one), and a
|
|
711
|
-
// layout is found by search rather than by a fixed path. Two sources of the same truth is the
|
|
712
|
-
// problem being fixed here — one of them being a SUPERSET is not.
|
|
713
|
-
const extra = {
|
|
714
|
-
...(input.nextConfigFile !== null && input.nextConfigFile !== undefined
|
|
715
|
-
? { [input.nextConfigFile]: input.nextConfigSource ?? undefined }
|
|
716
|
-
: {}),
|
|
717
|
-
...(input.nextLayout ? { [input.nextLayout.path]: input.nextLayout.source } : {}),
|
|
718
|
-
};
|
|
719
|
-
const read = (file) => extra[file] ?? input.cspSources?.[file];
|
|
720
|
-
const findings = diagnoseWebCsp(read, input.options.port ?? RETICLE_DEFAULT_PORT, [
|
|
721
|
-
...Object.keys(extra),
|
|
722
|
-
]);
|
|
723
|
-
const first = findings[0];
|
|
724
|
-
if (first === undefined)
|
|
725
|
-
return [];
|
|
726
|
-
return [
|
|
727
|
-
{
|
|
728
|
-
title: CSP_STEP_TITLE,
|
|
729
|
-
target: first.file,
|
|
730
|
-
status: StepStatus.NOTICE,
|
|
731
|
-
// `problem` already ends with the text to paste; `fix` is the same sentence for callers that
|
|
732
|
-
// want it on its own (doctor renders them separately). Printing both said it twice.
|
|
733
|
-
detail: first.problem,
|
|
734
|
-
},
|
|
512
|
+
patchStep(StepTitle.ASTRO_ENV_DTS, ASTRO_ENV_DTS_PATH, envPatch, 'declare window.__RETICLE_TOKEN__ / __RETICLE_ROOT__ for astro check', manualWithLayout),
|
|
735
513
|
];
|
|
736
514
|
}
|
|
737
515
|
/**
|
|
@@ -742,12 +520,25 @@ export function cspStep(input) {
|
|
|
742
520
|
* where the if/else chain this replaced used to hand it these instructions silently.
|
|
743
521
|
*/
|
|
744
522
|
export function htmlSteps(input) {
|
|
523
|
+
// Read the page before calling the step undone. It used to be MANUAL unconditionally, so a re-run
|
|
524
|
+
// over a page that already connects said "This app will NOT connect until the ⚠ step".
|
|
525
|
+
const index = input.htmlIndexSource ?? null;
|
|
526
|
+
if (null !== index && hasStaticSnippet(index)) {
|
|
527
|
+
return [
|
|
528
|
+
alreadyOrMovedPort({
|
|
529
|
+
title: StepTitle.CONNECT_SNIPPET,
|
|
530
|
+
target: HTML_INDEX_PATH,
|
|
531
|
+
status: StepStatus.ALREADY,
|
|
532
|
+
detail: 'index.html already loads the Reticle SDK',
|
|
533
|
+
}, index, input.options.port),
|
|
534
|
+
];
|
|
535
|
+
}
|
|
745
536
|
return [
|
|
746
537
|
{
|
|
747
538
|
title: StepTitle.CONNECT_SNIPPET,
|
|
748
|
-
target:
|
|
539
|
+
target: HTML_INDEX_PATH,
|
|
749
540
|
status: StepStatus.MANUAL,
|
|
750
|
-
detail: htmlManual(input.options.port, input.options.projectId, input.pairingToken),
|
|
541
|
+
detail: htmlManual(input.options.port, input.options.projectId, input.pairingToken, input.detection.uiLibrary),
|
|
751
542
|
},
|
|
752
543
|
];
|
|
753
544
|
}
|