@spacefast/common 0.0.26 → 0.2.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.
Files changed (302) hide show
  1. package/dist/agents/connect-targets.d.ts +6 -146
  2. package/dist/agents/connect-targets.js +63 -295
  3. package/dist/agents/private-key-oauth.d.ts +31 -1
  4. package/dist/agents/private-key-oauth.js +124 -33
  5. package/dist/agents/registry/core.d.ts +292 -0
  6. package/dist/agents/registry/core.js +1388 -0
  7. package/dist/agents/registry/deeplink-probes.d.ts +23 -0
  8. package/dist/agents/registry/deeplink-probes.js +18 -0
  9. package/dist/agents/registry/node.d.ts +56 -0
  10. package/dist/agents/registry/node.js +339 -0
  11. package/dist/brand-assets-build.js +1 -2
  12. package/dist/brand-assets.js +4 -7
  13. package/dist/brand.d.ts +17 -0
  14. package/dist/brand.js +24 -4
  15. package/dist/config/domains.d.ts +55 -28
  16. package/dist/config/domains.js +85 -41
  17. package/dist/config/index.js +1 -2
  18. package/dist/contracts/abuse.js +9 -24
  19. package/dist/contracts/access-profiles.d.ts +86 -0
  20. package/dist/contracts/access-profiles.js +89 -0
  21. package/dist/contracts/access.d.ts +21 -27
  22. package/dist/contracts/access.js +33 -64
  23. package/dist/contracts/activity.d.ts +18 -1
  24. package/dist/contracts/activity.js +70 -26
  25. package/dist/contracts/analytics.js +8 -10
  26. package/dist/contracts/api-keys.d.ts +77 -52
  27. package/dist/contracts/api-keys.js +44 -24
  28. package/dist/contracts/application-journal.d.ts +216 -0
  29. package/dist/contracts/application-journal.js +167 -0
  30. package/dist/contracts/auth.d.ts +90 -17
  31. package/dist/contracts/auth.js +86 -17
  32. package/dist/contracts/beta.d.ts +9 -4
  33. package/dist/contracts/beta.js +20 -16
  34. package/dist/contracts/billing.js +7 -11
  35. package/dist/contracts/bootstrap.d.ts +791 -0
  36. package/dist/contracts/bootstrap.js +51 -0
  37. package/dist/contracts/builds.d.ts +192 -7
  38. package/dist/contracts/builds.js +84 -21
  39. package/dist/contracts/channels.d.ts +43 -0
  40. package/dist/contracts/channels.js +25 -9
  41. package/dist/contracts/cli.d.ts +7 -0
  42. package/dist/contracts/cli.js +6 -0
  43. package/dist/contracts/collab-public.d.ts +1 -1
  44. package/dist/contracts/collab-public.js +5 -8
  45. package/dist/contracts/comments.d.ts +45 -35
  46. package/dist/contracts/comments.js +107 -77
  47. package/dist/contracts/commerce.d.ts +165 -0
  48. package/dist/contracts/commerce.js +146 -0
  49. package/dist/contracts/common.d.ts +3 -3
  50. package/dist/contracts/common.js +18 -29
  51. package/dist/contracts/content-contract-verification.d.ts +521 -0
  52. package/dist/contracts/content-contract-verification.js +320 -0
  53. package/dist/contracts/content-platform-fixture.d.ts +1278 -0
  54. package/dist/contracts/content-platform-fixture.js +44 -0
  55. package/dist/contracts/content-program.d.ts +994 -0
  56. package/dist/contracts/content-program.js +658 -0
  57. package/dist/contracts/content-sync.d.ts +290 -0
  58. package/dist/contracts/content-sync.js +167 -0
  59. package/dist/contracts/content.d.ts +432 -0
  60. package/dist/contracts/content.js +303 -0
  61. package/dist/contracts/continuation.d.ts +17 -1
  62. package/dist/contracts/continuation.js +23 -23
  63. package/dist/contracts/countries.js +3 -4
  64. package/dist/contracts/crons.d.ts +17 -0
  65. package/dist/contracts/crons.js +33 -15
  66. package/dist/contracts/dashboard-prefs.d.ts +56 -0
  67. package/dist/contracts/dashboard-prefs.js +51 -0
  68. package/dist/contracts/device-auth.d.ts +234 -37
  69. package/dist/contracts/device-auth.js +267 -53
  70. package/dist/contracts/docs.d.ts +6 -10
  71. package/dist/contracts/docs.js +5 -13
  72. package/dist/contracts/domains.d.ts +499 -102
  73. package/dist/contracts/domains.js +169 -58
  74. package/dist/contracts/email-preferences.d.ts +3 -3
  75. package/dist/contracts/email-preferences.js +6 -7
  76. package/dist/contracts/enums.d.ts +26 -26
  77. package/dist/contracts/enums.js +61 -54
  78. package/dist/contracts/error-code-meta.d.ts +273 -7
  79. package/dist/contracts/error-code-meta.js +81 -5
  80. package/dist/contracts/error-codes.d.ts +5 -5
  81. package/dist/contracts/error-codes.js +79 -16
  82. package/dist/contracts/events.d.ts +100 -20
  83. package/dist/contracts/events.js +107 -20
  84. package/dist/contracts/execution.d.ts +47 -66
  85. package/dist/contracts/execution.js +51 -64
  86. package/dist/contracts/feature-lifecycle.d.ts +70 -14
  87. package/dist/contracts/feature-lifecycle.js +86 -15
  88. package/dist/contracts/features.d.ts +100 -37
  89. package/dist/contracts/features.js +21 -27
  90. package/dist/contracts/fixtures/content-platform-v1.json +1108 -0
  91. package/dist/contracts/frame-session.d.ts +192 -0
  92. package/dist/contracts/frame-session.js +164 -0
  93. package/dist/contracts/functions.d.ts +218 -211
  94. package/dist/contracts/functions.js +273 -260
  95. package/dist/contracts/grant-copy.d.ts +43 -0
  96. package/dist/contracts/grant-copy.js +44 -0
  97. package/dist/contracts/grants.d.ts +64 -56
  98. package/dist/contracts/grants.js +124 -23
  99. package/dist/contracts/ids.d.ts +4 -1
  100. package/dist/contracts/ids.js +4 -1
  101. package/dist/contracts/internal.d.ts +5 -4
  102. package/dist/contracts/internal.js +12 -3
  103. package/dist/contracts/mcp.d.ts +17 -0
  104. package/dist/contracts/mcp.js +23 -0
  105. package/dist/contracts/me.d.ts +6 -1
  106. package/dist/contracts/me.js +12 -6
  107. package/dist/contracts/notifications.d.ts +27 -29
  108. package/dist/contracts/notifications.js +9 -4
  109. package/dist/contracts/oauth-resources.d.ts +121 -10
  110. package/dist/contracts/oauth-resources.js +130 -11
  111. package/dist/contracts/oauth-scope-actions.d.ts +90 -0
  112. package/dist/contracts/oauth-scope-actions.js +126 -0
  113. package/dist/contracts/operations.d.ts +27 -4
  114. package/dist/contracts/operations.js +11 -5
  115. package/dist/contracts/pages.d.ts +29 -5
  116. package/dist/contracts/pages.js +22 -5
  117. package/dist/contracts/partner-notifications.d.ts +3 -0
  118. package/dist/contracts/partner-notifications.js +65 -0
  119. package/dist/contracts/{platform.d.ts → partner.d.ts} +29 -42
  120. package/dist/contracts/{platform.js → partner.js} +27 -40
  121. package/dist/contracts/plan-policy.d.ts +1 -1
  122. package/dist/contracts/principal-assertion.d.ts +43 -0
  123. package/dist/contracts/principal-assertion.js +67 -0
  124. package/dist/contracts/principals.d.ts +73 -0
  125. package/dist/contracts/principals.js +59 -0
  126. package/dist/contracts/privacy.d.ts +11 -0
  127. package/dist/contracts/privacy.js +17 -0
  128. package/dist/contracts/problem-document.js +3 -4
  129. package/dist/contracts/publish-archive.d.ts +116 -18
  130. package/dist/contracts/publish-archive.js +6 -3
  131. package/dist/contracts/push-new.d.ts +4 -4
  132. package/dist/contracts/quotas.js +3 -3
  133. package/dist/contracts/realtime.d.ts +52 -0
  134. package/dist/contracts/realtime.js +54 -0
  135. package/dist/contracts/repository-connections.d.ts +1 -1
  136. package/dist/contracts/repository-connections.js +6 -2
  137. package/dist/contracts/resources.d.ts +105 -14
  138. package/dist/contracts/resources.js +145 -21
  139. package/dist/contracts/route-inventory.d.ts +358 -0
  140. package/dist/contracts/route-inventory.js +141 -0
  141. package/dist/contracts/runtime-api.d.ts +112 -35
  142. package/dist/contracts/runtime-api.js +86 -66
  143. package/dist/contracts/runtime-app.d.ts +11 -21
  144. package/dist/contracts/runtime-app.js +39 -24
  145. package/dist/contracts/runtime-components.d.ts +143 -0
  146. package/dist/contracts/runtime-components.js +199 -0
  147. package/dist/contracts/runtime-db.d.ts +45 -19
  148. package/dist/contracts/runtime-db.js +85 -20
  149. package/dist/contracts/runtime-purge.js +3 -5
  150. package/dist/contracts/runtime-services.d.ts +43 -75
  151. package/dist/contracts/runtime-services.js +29 -49
  152. package/dist/contracts/runtime-storage.d.ts +14 -14
  153. package/dist/contracts/runtime-storage.js +28 -13
  154. package/dist/contracts/sf-config-v1.d.ts +14 -18
  155. package/dist/contracts/slugs.js +13 -4
  156. package/dist/contracts/space-config.d.ts +235 -28
  157. package/dist/contracts/space-config.js +246 -91
  158. package/dist/contracts/spaces.d.ts +360 -255
  159. package/dist/contracts/spaces.js +190 -122
  160. package/dist/contracts/superadmin-activity.d.ts +3 -43
  161. package/dist/contracts/superadmin-activity.js +6 -30
  162. package/dist/contracts/superadmin-emails.d.ts +1 -0
  163. package/dist/contracts/superadmin-emails.js +17 -1
  164. package/dist/contracts/superadmin-runtime.d.ts +199 -0
  165. package/dist/contracts/superadmin-runtime.js +141 -0
  166. package/dist/contracts/superadmin-search.d.ts +22 -2
  167. package/dist/contracts/superadmin-search.js +14 -6
  168. package/dist/contracts/superadmin-spaces.d.ts +435 -186
  169. package/dist/contracts/superadmin-spaces.js +131 -25
  170. package/dist/contracts/superadmin-teams.d.ts +3 -0
  171. package/dist/contracts/superadmin-teams.js +1 -2
  172. package/dist/contracts/superadmin-tenants.d.ts +627 -6
  173. package/dist/contracts/superadmin-tenants.js +32 -7
  174. package/dist/contracts/superadmin.d.ts +98 -16
  175. package/dist/contracts/superadmin.js +63 -10
  176. package/dist/contracts/tags.d.ts +85 -58
  177. package/dist/contracts/tags.js +44 -34
  178. package/dist/contracts/teams.d.ts +72 -9
  179. package/dist/contracts/teams.js +84 -25
  180. package/dist/contracts/tenants.d.ts +135 -0
  181. package/dist/contracts/tenants.js +120 -0
  182. package/dist/contracts/test-triggers.d.ts +70 -0
  183. package/dist/contracts/test-triggers.js +116 -0
  184. package/dist/contracts/theme-json.js +3 -8
  185. package/dist/contracts/transfers.d.ts +1 -0
  186. package/dist/contracts/transfers.js +1 -0
  187. package/dist/contracts/usage.d.ts +209 -0
  188. package/dist/contracts/usage.js +124 -12
  189. package/dist/contracts/variables.d.ts +2 -2
  190. package/dist/contracts/variables.js +9 -13
  191. package/dist/contracts/webhooks.d.ts +13 -5
  192. package/dist/contracts/webhooks.js +3 -1
  193. package/dist/contracts/zero.d.ts +57 -64
  194. package/dist/contracts/zero.js +68 -66
  195. package/dist/dashboard-paths/index.d.ts +24 -21
  196. package/dist/dashboard-paths/index.js +35 -47
  197. package/dist/dashboard-paths/route-reservations.generated.d.ts +2 -0
  198. package/dist/dashboard-paths/route-reservations.generated.js +36 -0
  199. package/dist/docs/agent-arrival.d.ts +92 -0
  200. package/dist/docs/agent-arrival.js +227 -0
  201. package/dist/docs/agent-output-policy.d.ts +6 -0
  202. package/dist/docs/agent-output-policy.js +22 -0
  203. package/dist/docs/agent-prose.d.ts +11 -17
  204. package/dist/docs/agent-prose.js +62 -37
  205. package/dist/docs/agent-setup.d.ts +72 -41
  206. package/dist/docs/agent-setup.js +336 -436
  207. package/dist/docs/catalog.d.ts +147 -89
  208. package/dist/docs/catalog.js +157 -42
  209. package/dist/docs/error-docs.d.ts +2102 -1
  210. package/dist/docs/error-docs.js +285 -16
  211. package/dist/docs/index-build.d.ts +0 -1
  212. package/dist/docs/index-build.js +2 -3
  213. package/dist/docs/page-context-prompt.d.ts +77 -0
  214. package/dist/docs/page-context-prompt.js +131 -0
  215. package/dist/docs/reference-urls.d.ts +46 -0
  216. package/dist/docs/reference-urls.js +79 -0
  217. package/dist/docs/search.d.ts +6 -8
  218. package/dist/docs/search.js +10 -13
  219. package/dist/docs/skill-distribution.d.ts +4 -1
  220. package/dist/docs/skill-distribution.js +10 -5
  221. package/dist/docs/skills.d.ts +117 -117
  222. package/dist/docs/skills.js +120 -72
  223. package/dist/docs/start-prompts.d.ts +45 -0
  224. package/dist/docs/start-prompts.js +62 -0
  225. package/dist/domain-exploration.js +9 -13
  226. package/dist/og-template.d.ts +56 -0
  227. package/dist/og-template.js +56 -0
  228. package/dist/slug-policy/index.js +5 -11
  229. package/dist/utils/browser-credential.d.ts +13 -6
  230. package/dist/utils/browser-credential.js +14 -7
  231. package/dist/utils/build-settings.d.ts +16 -0
  232. package/dist/utils/build-settings.js +63 -17
  233. package/dist/utils/canonical-json.d.ts +3 -5
  234. package/dist/utils/canonical-json.js +3 -5
  235. package/dist/utils/cast-transport.d.ts +5 -17
  236. package/dist/utils/cast-transport.js +6 -19
  237. package/dist/utils/claim-token.js +1 -2
  238. package/dist/utils/comment-avatars.js +4 -6
  239. package/dist/utils/concurrency.js +2 -3
  240. package/dist/utils/content-type.d.ts +7 -6
  241. package/dist/utils/content-type.js +73 -51
  242. package/dist/utils/credential-policy.d.ts +50 -6
  243. package/dist/utils/credential-policy.js +123 -57
  244. package/dist/utils/egress-policy.fixtures.json +1 -1
  245. package/dist/utils/generate-space-name.d.ts +3 -8
  246. package/dist/utils/generate-space-name.js +3 -8
  247. package/dist/utils/grants.d.ts +0 -1
  248. package/dist/utils/grants.js +4 -7
  249. package/dist/utils/gravatar.d.ts +2 -4
  250. package/dist/utils/gravatar.js +6 -12
  251. package/dist/utils/id-hints.js +14 -23
  252. package/dist/utils/idempotency.d.ts +3 -3
  253. package/dist/utils/idempotency.js +6 -7
  254. package/dist/utils/local-space-state.d.ts +15 -15
  255. package/dist/utils/local-space-state.js +34 -23
  256. package/dist/utils/oauth-signed-query.d.ts +3 -5
  257. package/dist/utils/oauth-signed-query.js +6 -9
  258. package/dist/utils/one-shot-replay.d.ts +3 -5
  259. package/dist/utils/one-shot-replay.js +3 -5
  260. package/dist/utils/page-colors.d.ts +4 -10
  261. package/dist/utils/page-colors.js +11 -35
  262. package/dist/utils/page-fonts.js +6 -11
  263. package/dist/utils/page-preview.d.ts +0 -1
  264. package/dist/utils/page-preview.js +0 -1
  265. package/dist/utils/pages.d.ts +22 -11
  266. package/dist/utils/pages.js +70 -30
  267. package/dist/utils/privacy-regions.d.ts +19 -0
  268. package/dist/utils/privacy-regions.js +91 -0
  269. package/dist/utils/publish-form-data.d.ts +4 -5
  270. package/dist/utils/publish-form-data.js +2 -3
  271. package/dist/utils/publish-policy.d.ts +10 -23
  272. package/dist/utils/publish-policy.js +63 -116
  273. package/dist/utils/query-client.d.ts +2 -0
  274. package/dist/utils/query-client.js +10 -1
  275. package/dist/utils/runtime-paths.d.ts +4 -4
  276. package/dist/utils/runtime-paths.js +6 -8
  277. package/dist/utils/runtime-upload.d.ts +4 -0
  278. package/dist/utils/runtime-upload.js +5 -0
  279. package/dist/utils/secure-local-file.d.ts +3 -3
  280. package/dist/utils/secure-local-file.js +18 -24
  281. package/dist/utils/space-config.d.ts +11 -14
  282. package/dist/utils/space-config.js +10 -16
  283. package/dist/utils/static-runtime-policy.d.ts +5 -8
  284. package/dist/utils/static-runtime-policy.generated.d.ts +16 -0
  285. package/dist/utils/static-runtime-policy.generated.js +70 -0
  286. package/dist/utils/static-runtime-policy.js +17 -84
  287. package/dist/utils/storage-policy.js +1 -2
  288. package/dist/utils/upload-session.js +8 -14
  289. package/dist/utils/version-path.js +11 -19
  290. package/dist/utils/wpcom-auth-redirects.d.ts +5 -14
  291. package/dist/utils/wpcom-auth-redirects.js +1 -4
  292. package/dist/vocabulary.d.ts +7 -7
  293. package/dist/vocabulary.js +30 -16
  294. package/package.json +14 -2
  295. package/dist/agents/client-registry.d.ts +0 -22
  296. package/dist/agents/client-registry.js +0 -41
  297. package/dist/docs/agent-handoff-document.d.ts +0 -15
  298. package/dist/docs/agent-handoff-document.js +0 -180
  299. package/dist/docs/agent-solutions.d.ts +0 -110
  300. package/dist/docs/agent-solutions.js +0 -183
  301. package/dist/utils/query-keys.d.ts +0 -84
  302. 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
