@spacefast/common 0.0.5 → 0.0.6

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 (150) hide show
  1. package/dist/agents/connect-targets.d.ts +133 -0
  2. package/dist/agents/connect-targets.js +224 -0
  3. package/dist/brand.d.ts +1 -0
  4. package/dist/brand.js +5 -0
  5. package/dist/config/domains.d.ts +12 -6
  6. package/dist/config/domains.js +30 -38
  7. package/dist/contracts/access.d.ts +825 -125
  8. package/dist/contracts/access.js +771 -148
  9. package/dist/contracts/activity.d.ts +1 -1
  10. package/dist/contracts/activity.js +28 -2
  11. package/dist/contracts/annotations.d.ts +2 -4
  12. package/dist/contracts/api-keys.d.ts +3 -4
  13. package/dist/contracts/api-keys.js +9 -6
  14. package/dist/contracts/archives.d.ts +8 -2
  15. package/dist/contracts/archives.js +11 -4
  16. package/dist/contracts/billing.d.ts +0 -9
  17. package/dist/contracts/builds.d.ts +25 -21
  18. package/dist/contracts/builds.js +12 -0
  19. package/dist/contracts/common.d.ts +2 -0
  20. package/dist/contracts/common.js +4 -0
  21. package/dist/contracts/continuation.d.ts +0 -3
  22. package/dist/contracts/countries.d.ts +2 -0
  23. package/dist/contracts/countries.js +261 -0
  24. package/dist/contracts/deployments.d.ts +34 -34
  25. package/dist/contracts/device-auth.d.ts +4 -11
  26. package/dist/contracts/device-auth.js +4 -0
  27. package/dist/contracts/docs.d.ts +119 -0
  28. package/dist/contracts/docs.js +51 -0
  29. package/dist/contracts/domains.d.ts +96 -54
  30. package/dist/contracts/domains.js +26 -4
  31. package/dist/contracts/enums.d.ts +15 -16
  32. package/dist/contracts/enums.js +25 -14
  33. package/dist/contracts/error-code-meta.d.ts +72 -0
  34. package/dist/contracts/error-code-meta.js +96 -10
  35. package/dist/contracts/error-codes.d.ts +1 -1
  36. package/dist/contracts/error-codes.js +14 -0
  37. package/dist/contracts/events.d.ts +1 -1
  38. package/dist/contracts/features.d.ts +76 -6
  39. package/dist/contracts/features.js +178 -13
  40. package/dist/contracts/git.d.ts +2 -8
  41. package/dist/contracts/ids.d.ts +5 -0
  42. package/dist/contracts/ids.js +5 -0
  43. package/dist/contracts/mcp.d.ts +8 -10
  44. package/dist/contracts/me.js +1 -1
  45. package/dist/contracts/oauth-resources.d.ts +19 -8
  46. package/dist/contracts/oauth-resources.js +44 -13
  47. package/dist/contracts/platform.d.ts +0 -102
  48. package/dist/contracts/platform.js +0 -50
  49. package/dist/contracts/push-new.d.ts +4 -4
  50. package/dist/contracts/quotas.d.ts +2 -0
  51. package/dist/contracts/quotas.js +8 -0
  52. package/dist/contracts/repository-connections.d.ts +6 -8
  53. package/dist/contracts/resources.d.ts +97 -13
  54. package/dist/contracts/resources.js +26 -2
  55. package/dist/contracts/routes.d.ts +0 -2
  56. package/dist/contracts/runtime-api.d.ts +361 -39
  57. package/dist/contracts/runtime-api.js +275 -6
  58. package/dist/contracts/sites.d.ts +19 -2
  59. package/dist/contracts/sites.js +21 -0
  60. package/dist/contracts/space-config.d.ts +201 -2
  61. package/dist/contracts/space-config.js +135 -5
  62. package/dist/contracts/spaces.d.ts +364 -193
  63. package/dist/contracts/spaces.js +40 -8
  64. package/dist/contracts/superadmin-emails.d.ts +4 -2
  65. package/dist/contracts/superadmin-emails.js +8 -0
  66. package/dist/contracts/superadmin-queues.d.ts +26 -26
  67. package/dist/contracts/superadmin-spaces.d.ts +170 -42
  68. package/dist/contracts/superadmin.d.ts +6 -93
  69. package/dist/contracts/superadmin.js +6 -14
  70. package/dist/contracts/tags.d.ts +10 -9
  71. package/dist/contracts/tags.js +2 -1
  72. package/dist/contracts/teams.d.ts +21 -22
  73. package/dist/contracts/teams.js +0 -1
  74. package/dist/contracts/theme-json.d.ts +30 -0
  75. package/dist/contracts/theme-json.js +48 -0
  76. package/dist/contracts/variables.d.ts +0 -2
  77. package/dist/contracts/variables.js +0 -5
  78. package/dist/contracts/webhooks.d.ts +2 -0
  79. package/dist/contracts/webhooks.js +1 -1
  80. package/dist/contracts/zero.d.ts +5 -10
  81. package/dist/docs/agent-prose.d.ts +43 -3
  82. package/dist/docs/agent-prose.js +146 -7
  83. package/dist/docs/agent-setup.js +45 -13
  84. package/dist/docs/agent-solutions.d.ts +5 -5
  85. package/dist/docs/agent-solutions.js +2 -2
  86. package/dist/docs/catalog.d.ts +650 -0
  87. package/dist/docs/catalog.js +758 -0
  88. package/dist/docs/error-docs.d.ts +0 -2
  89. package/dist/docs/error-docs.js +77 -35
  90. package/dist/docs/index-build.d.ts +17 -0
  91. package/dist/docs/index-build.js +24 -0
  92. package/dist/docs/search.d.ts +54 -0
  93. package/dist/docs/search.js +96 -0
  94. package/dist/docs/skill-distribution.d.ts +5 -3
  95. package/dist/docs/skill-distribution.js +8 -8
  96. package/dist/utils/access-match.d.ts +39 -0
  97. package/dist/utils/access-match.js +296 -0
  98. package/dist/utils/auth-redirect.d.ts +3 -1
  99. package/dist/utils/auth-redirect.js +21 -1
  100. package/dist/utils/build-settings.d.ts +6 -0
  101. package/dist/utils/build-settings.js +429 -46
  102. package/dist/utils/claim-token.d.ts +8 -0
  103. package/dist/utils/claim-token.js +23 -2
  104. package/dist/utils/content-type.d.ts +1 -0
  105. package/dist/utils/content-type.js +1 -1
  106. package/dist/utils/credential-policy.d.ts +54 -0
  107. package/dist/utils/{access-policy.js → credential-policy.js} +28 -2
  108. package/dist/utils/gate-theme.d.ts +61 -0
  109. package/dist/utils/gate-theme.js +217 -0
  110. package/dist/utils/git-repository.d.ts +0 -1
  111. package/dist/utils/git-repository.js +0 -3
  112. package/dist/utils/id-hints.d.ts +8 -0
  113. package/dist/utils/id-hints.js +61 -0
  114. package/dist/utils/local-space-state.d.ts +91 -0
  115. package/dist/utils/local-space-state.js +251 -0
  116. package/dist/utils/oauth-signed-query.d.ts +8 -0
  117. package/dist/utils/oauth-signed-query.js +26 -0
  118. package/dist/utils/publish-detection.d.ts +0 -8
  119. package/dist/utils/publish-detection.js +0 -93
  120. package/dist/utils/publish-policy.d.ts +6 -5
  121. package/dist/utils/publish-policy.js +71 -7
  122. package/dist/utils/query-keys.d.ts +15 -4
  123. package/dist/utils/query-keys.js +27 -4
  124. package/dist/utils/runtime-paths.d.ts +0 -1
  125. package/dist/utils/runtime-paths.js +0 -1
  126. package/dist/utils/runtime-upload-batch.d.ts +18 -0
  127. package/dist/utils/runtime-upload-batch.js +90 -0
  128. package/dist/utils/runtime-upload.d.ts +2 -0
  129. package/dist/utils/runtime-upload.js +5 -0
  130. package/dist/utils/space-config.d.ts +4 -0
  131. package/dist/utils/space-config.js +10 -1
  132. package/dist/utils/space-theme.d.ts +2 -0
  133. package/dist/utils/space-theme.js +9 -0
  134. package/dist/utils/static-runtime-policy.d.ts +0 -21
  135. package/dist/utils/static-runtime-policy.js +19 -103
  136. package/dist/utils/upload-session.d.ts +1 -1
  137. package/dist/utils/wpcom-auth-redirects.d.ts +32 -2
  138. package/dist/utils/wpcom-auth-redirects.js +26 -10
  139. package/dist/vocabulary.d.ts +130 -30
  140. package/dist/vocabulary.js +77 -36
  141. package/package.json +16 -1
  142. package/dist/contracts/account.d.ts +0 -28
  143. package/dist/contracts/account.js +0 -54
  144. package/dist/contracts/intercom.d.ts +0 -6
  145. package/dist/contracts/intercom.js +0 -5
  146. package/dist/contracts/telemetry.d.ts +0 -2
  147. package/dist/contracts/telemetry.js +0 -1
  148. package/dist/utils/access-policy.d.ts +0 -39
  149. package/dist/utils/error-display.d.ts +0 -7
  150. package/dist/utils/error-display.js +0 -42
