@spacefast/common 0.0.24 → 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/brand-assets/LICENSE-Haskoy.txt +93 -0
- package/brand-assets/haskoy-latin-variable.woff2 +0 -0
- package/brand-assets/spacefast-favicon.svg +4 -4
- package/brand-assets/spacefast-sf-full-bleed.svg +3 -3
- package/brand-assets/spacefast-wordmark.svg +2 -4
- 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 -2
- package/dist/agents/private-key-oauth.js +166 -92
- 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 +3 -6
- package/dist/brand-assets.d.ts +5 -15
- package/dist/brand-assets.js +12 -14
- package/dist/brand.d.ts +17 -0
- package/dist/brand.js +24 -4
- package/dist/config/domains.d.ts +79 -12
- package/dist/config/domains.js +128 -29
- 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 +24 -30
- package/dist/contracts/access.js +33 -64
- package/dist/contracts/activity.d.ts +18 -1
- package/dist/contracts/activity.js +72 -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.d.ts +2 -1
- package/dist/contracts/billing.js +37 -11
- package/dist/contracts/bootstrap.d.ts +791 -0
- package/dist/contracts/bootstrap.js +51 -0
- package/dist/contracts/builds.d.ts +209 -18
- package/dist/contracts/builds.js +93 -27
- 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 +44 -34
- 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 +20 -31
- 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 +52 -0
- package/dist/contracts/crons.js +60 -0
- 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 +1 -5
- package/dist/contracts/docs.js +7 -15
- package/dist/contracts/domains.d.ts +501 -93
- package/dist/contracts/domains.js +193 -55
- package/dist/contracts/email-preferences.d.ts +3 -3
- package/dist/contracts/email-preferences.js +6 -7
- package/dist/contracts/enums.d.ts +27 -26
- package/dist/contracts/enums.js +68 -61
- package/dist/contracts/error-code-meta.d.ts +283 -7
- package/dist/contracts/error-code-meta.js +97 -5
- package/dist/contracts/error-codes.d.ts +5 -5
- package/dist/contracts/error-codes.js +82 -17
- package/dist/contracts/events.d.ts +100 -20
- package/dist/contracts/events.js +107 -20
- package/dist/contracts/execution.d.ts +48 -64
- package/dist/contracts/execution.js +52 -62
- package/dist/contracts/feature-lifecycle.d.ts +73 -27
- package/dist/contracts/feature-lifecycle.js +86 -26
- package/dist/contracts/features.d.ts +68 -19
- package/dist/contracts/features.js +42 -41
- 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 +338 -164
- package/dist/contracts/functions.js +367 -198
- package/dist/contracts/git.d.ts +27 -33
- package/dist/contracts/git.js +10 -11
- package/dist/contracts/grant-copy.d.ts +43 -0
- package/dist/contracts/grant-copy.js +44 -0
- package/dist/contracts/grants.d.ts +62 -54
- package/dist/contracts/grants.js +124 -23
- package/dist/contracts/ids.d.ts +4 -1
- package/dist/contracts/ids.js +6 -3
- package/dist/contracts/internal.d.ts +8 -18
- package/dist/contracts/internal.js +13 -14
- package/dist/contracts/mcp.d.ts +28 -0
- package/dist/contracts/mcp.js +28 -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 +12 -6
- package/dist/contracts/pages.d.ts +31 -7
- 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.js +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 +269 -161
- package/dist/contracts/publish-archive.js +11 -1
- package/dist/contracts/push-new.d.ts +8 -8
- package/dist/contracts/quotas.js +5 -5
- package/dist/contracts/realtime.d.ts +52 -0
- package/dist/contracts/realtime.js +54 -0
- package/dist/contracts/repository-connections.d.ts +44 -31
- package/dist/contracts/repository-connections.js +17 -5
- package/dist/contracts/resources.d.ts +105 -14
- package/dist/contracts/resources.js +150 -26
- package/dist/contracts/route-inventory.d.ts +358 -0
- package/dist/contracts/route-inventory.js +141 -0
- package/dist/contracts/runtime-api.d.ts +206 -82
- package/dist/contracts/runtime-api.js +131 -70
- package/dist/contracts/runtime-app.d.ts +13 -23
- 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 +49 -23
- package/dist/contracts/runtime-db.js +87 -22
- package/dist/contracts/runtime-purge.d.ts +8 -0
- package/dist/contracts/runtime-purge.js +5 -0
- package/dist/contracts/runtime-services.d.ts +43 -75
- package/dist/contracts/runtime-services.js +29 -49
- package/dist/contracts/runtime-storage.d.ts +15 -14
- package/dist/contracts/runtime-storage.js +30 -14
- package/dist/contracts/sf-config-v1.d.ts +48 -14
- package/dist/contracts/slugs.js +13 -4
- package/dist/contracts/space-config.d.ts +283 -19
- package/dist/contracts/space-config.js +430 -80
- package/dist/contracts/spaces.d.ts +376 -290
- package/dist/contracts/spaces.js +200 -135
- 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 +438 -189
- package/dist/contracts/superadmin-spaces.js +131 -25
- package/dist/contracts/superadmin-teams.d.ts +5 -2
- package/dist/contracts/superadmin-teams.js +1 -2
- package/dist/contracts/superadmin-tenants.d.ts +627 -12
- package/dist/contracts/superadmin-tenants.js +32 -13
- package/dist/contracts/superadmin.d.ts +232 -37
- package/dist/contracts/superadmin.js +106 -22
- package/dist/contracts/tags.d.ts +85 -58
- package/dist/contracts/tags.js +47 -37
- package/dist/contracts/teams.d.ts +86 -26
- package/dist/contracts/teams.js +90 -31
- 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 +3 -2
- 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 +72 -81
- package/dist/contracts/zero.js +80 -78
- 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 +12 -18
- package/dist/docs/agent-prose.js +73 -51
- package/dist/docs/agent-setup.d.ts +72 -41
- package/dist/docs/agent-setup.js +339 -436
- package/dist/docs/catalog.d.ts +153 -95
- package/dist/docs/catalog.js +163 -48
- package/dist/docs/error-docs.d.ts +2098 -1
- package/dist/docs/error-docs.js +421 -144
- 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 +120 -120
- package/dist/docs/skills.js +121 -73
- 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/test-helpers/fetch-stub.js +1 -0
- package/dist/utils/browser-credential.d.ts +13 -6
- package/dist/utils/browser-credential.js +14 -7
- package/dist/utils/build-settings.d.ts +21 -1
- package/dist/utils/build-settings.js +133 -15
- package/dist/utils/canonical-json.d.ts +3 -5
- package/dist/utils/canonical-json.js +4 -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 +3 -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 +126 -59
- package/dist/utils/dns-instructions.js +9 -9
- package/dist/utils/egress-policy.fixtures.json +1 -1
- package/dist/utils/error-code.js +1 -0
- package/dist/utils/generate-space-name.d.ts +3 -8
- package/dist/utils/generate-space-name.js +3 -8
- package/dist/utils/grant-decision.fixtures.json +2 -2
- package/dist/utils/grants.d.ts +0 -1
- package/dist/utils/grants.js +7 -8
- 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 +23 -26
- package/dist/utils/local-space-state.js +46 -46
- 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 +7 -6
- package/dist/utils/one-shot-replay.js +3 -5
- package/dist/utils/page-colors.d.ts +15 -19
- package/dist/utils/page-colors.js +23 -45
- package/dist/utils/page-fonts.d.ts +1 -1
- package/dist/utils/page-fonts.js +8 -20
- package/dist/utils/page-preview.d.ts +0 -1
- package/dist/utils/page-preview.js +0 -1
- package/dist/utils/pages.d.ts +33 -3
- package/dist/utils/pages.js +83 -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 +11 -17
- package/dist/utils/publish-policy.fixtures.json +11 -5
- package/dist/utils/publish-policy.js +65 -110
- 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 +9 -0
- package/dist/utils/runtime-upload.js +12 -0
- package/dist/utils/secure-local-file.d.ts +3 -3
- package/dist/utils/secure-local-file.js +23 -40
- package/dist/utils/space-config.d.ts +12 -15
- package/dist/utils/space-config.js +13 -16
- package/dist/utils/static-runtime-policy.d.ts +10 -7
- package/dist/utils/static-runtime-policy.fixtures.json +38 -41
- 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 +31 -132
- package/dist/utils/storage-policy.js +1 -2
- package/dist/utils/upload-session.d.ts +4 -1
- package/dist/utils/upload-session.js +41 -19
- 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 +13 -13
- package/dist/vocabulary.js +52 -27
- package/package.json +15 -18
- 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)) {
|
|
@@ -2,6 +2,7 @@ import { errorDocsUrl, errorTitle, PROBLEM_CONTENT_TYPE } from "../contracts/err
|
|
|
2
2
|
/** The original fetch implementation captured before a test installs a stub. */
|
|
3
3
|
export const realFetch = globalThis.fetch;
|
|
4
4
|
export function stubFetch(handler) {
|
|
5
|
+
// SAFETY: The wrapper accepts fetch's parameter types and always resolves to the handler's Response.
|
|
5
6
|
globalThis.fetch = (async (input, init) => handler(input instanceof Request && init === undefined ? input : new Request(input, init)));
|
|
6
7
|
}
|
|
7
8
|
export function restoreFetch() {
|
|
@@ -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);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SpaceCronConfig } from "../contracts/space-config.js";
|
|
1
2
|
export type BuildSettingsInput = {
|
|
2
3
|
fileNames: Iterable<string>;
|
|
3
4
|
packageJson?: string | null | undefined;
|
|
@@ -24,7 +25,7 @@ export type BuildAnalyzerCandidateRankInput = {
|
|
|
24
25
|
rootDirectory: string | null;
|
|
25
26
|
fileNames: Iterable<string>;
|
|
26
27
|
};
|
|
27
|
-
export declare function hasWorkspaceSignal(fileNames: Iterable<string>, packageJsonText?: string | null):
|
|
28
|
+
export declare function hasWorkspaceSignal(fileNames: Iterable<string>, packageJsonText?: string | null): boolean;
|
|
28
29
|
export declare function rankBuildAnalyzerCandidate(input: BuildAnalyzerCandidateRankInput): {
|
|
29
30
|
confidence: number;
|
|
30
31
|
reasons: string[];
|
|
@@ -43,15 +44,34 @@ export type PlatformConfig = {
|
|
|
43
44
|
redirects: Array<Record<string, unknown>>;
|
|
44
45
|
rewrites: Array<Record<string, unknown>>;
|
|
45
46
|
headers: Array<Record<string, unknown>>;
|
|
47
|
+
crons: Array<Record<string, unknown>>;
|
|
48
|
+
unsupportedFeatures: string[];
|
|
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[];
|
|
46
63
|
unsupportedFeatures: string[];
|
|
47
64
|
};
|
|
48
65
|
export declare function stringField(value: Record<string, unknown> | null, key: string): string | null;
|
|
49
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;
|
|
50
68
|
export declare function detectPlatformConfigs(input: Omit<BuildSettingsInput, "fileNames" | "packageJson">): PlatformConfig[];
|
|
51
69
|
export type PlatformRoutingConversion = {
|
|
52
70
|
redirectLines: string[];
|
|
53
71
|
headerBlocks: string[];
|
|
72
|
+
crons: SpaceCronConfig[];
|
|
54
73
|
notes: string[];
|
|
55
74
|
};
|
|
56
75
|
export declare function convertPlatformRouting(configs: PlatformConfig[]): PlatformRoutingConversion;
|
|
76
|
+
export declare const BUILD_MARKER_FILE_NAMES: Set<string>;
|
|
57
77
|
export declare function detectBuildSettings(input: BuildSettingsInput): DetectedBuildSettings;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { load as parseYaml } from "js-yaml";
|
|
2
2
|
import { parse as parseToml } from "smol-toml";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { SPACE_CONFIG_CAPS, spaceCronKey, spaceCronSchema, } from "../contracts/space-config.js";
|
|
3
5
|
export const BUILD_ANALYZER_CANDIDATE_ROOTS = ["apps", "packages", "sites", "docs"];
|
|
4
6
|
const PACKAGE_MANAGER_ONLY_FILES = new Set([
|
|
5
7
|
"package.json",
|
|
@@ -12,12 +14,15 @@ const PACKAGE_MANAGER_ONLY_FILES = new Set([
|
|
|
12
14
|
"Gemfile",
|
|
13
15
|
"Gemfile.lock",
|
|
14
16
|
]);
|
|
17
|
+
const WORKSPACE_ROOT_FILES = new Set([
|
|
18
|
+
"pnpm-workspace.yaml",
|
|
19
|
+
"turbo.json",
|
|
20
|
+
"nx.json",
|
|
21
|
+
"lerna.json",
|
|
22
|
+
]);
|
|
15
23
|
export function hasWorkspaceSignal(fileNames, packageJsonText) {
|
|
16
24
|
const names = new Set(fileNames);
|
|
17
|
-
if (names.has(
|
|
18
|
-
names.has("turbo.json") ||
|
|
19
|
-
names.has("nx.json") ||
|
|
20
|
-
names.has("lerna.json")) {
|
|
25
|
+
if ([...WORKSPACE_ROOT_FILES].some((file) => names.has(file))) {
|
|
21
26
|
return true;
|
|
22
27
|
}
|
|
23
28
|
if (!packageJsonText) {
|
|
@@ -25,10 +30,14 @@ export function hasWorkspaceSignal(fileNames, packageJsonText) {
|
|
|
25
30
|
}
|
|
26
31
|
try {
|
|
27
32
|
const parsed = JSON.parse(packageJsonText);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
if (Array.isArray(parsed.workspaces)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (!parsed.workspaces || typeof parsed.workspaces !== "object") {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
// SAFETY: The preceding non-null object check proves workspaces can carry an optional packages property.
|
|
40
|
+
return Array.isArray(parsed.workspaces.packages);
|
|
32
41
|
}
|
|
33
42
|
catch {
|
|
34
43
|
return false;
|
|
@@ -72,12 +81,15 @@ export function hasBuildAnalyzerCandidateSignal(input) {
|
|
|
72
81
|
input.detected.buildCommand !== null ||
|
|
73
82
|
names.has("index.html"));
|
|
74
83
|
}
|
|
84
|
+
const BUILD_ENVIRONMENT_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
85
|
+
const netlifyBuildEnvironmentValueSchema = z.union([z.string(), z.number(), z.boolean()]);
|
|
75
86
|
function parseJsonObject(value) {
|
|
76
87
|
if (!value) {
|
|
77
88
|
return null;
|
|
78
89
|
}
|
|
79
90
|
try {
|
|
80
91
|
const parsed = JSON.parse(value);
|
|
92
|
+
// SAFETY: The conditional establishes parsed is a non-null, non-array object before returning it as a dictionary.
|
|
81
93
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
82
94
|
? parsed
|
|
83
95
|
: null;
|
|
@@ -98,6 +110,7 @@ function parseTomlObject(value) {
|
|
|
98
110
|
}
|
|
99
111
|
try {
|
|
100
112
|
const parsed = parseToml(value);
|
|
113
|
+
// SAFETY: The conditional establishes parsed is a non-null, non-array object before returning it as a dictionary.
|
|
101
114
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
102
115
|
? parsed
|
|
103
116
|
: null;
|
|
@@ -112,6 +125,7 @@ function parseYamlObject(value) {
|
|
|
112
125
|
}
|
|
113
126
|
try {
|
|
114
127
|
const parsed = parseYaml(value);
|
|
128
|
+
// SAFETY: The conditional establishes parsed is a non-null, non-array object before returning it as a dictionary.
|
|
115
129
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
116
130
|
? parsed
|
|
117
131
|
: null;
|
|
@@ -130,6 +144,7 @@ export function numberField(value, key) {
|
|
|
130
144
|
}
|
|
131
145
|
function objectField(value, key) {
|
|
132
146
|
const field = value?.[key];
|
|
147
|
+
// SAFETY: The conditional establishes field is a non-null, non-array object before returning it as a dictionary.
|
|
133
148
|
return field && typeof field === "object" && !Array.isArray(field)
|
|
134
149
|
? field
|
|
135
150
|
: null;
|
|
@@ -140,6 +155,54 @@ function arrayField(value, key) {
|
|
|
140
155
|
? field.filter((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry))
|
|
141
156
|
: [];
|
|
142
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
|
+
}
|
|
143
206
|
function vercelConfig(value) {
|
|
144
207
|
const config = parseJsonObjectWithComments(value);
|
|
145
208
|
if (!config) {
|
|
@@ -155,11 +218,11 @@ function vercelConfig(value) {
|
|
|
155
218
|
redirects: arrayField(config, "redirects"),
|
|
156
219
|
rewrites: arrayField(config, "rewrites"),
|
|
157
220
|
headers: arrayField(config, "headers"),
|
|
221
|
+
crons: arrayField(config, "crons"),
|
|
158
222
|
unsupportedFeatures: [
|
|
159
223
|
...("functions" in config ? ["Vercel functions are not converted."] : []),
|
|
160
224
|
...("middleware" in config ? ["Vercel middleware is not converted."] : []),
|
|
161
225
|
...("images" in config ? ["Vercel image optimization config is not converted."] : []),
|
|
162
|
-
...("crons" in config ? ["Vercel cron jobs are not converted."] : []),
|
|
163
226
|
],
|
|
164
227
|
};
|
|
165
228
|
}
|
|
@@ -169,6 +232,7 @@ function netlifyConfig(value) {
|
|
|
169
232
|
return null;
|
|
170
233
|
}
|
|
171
234
|
const build = objectField(config, "build");
|
|
235
|
+
const nativeBuild = parseNetlifyBuildConfiguration(value);
|
|
172
236
|
return {
|
|
173
237
|
platform: "netlify",
|
|
174
238
|
rootDirectory: stringField(build, "base") ?? stringField(config, "base"),
|
|
@@ -179,10 +243,13 @@ function netlifyConfig(value) {
|
|
|
179
243
|
redirects: arrayField(config, "redirects"),
|
|
180
244
|
rewrites: [],
|
|
181
245
|
headers: arrayField(config, "headers"),
|
|
246
|
+
// Netlify has no scheduled-request config: its scheduled functions are
|
|
247
|
+
// declared in function code, which this importer does not read.
|
|
248
|
+
crons: [],
|
|
182
249
|
unsupportedFeatures: [
|
|
183
250
|
...("functions" in config ? ["Netlify functions are not converted."] : []),
|
|
184
251
|
...("edge_functions" in config ? ["Netlify edge functions are not converted."] : []),
|
|
185
|
-
...(
|
|
252
|
+
...(nativeBuild?.unsupportedFeatures ?? []),
|
|
186
253
|
...("processing" in config ? ["Netlify post-processing config is not converted."] : []),
|
|
187
254
|
],
|
|
188
255
|
};
|
|
@@ -224,6 +291,7 @@ function cloudflarePagesConfig(input) {
|
|
|
224
291
|
redirects: [],
|
|
225
292
|
rewrites: [],
|
|
226
293
|
headers: [],
|
|
294
|
+
crons: [],
|
|
227
295
|
unsupportedFeatures,
|
|
228
296
|
};
|
|
229
297
|
}
|
|
@@ -276,7 +344,7 @@ function vercelDestination(destination, splat) {
|
|
|
276
344
|
converted = converted.replaceAll("$1", ":splat");
|
|
277
345
|
}
|
|
278
346
|
else if (splat) {
|
|
279
|
-
// `:name`, `:name*`, or `:name
|
|
347
|
+
// `:name`, `:name*`, or `:name+`, but not a longer name sharing the prefix.
|
|
280
348
|
const reference = new RegExp(`\\${splat}[*+]?(?![A-Za-z0-9_])`, "g");
|
|
281
349
|
converted = converted.replace(reference, ":splat");
|
|
282
350
|
}
|
|
@@ -387,6 +455,7 @@ function convertVercelHeaderRule(rule, out) {
|
|
|
387
455
|
const entries = rule.headers;
|
|
388
456
|
const headers = (Array.isArray(entries) ? entries : [])
|
|
389
457
|
.map((entry) => {
|
|
458
|
+
// SAFETY: The conditional establishes entry is a non-null object before reading dictionary properties.
|
|
390
459
|
const header = entry && typeof entry === "object" ? entry : null;
|
|
391
460
|
const key = stringField(header, "key");
|
|
392
461
|
const value = header && typeof header.value === "string" ? header.value : null;
|
|
@@ -425,8 +494,43 @@ function convertNetlifyHeaderRule(rule, out) {
|
|
|
425
494
|
}
|
|
426
495
|
out.headerBlocks.push(headerBlock(matcher, headers));
|
|
427
496
|
}
|
|
497
|
+
/**
|
|
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.
|
|
503
|
+
*/
|
|
504
|
+
function convertVercelCron(rule, out) {
|
|
505
|
+
const path = stringField(rule, "path");
|
|
506
|
+
const schedule = stringField(rule, "schedule");
|
|
507
|
+
const label = `vercel.json cron ${path ?? "(missing path)"}`;
|
|
508
|
+
if (!path || !schedule) {
|
|
509
|
+
out.notes.push(`${label}: skipped — missing path or schedule.`);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const parsed = spaceCronSchema.safeParse({ path, schedule });
|
|
513
|
+
if (!parsed.success) {
|
|
514
|
+
out.notes.push(`${label}: skipped — ${parsed.error.issues.map((issue) => issue.message).join("; ")}`);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (out.crons.some((entry) => spaceCronKey(entry.path) === spaceCronKey(path))) {
|
|
518
|
+
out.notes.push(`${label}: skipped — another cron already schedules this path.`);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (out.crons.length >= SPACE_CONFIG_CAPS.crons) {
|
|
522
|
+
out.notes.push(`${label}: skipped — at most ${SPACE_CONFIG_CAPS.crons} crons are supported.`);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
out.crons.push(parsed.data);
|
|
526
|
+
}
|
|
428
527
|
export function convertPlatformRouting(configs) {
|
|
429
|
-
const out = {
|
|
528
|
+
const out = {
|
|
529
|
+
redirectLines: [],
|
|
530
|
+
headerBlocks: [],
|
|
531
|
+
crons: [],
|
|
532
|
+
notes: [],
|
|
533
|
+
};
|
|
430
534
|
for (const config of configs) {
|
|
431
535
|
if (config.platform === "vercel") {
|
|
432
536
|
for (const rule of config.redirects) {
|
|
@@ -438,6 +542,9 @@ export function convertPlatformRouting(configs) {
|
|
|
438
542
|
for (const rule of config.headers) {
|
|
439
543
|
convertVercelHeaderRule(rule, out);
|
|
440
544
|
}
|
|
545
|
+
for (const rule of config.crons) {
|
|
546
|
+
convertVercelCron(rule, out);
|
|
547
|
+
}
|
|
441
548
|
}
|
|
442
549
|
if (config.platform === "netlify") {
|
|
443
550
|
// Netlify expresses rewrites as 200-status redirects; both arrive in
|
|
@@ -542,9 +649,8 @@ const FRAMEWORKS = [
|
|
|
542
649
|
defaultBuildCommand: "vinxi build",
|
|
543
650
|
},
|
|
544
651
|
{
|
|
545
|
-
// Qwik City
|
|
546
|
-
//
|
|
547
|
-
// 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.
|
|
548
654
|
preset: "qwik",
|
|
549
655
|
dependency: "@builder.io/qwik-city",
|
|
550
656
|
outputDirectory: "dist",
|
|
@@ -671,6 +777,18 @@ const FRAMEWORKS = [
|
|
|
671
777
|
defaultBuildCommand: "bundle exec middleman build",
|
|
672
778
|
},
|
|
673
779
|
];
|
|
780
|
+
// Every file name the build analyzer keys on. An archive carrying any of them
|
|
781
|
+
// is a project to build, not a website.
|
|
782
|
+
export const BUILD_MARKER_FILE_NAMES = new Set([
|
|
783
|
+
...PACKAGE_MANAGER_ONLY_FILES,
|
|
784
|
+
...WORKSPACE_ROOT_FILES,
|
|
785
|
+
"vercel.json",
|
|
786
|
+
"netlify.toml",
|
|
787
|
+
"wrangler.toml",
|
|
788
|
+
"wrangler.json",
|
|
789
|
+
"wrangler.jsonc",
|
|
790
|
+
...FRAMEWORKS.flatMap((framework) => framework.configFiles ?? []),
|
|
791
|
+
]);
|
|
674
792
|
function frameworkForPreset(frameworkPreset) {
|
|
675
793
|
return FRAMEWORKS.find((framework) => framework.preset === frameworkPreset) ?? null;
|
|
676
794
|
}
|
|
@@ -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,17 +1,16 @@
|
|
|
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)) {
|
|
12
10
|
return `[${value.map(canonicalJson).join(",")}]`;
|
|
13
11
|
}
|
|
14
12
|
if (value && typeof value === "object") {
|
|
13
|
+
// SAFETY: This branch establishes value is a non-null object before enumerating its entries.
|
|
15
14
|
return `{${Object.entries(value)
|
|
16
15
|
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
17
16
|
.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
|
|
@@ -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;
|
|
@@ -10,15 +10,15 @@ export async function mapConcurrent(items, limit, mapper) {
|
|
|
10
10
|
if (index >= items.length) {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
|
+
// SAFETY: the bounds check above proves this index addresses an item in the input array.
|
|
13
14
|
results[index] = await mapper(items[index], index);
|
|
14
15
|
await worker();
|
|
15
16
|
}
|
|
16
17
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
17
18
|
return results;
|
|
18
19
|
}
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// 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.
|
|
22
22
|
export async function findConcurrent(items, limit, predicate) {
|
|
23
23
|
if (!Number.isInteger(limit) || limit < 1) {
|
|
24
24
|
throw new Error("Concurrency limit must be a positive integer.");
|