@spacefast/common 0.0.26 → 0.2.1
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/agents/connect-targets.d.ts +6 -146
- package/dist/agents/connect-targets.js +63 -295
- package/dist/agents/private-key-oauth.d.ts +31 -1
- package/dist/agents/private-key-oauth.js +124 -33
- package/dist/agents/registry/core.d.ts +292 -0
- package/dist/agents/registry/core.js +1388 -0
- package/dist/agents/registry/deeplink-probes.d.ts +23 -0
- package/dist/agents/registry/deeplink-probes.js +18 -0
- package/dist/agents/registry/node.d.ts +56 -0
- package/dist/agents/registry/node.js +339 -0
- package/dist/brand-assets-build.js +1 -2
- package/dist/brand-assets.js +4 -7
- package/dist/brand.d.ts +17 -0
- package/dist/brand.js +24 -4
- package/dist/config/domains.d.ts +55 -28
- package/dist/config/domains.js +85 -41
- package/dist/config/index.js +1 -2
- package/dist/contracts/abuse.js +9 -24
- package/dist/contracts/access-profiles.d.ts +86 -0
- package/dist/contracts/access-profiles.js +89 -0
- package/dist/contracts/access.d.ts +21 -27
- package/dist/contracts/access.js +33 -64
- package/dist/contracts/activity.d.ts +18 -1
- package/dist/contracts/activity.js +70 -26
- package/dist/contracts/analytics.js +8 -10
- package/dist/contracts/api-keys.d.ts +77 -52
- package/dist/contracts/api-keys.js +44 -24
- package/dist/contracts/application-journal.d.ts +216 -0
- package/dist/contracts/application-journal.js +167 -0
- package/dist/contracts/auth.d.ts +90 -17
- package/dist/contracts/auth.js +86 -17
- package/dist/contracts/beta.d.ts +9 -4
- package/dist/contracts/beta.js +20 -16
- package/dist/contracts/billing.js +7 -11
- package/dist/contracts/bootstrap.d.ts +791 -0
- package/dist/contracts/bootstrap.js +51 -0
- package/dist/contracts/builds.d.ts +186 -7
- package/dist/contracts/builds.js +75 -21
- package/dist/contracts/channels.d.ts +43 -0
- package/dist/contracts/channels.js +25 -9
- package/dist/contracts/cli.d.ts +7 -0
- package/dist/contracts/cli.js +6 -0
- package/dist/contracts/collab-public.d.ts +1 -1
- package/dist/contracts/collab-public.js +5 -8
- package/dist/contracts/comments.d.ts +45 -35
- package/dist/contracts/comments.js +107 -77
- package/dist/contracts/commerce.d.ts +165 -0
- package/dist/contracts/commerce.js +146 -0
- package/dist/contracts/common.d.ts +3 -3
- package/dist/contracts/common.js +18 -29
- package/dist/contracts/content-contract-verification.d.ts +521 -0
- package/dist/contracts/content-contract-verification.js +320 -0
- package/dist/contracts/content-platform-fixture.d.ts +1278 -0
- package/dist/contracts/content-platform-fixture.js +44 -0
- package/dist/contracts/content-program.d.ts +994 -0
- package/dist/contracts/content-program.js +658 -0
- package/dist/contracts/content-sync.d.ts +290 -0
- package/dist/contracts/content-sync.js +167 -0
- package/dist/contracts/content.d.ts +432 -0
- package/dist/contracts/content.js +303 -0
- package/dist/contracts/continuation.d.ts +17 -1
- package/dist/contracts/continuation.js +23 -23
- package/dist/contracts/countries.js +3 -4
- package/dist/contracts/crons.d.ts +17 -0
- package/dist/contracts/crons.js +33 -15
- package/dist/contracts/dashboard-prefs.d.ts +56 -0
- package/dist/contracts/dashboard-prefs.js +51 -0
- package/dist/contracts/device-auth.d.ts +234 -37
- package/dist/contracts/device-auth.js +267 -53
- package/dist/contracts/docs.d.ts +6 -10
- package/dist/contracts/docs.js +5 -13
- package/dist/contracts/domains.d.ts +499 -102
- package/dist/contracts/domains.js +169 -58
- package/dist/contracts/email-preferences.d.ts +3 -3
- package/dist/contracts/email-preferences.js +6 -7
- package/dist/contracts/enums.d.ts +26 -26
- package/dist/contracts/enums.js +61 -54
- package/dist/contracts/error-code-meta.d.ts +269 -7
- package/dist/contracts/error-code-meta.js +80 -5
- package/dist/contracts/error-codes.d.ts +5 -5
- package/dist/contracts/error-codes.js +78 -16
- package/dist/contracts/events.d.ts +100 -20
- package/dist/contracts/events.js +107 -20
- package/dist/contracts/execution.d.ts +47 -66
- package/dist/contracts/execution.js +51 -64
- package/dist/contracts/feature-lifecycle.d.ts +70 -14
- package/dist/contracts/feature-lifecycle.js +86 -15
- package/dist/contracts/features.d.ts +100 -37
- package/dist/contracts/features.js +21 -27
- package/dist/contracts/fixtures/content-platform-v1.json +1108 -0
- package/dist/contracts/frame-session.d.ts +192 -0
- package/dist/contracts/frame-session.js +164 -0
- package/dist/contracts/functions.d.ts +218 -211
- package/dist/contracts/functions.js +273 -260
- package/dist/contracts/grant-copy.d.ts +43 -0
- package/dist/contracts/grant-copy.js +44 -0
- package/dist/contracts/grants.d.ts +64 -56
- package/dist/contracts/grants.js +124 -23
- package/dist/contracts/ids.d.ts +4 -1
- package/dist/contracts/ids.js +4 -1
- package/dist/contracts/internal.d.ts +5 -4
- package/dist/contracts/internal.js +12 -3
- package/dist/contracts/mcp.d.ts +17 -0
- package/dist/contracts/mcp.js +23 -0
- package/dist/contracts/me.d.ts +6 -1
- package/dist/contracts/me.js +12 -6
- package/dist/contracts/notifications.d.ts +27 -29
- package/dist/contracts/notifications.js +9 -4
- package/dist/contracts/oauth-resources.d.ts +112 -10
- package/dist/contracts/oauth-resources.js +116 -11
- package/dist/contracts/oauth-scope-actions.d.ts +90 -0
- package/dist/contracts/oauth-scope-actions.js +126 -0
- package/dist/contracts/operations.d.ts +27 -4
- package/dist/contracts/operations.js +11 -5
- package/dist/contracts/pages.d.ts +29 -5
- package/dist/contracts/pages.js +22 -5
- package/dist/contracts/partner-notifications.d.ts +3 -0
- package/dist/contracts/partner-notifications.js +65 -0
- package/dist/contracts/{platform.d.ts → partner.d.ts} +29 -42
- package/dist/contracts/{platform.js → partner.js} +27 -40
- package/dist/contracts/plan-policy.d.ts +1 -1
- package/dist/contracts/principal-assertion.d.ts +43 -0
- package/dist/contracts/principal-assertion.js +67 -0
- package/dist/contracts/principals.d.ts +73 -0
- package/dist/contracts/principals.js +59 -0
- package/dist/contracts/privacy.d.ts +11 -0
- package/dist/contracts/privacy.js +17 -0
- package/dist/contracts/problem-document.js +3 -4
- package/dist/contracts/publish-archive.d.ts +116 -18
- package/dist/contracts/publish-archive.js +6 -3
- package/dist/contracts/push-new.d.ts +4 -4
- package/dist/contracts/quotas.js +3 -3
- package/dist/contracts/realtime.d.ts +52 -0
- package/dist/contracts/realtime.js +54 -0
- package/dist/contracts/repository-connections.d.ts +1 -1
- package/dist/contracts/repository-connections.js +6 -2
- package/dist/contracts/resources.d.ts +105 -14
- package/dist/contracts/resources.js +145 -21
- package/dist/contracts/route-inventory.d.ts +358 -0
- package/dist/contracts/route-inventory.js +141 -0
- package/dist/contracts/runtime-api.d.ts +112 -35
- package/dist/contracts/runtime-api.js +86 -66
- package/dist/contracts/runtime-app.d.ts +11 -21
- package/dist/contracts/runtime-app.js +39 -24
- package/dist/contracts/runtime-components.d.ts +143 -0
- package/dist/contracts/runtime-components.js +199 -0
- package/dist/contracts/runtime-db.d.ts +45 -19
- package/dist/contracts/runtime-db.js +85 -20
- package/dist/contracts/runtime-purge.js +3 -5
- package/dist/contracts/runtime-services.d.ts +43 -75
- package/dist/contracts/runtime-services.js +29 -49
- package/dist/contracts/runtime-storage.d.ts +14 -14
- package/dist/contracts/runtime-storage.js +28 -13
- package/dist/contracts/sf-config-v1.d.ts +14 -18
- package/dist/contracts/slugs.js +13 -4
- package/dist/contracts/space-config.d.ts +235 -28
- package/dist/contracts/space-config.js +246 -91
- package/dist/contracts/spaces.d.ts +360 -255
- package/dist/contracts/spaces.js +190 -122
- package/dist/contracts/superadmin-activity.d.ts +3 -43
- package/dist/contracts/superadmin-activity.js +6 -30
- package/dist/contracts/superadmin-emails.d.ts +1 -0
- package/dist/contracts/superadmin-emails.js +17 -1
- package/dist/contracts/superadmin-runtime.d.ts +199 -0
- package/dist/contracts/superadmin-runtime.js +141 -0
- package/dist/contracts/superadmin-search.d.ts +22 -2
- package/dist/contracts/superadmin-search.js +14 -6
- package/dist/contracts/superadmin-spaces.d.ts +435 -186
- package/dist/contracts/superadmin-spaces.js +131 -25
- package/dist/contracts/superadmin-teams.d.ts +3 -0
- package/dist/contracts/superadmin-teams.js +1 -2
- package/dist/contracts/superadmin-tenants.d.ts +627 -6
- package/dist/contracts/superadmin-tenants.js +32 -7
- package/dist/contracts/superadmin.d.ts +98 -16
- package/dist/contracts/superadmin.js +63 -10
- package/dist/contracts/tags.d.ts +85 -58
- package/dist/contracts/tags.js +44 -34
- package/dist/contracts/teams.d.ts +72 -9
- package/dist/contracts/teams.js +84 -25
- package/dist/contracts/tenants.d.ts +135 -0
- package/dist/contracts/tenants.js +120 -0
- package/dist/contracts/test-triggers.d.ts +70 -0
- package/dist/contracts/test-triggers.js +116 -0
- package/dist/contracts/theme-json.js +3 -8
- package/dist/contracts/transfers.d.ts +1 -0
- package/dist/contracts/transfers.js +1 -0
- package/dist/contracts/usage.d.ts +209 -0
- package/dist/contracts/usage.js +124 -12
- package/dist/contracts/variables.d.ts +2 -2
- package/dist/contracts/variables.js +9 -13
- package/dist/contracts/webhooks.d.ts +13 -5
- package/dist/contracts/webhooks.js +3 -1
- package/dist/contracts/zero.d.ts +57 -64
- package/dist/contracts/zero.js +68 -66
- package/dist/dashboard-paths/index.d.ts +24 -21
- package/dist/dashboard-paths/index.js +35 -47
- package/dist/dashboard-paths/route-reservations.generated.d.ts +2 -0
- package/dist/dashboard-paths/route-reservations.generated.js +36 -0
- package/dist/docs/agent-arrival.d.ts +92 -0
- package/dist/docs/agent-arrival.js +227 -0
- package/dist/docs/agent-output-policy.d.ts +6 -0
- package/dist/docs/agent-output-policy.js +22 -0
- package/dist/docs/agent-prose.d.ts +11 -17
- package/dist/docs/agent-prose.js +62 -37
- package/dist/docs/agent-setup.d.ts +72 -41
- package/dist/docs/agent-setup.js +336 -436
- package/dist/docs/catalog.d.ts +147 -89
- package/dist/docs/catalog.js +157 -42
- package/dist/docs/error-docs.d.ts +2098 -1
- package/dist/docs/error-docs.js +281 -16
- package/dist/docs/index-build.d.ts +0 -1
- package/dist/docs/index-build.js +2 -3
- package/dist/docs/page-context-prompt.d.ts +77 -0
- package/dist/docs/page-context-prompt.js +131 -0
- package/dist/docs/reference-urls.d.ts +46 -0
- package/dist/docs/reference-urls.js +79 -0
- package/dist/docs/search.d.ts +6 -8
- package/dist/docs/search.js +10 -13
- package/dist/docs/skill-distribution.d.ts +4 -1
- package/dist/docs/skill-distribution.js +10 -5
- package/dist/docs/skills.d.ts +117 -117
- package/dist/docs/skills.js +120 -72
- package/dist/docs/start-prompts.d.ts +45 -0
- package/dist/docs/start-prompts.js +62 -0
- package/dist/domain-exploration.js +9 -13
- package/dist/og-template.d.ts +56 -0
- package/dist/og-template.js +56 -0
- package/dist/slug-policy/index.js +5 -11
- package/dist/utils/browser-credential.d.ts +13 -6
- package/dist/utils/browser-credential.js +14 -7
- package/dist/utils/build-settings.d.ts +16 -0
- package/dist/utils/build-settings.js +63 -17
- package/dist/utils/canonical-json.d.ts +3 -5
- package/dist/utils/canonical-json.js +3 -5
- package/dist/utils/cast-transport.d.ts +5 -17
- package/dist/utils/cast-transport.js +6 -19
- package/dist/utils/claim-token.js +1 -2
- package/dist/utils/comment-avatars.js +4 -6
- package/dist/utils/concurrency.js +2 -3
- package/dist/utils/content-type.d.ts +7 -6
- package/dist/utils/content-type.js +73 -51
- package/dist/utils/credential-policy.d.ts +50 -6
- package/dist/utils/credential-policy.js +123 -57
- package/dist/utils/egress-policy.fixtures.json +1 -1
- package/dist/utils/generate-space-name.d.ts +3 -8
- package/dist/utils/generate-space-name.js +3 -8
- package/dist/utils/grants.d.ts +0 -1
- package/dist/utils/grants.js +4 -7
- package/dist/utils/gravatar.d.ts +2 -4
- package/dist/utils/gravatar.js +6 -12
- package/dist/utils/id-hints.js +14 -23
- package/dist/utils/idempotency.d.ts +3 -3
- package/dist/utils/idempotency.js +6 -7
- package/dist/utils/local-space-state.d.ts +15 -15
- package/dist/utils/local-space-state.js +34 -23
- package/dist/utils/oauth-signed-query.d.ts +3 -5
- package/dist/utils/oauth-signed-query.js +6 -9
- package/dist/utils/one-shot-replay.d.ts +3 -5
- package/dist/utils/one-shot-replay.js +3 -5
- package/dist/utils/page-colors.d.ts +4 -10
- package/dist/utils/page-colors.js +11 -35
- package/dist/utils/page-fonts.js +6 -11
- package/dist/utils/page-preview.d.ts +0 -1
- package/dist/utils/page-preview.js +0 -1
- package/dist/utils/pages.d.ts +22 -11
- package/dist/utils/pages.js +70 -30
- package/dist/utils/privacy-regions.d.ts +19 -0
- package/dist/utils/privacy-regions.js +91 -0
- package/dist/utils/publish-form-data.d.ts +4 -5
- package/dist/utils/publish-form-data.js +2 -3
- package/dist/utils/publish-policy.d.ts +10 -23
- package/dist/utils/publish-policy.js +63 -116
- package/dist/utils/query-client.d.ts +2 -0
- package/dist/utils/query-client.js +10 -1
- package/dist/utils/runtime-paths.d.ts +4 -4
- package/dist/utils/runtime-paths.js +6 -8
- package/dist/utils/runtime-upload.d.ts +4 -0
- package/dist/utils/runtime-upload.js +5 -0
- package/dist/utils/secure-local-file.d.ts +3 -3
- package/dist/utils/secure-local-file.js +18 -24
- package/dist/utils/space-config.d.ts +11 -14
- package/dist/utils/space-config.js +10 -16
- package/dist/utils/static-runtime-policy.d.ts +5 -8
- package/dist/utils/static-runtime-policy.generated.d.ts +16 -0
- package/dist/utils/static-runtime-policy.generated.js +70 -0
- package/dist/utils/static-runtime-policy.js +17 -84
- package/dist/utils/storage-policy.js +1 -2
- package/dist/utils/upload-session.js +8 -14
- package/dist/utils/version-path.js +11 -19
- package/dist/utils/wpcom-auth-redirects.d.ts +5 -14
- package/dist/utils/wpcom-auth-redirects.js +1 -4
- package/dist/vocabulary.d.ts +7 -7
- package/dist/vocabulary.js +30 -16
- package/package.json +14 -2
- package/dist/agents/client-registry.d.ts +0 -22
- package/dist/agents/client-registry.js +0 -41
- package/dist/docs/agent-handoff-document.d.ts +0 -15
- package/dist/docs/agent-handoff-document.js +0 -180
- package/dist/docs/agent-solutions.d.ts +0 -110
- package/dist/docs/agent-solutions.js +0 -183
- package/dist/utils/query-keys.d.ts +0 -84
- package/dist/utils/query-keys.js +0 -108
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical geometry for the Spacefast Open Graph image exported from Figma.
|
|
3
|
+
*
|
|
4
|
+
* Renderers may use different engines, but they must consume these dimensions
|
|
5
|
+
* and positions so every first-party surface produces the same template.
|
|
6
|
+
*/
|
|
7
|
+
export const SPACEFAST_OG_TEMPLATE = {
|
|
8
|
+
width: 2400,
|
|
9
|
+
height: 800,
|
|
10
|
+
background: "#fcfcfa",
|
|
11
|
+
grid: {
|
|
12
|
+
size: 80,
|
|
13
|
+
stroke: "#e8e8e5",
|
|
14
|
+
strokeWidth: 2,
|
|
15
|
+
},
|
|
16
|
+
title: {
|
|
17
|
+
left: 120,
|
|
18
|
+
top: 96,
|
|
19
|
+
width: 820,
|
|
20
|
+
fontFamily: "Recoleta",
|
|
21
|
+
fontWeight: 400,
|
|
22
|
+
fontSize: 112,
|
|
23
|
+
lineHeight: 0.9,
|
|
24
|
+
letterSpacing: -4,
|
|
25
|
+
},
|
|
26
|
+
wordmark: {
|
|
27
|
+
left: 136,
|
|
28
|
+
bottom: 96,
|
|
29
|
+
width: 360,
|
|
30
|
+
height: 68,
|
|
31
|
+
},
|
|
32
|
+
globe: {
|
|
33
|
+
left: 1178,
|
|
34
|
+
top: 96,
|
|
35
|
+
width: 160,
|
|
36
|
+
height: 160,
|
|
37
|
+
},
|
|
38
|
+
pin: {
|
|
39
|
+
left: 1602,
|
|
40
|
+
top: -82,
|
|
41
|
+
width: 426,
|
|
42
|
+
height: 402,
|
|
43
|
+
},
|
|
44
|
+
people: {
|
|
45
|
+
left: 1290,
|
|
46
|
+
top: 386,
|
|
47
|
+
width: 490,
|
|
48
|
+
height: 421,
|
|
49
|
+
},
|
|
50
|
+
branch: {
|
|
51
|
+
left: 2036,
|
|
52
|
+
top: 336,
|
|
53
|
+
width: 256,
|
|
54
|
+
height: 264,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { MANAGEMENT_LABEL_PREFIX } from "../config/domains.js";
|
|
2
1
|
import { INFRA_SLUG_BLOCKLIST, PRODUCT_PLATFORM_SLUG_BLOCKLIST, SUBSTRING_SLUG_BLOCKLIST, TRADEMARK_EXACT_SLUG_BLOCKLIST, } from "./blocklist.js";
|
|
3
2
|
export { INFRA_SLUG_BLOCKLIST, PRODUCT_PLATFORM_SLUG_BLOCKLIST, SUBSTRING_SLUG_BLOCKLIST, TRADEMARK_EXACT_SLUG_BLOCKLIST, } from "./blocklist.js";
|
|
4
3
|
export const SLUG_MIN_LENGTH = 3;
|
|
@@ -9,8 +8,8 @@ const productPlatformReservedSlugs = new Set(PRODUCT_PLATFORM_SLUG_BLOCKLIST);
|
|
|
9
8
|
const trademarkExactReservedSlugs = new Set(TRADEMARK_EXACT_SLUG_BLOCKLIST);
|
|
10
9
|
export function evaluateSlugCandidate(input, options = {}) {
|
|
11
10
|
const maxLength = options.maxLength ?? TEAM_SLUG_MAX_LENGTH;
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// Case, surrounding whitespace, and diacritics are the only canonicalization.
|
|
12
|
+
// Anything else is rejected rather than silently reshaped.
|
|
14
13
|
const casefolded = input
|
|
15
14
|
.trim()
|
|
16
15
|
.normalize("NFD")
|
|
@@ -47,20 +46,15 @@ function evaluateStructuralRules(normalized, structuralCandidate, maxLength) {
|
|
|
47
46
|
if (/^[0-9]+$/.test(normalized)) {
|
|
48
47
|
return invalid("Slug cannot be all numeric.");
|
|
49
48
|
}
|
|
50
|
-
if (normalized.startsWith(MANAGEMENT_LABEL_PREFIX)) {
|
|
51
|
-
return reserved(`Slug cannot start with reserved prefix ${MANAGEMENT_LABEL_PREFIX}.`);
|
|
52
|
-
}
|
|
53
49
|
return { ok: true, normalized };
|
|
54
50
|
}
|
|
55
|
-
// A substring reservation survives numeric suffixing (`paypal-2` still
|
|
56
|
-
//
|
|
57
|
-
// abandon the base instead of exhausting every suffix.
|
|
51
|
+
// A substring reservation survives numeric suffixing (`paypal-2` still contains
|
|
52
|
+
// `paypal`), so suggestion generators abandon the base instead of trying suffixes.
|
|
58
53
|
function matchingReservedSubstring(normalized) {
|
|
59
54
|
return SUBSTRING_SLUG_BLOCKLIST.find((reservedSubstring) => normalized.includes(reservedSubstring));
|
|
60
55
|
}
|
|
61
56
|
export function slugPolicyFailureSurvivesSuffix(normalized) {
|
|
62
|
-
return
|
|
63
|
-
normalized.startsWith(MANAGEMENT_LABEL_PREFIX));
|
|
57
|
+
return matchingReservedSubstring(normalized) !== undefined;
|
|
64
58
|
}
|
|
65
59
|
function evaluateReservedRules(normalized) {
|
|
66
60
|
if (infraReservedSlugs.has(normalized)) {
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
* The opaque one-use token every browser-handoff URL ends with. Exported
|
|
3
|
+
* because the agent-handoff grammar is built on it and several entrypoints
|
|
4
|
+
* (the CLI's argv sniffer among them) need the shape without paying for the
|
|
5
|
+
* agent registry that `docs/agent-setup.ts` pulls in.
|
|
6
|
+
*/
|
|
7
|
+
export declare const BROWSER_HANDOFF_TOKEN_PATTERN: RegExp;
|
|
8
|
+
/** Path shape of an agent-handoff link, for callers that have no expected origin. */
|
|
9
|
+
export declare const AGENT_HANDOFF_PATH_PATTERN: RegExp;
|
|
10
|
+
/**
|
|
11
|
+
* A browser handoff URL around a short-lived, one-use exchange token. The
|
|
12
|
+
* durable credential never appears in it: the access origin consumes the final
|
|
13
|
+
* path segment once, then redirects to a clean path-scoped session. A caller can
|
|
14
|
+
* add a non-secret `publicId` only when its protocol genuinely needs one.
|
|
8
15
|
*/
|
|
9
16
|
export declare function createBrowserCredentialUrl(input: {
|
|
10
17
|
origin: string;
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The opaque one-use token every browser-handoff URL ends with. Exported
|
|
3
|
+
* because the agent-handoff grammar is built on it and several entrypoints
|
|
4
|
+
* (the CLI's argv sniffer among them) need the shape without paying for the
|
|
5
|
+
* agent registry that `docs/agent-setup.ts` pulls in.
|
|
6
|
+
*/
|
|
7
|
+
export const BROWSER_HANDOFF_TOKEN_PATTERN = /^[A-Za-z0-9_-]{16,512}$/;
|
|
8
|
+
/** Path shape of an agent-handoff link, for callers that have no expected origin. */
|
|
9
|
+
export const AGENT_HANDOFF_PATH_PATTERN = /^\/handoffs\/[A-Za-z0-9_-]{16,512}\/?$/;
|
|
10
|
+
const browserHandoffSegmentPattern = BROWSER_HANDOFF_TOKEN_PATTERN;
|
|
2
11
|
const browserHandoffPublicIdPattern = /^[A-Za-z0-9_-]{1,512}$/;
|
|
3
12
|
function requireBrowserHandoffSegment(value) {
|
|
4
13
|
if (!browserHandoffSegmentPattern.test(value)) {
|
|
@@ -7,12 +16,10 @@ function requireBrowserHandoffSegment(value) {
|
|
|
7
16
|
return value;
|
|
8
17
|
}
|
|
9
18
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* path-scoped session. `publicId` is used only by agent handoffs, whose clean
|
|
15
|
-
* static document remains `/agent/<id>`.
|
|
19
|
+
* A browser handoff URL around a short-lived, one-use exchange token. The
|
|
20
|
+
* durable credential never appears in it: the access origin consumes the final
|
|
21
|
+
* path segment once, then redirects to a clean path-scoped session. A caller can
|
|
22
|
+
* add a non-secret `publicId` only when its protocol genuinely needs one.
|
|
16
23
|
*/
|
|
17
24
|
export function createBrowserCredentialUrl(input) {
|
|
18
25
|
const origin = new URL(input.origin);
|
|
@@ -47,8 +47,24 @@ export type PlatformConfig = {
|
|
|
47
47
|
crons: Array<Record<string, unknown>>;
|
|
48
48
|
unsupportedFeatures: string[];
|
|
49
49
|
};
|
|
50
|
+
export type NetlifyBuildPlugin = {
|
|
51
|
+
package: string;
|
|
52
|
+
inputs: NetlifyBuildPluginInputs;
|
|
53
|
+
};
|
|
54
|
+
export type NetlifyBuildPluginInputValue = null | boolean | number | string | NetlifyBuildPluginInputValue[] | {
|
|
55
|
+
[name: string]: NetlifyBuildPluginInputValue;
|
|
56
|
+
};
|
|
57
|
+
export type NetlifyBuildPluginInputs = {
|
|
58
|
+
[name: string]: NetlifyBuildPluginInputValue;
|
|
59
|
+
};
|
|
60
|
+
export type NetlifyBuildConfiguration = {
|
|
61
|
+
environment: Record<string, string>;
|
|
62
|
+
plugins: NetlifyBuildPlugin[];
|
|
63
|
+
unsupportedFeatures: string[];
|
|
64
|
+
};
|
|
50
65
|
export declare function stringField(value: Record<string, unknown> | null, key: string): string | null;
|
|
51
66
|
export declare function numberField(value: Record<string, unknown> | null, key: string): number | null;
|
|
67
|
+
export declare function parseNetlifyBuildConfiguration(value: string | null | undefined): NetlifyBuildConfiguration | null;
|
|
52
68
|
export declare function detectPlatformConfigs(input: Omit<BuildSettingsInput, "fileNames" | "packageJson">): PlatformConfig[];
|
|
53
69
|
export type PlatformRoutingConversion = {
|
|
54
70
|
redirectLines: string[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { load as parseYaml } from "js-yaml";
|
|
2
2
|
import { parse as parseToml } from "smol-toml";
|
|
3
|
+
import { z } from "zod";
|
|
3
4
|
import { SPACE_CONFIG_CAPS, spaceCronKey, spaceCronSchema, } from "../contracts/space-config.js";
|
|
4
5
|
export const BUILD_ANALYZER_CANDIDATE_ROOTS = ["apps", "packages", "sites", "docs"];
|
|
5
6
|
const PACKAGE_MANAGER_ONLY_FILES = new Set([
|
|
@@ -80,6 +81,8 @@ export function hasBuildAnalyzerCandidateSignal(input) {
|
|
|
80
81
|
input.detected.buildCommand !== null ||
|
|
81
82
|
names.has("index.html"));
|
|
82
83
|
}
|
|
84
|
+
const BUILD_ENVIRONMENT_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
85
|
+
const netlifyBuildEnvironmentValueSchema = z.union([z.string(), z.number(), z.boolean()]);
|
|
83
86
|
function parseJsonObject(value) {
|
|
84
87
|
if (!value) {
|
|
85
88
|
return null;
|
|
@@ -152,6 +155,54 @@ function arrayField(value, key) {
|
|
|
152
155
|
? field.filter((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry))
|
|
153
156
|
: [];
|
|
154
157
|
}
|
|
158
|
+
function netlifyBuildEnvironment(build) {
|
|
159
|
+
const environment = objectField(build, "environment");
|
|
160
|
+
const values = {};
|
|
161
|
+
const unsupportedFeatures = [];
|
|
162
|
+
for (const [name, value] of Object.entries(environment ?? {})) {
|
|
163
|
+
if (!BUILD_ENVIRONMENT_NAME.test(name)) {
|
|
164
|
+
unsupportedFeatures.push(`Netlify build environment variable "${name}" has an invalid name.`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const parsed = netlifyBuildEnvironmentValueSchema.safeParse(value);
|
|
168
|
+
if (parsed.success) {
|
|
169
|
+
values[name] = String(parsed.data);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
unsupportedFeatures.push(`Netlify build environment variable "${name}" must have a scalar value.`);
|
|
173
|
+
}
|
|
174
|
+
return { values, unsupportedFeatures };
|
|
175
|
+
}
|
|
176
|
+
function netlifyBuildPlugins(config) {
|
|
177
|
+
const plugins = [];
|
|
178
|
+
const unsupportedFeatures = [];
|
|
179
|
+
for (const entry of arrayField(config, "plugins")) {
|
|
180
|
+
const packageName = stringField(entry, "package");
|
|
181
|
+
if (!packageName) {
|
|
182
|
+
unsupportedFeatures.push("A Netlify build plugin is missing its package name.");
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const inputsResult = z
|
|
186
|
+
.record(z.string(), z.json())
|
|
187
|
+
.safeParse(objectField(entry, "inputs") ?? {});
|
|
188
|
+
const inputs = inputsResult.success ? inputsResult.data : {};
|
|
189
|
+
plugins.push({ package: packageName, inputs });
|
|
190
|
+
}
|
|
191
|
+
return { plugins, unsupportedFeatures };
|
|
192
|
+
}
|
|
193
|
+
export function parseNetlifyBuildConfiguration(value) {
|
|
194
|
+
const config = parseTomlObject(value);
|
|
195
|
+
if (!config) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const environment = netlifyBuildEnvironment(objectField(config, "build"));
|
|
199
|
+
const plugins = netlifyBuildPlugins(config);
|
|
200
|
+
return {
|
|
201
|
+
environment: environment.values,
|
|
202
|
+
plugins: plugins.plugins,
|
|
203
|
+
unsupportedFeatures: [...environment.unsupportedFeatures, ...plugins.unsupportedFeatures],
|
|
204
|
+
};
|
|
205
|
+
}
|
|
155
206
|
function vercelConfig(value) {
|
|
156
207
|
const config = parseJsonObjectWithComments(value);
|
|
157
208
|
if (!config) {
|
|
@@ -181,6 +232,7 @@ function netlifyConfig(value) {
|
|
|
181
232
|
return null;
|
|
182
233
|
}
|
|
183
234
|
const build = objectField(config, "build");
|
|
235
|
+
const nativeBuild = parseNetlifyBuildConfiguration(value);
|
|
184
236
|
return {
|
|
185
237
|
platform: "netlify",
|
|
186
238
|
rootDirectory: stringField(build, "base") ?? stringField(config, "base"),
|
|
@@ -197,7 +249,7 @@ function netlifyConfig(value) {
|
|
|
197
249
|
unsupportedFeatures: [
|
|
198
250
|
...("functions" in config ? ["Netlify functions are not converted."] : []),
|
|
199
251
|
...("edge_functions" in config ? ["Netlify edge functions are not converted."] : []),
|
|
200
|
-
...(
|
|
252
|
+
...(nativeBuild?.unsupportedFeatures ?? []),
|
|
201
253
|
...("processing" in config ? ["Netlify post-processing config is not converted."] : []),
|
|
202
254
|
],
|
|
203
255
|
};
|
|
@@ -292,7 +344,7 @@ function vercelDestination(destination, splat) {
|
|
|
292
344
|
converted = converted.replaceAll("$1", ":splat");
|
|
293
345
|
}
|
|
294
346
|
else if (splat) {
|
|
295
|
-
// `:name`, `:name*`, or `:name
|
|
347
|
+
// `:name`, `:name*`, or `:name+`, but not a longer name sharing the prefix.
|
|
296
348
|
const reference = new RegExp(`\\${splat}[*+]?(?![A-Za-z0-9_])`, "g");
|
|
297
349
|
converted = converted.replace(reference, ":splat");
|
|
298
350
|
}
|
|
@@ -443,15 +495,11 @@ function convertNetlifyHeaderRule(rule, out) {
|
|
|
443
495
|
out.headerBlocks.push(headerBlock(matcher, headers));
|
|
444
496
|
}
|
|
445
497
|
/**
|
|
446
|
-
* One `vercel.json` cron entry into an `sf.jsonc` `crons` entry.
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
* and schedules our grammar does not, and it has no per-project cap the way we
|
|
452
|
-
* do. Every rejection names the entry, because a scheduled job that silently
|
|
453
|
-
* stops existing is the one migration failure nobody notices until the work
|
|
454
|
-
* has not run for a week.
|
|
498
|
+
* One `vercel.json` cron entry into an `sf.jsonc` `crons` entry. Both sides are
|
|
499
|
+
* `{ path, schedule }`, so a valid entry converts verbatim. What this guards is
|
|
500
|
+
* the tail: Vercel accepts paths and schedules our grammar does not, and has no
|
|
501
|
+
* per-project cap. Every rejection names its entry, because a cron that
|
|
502
|
+
* silently stops existing is the migration failure nobody notices.
|
|
455
503
|
*/
|
|
456
504
|
function convertVercelCron(rule, out) {
|
|
457
505
|
const path = stringField(rule, "path");
|
|
@@ -601,9 +649,8 @@ const FRAMEWORKS = [
|
|
|
601
649
|
defaultBuildCommand: "vinxi build",
|
|
602
650
|
},
|
|
603
651
|
{
|
|
604
|
-
// Qwik City
|
|
605
|
-
//
|
|
606
|
-
// meaningful bare-binary default); static output lands in dist.
|
|
652
|
+
// Qwik City builds through the repo's `build` script, so there is no
|
|
653
|
+
// meaningful bare-binary default; static output lands in dist.
|
|
607
654
|
preset: "qwik",
|
|
608
655
|
dependency: "@builder.io/qwik-city",
|
|
609
656
|
outputDirectory: "dist",
|
|
@@ -730,9 +777,8 @@ const FRAMEWORKS = [
|
|
|
730
777
|
defaultBuildCommand: "bundle exec middleman build",
|
|
731
778
|
},
|
|
732
779
|
];
|
|
733
|
-
// Every file name the build analyzer keys on
|
|
734
|
-
//
|
|
735
|
-
// from. An archive carrying any of them is a project to build, not a website.
|
|
780
|
+
// Every file name the build analyzer keys on. An archive carrying any of them
|
|
781
|
+
// is a project to build, not a website.
|
|
736
782
|
export const BUILD_MARKER_FILE_NAMES = new Set([
|
|
737
783
|
...PACKAGE_MANAGER_ONLY_FILES,
|
|
738
784
|
...WORKSPACE_ROOT_FILES,
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Recursively key-sorted JSON, so a value round-tripped through Postgres jsonb
|
|
3
|
-
*
|
|
4
|
-
* JS. Arrays keep their original order since order is meaningful there.
|
|
3
|
+
* digests identically to one freshly built in JS. Arrays keep their order.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* hashes stop matching freshly computed ones.
|
|
5
|
+
* The control plane, the CLI and the capsule compiler all digest this exact
|
|
6
|
+
* spelling. Change it and stored hashes stop matching new ones.
|
|
9
7
|
*/
|
|
10
8
|
export declare function canonicalJson(value: unknown): string;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Recursively key-sorted JSON, so a value round-tripped through Postgres jsonb
|
|
3
|
-
*
|
|
4
|
-
* JS. Arrays keep their original order since order is meaningful there.
|
|
3
|
+
* digests identically to one freshly built in JS. Arrays keep their order.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* hashes stop matching freshly computed ones.
|
|
5
|
+
* The control plane, the CLI and the capsule compiler all digest this exact
|
|
6
|
+
* spelling. Change it and stored hashes stop matching new ones.
|
|
9
7
|
*/
|
|
10
8
|
export function canonicalJson(value) {
|
|
11
9
|
if (Array.isArray(value)) {
|
|
@@ -1,23 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How a client addresses Cast over Phoenix. Both Spacefast clients — the
|
|
3
|
-
* dashboard's notice subscription and the injected Collab SDK — build their
|
|
4
|
-
* socket and their topics here, and nowhere else.
|
|
5
|
-
*/
|
|
1
|
+
/** Every Spacefast client builds its Cast socket and topics here, and nowhere else. */
|
|
6
2
|
/**
|
|
7
3
|
* The Phoenix topic a Cast room's messages travel on.
|
|
8
4
|
*
|
|
9
|
-
* Phoenix routes a broadcast by the *literal* topic string
|
|
10
|
-
*
|
|
11
|
-
* room
|
|
12
|
-
*
|
|
13
|
-
* Both sides therefore encode exactly once, here and in that module: the `~n`
|
|
14
|
-
* table first (it survives round-tripping a key that itself contains `~` or
|
|
15
|
-
* `%`), then `encodeURIComponent`.
|
|
16
|
-
*
|
|
17
|
-
* Proven by delivery, not by inspection: `./cast-transport.test.ts` pins the
|
|
18
|
-
* strings, and on the other side `room_channel_notice_test.exs` and
|
|
19
|
-
* `spacefast_inbox_fanout_test.exs` join those literal topics and assert a real
|
|
20
|
-
* Cast broadcast arrives on them.
|
|
5
|
+
* Phoenix routes a broadcast by the *literal* topic string, so a client that
|
|
6
|
+
* encodes the room key differently from `Cast.RoomTopic.for/2` joins a real and
|
|
7
|
+
* permanently silent room. Both sides encode once: the `~n` table first, then
|
|
8
|
+
* `encodeURIComponent`.
|
|
21
9
|
*/
|
|
22
10
|
export declare function castRoomTopic(resourceKey: string, roomKey: string): string;
|
|
23
11
|
/**
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How a client addresses Cast over Phoenix. Both Spacefast clients — the
|
|
3
|
-
* dashboard's notice subscription and the injected Collab SDK — build their
|
|
4
|
-
* socket and their topics here, and nowhere else.
|
|
5
|
-
*/
|
|
1
|
+
/** Every Spacefast client builds its Cast socket and topics here, and nowhere else. */
|
|
6
2
|
/**
|
|
7
3
|
* `~n` escapes, the inverse of `CastWeb.ApiContext.decode_path_segment/1`.
|
|
8
|
-
*
|
|
9
|
-
* escaped marker can never be escaped twice.
|
|
4
|
+
* Applied in one pass with `~` first, so an escaped marker is never escaped twice.
|
|
10
5
|
*/
|
|
11
6
|
const ESCAPED = new Map([
|
|
12
7
|
["~", "~0"],
|
|
@@ -19,18 +14,10 @@ const ESCAPED = new Map([
|
|
|
19
14
|
/**
|
|
20
15
|
* The Phoenix topic a Cast room's messages travel on.
|
|
21
16
|
*
|
|
22
|
-
* Phoenix routes a broadcast by the *literal* topic string
|
|
23
|
-
*
|
|
24
|
-
* room
|
|
25
|
-
*
|
|
26
|
-
* Both sides therefore encode exactly once, here and in that module: the `~n`
|
|
27
|
-
* table first (it survives round-tripping a key that itself contains `~` or
|
|
28
|
-
* `%`), then `encodeURIComponent`.
|
|
29
|
-
*
|
|
30
|
-
* Proven by delivery, not by inspection: `./cast-transport.test.ts` pins the
|
|
31
|
-
* strings, and on the other side `room_channel_notice_test.exs` and
|
|
32
|
-
* `spacefast_inbox_fanout_test.exs` join those literal topics and assert a real
|
|
33
|
-
* Cast broadcast arrives on them.
|
|
17
|
+
* Phoenix routes a broadcast by the *literal* topic string, so a client that
|
|
18
|
+
* encodes the room key differently from `Cast.RoomTopic.for/2` joins a real and
|
|
19
|
+
* permanently silent room. Both sides encode once: the `~n` table first, then
|
|
20
|
+
* `encodeURIComponent`.
|
|
34
21
|
*/
|
|
35
22
|
export function castRoomTopic(resourceKey, roomKey) {
|
|
36
23
|
let escaped = "";
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// The space key (`sfc_…`, or the legacy five-word form) is the one claim
|
|
2
|
-
// credential
|
|
3
|
-
// fragments verbatim.
|
|
2
|
+
// credential: it rides `Bearer` headers and claim-link fragments verbatim.
|
|
4
3
|
export function normalizeClaimToken(token) {
|
|
5
4
|
const trimmed = token?.trim();
|
|
6
5
|
if (!trimmed) {
|
|
@@ -21,12 +21,10 @@ export function normalizeCommentAvatarUrl(value) {
|
|
|
21
21
|
if (source.origin === CANONICAL_GRAVATAR_ORIGIN || source.origin === AVATAR_PROXY_ORIGIN) {
|
|
22
22
|
return source.href.length <= MAX_AVATAR_URL_LENGTH ? source.href : null;
|
|
23
23
|
}
|
|
24
|
-
// Assign the proxy path instead of parsing `<host><path>` as a URL
|
|
25
|
-
// with a non-default port
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// to the same proxy URL. `source.host` deliberately retains an explicit
|
|
29
|
-
// non-default port.
|
|
24
|
+
// Assign the proxy path instead of parsing `<host><path>` as a URL: a host
|
|
25
|
+
// with a non-default port carries a colon and would become a made-up scheme
|
|
26
|
+
// (`javascript:8443/...`). `ssl=1` keeps an HTTPS and an HTTP source from
|
|
27
|
+
// collapsing to the same proxy URL.
|
|
30
28
|
const proxied = new URL(AVATAR_PROXY_ORIGIN);
|
|
31
29
|
proxied.pathname = `/${source.host}${source.pathname}`;
|
|
32
30
|
proxied.search = source.search;
|
|
@@ -17,9 +17,8 @@ export async function mapConcurrent(items, limit, mapper) {
|
|
|
17
17
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
18
18
|
return results;
|
|
19
19
|
}
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// observable. Later batches are never started after a match is found.
|
|
20
|
+
// Bounded batches with Array.find semantics: the earliest matching item wins,
|
|
21
|
+
// and only a rejection before that match is observable.
|
|
23
22
|
export async function findConcurrent(items, limit, predicate) {
|
|
24
23
|
if (!Number.isInteger(limit) || limit < 1) {
|
|
25
24
|
throw new Error("Concurrency limit must be a positive integer.");
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* `
|
|
4
|
-
*
|
|
5
|
-
* MIME extension coverage comes from the maintained `mime` database. The
|
|
6
|
-
* small overrides above preserve intentional existing policy; this module
|
|
7
|
-
* does not carry a second partial extension registry.
|
|
2
|
+
* Content-Type for a file extension, `application/octet-stream` when unknown.
|
|
3
|
+
* Coverage comes from the `mime` database; the overrides above are the only
|
|
4
|
+
* exceptions, and this module never grows a second extension registry.
|
|
8
5
|
*/
|
|
9
6
|
export declare function contentTypeForPath(filePath: string): string;
|
|
10
7
|
export declare function textTypeForPath(filePath: string): string | null;
|
|
8
|
+
export declare const CONTENT_SNIFF_KINDS: readonly ["png", "jpeg", "gif", "webp", "avif", "ico", "pdf", "gzip", "woff", "woff2", "ttf", "otf", "text", "binary"];
|
|
9
|
+
export type ContentSniffKind = (typeof CONTENT_SNIFF_KINDS)[number];
|
|
10
|
+
export declare function contentSniffKind(bytes: Uint8Array): ContentSniffKind;
|
|
11
|
+
export declare function contentTypeForSniff(filePath: string, sniff: ContentSniffKind): string;
|
|
11
12
|
export declare function contentTypeForBytes(filePath: string, bytes: Uint8Array): string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import mime from "mime";
|
|
2
2
|
const CONTENT_TYPE_OVERRIDES = {
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
// Where the mime database's server-oriented type differs from the
|
|
4
|
+
// browser-facing type we already serve.
|
|
5
5
|
".cjs": "text/javascript",
|
|
6
6
|
".cts": "text/plain",
|
|
7
7
|
".d.ts": "text/plain",
|
|
@@ -18,12 +18,9 @@ const UTF8_APPLICATION_TYPES = new Set([
|
|
|
18
18
|
"application/xml",
|
|
19
19
|
]);
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
* `
|
|
23
|
-
*
|
|
24
|
-
* MIME extension coverage comes from the maintained `mime` database. The
|
|
25
|
-
* small overrides above preserve intentional existing policy; this module
|
|
26
|
-
* does not carry a second partial extension registry.
|
|
21
|
+
* Content-Type for a file extension, `application/octet-stream` when unknown.
|
|
22
|
+
* Coverage comes from the `mime` database; the overrides above are the only
|
|
23
|
+
* exceptions, and this module never grows a second extension registry.
|
|
27
24
|
*/
|
|
28
25
|
export function contentTypeForPath(filePath) {
|
|
29
26
|
const normalizedPath = filePath.toLowerCase();
|
|
@@ -70,53 +67,78 @@ function bytesLookTextual(bytes) {
|
|
|
70
67
|
}
|
|
71
68
|
return true;
|
|
72
69
|
}
|
|
73
|
-
export
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
70
|
+
export const CONTENT_SNIFF_KINDS = [
|
|
71
|
+
"png",
|
|
72
|
+
"jpeg",
|
|
73
|
+
"gif",
|
|
74
|
+
"webp",
|
|
75
|
+
"avif",
|
|
76
|
+
"ico",
|
|
77
|
+
"pdf",
|
|
78
|
+
"gzip",
|
|
79
|
+
"woff",
|
|
80
|
+
"woff2",
|
|
81
|
+
"ttf",
|
|
82
|
+
"otf",
|
|
83
|
+
"text",
|
|
84
|
+
"binary",
|
|
85
|
+
];
|
|
86
|
+
export function contentSniffKind(bytes) {
|
|
87
|
+
if (hasPrefix(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
|
|
88
|
+
return "png";
|
|
89
|
+
if (hasPrefix(bytes, [0xff, 0xd8, 0xff]))
|
|
90
|
+
return "jpeg";
|
|
91
|
+
if (hasAsciiPrefix(bytes, "GIF87a") || hasAsciiPrefix(bytes, "GIF89a"))
|
|
92
|
+
return "gif";
|
|
93
|
+
if (hasAsciiPrefix(bytes, "RIFF") && hasAsciiPrefix(bytes.slice(8), "WEBP"))
|
|
94
|
+
return "webp";
|
|
86
95
|
if (hasAsciiPrefix(bytes.slice(4), "ftypavif") || hasAsciiPrefix(bytes.slice(4), "ftypavis")) {
|
|
87
|
-
return "
|
|
88
|
-
}
|
|
89
|
-
if (hasPrefix(bytes, [0x00, 0x00, 0x01, 0x00]))
|
|
90
|
-
return "
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
return "
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (hasAsciiPrefix(bytes, "
|
|
102
|
-
return "
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
96
|
+
return "avif";
|
|
97
|
+
}
|
|
98
|
+
if (hasPrefix(bytes, [0x00, 0x00, 0x01, 0x00]))
|
|
99
|
+
return "ico";
|
|
100
|
+
if (hasAsciiPrefix(bytes, "%PDF-"))
|
|
101
|
+
return "pdf";
|
|
102
|
+
if (hasPrefix(bytes, [0x1f, 0x8b, 0x08]))
|
|
103
|
+
return "gzip";
|
|
104
|
+
if (hasAsciiPrefix(bytes, "wOFF"))
|
|
105
|
+
return "woff";
|
|
106
|
+
if (hasAsciiPrefix(bytes, "wOF2"))
|
|
107
|
+
return "woff2";
|
|
108
|
+
if (hasPrefix(bytes, [0x00, 0x01, 0x00, 0x00]) || hasAsciiPrefix(bytes, "true"))
|
|
109
|
+
return "ttf";
|
|
110
|
+
if (hasAsciiPrefix(bytes, "OTTO"))
|
|
111
|
+
return "otf";
|
|
112
|
+
return bytesLookTextual(bytes) ? "text" : "binary";
|
|
113
|
+
}
|
|
114
|
+
const CONTENT_TYPES_BY_SNIFF = {
|
|
115
|
+
png: "image/png",
|
|
116
|
+
jpeg: "image/jpeg",
|
|
117
|
+
gif: "image/gif",
|
|
118
|
+
webp: "image/webp",
|
|
119
|
+
avif: "image/avif",
|
|
120
|
+
ico: "image/x-icon",
|
|
121
|
+
pdf: "application/pdf",
|
|
122
|
+
gzip: "application/gzip",
|
|
123
|
+
woff: "font/woff",
|
|
124
|
+
woff2: "font/woff2",
|
|
125
|
+
ttf: "font/ttf",
|
|
126
|
+
otf: "font/otf",
|
|
127
|
+
text: null,
|
|
128
|
+
binary: null,
|
|
129
|
+
};
|
|
130
|
+
export function contentTypeForSniff(filePath, sniff) {
|
|
131
|
+
const detected = CONTENT_TYPES_BY_SNIFF[sniff];
|
|
132
|
+
if (detected)
|
|
133
|
+
return detected;
|
|
110
134
|
const textType = textTypeForPath(filePath);
|
|
111
|
-
if (
|
|
135
|
+
if (sniff === "text" && textType)
|
|
112
136
|
return textType;
|
|
113
|
-
}
|
|
114
|
-
// Container signatures such as ISO-BMFF, WebM, and Ogg are shared by audio
|
|
115
|
-
// and video variants. The extension is the reliable discriminator for the
|
|
116
|
-
// manifest declaration, while unknown binary extensions remain generic.
|
|
117
137
|
const pathType = contentTypeForPath(filePath);
|
|
118
|
-
if (pathType.startsWith("audio/") || pathType.startsWith("video/"))
|
|
138
|
+
if (pathType.startsWith("audio/") || pathType.startsWith("video/"))
|
|
119
139
|
return pathType;
|
|
120
|
-
}
|
|
121
140
|
return "application/octet-stream";
|
|
122
141
|
}
|
|
142
|
+
export function contentTypeForBytes(filePath, bytes) {
|
|
143
|
+
return contentTypeForSniff(filePath, contentSniffKind(bytes));
|
|
144
|
+
}
|