@@ -216,6 +216,224 @@ export function detectPlatformConfigs(input) {
216
216
  cloudflarePagesConfig(input),
217
217
  ].filter((config) => Boolean(config));
218
218
  }
219
+ const SUPPORTED_REDIRECT_STATUSES = new Set([200, 301, 302, 303, 307, 308, 404]);
220
+ const NETLIFY_CONDITION_KEYS = new Map([
221
+ ["country", "Country"],
222
+ ["language", "Language"],
223
+ ["cookie", "Cookie"],
224
+ ]);
225
+ const VERCEL_TRAILING_REGEX_SPLAT = /\(\.\*\)$/;
226
+ const VERCEL_TRAILING_NAMED_SPLAT = /:([A-Za-z][A-Za-z0-9_]*)[*+]$/;
227
+ const UNSUPPORTED_PATTERN_CHARS = /[(){}+?!$^[\]|\\]/;
228
+ function booleanField(value, key) {
229
+ const field = value[key];
230
+ return typeof field === "boolean" ? field : null;
231
+ }
232
+ // Converts a Vercel path-to-regexp source into `_redirects` pattern syntax.
233
+ // Supported: literal segments, `:name` segment params, and one trailing
234
+ // catch-all (`:name*`, `:name+`, or `(.*)`). Anything else returns null.
235
+ function vercelSourceToPattern(source) {
236
+ let splat = null;
237
+ let pattern = source;
238
+ const regexSplat = pattern.match(VERCEL_TRAILING_REGEX_SPLAT);
239
+ if (regexSplat) {
240
+ pattern = pattern.slice(0, -regexSplat[0].length);
241
+ splat = "$1";
242
+ }
243
+ else {
244
+ const namedSplat = pattern.match(VERCEL_TRAILING_NAMED_SPLAT);
245
+ if (namedSplat?.[1]) {
246
+ pattern = pattern.slice(0, -namedSplat[0].length);
247
+ splat = `:${namedSplat[1]}`;
248
+ }
249
+ }
250
+ if (UNSUPPORTED_PATTERN_CHARS.test(pattern) || pattern.includes("*")) {
251
+ return null;
252
+ }
253
+ return { pattern: splat === null ? pattern : `${pattern}*`, splat };
254
+ }
255
+ function vercelDestination(destination, splat) {
256
+ let converted = destination;
257
+ if (splat === "$1") {
258
+ converted = converted.replaceAll("$1", ":splat");
259
+ }
260
+ else if (splat) {
261
+ // `:name`, `:name*`, or `:name+` — but not a longer name sharing the prefix.
262
+ const reference = new RegExp(`\\${splat}[*+]?(?![A-Za-z0-9_])`, "g");
263
+ converted = converted.replace(reference, ":splat");
264
+ }
265
+ // Unresolved regex captures cannot be expressed in `_redirects`.
266
+ return /\$\d/.test(converted) ? null : converted;
267
+ }
268
+ function vercelRedirectStatus(rule) {
269
+ const statusCode = numberField(rule, "statusCode");
270
+ if (statusCode !== null) {
271
+ return statusCode;
272
+ }
273
+ return booleanField(rule, "permanent") ? 308 : 307;
274
+ }
275
+ function convertVercelRoutingRule(rule, kind, out) {
276
+ const source = stringField(rule, "source");
277
+ const destination = stringField(rule, "destination");
278
+ const label = `vercel.json ${kind} ${source ?? "(missing source)"}`;
279
+ if (!source || !destination) {
280
+ out.notes.push(`${label}: skipped — missing source or destination.`);
281
+ return;
282
+ }
283
+ if ("has" in rule || "missing" in rule) {
284
+ out.notes.push(`${label}: skipped — has/missing conditions are not supported.`);
285
+ return;
286
+ }
287
+ const converted = vercelSourceToPattern(source);
288
+ if (!converted) {
289
+ out.notes.push(`${label}: skipped — the source pattern does not map to _redirects syntax.`);
290
+ return;
291
+ }
292
+ const convertedDestination = vercelDestination(destination, converted.splat);
293
+ if (!convertedDestination) {
294
+ out.notes.push(`${label}: skipped — the destination uses unsupported regex captures.`);
295
+ return;
296
+ }
297
+ const status = kind === "rewrite" ? 200 : vercelRedirectStatus(rule);
298
+ if (!SUPPORTED_REDIRECT_STATUSES.has(status)) {
299
+ out.notes.push(`${label}: skipped — status ${status} is not supported.`);
300
+ return;
301
+ }
302
+ out.redirectLines.push(`${converted.pattern} ${convertedDestination} ${status}`);
303
+ }
304
+ function convertNetlifyRedirect(rule, out) {
305
+ const from = stringField(rule, "from");
306
+ const to = stringField(rule, "to");
307
+ const label = `netlify.toml redirect ${from ?? "(missing from)"}`;
308
+ if (!from || !to) {
309
+ out.notes.push(`${label}: skipped — missing from or to.`);
310
+ return;
311
+ }
312
+ if ("signed" in rule || "headers" in rule) {
313
+ out.notes.push(`${label}: skipped — signed proxying and proxy headers are not supported.`);
314
+ return;
315
+ }
316
+ const status = numberField(rule, "status") ?? 301;
317
+ if (!SUPPORTED_REDIRECT_STATUSES.has(status)) {
318
+ out.notes.push(`${label}: skipped — status ${status} is not supported.`);
319
+ return;
320
+ }
321
+ const queryTokens = [];
322
+ const query = objectField(rule, "query");
323
+ if (query) {
324
+ for (const [name, capture] of Object.entries(query)) {
325
+ if (typeof capture !== "string" || !capture.startsWith(":")) {
326
+ out.notes.push(`${label}: skipped — literal query matches are not supported.`);
327
+ return;
328
+ }
329
+ queryTokens.push(`${name}=${capture}`);
330
+ }
331
+ }
332
+ const conditionTokens = [];
333
+ const conditions = objectField(rule, "conditions");
334
+ if (conditions) {
335
+ for (const [name, values] of Object.entries(conditions)) {
336
+ const mapped = NETLIFY_CONDITION_KEYS.get(name.toLowerCase());
337
+ const list = Array.isArray(values)
338
+ ? values.filter((value) => typeof value === "string")
339
+ : null;
340
+ if (!mapped || !list || list.length === 0) {
341
+ out.notes.push(`${label}: skipped — the ${name} condition is not supported.`);
342
+ return;
343
+ }
344
+ conditionTokens.push(`${mapped}=${list.join(",")}`);
345
+ }
346
+ }
347
+ const force = booleanField(rule, "force") ? "!" : "";
348
+ out.redirectLines.push([from, ...queryTokens, to, `${status}${force}`, ...conditionTokens].join(" "));
349
+ }
350
+ function headerBlock(matcher, headers) {
351
+ return [matcher, ...headers.map(([name, value]) => ` ${name}: ${value}`)].join("\n");
352
+ }
353
+ function convertVercelHeaderRule(rule, out) {
354
+ const source = stringField(rule, "source");
355
+ const label = `vercel.json headers ${source ?? "(missing source)"}`;
356
+ if (!source) {
357
+ out.notes.push(`${label}: skipped — missing source.`);
358
+ return;
359
+ }
360
+ if ("has" in rule || "missing" in rule) {
361
+ out.notes.push(`${label}: skipped — has/missing conditions are not supported.`);
362
+ return;
363
+ }
364
+ const converted = vercelSourceToPattern(source);
365
+ if (!converted) {
366
+ out.notes.push(`${label}: skipped — the source pattern does not map to _headers syntax.`);
367
+ return;
368
+ }
369
+ const entries = rule.headers;
370
+ const headers = (Array.isArray(entries) ? entries : [])
371
+ .map((entry) => {
372
+ const header = entry && typeof entry === "object" ? entry : null;
373
+ const key = stringField(header, "key");
374
+ const value = header && typeof header.value === "string" ? header.value : null;
375
+ return key && value !== null ? [key, value] : null;
376
+ })
377
+ .filter((entry) => entry !== null);
378
+ if (headers.length === 0) {
379
+ out.notes.push(`${label}: skipped — no header values.`);
380
+ return;
381
+ }
382
+ out.headerBlocks.push(headerBlock(converted.pattern, headers));
383
+ }
384
+ function convertNetlifyHeaderRule(rule, out) {
385
+ const matcher = stringField(rule, "for");
386
+ const label = `netlify.toml headers ${matcher ?? "(missing for)"}`;
387
+ const values = objectField(rule, "values");
388
+ if (!matcher || !values) {
389
+ out.notes.push(`${label}: skipped — missing for or values.`);
390
+ return;
391
+ }
392
+ const headers = Object.entries(values)
393
+ .map((entry) => {
394
+ const [name, value] = entry;
395
+ if (typeof value === "string") {
396
+ return [name, value];
397
+ }
398
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
399
+ return [name, value.join(", ")];
400
+ }
401
+ return null;
402
+ })
403
+ .filter((entry) => entry !== null);
404
+ if (headers.length === 0) {
405
+ out.notes.push(`${label}: skipped — no header values.`);
406
+ return;
407
+ }
408
+ out.headerBlocks.push(headerBlock(matcher, headers));
409
+ }
410
+ export function convertPlatformRouting(configs) {
411
+ const out = { redirectLines: [], headerBlocks: [], notes: [] };
412
+ for (const config of configs) {
413
+ if (config.platform === "vercel") {
414
+ for (const rule of config.redirects) {
415
+ convertVercelRoutingRule(rule, "redirect", out);
416
+ }
417
+ for (const rule of config.rewrites) {
418
+ convertVercelRoutingRule(rule, "rewrite", out);
419
+ }
420
+ for (const rule of config.headers) {
421
+ convertVercelHeaderRule(rule, out);
422
+ }
423
+ }
424
+ if (config.platform === "netlify") {
425
+ // Netlify expresses rewrites as 200-status redirects; both arrive in
426
+ // `redirects`, already in (almost) `_redirects` syntax.
427
+ for (const rule of config.redirects) {
428
+ convertNetlifyRedirect(rule, out);
429
+ }
430
+ for (const rule of config.headers) {
431
+ convertNetlifyHeaderRule(rule, out);
432
+ }
433
+ }
434
+ }
435
+ return out;
436
+ }
219
437
  function parsePackageJson(value) {
220
438
  const parsed = parseJsonObject(value);
221
439
  if (!parsed) {
@@ -243,50 +461,219 @@ function packageManagerForFiles(files) {
243
461
  return "yarn";
244
462
  return "npm";
245
463
  }
464
+ const FRAMEWORKS = [
465
+ {
466
+ // Nextra is Next.js underneath (its docs deploy via `next build` +
467
+ // static export to `out`), so it must supersede the bare `next` row.
468
+ preset: "nextra",
469
+ dependency: "nextra",
470
+ outputDirectory: "out",
471
+ defaultBuildCommand: "next build",
472
+ },
473
+ {
474
+ preset: "next",
475
+ dependency: "next",
476
+ outputDirectory: "out",
477
+ defaultBuildCommand: "next build",
478
+ },
479
+ {
480
+ preset: "tanstack-start",
481
+ dependency: "@tanstack/react-start",
482
+ outputDirectory: "dist/client",
483
+ },
484
+ {
485
+ // Nuxt 3 `nuxt generate` pre-renders the site into .output/public
486
+ // (dist/ is just a compatibility symlink to it).
487
+ preset: "nuxt",
488
+ dependency: "nuxt",
489
+ outputDirectory: ".output/public",
490
+ defaultBuildCommand: "nuxt generate",
491
+ },
492
+ {
493
+ preset: "astro",
494
+ dependency: "astro",
495
+ configFiles: [
496
+ "astro.config.mjs",
497
+ "astro.config.js",
498
+ "astro.config.ts",
499
+ "astro.config.cjs",
500
+ "astro.config.mts",
501
+ "astro.config.cts",
502
+ ],
503
+ outputDirectory: "dist",
504
+ defaultBuildCommand: "astro build",
505
+ },
506
+ {
507
+ // VitePress scaffolds into ./docs and builds to <srcDir>/.vitepress/dist.
508
+ // Vite-based, so it must come before the bare `vite` row.
509
+ preset: "vitepress",
510
+ dependency: "vitepress",
511
+ outputDirectory: "docs/.vitepress/dist",
512
+ defaultBuildCommand: "vitepress build docs",
513
+ },
514
+ {
515
+ preset: "sveltekit",
516
+ dependency: "@sveltejs/kit",
517
+ outputDirectory: "build",
518
+ },
519
+ {
520
+ // SolidStart builds through vinxi; static assets land in .output/public.
521
+ preset: "solid-start",
522
+ dependency: "@solidjs/start",
523
+ outputDirectory: ".output/public",
524
+ defaultBuildCommand: "vinxi build",
525
+ },
526
+ {
527
+ // Qwik City starters build with the repo's `build` script (`qwik build`
528
+ // orchestrates the package.json build.* scripts, so there is no
529
+ // meaningful bare-binary default); static output lands in dist.
530
+ preset: "qwik",
531
+ dependency: "@builder.io/qwik-city",
532
+ outputDirectory: "dist",
533
+ },
534
+ {
535
+ preset: "gatsby",
536
+ dependency: "gatsby",
537
+ outputDirectory: "public",
538
+ },
539
+ {
540
+ preset: "remix",
541
+ dependency: "@remix-run/dev",
542
+ outputDirectory: "build/client",
543
+ },
544
+ {
545
+ preset: "docusaurus",
546
+ dependency: "@docusaurus/core",
547
+ outputDirectory: "build",
548
+ },
549
+ {
550
+ // RedwoodJS: `yarn rw build web` emits the static web side to web/dist.
551
+ preset: "redwoodjs",
552
+ dependency: "@redwoodjs/core",
553
+ configFiles: ["redwood.toml"],
554
+ outputDirectory: "web/dist",
555
+ defaultBuildCommand: "redwood build web",
556
+ },
557
+ {
558
+ // Angular CLI: `ng build` emits under dist/ (dist/<project>/browser on
559
+ // modern application builds; the runtime candidate scan finds the leaf).
560
+ preset: "angular",
561
+ dependency: "@angular/cli",
562
+ configFiles: ["angular.json"],
563
+ outputDirectory: "dist",
564
+ defaultBuildCommand: "ng build",
565
+ },
566
+ {
567
+ preset: "ember",
568
+ dependency: "ember-cli",
569
+ configFiles: ["ember-cli-build.js"],
570
+ outputDirectory: "dist",
571
+ defaultBuildCommand: "ember build",
572
+ },
573
+ {
574
+ preset: "gridsome",
575
+ dependency: "gridsome",
576
+ configFiles: ["gridsome.config.js"],
577
+ outputDirectory: "dist",
578
+ defaultBuildCommand: "gridsome build",
579
+ },
580
+ {
581
+ preset: "eleventy",
582
+ dependency: "@11ty/eleventy",
583
+ configFiles: [
584
+ ".eleventy.js",
585
+ "eleventy.config.js",
586
+ "eleventy.config.mjs",
587
+ "eleventy.config.cjs",
588
+ ],
589
+ outputDirectory: "_site",
590
+ defaultBuildCommand: "eleventy",
591
+ },
592
+ {
593
+ // Hexo is npm-managed, so the dependency is the signal; its _config.yml
594
+ // deliberately stays out of configFiles to avoid clashing with jekyll.
595
+ preset: "hexo",
596
+ dependency: "hexo",
597
+ outputDirectory: "public",
598
+ defaultBuildCommand: "hexo generate",
599
+ },
600
+ {
601
+ preset: "vite",
602
+ dependency: "vite",
603
+ configFiles: [
604
+ "vite.config.js",
605
+ "vite.config.ts",
606
+ "vite.config.mjs",
607
+ "vite.config.mts",
608
+ "vite.config.cjs",
609
+ "vite.config.cts",
610
+ ],
611
+ outputDirectory: "dist",
612
+ defaultBuildCommand: "vite build",
613
+ },
614
+ {
615
+ // Zola shares config.toml with legacy Hugo sites; a Zola site always has
616
+ // a templates/ directory, which legacy Hugo sites (layouts/) do not.
617
+ preset: "zola",
618
+ configFiles: ["config.toml"],
619
+ requiredFiles: ["templates"],
620
+ outputDirectory: "public",
621
+ defaultBuildCommand: "zola build",
622
+ },
623
+ {
624
+ preset: "hugo",
625
+ configFiles: ["hugo.toml", "config.toml"],
626
+ outputDirectory: "public",
627
+ defaultBuildCommand: "hugo",
628
+ },
629
+ {
630
+ preset: "jekyll",
631
+ configFiles: ["_config.yml", "_config.yaml"],
632
+ outputDirectory: "_site",
633
+ defaultBuildCommand: "bundle exec jekyll build",
634
+ },
635
+ {
636
+ preset: "mkdocs",
637
+ configFiles: ["mkdocs.yml", "mkdocs.yaml"],
638
+ outputDirectory: "site",
639
+ defaultBuildCommand: "mkdocs build",
640
+ },
641
+ {
642
+ // sphinx-quickstart default layout: conf.py at the root, no separate
643
+ // source dir, `sphinx-build -M html . _build` renders to _build/html.
644
+ preset: "sphinx",
645
+ configFiles: ["conf.py"],
646
+ outputDirectory: "_build/html",
647
+ defaultBuildCommand: "sphinx-build -M html . _build",
648
+ },
649
+ {
650
+ preset: "middleman",
651
+ configFiles: ["config.rb"],
652
+ requiredFiles: ["source"],
653
+ outputDirectory: "build",
654
+ defaultBuildCommand: "bundle exec middleman build",
655
+ },
656
+ ];
657
+ function frameworkForPreset(frameworkPreset) {
658
+ return FRAMEWORKS.find((framework) => framework.preset === frameworkPreset) ?? null;
659
+ }
660
+ function hasRequiredFiles(framework, files) {
661
+ return (framework.requiredFiles ?? []).every((file) => files.has(file));
662
+ }
246
663
  function detectFrameworkPreset(files, dependencies) {
247
- if ("next" in dependencies)
248
- return "next";
249
- if ("@tanstack/react-start" in dependencies)
250
- return "tanstack-start";
251
- if ("vite" in dependencies)
252
- return "vite";
253
- if ("astro" in dependencies)
254
- return "astro";
255
- if ("@sveltejs/kit" in dependencies)
256
- return "sveltekit";
257
- if ("gatsby" in dependencies)
258
- return "gatsby";
259
- if ("@remix-run/dev" in dependencies)
260
- return "remix";
261
- if ("@docusaurus/core" in dependencies)
262
- return "docusaurus";
263
- if (files.has("hugo.toml") || files.has("config.toml"))
264
- return "hugo";
265
- if (files.has("_config.yml") || files.has("_config.yaml"))
266
- return "jekyll";
267
- return null;
664
+ const byDependency = FRAMEWORKS.find((framework) => framework.dependency !== undefined &&
665
+ framework.dependency in dependencies &&
666
+ hasRequiredFiles(framework, files));
667
+ if (byDependency)
668
+ return byDependency.preset;
669
+ const byConfigFile = FRAMEWORKS.find((framework) => framework.configFiles?.some((file) => files.has(file)) && hasRequiredFiles(framework, files));
670
+ return byConfigFile?.preset ?? null;
268
671
  }
269
672
  function outputDirectoryForFramework(frameworkPreset, files) {
270
- switch (frameworkPreset) {
271
- case "next":
272
- return "out";
273
- case "tanstack-start":
274
- return "dist/client";
275
- case "gatsby":
276
- return "public";
277
- case "sveltekit":
278
- return "build";
279
- case "remix":
280
- return "build/client";
281
- case "docusaurus":
282
- return "build";
283
- case "hugo":
284
- return "public";
285
- case "jekyll":
286
- return "_site";
287
- default:
288
- return files.has("dist") ? "dist" : null;
289
- }
673
+ const framework = frameworkForPreset(frameworkPreset);
674
+ if (framework)
675
+ return framework.outputDirectory;
676
+ return files.has("dist") ? "dist" : null;
290
677
  }
291
678
  function installCommandForManager(packageManager, packageJson) {
292
679
  switch (packageManager) {
@@ -303,11 +690,7 @@ function installCommandForManager(packageManager, packageJson) {
303
690
  function buildCommandForDefaults(packageJson, run, frameworkPreset) {
304
691
  if (packageJson?.scripts?.build)
305
692
  return `${run} build`;
306
- if (frameworkPreset === "hugo")
307
- return "hugo";
308
- if (frameworkPreset === "jekyll")
309
- return "bundle exec jekyll build";
310
- return null;
693
+ return frameworkForPreset(frameworkPreset)?.defaultBuildCommand ?? null;
311
694
  }
312
695
  function packageDefaults(files, packageJsonText) {
313
696
  const packageJson = parsePackageJson(packageJsonText);
@@ -1 +1,9 @@
1
+ export type ClaimCredential = {
2
+ kind: "token";
3
+ claimToken: string;
4
+ } | {
5
+ kind: "nonce";
6
+ nonce: string;
7
+ };
8
+ export declare function classifyClaimCredential(raw: string | null | undefined): ClaimCredential | undefined;
1
9
  export declare function normalizeClaimToken(token: string | null | undefined): string | undefined;
@@ -1,6 +1,27 @@
1
+ const LEGACY_WORD_TOKEN_SHAPE = /^[a-z]+(?:-[a-z]+){2,}$/;
2
+ export function classifyClaimCredential(raw) {
3
+ const trimmed = raw?.trim();
4
+ if (!trimmed) {
5
+ return undefined;
6
+ }
7
+ if (trimmed.startsWith("sfc_")) {
8
+ return { kind: "token", claimToken: trimmed };
9
+ }
10
+ const normalized = normalizeClaimToken(trimmed);
11
+ if (normalized && LEGACY_WORD_TOKEN_SHAPE.test(normalized)) {
12
+ return { kind: "token", claimToken: normalized };
13
+ }
14
+ return { kind: "nonce", nonce: trimmed };
15
+ }
1
16
  export function normalizeClaimToken(token) {
2
- const normalized = token
3
- ?.trim()
17
+ const trimmed = token?.trim();
18
+ if (!trimmed) {
19
+ return undefined;
20
+ }
21
+ if (trimmed.startsWith("sfc_")) {
22
+ return trimmed;
23
+ }
24
+ const normalized = trimmed
4
25
  .toLowerCase()
5
26
  .split(/[\s_-]+/)
6
27
  .filter(Boolean)
@@ -7,4 +7,5 @@
7
7
  * same content types when uploading and serving version files.
8
8
  */
9
9
  export declare function contentTypeForPath(filePath: string): string;
10
+ export declare function textTypeForPath(filePath: string): string | null;
10
11
  export declare function contentTypeForBytes(filePath: string, bytes: Uint8Array): string;
@@ -73,7 +73,7 @@ function hasPrefix(bytes, prefix) {
73
73
  function hasAsciiPrefix(bytes, prefix) {
74
74
  return hasPrefix(bytes, Array.from(prefix, (char) => char.charCodeAt(0)));
75
75
  }
76
- function textTypeForPath(filePath) {
76
+ export function textTypeForPath(filePath) {
77
77
  const contentType = contentTypeForPath(filePath);
78
78
  const normalized = contentType.split(";")[0]?.trim().toLowerCase();
79
79
  return normalized &&
@@ -0,0 +1,54 @@
1
+ import { type ApiKeyPreset, type ApiCredentialPolicy, type StatticAction } from "../contracts/api-keys.js";
2
+ type RequestCondition = NonNullable<ApiCredentialPolicy["statements"][number]["conditions"]>;
3
+ export type PolicyCompileInput = {
4
+ tenantId: string;
5
+ teamId: string;
6
+ };
7
+ export type PolicyAuthorizeInput = {
8
+ policy: ApiCredentialPolicy;
9
+ action: StatticAction;
10
+ /**
11
+ * Candidate resource URIs for one request target, evaluated together: spaces
12
+ * are addressed by their canonical site-rooted URI AND a principal-scoped
13
+ * candidate so principal wildcards cover them. A deny matching ANY candidate
14
+ * wins; otherwise an allow matching any candidate permits.
15
+ */
16
+ resource: string | readonly string[];
17
+ conditions?: RequestCondition;
18
+ };
19
+ export declare const publishActions: ("spaces:read" | "spaces:create" | "spaces:write" | "spaces:publish" | "versions:read" | "versions:promote" | "versions:download" | "builds:read" | "builds:create")[];
20
+ export declare function compileApiKeyPreset(preset: ApiKeyPreset, input: PolicyCompileInput): ApiCredentialPolicy;
21
+ export declare function compileTeamRolePolicy(input: PolicyCompileInput & {
22
+ role: string;
23
+ }): ApiCredentialPolicy;
24
+ export declare function compileSpaceContinuationPolicy(input: {
25
+ tenantId: string;
26
+ spaceId: string;
27
+ }): ApiCredentialPolicy;
28
+ export declare function allowPolicy(actions: StatticAction[], resources: string[]): ApiCredentialPolicy;
29
+ export declare function denyAllPolicy(): ApiCredentialPolicy;
30
+ export declare function policyIsSubsetOfGrantor(requested: ApiCredentialPolicy, grantor: ApiCredentialPolicy): boolean;
31
+ export declare function policyAllows(input: PolicyAuthorizeInput): boolean;
32
+ export declare function grantedTenantsFromPolicy(policy: ApiCredentialPolicy): {
33
+ all: boolean;
34
+ tenantIds: string[];
35
+ };
36
+ export declare function grantedPrincipalsFromPolicy(policy: ApiCredentialPolicy, input: {
37
+ tenantId: string;
38
+ principalType: "team" | "external";
39
+ }): {
40
+ all: boolean;
41
+ principalIds: string[];
42
+ grants: ({
43
+ kind: "principal";
44
+ tenantId: string;
45
+ principalType: "team" | "external";
46
+ principalId: string;
47
+ } | {
48
+ kind: "space";
49
+ tenantId: string;
50
+ spaceId: string;
51
+ })[];
52
+ };
53
+ export declare function policyReaches(policy: ApiCredentialPolicy, resourceBase: string): boolean;
54
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { SPACEFAST_ACCESS_POLICY_VERSION, parseStatticResource, } from "../contracts/api-keys.js";
2
- const publishActions = [
2
+ export const publishActions = [
3
3
  "spaces:read",
4
4
  "spaces:create",
5
5
  "spaces:write",
@@ -35,6 +35,9 @@ const domainActions = [
35
35
  function teamResource(input) {
36
36
  return `stt://tenant/${input.tenantId}/principal/team/${input.teamId}/*`;
37
37
  }
38
+ function spaceResource(input) {
39
+ return `stt://tenant/${input.tenantId}/space/${input.spaceId}`;
40
+ }
38
41
  export function compileApiKeyPreset(preset, input) {
39
42
  const resource = teamResource(input);
40
43
  switch (preset) {
@@ -88,6 +91,9 @@ export function compileTeamRolePolicy(input) {
88
91
  return denyAllPolicy();
89
92
  }
90
93
  }
94
+ export function compileSpaceContinuationPolicy(input) {
95
+ return allowPolicy(publishActions.filter((action) => action !== "spaces:create"), [spaceResource(input)]);
96
+ }
91
97
  export function allowPolicy(actions, resources) {
92
98
  return {
93
99
  version: SPACEFAST_ACCESS_POLICY_VERSION,
@@ -145,6 +151,7 @@ export function grantedTenantsFromPolicy(policy) {
145
151
  // Used to scope list queries; specific actions still go through `policyAllows`.
146
152
  export function grantedPrincipalsFromPolicy(policy, input) {
147
153
  const principalIds = new Set();
154
+ const spaceIds = new Set();
148
155
  let all = false;
149
156
  for (const statement of policy.statements) {
150
157
  if (statement.effect !== "allow") {
@@ -169,9 +176,28 @@ export function grantedPrincipalsFromPolicy(policy, input) {
169
176
  principalIds.add(parsed.principalId);
170
177
  }
171
178
  }
179
+ else if (parsed.kind === "space") {
180
+ spaceIds.add(parsed.spaceId);
181
+ }
172
182
  }
173
183
  }
174
- return { all, principalIds: [...principalIds] };
184
+ return {
185
+ all,
186
+ principalIds: [...principalIds],
187
+ grants: [
188
+ ...[...principalIds].map((principalId) => ({
189
+ kind: "principal",
190
+ tenantId: input.tenantId,
191
+ principalType: input.principalType,
192
+ principalId,
193
+ })),
194
+ ...[...spaceIds].map((spaceId) => ({
195
+ kind: "space",
196
+ tenantId: input.tenantId,
197
+ spaceId,
198
+ })),
199
+ ],
200
+ };
175
201
  }
176
202
  // Whether the policy's allow statements reach a resource subtree at all —
177
203
  // either a statement resource covers the base, or it lives inside the base.