@agent-native/core 0.168.0 → 0.168.2
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/netlify.toml +4 -4
- package/corpus/templates/assets/netlify.toml +4 -1
- package/corpus/templates/brain/netlify.toml +4 -1
- package/corpus/templates/calendar/netlify.toml +4 -1
- package/corpus/templates/chat/netlify.toml +4 -1
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +32 -0
- package/corpus/templates/clips/netlify.toml +4 -1
- package/corpus/templates/content/netlify.toml +4 -1
- package/corpus/templates/crm/netlify.toml +6 -1
- package/corpus/templates/design/netlify.toml +4 -1
- package/corpus/templates/dispatch/netlify.toml +4 -1
- package/corpus/templates/factory/netlify.toml +4 -1
- package/corpus/templates/forms/netlify.toml +4 -1
- package/corpus/templates/macros/netlify.toml +3 -3
- package/corpus/templates/mail/actions/view-screen.ts +80 -8
- package/corpus/templates/mail/app/components/email/EmailList.tsx +3 -1
- package/corpus/templates/mail/app/components/email/EmailListItem.tsx +1 -35
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +16 -8
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +146 -87
- package/corpus/templates/mail/app/global.css +21 -0
- package/corpus/templates/mail/app/hooks/use-emails.ts +6 -0
- package/corpus/templates/mail/app/hooks/use-navigation-state.ts +2 -0
- package/corpus/templates/mail/app/lib/inbox-tabs.ts +5 -0
- package/corpus/templates/mail/app/lib/label-colors.ts +47 -0
- package/corpus/templates/mail/app/pages/InboxPage.tsx +25 -8
- package/corpus/templates/mail/netlify.toml +4 -1
- package/corpus/templates/mail/server/handlers/emails.ts +27 -2
- package/corpus/templates/plan/netlify.toml +4 -1
- package/corpus/templates/slides/netlify.toml +4 -1
- package/corpus/templates/tasks/netlify.toml +6 -1
- package/dist/app-config/migration.d.ts +5 -0
- package/dist/app-config/migration.js +11 -0
- package/dist/app-config/schema.d.ts +4 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/client/AgentPanel.d.ts +3 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/agent-page/AgentTabsPage.d.ts +4 -1
- package/dist/client/agent-page/AgentTabsPage.js +2 -2
- package/dist/client/integrations/IntegrationConnectionChoice.d.ts +4 -1
- package/dist/client/integrations/IntegrationConnectionChoice.js +6 -2
- package/dist/client/onboarding/FirstRunOnboarding.js +9 -5
- package/dist/client/resources/McpIntegrationDialog.d.ts +3 -1
- package/dist/client/resources/McpIntegrationDialog.js +181 -112
- package/dist/client/resources/mcp-integration-catalog.d.ts +1 -0
- package/dist/client/resources/mcp-integration-catalog.js +5 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/migrations.js +10 -2
- package/dist/deploy/build.js +6 -0
- package/dist/deploy/netlify-static-headers.d.ts +3 -0
- package/dist/deploy/netlify-static-headers.js +60 -0
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +9 -0
- package/dist/localization/default-messages.js +9 -0
- package/dist/observability/routes.d.ts +6 -6
- package/dist/provider-api/actions/custom-provider-registration.d.ts +16 -16
- package/dist/provider-api/actions/provider-api.d.ts +17 -17
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/chat/netlify.toml +4 -1
- package/dist/vite/client.js +2 -0
- package/package.json +2 -1
- package/src/templates/chat/netlify.toml +4 -1
|
@@ -8,9 +8,9 @@ import { useT } from "../i18n.js";
|
|
|
8
8
|
import { IntegrationConnectionChoice } from "../integrations/IntegrationConnectionChoice.js";
|
|
9
9
|
import { IntegrationGrid } from "../integrations/IntegrationGrid.js";
|
|
10
10
|
import { cn } from "../utils.js";
|
|
11
|
-
import { buildMcpOAuthStartUrl, createMcpIntegrationFormDefaults, filterMcpIntegrations, getMcpIntegrationApiFallback, getDefaultMcpIntegrations, isCustomMcpIntegrationEnabled, navigateToMcpOAuthStart, resolveMcpIntegrationScope, shouldOfferMcpIntegrationOrganizationScope, shouldOfferMcpOrganizationScope, } from "./mcp-integration-catalog.js";
|
|
11
|
+
import { buildMcpOAuthStartUrl, createMcpIntegrationFormDefaults, filterMcpIntegrations, getMcpIntegrationApiFallback, getDefaultMcpIntegrations, isCustomMcpIntegrationEnabled, navigateToMcpOAuthStart, resolveMcpIntegrationScope, shouldOfferMcpIntegrationOrganizationScope, shouldOfferMcpOrganizationScope, supportsMcpIntegrationOrganizationScope, } from "./mcp-integration-catalog.js";
|
|
12
12
|
import { McpIntegrationLogo } from "./McpIntegrationLogo.js";
|
|
13
|
-
import { formatMcpServerError, getMcpUrlValidationError, useMcpServersApi, useMcpServers, } from "./use-mcp-servers.js";
|
|
13
|
+
import { formatMcpServerError, formatMcpServersLoadError, getMcpUrlValidationError, useMcpServersApi, useMcpServers, } from "./use-mcp-servers.js";
|
|
14
14
|
function parseHeaderLines(text) {
|
|
15
15
|
const out = {};
|
|
16
16
|
for (const line of text.split(/\r?\n/)) {
|
|
@@ -49,7 +49,7 @@ function resolveIntegrationScope(integration, defaultScope, hasOrg, canCreateOrg
|
|
|
49
49
|
(integration.supportsOrganizationScope === true &&
|
|
50
50
|
integration.managedOAuth !== true));
|
|
51
51
|
}
|
|
52
|
-
export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId = null, connectIntegrationId = null, quickConnectIntegrationId = null, defaultScope, canCreateOrgMcp, hasOrg, onCreateMcpServer, onCreated, integrations, }) {
|
|
52
|
+
export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId = null, connectIntegrationId = null, quickConnectIntegrationId = null, defaultScope, canCreateOrgMcp, hasOrg, onCreateMcpServer, onOAuthStart, oauthReturnPath, onCreated, integrations, }) {
|
|
53
53
|
const t = useT();
|
|
54
54
|
const [mode, setMode] = useState("catalog");
|
|
55
55
|
const [query, setQuery] = useState("");
|
|
@@ -89,11 +89,21 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
|
|
|
89
89
|
useEffect(() => {
|
|
90
90
|
if (!open)
|
|
91
91
|
return;
|
|
92
|
+
if (initialIntegrationId && !mcpServersQuery.isSuccess)
|
|
93
|
+
return;
|
|
92
94
|
const initialIntegration = initialIntegrationId
|
|
93
95
|
? defaultIntegrations.find((integration) => integration.id === initialIntegrationId)
|
|
94
96
|
: null;
|
|
95
97
|
const initialDefaults = createMcpIntegrationFormDefaults(initialIntegration);
|
|
96
|
-
|
|
98
|
+
const initialNeedsScopeChoice = Boolean(initialIntegration &&
|
|
99
|
+
hasOrg &&
|
|
100
|
+
requiresMcpIntegrationSetup(initialIntegration) &&
|
|
101
|
+
supportsMcpIntegrationOrganizationScope(initialIntegration));
|
|
102
|
+
setMode(initialNeedsScopeChoice
|
|
103
|
+
? "choice"
|
|
104
|
+
: initialIntegration || !showCatalog
|
|
105
|
+
? "form"
|
|
106
|
+
: "catalog");
|
|
97
107
|
setQuery("");
|
|
98
108
|
setSelected(initialIntegration ?? null);
|
|
99
109
|
setScope(resolveIntegrationScope(initialIntegration, defaultScope, hasOrg, canCreateOrgMcp));
|
|
@@ -114,6 +124,7 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
|
|
|
114
124
|
open,
|
|
115
125
|
safeDefaultScope,
|
|
116
126
|
showCatalog,
|
|
127
|
+
mcpServersQuery.isSuccess,
|
|
117
128
|
]);
|
|
118
129
|
useEffect(() => {
|
|
119
130
|
if (open && mode === "form") {
|
|
@@ -147,18 +158,31 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
|
|
|
147
158
|
return;
|
|
148
159
|
}
|
|
149
160
|
setBusy(true);
|
|
150
|
-
const returnUrl =
|
|
151
|
-
?
|
|
152
|
-
: window
|
|
153
|
-
|
|
154
|
-
window.location.
|
|
155
|
-
|
|
161
|
+
const returnUrl = oauthReturnPath?.startsWith("/")
|
|
162
|
+
? oauthReturnPath
|
|
163
|
+
: typeof window === "undefined"
|
|
164
|
+
? "/"
|
|
165
|
+
: window.location.pathname +
|
|
166
|
+
window.location.search +
|
|
167
|
+
window.location.hash;
|
|
168
|
+
const oauthUrl = agentNativePath(buildMcpOAuthStartUrl({
|
|
156
169
|
name: args.name,
|
|
157
170
|
url: args.url,
|
|
158
171
|
description: args.description,
|
|
159
172
|
scope: options?.scope ?? scope,
|
|
160
173
|
returnUrl,
|
|
161
|
-
}))
|
|
174
|
+
}));
|
|
175
|
+
if (!onOAuthStart) {
|
|
176
|
+
navigateToMcpOAuthStart(oauthUrl);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
void Promise.resolve()
|
|
180
|
+
.then(() => onOAuthStart(oauthUrl))
|
|
181
|
+
.then(() => onOpenChange(false))
|
|
182
|
+
.catch((cause) => {
|
|
183
|
+
setBusy(false);
|
|
184
|
+
setError(formatMcpServerError(cause));
|
|
185
|
+
});
|
|
162
186
|
};
|
|
163
187
|
const connectWithOAuth = (integration, options) => beginOAuth({
|
|
164
188
|
name: integration.name,
|
|
@@ -218,6 +242,11 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
|
|
|
218
242
|
});
|
|
219
243
|
};
|
|
220
244
|
const quickConnect = (integration) => {
|
|
245
|
+
if (hasOrg) {
|
|
246
|
+
setSelected(integration);
|
|
247
|
+
setMode("choice");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
221
250
|
if (requiresMcpIntegrationSetup(integration)) {
|
|
222
251
|
openForm(integration);
|
|
223
252
|
return;
|
|
@@ -240,6 +269,13 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
|
|
|
240
269
|
});
|
|
241
270
|
};
|
|
242
271
|
const selectCatalogConnection = (integration) => {
|
|
272
|
+
if (!mcpServersQuery.isSuccess)
|
|
273
|
+
return;
|
|
274
|
+
if (hasOrg) {
|
|
275
|
+
setSelected(integration);
|
|
276
|
+
setMode("choice");
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
243
279
|
if (requiresMcpIntegrationSetup(integration)) {
|
|
244
280
|
const apiFallback = getMcpIntegrationApiFallback(integration);
|
|
245
281
|
if (apiFallback) {
|
|
@@ -250,11 +286,6 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
|
|
|
250
286
|
}
|
|
251
287
|
return;
|
|
252
288
|
}
|
|
253
|
-
if (shouldOfferMcpIntegrationOrganizationScope(integration, hasOrg, canCreateOrgMcp)) {
|
|
254
|
-
setSelected(integration);
|
|
255
|
-
setMode("choice");
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
289
|
quickConnect(integration);
|
|
259
290
|
};
|
|
260
291
|
quickConnectRef.current = quickConnect;
|
|
@@ -267,15 +298,30 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
|
|
|
267
298
|
quickConnectAttemptedRef.current === quickConnectIntegrationId) {
|
|
268
299
|
return;
|
|
269
300
|
}
|
|
301
|
+
if (mcpServersQuery.isError)
|
|
302
|
+
return;
|
|
303
|
+
if (!mcpServersQuery.isSuccess)
|
|
304
|
+
return;
|
|
270
305
|
const integration = defaultIntegrations.find((candidate) => candidate.id === quickConnectIntegrationId);
|
|
271
306
|
if (!integration)
|
|
272
307
|
return;
|
|
273
308
|
quickConnectAttemptedRef.current = quickConnectIntegrationId;
|
|
274
309
|
quickConnectRef.current?.(integration);
|
|
275
|
-
}, [
|
|
310
|
+
}, [
|
|
311
|
+
defaultIntegrations,
|
|
312
|
+
hasOrg,
|
|
313
|
+
mcpServersQuery.isError,
|
|
314
|
+
mcpServersQuery.isSuccess,
|
|
315
|
+
open,
|
|
316
|
+
quickConnectIntegrationId,
|
|
317
|
+
]);
|
|
276
318
|
useEffect(() => {
|
|
277
319
|
if (!open || !connectIntegrationId)
|
|
278
320
|
return;
|
|
321
|
+
if (mcpServersQuery.isError)
|
|
322
|
+
return;
|
|
323
|
+
if (!mcpServersQuery.isSuccess)
|
|
324
|
+
return;
|
|
279
325
|
const integration = defaultIntegrations.find((candidate) => candidate.id === connectIntegrationId);
|
|
280
326
|
if (!integration)
|
|
281
327
|
return;
|
|
@@ -283,25 +329,36 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
|
|
|
283
329
|
if (quickConnectAttemptedRef.current === attemptKey)
|
|
284
330
|
return;
|
|
285
331
|
quickConnectAttemptedRef.current = attemptKey;
|
|
332
|
+
if (hasOrg) {
|
|
333
|
+
setSelected(integration);
|
|
334
|
+
setMode("choice");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
286
337
|
if (requiresMcpIntegrationSetup(integration)) {
|
|
287
338
|
openForm(integration);
|
|
288
339
|
return;
|
|
289
340
|
}
|
|
290
|
-
|
|
291
|
-
if (!showWorkspaceChoice) {
|
|
292
|
-
quickConnectRef.current?.(integration);
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
setMode("choice");
|
|
296
|
-
}
|
|
341
|
+
quickConnectRef.current?.(integration);
|
|
297
342
|
}, [
|
|
298
343
|
canCreateOrgMcp,
|
|
299
344
|
connectIntegrationId,
|
|
300
345
|
defaultIntegrations,
|
|
301
346
|
hasOrg,
|
|
347
|
+
mcpServersQuery.isError,
|
|
348
|
+
mcpServersQuery.isSuccess,
|
|
302
349
|
open,
|
|
303
350
|
]);
|
|
304
351
|
const connectPersonal = (integration) => {
|
|
352
|
+
if (requiresMcpIntegrationSetup(integration)) {
|
|
353
|
+
const apiFallback = getMcpIntegrationApiFallback(integration);
|
|
354
|
+
if (apiFallback) {
|
|
355
|
+
openAgentSettings(`secrets:${apiFallback.secretKey}`);
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
openForm(integration, { scope: "user" });
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
305
362
|
if (integration.authMode === "oauth") {
|
|
306
363
|
connectWithOAuth(integration, { scope: "user" });
|
|
307
364
|
return;
|
|
@@ -319,6 +376,12 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
|
|
|
319
376
|
openForm(integration, { scope: "user" });
|
|
320
377
|
};
|
|
321
378
|
const connectWorkspace = (integration) => {
|
|
379
|
+
if (!canCreateOrgMcp)
|
|
380
|
+
return;
|
|
381
|
+
if (requiresMcpIntegrationSetup(integration)) {
|
|
382
|
+
openForm(integration, { scope: "org" });
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
322
385
|
if (integration.authMode === "oauth") {
|
|
323
386
|
connectWithOAuth(integration, { scope: "org" });
|
|
324
387
|
return;
|
|
@@ -384,93 +447,99 @@ export function McpIntegrationDialog({ open, onOpenChange, initialIntegrationId
|
|
|
384
447
|
};
|
|
385
448
|
if (!showCatalog && !customIntegrationEnabled)
|
|
386
449
|
return null;
|
|
387
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children:
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
actionLabel: connected
|
|
407
|
-
? "Manage"
|
|
408
|
-
: setupOnly && apiFallback
|
|
409
|
-
? t("mcpIntegrations.useApiToken")
|
|
450
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { "aria-describedby": undefined, className: "inset-0 flex h-[100dvh] max-h-none w-full max-w-none translate-x-0 translate-y-0 flex-col gap-0 overflow-hidden rounded-none p-0", children: [mcpServersQuery.isError ? (_jsxs("div", { role: "alert", className: "mx-7 mt-4 shrink-0 rounded-md border border-destructive/20 bg-destructive/5 px-3 py-2 text-xs text-destructive sm:mx-10", children: [_jsx("p", { children: formatMcpServersLoadError(mcpServersQuery.error) }), _jsx("button", { type: "button", onClick: () => void mcpServersQuery.refetch(), disabled: mcpServersQuery.isFetching, className: "mt-2 font-medium underline underline-offset-2 hover:text-foreground disabled:cursor-wait disabled:opacity-60", children: mcpServersQuery.isFetching
|
|
451
|
+
? t("mcpIntegrations.retrying")
|
|
452
|
+
: t("mcpIntegrations.retry") })] })) : null, mode === "choice" && selected ? (_jsxs(_Fragment, { children: [_jsx(DialogHeader, { className: "sr-only", children: _jsxs(DialogTitle, { children: [t("mcpIntegrations.connect"), " ", selected.name] }) }), _jsx(IntegrationConnectionChoice, { name: selected.name, logo: _jsx(McpIntegrationLogo, { name: selected.name, logoUrl: selected.logoUrl, integrationId: selected.id, className: "size-7 rounded-md", imageClassName: "size-full p-1" }), showWorkspaceOption: supportsMcpIntegrationOrganizationScope(selected), workspaceOptionDisabled: !canCreateOrgMcp, workspaceOptionDisabledReason: !canCreateOrgMcp
|
|
453
|
+
? t("mcpIntegrations.workspaceAdminRequired")
|
|
454
|
+
: undefined, personalOnlyReason: !supportsMcpIntegrationOrganizationScope(selected)
|
|
455
|
+
? t("mcpIntegrations.personalOnlyDescription")
|
|
456
|
+
: undefined, busy: busy, onPersonal: () => connectPersonal(selected), onWorkspace: () => connectWorkspace(selected) })] })) : mode === "catalog" ? (_jsxs(_Fragment, { children: [_jsxs(DialogHeader, { className: "shrink-0 border-b border-border px-7 pb-5 pe-14 pt-7 sm:px-10", children: [_jsx(DialogTitle, { children: t("mcpIntegrations.title") }), _jsx(DialogDescription, { children: t("mcpIntegrations.description", {
|
|
457
|
+
count: defaultIntegrations.length,
|
|
458
|
+
}) })] }), _jsx("div", { className: "shrink-0 px-7 pb-5 sm:px-10", children: _jsxs("div", { className: "mx-auto flex w-full max-w-5xl flex-col gap-3 sm:flex-row", children: [_jsxs("label", { className: "relative min-w-0 flex-1", children: [_jsx(IconSearch, { className: "pointer-events-none absolute start-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx("input", { value: query, onChange: (event) => setQuery(event.target.value), className: "h-9 w-full rounded-md border border-border bg-background pe-3 ps-8 text-[13px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-ring", placeholder: t("mcpIntegrations.searchPlaceholder") })] }), _jsx("button", { type: "button", onClick: () => openForm(null), className: cn("inline-flex h-9 items-center justify-center gap-1.5 rounded-md border border-border bg-background px-3 text-[12px] font-medium text-foreground hover:bg-accent", !customIntegrationEnabled && "hidden"), children: t("mcpIntegrations.addYourOwn") })] }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-7 pb-10 pt-7 sm:px-10", children: _jsxs("div", { className: "mx-auto w-full max-w-5xl", children: [error && (_jsx("div", { className: "mb-3 rounded-md border border-destructive/20 bg-destructive/5 px-3 py-2 text-[12px] leading-relaxed text-destructive", children: error })), !mcpServersQuery.isSuccess && !mcpServersQuery.isError ? (_jsx("div", { role: "status", className: "mb-3 rounded-md border border-border bg-muted/30 px-3 py-2 text-[12px] leading-relaxed text-muted-foreground", children: t("mcpIntegrations.loadingScopeMetadata") })) : null, _jsx(IntegrationGrid, { items: filteredIntegrations.map((integration) => {
|
|
459
|
+
const connected = connectedUrls.has(compareUrl(integration.url));
|
|
460
|
+
const setupOnly = requiresMcpIntegrationSetup(integration);
|
|
461
|
+
const apiFallback = getMcpIntegrationApiFallback(integration);
|
|
462
|
+
return {
|
|
463
|
+
id: integration.id,
|
|
464
|
+
name: integration.name,
|
|
465
|
+
description: t(integration.descriptionKey),
|
|
466
|
+
logo: (_jsx(McpIntegrationLogo, { name: integration.name, logoUrl: integration.logoUrl, integrationId: integration.id, className: "size-7 rounded-md", imageClassName: "size-full p-1" })),
|
|
467
|
+
status: connected
|
|
468
|
+
? t("mcpIntegrations.connected")
|
|
410
469
|
: setupOnly
|
|
411
|
-
? t("mcpIntegrations.
|
|
412
|
-
:
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
})
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
? t("mcpIntegrations.
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
"mcpIntegrations.
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
470
|
+
? t("mcpIntegrations.status.setupRequired")
|
|
471
|
+
: undefined,
|
|
472
|
+
statusClassName: connected
|
|
473
|
+
? "text-emerald-600 dark:text-emerald-400"
|
|
474
|
+
: "text-muted-foreground",
|
|
475
|
+
actionLabel: connected
|
|
476
|
+
? "Manage"
|
|
477
|
+
: setupOnly && apiFallback
|
|
478
|
+
? t("mcpIntegrations.useApiToken")
|
|
479
|
+
: setupOnly
|
|
480
|
+
? t("mcpIntegrations.viewSetup")
|
|
481
|
+
: t("mcpIntegrations.connect"),
|
|
482
|
+
disabled: connected || busy || !mcpServersQuery.isSuccess,
|
|
483
|
+
onAction: () => {
|
|
484
|
+
if (connected) {
|
|
485
|
+
openForm(integration);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
selectCatalogConnection(integration);
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
}), emptyLabel: t("mcpIntegrations.noMatches") })] }) })] })) : (_jsxs(_Fragment, { children: [_jsxs(DialogHeader, { className: "shrink-0 border-b border-border px-7 pb-5 pe-14 pt-7 sm:px-10", children: [_jsxs("button", { type: "button", onClick: () => {
|
|
492
|
+
clearFeedback();
|
|
493
|
+
setMode("catalog");
|
|
494
|
+
}, className: cn("mb-1 inline-flex w-fit items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground", !showCatalog && "hidden"), children: [_jsx(IconArrowLeft, { className: "h-3 w-3 rtl:-scale-x-100" }), t("mcpIntegrations.backToIntegrations")] }), _jsx(DialogTitle, { children: selected
|
|
495
|
+
? selectedRequiresSetup
|
|
496
|
+
? t("mcpIntegrations.setupTitle", {
|
|
497
|
+
name: selected.name,
|
|
498
|
+
})
|
|
499
|
+
: t("mcpIntegrations.configureTitle", {
|
|
500
|
+
name: selected.name,
|
|
501
|
+
})
|
|
502
|
+
: t("mcpIntegrations.customTitle") }), _jsx(DialogDescription, { children: selected
|
|
503
|
+
? selectedRequiresSetup
|
|
504
|
+
? t("mcpIntegrations.providerSetupFormDescription")
|
|
505
|
+
: selected.authMode === "none"
|
|
506
|
+
? t("mcpIntegrations.presetNoAuthDescription")
|
|
507
|
+
: t("mcpIntegrations.presetAuthDescription")
|
|
508
|
+
: t("mcpIntegrations.customDescription") })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-7 py-7 sm:px-10", children: _jsxs("div", { className: "mx-auto max-w-2xl space-y-3", children: [renderScopeSelector(), selected?.setupNoteKey && !selectedRequiresSetup ? (_jsx("div", { className: "rounded-md bg-muted/40 px-3 py-2 text-[11px] leading-relaxed text-muted-foreground", children: t(selected.setupNoteKey) })) : null, selectedRequiresSetup && selected && (_jsxs("div", { className: "mx-auto grid w-full max-w-xl gap-4 py-8", children: [_jsxs("div", { children: [_jsx("p", { className: "text-base font-semibold tracking-[-0.02em] text-foreground", children: t("mcpIntegrations.providerSetupRequired") }), _jsx("p", { className: "mt-1 text-sm leading-6 text-muted-foreground", children: t("mcpIntegrations.providerSetupDescription", {
|
|
509
|
+
name: selected.name,
|
|
510
|
+
}) })] }), selected.setupNoteKey ? (_jsx("p", { className: "text-sm leading-6 text-muted-foreground", children: t(selected.setupNoteKey) })) : null, selected.docsUrl ? (_jsxs("a", { href: selected.docsUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex w-fit items-center gap-1 text-sm font-medium text-foreground underline underline-offset-4 hover:text-muted-foreground", children: [t("mcpIntegrations.viewSetup"), _jsx(IconExternalLink, { className: "size-3.5" })] })) : null] })), !selected && (_jsxs("div", { className: "flex items-center justify-between gap-3 rounded-md bg-muted/30 px-3 py-2 text-[11px]", children: [_jsx("span", { className: "text-muted-foreground", children: customAuthMode === "oauth"
|
|
511
|
+
? t(
|
|
512
|
+
/* i18n-key-ignore */
|
|
513
|
+
"mcpIntegrations.customOAuthDefault", { defaultValue: "Sign in with OAuth" })
|
|
514
|
+
: t(
|
|
515
|
+
/* i18n-key-ignore */
|
|
516
|
+
"mcpIntegrations.customHeadersMode", { defaultValue: "Use an API key" }) }), _jsx("button", { type: "button", onClick: () => {
|
|
517
|
+
setCustomAuthMode((current) => current === "oauth" ? "headers" : "oauth");
|
|
518
|
+
clearFeedback();
|
|
519
|
+
}, className: "font-medium text-foreground underline underline-offset-2 hover:text-muted-foreground", children: customAuthMode === "oauth"
|
|
520
|
+
? t(
|
|
521
|
+
/* i18n-key-ignore */
|
|
522
|
+
"mcpIntegrations.useApiKeyInstead", { defaultValue: "Use an API key instead" })
|
|
523
|
+
: t(
|
|
524
|
+
/* i18n-key-ignore */
|
|
525
|
+
"mcpIntegrations.useOAuthInstead", { defaultValue: "Use OAuth instead" }) })] })), !selectedRequiresSetup && (_jsxs(_Fragment, { children: [selected?.authMode === "oauth" && (_jsx("div", { className: "rounded-md border border-primary/20 bg-primary/5 px-3 py-2 text-[11px] leading-relaxed text-primary", children: t("mcpIntegrations.oauthNotice") })), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[10px] font-medium text-muted-foreground", children: t("mcpIntegrations.serverName") }), _jsx("input", { ref: inputRef, value: name, onChange: (event) => {
|
|
526
|
+
setName(event.target.value);
|
|
527
|
+
clearFeedback();
|
|
528
|
+
}, className: "w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-[13px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-ring", placeholder: t("mcpIntegrations.serverNamePlaceholder") })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[10px] font-medium text-muted-foreground", children: t("mcpIntegrations.url") }), _jsx("input", { value: url, onChange: (event) => {
|
|
529
|
+
setUrl(event.target.value);
|
|
530
|
+
clearFeedback();
|
|
531
|
+
}, className: "w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-[13px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-ring", placeholder: t("mcpIntegrations.urlPlaceholder") })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[10px] font-medium text-muted-foreground", children: t("mcpIntegrations.fieldDescription") }), _jsx("input", { value: description, onChange: (event) => {
|
|
532
|
+
setDescription(event.target.value);
|
|
533
|
+
clearFeedback();
|
|
534
|
+
}, className: "w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-[13px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-ring", placeholder: t("mcpIntegrations.descriptionPlaceholder") })] }), (selected
|
|
535
|
+
? selected.authMode !== "oauth"
|
|
536
|
+
: customAuthMode === "headers") && (_jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[10px] font-medium text-muted-foreground", children: t("mcpIntegrations.headers") }), _jsx("textarea", { value: headersText, onChange: (event) => {
|
|
537
|
+
setHeadersText(event.target.value);
|
|
538
|
+
clearFeedback();
|
|
539
|
+
}, rows: 3, className: "w-full resize-y rounded-md border border-border bg-background px-2.5 py-1.5 text-[12px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-ring", style: {
|
|
540
|
+
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
|
541
|
+
}, placeholder: selected?.headerPlaceholder ??
|
|
542
|
+
t("mcpIntegrations.headersPlaceholder") })] })), selected?.docsUrl && (_jsxs("a", { href: selected.docsUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground underline hover:text-foreground", children: [t("mcpIntegrations.openSetupDocs"), _jsx(IconExternalLink, { className: "h-3 w-3" })] }))] })), testResult && (_jsxs("div", { className: cn("flex items-start gap-1 rounded-md px-3 py-2 text-[11px] leading-snug", testResult.ok
|
|
543
|
+
? "bg-green-500/5 text-green-600 dark:text-green-400"
|
|
544
|
+
: "bg-red-500/5 text-red-600 dark:text-red-400"), children: [testResult.ok && (_jsx(IconCheck, { className: "mt-0.5 h-3 w-3 shrink-0" })), _jsx("span", { className: "min-w-0 break-words", children: testResult.message })] })), error && (_jsx("div", { className: "break-words rounded-md bg-red-500/5 px-3 py-2 text-[11px] leading-snug text-red-600 dark:text-red-400", children: error }))] }) }), _jsxs("div", { className: "flex shrink-0 items-center justify-between gap-2 border-t border-border px-7 py-4", children: [!selectedRequiresSetup && (_jsx("button", { type: "button", onClick: runTest, disabled: !url.trim() || busy, className: "rounded-md border border-border bg-background px-3 py-1.5 text-[12px] font-medium text-foreground hover:bg-accent disabled:pointer-events-none disabled:opacity-40", children: t("mcpIntegrations.test") })), !selected && customAuthMode === "oauth" ? (_jsxs("button", { type: "button", onClick: connectCustomWithOAuth, disabled: !name.trim() || !url.trim() || busy, "aria-busy": busy, className: "rounded-md border border-border bg-background px-3 py-1.5 text-[12px] font-medium text-foreground hover:bg-accent disabled:pointer-events-none disabled:opacity-40", children: [busy && (_jsx(IconLoader2, { className: "me-1.5 inline h-3.5 w-3.5 animate-spin" })), t("mcpIntegrations.connectWithOAuth")] })) : null, selectedRequiresSetup ? (_jsxs("div", { className: "ms-auto flex items-center gap-2", children: [selected?.docsUrl ? (_jsxs("a", { href: selected.docsUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex min-w-[92px] items-center justify-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-[12px] font-medium text-foreground hover:bg-accent", children: [t("mcpIntegrations.viewSetup"), _jsx(IconExternalLink, { className: "h-3 w-3" })] })) : null, selected?.authMode === "oauth" && (_jsxs("button", { type: "button", onClick: () => connectWithOAuth(selected), disabled: busy, "aria-busy": busy, className: "inline-flex min-w-[132px] items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-40", children: [busy && (_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" })), t("mcpIntegrations.continueToConnect")] }))] })) : selected?.authMode === "oauth" ? (_jsxs("button", { type: "button", onClick: () => connectWithOAuth(selected), disabled: !name.trim() || !url.trim() || busy, "aria-busy": busy, className: "inline-flex min-w-[92px] items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-40", children: [busy && _jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), t("mcpIntegrations.connectWithOAuth")] })) : !selected && customAuthMode === "oauth" ? null : (_jsxs("button", { type: "button", onClick: submitForm, disabled: !name.trim() || !url.trim() || busy, className: "inline-flex min-w-[92px] items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-40", children: [busy && _jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), t("mcpIntegrations.connect")] }))] })] }))] }) }));
|
|
476
545
|
}
|
|
@@ -71,6 +71,7 @@ export declare function buildMcpOAuthStartUrl({ name, url, description, scope, r
|
|
|
71
71
|
export declare function navigateToMcpOAuthStart(url: string): void;
|
|
72
72
|
export declare function resolveMcpIntegrationScope(defaultScope: "user" | "org", hasOrg: boolean, canCreateOrgMcp: boolean, supportsOrganizationScope?: boolean): "user" | "org";
|
|
73
73
|
export declare function shouldOfferMcpOrganizationScope(hasOrg: boolean, canCreateOrgMcp: boolean): boolean;
|
|
74
|
+
export declare function supportsMcpIntegrationOrganizationScope(integration: DefaultMcpIntegration): boolean;
|
|
74
75
|
export declare function shouldOfferMcpIntegrationOrganizationScope(integration: DefaultMcpIntegration, hasOrg: boolean, canCreateOrgMcp: boolean): boolean;
|
|
75
76
|
export declare function filterMcpIntegrations(query: string, integrations?: DefaultMcpIntegration[]): DefaultMcpIntegration[];
|
|
76
77
|
export declare function findMcpIntegrationForText(text: string, integrations?: DefaultMcpIntegration[]): DefaultMcpIntegration | null;
|
|
@@ -863,10 +863,13 @@ export function resolveMcpIntegrationScope(defaultScope, hasOrg, canCreateOrgMcp
|
|
|
863
863
|
export function shouldOfferMcpOrganizationScope(hasOrg, canCreateOrgMcp) {
|
|
864
864
|
return hasOrg && canCreateOrgMcp;
|
|
865
865
|
}
|
|
866
|
+
export function supportsMcpIntegrationOrganizationScope(integration) {
|
|
867
|
+
return (integration.supportsOrganizationScope === true &&
|
|
868
|
+
integration.managedOAuth !== true);
|
|
869
|
+
}
|
|
866
870
|
export function shouldOfferMcpIntegrationOrganizationScope(integration, hasOrg, canCreateOrgMcp) {
|
|
867
871
|
return (shouldOfferMcpOrganizationScope(hasOrg, canCreateOrgMcp) &&
|
|
868
|
-
integration
|
|
869
|
-
integration.managedOAuth !== true);
|
|
872
|
+
supportsMcpIntegrationOrganizationScope(integration));
|
|
870
873
|
}
|
|
871
874
|
export function filterMcpIntegrations(query, integrations = getDefaultMcpIntegrations()) {
|
|
872
875
|
const needle = query.trim().toLowerCase();
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
26
26
|
* Body: { update: string (base64), requestSource?: string }
|
|
27
27
|
*/
|
|
28
28
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
29
|
-
error: string;
|
|
30
29
|
ok?: undefined;
|
|
30
|
+
error: string;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
33
33
|
ok: boolean;
|
package/dist/db/migrations.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getAppConfig } from "../app-config/index.js";
|
|
1
2
|
import { getDbExec, createDbExec, isPostgres, getDialect, getMigrationDatabaseUrl, getCloudflareD1Binding, retrySqliteBusy, } from "./client.js";
|
|
2
3
|
import { isMigrationAuthorizedRuntime } from "./migration-runtime.js";
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
@@ -250,8 +251,15 @@ function isServerlessRequestRuntime() {
|
|
|
250
251
|
* migration skipping is safe only when that runner owns schema setup.
|
|
251
252
|
*/
|
|
252
253
|
function appMigratesAtRelease() {
|
|
253
|
-
const
|
|
254
|
-
|
|
254
|
+
const { migration } = getAppConfig();
|
|
255
|
+
if (migration.releaseMigrations)
|
|
256
|
+
return true;
|
|
257
|
+
// Clips beta uses the production-owned schema but its masked prebuilt build
|
|
258
|
+
// must not run migrate:production against the fake build database. The
|
|
259
|
+
// owner marker is embedded into the deployed server bundle and therefore
|
|
260
|
+
// carries the same request-path skip decision without pretending the masked
|
|
261
|
+
// build performed the release migration.
|
|
262
|
+
return migration.betaSchemaOwner?.toLowerCase() === "production";
|
|
255
263
|
}
|
|
256
264
|
/**
|
|
257
265
|
* Run an explicit release-time migration job with migration duty enabled.
|
package/dist/deploy/build.js
CHANGED
|
@@ -34,6 +34,7 @@ import { generateActionRegistryForProject } from "../vite/action-types-plugin.js
|
|
|
34
34
|
import { createAgentNativeConfigContext, loadResolvedAgentNativeConfig, } from "../vite/agent-native-config-loader.js";
|
|
35
35
|
import { cloneServerBundleForFunction, copyDir } from "./function-bundle.js";
|
|
36
36
|
import { collectImmutableAssetPaths, IMMUTABLE_ASSET_CACHE_CONTROL, IMMUTABLE_ASSET_CACHE_HEADERS, prefixAssetPath, } from "./immutable-assets.js";
|
|
37
|
+
import { writeNetlifyStaticHeaders } from "./netlify-static-headers.js";
|
|
37
38
|
import { discoverApiRoutes, discoverPlugins, discoverActionFiles, getMissingDefaultPlugins, DEFAULT_PLUGIN_REGISTRY, } from "./route-discovery.js";
|
|
38
39
|
import { getWorkspaceCoreExports, } from "./workspace-core.js";
|
|
39
40
|
const cwd = process.cwd();
|
|
@@ -3892,6 +3893,7 @@ export function resolveNitroBuildReplacements(env = process.env, deploymentEnvir
|
|
|
3892
3893
|
// a separate server build, so embed release migration ownership here as
|
|
3893
3894
|
// well as in the Vite server bundle.
|
|
3894
3895
|
"process.env.AGENT_NATIVE_RELEASE_MIGRATIONS": JSON.stringify(env.AGENT_NATIVE_RELEASE_MIGRATIONS?.trim() || ""),
|
|
3896
|
+
"process.env.AGENT_NATIVE_BETA_SCHEMA_OWNER": JSON.stringify(env.AGENT_NATIVE_BETA_SCHEMA_OWNER?.trim() || ""),
|
|
3895
3897
|
"process.env.AGENT_NATIVE_BUILD_DEPLOY_CONTEXT": JSON.stringify(env.CONTEXT?.trim() || env.NETLIFY_CONTEXT?.trim() || ""),
|
|
3896
3898
|
// Whether the recurring-jobs scheduled function exists is decided HERE, by
|
|
3897
3899
|
// the build env. `scheduledTriggerAvailability` cannot re-derive it later —
|
|
@@ -4087,6 +4089,10 @@ export default bundle;
|
|
|
4087
4089
|
}
|
|
4088
4090
|
}
|
|
4089
4091
|
writeSingleTemplateNetlifyRedirects(cwd);
|
|
4092
|
+
// React Router prerendered pages bypass the SSR function and are served
|
|
4093
|
+
// directly from Netlify's static backing store. Keep that artifact on the
|
|
4094
|
+
// same public SWR policy as runtime SSR and .data responses.
|
|
4095
|
+
writeNetlifyStaticHeaders(path.join(cwd, "dist"));
|
|
4090
4096
|
assertSingleTemplateNetlifyBuildOutput(cwd);
|
|
4091
4097
|
}
|
|
4092
4098
|
// Resolve remaining bare npm imports by bundling them into _libs/.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const GENERATED_NETLIFY_HEADERS_MARKER = "# Generated by @agent-native/core deploy/netlify-static-headers.ts";
|
|
2
|
+
export declare function renderNetlifyStaticHeaders(env?: Record<string, string | undefined>): string;
|
|
3
|
+
export declare function writeNetlifyStaticHeaders(publishDir: string, env?: Record<string, string | undefined>): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveSsrCacheHeaders } from "../shared/cache-control.js";
|
|
4
|
+
import { IMMUTABLE_ASSET_CACHE_CONTROL } from "./immutable-assets.js";
|
|
5
|
+
export const GENERATED_NETLIFY_HEADERS_MARKER = "# Generated by @agent-native/core deploy/netlify-static-headers.ts";
|
|
6
|
+
const LEGACY_GENERATED_NETLIFY_HEADERS_MARKER = "# Generated by @agent-native/docs scripts/generate-netlify-headers.ts";
|
|
7
|
+
function renderHeaderBlock(pathname, headers) {
|
|
8
|
+
return [
|
|
9
|
+
pathname,
|
|
10
|
+
...headers.map(([name, value]) => ` ${name}: ${value}`),
|
|
11
|
+
].join("\n");
|
|
12
|
+
}
|
|
13
|
+
export function renderNetlifyStaticHeaders(env = process.env) {
|
|
14
|
+
const ssrHeaders = resolveSsrCacheHeaders(env);
|
|
15
|
+
const ssrHeaderEntries = [
|
|
16
|
+
["Cache-Control", ssrHeaders["cache-control"]],
|
|
17
|
+
["CDN-Cache-Control", ssrHeaders["cdn-cache-control"]],
|
|
18
|
+
["Netlify-CDN-Cache-Control", ssrHeaders["netlify-cdn-cache-control"]],
|
|
19
|
+
];
|
|
20
|
+
const immutableAssetHeaders = [
|
|
21
|
+
["Cache-Control", IMMUTABLE_ASSET_CACHE_CONTROL],
|
|
22
|
+
["CDN-Cache-Control", IMMUTABLE_ASSET_CACHE_CONTROL],
|
|
23
|
+
["Netlify-CDN-Cache-Control", IMMUTABLE_ASSET_CACHE_CONTROL],
|
|
24
|
+
];
|
|
25
|
+
const internalHeaders = [
|
|
26
|
+
["Cache-Control", "no-cache, no-store"],
|
|
27
|
+
["CDN-Cache-Control", "no-cache, no-store"],
|
|
28
|
+
["Netlify-CDN-Cache-Control", "no-cache, no-store"],
|
|
29
|
+
];
|
|
30
|
+
return ([
|
|
31
|
+
GENERATED_NETLIFY_HEADERS_MARKER,
|
|
32
|
+
renderHeaderBlock("/*", ssrHeaderEntries),
|
|
33
|
+
renderHeaderBlock("/assets/*", immutableAssetHeaders),
|
|
34
|
+
renderHeaderBlock("/_agent-native/*", internalHeaders),
|
|
35
|
+
].join("\n\n") + "\n");
|
|
36
|
+
}
|
|
37
|
+
export function writeNetlifyStaticHeaders(publishDir, env = process.env) {
|
|
38
|
+
if (!fs.existsSync(publishDir)) {
|
|
39
|
+
throw new Error(`Netlify publish directory does not exist: ${publishDir}`);
|
|
40
|
+
}
|
|
41
|
+
const headersPath = path.join(publishDir, "_headers");
|
|
42
|
+
const existing = fs.existsSync(headersPath)
|
|
43
|
+
? fs.readFileSync(headersPath, "utf8").trim()
|
|
44
|
+
: "";
|
|
45
|
+
const generated = renderNetlifyStaticHeaders(env).trim();
|
|
46
|
+
const generatedStart = [
|
|
47
|
+
GENERATED_NETLIFY_HEADERS_MARKER,
|
|
48
|
+
LEGACY_GENERATED_NETLIFY_HEADERS_MARKER,
|
|
49
|
+
]
|
|
50
|
+
.map((marker) => existing.indexOf(marker))
|
|
51
|
+
.filter((index) => index >= 0)
|
|
52
|
+
.sort((a, b) => a - b)[0];
|
|
53
|
+
const existingWithoutGenerated = generatedStart === undefined
|
|
54
|
+
? existing
|
|
55
|
+
: existing.slice(0, generatedStart).trimEnd();
|
|
56
|
+
const contents = existingWithoutGenerated
|
|
57
|
+
? `${existingWithoutGenerated}\n\n${generated}`
|
|
58
|
+
: generated;
|
|
59
|
+
fs.writeFileSync(headersPath, `${contents}\n`);
|
|
60
|
+
}
|