@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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The connect snippet, written INTO a static page rather than printed beside it.
|
|
3
|
+
*
|
|
4
|
+
* A page with no package.json has no build, so the pasted `<script type="module">` is the whole
|
|
5
|
+
* install — proven end to end on a page served by `python3 -m http.server`. `init` used to print it
|
|
6
|
+
* and exit 1 with `no_package_json`, which made the one path that works read as a failure, and left
|
|
7
|
+
* the step to a human who then had to be told where to put it.
|
|
8
|
+
*
|
|
9
|
+
* Two things differ from the printed recipe, both because the file is the deployable artifact:
|
|
10
|
+
*
|
|
11
|
+
* - It runs only on a loopback host. A static file has no development build to strip it, so the
|
|
12
|
+
* host is the only line between this snippet and a published page; guarded, a published copy
|
|
13
|
+
* does not even fetch the SDK. The printed bundled-app recipe deliberately does NOT do this
|
|
14
|
+
* (a hosts-file dev alias defeats it, see html-no-build.test.ts) — that recipe has NODE_ENV.
|
|
15
|
+
* - It is marked, so a re-run recognises its own block and never writes a second one.
|
|
16
|
+
* - It carries no pairing token. The token used to be a literal in the page, so committing or
|
|
17
|
+
* publishing index.html published it; it now sits in a module beside the page that `init`
|
|
18
|
+
* gitignores, which the block imports and any static server serves. Absent — a teammate's
|
|
19
|
+
* checkout, a published copy — the connect goes without one, and the bridge says why it refused.
|
|
20
|
+
*/
|
|
21
|
+
/** The page a static site serves at its root, and the one file `init` will write the snippet into. */
|
|
22
|
+
export declare const HTML_INDEX_PATH = "index.html";
|
|
23
|
+
/** The gitignored module beside the page that holds this machine's pairing token. */
|
|
24
|
+
export declare const STATIC_TOKEN_MODULE = "reticle.local.js";
|
|
25
|
+
/** Opens the block `init` writes; a re-run looks for it. */
|
|
26
|
+
export declare const STATIC_SNIPPET_MARKER = "<!-- reticle:connect (dev only, written by reticle init) -->";
|
|
27
|
+
/** Whether this page already carries a Reticle connect. */
|
|
28
|
+
export declare function hasStaticSnippet(html: string): boolean;
|
|
29
|
+
/** The token module's content. Rewritten on every run, so a re-minted token is picked up. */
|
|
30
|
+
export declare function staticTokenModule(pairingToken: string): string;
|
|
31
|
+
/** The files to write so the token module exists and git ignores it; only what actually changes. */
|
|
32
|
+
export declare function staticTokenFiles(pairingToken: string | undefined, readFile: (path: string) => string | null): Record<string, string>;
|
|
33
|
+
/**
|
|
34
|
+
* The page with the snippet added, or null when it already connects.
|
|
35
|
+
*
|
|
36
|
+
* Before `</body>` so the SDK starts on a parsed document; before `</head>` for a page with no body
|
|
37
|
+
* tag, and at the end for a fragment with neither.
|
|
38
|
+
*/
|
|
39
|
+
export declare function withStaticSnippet(html: string, connectArgLiteral: string): string | null;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The connect snippet, written INTO a static page rather than printed beside it.
|
|
3
|
+
*
|
|
4
|
+
* A page with no package.json has no build, so the pasted `<script type="module">` is the whole
|
|
5
|
+
* install — proven end to end on a page served by `python3 -m http.server`. `init` used to print it
|
|
6
|
+
* and exit 1 with `no_package_json`, which made the one path that works read as a failure, and left
|
|
7
|
+
* the step to a human who then had to be told where to put it.
|
|
8
|
+
*
|
|
9
|
+
* Two things differ from the printed recipe, both because the file is the deployable artifact:
|
|
10
|
+
*
|
|
11
|
+
* - It runs only on a loopback host. A static file has no development build to strip it, so the
|
|
12
|
+
* host is the only line between this snippet and a published page; guarded, a published copy
|
|
13
|
+
* does not even fetch the SDK. The printed bundled-app recipe deliberately does NOT do this
|
|
14
|
+
* (a hosts-file dev alias defeats it, see html-no-build.test.ts) — that recipe has NODE_ENV.
|
|
15
|
+
* - It is marked, so a re-run recognises its own block and never writes a second one.
|
|
16
|
+
* - It carries no pairing token. The token used to be a literal in the page, so committing or
|
|
17
|
+
* publishing index.html published it; it now sits in a module beside the page that `init`
|
|
18
|
+
* gitignores, which the block imports and any static server serves. Absent — a teammate's
|
|
19
|
+
* checkout, a published copy — the connect goes without one, and the bridge says why it refused.
|
|
20
|
+
*/
|
|
21
|
+
import { CDN_SDK_URL } from './snippets.js';
|
|
22
|
+
/** The page a static site serves at its root, and the one file `init` will write the snippet into. */
|
|
23
|
+
export const HTML_INDEX_PATH = 'index.html';
|
|
24
|
+
/** The gitignored module beside the page that holds this machine's pairing token. */
|
|
25
|
+
export const STATIC_TOKEN_MODULE = 'reticle.local.js';
|
|
26
|
+
const GITIGNORE_PATH = '.gitignore';
|
|
27
|
+
/** Opens the block `init` writes; a re-run looks for it. */
|
|
28
|
+
export const STATIC_SNIPPET_MARKER = '<!-- reticle:connect (dev only, written by reticle init) -->';
|
|
29
|
+
const STATIC_SNIPPET_END = '<!-- /reticle:connect -->';
|
|
30
|
+
/** Hostnames a developer's own machine serves a page on. */
|
|
31
|
+
const LOOPBACK_HOSTS = ['localhost', '127.0.0.1', '[::1]'];
|
|
32
|
+
/**
|
|
33
|
+
* Signs that the page already connects, whoever put the snippet there — the printed recipe pasted
|
|
34
|
+
* by hand counts as much as our own block.
|
|
35
|
+
*/
|
|
36
|
+
const ALREADY_CONNECTS = [STATIC_SNIPPET_MARKER, '@reticlehq/browser'];
|
|
37
|
+
const BODY_CLOSE = /<\/body>/i;
|
|
38
|
+
const HEAD_CLOSE = /<\/head>/i;
|
|
39
|
+
/** Whether this page already carries a Reticle connect. */
|
|
40
|
+
export function hasStaticSnippet(html) {
|
|
41
|
+
return ALREADY_CONNECTS.some((sign) => html.includes(sign));
|
|
42
|
+
}
|
|
43
|
+
function block(connectArgLiteral) {
|
|
44
|
+
const hosts = LOOPBACK_HOSTS.map((h) => `'${h}'`).join(', ');
|
|
45
|
+
const arg = '' === connectArgLiteral
|
|
46
|
+
? '{ token: local.token }'
|
|
47
|
+
: `{ ...${connectArgLiteral}, token: local.token }`;
|
|
48
|
+
return ` ${STATIC_SNIPPET_MARKER}
|
|
49
|
+
<script type="module">
|
|
50
|
+
if ([${hosts}].includes(location.hostname)) {
|
|
51
|
+
Promise.all([
|
|
52
|
+
import('${CDN_SDK_URL}'),
|
|
53
|
+
import('./${STATIC_TOKEN_MODULE}').catch(() => ({})),
|
|
54
|
+
]).then(([{ reticle }, local]) => reticle.connect(${arg}));
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
${STATIC_SNIPPET_END}
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
/** The token module's content. Rewritten on every run, so a re-minted token is picked up. */
|
|
61
|
+
export function staticTokenModule(pairingToken) {
|
|
62
|
+
return (`// This machine's Reticle pairing token, written by \`reticle init\`. Gitignored: never commit\n` +
|
|
63
|
+
`// or publish it — each machine runs \`reticle init\` for its own.\n` +
|
|
64
|
+
`export const token = '${pairingToken}';\n`);
|
|
65
|
+
}
|
|
66
|
+
/** The files to write so the token module exists and git ignores it; only what actually changes. */
|
|
67
|
+
export function staticTokenFiles(pairingToken, readFile) {
|
|
68
|
+
if (pairingToken === undefined || 0 === pairingToken.length)
|
|
69
|
+
return {};
|
|
70
|
+
const files = {};
|
|
71
|
+
const module = staticTokenModule(pairingToken);
|
|
72
|
+
if (readFile(STATIC_TOKEN_MODULE) !== module)
|
|
73
|
+
files[STATIC_TOKEN_MODULE] = module;
|
|
74
|
+
const ignore = readFile(GITIGNORE_PATH) ?? '';
|
|
75
|
+
const covered = ignore
|
|
76
|
+
.split('\n')
|
|
77
|
+
.some((l) => [STATIC_TOKEN_MODULE, `/${STATIC_TOKEN_MODULE}`].includes(l.trim()));
|
|
78
|
+
if (!covered) {
|
|
79
|
+
files[GITIGNORE_PATH] =
|
|
80
|
+
`${'' === ignore ? '' : ignore.replace(/\n*$/, '\n')}${STATIC_TOKEN_MODULE}\n`;
|
|
81
|
+
}
|
|
82
|
+
return files;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The page with the snippet added, or null when it already connects.
|
|
86
|
+
*
|
|
87
|
+
* Before `</body>` so the SDK starts on a parsed document; before `</head>` for a page with no body
|
|
88
|
+
* tag, and at the end for a fragment with neither.
|
|
89
|
+
*/
|
|
90
|
+
export function withStaticSnippet(html, connectArgLiteral) {
|
|
91
|
+
if (hasStaticSnippet(html))
|
|
92
|
+
return null;
|
|
93
|
+
const snippet = block(connectArgLiteral);
|
|
94
|
+
const anchor = BODY_CLOSE.exec(html) ?? HEAD_CLOSE.exec(html);
|
|
95
|
+
if (null === anchor)
|
|
96
|
+
return `${html.trimEnd()}\n${snippet}`;
|
|
97
|
+
// At the start of the closing tag's own line when it sits alone on one, so its indentation stays
|
|
98
|
+
// with it rather than moving onto the first line of the snippet.
|
|
99
|
+
const lineStart = html.lastIndexOf('\n', anchor.index - 1) + 1;
|
|
100
|
+
const at = '' === html.slice(lineStart, anchor.index).trim() ? lineStart : anchor.index;
|
|
101
|
+
return `${html.slice(0, at)}${snippet}${html.slice(at)}`;
|
|
102
|
+
}
|
|
@@ -1,17 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The TanStack Start connect
|
|
2
|
+
* The TanStack Start connect: a client-only component, rendered in the document the root route SSRs.
|
|
3
3
|
*
|
|
4
|
-
* Its own module because Start is the one framework whose document module
|
|
5
|
-
*
|
|
4
|
+
* Its own module because Start is the one framework whose document module is ALSO the server
|
|
5
|
+
* render, so what may be written into it — and what must stay out — is a unit.
|
|
6
6
|
*/
|
|
7
|
+
import { type SourcePatch } from './patch-kind.js';
|
|
7
8
|
/** TanStack Start's document module — the file that SSRs `<html>`. */
|
|
8
9
|
export declare const TANSTACK_START_ROOT_PATH = "src/routes/__root.tsx";
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* Where the connect component goes: beside the `routes/` directory, never inside it — a file under
|
|
12
|
+
* `routes/` is a route to Start's file-based router.
|
|
13
|
+
*/
|
|
14
|
+
export declare function tanstackStartConnectPath(rootPath?: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* The connect component.
|
|
17
|
+
*
|
|
18
|
+
* A dynamic SDK import inside `useEffect`, because a static import on anything the root document
|
|
19
|
+
* imports is evaluated during the server render and 500s the page. Effects never run on the server.
|
|
20
|
+
* The pairing token is the one the Vite plugin inlines as `__RETICLE_TOKEN__`, declared here so the
|
|
21
|
+
* file passes the scaffold's strict `tsc` — the recipe this replaced used the name undeclared and
|
|
22
|
+
* failed with `TS2304: Cannot find name '__RETICLE_TOKEN__'`.
|
|
23
|
+
*
|
|
24
|
+
* It also loads the app's `src/reticle-dev.ts` after connecting. Start is written `inject: false`,
|
|
25
|
+
* and the plugin's injected connect is the only other thing that imports that file, so without this
|
|
26
|
+
* line every store and capability registered there silently never ran.
|
|
27
|
+
*/
|
|
28
|
+
export declare function tanstackStartConnectFile(port: number | undefined, projectId?: string, rootPath?: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Render the component in the root document, just before `<Scripts />`.
|
|
31
|
+
*
|
|
32
|
+
* `<Scripts />` is the anchor because every Start root has to render it — it is how the client
|
|
33
|
+
* bundle reaches the page — whatever the component around it is called (`RootDocument` as the
|
|
34
|
+
* route's `shellComponent` in the current template; `RootComponent` in older ones). A root without
|
|
35
|
+
* one is a shape we do not recognise, and a half-edited document is worse than a documented step.
|
|
36
|
+
*/
|
|
37
|
+
export declare function patchTanstackStartRoot(source: string, rootPath: string): SourcePatch;
|
|
38
|
+
/**
|
|
39
|
+
* The recipe, for a root `patchTanstackStartRoot` would not edit.
|
|
11
40
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* makes about `app/entry.client.tsx`: a half-written document is worse than a documented manual step.
|
|
41
|
+
* The same two edits it makes, stated as edits: create the component, render it before
|
|
42
|
+
* `<Scripts />`. It used to say "add this useEffect to the App component", naming a component the
|
|
43
|
+
* current template does not have, and its snippet used `__RETICLE_TOKEN__` undeclared.
|
|
16
44
|
*/
|
|
17
45
|
export declare function tanstackStartManual(port: number | undefined, projectId?: string, rootPath?: string): string;
|
|
@@ -1,52 +1,141 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The TanStack Start connect
|
|
2
|
+
* The TanStack Start connect: a client-only component, rendered in the document the root route SSRs.
|
|
3
3
|
*
|
|
4
|
-
* Its own module because Start is the one framework whose document module
|
|
5
|
-
*
|
|
4
|
+
* Its own module because Start is the one framework whose document module is ALSO the server
|
|
5
|
+
* render, so what may be written into it — and what must stay out — is a unit.
|
|
6
6
|
*/
|
|
7
|
+
import { posix } from 'node:path';
|
|
7
8
|
import { UiLibrary } from '../detect/detect.js';
|
|
8
|
-
import { connectArg, sdkImport, UNVERIFIED_TANSTACK_START_NOTE } from './snippets.js';
|
|
9
|
+
import { connectArg, devModuleSpecifier, sdkImport, UNVERIFIED_TANSTACK_START_NOTE, } from './snippets.js';
|
|
10
|
+
import { PatchKind } from './patch-kind.js';
|
|
11
|
+
import { VITE_ENV_DEV_DECLARATION } from './vite-env-types.js';
|
|
9
12
|
/** TanStack Start's document module — the file that SSRs `<html>`. */
|
|
10
13
|
export const TANSTACK_START_ROOT_PATH = 'src/routes/__root.tsx';
|
|
14
|
+
/** The component `init` writes, and renders in the root document. */
|
|
15
|
+
const CONNECT_COMPONENT = 'ReticleConnect';
|
|
16
|
+
const CONNECT_FILE_BASENAME = 'reticle-connect';
|
|
17
|
+
/** `<Scripts />` — Start's own client-script outlet, which every root document has to render. */
|
|
18
|
+
const SCRIPTS_OUTLET = /^([ \t]*)<Scripts\s*\/>/m;
|
|
11
19
|
/**
|
|
12
|
-
*
|
|
20
|
+
* Where the connect component goes: beside the `routes/` directory, never inside it — a file under
|
|
21
|
+
* `routes/` is a route to Start's file-based router.
|
|
22
|
+
*/
|
|
23
|
+
export function tanstackStartConnectPath(rootPath = TANSTACK_START_ROOT_PATH) {
|
|
24
|
+
return posix.join(posix.dirname(posix.dirname(rootPath)), `${CONNECT_FILE_BASENAME}.tsx`);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The connect component.
|
|
28
|
+
*
|
|
29
|
+
* A dynamic SDK import inside `useEffect`, because a static import on anything the root document
|
|
30
|
+
* imports is evaluated during the server render and 500s the page. Effects never run on the server.
|
|
31
|
+
* The pairing token is the one the Vite plugin inlines as `__RETICLE_TOKEN__`, declared here so the
|
|
32
|
+
* file passes the scaffold's strict `tsc` — the recipe this replaced used the name undeclared and
|
|
33
|
+
* failed with `TS2304: Cannot find name '__RETICLE_TOKEN__'`.
|
|
13
34
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* makes about `app/entry.client.tsx`: a half-written document is worse than a documented manual step.
|
|
35
|
+
* It also loads the app's `src/reticle-dev.ts` after connecting. Start is written `inject: false`,
|
|
36
|
+
* and the plugin's injected connect is the only other thing that imports that file, so without this
|
|
37
|
+
* line every store and capability registered there silently never ran.
|
|
18
38
|
*/
|
|
19
|
-
export function
|
|
39
|
+
export function tanstackStartConnectFile(port, projectId, rootPath = TANSTACK_START_ROOT_PATH) {
|
|
20
40
|
const sdk = sdkImport(UiLibrary.REACT);
|
|
21
41
|
const base = connectArg(port, projectId);
|
|
22
42
|
const fields = '' === base ? '' : `${base.slice(1, -1).trim()}, `;
|
|
23
|
-
const installLine = sdk.usesInstall
|
|
24
|
-
? ' install();'
|
|
25
|
-
: ' // No React adapter here: the sensor has no install() to call.';
|
|
26
43
|
const imports = sdk.usesInstall ? 'reticle, install' : 'reticle';
|
|
44
|
+
const installLine = sdk.usesInstall
|
|
45
|
+
? ' install();'
|
|
46
|
+
: ' // No React adapter here: the sensor has no install() to call.';
|
|
47
|
+
const devModule = devModuleSpecifier(tanstackStartConnectPath(rootPath));
|
|
48
|
+
return `import { useEffect } from 'react';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Dev-only: connect Reticle from the client. Start SSRs its own <html> and never sends Vite's
|
|
52
|
+
* index.html, so the plugin's injection cannot do it. Rendered once in the root document.
|
|
53
|
+
*/
|
|
54
|
+
export function ${CONNECT_COMPONENT}() {
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!import.meta.env.DEV) return;
|
|
57
|
+
// Inlined by @reticlehq/vite-plugin. The bridge refuses a connect without it, even on localhost.
|
|
58
|
+
const token = typeof __RETICLE_TOKEN__ !== 'undefined' ? __RETICLE_TOKEN__ : '';
|
|
59
|
+
const root = typeof __RETICLE_ROOT__ !== 'undefined' ? __RETICLE_ROOT__ : '';
|
|
60
|
+
void import('${sdk.specifier}').then(({ ${imports} }) => {
|
|
61
|
+
${installLine}
|
|
62
|
+
reticle.connect({
|
|
63
|
+
${fields}...(token.length > 0 ? { token } : {}),
|
|
64
|
+
...(root.length > 0 ? { root } : {}),
|
|
65
|
+
});
|
|
66
|
+
// Your stores and capabilities live here; nothing else loads this file under Start.
|
|
67
|
+
void import('${devModule}');
|
|
68
|
+
});
|
|
69
|
+
}, []);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare const __RETICLE_TOKEN__: string | undefined;
|
|
74
|
+
declare const __RETICLE_ROOT__: string | undefined;
|
|
75
|
+
|
|
76
|
+
${VITE_ENV_DEV_DECLARATION}`;
|
|
77
|
+
}
|
|
78
|
+
/** The import the root document gets, relative to where it lives. */
|
|
79
|
+
function connectImport(rootPath) {
|
|
80
|
+
const target = tanstackStartConnectPath(rootPath).replace(/\.tsx$/, '');
|
|
81
|
+
const rel = posix.relative(posix.dirname(rootPath), target);
|
|
82
|
+
return `import { ${CONNECT_COMPONENT} } from '${rel.startsWith('.') ? rel : `./${rel}`}';`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Render the component in the root document, just before `<Scripts />`.
|
|
86
|
+
*
|
|
87
|
+
* `<Scripts />` is the anchor because every Start root has to render it — it is how the client
|
|
88
|
+
* bundle reaches the page — whatever the component around it is called (`RootDocument` as the
|
|
89
|
+
* route's `shellComponent` in the current template; `RootComponent` in older ones). A root without
|
|
90
|
+
* one is a shape we do not recognise, and a half-edited document is worse than a documented step.
|
|
91
|
+
*/
|
|
92
|
+
export function patchTanstackStartRoot(source, rootPath) {
|
|
93
|
+
if (source.includes(CONNECT_COMPONENT))
|
|
94
|
+
return { kind: PatchKind.ALREADY };
|
|
95
|
+
if (!rootPath.endsWith('.tsx')) {
|
|
96
|
+
return { kind: PatchKind.MANUAL, reason: `${rootPath} is not TypeScript` };
|
|
97
|
+
}
|
|
98
|
+
const outlet = SCRIPTS_OUTLET.exec(source);
|
|
99
|
+
if (null === outlet) {
|
|
100
|
+
return { kind: PatchKind.MANUAL, reason: `no <Scripts /> found in ${rootPath}` };
|
|
101
|
+
}
|
|
102
|
+
const indent = outlet[1] ?? '';
|
|
103
|
+
const rendered = source.replace(SCRIPTS_OUTLET, `${indent}<${CONNECT_COMPONENT} />\n$&`);
|
|
104
|
+
const lines = rendered.split('\n');
|
|
105
|
+
let lastImport = -1;
|
|
106
|
+
for (const [index, line] of lines.entries()) {
|
|
107
|
+
if (/^import\s/.test(line))
|
|
108
|
+
lastImport = index;
|
|
109
|
+
}
|
|
110
|
+
lines.splice(lastImport + 1, 0, connectImport(rootPath));
|
|
111
|
+
return { kind: PatchKind.APPLY, code: lines.join('\n') };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The recipe, for a root `patchTanstackStartRoot` would not edit.
|
|
115
|
+
*
|
|
116
|
+
* The same two edits it makes, stated as edits: create the component, render it before
|
|
117
|
+
* `<Scripts />`. It used to say "add this useEffect to the App component", naming a component the
|
|
118
|
+
* current template does not have, and its snippet used `__RETICLE_TOKEN__` undeclared.
|
|
119
|
+
*/
|
|
120
|
+
export function tanstackStartManual(port, projectId, rootPath = TANSTACK_START_ROOT_PATH) {
|
|
121
|
+
const component = tanstackStartConnectFile(port, projectId, rootPath)
|
|
122
|
+
.split('\n')
|
|
123
|
+
.map((line) => (0 === line.length ? line : ` ${line}`))
|
|
124
|
+
.join('\n');
|
|
27
125
|
return `TanStack Start SSRs <html> from ${rootPath} (HeadContent / Scripts) and never sends Vite's
|
|
28
126
|
index.html, so the plugin's connect injection never fires. Keep the plugin anyway, with
|
|
29
127
|
reticle({ inject: false }): only the injection half is inapplicable, and the stamping half is
|
|
30
128
|
what puts data-reticle-source on the JSX. Dropping the plugin because one of its two jobs did
|
|
31
129
|
not apply is how Remix lost file:line.
|
|
32
130
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Add this useEffect to the App component (import useEffect from react if it is not already there):
|
|
131
|
+
1. Create ${tanstackStartConnectPath(rootPath)}. It connects from a CLIENT-ONLY effect: a static SDK
|
|
132
|
+
import on anything the root document imports SSRs and 500s.
|
|
133
|
+
Do not guard on window.location.hostname === 'localhost' — import.meta.env.DEV is the correct
|
|
134
|
+
guard, and it does not care what host you develop on.
|
|
38
135
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
void import('${sdk.specifier}').then(({ ${imports} }) => {
|
|
43
|
-
${installLine}
|
|
44
|
-
reticle.connect({
|
|
45
|
-
${fields}...(token.length > 0 ? { token } : {}),
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
}, []);
|
|
136
|
+
${component}
|
|
137
|
+
2. In ${rootPath}, add \`${connectImport(rootPath)}\` and render <${CONNECT_COMPONENT} /> in the
|
|
138
|
+
component that renders <html> (the route's shellComponent), just before <Scripts />.
|
|
50
139
|
|
|
51
140
|
The plugin inlines __RETICLE_TOKEN__ when Vite resolves its config. Start the daemon BEFORE the
|
|
52
141
|
dev server so that file exists; a server that started first froze an empty token and the page
|
|
@@ -14,5 +14,10 @@ export declare const VitePatchKind: {
|
|
|
14
14
|
};
|
|
15
15
|
export type VitePatchKind = PatchKind;
|
|
16
16
|
type VitePatch = SourcePatch;
|
|
17
|
+
/**
|
|
18
|
+
* A whole config for a plain Vite app that has none: Vite's defaults plus the plugin. Written out
|
|
19
|
+
* rather than patched from an empty one, because it is a file the person will open and read.
|
|
20
|
+
*/
|
|
21
|
+
export declare function newViteConfig(port?: number, captureBodies?: boolean, inject?: boolean, sourceMapping?: boolean): string;
|
|
17
22
|
export declare function patchViteConfig(source: string, port?: number, captureBodies?: boolean, inject?: boolean, sourceMapping?: boolean): VitePatch;
|
|
18
23
|
export {};
|
|
@@ -98,6 +98,14 @@ function insertPluginsKey(source, call) {
|
|
|
98
98
|
return multiline ? `${prefix}{\n${indent}${key}` : `${prefix}{ ${key}`;
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* A whole config for a plain Vite app that has none: Vite's defaults plus the plugin. Written out
|
|
103
|
+
* rather than patched from an empty one, because it is a file the person will open and read.
|
|
104
|
+
*/
|
|
105
|
+
export function newViteConfig(port, captureBodies = false, inject = true, sourceMapping = true) {
|
|
106
|
+
const call = reticlePluginCall({ port, captureBodies, inject, sourceMapping });
|
|
107
|
+
return `import { defineConfig } from 'vite';\n${VITE_IMPORT}\n\nexport default defineConfig({\n plugins: [${call}],\n});\n`;
|
|
108
|
+
}
|
|
101
109
|
export function patchViteConfig(source, port, captureBodies = false, inject = true, sourceMapping = true) {
|
|
102
110
|
if (source.includes(RETICLE_MARKER)) {
|
|
103
111
|
return { kind: VitePatchKind.ALREADY };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generated dev module must compile even when the host excludes `vite/client` ambient types.
|
|
3
|
+
* Keep the guard itself as `import.meta.env.DEV` so Vite can remove dev-only code from production.
|
|
4
|
+
*
|
|
5
|
+
* These declarations must merge with Vite's existing interfaces: DEV is mutable there, while env
|
|
6
|
+
* is readonly and refers to the named ImportMetaEnv interface. A triple-slash package reference
|
|
7
|
+
* would also require vite to be directly resolvable, which electron-vite-only projects need not have.
|
|
8
|
+
*/
|
|
9
|
+
export declare const VITE_ENV_DEV_DECLARATION = "// Supply the dev flag type when the host does not include vite/client.\n// These interfaces merge with vite/client when it is already present.\ndeclare global {\n interface ImportMetaEnv {\n DEV: boolean;\n }\n interface ImportMeta {\n readonly env: ImportMetaEnv;\n }\n}\n";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generated dev module must compile even when the host excludes `vite/client` ambient types.
|
|
3
|
+
* Keep the guard itself as `import.meta.env.DEV` so Vite can remove dev-only code from production.
|
|
4
|
+
*
|
|
5
|
+
* These declarations must merge with Vite's existing interfaces: DEV is mutable there, while env
|
|
6
|
+
* is readonly and refers to the named ImportMetaEnv interface. A triple-slash package reference
|
|
7
|
+
* would also require vite to be directly resolvable, which electron-vite-only projects need not have.
|
|
8
|
+
*/
|
|
9
|
+
export const VITE_ENV_DEV_DECLARATION = `// Supply the dev flag type when the host does not include vite/client.
|
|
10
|
+
// These interfaces merge with vite/client when it is already present.
|
|
11
|
+
declare global {
|
|
12
|
+
interface ImportMetaEnv {
|
|
13
|
+
DEV: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface ImportMeta {
|
|
16
|
+
readonly env: ImportMetaEnv;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
@@ -21,5 +21,52 @@ export interface ViteOwningConfig {
|
|
|
21
21
|
readonly defineCall: RegExp;
|
|
22
22
|
/** The SDK package this framework's connect imports, declared so Vite pre-bundles it. */
|
|
23
23
|
readonly sdkSpecifier: string;
|
|
24
|
+
/**
|
|
25
|
+
* When true (default), inline the pairing token and project root via `vite.define`.
|
|
26
|
+
*
|
|
27
|
+
* Nuxt still needs this: its client plugin reads `__RETICLE_TOKEN__` from the define. Astro 7.2+
|
|
28
|
+
* no longer substitutes `define` into the client pipeline (#1008), so Astro sets this false and
|
|
29
|
+
* puts the token on a frontmatter `<meta>` instead.
|
|
30
|
+
*/
|
|
31
|
+
readonly defineToken?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Present once this patch has applied. Defaults to `__RETICLE_TOKEN__`, which is what the define
|
|
34
|
+
* path writes. Astro's path never writes that name into the config, so it supplies its own.
|
|
35
|
+
*/
|
|
36
|
+
readonly alreadyMarker?: string;
|
|
37
|
+
/**
|
|
38
|
+
* The installed Vite's major (see `installedViteMajor`), or null when nothing on disk said.
|
|
39
|
+
*
|
|
40
|
+
* Decides whether the dep-optimizer target is written at all. Vite 7 moved the optimizer to
|
|
41
|
+
* rolldown and deprecated `optimizeDeps.esbuildOptions`: on Nuxt 4.5 / Vite 8 the key printed a
|
|
42
|
+
* deprecation warning on every dev start and build. The target exists for esbuild, which cannot
|
|
43
|
+
* lower some of the SDK's syntax to Vite's older default; rolldown has no such gap, so on 7+ there
|
|
44
|
+
* is nothing to write under either key.
|
|
45
|
+
*/
|
|
46
|
+
readonly viteMajor?: number | null | undefined;
|
|
24
47
|
}
|
|
25
48
|
export declare function patchViteOwningConfig(source: string, config: ViteOwningConfig): SourcePatch;
|
|
49
|
+
/**
|
|
50
|
+
* The major version of the Vite this app will actually run, read from disk.
|
|
51
|
+
*
|
|
52
|
+
* Needed by the configs `init` patches for frameworks that own their Vite instance (Nuxt, Astro):
|
|
53
|
+
* Vite 7 moved the dep optimizer to rolldown and deprecated `optimizeDeps.esbuildOptions`, so the
|
|
54
|
+
* key `init` wrote printed a deprecation warning on every dev start and build there. The Vite
|
|
55
|
+
* plugin answers the same question for itself (`optimizerOptionsKey` in @reticlehq/vite-plugin),
|
|
56
|
+
* but these frameworks never load the plugin.
|
|
57
|
+
*
|
|
58
|
+
* Nuxt and Astro do not list `vite` in the app's package.json — it comes with the framework — so the
|
|
59
|
+
* declared range is the last resort, after the hoisted package and pnpm's store. Null when none
|
|
60
|
+
* says: the caller keeps the older key, as the plugin does, because a deprecation notice is a much
|
|
61
|
+
* smaller failure than an option the installed Vite has never heard of.
|
|
62
|
+
*/
|
|
63
|
+
interface ViteReader {
|
|
64
|
+
readFile(relPath: string): string | null;
|
|
65
|
+
listDirs(relPath: string): readonly string[];
|
|
66
|
+
}
|
|
67
|
+
interface DeclaredDeps {
|
|
68
|
+
dependencies?: Record<string, string>;
|
|
69
|
+
devDependencies?: Record<string, string>;
|
|
70
|
+
}
|
|
71
|
+
export declare function installedViteMajor(io: ViteReader, pkg: DeclaredDeps): number | null;
|
|
72
|
+
export {};
|