@agent-native/core 0.90.3 → 0.90.4
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/bin/agent-native.js +25 -3
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +9 -0
- package/corpus/core/bin/agent-native.js +25 -3
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/index.ts +1 -0
- package/corpus/core/src/agent/engine/registry.ts +63 -2
- package/corpus/core/src/cli/create.ts +35 -0
- package/corpus/core/src/cli/design-connect.ts +248 -0
- package/corpus/core/src/cli/index.ts +84 -7
- package/corpus/core/src/cli/workspacify.ts +34 -0
- package/corpus/core/src/client/history/VersionHistoryPanel.tsx +77 -47
- package/corpus/core/src/client/history/index.ts +3 -0
- package/corpus/core/src/client/history/use-history.ts +28 -0
- package/corpus/core/src/client/index.ts +8 -0
- package/corpus/core/src/client/review/index.ts +5 -0
- package/corpus/core/src/client/review/use-review.ts +40 -2
- package/corpus/core/src/history/actions/create-resource-version.ts +11 -8
- package/corpus/core/src/history/index.ts +0 -1
- package/corpus/core/src/history/registry.ts +24 -12
- package/corpus/core/src/history/store.ts +84 -53
- package/corpus/core/src/review/actions/consume-review-feedback.ts +15 -5
- package/corpus/core/src/review/actions/delete-review-comment.ts +15 -3
- package/corpus/core/src/review/actions/resolve-review-thread.ts +12 -5
- package/corpus/core/src/review/index.ts +0 -5
- package/corpus/core/src/review/registry.ts +24 -12
- package/corpus/core/src/review/store.ts +10 -7
- package/corpus/core/src/server/agent-chat-plugin.ts +6 -0
- package/corpus/core/src/server/credential-provider.ts +13 -3
- package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +11 -11
- package/corpus/templates/assets/actions/generate-image.ts +64 -28
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +91 -74
- package/corpus/templates/assets/server/lib/generation.ts +29 -4
- package/corpus/templates/assets/server/lib/image-processing.ts +131 -0
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +24 -2
- package/corpus/templates/calendar/AGENTS.md +3 -0
- package/corpus/templates/calendar/actions/create-event.ts +8 -34
- package/corpus/templates/calendar/actions/event-action-helpers.ts +52 -0
- package/corpus/templates/calendar/actions/manage-event-draft.ts +7 -34
- package/corpus/templates/calendar/actions/update-event.ts +10 -23
- package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +47 -0
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +23 -0
- package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +59 -2
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +40 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +34 -1
- package/corpus/templates/calendar/app/hooks/use-events.ts +6 -0
- package/corpus/templates/calendar/app/i18n/zh-TW.ts +4 -0
- package/corpus/templates/calendar/app/i18n-data.ts +40 -0
- package/corpus/templates/calendar/changelog/2026-07-07-you-can-mark-event-guests-as-optional-while-inviting-them-or.md +6 -0
- package/corpus/templates/calendar/server/handlers/events.ts +1 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +4 -0
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/calendar/shared/api.ts +2 -0
- package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +29 -3
- package/corpus/templates/clips/actions/generate-workflow.ts +19 -8
- package/corpus/templates/clips/actions/get-clips-ai-prefs.ts +36 -0
- package/corpus/templates/clips/actions/lib/clips-ai-prefs.ts +28 -0
- package/corpus/templates/clips/actions/regenerate-chapters.ts +21 -7
- package/corpus/templates/clips/actions/regenerate-summary.ts +26 -8
- package/corpus/templates/clips/actions/regenerate-title.ts +44 -7
- package/corpus/templates/clips/actions/update-clips-ai-prefs.ts +50 -0
- package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +7 -2
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +25 -10
- package/corpus/templates/clips/app/i18n/ar-SA.ts +28 -6
- package/corpus/templates/clips/app/i18n/de-DE.ts +28 -6
- package/corpus/templates/clips/app/i18n/en-US.ts +29 -6
- package/corpus/templates/clips/app/i18n/es-ES.ts +29 -6
- package/corpus/templates/clips/app/i18n/fr-FR.ts +30 -6
- package/corpus/templates/clips/app/i18n/hi-IN.ts +26 -6
- package/corpus/templates/clips/app/i18n/ja-JP.ts +27 -6
- package/corpus/templates/clips/app/i18n/ko-KR.ts +28 -6
- package/corpus/templates/clips/app/i18n/pt-BR.ts +28 -6
- package/corpus/templates/clips/app/i18n/zh-CN.ts +26 -6
- package/corpus/templates/clips/app/i18n/zh-TW.ts +25 -6
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +73 -0
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -2
- package/corpus/templates/clips/changelog/2026-07-07-ai-tools-can-include-the-full-video-not-just-the-transcript-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-clips-desktop-now-surfaces-meetings-and-dictate-directly-in-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-meeting-notes-now-explain-exactly-how-to-start-granola-style.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-meeting-start-alerts-now-include-separate-start-notes-and-jo.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-recordings-start-more-predictably-and-newly-saved-clips-refr.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/desktop/src/app.tsx +435 -105
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +27 -21
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +23 -22
- package/corpus/templates/clips/desktop/src/styles.css +167 -2
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +3 -7
- package/corpus/templates/clips/desktop/src-tauri/src/tray_meetings.rs +5 -4
- package/corpus/templates/clips/server/routes/_agent-native/clips/user-prefs.put.ts +16 -8
- package/corpus/templates/clips/shared/clips-ai-prefs.ts +81 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +16 -1
- package/corpus/templates/design/actions/add-localhost-screens.ts +1 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +345 -11
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +89 -14
- package/corpus/templates/design/app/components/design/bridge-security.ts +7 -1
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +1 -1
- package/corpus/templates/design/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +41 -0
- package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-uses-live-app-iframes-for-click-to-edit-pr.md +6 -0
- package/dist/agent/engine/index.d.ts +1 -1
- package/dist/agent/engine/index.d.ts.map +1 -1
- package/dist/agent/engine/index.js +1 -1
- package/dist/agent/engine/index.js.map +1 -1
- package/dist/agent/engine/registry.d.ts +14 -0
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +56 -2
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +31 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +186 -0
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/index.js +76 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/workspacify.d.ts.map +1 -1
- package/dist/cli/workspacify.js +30 -0
- package/dist/cli/workspacify.js.map +1 -1
- package/dist/client/history/VersionHistoryPanel.d.ts +1 -1
- package/dist/client/history/VersionHistoryPanel.d.ts.map +1 -1
- package/dist/client/history/VersionHistoryPanel.js +12 -5
- package/dist/client/history/VersionHistoryPanel.js.map +1 -1
- package/dist/client/history/index.d.ts +1 -1
- package/dist/client/history/index.d.ts.map +1 -1
- package/dist/client/history/index.js +1 -1
- package/dist/client/history/index.js.map +1 -1
- package/dist/client/history/use-history.d.ts +12 -0
- package/dist/client/history/use-history.d.ts.map +1 -1
- package/dist/client/history/use-history.js +6 -0
- package/dist/client/history/use-history.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/review/index.d.ts +1 -1
- package/dist/client/review/index.d.ts.map +1 -1
- package/dist/client/review/index.js +1 -1
- package/dist/client/review/index.js.map +1 -1
- package/dist/client/review/use-review.d.ts +23 -0
- package/dist/client/review/use-review.d.ts.map +1 -1
- package/dist/client/review/use-review.js +8 -0
- package/dist/client/review/use-review.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/history/actions/create-resource-version.js +7 -4
- package/dist/history/actions/create-resource-version.js.map +1 -1
- package/dist/history/index.d.ts +1 -1
- package/dist/history/index.d.ts.map +1 -1
- package/dist/history/index.js +1 -1
- package/dist/history/index.js.map +1 -1
- package/dist/history/registry.d.ts.map +1 -1
- package/dist/history/registry.js +14 -12
- package/dist/history/registry.js.map +1 -1
- package/dist/history/store.d.ts.map +1 -1
- package/dist/history/store.js +74 -49
- package/dist/history/store.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/review/actions/consume-review-feedback.d.ts +1 -0
- package/dist/review/actions/consume-review-feedback.js +8 -2
- package/dist/review/actions/consume-review-feedback.js.map +1 -1
- package/dist/review/actions/delete-review-comment.d.ts +2 -1
- package/dist/review/actions/delete-review-comment.js +11 -3
- package/dist/review/actions/delete-review-comment.js.map +1 -1
- package/dist/review/actions/resolve-review-thread.d.ts +2 -1
- package/dist/review/actions/resolve-review-thread.js +5 -2
- package/dist/review/actions/resolve-review-thread.js.map +1 -1
- package/dist/review/index.d.ts +1 -1
- package/dist/review/index.d.ts.map +1 -1
- package/dist/review/index.js +1 -1
- package/dist/review/index.js.map +1 -1
- package/dist/review/registry.d.ts.map +1 -1
- package/dist/review/registry.js +14 -12
- package/dist/review/registry.js.map +1 -1
- package/dist/review/store.d.ts +3 -3
- package/dist/review/store.d.ts.map +1 -1
- package/dist/review/store.js +7 -4
- package/dist/review/store.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +5 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts +2 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +13 -3
- package/dist/server/credential-provider.js.map +1 -1
- package/package.json +1 -1
package/bin/agent-native.js
CHANGED
|
@@ -1,17 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
4
|
+
import { existsSync, statSync } from "node:fs";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
8
|
const binDir = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
const distEntry = join(binDir, "../dist/cli/index.js");
|
|
10
|
+
const sourceEntry = join(binDir, "../src/cli/index.ts");
|
|
11
|
+
const freshnessChecks = [
|
|
12
|
+
[sourceEntry, distEntry],
|
|
13
|
+
[
|
|
14
|
+
join(binDir, "../src/cli/design-connect.ts"),
|
|
15
|
+
join(binDir, "../dist/cli/design-connect.js"),
|
|
16
|
+
],
|
|
17
|
+
];
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
function shouldUseSourceFallback() {
|
|
20
|
+
if (!existsSync(sourceEntry)) return false;
|
|
21
|
+
if (!existsSync(distEntry)) return true;
|
|
22
|
+
try {
|
|
23
|
+
return freshnessChecks.some(
|
|
24
|
+
([source, dist]) =>
|
|
25
|
+
existsSync(source) &&
|
|
26
|
+
existsSync(dist) &&
|
|
27
|
+
statSync(source).mtimeMs > statSync(dist).mtimeMs,
|
|
28
|
+
);
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!shouldUseSourceFallback() && existsSync(distEntry)) {
|
|
12
35
|
await import(pathToFileURL(distEntry).href);
|
|
13
36
|
} else {
|
|
14
|
-
const sourceEntry = join(binDir, "../src/cli/index.ts");
|
|
15
37
|
if (!existsSync(sourceEntry)) {
|
|
16
38
|
console.error(
|
|
17
39
|
"agent-native CLI build output is missing. Run `pnpm --filter @agent-native/core build` and try again.",
|
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.90.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9f396bc: Fail closed for unregistered History/Review resource types, allocate unique version numbers atomically, verify resolve/consume update counts, and prefer server snapshots over client-supplied ones.
|
|
8
|
+
- 9f396bc: Add live visual-edit bridge support for localhost apps so Design can render editable live iframes instead of static HTML snapshots.
|
|
9
|
+
- 9f396bc: Expire sticky provider auth-failure markers, skip rejected deploy env keys during engine auto-detect, and clear failure markers from the legacy save-key path.
|
|
10
|
+
- 9f396bc: Keep React Router build packages installed for scaffolded app builds and report missing build dependencies before spawning the CLI.
|
|
11
|
+
|
|
3
12
|
## 0.90.3
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
|
@@ -1,17 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
4
|
+
import { existsSync, statSync } from "node:fs";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
8
|
const binDir = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
const distEntry = join(binDir, "../dist/cli/index.js");
|
|
10
|
+
const sourceEntry = join(binDir, "../src/cli/index.ts");
|
|
11
|
+
const freshnessChecks = [
|
|
12
|
+
[sourceEntry, distEntry],
|
|
13
|
+
[
|
|
14
|
+
join(binDir, "../src/cli/design-connect.ts"),
|
|
15
|
+
join(binDir, "../dist/cli/design-connect.js"),
|
|
16
|
+
],
|
|
17
|
+
];
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
function shouldUseSourceFallback() {
|
|
20
|
+
if (!existsSync(sourceEntry)) return false;
|
|
21
|
+
if (!existsSync(distEntry)) return true;
|
|
22
|
+
try {
|
|
23
|
+
return freshnessChecks.some(
|
|
24
|
+
([source, dist]) =>
|
|
25
|
+
existsSync(source) &&
|
|
26
|
+
existsSync(dist) &&
|
|
27
|
+
statSync(source).mtimeMs > statSync(dist).mtimeMs,
|
|
28
|
+
);
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!shouldUseSourceFallback() && existsSync(distEntry)) {
|
|
12
35
|
await import(pathToFileURL(distEntry).href);
|
|
13
36
|
} else {
|
|
14
|
-
const sourceEntry = join(binDir, "../src/cli/index.ts");
|
|
15
37
|
if (!existsSync(sourceEntry)) {
|
|
16
38
|
console.error(
|
|
17
39
|
"agent-native CLI build output is missing. Run `pnpm --filter @agent-native/core build` and try again.",
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.90.
|
|
3
|
+
"version": "0.90.4",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -156,6 +156,10 @@ function assertAgentEnginePackageInstalled(entry: AgentEngineEntry): void {
|
|
|
156
156
|
* on the first pass, so an explicit provider key (ANTHROPIC_API_KEY etc.)
|
|
157
157
|
* is picked instead. Builder is still used as the fallback when no other
|
|
158
158
|
* provider key is set.
|
|
159
|
+
*
|
|
160
|
+
* This sync helper is for CLI/status callers that cannot await settings. Prefer
|
|
161
|
+
* {@link detectEngineFromEnvForRequest} at request time so sticky auth-failure
|
|
162
|
+
* markers can skip rejected deploy keys.
|
|
159
163
|
*/
|
|
160
164
|
export function detectEngineFromEnv(): AgentEngineEntry | null {
|
|
161
165
|
const preferByo = /^(1|true)$/i.test(
|
|
@@ -196,6 +200,59 @@ export function detectEngineFromEnv(): AgentEngineEntry | null {
|
|
|
196
200
|
return null;
|
|
197
201
|
}
|
|
198
202
|
|
|
203
|
+
async function envKeyUsableForEntry(
|
|
204
|
+
entry: AgentEngineEntry,
|
|
205
|
+
key: string,
|
|
206
|
+
): Promise<boolean> {
|
|
207
|
+
if (
|
|
208
|
+
!(
|
|
209
|
+
canUseDeployCredentialFallbackForRequest(key) &&
|
|
210
|
+
!!readDeployCredentialEnv(key)
|
|
211
|
+
)
|
|
212
|
+
) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
if (entry.name === "builder") {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
const value = readDeployCredentialEnv(key);
|
|
219
|
+
if (!value) return false;
|
|
220
|
+
return !(await getProviderCredentialAuthFailure({ key, value }));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function hasUsableEnvKeys(entry: AgentEngineEntry): Promise<boolean> {
|
|
224
|
+
if (!isAgentEnginePackageInstalled(entry)) return false;
|
|
225
|
+
if (entry.requiredEnvVars.length === 0) return false;
|
|
226
|
+
for (const key of entry.requiredEnvVars) {
|
|
227
|
+
if (!(await envKeyUsableForEntry(entry, key))) return false;
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Request-aware env auto-detect. Same priority as {@link detectEngineFromEnv},
|
|
234
|
+
* but skips provider keys that currently have an auth-failure marker so a
|
|
235
|
+
* rejected deploy key does not permanently win selection and leave chat stuck
|
|
236
|
+
* on `missing_credentials`.
|
|
237
|
+
*/
|
|
238
|
+
export async function detectEngineFromEnvForRequest(): Promise<AgentEngineEntry | null> {
|
|
239
|
+
const preferByo = /^(1|true)$/i.test(
|
|
240
|
+
process.env.AGENT_ENGINE_PREFER_BYO_KEY ?? "",
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
if (preferByo) {
|
|
244
|
+
for (const entry of _registry.values()) {
|
|
245
|
+
if (entry.name === "builder") continue;
|
|
246
|
+
if (await hasUsableEnvKeys(entry)) return entry;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
for (const entry of _registry.values()) {
|
|
251
|
+
if (await hasUsableEnvKeys(entry)) return entry;
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
199
256
|
function shouldTraceEngineDetection(): boolean {
|
|
200
257
|
return /^(1|true)$/i.test(
|
|
201
258
|
process.env.AGENT_NATIVE_DEBUG_AGENT_ENGINE_DETECT ??
|
|
@@ -399,6 +456,9 @@ async function engineCreateConfigForEntry(
|
|
|
399
456
|
* AND an API key for it is reachable via the engine's required env vars.
|
|
400
457
|
* Inline keys on the global settings row are ignored; see
|
|
401
458
|
* `isAgentEngineSettingConfigured`.
|
|
459
|
+
*
|
|
460
|
+
* Sync helper for CLI/status. Prefer {@link isStoredEngineUsableForRequest}
|
|
461
|
+
* so sticky auth-failure markers are respected.
|
|
402
462
|
*/
|
|
403
463
|
export function isStoredEngineUsable(
|
|
404
464
|
stored: unknown,
|
|
@@ -618,8 +678,9 @@ export async function resolveEngine(
|
|
|
618
678
|
}
|
|
619
679
|
|
|
620
680
|
// 8. Auto-detect from any provider env var — so just dropping a key in
|
|
621
|
-
// .env works without also setting AGENT_ENGINE.
|
|
622
|
-
|
|
681
|
+
// .env works without also setting AGENT_ENGINE. Skip keys with active
|
|
682
|
+
// auth-failure markers so a rejected deploy key cannot permanently win.
|
|
683
|
+
const detected = await detectEngineFromEnvForRequest();
|
|
623
684
|
if (detected) {
|
|
624
685
|
return detected.create(await engineCreateConfigForEntry(detected, apiKey));
|
|
625
686
|
}
|
|
@@ -27,6 +27,12 @@ const STANDALONE_EXACT_DEPENDENCY_OVERRIDES: Record<string, string> = {
|
|
|
27
27
|
"@react-router/fs-routes": "8.1.0",
|
|
28
28
|
"react-router": "8.1.0",
|
|
29
29
|
};
|
|
30
|
+
const REACT_ROUTER_BUILD_DEPENDENCIES = [
|
|
31
|
+
"@react-router/dev",
|
|
32
|
+
"@react-router/fs-routes",
|
|
33
|
+
"react-router",
|
|
34
|
+
"vite",
|
|
35
|
+
] as const;
|
|
30
36
|
const SENTRY_MINIMUM_RELEASE_AGE_EXCLUDES = ['"@sentry/*"'];
|
|
31
37
|
const FIRST_PARTY_TARBALL_SYMLINK_EXCLUDES = [
|
|
32
38
|
"*/CLAUDE.md",
|
|
@@ -1045,6 +1051,7 @@ function postProcessStandalone(
|
|
|
1045
1051
|
// under pnpm 10+ without prompting for `pnpm approve-builds`.
|
|
1046
1052
|
pkg.dependencies = pkg.dependencies ?? {};
|
|
1047
1053
|
pkg.dependencies.postgres ??= POSTGRES_DEPENDENCY_VERSION;
|
|
1054
|
+
ensureReactRouterBuildDependencies(pkg);
|
|
1048
1055
|
|
|
1049
1056
|
const requiredBuilt = ["better-sqlite3", "esbuild", "node-pty"];
|
|
1050
1057
|
if (!pkg.pnpm || typeof pkg.pnpm !== "object") {
|
|
@@ -1105,6 +1112,34 @@ function postProcessStandalone(
|
|
|
1105
1112
|
setupAgentSymlinks(targetDir);
|
|
1106
1113
|
}
|
|
1107
1114
|
|
|
1115
|
+
function ensureReactRouterBuildDependencies(pkg: Record<string, any>): void {
|
|
1116
|
+
const allDeps = {
|
|
1117
|
+
...pkg.dependencies,
|
|
1118
|
+
...pkg.devDependencies,
|
|
1119
|
+
...pkg.peerDependencies,
|
|
1120
|
+
};
|
|
1121
|
+
if (
|
|
1122
|
+
!allDeps["@react-router/dev"] &&
|
|
1123
|
+
!allDeps["react-router"] &&
|
|
1124
|
+
!allDeps["@react-router/fs-routes"]
|
|
1125
|
+
) {
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
pkg.dependencies = pkg.dependencies ?? {};
|
|
1130
|
+
for (const key of REACT_ROUTER_BUILD_DEPENDENCIES) {
|
|
1131
|
+
const existing =
|
|
1132
|
+
pkg.dependencies[key] ??
|
|
1133
|
+
pkg.devDependencies?.[key] ??
|
|
1134
|
+
pkg.peerDependencies?.[key];
|
|
1135
|
+
if (!existing) continue;
|
|
1136
|
+
pkg.dependencies[key] =
|
|
1137
|
+
STANDALONE_EXACT_DEPENDENCY_OVERRIDES[key] ?? existing;
|
|
1138
|
+
delete pkg.devDependencies?.[key];
|
|
1139
|
+
delete pkg.peerDependencies?.[key];
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1108
1143
|
function fixStandaloneTsconfig(targetDir: string, templateName?: string): void {
|
|
1109
1144
|
const tsconfigPath = path.join(targetDir, "tsconfig.json");
|
|
1110
1145
|
if (!fs.existsSync(tsconfigPath)) return;
|
|
@@ -507,6 +507,47 @@ function sendJson(
|
|
|
507
507
|
res.end(`${JSON.stringify(body, null, 2)}\n`);
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
+
function sendText(
|
|
511
|
+
res: ServerResponse,
|
|
512
|
+
statusCode: number,
|
|
513
|
+
body: string,
|
|
514
|
+
contentType: string,
|
|
515
|
+
) {
|
|
516
|
+
res.writeHead(statusCode, {
|
|
517
|
+
"content-type": contentType,
|
|
518
|
+
"access-control-allow-origin": "*",
|
|
519
|
+
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
520
|
+
"access-control-allow-headers": "content-type, x-bridge-token",
|
|
521
|
+
"access-control-allow-private-network": "true",
|
|
522
|
+
});
|
|
523
|
+
res.end(body);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function sendBytes(
|
|
527
|
+
res: ServerResponse,
|
|
528
|
+
statusCode: number,
|
|
529
|
+
body: Buffer,
|
|
530
|
+
headers: Headers,
|
|
531
|
+
) {
|
|
532
|
+
const responseHeaders: Record<string, string> = {
|
|
533
|
+
"access-control-allow-origin": "*",
|
|
534
|
+
"access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
|
|
535
|
+
"access-control-allow-headers": "content-type, x-bridge-token",
|
|
536
|
+
"access-control-allow-private-network": "true",
|
|
537
|
+
};
|
|
538
|
+
for (const name of [
|
|
539
|
+
"content-type",
|
|
540
|
+
"cache-control",
|
|
541
|
+
"etag",
|
|
542
|
+
"last-modified",
|
|
543
|
+
]) {
|
|
544
|
+
const value = headers.get(name);
|
|
545
|
+
if (value) responseHeaders[name] = value;
|
|
546
|
+
}
|
|
547
|
+
res.writeHead(statusCode, responseHeaders);
|
|
548
|
+
res.end(body);
|
|
549
|
+
}
|
|
550
|
+
|
|
510
551
|
function sameOrigin(a: string, b: string): boolean {
|
|
511
552
|
try {
|
|
512
553
|
return new URL(a).origin === new URL(b).origin;
|
|
@@ -531,6 +572,23 @@ function resolvePreviewSnapshotUrl(
|
|
|
531
572
|
return parsed.toString();
|
|
532
573
|
}
|
|
533
574
|
|
|
575
|
+
function resolvePreviewProxyUrl(
|
|
576
|
+
devServerUrl: string,
|
|
577
|
+
requestUrl: string | undefined,
|
|
578
|
+
): string {
|
|
579
|
+
const base = normalizeHttpUrl(devServerUrl);
|
|
580
|
+
const parsedRequest = new URL(requestUrl ?? "/", base);
|
|
581
|
+
const parsed = new URL(
|
|
582
|
+
`${parsedRequest.pathname}${parsedRequest.search}`,
|
|
583
|
+
`${base}/`,
|
|
584
|
+
);
|
|
585
|
+
parsed.hash = "";
|
|
586
|
+
if (!sameOrigin(parsed.toString(), base)) {
|
|
587
|
+
throw new Error("Proxy URL must stay on the connected dev server.");
|
|
588
|
+
}
|
|
589
|
+
return parsed.toString();
|
|
590
|
+
}
|
|
591
|
+
|
|
534
592
|
async function fetchPreviewSnapshot(
|
|
535
593
|
devServerUrl: string,
|
|
536
594
|
targetUrl: string,
|
|
@@ -579,6 +637,79 @@ async function fetchPreviewSnapshot(
|
|
|
579
637
|
}
|
|
580
638
|
}
|
|
581
639
|
|
|
640
|
+
async function fetchPreviewProxyResource(
|
|
641
|
+
devServerUrl: string,
|
|
642
|
+
targetUrl: string,
|
|
643
|
+
redirects = 0,
|
|
644
|
+
): Promise<{
|
|
645
|
+
url: string;
|
|
646
|
+
status: number;
|
|
647
|
+
headers: Headers;
|
|
648
|
+
body: Buffer;
|
|
649
|
+
}> {
|
|
650
|
+
if (redirects > 5) {
|
|
651
|
+
throw new Error("Too many redirects while proxying preview resource.");
|
|
652
|
+
}
|
|
653
|
+
const controller = new AbortController();
|
|
654
|
+
const timeout = setTimeout(() => controller.abort(), 10_000);
|
|
655
|
+
try {
|
|
656
|
+
const response = await fetch(targetUrl, {
|
|
657
|
+
method: "GET",
|
|
658
|
+
redirect: "manual",
|
|
659
|
+
signal: controller.signal,
|
|
660
|
+
});
|
|
661
|
+
const location = response.headers.get("location");
|
|
662
|
+
if (location && response.status >= 300 && response.status < 400) {
|
|
663
|
+
const redirected = new URL(location, targetUrl);
|
|
664
|
+
redirected.hash = "";
|
|
665
|
+
if (!sameOrigin(redirected.toString(), devServerUrl)) {
|
|
666
|
+
throw new Error("Proxy redirect left the connected dev server.");
|
|
667
|
+
}
|
|
668
|
+
return fetchPreviewProxyResource(
|
|
669
|
+
devServerUrl,
|
|
670
|
+
redirected.toString(),
|
|
671
|
+
redirects + 1,
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
url: response.url || targetUrl,
|
|
676
|
+
status: response.status,
|
|
677
|
+
headers: response.headers,
|
|
678
|
+
body: Buffer.from(await response.arrayBuffer()),
|
|
679
|
+
};
|
|
680
|
+
} finally {
|
|
681
|
+
clearTimeout(timeout);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function addLiveEditBaseHref(html: string, href: string): string {
|
|
686
|
+
const escapedHref = href.replace(/&/g, "&").replace(/"/g, """);
|
|
687
|
+
const baseTag = `<base href="${escapedHref}">`;
|
|
688
|
+
if (/<base\b/i.test(html)) return html;
|
|
689
|
+
if (/<head\b[^>]*>/i.test(html)) {
|
|
690
|
+
return html.replace(/<head\b[^>]*>/i, (match) => `${match}${baseTag}`);
|
|
691
|
+
}
|
|
692
|
+
if (/<html\b[^>]*>/i.test(html)) {
|
|
693
|
+
return html.replace(
|
|
694
|
+
/<html\b[^>]*>/i,
|
|
695
|
+
(match) => `${match}<head>${baseTag}</head>`,
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
return `<!DOCTYPE html><html><head>${baseTag}<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head><body>${html}</body></html>`;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function injectLiveEditBridge(html: string, targetUrl: string, script: string) {
|
|
702
|
+
const withBase = addLiveEditBaseHref(html, targetUrl);
|
|
703
|
+
if (!script) return withBase;
|
|
704
|
+
if (withBase.includes("</body>")) {
|
|
705
|
+
return withBase.replace("</body>", `${script}</body>`);
|
|
706
|
+
}
|
|
707
|
+
if (withBase.includes("</html>")) {
|
|
708
|
+
return withBase.replace("</html>", `${script}</html>`);
|
|
709
|
+
}
|
|
710
|
+
return `${withBase}${script}`;
|
|
711
|
+
}
|
|
712
|
+
|
|
582
713
|
/** Read the full request body as a UTF-8 string. */
|
|
583
714
|
async function readRequestBody(req: IncomingMessage): Promise<string> {
|
|
584
715
|
return new Promise<string>((resolve, reject) => {
|
|
@@ -1046,6 +1177,7 @@ export async function startDesignConnectBridge(
|
|
|
1046
1177
|
// an unauthenticated caller cannot read it. The server-side grant action
|
|
1047
1178
|
// obtains it out-of-band (via the exported bridge reference).
|
|
1048
1179
|
const bridgeToken = crypto.randomBytes(32).toString("hex");
|
|
1180
|
+
let liveEditBridgeScript = "";
|
|
1049
1181
|
|
|
1050
1182
|
const server = http.createServer(
|
|
1051
1183
|
(req: IncomingMessage, res: ServerResponse) => {
|
|
@@ -1077,6 +1209,95 @@ export async function startDesignConnectBridge(
|
|
|
1077
1209
|
sendJson(res, 200, { ok: true, source: manifest.source });
|
|
1078
1210
|
return;
|
|
1079
1211
|
}
|
|
1212
|
+
if (pathname === "/live-edit-bridge") {
|
|
1213
|
+
if (req.method !== "POST") {
|
|
1214
|
+
sendJson(res, 405, { ok: false, error: "method not allowed" });
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
const tokenHeader = req.headers["x-bridge-token"];
|
|
1218
|
+
const providedToken =
|
|
1219
|
+
typeof tokenHeader === "string" ? tokenHeader : "";
|
|
1220
|
+
let tokenValid = false;
|
|
1221
|
+
try {
|
|
1222
|
+
tokenValid =
|
|
1223
|
+
providedToken.length === bridgeToken.length &&
|
|
1224
|
+
crypto.timingSafeEqual(
|
|
1225
|
+
Buffer.from(providedToken, "utf8"),
|
|
1226
|
+
Buffer.from(bridgeToken, "utf8"),
|
|
1227
|
+
);
|
|
1228
|
+
} catch {
|
|
1229
|
+
tokenValid = false;
|
|
1230
|
+
}
|
|
1231
|
+
if (!tokenValid) {
|
|
1232
|
+
sendJson(res, 401, {
|
|
1233
|
+
ok: false,
|
|
1234
|
+
error: "invalid or missing bridge token",
|
|
1235
|
+
});
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
void (async () => {
|
|
1239
|
+
try {
|
|
1240
|
+
const raw = await readRequestBody(req);
|
|
1241
|
+
const body = JSON.parse(raw) as Record<string, unknown>;
|
|
1242
|
+
const script =
|
|
1243
|
+
typeof body["script"] === "string" ? body["script"] : "";
|
|
1244
|
+
if (!script.includes("agent-native:editor-chrome-ready")) {
|
|
1245
|
+
sendJson(res, 400, {
|
|
1246
|
+
ok: false,
|
|
1247
|
+
error: "script must install the Agent Native editor bridge",
|
|
1248
|
+
});
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
liveEditBridgeScript = script;
|
|
1252
|
+
sendJson(res, 200, { ok: true });
|
|
1253
|
+
} catch (err: unknown) {
|
|
1254
|
+
sendJson(res, 400, {
|
|
1255
|
+
ok: false,
|
|
1256
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
})();
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
if (pathname === "/live-edit") {
|
|
1263
|
+
if (req.method !== "GET") {
|
|
1264
|
+
sendJson(res, 405, { ok: false, error: "method not allowed" });
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
void (async () => {
|
|
1268
|
+
try {
|
|
1269
|
+
const requestUrl = new URL(req.url ?? "/", manifest.bridgeUrl);
|
|
1270
|
+
const targetUrl = resolvePreviewSnapshotUrl(
|
|
1271
|
+
manifest.devServerUrl,
|
|
1272
|
+
requestUrl.searchParams.get("url") ??
|
|
1273
|
+
requestUrl.searchParams.get("path"),
|
|
1274
|
+
);
|
|
1275
|
+
const snapshot = await fetchPreviewSnapshot(
|
|
1276
|
+
manifest.devServerUrl,
|
|
1277
|
+
targetUrl,
|
|
1278
|
+
);
|
|
1279
|
+
const html = injectLiveEditBridge(
|
|
1280
|
+
snapshot.html,
|
|
1281
|
+
new URL("/", manifest.bridgeUrl).toString(),
|
|
1282
|
+
liveEditBridgeScript,
|
|
1283
|
+
);
|
|
1284
|
+
sendText(
|
|
1285
|
+
res,
|
|
1286
|
+
snapshot.status >= 400 ? snapshot.status : 200,
|
|
1287
|
+
html,
|
|
1288
|
+
snapshot.contentType.includes("html")
|
|
1289
|
+
? snapshot.contentType
|
|
1290
|
+
: "text/html; charset=utf-8",
|
|
1291
|
+
);
|
|
1292
|
+
} catch (err: unknown) {
|
|
1293
|
+
sendJson(res, 400, {
|
|
1294
|
+
ok: false,
|
|
1295
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
})();
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1080
1301
|
if (pathname === "/snapshot") {
|
|
1081
1302
|
if (req.method !== "GET") {
|
|
1082
1303
|
sendJson(res, 405, { ok: false, error: "method not allowed" });
|
|
@@ -1356,6 +1577,33 @@ export async function startDesignConnectBridge(
|
|
|
1356
1577
|
return;
|
|
1357
1578
|
}
|
|
1358
1579
|
|
|
1580
|
+
if (req.method === "GET" || req.method === "HEAD") {
|
|
1581
|
+
void (async () => {
|
|
1582
|
+
try {
|
|
1583
|
+
const targetUrl = resolvePreviewProxyUrl(
|
|
1584
|
+
manifest.devServerUrl,
|
|
1585
|
+
req.url,
|
|
1586
|
+
);
|
|
1587
|
+
const proxied = await fetchPreviewProxyResource(
|
|
1588
|
+
manifest.devServerUrl,
|
|
1589
|
+
targetUrl,
|
|
1590
|
+
);
|
|
1591
|
+
sendBytes(
|
|
1592
|
+
res,
|
|
1593
|
+
proxied.status >= 400 ? proxied.status : 200,
|
|
1594
|
+
req.method === "HEAD" ? Buffer.alloc(0) : proxied.body,
|
|
1595
|
+
proxied.headers,
|
|
1596
|
+
);
|
|
1597
|
+
} catch (err: unknown) {
|
|
1598
|
+
sendJson(res, 400, {
|
|
1599
|
+
ok: false,
|
|
1600
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
})();
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1359
1607
|
sendJson(res, 404, { ok: false, error: "not found" });
|
|
1360
1608
|
},
|
|
1361
1609
|
);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { execSync, spawn } from "child_process";
|
|
4
4
|
import fs from "fs";
|
|
5
|
+
import { createRequire } from "module";
|
|
5
6
|
import path from "path";
|
|
6
7
|
import { fileURLToPath } from "url";
|
|
7
8
|
|
|
@@ -253,11 +254,20 @@ function handleScaffoldImportError(err: any): never {
|
|
|
253
254
|
throw err;
|
|
254
255
|
}
|
|
255
256
|
|
|
257
|
+
function findBinUpwards(binName: string): string | undefined {
|
|
258
|
+
let dir = process.cwd();
|
|
259
|
+
for (let i = 0; i < 20; i++) {
|
|
260
|
+
const candidate = path.join(dir, "node_modules", ".bin", binName);
|
|
261
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
262
|
+
const parent = path.dirname(dir);
|
|
263
|
+
if (parent === dir) break;
|
|
264
|
+
dir = parent;
|
|
265
|
+
}
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
|
|
256
269
|
function findViteBin(): string {
|
|
257
|
-
|
|
258
|
-
const localVite = path.resolve("node_modules/.bin/vite");
|
|
259
|
-
if (fs.existsSync(localVite)) return localVite;
|
|
260
|
-
return "vite"; // fallback to PATH
|
|
270
|
+
return findBinUpwards("vite") ?? "vite";
|
|
261
271
|
}
|
|
262
272
|
|
|
263
273
|
function findTsxBin(): string {
|
|
@@ -277,9 +287,7 @@ function findTypeScriptCompilerBin(): string {
|
|
|
277
287
|
}
|
|
278
288
|
|
|
279
289
|
function findReactRouterBin(): string {
|
|
280
|
-
|
|
281
|
-
if (fs.existsSync(localBin)) return localBin;
|
|
282
|
-
return "react-router";
|
|
290
|
+
return findBinUpwards("react-router") ?? "react-router";
|
|
283
291
|
}
|
|
284
292
|
|
|
285
293
|
/** Check if the project uses React Router framework mode (has react-router.config.ts) */
|
|
@@ -290,6 +298,73 @@ function isReactRouterFramework(): boolean {
|
|
|
290
298
|
);
|
|
291
299
|
}
|
|
292
300
|
|
|
301
|
+
function canResolveFromProject(specifier: string): boolean {
|
|
302
|
+
try {
|
|
303
|
+
const requireFromProject = createRequire(path.resolve("package.json"));
|
|
304
|
+
requireFromProject.resolve(specifier);
|
|
305
|
+
return true;
|
|
306
|
+
} catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function projectUsesFsRoutes(): boolean {
|
|
312
|
+
for (const file of [
|
|
313
|
+
path.resolve("app/routes.ts"),
|
|
314
|
+
path.resolve("app/routes.tsx"),
|
|
315
|
+
path.resolve("routes.ts"),
|
|
316
|
+
path.resolve("routes.tsx"),
|
|
317
|
+
]) {
|
|
318
|
+
try {
|
|
319
|
+
if (
|
|
320
|
+
fs.existsSync(file) &&
|
|
321
|
+
fs.readFileSync(file, "utf-8").includes("@react-router/fs-routes")
|
|
322
|
+
) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
} catch {}
|
|
326
|
+
}
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function validateReactRouterBuildDependencies(): void {
|
|
331
|
+
const required: Array<[packageName: string, specifier: string]> = [
|
|
332
|
+
["react-router", "react-router"],
|
|
333
|
+
["@react-router/dev", "@react-router/dev/vite"],
|
|
334
|
+
["vite", "vite"],
|
|
335
|
+
];
|
|
336
|
+
if (projectUsesFsRoutes()) {
|
|
337
|
+
required.push(["@react-router/fs-routes", "@react-router/fs-routes"]);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const missing = required
|
|
341
|
+
.filter(([, specifier]) => !canResolveFromProject(specifier))
|
|
342
|
+
.map(([packageName]) => packageName);
|
|
343
|
+
|
|
344
|
+
if (missing.length === 0) return;
|
|
345
|
+
|
|
346
|
+
const packageManager = fs.existsSync(path.resolve("pnpm-lock.yaml"))
|
|
347
|
+
? "pnpm"
|
|
348
|
+
: fs.existsSync(path.resolve("yarn.lock"))
|
|
349
|
+
? "yarn"
|
|
350
|
+
: "npm";
|
|
351
|
+
const installCommand =
|
|
352
|
+
packageManager === "pnpm"
|
|
353
|
+
? `pnpm add ${missing.join(" ")}`
|
|
354
|
+
: packageManager === "yarn"
|
|
355
|
+
? `yarn add ${missing.join(" ")}`
|
|
356
|
+
: `npm install ${missing.join(" ")}`;
|
|
357
|
+
|
|
358
|
+
console.error(
|
|
359
|
+
[
|
|
360
|
+
"React Router framework mode requires build packages that are not installed from this app root.",
|
|
361
|
+
`Missing: ${missing.join(", ")}`,
|
|
362
|
+
`Install them with: ${installCommand}`,
|
|
363
|
+
].join("\n"),
|
|
364
|
+
);
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
|
|
293
368
|
function isWorkspaceRoot(): boolean {
|
|
294
369
|
const pkgPath = path.resolve("package.json");
|
|
295
370
|
if (!fs.existsSync(pkgPath)) return false;
|
|
@@ -488,6 +563,7 @@ switch (command) {
|
|
|
488
563
|
// continuation only runs on success.
|
|
489
564
|
(async () => {
|
|
490
565
|
if (isReactRouterFramework()) {
|
|
566
|
+
validateReactRouterBuildDependencies();
|
|
491
567
|
const rr = findReactRouterBin();
|
|
492
568
|
console.log("Building (React Router framework mode)...");
|
|
493
569
|
await runBuildStep(rr, ["build"], { label: "react-router-build" });
|
|
@@ -592,6 +668,7 @@ switch (command) {
|
|
|
592
668
|
// Run TypeScript type checking
|
|
593
669
|
// React Router framework mode generates route types first
|
|
594
670
|
if (isReactRouterFramework()) {
|
|
671
|
+
validateReactRouterBuildDependencies();
|
|
595
672
|
const rr = findReactRouterBin();
|
|
596
673
|
try {
|
|
597
674
|
execSync(`${rr} typegen`, { stdio: "inherit" });
|