- // Explicit user choices are rejected rather than silently reshaped. Case,
13
- // surrounding whitespace, and diacritics are the only accepted canonicalization.
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
- // contains `paypal`). Suggestion generators use this structured signal to
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 (matchingReservedSubstring(normalized) !== undefined ||
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
- * Builds a browser handoff URL around a short-lived, one-use exchange token.
3
- *
4
- * The durable credential never appears in this URL. The access origin consumes
5
- * the final path segment once and immediately redirects browsers to a clean
6
- * path-scoped session. `publicId` is used only by agent handoffs, whose clean
7
- * static document remains `/agent/<id>`.
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
- const browserHandoffSegmentPattern = /^[A-Za-z0-9_-]{16,512}$/;
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
- * Builds a browser handoff URL around a short-lived, one-use exchange token.
11
- *
12
- * The durable credential never appears in this URL. The access origin consumes
13
- * the final path segment once and immediately redirects browsers to a clean
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
- ...("plugins" in config ? ["Netlify build plugins are not converted."] : []),
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+` but not a longer name sharing the prefix.
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
- * Both sides are `{ path, schedule }` over a literal path and a five-field
449
- * crontab expression, so a valid Vercel cron converts verbatim there is no
450
- * translation to get wrong. What this guards is the tail: Vercel accepts paths
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 starters build with the repo's `build` script (`qwik build`
605
- // orchestrates the package.json build.* scripts, so there is no
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: package manifests, lockfiles,
734
- // workspace roots, and the platform and framework configs it detects a build
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
- * (which canonicalizes key order) digests identically to one freshly built in
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
- * Every digest that has to agree across the control plane, the CLI and the
7
- * capsule compiler is built on this exact spelling change it and stored
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
- * (which canonicalizes key order) digests identically to one freshly built in
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
- * Every digest that has to agree across the control plane, the CLI and the
7
- * capsule compiler is built on this exact spelling change it and stored
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. Cast broadcasts
10
- * anything computed outside a channel process Space notices, REST-published
11
- * room events to the topic `Cast.RoomTopic.for/2` builds, so a client that
12
- * encodes the room key any other way joins a real and permanently silent room.
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
- * `~` goes first in intent: the map is applied in one pass, so an already
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. Cast broadcasts
23
- * anything computed outside a channel process Space notices, REST-published
24
- * room events to the topic `Cast.RoomTopic.for/2` builds, so a client that
25
- * encodes the room key any other way joins a real and permanently silent room.
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; it authenticates as a `Bearer` header and rides claim-link
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. A host
25
- // with a non-default port contains a colon and would otherwise become a
26
- // made-up scheme (`javascript:8443/...`, for example). Photon addresses an
27
- // HTTPS source with `ssl=1`; without it, an HTTPS and HTTP source collapse
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
- // Runs predicates in bounded batches while preserving Array.find semantics:
21
- // the earliest matching item wins, and only a rejection before that match is
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
- * Map a file extension to a Content-Type header value. Returns
3
- * `application/octet-stream` when the extension is not recognized.
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
- // Preserve the public uploader/runtime policy where the database's preferred
4
- // server-oriented type differs from the established browser-facing type.
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
- * Map a file extension to a Content-Type header value. Returns
22
- * `application/octet-stream` when the extension is not recognized.
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 function contentTypeForBytes(filePath, bytes) {
74
- if (hasPrefix(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
75
- return "image/png";
76
- }
77
- if (hasPrefix(bytes, [0xff, 0xd8, 0xff])) {
78
- return "image/jpeg";
79
- }
80
- if (hasAsciiPrefix(bytes, "GIF87a") || hasAsciiPrefix(bytes, "GIF89a")) {
81
- return "image/gif";
82
- }
83
- if (hasAsciiPrefix(bytes, "RIFF") && hasAsciiPrefix(bytes.slice(8), "WEBP")) {
84
- return "image/webp";
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 "image/avif";
88
- }
89
- if (hasPrefix(bytes, [0x00, 0x00, 0x01, 0x00])) {
90
- return "image/x-icon";
91
- }
92
- if (hasAsciiPrefix(bytes, "%PDF-")) {
93
- return "application/pdf";
94
- }
95
- if (hasPrefix(bytes, [0x1f, 0x8b, 0x08])) {
96
- return "application/gzip";
97
- }
98
- if (hasAsciiPrefix(bytes, "wOFF")) {
99
- return "font/woff";
100
- }
101
- if (hasAsciiPrefix(bytes, "wOF2")) {
102
- return "font/woff2";
103
- }
104
- if (hasPrefix(bytes, [0x00, 0x01, 0x00, 0x00]) || hasAsciiPrefix(bytes, "true")) {
105
- return "font/ttf";
106
- }
107
- if (hasAsciiPrefix(bytes, "OTTO")) {
108
- return "font/otf";
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 (textType && bytesLookTextual(bytes)) {
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
+ }