@reticlehq/init 3.5.0 → 3.7.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/detect.d.ts +8 -0
- package/dist/detect/detect.js +6 -3
- package/dist/detect/dev-script.d.ts +17 -0
- package/dist/detect/dev-script.js +128 -0
- package/dist/detect/non-js-project.d.ts +23 -0
- package/dist/detect/non-js-project.js +55 -23
- package/dist/detect/workspace-apps.d.ts +16 -0
- package/dist/detect/workspace-apps.js +70 -0
- package/dist/detect/workspace-redirect.d.ts +12 -0
- package/dist/detect/workspace-redirect.js +84 -9
- package/dist/diagnose/closing-hint.d.ts +7 -1
- package/dist/diagnose/closing-hint.js +39 -12
- package/dist/diagnose/csp-check.js +65 -20
- package/dist/diagnose/init-failure.d.ts +2 -0
- package/dist/diagnose/init-failure.js +2 -0
- package/dist/gather-plan-input.d.ts +11 -0
- package/dist/gather-plan-input.js +416 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +7 -2
- package/dist/no-package-json.js +17 -8
- package/dist/node-io.d.ts +1 -1
- package/dist/node-io.js +11 -2
- package/dist/patch/next-patch.d.ts +28 -0
- package/dist/patch/next-patch.js +66 -0
- package/dist/patch/nuxt-snippets.js +1 -1
- package/dist/patch/snippets.d.ts +1 -1
- package/dist/patch/snippets.js +2 -2
- package/dist/patch/static-page.d.ts +1 -0
- package/dist/patch/static-page.js +3 -3
- package/dist/plan/connect-steps.d.ts +1 -0
- package/dist/plan/connect-steps.js +5 -0
- package/dist/plan/framework-adapter.js +2 -2
- package/dist/plan/plan-angular.js +1 -1
- package/dist/plan/plan-framework.d.ts +1 -1
- package/dist/plan/plan-framework.js +47 -13
- package/dist/plan/plan-types.d.ts +23 -2
- package/dist/plan/plan-vite.js +15 -0
- package/dist/plan/plan.js +68 -10
- package/dist/plan/stop-hook-step.d.ts +19 -0
- package/dist/plan/stop-hook-step.js +74 -1
- package/dist/project/agent-rules.d.ts +1 -1
- package/dist/project/agent-rules.js +16 -11
- package/dist/register/codex-toml.d.ts +12 -0
- package/dist/register/codex-toml.js +44 -0
- package/dist/register/mcp.d.ts +15 -2
- package/dist/register/mcp.js +16 -0
- package/dist/run-types.d.ts +6 -0
- package/dist/run.d.ts +1 -1
- package/dist/run.js +27 -414
- package/dist/sdk-packages.d.ts +19 -0
- package/dist/sdk-packages.js +34 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -29,8 +29,9 @@ export { DEV_SERVER_PORTS, isLikelyDevServerPort, devServerPortWarning, } from '
|
|
|
29
29
|
export { configWithInstallSource } from './project/install-source-config.js';
|
|
30
30
|
export { projectIdOf, rememberProjectOnDisk, type RegistryIo } from './project/remember-project.js';
|
|
31
31
|
export { detectPackageManager, Framework, installCommandParts, parseMajor, PackageManager, } from './detect/detect.js';
|
|
32
|
-
export { findWorkspaceApps } from './detect/workspace-apps.js';
|
|
32
|
+
export { enclosingWorkspaceRoot, findWorkspaceApps, isWebApp, workspacePackageDirs, } from './detect/workspace-apps.js';
|
|
33
33
|
export { desktopLaunch, type DesktopLaunch } from './detect/dev-script.js';
|
|
34
|
+
export { detectNonJsEcosystem, noPackageJsonMessage } from './detect/non-js-project.js';
|
|
34
35
|
export { deriveProjectId, packageName } from './project/project-id.js';
|
|
35
36
|
export { refreshAgentRules } from './project/refresh-rules.js';
|
|
36
37
|
export { diagnoseDesktop, isDesktopProject } from './diagnose/desktop-doctor.js';
|
|
@@ -46,5 +47,6 @@ export { reticleConfigContent } from './patch/snippets.js';
|
|
|
46
47
|
* here", and the two would disagree the first time a client was added to one of them.
|
|
47
48
|
*/
|
|
48
49
|
export { McpClient, ConfigScope, MCP_CLIENTS, ClientMergeStatus, clientSpec, mergeClientConfig, } from './register/mcp-clients.js';
|
|
49
|
-
export { claudeAddCommand, claudeAvailableProbe, claudeHasReticle, } from './register/mcp.js';
|
|
50
|
+
export { claudeAddCommand, claudeAvailableProbe, claudeHasReticle, codexAddCommand, codexAvailableProbe, } from './register/mcp.js';
|
|
51
|
+
export { codexNamesOurServer } from './register/codex-toml.js';
|
|
50
52
|
export { detectMcpClients, type DetectedClient } from './register/detect-clients.js';
|
package/dist/index.js
CHANGED
|
@@ -29,8 +29,9 @@ export { DEV_SERVER_PORTS, isLikelyDevServerPort, devServerPortWarning, } from '
|
|
|
29
29
|
export { configWithInstallSource } from './project/install-source-config.js';
|
|
30
30
|
export { projectIdOf, rememberProjectOnDisk } from './project/remember-project.js';
|
|
31
31
|
export { detectPackageManager, Framework, installCommandParts, parseMajor, PackageManager, } from './detect/detect.js';
|
|
32
|
-
export { findWorkspaceApps } from './detect/workspace-apps.js';
|
|
32
|
+
export { enclosingWorkspaceRoot, findWorkspaceApps, isWebApp, workspacePackageDirs, } from './detect/workspace-apps.js';
|
|
33
33
|
export { desktopLaunch } from './detect/dev-script.js';
|
|
34
|
+
export { detectNonJsEcosystem, noPackageJsonMessage } from './detect/non-js-project.js';
|
|
34
35
|
export { deriveProjectId, packageName } from './project/project-id.js';
|
|
35
36
|
export { refreshAgentRules } from './project/refresh-rules.js';
|
|
36
37
|
export { diagnoseDesktop, isDesktopProject } from './diagnose/desktop-doctor.js';
|
|
@@ -50,5 +51,9 @@ export { claudeAddCommand,
|
|
|
50
51
|
// The two probes `run.ts` uses to decide about Claude Code. Exported because `reticle setup mcp`
|
|
51
52
|
// asks the same question and had no way to ask it: Claude Code keeps no config file, so the
|
|
52
53
|
// file-reading detector cannot see it, and the installer silently skipped the commonest client.
|
|
53
|
-
claudeAvailableProbe, claudeHasReticle,
|
|
54
|
+
claudeAvailableProbe, claudeHasReticle,
|
|
55
|
+
// Codex keeps its registration behind `codex mcp add`, like Claude. `reticle setup mcp` needs the
|
|
56
|
+
// command, the probe and the "is the entry already there, in any shape" check to do the same.
|
|
57
|
+
codexAddCommand, codexAvailableProbe, } from './register/mcp.js';
|
|
58
|
+
export { codexNamesOurServer } from './register/codex-toml.js';
|
|
54
59
|
export { detectMcpClients } from './register/detect-clients.js';
|
package/dist/no-package-json.js
CHANGED
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
* static page it can see whole, or prints the snippet for the stack it recognised (Streamlit,
|
|
7
7
|
* Django, several pages) and stops.
|
|
8
8
|
*/
|
|
9
|
-
import { join } from 'node:path';
|
|
10
9
|
import { PackageManager, Framework } from './detect/detect.js';
|
|
11
|
-
import { detectDjangoProject, detectStreamlitProject, djangoSetupMessage,
|
|
10
|
+
import { detectDjangoProject, detectStreamlitProject, djangoSetupMessage, NoPackageJsonCase, noPackageJsonAnswer, streamlitSetupMessage, } from './detect/non-js-project.js';
|
|
12
11
|
import { InitFailure } from './diagnose/init-failure.js';
|
|
13
12
|
import { HTML_INDEX_PATH, STATIC_TOKEN_MODULE, staticTokenFiles, withStaticSnippet, } from './patch/static-page.js';
|
|
14
13
|
import { connectArg, connectArgWithToken, djangoMiddlewareSnippet, reticleConfigContent, staticPageSnippet, streamlitPageSnippet, } from './patch/snippets.js';
|
|
@@ -60,9 +59,23 @@ function wireStaticPage(options, io, connect) {
|
|
|
60
59
|
};
|
|
61
60
|
}
|
|
62
61
|
export function initWithoutPackageJson(options, io) {
|
|
62
|
+
// Relative to the IO's own root, like every other read here (`readFile` below, `HTML_INDEX_PATH`).
|
|
63
|
+
// The real IO resolves it to the same file; joining `options.cwd` onto it only made these checks
|
|
64
|
+
// miss in the in-memory IO the tests use, so no test could tell the branches below apart.
|
|
65
|
+
const existsHere = (file) => io.exists(file);
|
|
63
66
|
const streamlit = detectStreamlitProject((file) => io.readFile(file), io.rootFiles());
|
|
64
67
|
// Asked only when Streamlit already said no, so the two can never both claim the page.
|
|
65
|
-
const django = !streamlit && detectDjangoProject(
|
|
68
|
+
const django = !streamlit && detectDjangoProject(existsHere, io.rootFiles());
|
|
69
|
+
// "Run init from your app's directory" is the whole answer, so stop at it. This used to fall
|
|
70
|
+
// through to the shared tail below: it wrote `.reticle.json` into the directory it had just called
|
|
71
|
+
// wrong, then printed a script-tag snippet under a message that asks for none (#1366).
|
|
72
|
+
const answer = noPackageJsonAnswer(existsHere, io.rootFiles());
|
|
73
|
+
if (!streamlit && !django && answer.kind === NoPackageJsonCase.WRONG_DIRECTORY) {
|
|
74
|
+
io.print(answer.message);
|
|
75
|
+
if (!options.dryRun)
|
|
76
|
+
io.host.reportOutcome({ ok: false, reason: InitFailure.NO_PACKAGE_JSON });
|
|
77
|
+
return { ok: false, applied: 0, manual: 0 };
|
|
78
|
+
}
|
|
66
79
|
// Scope the project on disk before anything else, so the daemon can serve this page.
|
|
67
80
|
//
|
|
68
81
|
// This path used to print and exit, leaving no `.reticle.json` at all — and the reporter who
|
|
@@ -110,11 +123,7 @@ export function initWithoutPackageJson(options, io) {
|
|
|
110
123
|
// a path problem and goes looking for a directory that cannot exist — reported from a
|
|
111
124
|
// Streamlit app, where the search continued into hunting for a browser bundle to inject by
|
|
112
125
|
// hand before the real answer surfaced.
|
|
113
|
-
streamlit
|
|
114
|
-
? streamlitSetupMessage()
|
|
115
|
-
: django
|
|
116
|
-
? djangoSetupMessage()
|
|
117
|
-
: noPackageJsonMessage((file) => io.exists(join(options.cwd, file)), io.rootFiles()));
|
|
126
|
+
streamlit ? streamlitSetupMessage() : django ? djangoSetupMessage() : answer.message);
|
|
118
127
|
writeConfig();
|
|
119
128
|
// The message says "add the snippet below". Print the snippet, or the message is the same broken
|
|
120
129
|
// promise in the other direction.
|
package/dist/node-io.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { InitHost } from './host.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* Every program `init` is ever allowed to run.
|
|
10
10
|
*
|
|
11
|
-
* The set is closed and tiny — the four package managers, `npx`, `corepack`, and the Claude
|
|
11
|
+
* The set is closed and tiny — the four package managers, `npx`, `corepack`, and the Claude and Codex CLIs —
|
|
12
12
|
* because those are the only things the plan can ask for. It is a named constant for the same reason
|
|
13
13
|
* every other wire string here is one, and it is CHECKED because of the shell: on Windows these spawn
|
|
14
14
|
* through a shell, and a shell turns the command name into something the shell parses rather than a
|
package/dist/node-io.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* command, not the MCP stdio transport.
|
|
5
5
|
*/
|
|
6
6
|
import { terminalWidth, wrapForTerminal } from './diagnose/terminal-wrap.js';
|
|
7
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, accessSync, constants, } from 'node:fs';
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, accessSync, constants, lstatSync, } from 'node:fs';
|
|
8
8
|
import { NodePlatform } from './detect/platform.js';
|
|
9
9
|
import { join, dirname, isAbsolute } from 'node:path';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
@@ -24,7 +24,7 @@ function shellSafe(args) {
|
|
|
24
24
|
/**
|
|
25
25
|
* Every program `init` is ever allowed to run.
|
|
26
26
|
*
|
|
27
|
-
* The set is closed and tiny — the four package managers, `npx`, `corepack`, and the Claude
|
|
27
|
+
* The set is closed and tiny — the four package managers, `npx`, `corepack`, and the Claude and Codex CLIs —
|
|
28
28
|
* because those are the only things the plan can ask for. It is a named constant for the same reason
|
|
29
29
|
* every other wire string here is one, and it is CHECKED because of the shell: on Windows these spawn
|
|
30
30
|
* through a shell, and a shell turns the command name into something the shell parses rather than a
|
|
@@ -46,6 +46,7 @@ export const RUNNABLE_COMMANDS = [
|
|
|
46
46
|
'npx',
|
|
47
47
|
'corepack',
|
|
48
48
|
'claude',
|
|
49
|
+
'codex',
|
|
49
50
|
];
|
|
50
51
|
/** The command, proven to be one of `RUNNABLE_COMMANDS` — never the caller's string. */
|
|
51
52
|
function runnable(command) {
|
|
@@ -158,6 +159,14 @@ export function buildNodeIo(cwd, host, output = {}) {
|
|
|
158
159
|
return [];
|
|
159
160
|
}
|
|
160
161
|
},
|
|
162
|
+
isSymlink(rel) {
|
|
163
|
+
try {
|
|
164
|
+
return lstatSync(abs(rel)).isSymbolicLink();
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
161
170
|
scoped(rel) {
|
|
162
171
|
// The host travels with the re-rooted IO: a monorepo redirect re-enters `runInit` through
|
|
163
172
|
// this, and an untraced, unreported inner run is the half of init that actually did the work.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* half-edit. These must not be left manual: both edits fail silently when skipped, and one of them
|
|
6
6
|
* is JSX.
|
|
7
7
|
*/
|
|
8
|
+
import type { InitIo } from '../run-types.js';
|
|
8
9
|
import { type SourcePatch } from './patch-kind.js';
|
|
9
10
|
/** Where the generated connect component goes, and how the mount file should import it. */
|
|
10
11
|
interface ReticleDevLocation {
|
|
@@ -41,9 +42,36 @@ export declare function patchRootLayout(source: string): SourcePatch;
|
|
|
41
42
|
* ReticleDev component has to land NEXT TO the layout or the relative import it generates is dead.
|
|
42
43
|
*/
|
|
43
44
|
export declare const NEXT_LAYOUT_CANDIDATES: string[];
|
|
45
|
+
/** What `findRouteGroupRootLayout` found: the layout to mount into, or the ones it could not choose between. */
|
|
46
|
+
export type RouteGroupLayouts = {
|
|
47
|
+
path: string;
|
|
48
|
+
} | {
|
|
49
|
+
candidates: string[];
|
|
50
|
+
};
|
|
51
|
+
type ListingIo = Pick<InitIo, 'listDirs' | 'listFiles' | 'readFile'>;
|
|
52
|
+
/**
|
|
53
|
+
* The root layout of an App Router app with no top-level `app/layout.*`: its root layouts then live
|
|
54
|
+
* in route groups (`app/(site)/layout.tsx`), one per group, which Next documents as "multiple root
|
|
55
|
+
* layouts" (#1538). `null` when the app has none, so the Pages Router candidates are still asked.
|
|
56
|
+
*
|
|
57
|
+
* One layout is chosen, because a step patches one file: a group layout that already mounts
|
|
58
|
+
* `<ReticleDev />` (so the patch reports it already wired), else the one group that renders `/`,
|
|
59
|
+
* else the only group layout there is. Several and none of those is returned as candidates, so the
|
|
60
|
+
* manual step can name them rather than say there is no root layout.
|
|
61
|
+
*/
|
|
62
|
+
export declare function findRouteGroupRootLayout(io: ListingIo): RouteGroupLayouts | null;
|
|
44
63
|
/**
|
|
45
64
|
* Pages Router mount points, checked only when there is no App Router layout. A Pages app has no
|
|
46
65
|
* `app/` directory at all, so writing the component there produced a file nothing imported.
|
|
47
66
|
*/
|
|
48
67
|
export declare const NEXT_PAGES_APP_CANDIDATES: string[];
|
|
68
|
+
/**
|
|
69
|
+
* The file `<ReticleDev />` is mounted in: the App Router's root layout, then a route-group root
|
|
70
|
+
* layout (#1538), then the Pages Router's `_app`. `candidates` is set when route groups hold several
|
|
71
|
+
* root layouts and none could be chosen, so the manual step can name them.
|
|
72
|
+
*/
|
|
73
|
+
export declare function findNextMountFile(io: ListingIo & Pick<InitIo, 'exists'>): {
|
|
74
|
+
path: string | null;
|
|
75
|
+
candidates?: string[];
|
|
76
|
+
};
|
|
49
77
|
export {};
|
package/dist/patch/next-patch.js
CHANGED
|
@@ -223,6 +223,57 @@ export const NEXT_LAYOUT_CANDIDATES = [
|
|
|
223
223
|
'src/app/layout.jsx',
|
|
224
224
|
'src/app/layout.js',
|
|
225
225
|
];
|
|
226
|
+
/** The App Router roots, in the order `NEXT_LAYOUT_CANDIDATES` checks them. */
|
|
227
|
+
const NEXT_APP_DIRS = ['app', 'src/app'];
|
|
228
|
+
const LAYOUT_BASENAMES = ['layout.tsx', 'layout.jsx', 'layout.js'];
|
|
229
|
+
const PAGE_FILE = /^page\.(tsx|ts|jsx|js|mdx)$/;
|
|
230
|
+
/** `(marketing)`: a route group, which organises routes without adding a URL segment. */
|
|
231
|
+
const ROUTE_GROUP = /^\(.+\)$/;
|
|
232
|
+
/** Whether `dir` renders `/`: a page of its own, or one in a route group nested under it. */
|
|
233
|
+
function servesRoot(io, dir) {
|
|
234
|
+
if (io.listFiles(dir).some((name) => PAGE_FILE.test(name)))
|
|
235
|
+
return true;
|
|
236
|
+
return io
|
|
237
|
+
.listDirs(dir)
|
|
238
|
+
.filter((name) => ROUTE_GROUP.test(name))
|
|
239
|
+
.some((name) => servesRoot(io, `${dir}/${name}`));
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* The root layout of an App Router app with no top-level `app/layout.*`: its root layouts then live
|
|
243
|
+
* in route groups (`app/(site)/layout.tsx`), one per group, which Next documents as "multiple root
|
|
244
|
+
* layouts" (#1538). `null` when the app has none, so the Pages Router candidates are still asked.
|
|
245
|
+
*
|
|
246
|
+
* One layout is chosen, because a step patches one file: a group layout that already mounts
|
|
247
|
+
* `<ReticleDev />` (so the patch reports it already wired), else the one group that renders `/`,
|
|
248
|
+
* else the only group layout there is. Several and none of those is returned as candidates, so the
|
|
249
|
+
* manual step can name them rather than say there is no root layout.
|
|
250
|
+
*/
|
|
251
|
+
export function findRouteGroupRootLayout(io) {
|
|
252
|
+
for (const appDir of NEXT_APP_DIRS) {
|
|
253
|
+
const layouts = io
|
|
254
|
+
.listDirs(appDir)
|
|
255
|
+
.filter((name) => ROUTE_GROUP.test(name))
|
|
256
|
+
.sort()
|
|
257
|
+
.flatMap((group) => {
|
|
258
|
+
const dir = `${appDir}/${group}`;
|
|
259
|
+
const files = io.listFiles(dir);
|
|
260
|
+
const layout = LAYOUT_BASENAMES.find((name) => files.includes(name));
|
|
261
|
+
return layout === undefined ? [] : [{ dir, path: `${dir}/${layout}` }];
|
|
262
|
+
});
|
|
263
|
+
if (0 === layouts.length)
|
|
264
|
+
continue;
|
|
265
|
+
const wired = layouts.find((l) => true === io.readFile(l.path)?.includes(RETICLE_DEV_COMPONENT));
|
|
266
|
+
if (wired !== undefined)
|
|
267
|
+
return { path: wired.path };
|
|
268
|
+
const root = layouts.filter((l) => servesRoot(io, l.dir));
|
|
269
|
+
if (1 === root.length && root[0] !== undefined)
|
|
270
|
+
return { path: root[0].path };
|
|
271
|
+
if (1 === layouts.length && layouts[0] !== undefined)
|
|
272
|
+
return { path: layouts[0].path };
|
|
273
|
+
return { candidates: layouts.map((l) => l.path) };
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
226
277
|
/**
|
|
227
278
|
* Pages Router mount points, checked only when there is no App Router layout. A Pages app has no
|
|
228
279
|
* `app/` directory at all, so writing the component there produced a file nothing imported.
|
|
@@ -235,3 +286,18 @@ export const NEXT_PAGES_APP_CANDIDATES = [
|
|
|
235
286
|
'src/pages/_app.jsx',
|
|
236
287
|
'src/pages/_app.js',
|
|
237
288
|
];
|
|
289
|
+
/**
|
|
290
|
+
* The file `<ReticleDev />` is mounted in: the App Router's root layout, then a route-group root
|
|
291
|
+
* layout (#1538), then the Pages Router's `_app`. `candidates` is set when route groups hold several
|
|
292
|
+
* root layouts and none could be chosen, so the manual step can name them.
|
|
293
|
+
*/
|
|
294
|
+
export function findNextMountFile(io) {
|
|
295
|
+
const top = NEXT_LAYOUT_CANDIDATES.find((p) => io.exists(p));
|
|
296
|
+
if (top !== undefined)
|
|
297
|
+
return { path: top };
|
|
298
|
+
const groups = findRouteGroupRootLayout(io);
|
|
299
|
+
if (groups !== null && 'path' in groups)
|
|
300
|
+
return { path: groups.path };
|
|
301
|
+
const pagesApp = NEXT_PAGES_APP_CANDIDATES.find((p) => io.exists(p)) ?? null;
|
|
302
|
+
return null === groups ? { path: pagesApp } : { path: pagesApp, candidates: groups.candidates };
|
|
303
|
+
}
|
|
@@ -130,6 +130,6 @@ and no index.html to inject into. Wire it with a dev-only CLIENT plugin, which i
|
|
|
130
130
|
|
|
131
131
|
The package is @reticlehq/browser — the framework-neutral sensor. DOM, network, console, routing and
|
|
132
132
|
source file:line all work in Vue. What you do not get is React component identity, which is the only
|
|
133
|
-
thing the React adapter adds. The install gate scaffolds a Nuxt app
|
|
133
|
+
thing the React adapter adds. The install gate scaffolds a Nuxt app whenever the install path changes and requires a session, so this wiring
|
|
134
134
|
is proven; if something does not work, please open an issue.`;
|
|
135
135
|
}
|
package/dist/patch/snippets.d.ts
CHANGED
|
@@ -182,7 +182,7 @@ export declare const SVELTEKIT_HOOKS_PATH = "src/hooks.client.ts";
|
|
|
182
182
|
* reporting all-green.
|
|
183
183
|
*/
|
|
184
184
|
export declare function unverifiedUiLibraryNote(library: string, installGated?: boolean): string;
|
|
185
|
-
export declare const SVELTEKIT_SETUP_GATED_NOTE = "The install gate scaffolds a SvelteKit app
|
|
185
|
+
export declare const SVELTEKIT_SETUP_GATED_NOTE = "The install gate scaffolds a SvelteKit app whenever the install path changes and requires this hook to connect a session, so the wiring is proven; driving flows on SvelteKit to a verdict is not. If the hook does not register a session, please open an issue.";
|
|
186
186
|
export declare const UNVERIFIED_TANSTACK_START_NOTE = "Reticle has no TanStack Start app and no CI gate for one, so this wiring is untested \u2014 it may work, but nothing proves it and nothing will tell us if it breaks. Gated today: Vite (React, Vue), Next.js, Nuxt, React Router, Remix, Astro, SvelteKit, Create React App and Angular. If the client effect does not register a session, please open an issue.";
|
|
187
187
|
/**
|
|
188
188
|
* Printed when init detects react-three-fiber. Reticle's model is DOM + store + network; a WebGL
|
package/dist/patch/snippets.js
CHANGED
|
@@ -675,9 +675,9 @@ export function unverifiedUiLibraryNote(library, installGated = false) {
|
|
|
675
675
|
const identity = 'preact' === library
|
|
676
676
|
? 'React component identity — component names and stacks — comes from `@reticlehq/react`, which reaches Preact through `preact/compat`. That path is not covered by a CI gate here, so treat it as expected-to-work rather than proven.'
|
|
677
677
|
: 'What `@reticlehq/react` adds and you will NOT get is React component identity: component names and component stacks.';
|
|
678
|
-
return `Detected a ${library} app. Reticle's DOM, network, console and state tools work here. ${'vue' === library ? 'Source `file:line` does NOT come through: the build plugin stamps JSX and, separately, Svelte components, and a Vue single-file component is neither — measured, a Svelte counter reports `src/lib/Counter.svelte:5` and the same drive on Vue reports no source at all.' : 'Source `file:line` does too — the build plugin stamps it for this library (measured on preact and svelte).'} ${identity} ${installGated ? `The install gate scaffolds this ${library} stack from scratch
|
|
678
|
+
return `Detected a ${library} app. Reticle's DOM, network, console and state tools work here. ${'vue' === library ? 'Source `file:line` does NOT come through: the build plugin stamps JSX and, separately, Svelte components, and a Vue single-file component is neither — measured, a Svelte counter reports `src/lib/Counter.svelte:5` and the same drive on Vue reports no source at all.' : 'Source `file:line` does too — the build plugin stamps it for this library (measured on preact and svelte).'} ${identity} ${installGated ? `The install gate scaffolds this ${library} stack from scratch whenever the install path changes, so this SETUP is proven; no gate drives it to a verdict, so the drive is not.` : `No CI gate covers ${library}.`} Driven to a verdict in CI: Vite + React and Next.js; Astro is checked for a connected session. If something doesn't work, please open an issue.`;
|
|
679
679
|
}
|
|
680
|
-
export const SVELTEKIT_SETUP_GATED_NOTE = 'The install gate scaffolds a SvelteKit app
|
|
680
|
+
export const SVELTEKIT_SETUP_GATED_NOTE = 'The install gate scaffolds a SvelteKit app whenever the install path changes and requires this hook to connect a session, so the wiring is proven; driving flows on SvelteKit to a verdict is not. If the hook does not register a session, please open an issue.';
|
|
681
681
|
export const UNVERIFIED_TANSTACK_START_NOTE = 'Reticle has no TanStack Start app and no CI gate for one, so this wiring is untested — it may work, but nothing proves it and nothing will tell us if it breaks. Gated today: Vite (React, Vue), Next.js, Nuxt, React Router, Remix, Astro, SvelteKit, Create React App and Angular. If the client effect does not register a session, please open an issue.';
|
|
682
682
|
/**
|
|
683
683
|
* Printed when init detects react-three-fiber. Reticle's model is DOM + store + network; a WebGL
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
export declare const HTML_INDEX_PATH = "index.html";
|
|
23
23
|
/** The gitignored module beside the page that holds this machine's pairing token. */
|
|
24
24
|
export declare const STATIC_TOKEN_MODULE = "reticle.local.js";
|
|
25
|
+
export declare const STATIC_GITIGNORE_PATH = ".gitignore";
|
|
25
26
|
/** Opens the block `init` writes; a re-run looks for it. */
|
|
26
27
|
export declare const STATIC_SNIPPET_MARKER = "<!-- reticle:connect (dev only, written by reticle init) -->";
|
|
27
28
|
/** Whether this page already carries a Reticle connect. */
|
|
@@ -23,7 +23,7 @@ import { CDN_SDK_URL } from './snippets.js';
|
|
|
23
23
|
export const HTML_INDEX_PATH = 'index.html';
|
|
24
24
|
/** The gitignored module beside the page that holds this machine's pairing token. */
|
|
25
25
|
export const STATIC_TOKEN_MODULE = 'reticle.local.js';
|
|
26
|
-
const
|
|
26
|
+
export const STATIC_GITIGNORE_PATH = '.gitignore';
|
|
27
27
|
/** Opens the block `init` writes; a re-run looks for it. */
|
|
28
28
|
export const STATIC_SNIPPET_MARKER = '<!-- reticle:connect (dev only, written by reticle init) -->';
|
|
29
29
|
const STATIC_SNIPPET_END = '<!-- /reticle:connect -->';
|
|
@@ -71,12 +71,12 @@ export function staticTokenFiles(pairingToken, readFile) {
|
|
|
71
71
|
const module = staticTokenModule(pairingToken);
|
|
72
72
|
if (readFile(STATIC_TOKEN_MODULE) !== module)
|
|
73
73
|
files[STATIC_TOKEN_MODULE] = module;
|
|
74
|
-
const ignore = readFile(
|
|
74
|
+
const ignore = readFile(STATIC_GITIGNORE_PATH) ?? '';
|
|
75
75
|
const covered = ignore
|
|
76
76
|
.split('\n')
|
|
77
77
|
.some((l) => [STATIC_TOKEN_MODULE, `/${STATIC_TOKEN_MODULE}`].includes(l.trim()));
|
|
78
78
|
if (!covered) {
|
|
79
|
-
files[
|
|
79
|
+
files[STATIC_GITIGNORE_PATH] =
|
|
80
80
|
`${'' === ignore ? '' : ignore.replace(/\n*$/, '\n')}${STATIC_TOKEN_MODULE}\n`;
|
|
81
81
|
}
|
|
82
82
|
return files;
|
|
@@ -21,6 +21,7 @@ export declare const StepTitle: {
|
|
|
21
21
|
readonly CONNECT_MODULE: "Reticle connect module";
|
|
22
22
|
readonly PAIRING_TOKEN: "Pairing token";
|
|
23
23
|
readonly PAIRING_TOKEN_PER_MACHINE: "Pairing token is per-machine";
|
|
24
|
+
readonly STATIC_PAIRING_TOKEN: "Pairing token (static page)";
|
|
24
25
|
readonly CONNECT_SNIPPET_CRA: "Connect snippet (CRA)";
|
|
25
26
|
readonly CONNECT_SNIPPET_NUXT: "Connect snippet (Nuxt)";
|
|
26
27
|
readonly NUXT_CONFIG: "Nuxt config (token + watcher)";
|
|
@@ -22,6 +22,7 @@ export const StepTitle = {
|
|
|
22
22
|
CONNECT_MODULE: 'Reticle connect module',
|
|
23
23
|
PAIRING_TOKEN: 'Pairing token',
|
|
24
24
|
PAIRING_TOKEN_PER_MACHINE: 'Pairing token is per-machine',
|
|
25
|
+
STATIC_PAIRING_TOKEN: 'Pairing token (static page)',
|
|
25
26
|
CONNECT_SNIPPET_CRA: 'Connect snippet (CRA)',
|
|
26
27
|
CONNECT_SNIPPET_NUXT: 'Connect snippet (Nuxt)',
|
|
27
28
|
NUXT_CONFIG: 'Nuxt config (token + watcher)',
|
|
@@ -98,6 +99,10 @@ export const CONNECT_STEP_TITLES = new Set([
|
|
|
98
99
|
// not, and `gate:install` scaffolds no CRA app, so that change would ship with no coverage of the
|
|
99
100
|
// path it changes. It is worth doing, and worth doing with a scaffold behind it.
|
|
100
101
|
StepTitle.VITE_PLUGIN,
|
|
102
|
+
// A plain page imports its token from a gitignored sibling module. Unlike CRA's, this step never
|
|
103
|
+
// goes MANUAL by plan: it is ⚠ only when the write itself failed — no token module means the page
|
|
104
|
+
// is refused, and no ignore entry means the token is on disk without the protection we promised.
|
|
105
|
+
StepTitle.STATIC_PAIRING_TOKEN,
|
|
101
106
|
// electron-vite's connect IS the renderer plugin. Without it the SDK never injects, and the
|
|
102
107
|
// preload/capture steps are not enough to produce a session.
|
|
103
108
|
StepTitle.ELECTRON_VITE_PLUGIN,
|
|
@@ -223,10 +223,10 @@ export const FRAMEWORK_ADAPTERS = {
|
|
|
223
223
|
carriesOwnUnverifiedNote: false,
|
|
224
224
|
},
|
|
225
225
|
[Framework.HTML]: {
|
|
226
|
-
// No bundler plugin to install —
|
|
226
|
+
// No bundler plugin to install — write the static connect into index.html when it is present.
|
|
227
227
|
packages: (kit) => [kit],
|
|
228
228
|
steps: htmlSteps,
|
|
229
|
-
connectStepTitles: [StepTitle.CONNECT_SNIPPET],
|
|
229
|
+
connectStepTitles: [StepTitle.CONNECT_SNIPPET, StepTitle.STATIC_PAIRING_TOKEN],
|
|
230
230
|
carriesOwnUnverifiedNote: false,
|
|
231
231
|
},
|
|
232
232
|
};
|
|
@@ -9,7 +9,7 @@ import { StepTitle } from './connect-steps.js';
|
|
|
9
9
|
import { patchStep } from './plan-framework.js';
|
|
10
10
|
import { alreadyOrMovedPort, retargetPort } from './port-steps.js';
|
|
11
11
|
import { StepStatus } from './plan-types.js';
|
|
12
|
-
const ANGULAR_SETUP_GATED_NOTE = 'The install gate scaffolds an Angular app (Angular 21, no SSR)
|
|
12
|
+
const ANGULAR_SETUP_GATED_NOTE = 'The install gate scaffolds an Angular app (Angular 21, no SSR) whenever the install path changes and requires a ' +
|
|
13
13
|
'session, and this wiring was proven by hand on Angular 22 with and without SSR; nothing yet ' +
|
|
14
14
|
'drives an Angular app to a verdict on each change. DOM, network, console and state ' +
|
|
15
15
|
'tools work here; component identity does not (that is the React adapter). If no session ' +
|
|
@@ -66,7 +66,7 @@ export declare function svelteKitSteps(input: PlanInput): Step[];
|
|
|
66
66
|
*/
|
|
67
67
|
export declare function astroSteps(input: PlanInput): Step[];
|
|
68
68
|
/**
|
|
69
|
-
* The plain-HTML path:
|
|
69
|
+
* The plain-HTML path: write the static connect when the page exists, otherwise print the recipe.
|
|
70
70
|
*
|
|
71
71
|
* A function of its own, and NOT a fallthrough. Every framework is sent here on purpose or not at
|
|
72
72
|
* all — a member of `Framework` with no adapter entry is a compile error in `FRAMEWORK_ADAPTERS`,
|
|
@@ -9,14 +9,25 @@ import { patchNextConfig, patchRootLayout, patchPagesApp } from '../patch/next-p
|
|
|
9
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 { 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';
|
|
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, connectArg, } from '../patch/snippets.js';
|
|
13
13
|
import { StepStatus } from './plan-types.js';
|
|
14
14
|
import { StepTitle } from './connect-steps.js';
|
|
15
15
|
import { patchTanstackStartRoot, tanstackStartConnectFile, tanstackStartConnectPath, tanstackStartManual, TANSTACK_START_ROOT_PATH, } from '../patch/tanstack-start.js';
|
|
16
16
|
import { patchNuxtConfig } from '../patch/nuxt-patch.js';
|
|
17
17
|
import { alreadyOrMovedPort } from './port-steps.js';
|
|
18
18
|
import { capabilitiesTodo, needsManualStore } from './plan-vite.js';
|
|
19
|
-
import { HTML_INDEX_PATH,
|
|
19
|
+
import { HTML_INDEX_PATH, STATIC_GITIGNORE_PATH, STATIC_SNIPPET_MARKER, STATIC_TOKEN_MODULE, staticTokenFiles, withStaticSnippet, } from '../patch/static-page.js';
|
|
20
|
+
/*
|
|
21
|
+
* Threat model for the static page's token module. `reticle.local.js` sits beside `index.html`, so
|
|
22
|
+
* whatever serves the page serves it too, to any client that can reach that server. That is the same
|
|
23
|
+
* exposure every other dev path already has: the Vite plugin bakes the token into the connect module
|
|
24
|
+
* it serves, and the no-package.json path writes this very file. The dev server's bind address is
|
|
25
|
+
* the boundary; the module is gitignored so it never reaches a commit or a published build.
|
|
26
|
+
*/
|
|
27
|
+
const STATIC_TOKEN_DETAIL = `write this machine’s pairing token to ${STATIC_TOKEN_MODULE} beside the page`;
|
|
28
|
+
const STATIC_IGNORE_DETAIL = `add ${STATIC_TOKEN_MODULE} to ${STATIC_GITIGNORE_PATH} — it holds this machine’s pairing token`;
|
|
29
|
+
const STATIC_CONNECT_DETAIL = 'write the dev-only connect snippet (loopback hosts only)';
|
|
30
|
+
const STATIC_CONNECT_ALREADY_DETAIL = `${HTML_INDEX_PATH} already loads the Reticle SDK`;
|
|
20
31
|
/**
|
|
21
32
|
* Turn a conservative source patch into a step: applied when it patched, already when the wiring is
|
|
22
33
|
* there, and the hand-edit instructions when the file shape wasn't one we recognise.
|
|
@@ -106,8 +117,14 @@ export function nextSteps(input) {
|
|
|
106
117
|
// Pages Router mounts through pages/_app, App Router through the root layout — different edits,
|
|
107
118
|
// and picking by path is what stops a Pages app being handed the layout patch that cannot apply.
|
|
108
119
|
const isPagesRouter = layout !== null && /(^|\/)pages\/_app\.[jt]sx?$/.test(layout.path);
|
|
120
|
+
const candidates = input.nextLayoutCandidates ?? [];
|
|
109
121
|
const layoutPatch = null === layout
|
|
110
|
-
? {
|
|
122
|
+
? {
|
|
123
|
+
kind: PatchKind.MANUAL,
|
|
124
|
+
reason: 0 === candidates.length
|
|
125
|
+
? 'no root layout (app/layout.tsx) or pages/_app found'
|
|
126
|
+
: `no app/layout.tsx; route-group root layouts ${candidates.join(', ')}, and none of them alone renders /`,
|
|
127
|
+
}
|
|
111
128
|
: isPagesRouter
|
|
112
129
|
? patchPagesApp(layout.source, input.nextReticleDevImport)
|
|
113
130
|
: patchRootLayout(layout.source);
|
|
@@ -513,24 +530,41 @@ export function astroSteps(input) {
|
|
|
513
530
|
];
|
|
514
531
|
}
|
|
515
532
|
/**
|
|
516
|
-
* The plain-HTML path:
|
|
533
|
+
* The plain-HTML path: write the static connect when the page exists, otherwise print the recipe.
|
|
517
534
|
*
|
|
518
535
|
* A function of its own, and NOT a fallthrough. Every framework is sent here on purpose or not at
|
|
519
536
|
* all — a member of `Framework` with no adapter entry is a compile error in `FRAMEWORK_ADAPTERS`,
|
|
520
537
|
* where the if/else chain this replaced used to hand it these instructions silently.
|
|
521
538
|
*/
|
|
522
539
|
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
540
|
const index = input.htmlIndexSource ?? null;
|
|
526
|
-
if (null !== index
|
|
541
|
+
if (null !== index) {
|
|
542
|
+
// The page is publishable; the pairing token belongs in its gitignored sibling module instead.
|
|
543
|
+
// Refresh that module even when the connect block itself is already in place.
|
|
544
|
+
const steps = Object.entries(staticTokenFiles(input.pairingToken, (path) => input.htmlLocalSources?.[path] ?? null)).map(([path, content]) => ({
|
|
545
|
+
title: StepTitle.STATIC_PAIRING_TOKEN,
|
|
546
|
+
target: path,
|
|
547
|
+
status: StepStatus.APPLY,
|
|
548
|
+
detail: STATIC_GITIGNORE_PATH === path ? STATIC_IGNORE_DETAIL : STATIC_TOKEN_DETAIL,
|
|
549
|
+
write: { path, content },
|
|
550
|
+
}));
|
|
551
|
+
const written = withStaticSnippet(index, connectArg(input.options.port, input.options.projectId));
|
|
527
552
|
return [
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
553
|
+
...steps,
|
|
554
|
+
null !== written
|
|
555
|
+
? {
|
|
556
|
+
title: StepTitle.CONNECT_SNIPPET,
|
|
557
|
+
target: HTML_INDEX_PATH,
|
|
558
|
+
status: StepStatus.APPLY,
|
|
559
|
+
detail: STATIC_CONNECT_DETAIL,
|
|
560
|
+
write: { path: HTML_INDEX_PATH, content: written, expect: [STATIC_SNIPPET_MARKER] },
|
|
561
|
+
}
|
|
562
|
+
: alreadyOrMovedPort({
|
|
563
|
+
title: StepTitle.CONNECT_SNIPPET,
|
|
564
|
+
target: HTML_INDEX_PATH,
|
|
565
|
+
status: StepStatus.ALREADY,
|
|
566
|
+
detail: STATIC_CONNECT_ALREADY_DETAIL,
|
|
567
|
+
}, index, input.options.port),
|
|
534
568
|
];
|
|
535
569
|
}
|
|
536
570
|
return [
|
|
@@ -96,6 +96,11 @@ export interface Plan {
|
|
|
96
96
|
}
|
|
97
97
|
export interface PlanInput {
|
|
98
98
|
detection: Detection;
|
|
99
|
+
/**
|
|
100
|
+
* `node_modules` is a link to another tree (a workspace's, a shared cache). A package manager run
|
|
101
|
+
* here replaces the link with a fresh tree, so the install is skipped and named instead.
|
|
102
|
+
*/
|
|
103
|
+
nodeModulesLinked?: boolean | undefined;
|
|
99
104
|
/**
|
|
100
105
|
* Write `captureNetworkBodies: true` into the app's config. Off unless the caller asked (#705).
|
|
101
106
|
*
|
|
@@ -145,11 +150,18 @@ export interface PlanInput {
|
|
|
145
150
|
configPath: string;
|
|
146
151
|
existing: string | null;
|
|
147
152
|
}[] | undefined;
|
|
153
|
+
/**
|
|
154
|
+
* Whether the `codex` CLI is on PATH. Only probed when Codex was detected, and absent otherwise.
|
|
155
|
+
* With it, Codex registers through `codex mcp add` instead of a block to paste.
|
|
156
|
+
*/
|
|
157
|
+
codexCli?: boolean | undefined;
|
|
148
158
|
/** Discovered Vite config: its path + source, or null if none found. */
|
|
149
159
|
viteConfig: {
|
|
150
160
|
path: string;
|
|
151
161
|
source: string;
|
|
152
162
|
} | null;
|
|
163
|
+
/** Config paths found but not safely attributable to the selected dev command. */
|
|
164
|
+
viteConfigCandidates?: readonly string[] | undefined;
|
|
153
165
|
/** Discovered electron-vite config: its path + source, or null if none found. */
|
|
154
166
|
electronViteConfig?: {
|
|
155
167
|
path: string;
|
|
@@ -200,6 +212,11 @@ export interface PlanInput {
|
|
|
200
212
|
path: string;
|
|
201
213
|
source: string;
|
|
202
214
|
} | null | undefined;
|
|
215
|
+
/**
|
|
216
|
+
* Route-group root layouts `init` could not choose between (`app/(a)/layout.tsx`, ...), set only
|
|
217
|
+
* when there is no top-level layout and no single group renders `/` (#1538).
|
|
218
|
+
*/
|
|
219
|
+
nextLayoutCandidates?: readonly string[] | undefined;
|
|
203
220
|
/** Where the dev-only connect component goes — never inside `pages/`, which routes on presence. */
|
|
204
221
|
nextReticleDevPath?: string | undefined;
|
|
205
222
|
/** What the mount file should import — a sibling for App Router, `../components/…` for Pages. */
|
|
@@ -277,10 +294,12 @@ export interface PlanInput {
|
|
|
277
294
|
/** The generated `reticle.proxy.mjs`, when a previous run left one (idempotency). */
|
|
278
295
|
angularProxySource?: string | null | undefined;
|
|
279
296
|
/**
|
|
280
|
-
* The plain-HTML app's `index.html`, when it has one —
|
|
281
|
-
*
|
|
297
|
+
* The plain-HTML app's `index.html`, when it has one — patched on the first run, recognised on
|
|
298
|
+
* later runs, and left manual only when there is no page to write into.
|
|
282
299
|
*/
|
|
283
300
|
htmlIndexSource?: string | null | undefined;
|
|
301
|
+
/** Current token module and .gitignore beside the HTML page, for idempotent token-file writes. */
|
|
302
|
+
htmlLocalSources?: Record<string, string | null> | undefined;
|
|
284
303
|
/** CRA's bundled entry (src/index.tsx or .js) — where the connect import has to go. */
|
|
285
304
|
craEntry?: {
|
|
286
305
|
path: string;
|
|
@@ -342,6 +361,8 @@ export interface PlanInput {
|
|
|
342
361
|
* that disagrees with the app's is worse than no root config, and only reading it can tell.
|
|
343
362
|
*/
|
|
344
363
|
agentRootConfigSource?: string | null | undefined;
|
|
364
|
+
/** The agent root holds several apps, so its config would name one and mislead about the rest. */
|
|
365
|
+
agentRootSeveralApps?: boolean | undefined;
|
|
345
366
|
options: {
|
|
346
367
|
port: number | undefined;
|
|
347
368
|
mcp: boolean;
|
package/dist/plan/plan-vite.js
CHANGED
|
@@ -194,6 +194,21 @@ function viteConfigSteps(input, detail, inject = true) {
|
|
|
194
194
|
// plugin, and a file carrying ours alone would not boot the app.
|
|
195
195
|
const plainVite = Framework.VITE === input.detection.framework;
|
|
196
196
|
if (null === cfg) {
|
|
197
|
+
const candidates = input.viteConfigCandidates ?? [];
|
|
198
|
+
if (candidates.length > 0) {
|
|
199
|
+
const named = candidates.map((path) => `\`${path}\``).join(', ');
|
|
200
|
+
return [
|
|
201
|
+
{
|
|
202
|
+
title: StepTitle.VITE_PLUGIN,
|
|
203
|
+
target: candidates[0] ?? 'vite.config',
|
|
204
|
+
status: StepStatus.MANUAL,
|
|
205
|
+
detail: `Reticle could not safely patch the Vite config used by the dev server. ` +
|
|
206
|
+
`Candidate config(s): ${named}. ` +
|
|
207
|
+
`Add the Reticle plugin to the config passed to createServer().\n\n` +
|
|
208
|
+
viteManual(port, input.detection.uiLibrary, inject, stampSource),
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
}
|
|
197
212
|
if (!plainVite) {
|
|
198
213
|
return [
|
|
199
214
|
{
|