@reticlehq/init 3.3.0 → 3.5.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 +45 -2
- package/dist/detect/detect.js +76 -5
- 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.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 +13 -1
- package/dist/diagnose/csp-doctor.js +2 -2
- package/dist/diagnose/csp-step.d.ts +27 -0
- package/dist/diagnose/csp-step.js +86 -0
- package/dist/diagnose/desktop-doctor.js +8 -2
- package/dist/diagnose/init-failure.d.ts +7 -0
- package/dist/diagnose/init-failure.js +7 -0
- package/dist/diagnose/install-hint.d.ts +9 -1
- package/dist/diagnose/install-hint.js +17 -7
- package/dist/diagnose/install-retries.d.ts +16 -1
- 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/index.d.ts +6 -2
- package/dist/index.js +6 -2
- package/dist/no-package-json.d.ts +10 -0
- package/dist/no-package-json.js +131 -0
- package/dist/node-io.d.ts +12 -6
- package/dist/node-io.js +33 -11
- package/dist/patch/angular.d.ts +68 -0
- package/dist/patch/angular.js +216 -0
- package/dist/patch/astro-patch.d.ts +1 -1
- package/dist/patch/astro-patch.js +2 -2
- 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.js +6 -6
- package/dist/patch/remix.d.ts +24 -0
- package/dist/patch/remix.js +59 -0
- package/dist/patch/snippets.d.ts +57 -9
- package/dist/patch/snippets.js +119 -28
- 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-owning-config.d.ts +34 -0
- package/dist/patch/vite-owning-config.js +63 -5
- package/dist/plan/connect-steps.d.ts +7 -1
- package/dist/plan/connect-steps.js +18 -1
- package/dist/plan/framework-adapter.d.ts +3 -1
- package/dist/plan/framework-adapter.js +99 -6
- 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 +8 -46
- package/dist/plan/plan-framework.js +81 -312
- package/dist/plan/plan-types.d.ts +29 -0
- package/dist/plan/plan-vite.d.ts +67 -0
- package/dist/plan/plan-vite.js +256 -0
- package/dist/plan/plan.js +38 -34
- 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/register/mcp-clients.js +5 -3
- package/dist/register/mcp.d.ts +18 -5
- package/dist/register/mcp.js +48 -8
- package/dist/run-types.d.ts +8 -0
- package/dist/run.js +118 -80
- package/package.json +3 -4
- package/NOTICE +0 -5
|
@@ -24,17 +24,27 @@ const REGISTRY_HINT = 'If it could not reach the registry at all (offline, a pro
|
|
|
24
24
|
* sent the reader through their dependency versions looking for a problem that was actually
|
|
25
25
|
* about where the store lives (#683).
|
|
26
26
|
*/
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
function virtualStoreHint(pmCommand) {
|
|
28
|
+
return ("If pnpm reported ERR_PNPM_UNEXPECTED_VIRTUAL_STORE, this checkout's node_modules is " +
|
|
29
|
+
"symlinked into another checkout's pnpm store (a git worktree, or an A/B harness). Either run " +
|
|
30
|
+
`\`${pmCommand} install\` once in that other checkout first, or point this one at its own store:\n` +
|
|
31
|
+
` ${pmCommand} add -D --config.virtual-store-dir=node_modules/.pnpm <packages>`);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* `pm` picks which hint applies; `pmCommand` is what the reader actually types.
|
|
35
|
+
*
|
|
36
|
+
* A corepack-only machine (see `preflight.ts`, #1149) has no bare `pnpm` on PATH — that is the whole
|
|
37
|
+
* shape of the bug this hint is used alongside. Printing `pnpm config set …` to that reader hands them
|
|
38
|
+
* a command that fails the same way the original install did; `pmCommand` is the resolved invocation
|
|
39
|
+
* (`corepack pnpm`, there) that is actually run on this machine.
|
|
40
|
+
*/
|
|
41
|
+
export function installFailureHint(pm, pmCommand = pm) {
|
|
32
42
|
if (pm !== PackageManager.PNPM) {
|
|
33
43
|
return `If the version was refused, install the SDK yourself. ${REGISTRY_HINT}`;
|
|
34
44
|
}
|
|
35
45
|
return ('If pnpm reported ERR_PNPM_NO_MATURE_MATCHING_VERSION, its minimumReleaseAge setting is holding ' +
|
|
36
46
|
'this release back. Either wait out the window, or allow these packages explicitly:\n' +
|
|
37
|
-
|
|
47
|
+
` ${pmCommand} config set minimumReleaseAgeExclude "@reticlehq/*"\n` +
|
|
38
48
|
'Do NOT drop the version pin — unpinned, pnpm installs an older SDK against a newer daemon, and ' +
|
|
39
|
-
`that mismatch surfaces as a -32000 with nothing naming a version.\n${
|
|
49
|
+
`that mismatch surfaces as a -32000 with nothing naming a version.\n${virtualStoreHint(pmCommand)}\n${REGISTRY_HINT}`);
|
|
40
50
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* a different cost and a different sentence to say afterwards.
|
|
7
7
|
*/
|
|
8
8
|
import { PackageManager } from '../detect/detect.js';
|
|
9
|
+
import type { InitIo } from '../run-types.js';
|
|
9
10
|
interface InstallRetry {
|
|
10
11
|
command: string;
|
|
11
12
|
args: string[];
|
|
@@ -18,5 +19,19 @@ interface InstallRetry {
|
|
|
18
19
|
* pnpm, yarn and bun would reject an unknown argument — turning one failed install into two, with
|
|
19
20
|
* the second failure blaming a flag the user never asked for.
|
|
20
21
|
*/
|
|
21
|
-
export declare function installRetries(pm: PackageManager, pinned: readonly string[], unpinned: readonly string[], sdkVersion: string | undefined
|
|
22
|
+
export declare function installRetries(pm: PackageManager, pinned: readonly string[], unpinned: readonly string[], sdkVersion: string | undefined,
|
|
23
|
+
/**
|
|
24
|
+
* The prefix that actually invokes `pm` on this machine — `pm` itself, or `corepack ${pm}` on a
|
|
25
|
+
* corepack-only machine (see `preflight.ts`, #1149). A retry that spawned the bare binary here,
|
|
26
|
+
* after the first attempt already went through the resolved prefix, would fail with the same
|
|
27
|
+
* ENOENT the resolution exists to avoid — on the one machine shape that needs a retry to reach a
|
|
28
|
+
* working install at all. Defaults to `pm` for callers that never went through preflight.
|
|
29
|
+
*/
|
|
30
|
+
pmCommand?: string): InstallRetry[];
|
|
31
|
+
export declare function quarantineNote(command: string): string;
|
|
32
|
+
/** Why the dependency install failed, when that can be read; undefined keeps the generic text. */
|
|
33
|
+
export declare function explainInstallFailure(io: Pick<InitIo, 'capture'>, packageManager: PackageManager, exec: {
|
|
34
|
+
command: string;
|
|
35
|
+
args: readonly string[];
|
|
36
|
+
}, display: string): string | undefined;
|
|
22
37
|
export {};
|
|
@@ -50,14 +50,61 @@ function unpinnedRetryNote(version, pm) {
|
|
|
50
50
|
* pnpm, yarn and bun would reject an unknown argument — turning one failed install into two, with
|
|
51
51
|
* the second failure blaming a flag the user never asked for.
|
|
52
52
|
*/
|
|
53
|
-
export function installRetries(pm, pinned, unpinned, sdkVersion
|
|
53
|
+
export function installRetries(pm, pinned, unpinned, sdkVersion,
|
|
54
|
+
/**
|
|
55
|
+
* The prefix that actually invokes `pm` on this machine — `pm` itself, or `corepack ${pm}` on a
|
|
56
|
+
* corepack-only machine (see `preflight.ts`, #1149). A retry that spawned the bare binary here,
|
|
57
|
+
* after the first attempt already went through the resolved prefix, would fail with the same
|
|
58
|
+
* ENOENT the resolution exists to avoid — on the one machine shape that needs a retry to reach a
|
|
59
|
+
* working install at all. Defaults to `pm` for callers that never went through preflight.
|
|
60
|
+
*/
|
|
61
|
+
pmCommand = pm) {
|
|
54
62
|
return [
|
|
55
63
|
// Peers first: concedes only peer resolution and KEEPS the version pin.
|
|
56
64
|
...(pm === PackageManager.NPM
|
|
57
|
-
? [
|
|
65
|
+
? [
|
|
66
|
+
{
|
|
67
|
+
...installCommandParts(pm, pinned, [LEGACY_PEER_DEPS], pmCommand),
|
|
68
|
+
note: LEGACY_PEER_NOTE,
|
|
69
|
+
},
|
|
70
|
+
]
|
|
58
71
|
: []),
|
|
59
72
|
// Unpinned, last. pnpm resolves the newest MATURE version there, which is how a project with a
|
|
60
73
|
// release-age hold gets a working install instead of no install.
|
|
61
|
-
{
|
|
74
|
+
{
|
|
75
|
+
...installCommandParts(pm, unpinned, [], pmCommand),
|
|
76
|
+
note: unpinnedRetryNote(sdkVersion, pm),
|
|
77
|
+
},
|
|
62
78
|
];
|
|
63
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Yarn Berry refusing a release for being too new, told as what it is.
|
|
82
|
+
*
|
|
83
|
+
* Yarn 4 quarantines versions younger than its `npmMinimalAgeGate`, and answers an exact pin with
|
|
84
|
+
* `All versions satisfying "<v>" are quarantined`. A Reticle release is exactly that young on the
|
|
85
|
+
* day a user installs it. `init` printed "run manually: yarn add -D …", which fails the same way,
|
|
86
|
+
* and the runtime then booted the app and blamed the dev server for an SDK that was never installed.
|
|
87
|
+
*
|
|
88
|
+
* `exec` inherits stdio so the install's progress is live, which also means its text never reaches
|
|
89
|
+
* us. So on a Yarn failure the pinned command is run once more, quietly, to read why — a quarantine
|
|
90
|
+
* is refused at resolution, before anything is fetched, so the second run costs well under a
|
|
91
|
+
* second. Every other failure keeps the generic fallback.
|
|
92
|
+
*/
|
|
93
|
+
/** Yarn's own wording, and the environment variable that is its one-shot override. */
|
|
94
|
+
const QUARANTINED = /are quarantined/;
|
|
95
|
+
const AGE_GATE_ENV = 'YARN_NPM_MINIMAL_AGE_GATE';
|
|
96
|
+
export function quarantineNote(command) {
|
|
97
|
+
return ("Yarn refused the release: it is younger than Yarn's `npmMinimalAgeGate`, so every matching " +
|
|
98
|
+
'version is quarantined ("are quarantined" above) and running the same command by hand fails ' +
|
|
99
|
+
'the same way. Nothing was wired, so there is no app to start yet. Either wait until the ' +
|
|
100
|
+
'release is older than the gate, or lift the gate for this one install and re-run init:\n\n' +
|
|
101
|
+
` ${AGE_GATE_ENV}=0 ${command}\n\n` +
|
|
102
|
+
` (PowerShell: $env:${AGE_GATE_ENV}=0; ${command})`);
|
|
103
|
+
}
|
|
104
|
+
/** Why the dependency install failed, when that can be read; undefined keeps the generic text. */
|
|
105
|
+
export function explainInstallFailure(io, packageManager, exec, display) {
|
|
106
|
+
if (PackageManager.YARN !== packageManager || io.capture === undefined)
|
|
107
|
+
return undefined;
|
|
108
|
+
const { output } = io.capture(exec.command, exec.args);
|
|
109
|
+
return QUARANTINED.test(output) ? quarantineNote(display) : undefined;
|
|
110
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup output, laid out for the person reading it in a terminal.
|
|
3
|
+
*
|
|
4
|
+
* Setup's sentences are written one per line and were printed as they are, so a paragraph ran off
|
|
5
|
+
* the right edge and the terminal broke it mid-word, or they were broken by hand at a width that
|
|
6
|
+
* matched nobody's window. This wraps at the reader's real width instead, keeping each line's indent
|
|
7
|
+
* and hanging continuation lines under a list marker, so a numbered step still reads as one step.
|
|
8
|
+
*
|
|
9
|
+
* Commands are never wrapped: a command split across two lines is one nobody can paste. And output
|
|
10
|
+
* that is not going to a terminal — an agent reading a pipe — is left exactly as written.
|
|
11
|
+
*/
|
|
12
|
+
/** The width to wrap at, or undefined when stdout is not a terminal. */
|
|
13
|
+
export declare function terminalWidth(stream?: {
|
|
14
|
+
isTTY?: boolean;
|
|
15
|
+
columns?: number;
|
|
16
|
+
}): number | undefined;
|
|
17
|
+
/** `text` wrapped line by line to `width`; unchanged when `width` is undefined. */
|
|
18
|
+
export declare function wrapForTerminal(text: string, width: number | undefined): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup output, laid out for the person reading it in a terminal.
|
|
3
|
+
*
|
|
4
|
+
* Setup's sentences are written one per line and were printed as they are, so a paragraph ran off
|
|
5
|
+
* the right edge and the terminal broke it mid-word, or they were broken by hand at a width that
|
|
6
|
+
* matched nobody's window. This wraps at the reader's real width instead, keeping each line's indent
|
|
7
|
+
* and hanging continuation lines under a list marker, so a numbered step still reads as one step.
|
|
8
|
+
*
|
|
9
|
+
* Commands are never wrapped: a command split across two lines is one nobody can paste. And output
|
|
10
|
+
* that is not going to a terminal — an agent reading a pipe — is left exactly as written.
|
|
11
|
+
*/
|
|
12
|
+
/** Narrow enough to read, wide enough that a step is not ten lines tall. */
|
|
13
|
+
const MAX_WIDTH = 100;
|
|
14
|
+
const MIN_WIDTH = 40;
|
|
15
|
+
/** A line a reader runs rather than reads. */
|
|
16
|
+
const COMMAND = /^\s*(?:\$ |npx |npm |pnpm |yarn |bun |reticle |curl |cd |export |import )/;
|
|
17
|
+
/** What a continuation line hangs under: `1. `, `- `, `• `, `✓ `, `⚠ `, `why: `, `[✓] `. */
|
|
18
|
+
const MARKER = /^(\s*)(\d+\.\s+|[-•✓⚠]\s+|why:\s+|\[.\]\s+)?/u;
|
|
19
|
+
/** The width to wrap at, or undefined when stdout is not a terminal. */
|
|
20
|
+
export function terminalWidth(stream = process.stdout) {
|
|
21
|
+
if (true !== stream.isTTY || stream.columns === undefined)
|
|
22
|
+
return undefined;
|
|
23
|
+
return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, stream.columns - 1));
|
|
24
|
+
}
|
|
25
|
+
function wrapLine(line, width) {
|
|
26
|
+
if (line.length <= width || COMMAND.test(line))
|
|
27
|
+
return line;
|
|
28
|
+
const match = MARKER.exec(line);
|
|
29
|
+
const lead = match?.[0] ?? '';
|
|
30
|
+
const hang = ' '.repeat(lead.length);
|
|
31
|
+
const words = line
|
|
32
|
+
.slice(lead.length)
|
|
33
|
+
.split(' ')
|
|
34
|
+
.filter((w) => w.length > 0);
|
|
35
|
+
// A path or URL wider than the window cannot be kept whole by wrapping, and breaking the words
|
|
36
|
+
// around it only strands its label on a line of its own. The terminal's own wrap does better.
|
|
37
|
+
if (words.some((w) => hang.length + w.length > width))
|
|
38
|
+
return line;
|
|
39
|
+
const out = [];
|
|
40
|
+
let current = lead;
|
|
41
|
+
let prefix = lead;
|
|
42
|
+
for (const word of words) {
|
|
43
|
+
if (current.length > prefix.length && current.length + 1 + word.length > width) {
|
|
44
|
+
out.push(current);
|
|
45
|
+
prefix = hang;
|
|
46
|
+
current = hang + word;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
current = current.length > prefix.length ? `${current} ${word}` : current + word;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
out.push(current);
|
|
53
|
+
return out.join('\n');
|
|
54
|
+
}
|
|
55
|
+
/** `text` wrapped line by line to `width`; unchanged when `width` is undefined. */
|
|
56
|
+
export function wrapForTerminal(text, width) {
|
|
57
|
+
if (width === undefined)
|
|
58
|
+
return text;
|
|
59
|
+
return text
|
|
60
|
+
.split('\n')
|
|
61
|
+
.map((line) => wrapLine(line, width))
|
|
62
|
+
.join('\n');
|
|
63
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export { runInit, resolveLockfiles, type InitIo, type InitOptions, type InitResu
|
|
|
19
19
|
*/
|
|
20
20
|
export { windowsShellArg } from './register/windows-quote.js';
|
|
21
21
|
export { buildNodeIo, probeCli } from './node-io.js';
|
|
22
|
+
export { terminalWidth, wrapForTerminal } from './diagnose/terminal-wrap.js';
|
|
23
|
+
export { FEEDBACK_HINT } from './diagnose/closing-hint.js';
|
|
22
24
|
export { type InitHost, SILENT_HOST } from './host.js';
|
|
23
25
|
export { RETICLE_VERSION, RETICLE_NPM_PACKAGE } from './version.js';
|
|
24
26
|
export { InitFailure } from './diagnose/init-failure.js';
|
|
@@ -26,12 +28,14 @@ export { NodePlatform } from './detect/platform.js';
|
|
|
26
28
|
export { DEV_SERVER_PORTS, isLikelyDevServerPort, devServerPortWarning, } from './detect/dev-server-ports.js';
|
|
27
29
|
export { configWithInstallSource } from './project/install-source-config.js';
|
|
28
30
|
export { projectIdOf, rememberProjectOnDisk, type RegistryIo } from './project/remember-project.js';
|
|
29
|
-
export { detectPackageManager, installCommandParts, parseMajor, PackageManager, } from './detect/detect.js';
|
|
31
|
+
export { detectPackageManager, Framework, installCommandParts, parseMajor, PackageManager, } from './detect/detect.js';
|
|
30
32
|
export { findWorkspaceApps } from './detect/workspace-apps.js';
|
|
33
|
+
export { desktopLaunch, type DesktopLaunch } from './detect/dev-script.js';
|
|
31
34
|
export { deriveProjectId, packageName } from './project/project-id.js';
|
|
32
35
|
export { refreshAgentRules } from './project/refresh-rules.js';
|
|
33
36
|
export { diagnoseDesktop, isDesktopProject } from './diagnose/desktop-doctor.js';
|
|
34
37
|
export { CspBasis, diagnoseObservedWebCsp, diagnoseWebCsp, resolveWebCspFindings, type CspDiagnosis, type ObservedWebDocument, } from './diagnose/csp-doctor.js';
|
|
38
|
+
export { webCspOptionsFor } from './diagnose/csp-step.js';
|
|
35
39
|
export { reticleConfigContent } from './patch/snippets.js';
|
|
36
40
|
/**
|
|
37
41
|
* MCP registration, for a caller that has no project.
|
|
@@ -42,5 +46,5 @@ export { reticleConfigContent } from './patch/snippets.js';
|
|
|
42
46
|
* here", and the two would disagree the first time a client was added to one of them.
|
|
43
47
|
*/
|
|
44
48
|
export { McpClient, ConfigScope, MCP_CLIENTS, ClientMergeStatus, clientSpec, mergeClientConfig, } from './register/mcp-clients.js';
|
|
45
|
-
export { claudeAddCommand, claudeAvailableProbe,
|
|
49
|
+
export { claudeAddCommand, claudeAvailableProbe, claudeHasReticle, } from './register/mcp.js';
|
|
46
50
|
export { detectMcpClients, type DetectedClient } from './register/detect-clients.js';
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,8 @@ export { runInit, resolveLockfiles, } from './run.js';
|
|
|
19
19
|
*/
|
|
20
20
|
export { windowsShellArg } from './register/windows-quote.js';
|
|
21
21
|
export { buildNodeIo, probeCli } from './node-io.js';
|
|
22
|
+
export { terminalWidth, wrapForTerminal } from './diagnose/terminal-wrap.js';
|
|
23
|
+
export { FEEDBACK_HINT } from './diagnose/closing-hint.js';
|
|
22
24
|
export { SILENT_HOST } from './host.js';
|
|
23
25
|
export { RETICLE_VERSION, RETICLE_NPM_PACKAGE } from './version.js';
|
|
24
26
|
export { InitFailure } from './diagnose/init-failure.js';
|
|
@@ -26,12 +28,14 @@ export { NodePlatform } from './detect/platform.js';
|
|
|
26
28
|
export { DEV_SERVER_PORTS, isLikelyDevServerPort, devServerPortWarning, } from './detect/dev-server-ports.js';
|
|
27
29
|
export { configWithInstallSource } from './project/install-source-config.js';
|
|
28
30
|
export { projectIdOf, rememberProjectOnDisk } from './project/remember-project.js';
|
|
29
|
-
export { detectPackageManager, installCommandParts, parseMajor, PackageManager, } from './detect/detect.js';
|
|
31
|
+
export { detectPackageManager, Framework, installCommandParts, parseMajor, PackageManager, } from './detect/detect.js';
|
|
30
32
|
export { findWorkspaceApps } from './detect/workspace-apps.js';
|
|
33
|
+
export { desktopLaunch } from './detect/dev-script.js';
|
|
31
34
|
export { deriveProjectId, packageName } from './project/project-id.js';
|
|
32
35
|
export { refreshAgentRules } from './project/refresh-rules.js';
|
|
33
36
|
export { diagnoseDesktop, isDesktopProject } from './diagnose/desktop-doctor.js';
|
|
34
37
|
export { CspBasis, diagnoseObservedWebCsp, diagnoseWebCsp, resolveWebCspFindings, } from './diagnose/csp-doctor.js';
|
|
38
|
+
export { webCspOptionsFor } from './diagnose/csp-step.js';
|
|
35
39
|
export { reticleConfigContent } from './patch/snippets.js';
|
|
36
40
|
/**
|
|
37
41
|
* MCP registration, for a caller that has no project.
|
|
@@ -46,5 +50,5 @@ export { claudeAddCommand,
|
|
|
46
50
|
// The two probes `run.ts` uses to decide about Claude Code. Exported because `reticle setup mcp`
|
|
47
51
|
// asks the same question and had no way to ask it: Claude Code keeps no config file, so the
|
|
48
52
|
// file-reading detector cannot see it, and the installer silently skipped the commonest client.
|
|
49
|
-
claudeAvailableProbe,
|
|
53
|
+
claudeAvailableProbe, claudeHasReticle, } from './register/mcp.js';
|
|
50
54
|
export { detectMcpClients } from './register/detect-clients.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `init` in a directory with no package.json, once the workspace redirect has found no app below it.
|
|
3
|
+
*
|
|
4
|
+
* Its own module because it is its own flow: nothing here detects a framework, plans steps or runs a
|
|
5
|
+
* package manager. It scopes the project on disk, then either WRITES the connect snippet into a
|
|
6
|
+
* static page it can see whole, or prints the snippet for the stack it recognised (Streamlit,
|
|
7
|
+
* Django, several pages) and stops.
|
|
8
|
+
*/
|
|
9
|
+
import type { InitIo, InitOptions, InitResult } from './run-types.js';
|
|
10
|
+
export declare function initWithoutPackageJson(options: InitOptions, io: InitIo): InitResult;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `init` in a directory with no package.json, once the workspace redirect has found no app below it.
|
|
3
|
+
*
|
|
4
|
+
* Its own module because it is its own flow: nothing here detects a framework, plans steps or runs a
|
|
5
|
+
* package manager. It scopes the project on disk, then either WRITES the connect snippet into a
|
|
6
|
+
* static page it can see whole, or prints the snippet for the stack it recognised (Streamlit,
|
|
7
|
+
* Django, several pages) and stops.
|
|
8
|
+
*/
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { PackageManager, Framework } from './detect/detect.js';
|
|
11
|
+
import { detectDjangoProject, detectStreamlitProject, djangoSetupMessage, noPackageJsonMessage, streamlitSetupMessage, } from './detect/non-js-project.js';
|
|
12
|
+
import { InitFailure } from './diagnose/init-failure.js';
|
|
13
|
+
import { HTML_INDEX_PATH, STATIC_TOKEN_MODULE, staticTokenFiles, withStaticSnippet, } from './patch/static-page.js';
|
|
14
|
+
import { connectArg, connectArgWithToken, djangoMiddlewareSnippet, reticleConfigContent, staticPageSnippet, streamlitPageSnippet, } from './patch/snippets.js';
|
|
15
|
+
import { RETICLE_CONFIG_FILE } from './detect/existing-config.js';
|
|
16
|
+
import { deriveProjectId } from './project/project-id.js';
|
|
17
|
+
import { portMoveDetail, retargetConfigPort, retargetPort } from './plan/port-steps.js';
|
|
18
|
+
import { rememberProjectOnDisk } from './project/remember-project.js';
|
|
19
|
+
const SNIPPET_WRITTEN = `Wrote the dev-only connect snippet into ${HTML_INDEX_PATH}. It runs only when the page is ` +
|
|
20
|
+
`opened on localhost. This machine’s pairing token is in ${STATIC_TOKEN_MODULE}, which is ` +
|
|
21
|
+
'gitignored — keep it out of anything you publish, and let each teammate run `reticle init` ' +
|
|
22
|
+
'for their own.';
|
|
23
|
+
const SNIPPET_ALREADY = `${HTML_INDEX_PATH} already loads the Reticle SDK — nothing to change.`;
|
|
24
|
+
const DRY_RUN_PAIRING_TOKEN = 'DRY_RUN_TOKEN_NOT_FOR_USE';
|
|
25
|
+
/**
|
|
26
|
+
* Write the snippet into `index.html`, and hand over to setup as a success.
|
|
27
|
+
*
|
|
28
|
+
* This is the path where the snippet is the whole install, proven to connect when pasted. It used to
|
|
29
|
+
* print the snippet and exit 1 with `no_package_json`, which read to the funnel — and to the person —
|
|
30
|
+
* as the setup failing on exactly the path that works.
|
|
31
|
+
*/
|
|
32
|
+
function wireStaticPage(options, io, connect) {
|
|
33
|
+
for (const [path, content] of Object.entries(staticTokenFiles(io.host.pairingToken(), (p) => io.readFile(p)))) {
|
|
34
|
+
io.writeFile(path, content);
|
|
35
|
+
}
|
|
36
|
+
const html = io.readFile(HTML_INDEX_PATH) ?? '';
|
|
37
|
+
// A page that already connects still has its port compared: the snippet bakes the daemon URL, and
|
|
38
|
+
// a re-run with a new `--port` that answered "already" left the page dialling the old daemon.
|
|
39
|
+
const written = withStaticSnippet(html, connect);
|
|
40
|
+
const moved = null === written ? retargetPort(html, options.port) : null;
|
|
41
|
+
const patched = written ?? moved?.code ?? null;
|
|
42
|
+
if (null !== patched)
|
|
43
|
+
io.writeFile(HTML_INDEX_PATH, patched);
|
|
44
|
+
io.print(null !== written
|
|
45
|
+
? SNIPPET_WRITTEN
|
|
46
|
+
: null !== moved && options.port !== undefined
|
|
47
|
+
? `Updated ${HTML_INDEX_PATH}: ${portMoveDetail(moved.from, options.port)}.`
|
|
48
|
+
: SNIPPET_ALREADY);
|
|
49
|
+
const outcome = { ok: true, stack: Framework.HTML };
|
|
50
|
+
if (true !== options.deferOutcome)
|
|
51
|
+
io.host.reportOutcome(outcome);
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
applied: null === patched ? 0 : 1,
|
|
55
|
+
manual: 0,
|
|
56
|
+
// Setup takes it from here: with `--url` it opens the page and waits for the session, which is
|
|
57
|
+
// the only proof that matters; without one it says the page has no dev server to start.
|
|
58
|
+
context: { appDir: options.cwd, framework: Framework.HTML, packageManager: PackageManager.NPM },
|
|
59
|
+
...(true === options.deferOutcome ? { outcome } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function initWithoutPackageJson(options, io) {
|
|
63
|
+
const streamlit = detectStreamlitProject((file) => io.readFile(file), io.rootFiles());
|
|
64
|
+
// Asked only when Streamlit already said no, so the two can never both claim the page.
|
|
65
|
+
const django = !streamlit && detectDjangoProject((file) => io.exists(join(options.cwd, file)), io.rootFiles());
|
|
66
|
+
// Scope the project on disk before anything else, so the daemon can serve this page.
|
|
67
|
+
//
|
|
68
|
+
// This path used to print and exit, leaving no `.reticle.json` at all — and the reporter who
|
|
69
|
+
// asked for the Django path had to hand-write one alongside the middleware. Without it the
|
|
70
|
+
// daemon has no config in its own directory, refuses the page's dial, and every downstream
|
|
71
|
+
// symptom points somewhere other than the cause (see #685). The projectId derivation already
|
|
72
|
+
// handles the no-package case: it falls back to the root folder name, fingerprinted by the
|
|
73
|
+
// absolute path so two checkouts stay distinct.
|
|
74
|
+
//
|
|
75
|
+
// Written only when absent. A config a user or an earlier run already placed here is theirs.
|
|
76
|
+
const nonJsProjectId = deriveProjectId(undefined, io.cwd());
|
|
77
|
+
const writeConfig = () => {
|
|
78
|
+
if (options.dryRun) {
|
|
79
|
+
io.print('Dry run: no files written; the snippet uses a placeholder pairing token.');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Except its port. A re-run with a new `--port` starts the daemon there, and a config still
|
|
83
|
+
// naming the old one sends the agent to a daemon that is no longer running.
|
|
84
|
+
const existingConfig = io.readFile(RETICLE_CONFIG_FILE);
|
|
85
|
+
const moved = null === existingConfig ? null : retargetConfigPort(existingConfig, options.port);
|
|
86
|
+
if (null !== moved && options.port !== undefined) {
|
|
87
|
+
io.writeFile(RETICLE_CONFIG_FILE, moved.code);
|
|
88
|
+
io.print(`Updated ${RETICLE_CONFIG_FILE}: ${portMoveDetail(moved.from, options.port)}.`);
|
|
89
|
+
}
|
|
90
|
+
if (io.exists(RETICLE_CONFIG_FILE))
|
|
91
|
+
return;
|
|
92
|
+
io.writeFile(RETICLE_CONFIG_FILE, reticleConfigContent(Framework.HTML, options.port, nonJsProjectId, io.host.installSource()));
|
|
93
|
+
rememberProjectOnDisk(io, nonJsProjectId, io.cwd(), Date.now());
|
|
94
|
+
io.print(`Wrote ${RETICLE_CONFIG_FILE} (project "${nonJsProjectId}").`);
|
|
95
|
+
};
|
|
96
|
+
const connect = connectArgWithToken(options.port, nonJsProjectId, options.dryRun ? DRY_RUN_PAIRING_TOKEN : io.host.pairingToken());
|
|
97
|
+
// One page at the root is a page we can see whole. Several, or none, is a choice that is not ours.
|
|
98
|
+
if (!streamlit && !django && io.exists(HTML_INDEX_PATH)) {
|
|
99
|
+
writeConfig();
|
|
100
|
+
if (options.dryRun) {
|
|
101
|
+
io.print(staticPageSnippet(connect));
|
|
102
|
+
return { ok: true, applied: 0, manual: 0 };
|
|
103
|
+
}
|
|
104
|
+
// Without the token: the page is the deployable artifact, so the token goes beside it instead.
|
|
105
|
+
return wireStaticPage(options, io, connectArg(options.port, nonJsProjectId));
|
|
106
|
+
}
|
|
107
|
+
io.print(
|
|
108
|
+
// Two genuinely different situations used to share one sentence: a JS developer in the wrong
|
|
109
|
+
// directory, and a project that is not JavaScript at all. The second reads the old wording as
|
|
110
|
+
// a path problem and goes looking for a directory that cannot exist — reported from a
|
|
111
|
+
// Streamlit app, where the search continued into hunting for a browser bundle to inject by
|
|
112
|
+
// 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()));
|
|
118
|
+
writeConfig();
|
|
119
|
+
// The message says "add the snippet below". Print the snippet, or the message is the same broken
|
|
120
|
+
// promise in the other direction.
|
|
121
|
+
io.print(streamlit
|
|
122
|
+
? streamlitPageSnippet(connect)
|
|
123
|
+
: django
|
|
124
|
+
? djangoMiddlewareSnippet(connect)
|
|
125
|
+
: staticPageSnippet(connect));
|
|
126
|
+
// The onboarding funnel had NO instrumentation, so a setup that died here was indistinguishable
|
|
127
|
+
// from someone who never ran the command — the two failure modes with the most different fixes.
|
|
128
|
+
if (!options.dryRun)
|
|
129
|
+
io.host.reportOutcome({ ok: false, reason: InitFailure.NO_PACKAGE_JSON });
|
|
130
|
+
return { ok: false, applied: 0, manual: 0 };
|
|
131
|
+
}
|
package/dist/node-io.d.ts
CHANGED
|
@@ -8,11 +8,15 @@ 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`, and the Claude CLI —
|
|
12
|
-
* are the only things the plan can ask for. It is a named constant for the same reason
|
|
13
|
-
* wire string here is one, and it is CHECKED because of the shell: on Windows these spawn
|
|
14
|
-
* a shell, and a shell turns the command name into something the shell parses rather than a
|
|
15
|
-
* executes. Arguments are quoted by `shellSafe`; the command name was the half nothing
|
|
11
|
+
* The set is closed and tiny — the four package managers, `npx`, `corepack`, and the Claude CLI —
|
|
12
|
+
* because those are the only things the plan can ask for. It is a named constant for the same reason
|
|
13
|
+
* every other wire string here is one, and it is CHECKED because of the shell: on Windows these spawn
|
|
14
|
+
* through a shell, and a shell turns the command name into something the shell parses rather than a
|
|
15
|
+
* file it executes. Arguments are quoted by `shellSafe`; the command name was the half nothing
|
|
16
|
+
* covered.
|
|
17
|
+
*
|
|
18
|
+
* `corepack` runs the package manager, never IS it (see `preflight.ts`, #1149) — the allowlist has to
|
|
19
|
+
* name the actual program being spawned.
|
|
16
20
|
*
|
|
17
21
|
* A command outside this set is a programming error — the plan built something no branch of this
|
|
18
22
|
* package can produce — so it throws rather than returning `false`, which would read to the caller
|
|
@@ -30,4 +34,6 @@ export declare const RUNNABLE_COMMANDS: readonly string[];
|
|
|
30
34
|
* reason a second, broken copy existed.
|
|
31
35
|
*/
|
|
32
36
|
export declare function probeCli(command: string, args: readonly string[]): boolean;
|
|
33
|
-
export declare function buildNodeIo(cwd: string, host: InitHost
|
|
37
|
+
export declare function buildNodeIo(cwd: string, host: InitHost, output?: {
|
|
38
|
+
stderr?: boolean;
|
|
39
|
+
}): InitIo;
|
package/dist/node-io.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* `runInit` stays testable with an in-memory IO. Prints to stdout — `init` is a one-shot CLI
|
|
4
4
|
* command, not the MCP stdio transport.
|
|
5
5
|
*/
|
|
6
|
+
import { terminalWidth, wrapForTerminal } from './diagnose/terminal-wrap.js';
|
|
6
7
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, accessSync, constants, } from 'node:fs';
|
|
7
8
|
import { NodePlatform } from './detect/platform.js';
|
|
8
9
|
import { join, dirname, isAbsolute } from 'node:path';
|
|
@@ -23,17 +24,29 @@ function shellSafe(args) {
|
|
|
23
24
|
/**
|
|
24
25
|
* Every program `init` is ever allowed to run.
|
|
25
26
|
*
|
|
26
|
-
* The set is closed and tiny — the four package managers, `npx`, and the Claude CLI —
|
|
27
|
-
* are the only things the plan can ask for. It is a named constant for the same reason
|
|
28
|
-
* wire string here is one, and it is CHECKED because of the shell: on Windows these spawn
|
|
29
|
-
* a shell, and a shell turns the command name into something the shell parses rather than a
|
|
30
|
-
* executes. Arguments are quoted by `shellSafe`; the command name was the half nothing
|
|
27
|
+
* The set is closed and tiny — the four package managers, `npx`, `corepack`, and the Claude CLI —
|
|
28
|
+
* because those are the only things the plan can ask for. It is a named constant for the same reason
|
|
29
|
+
* every other wire string here is one, and it is CHECKED because of the shell: on Windows these spawn
|
|
30
|
+
* through a shell, and a shell turns the command name into something the shell parses rather than a
|
|
31
|
+
* file it executes. Arguments are quoted by `shellSafe`; the command name was the half nothing
|
|
32
|
+
* covered.
|
|
33
|
+
*
|
|
34
|
+
* `corepack` runs the package manager, never IS it (see `preflight.ts`, #1149) — the allowlist has to
|
|
35
|
+
* name the actual program being spawned.
|
|
31
36
|
*
|
|
32
37
|
* A command outside this set is a programming error — the plan built something no branch of this
|
|
33
38
|
* package can produce — so it throws rather than returning `false`, which would read to the caller
|
|
34
39
|
* as "the command ran and failed" and hide the bug in a retry.
|
|
35
40
|
*/
|
|
36
|
-
export const RUNNABLE_COMMANDS = [
|
|
41
|
+
export const RUNNABLE_COMMANDS = [
|
|
42
|
+
'pnpm',
|
|
43
|
+
'yarn',
|
|
44
|
+
'bun',
|
|
45
|
+
'npm',
|
|
46
|
+
'npx',
|
|
47
|
+
'corepack',
|
|
48
|
+
'claude',
|
|
49
|
+
];
|
|
37
50
|
/** The command, proven to be one of `RUNNABLE_COMMANDS` — never the caller's string. */
|
|
38
51
|
function runnable(command) {
|
|
39
52
|
const found = RUNNABLE_COMMANDS.find((allowed) => allowed === command);
|
|
@@ -89,7 +102,7 @@ export function probeCli(command, args) {
|
|
|
89
102
|
return false;
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
|
-
export function buildNodeIo(cwd, host) {
|
|
105
|
+
export function buildNodeIo(cwd, host, output = {}) {
|
|
93
106
|
// Project-relative by default; absolute paths (e.g. ~/.cursor/mcp.json) pass through unchanged.
|
|
94
107
|
const abs = (rel) => (isAbsolute(rel) ? rel : join(cwd, rel));
|
|
95
108
|
return {
|
|
@@ -148,13 +161,21 @@ export function buildNodeIo(cwd, host) {
|
|
|
148
161
|
scoped(rel) {
|
|
149
162
|
// The host travels with the re-rooted IO: a monorepo redirect re-enters `runInit` through
|
|
150
163
|
// this, and an untraced, unreported inner run is the half of init that actually did the work.
|
|
151
|
-
return buildNodeIo(abs(rel), host);
|
|
164
|
+
return buildNodeIo(abs(rel), host, output);
|
|
152
165
|
},
|
|
153
166
|
exec(command, args) {
|
|
154
|
-
//
|
|
155
|
-
const result = spawnAllowed(command, args, {
|
|
167
|
+
// JSON callers reserve stdout for the result, including across package-manager subprocesses.
|
|
168
|
+
const result = spawnAllowed(command, args, {
|
|
169
|
+
cwd,
|
|
170
|
+
stdio: true === output.stderr ? ['inherit', 2, 2] : 'inherit',
|
|
171
|
+
});
|
|
156
172
|
return 0 === result.status;
|
|
157
173
|
},
|
|
174
|
+
capture(command, args) {
|
|
175
|
+
const result = spawnAllowed(command, args, { cwd, stdio: 'pipe', encoding: 'utf8' });
|
|
176
|
+
const text = (part) => ('string' === typeof part ? part : '');
|
|
177
|
+
return { ok: 0 === result.status, output: `${text(result.stdout)}${text(result.stderr)}` };
|
|
178
|
+
},
|
|
158
179
|
/** One access check, rather than discovering it as an EACCES stack four phases later. */
|
|
159
180
|
canWrite() {
|
|
160
181
|
try {
|
|
@@ -171,7 +192,8 @@ export function buildNodeIo(cwd, host) {
|
|
|
171
192
|
return 0 === result.status;
|
|
172
193
|
},
|
|
173
194
|
print(line) {
|
|
174
|
-
process.stdout
|
|
195
|
+
const stream = true === output.stderr ? process.stderr : process.stdout;
|
|
196
|
+
stream.write(`${wrapForTerminal(line, terminalWidth())}\n`);
|
|
175
197
|
},
|
|
176
198
|
host,
|
|
177
199
|
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Angular CLI: a dev-only connect in the browser entry, and a way for the pairing token to reach it.
|
|
3
|
+
*
|
|
4
|
+
* The connect is the part proven by hand on Angular 22, with and without SSR: a dynamic
|
|
5
|
+
* `import('@reticlehq/browser')` guarded on `ngDevMode` (see DEV_GUARD for why not `isDevMode()`),
|
|
6
|
+
* which the production build folds to `false` and drops together with the chunk it would load.
|
|
7
|
+
*
|
|
8
|
+
* The token is the hard part, and why this is not one line. The bridge refuses a hello without the
|
|
9
|
+
* machine's pairing token even on localhost, and every other stack inlines it from Node at build
|
|
10
|
+
* time — the Vite plugin's `define`, Next's `NEXT_PUBLIC_*`, CRA's gitignored `.env.development.local`.
|
|
11
|
+
* The Angular CLI has no config file that runs code, so each of those has a failure here, all
|
|
12
|
+
* measured on a scaffolded Angular 21 app before this was written:
|
|
13
|
+
*
|
|
14
|
+
* - A literal in `angular.json` (`define`, `fileReplacements`) commits a per-machine secret.
|
|
15
|
+
* - A gitignored module imported from `main.ts` is bundled by `ng build` too: with the file present
|
|
16
|
+
* the production output held a lazy chunk carrying the token behind `isDevMode()`, which the
|
|
17
|
+
* build cannot fold — and a token must not depend on a guard being written exactly right.
|
|
18
|
+
* - Making that module a dev-only `fileReplacements` target breaks `ng serve` for every teammate
|
|
19
|
+
* who has not run `init` ("path in file replacements does not exist").
|
|
20
|
+
*
|
|
21
|
+
* What is left is the serve target, which `ng build` never reads. Its `proxyConfig` is a JavaScript
|
|
22
|
+
* module that `ng serve` loads in Node, so `reticle.proxy.mjs` reads `~/.reticle/pairing-token` at
|
|
23
|
+
* request time — the same file the Vite and Next plugins read — and answers one same-origin route
|
|
24
|
+
* with it. The token is never written into the project, and a production build has no route to it.
|
|
25
|
+
*/
|
|
26
|
+
import { type SourcePatch } from './patch-kind.js';
|
|
27
|
+
/** The generated dev-server module, beside `angular.json`. */
|
|
28
|
+
export declare const ANGULAR_PROXY_PATH = "reticle.proxy.mjs";
|
|
29
|
+
/** The one route that module answers, fetched by the entry's dev-only connect. */
|
|
30
|
+
export declare const ANGULAR_TOKEN_PATH = "/__reticle/pairing-token";
|
|
31
|
+
/** What an Angular CLI app's browser entry is when `angular.json` does not say. */
|
|
32
|
+
export declare const ANGULAR_DEFAULT_ENTRY = "src/main.ts";
|
|
33
|
+
/**
|
|
34
|
+
* The browser entry `angular.json` names — `browser` on the application builder (v17+), `main` on
|
|
35
|
+
* the webpack one — or the CLI's default when it cannot be read.
|
|
36
|
+
*/
|
|
37
|
+
export declare function angularEntry(source: string | null): string;
|
|
38
|
+
/**
|
|
39
|
+
* Point the one app's serve target at `reticle.proxy.mjs`.
|
|
40
|
+
*
|
|
41
|
+
* An existing `proxyConfig` is the app's, and is never replaced: the step goes MANUAL naming the
|
|
42
|
+
* entry to add to it. The file is rewritten through `JSON.stringify` with the CLI's own two-space
|
|
43
|
+
* indent, which is how `ng new` writes it, so the diff is the one key.
|
|
44
|
+
*/
|
|
45
|
+
export declare function patchAngularJson(source: string): SourcePatch;
|
|
46
|
+
/**
|
|
47
|
+
* The dev-server module. Plain ESM, no dependencies, because it runs in the app's `ng serve`.
|
|
48
|
+
*
|
|
49
|
+
* Two refusals, both about who may read a token: a request that did not come over loopback (an
|
|
50
|
+
* `ng serve --host 0.0.0.0` on a shared network), and a browser request from another origin — a
|
|
51
|
+
* different localhost app — which `Sec-Fetch-Site` names on every current browser.
|
|
52
|
+
*
|
|
53
|
+
* `bypass` answers the request itself and returns `false`, so the dummy `target` is never dialled.
|
|
54
|
+
* ponytail: relies on the Vite-based dev server (Angular 17+) ignoring the 404 it sets after the
|
|
55
|
+
* response has already ended; the older webpack dev server calls `next()` instead, and if it ever
|
|
56
|
+
* matters there the upgrade is a `configure` hook instead of `bypass`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function angularProxyFile(): string;
|
|
59
|
+
/**
|
|
60
|
+
* Add the dev-only connect to the browser entry, or ALREADY when it is there.
|
|
61
|
+
*
|
|
62
|
+
* Appended after the bootstrap rather than before it: the app must never wait on Reticle, and a
|
|
63
|
+
* connect that fails (no daemon yet) must not be able to stop Angular from starting.
|
|
64
|
+
*
|
|
65
|
+
* An entry wired by the previous version (`if (isDevMode())`) is rewritten in place, so a re-run is
|
|
66
|
+
* what takes the connect out of an existing app's production bundle.
|
|
67
|
+
*/
|
|
68
|
+
export declare function patchAngularMain(source: string, port: number | undefined, projectId: string | undefined): SourcePatch;
|