@spacefast/common 0.0.23 → 0.0.26
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.js +1 -1
- package/dist/agents/private-key-oauth.d.ts +0 -1
- package/dist/agents/private-key-oauth.js +46 -63
- package/dist/brand-assets-build.js +4 -6
- package/dist/brand-assets.d.ts +5 -15
- package/dist/brand-assets.js +15 -14
- package/dist/config/domains.d.ts +47 -7
- package/dist/config/domains.js +66 -10
- package/dist/config/index.d.ts +0 -1
- package/dist/config/index.js +0 -1
- package/dist/contracts/abuse.d.ts +2 -0
- package/dist/contracts/abuse.js +12 -0
- package/dist/contracts/access.d.ts +124 -12
- package/dist/contracts/access.js +73 -9
- package/dist/contracts/activity.d.ts +1 -1
- package/dist/contracts/activity.js +5 -12
- package/dist/contracts/api-keys.d.ts +21 -21
- package/dist/contracts/api-keys.js +2 -2
- package/dist/contracts/beta.js +1 -1
- package/dist/contracts/billing.d.ts +3 -35
- package/dist/contracts/billing.js +33 -52
- package/dist/contracts/builds.d.ts +30 -18
- package/dist/contracts/builds.js +28 -9
- package/dist/contracts/channels.js +1 -1
- package/dist/contracts/cli.d.ts +10 -0
- package/dist/contracts/cli.js +10 -0
- package/dist/contracts/collab-public.d.ts +63 -0
- package/dist/contracts/collab-public.js +46 -0
- package/dist/contracts/comments.d.ts +282 -53
- package/dist/contracts/comments.js +173 -52
- package/dist/contracts/common.d.ts +3 -3
- package/dist/contracts/common.js +5 -5
- package/dist/contracts/crons.d.ts +35 -0
- package/dist/contracts/crons.js +42 -0
- package/dist/contracts/device-auth.d.ts +6 -6
- package/dist/contracts/device-auth.js +2 -2
- package/dist/contracts/docs.js +2 -2
- package/dist/contracts/domains.d.ts +38 -27
- package/dist/contracts/domains.js +29 -2
- package/dist/contracts/enums.d.ts +34 -30
- package/dist/contracts/enums.js +62 -48
- package/dist/contracts/error-code-meta.d.ts +50 -125
- package/dist/contracts/error-code-meta.js +29 -41
- package/dist/contracts/error-codes.d.ts +1 -1
- package/dist/contracts/error-codes.js +14 -33
- package/dist/contracts/execution.d.ts +47 -66
- package/dist/contracts/execution.js +53 -49
- package/dist/contracts/feature-lifecycle-core.d.ts +0 -6
- package/dist/contracts/feature-lifecycle-core.js +2 -31
- package/dist/contracts/feature-lifecycle.d.ts +12 -52
- package/dist/contracts/feature-lifecycle.js +16 -59
- package/dist/contracts/features.d.ts +48 -60
- package/dist/contracts/features.js +59 -51
- package/dist/contracts/functions.d.ts +266 -72
- package/dist/contracts/functions.js +260 -37
- package/dist/contracts/git.d.ts +28 -34
- package/dist/contracts/git.js +10 -11
- package/dist/contracts/grants.d.ts +9 -5
- package/dist/contracts/grants.js +18 -9
- package/dist/contracts/ids.js +2 -2
- package/dist/contracts/internal.d.ts +8 -6
- package/dist/contracts/internal.js +12 -12
- package/dist/contracts/mcp.d.ts +11 -0
- package/dist/contracts/mcp.js +5 -0
- package/dist/contracts/me.js +1 -1
- package/dist/contracts/oauth-resources.d.ts +4 -3
- package/dist/contracts/oauth-resources.js +12 -3
- package/dist/contracts/operations.d.ts +90 -5
- package/dist/contracts/operations.js +77 -3
- package/dist/contracts/pages.d.ts +12 -7
- package/dist/contracts/pages.js +24 -1
- package/dist/contracts/plan-policy.d.ts +0 -2
- package/dist/contracts/plan-policy.js +2 -3
- package/dist/contracts/platform.d.ts +61 -148
- package/dist/contracts/platform.js +70 -126
- package/dist/contracts/publish-archive.d.ts +508 -0
- package/dist/contracts/publish-archive.js +32 -0
- package/dist/contracts/push-new.d.ts +4 -4
- package/dist/contracts/quotas.js +2 -2
- package/dist/contracts/repository-connections.d.ts +43 -30
- package/dist/contracts/repository-connections.js +11 -3
- package/dist/contracts/resources.d.ts +12 -24
- package/dist/contracts/resources.js +36 -16
- package/dist/contracts/runtime-api.d.ts +433 -192
- package/dist/contracts/runtime-api.js +421 -356
- package/dist/contracts/runtime-app.d.ts +127 -120
- package/dist/contracts/runtime-app.js +19 -10
- package/dist/contracts/runtime-db.d.ts +5 -5
- package/dist/contracts/runtime-db.js +2 -2
- package/dist/contracts/runtime-purge.d.ts +8 -0
- package/dist/contracts/runtime-purge.js +7 -0
- package/dist/contracts/runtime-services.d.ts +219 -0
- package/dist/contracts/runtime-services.js +437 -0
- package/dist/contracts/runtime-storage.d.ts +46 -32
- package/dist/contracts/runtime-storage.js +31 -8
- package/dist/contracts/sf-config-v1.d.ts +48 -10
- package/dist/contracts/space-config.d.ts +69 -18
- package/dist/contracts/space-config.js +226 -27
- package/dist/contracts/spaces.d.ts +236 -450
- package/dist/contracts/spaces.js +131 -151
- package/dist/contracts/superadmin-activity.d.ts +57 -4
- package/dist/contracts/superadmin-activity.js +48 -2
- package/dist/contracts/superadmin-search.d.ts +126 -0
- package/dist/contracts/superadmin-search.js +61 -0
- package/dist/contracts/superadmin-spaces.d.ts +361 -92
- package/dist/contracts/superadmin-spaces.js +97 -24
- package/dist/contracts/superadmin-teams.d.ts +28 -10
- package/dist/contracts/superadmin-teams.js +5 -8
- package/dist/contracts/superadmin-tenants.d.ts +0 -6
- package/dist/contracts/superadmin-tenants.js +1 -7
- package/dist/contracts/superadmin.d.ts +135 -54
- package/dist/contracts/superadmin.js +46 -23
- package/dist/contracts/tags.d.ts +12 -12
- package/dist/contracts/tags.js +3 -3
- package/dist/contracts/teams.d.ts +49 -14
- package/dist/contracts/teams.js +40 -22
- package/dist/contracts/transfers.d.ts +0 -49
- package/dist/contracts/transfers.js +4 -8
- package/dist/contracts/usage.d.ts +61 -0
- package/dist/contracts/usage.js +64 -0
- package/dist/contracts/variables.d.ts +2 -7
- package/dist/contracts/variables.js +9 -8
- package/dist/contracts/webhooks.d.ts +2 -2
- package/dist/contracts/zero.d.ts +102 -73
- package/dist/contracts/zero.js +94 -32
- package/dist/dashboard-paths/index.d.ts +1 -1
- package/dist/dashboard-paths/index.js +1 -0
- package/dist/docs/agent-prose.d.ts +2 -2
- package/dist/docs/agent-prose.js +63 -70
- package/dist/docs/agent-setup.js +45 -15
- package/dist/docs/catalog.d.ts +24 -24
- package/dist/docs/catalog.js +35 -24
- package/dist/docs/error-docs.js +194 -270
- package/dist/docs/skills.d.ts +12 -12
- package/dist/docs/skills.js +4 -4
- package/dist/test-helpers/fetch-stub.d.ts +16 -0
- package/dist/test-helpers/fetch-stub.js +25 -0
- package/dist/utils/build-settings.d.ts +5 -1
- package/dist/utils/build-settings.js +91 -13
- package/dist/utils/canonical-json.d.ts +10 -0
- package/dist/utils/canonical-json.js +22 -0
- package/dist/utils/cast-transport.d.ts +28 -0
- package/dist/utils/cast-transport.js +52 -0
- package/dist/utils/claim-token.d.ts +0 -8
- package/dist/utils/claim-token.js +3 -15
- package/dist/utils/concurrency.js +1 -0
- package/dist/utils/credential-policy.d.ts +1 -1
- package/dist/utils/credential-policy.js +4 -3
- package/dist/utils/dns-instructions.js +9 -9
- package/dist/utils/env-file.d.ts +17 -0
- package/dist/utils/env-file.js +96 -0
- package/dist/utils/error-code.d.ts +2 -0
- package/dist/utils/error-code.js +8 -0
- package/dist/utils/generate-space-name.d.ts +8 -10
- package/dist/utils/generate-space-name.js +206 -21
- package/dist/utils/grant-decision.fixtures.json +2938 -0
- package/dist/utils/grants.d.ts +2 -0
- package/dist/utils/grants.js +26 -1
- package/dist/utils/gravatar.d.ts +9 -0
- package/dist/utils/gravatar.js +31 -0
- package/dist/utils/idempotency.d.ts +6 -0
- package/dist/utils/idempotency.js +28 -0
- package/dist/utils/local-space-state.d.ts +9 -16
- package/dist/utils/local-space-state.js +20 -50
- package/dist/utils/one-shot-replay.d.ts +4 -1
- package/dist/utils/page-colors.d.ts +14 -12
- package/dist/utils/page-colors.js +15 -13
- package/dist/utils/page-fonts.d.ts +2 -2
- package/dist/utils/page-fonts.js +15 -4
- package/dist/utils/pages.d.ts +21 -3
- package/dist/utils/pages.js +42 -13
- package/dist/utils/publish-form-data.d.ts +12 -0
- package/dist/utils/publish-form-data.js +10 -0
- package/dist/utils/publish-policy.d.ts +27 -0
- package/dist/utils/publish-policy.fixtures.json +403 -0
- package/dist/utils/publish-policy.js +45 -20
- package/dist/utils/push-new.d.ts +7 -0
- package/dist/utils/push-new.js +7 -0
- package/dist/utils/query-keys.d.ts +4 -5
- package/dist/utils/query-keys.js +2 -3
- package/dist/utils/runtime-paths.d.ts +5 -0
- package/dist/utils/runtime-paths.js +5 -0
- package/dist/utils/runtime-upload-batch.d.ts +0 -5
- package/dist/utils/runtime-upload-batch.js +1 -27
- package/dist/utils/runtime-upload.d.ts +6 -11
- package/dist/utils/runtime-upload.js +10 -13
- package/dist/utils/secure-local-file.js +5 -16
- package/dist/utils/space-config.d.ts +1 -34
- package/dist/utils/space-config.js +8 -326
- package/dist/utils/static-runtime-policy.d.ts +9 -3
- package/dist/utils/static-runtime-policy.fixtures.json +38 -41
- package/dist/utils/static-runtime-policy.js +22 -56
- package/dist/utils/storage-policy.d.ts +1 -1
- package/dist/utils/storage-policy.js +3 -1
- package/dist/utils/structured-error.d.ts +14 -0
- package/dist/utils/structured-error.js +22 -0
- package/dist/utils/upload-session.d.ts +4 -1
- package/dist/utils/upload-session.js +33 -5
- package/dist/utils/version-path.js +13 -4
- package/dist/vocabulary.d.ts +13 -11
- package/dist/vocabulary.js +42 -28
- package/package.json +7 -17
- package/dist/config/postgres-budget.d.ts +0 -129
- package/dist/config/postgres-budget.js +0 -140
- package/dist/config/postgres-budget.json +0 -51
- package/dist/contracts/archives.d.ts +0 -331
- package/dist/contracts/archives.js +0 -120
- package/dist/contracts/deployments.d.ts +0 -574
- package/dist/contracts/deployments.js +0 -97
- package/dist/contracts/sites.d.ts +0 -416
- package/dist/contracts/sites.js +0 -168
- package/dist/contracts/superadmin-queues.d.ts +0 -414
- package/dist/contracts/superadmin-queues.js +0 -202
- package/dist/contracts/superadmin-runtime.d.ts +0 -27
- package/dist/contracts/superadmin-runtime.js +0 -38
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/utils/api.d.ts +0 -4
- package/dist/utils/api.js +0 -13
package/dist/docs/skills.d.ts
CHANGED
|
@@ -23,13 +23,13 @@ export interface SpacefastSkillDefinition {
|
|
|
23
23
|
export declare const SPACEFAST_SKILLS: readonly [{
|
|
24
24
|
readonly name: "create-and-publish-site";
|
|
25
25
|
readonly title: "Create and publish a polished website";
|
|
26
|
-
readonly description: "Turn a brief into a responsive
|
|
26
|
+
readonly description: "Turn a brief into a responsive website, review the result, publish it to Spacefast, and verify the live version.";
|
|
27
27
|
readonly whenToUse: "Use when the user wants an agent to design, build, improve, or launch a new website rather than only upload an existing build.";
|
|
28
28
|
readonly keywords: readonly ["create", "website", "design", "build", "responsive", "launch", "publish"];
|
|
29
29
|
readonly runtimes: readonly ["local", "hosted"];
|
|
30
30
|
readonly tools: readonly ["publish", "execute"];
|
|
31
31
|
readonly docs: readonly ["quickstart", "publishing"];
|
|
32
|
-
readonly instructions: readonly ["Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.", "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.", "Build a focused, accessible
|
|
32
|
+
readonly instructions: readonly ["Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.", "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.", "Build a focused, accessible experience with a clear hierarchy, real content, responsive layouts, useful interaction states, and no placeholder copy. Keep the main path simple and put secondary detail behind progressive disclosure.", "Run the real build and inspect the result at mobile and desktop widths. Fix overflow, illegible contrast, broken links, missing assets, keyboard traps, and avoidable layout shift.", "Review the workspace diff and summarize material design or content judgments before publishing. Never include API keys, claim credentials, upload credentials, or local auth state in the artifact.", "Publish the built output with `publish`. Present Open as the reusable access link and Claim as the one-time ownership link. Treat live and immutable-version URLs as receipt metadata, not extra actions."];
|
|
33
33
|
readonly verification: readonly ["Call `status` after publishing and confirm the intended version is live; use `diagnose` if serving is stalled or failed.", "Open the live URL and verify the primary page, navigation, assets, responsive layout, and main call to action before claiming success."];
|
|
34
34
|
readonly runtimeNotes: {
|
|
35
35
|
readonly local: "On-Device can work directly inside the configured local workspace root and publish a local build path.";
|
|
@@ -44,13 +44,13 @@ export declare const SPACEFAST_SKILLS: readonly [{
|
|
|
44
44
|
}, {
|
|
45
45
|
readonly name: "publish-and-verify";
|
|
46
46
|
readonly title: "Publish and verify";
|
|
47
|
-
readonly description: "Publish an existing
|
|
47
|
+
readonly description: "Publish an existing build, confirm the new version is actually live, and return the useful URLs without leaking credentials.";
|
|
48
48
|
readonly whenToUse: "Use when the artifact already exists and the user wants it published or updated.";
|
|
49
49
|
readonly keywords: readonly ["publish", "deploy", "live", "verify", "version", "claim"];
|
|
50
50
|
readonly runtimes: readonly ["local", "hosted"];
|
|
51
51
|
readonly tools: readonly ["publish", "status", "diagnose"];
|
|
52
52
|
readonly docs: readonly ["publishing", "workflow/publish-receipt", "workflow/secret-handling"];
|
|
53
|
-
readonly instructions: readonly ["Identify the actual publish root or build output. Run the real build first when the source tree is not itself the
|
|
53
|
+
readonly instructions: readonly ["Identify the actual publish root or build output. Run the real build first when the source tree is not itself the artifact to publish.", "Publish with `publish`. For an anonymous On-Device publish, keep the claim credential in local custody and return only the claim URL and expiry; never expose or ask the user to paste the raw token.", "Report the bare Live URL, private Access URL when present, immutable Version URL when present, anonymous Claim URL and expiry when present, plus actionable diagnostics."];
|
|
54
54
|
readonly verification: readonly ["Call `status` and confirm the intended version is live. Use `diagnose` for a failed or stalled publish.", "Open the live URL and verify representative content and assets rather than treating a successful upload or health check as serving proof."];
|
|
55
55
|
readonly runtimeNotes: {
|
|
56
56
|
readonly local: "Publish a bounded local path; locally custodied claim credentials are reused automatically.";
|
|
@@ -196,13 +196,13 @@ export declare const SPACEFAST_SKILLS: readonly [{
|
|
|
196
196
|
export declare const SPACEFAST_PROMPT_SKILLS: ({
|
|
197
197
|
readonly name: "create-and-publish-site";
|
|
198
198
|
readonly title: "Create and publish a polished website";
|
|
199
|
-
readonly description: "Turn a brief into a responsive
|
|
199
|
+
readonly description: "Turn a brief into a responsive website, review the result, publish it to Spacefast, and verify the live version.";
|
|
200
200
|
readonly whenToUse: "Use when the user wants an agent to design, build, improve, or launch a new website rather than only upload an existing build.";
|
|
201
201
|
readonly keywords: readonly ["create", "website", "design", "build", "responsive", "launch", "publish"];
|
|
202
202
|
readonly runtimes: readonly ["local", "hosted"];
|
|
203
203
|
readonly tools: readonly ["publish", "execute"];
|
|
204
204
|
readonly docs: readonly ["quickstart", "publishing"];
|
|
205
|
-
readonly instructions: readonly ["Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.", "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.", "Build a focused, accessible
|
|
205
|
+
readonly instructions: readonly ["Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.", "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.", "Build a focused, accessible experience with a clear hierarchy, real content, responsive layouts, useful interaction states, and no placeholder copy. Keep the main path simple and put secondary detail behind progressive disclosure.", "Run the real build and inspect the result at mobile and desktop widths. Fix overflow, illegible contrast, broken links, missing assets, keyboard traps, and avoidable layout shift.", "Review the workspace diff and summarize material design or content judgments before publishing. Never include API keys, claim credentials, upload credentials, or local auth state in the artifact.", "Publish the built output with `publish`. Present Open as the reusable access link and Claim as the one-time ownership link. Treat live and immutable-version URLs as receipt metadata, not extra actions."];
|
|
206
206
|
readonly verification: readonly ["Call `status` after publishing and confirm the intended version is live; use `diagnose` if serving is stalled or failed.", "Open the live URL and verify the primary page, navigation, assets, responsive layout, and main call to action before claiming success."];
|
|
207
207
|
readonly runtimeNotes: {
|
|
208
208
|
readonly local: "On-Device can work directly inside the configured local workspace root and publish a local build path.";
|
|
@@ -217,13 +217,13 @@ export declare const SPACEFAST_PROMPT_SKILLS: ({
|
|
|
217
217
|
} | {
|
|
218
218
|
readonly name: "publish-and-verify";
|
|
219
219
|
readonly title: "Publish and verify";
|
|
220
|
-
readonly description: "Publish an existing
|
|
220
|
+
readonly description: "Publish an existing build, confirm the new version is actually live, and return the useful URLs without leaking credentials.";
|
|
221
221
|
readonly whenToUse: "Use when the artifact already exists and the user wants it published or updated.";
|
|
222
222
|
readonly keywords: readonly ["publish", "deploy", "live", "verify", "version", "claim"];
|
|
223
223
|
readonly runtimes: readonly ["local", "hosted"];
|
|
224
224
|
readonly tools: readonly ["publish", "status", "diagnose"];
|
|
225
225
|
readonly docs: readonly ["publishing", "workflow/publish-receipt", "workflow/secret-handling"];
|
|
226
|
-
readonly instructions: readonly ["Identify the actual publish root or build output. Run the real build first when the source tree is not itself the
|
|
226
|
+
readonly instructions: readonly ["Identify the actual publish root or build output. Run the real build first when the source tree is not itself the artifact to publish.", "Publish with `publish`. For an anonymous On-Device publish, keep the claim credential in local custody and return only the claim URL and expiry; never expose or ask the user to paste the raw token.", "Report the bare Live URL, private Access URL when present, immutable Version URL when present, anonymous Claim URL and expiry when present, plus actionable diagnostics."];
|
|
227
227
|
readonly verification: readonly ["Call `status` and confirm the intended version is live. Use `diagnose` for a failed or stalled publish.", "Open the live URL and verify representative content and assets rather than treating a successful upload or health check as serving proof."];
|
|
228
228
|
readonly runtimeNotes: {
|
|
229
229
|
readonly local: "Publish a bounded local path; locally custodied claim credentials are reused automatically.";
|
|
@@ -369,13 +369,13 @@ export declare const SPACEFAST_PROMPT_SKILLS: ({
|
|
|
369
369
|
export declare const SPACEFAST_RECIPE_SKILLS: (({
|
|
370
370
|
readonly name: "create-and-publish-site";
|
|
371
371
|
readonly title: "Create and publish a polished website";
|
|
372
|
-
readonly description: "Turn a brief into a responsive
|
|
372
|
+
readonly description: "Turn a brief into a responsive website, review the result, publish it to Spacefast, and verify the live version.";
|
|
373
373
|
readonly whenToUse: "Use when the user wants an agent to design, build, improve, or launch a new website rather than only upload an existing build.";
|
|
374
374
|
readonly keywords: readonly ["create", "website", "design", "build", "responsive", "launch", "publish"];
|
|
375
375
|
readonly runtimes: readonly ["local", "hosted"];
|
|
376
376
|
readonly tools: readonly ["publish", "execute"];
|
|
377
377
|
readonly docs: readonly ["quickstart", "publishing"];
|
|
378
|
-
readonly instructions: readonly ["Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.", "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.", "Build a focused, accessible
|
|
378
|
+
readonly instructions: readonly ["Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.", "Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.", "Build a focused, accessible experience with a clear hierarchy, real content, responsive layouts, useful interaction states, and no placeholder copy. Keep the main path simple and put secondary detail behind progressive disclosure.", "Run the real build and inspect the result at mobile and desktop widths. Fix overflow, illegible contrast, broken links, missing assets, keyboard traps, and avoidable layout shift.", "Review the workspace diff and summarize material design or content judgments before publishing. Never include API keys, claim credentials, upload credentials, or local auth state in the artifact.", "Publish the built output with `publish`. Present Open as the reusable access link and Claim as the one-time ownership link. Treat live and immutable-version URLs as receipt metadata, not extra actions."];
|
|
379
379
|
readonly verification: readonly ["Call `status` after publishing and confirm the intended version is live; use `diagnose` if serving is stalled or failed.", "Open the live URL and verify the primary page, navigation, assets, responsive layout, and main call to action before claiming success."];
|
|
380
380
|
readonly runtimeNotes: {
|
|
381
381
|
readonly local: "On-Device can work directly inside the configured local workspace root and publish a local build path.";
|
|
@@ -390,13 +390,13 @@ export declare const SPACEFAST_RECIPE_SKILLS: (({
|
|
|
390
390
|
} | {
|
|
391
391
|
readonly name: "publish-and-verify";
|
|
392
392
|
readonly title: "Publish and verify";
|
|
393
|
-
readonly description: "Publish an existing
|
|
393
|
+
readonly description: "Publish an existing build, confirm the new version is actually live, and return the useful URLs without leaking credentials.";
|
|
394
394
|
readonly whenToUse: "Use when the artifact already exists and the user wants it published or updated.";
|
|
395
395
|
readonly keywords: readonly ["publish", "deploy", "live", "verify", "version", "claim"];
|
|
396
396
|
readonly runtimes: readonly ["local", "hosted"];
|
|
397
397
|
readonly tools: readonly ["publish", "status", "diagnose"];
|
|
398
398
|
readonly docs: readonly ["publishing", "workflow/publish-receipt", "workflow/secret-handling"];
|
|
399
|
-
readonly instructions: readonly ["Identify the actual publish root or build output. Run the real build first when the source tree is not itself the
|
|
399
|
+
readonly instructions: readonly ["Identify the actual publish root or build output. Run the real build first when the source tree is not itself the artifact to publish.", "Publish with `publish`. For an anonymous On-Device publish, keep the claim credential in local custody and return only the claim URL and expiry; never expose or ask the user to paste the raw token.", "Report the bare Live URL, private Access URL when present, immutable Version URL when present, anonymous Claim URL and expiry when present, plus actionable diagnostics."];
|
|
400
400
|
readonly verification: readonly ["Call `status` and confirm the intended version is live. Use `diagnose` for a failed or stalled publish.", "Open the live URL and verify representative content and assets rather than treating a successful upload or health check as serving proof."];
|
|
401
401
|
readonly runtimeNotes: {
|
|
402
402
|
readonly local: "Publish a bounded local path; locally custodied claim credentials are reused automatically.";
|
package/dist/docs/skills.js
CHANGED
|
@@ -5,7 +5,7 @@ export const SPACEFAST_SKILLS = [
|
|
|
5
5
|
{
|
|
6
6
|
name: "create-and-publish-site",
|
|
7
7
|
title: "Create and publish a polished website",
|
|
8
|
-
description: "Turn a brief into a responsive
|
|
8
|
+
description: "Turn a brief into a responsive website, review the result, publish it to Spacefast, and verify the live version.",
|
|
9
9
|
whenToUse: "Use when the user wants an agent to design, build, improve, or launch a new website rather than only upload an existing build.",
|
|
10
10
|
keywords: ["create", "website", "design", "build", "responsive", "launch", "publish"],
|
|
11
11
|
runtimes: BOTH_RUNTIMES,
|
|
@@ -14,7 +14,7 @@ export const SPACEFAST_SKILLS = [
|
|
|
14
14
|
instructions: [
|
|
15
15
|
"Clarify the audience, goal, required content, brand constraints, and desired call to action. If the visual direction is open, propose two or three distinct directions and choose one with the user.",
|
|
16
16
|
"Inspect the existing workspace before editing. Reuse its framework, package manager, components, assets, and build conventions when they are sound.",
|
|
17
|
-
"Build a focused, accessible
|
|
17
|
+
"Build a focused, accessible experience with a clear hierarchy, real content, responsive layouts, useful interaction states, and no placeholder copy. Keep the main path simple and put secondary detail behind progressive disclosure.",
|
|
18
18
|
"Run the real build and inspect the result at mobile and desktop widths. Fix overflow, illegible contrast, broken links, missing assets, keyboard traps, and avoidable layout shift.",
|
|
19
19
|
"Review the workspace diff and summarize material design or content judgments before publishing. Never include API keys, claim credentials, upload credentials, or local auth state in the artifact.",
|
|
20
20
|
"Publish the built output with `publish`. Present Open as the reusable access link and Claim as the one-time ownership link. Treat live and immutable-version URLs as receipt metadata, not extra actions.",
|
|
@@ -37,14 +37,14 @@ export const SPACEFAST_SKILLS = [
|
|
|
37
37
|
{
|
|
38
38
|
name: "publish-and-verify",
|
|
39
39
|
title: "Publish and verify",
|
|
40
|
-
description: "Publish an existing
|
|
40
|
+
description: "Publish an existing build, confirm the new version is actually live, and return the useful URLs without leaking credentials.",
|
|
41
41
|
whenToUse: "Use when the artifact already exists and the user wants it published or updated.",
|
|
42
42
|
keywords: ["publish", "deploy", "live", "verify", "version", "claim"],
|
|
43
43
|
runtimes: BOTH_RUNTIMES,
|
|
44
44
|
tools: ["publish", "status", "diagnose"],
|
|
45
45
|
docs: ["publishing", "workflow/publish-receipt", "workflow/secret-handling"],
|
|
46
46
|
instructions: [
|
|
47
|
-
"Identify the actual publish root or build output. Run the real build first when the source tree is not itself the
|
|
47
|
+
"Identify the actual publish root or build output. Run the real build first when the source tree is not itself the artifact to publish.",
|
|
48
48
|
"Publish with `publish`. For an anonymous On-Device publish, keep the claim credential in local custody and return only the claim URL and expiry; never expose or ask the user to paste the raw token.",
|
|
49
49
|
"Report the bare Live URL, private Access URL when present, immutable Version URL when present, anonymous Claim URL and expiry when present, plus actionable diagnostics.",
|
|
50
50
|
],
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The original fetch implementation captured before a test installs a stub. */
|
|
2
|
+
export declare const realFetch: typeof fetch;
|
|
3
|
+
export type FetchStubHandler = (request: Request) => Response | Promise<Response>;
|
|
4
|
+
export declare function stubFetch(handler: FetchStubHandler): void;
|
|
5
|
+
export declare function restoreFetch(): void;
|
|
6
|
+
/** Build the RFC 9457 problem response served by Spacefast APIs. */
|
|
7
|
+
export declare function problemResponse(input: {
|
|
8
|
+
code: string;
|
|
9
|
+
status: number;
|
|
10
|
+
detail: string;
|
|
11
|
+
pointer?: string;
|
|
12
|
+
details?: unknown;
|
|
13
|
+
suggestions?: readonly string[];
|
|
14
|
+
next?: readonly string[];
|
|
15
|
+
requestId?: string;
|
|
16
|
+
}): Response;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { errorDocsUrl, errorTitle, PROBLEM_CONTENT_TYPE } from "../contracts/error-codes.js";
|
|
2
|
+
/** The original fetch implementation captured before a test installs a stub. */
|
|
3
|
+
export const realFetch = globalThis.fetch;
|
|
4
|
+
export function stubFetch(handler) {
|
|
5
|
+
// SAFETY: The wrapper accepts fetch's parameter types and always resolves to the handler's Response.
|
|
6
|
+
globalThis.fetch = (async (input, init) => handler(input instanceof Request && init === undefined ? input : new Request(input, init)));
|
|
7
|
+
}
|
|
8
|
+
export function restoreFetch() {
|
|
9
|
+
globalThis.fetch = realFetch;
|
|
10
|
+
}
|
|
11
|
+
/** Build the RFC 9457 problem response served by Spacefast APIs. */
|
|
12
|
+
export function problemResponse(input) {
|
|
13
|
+
return new Response(JSON.stringify({
|
|
14
|
+
type: errorDocsUrl(input.code),
|
|
15
|
+
title: errorTitle(input.code),
|
|
16
|
+
status: input.status,
|
|
17
|
+
detail: input.detail,
|
|
18
|
+
code: input.code,
|
|
19
|
+
...(input.pointer === undefined ? {} : { pointer: input.pointer }),
|
|
20
|
+
...(input.details === undefined ? {} : { details: input.details }),
|
|
21
|
+
...(input.suggestions === undefined ? {} : { suggestions: input.suggestions }),
|
|
22
|
+
...(input.next === undefined ? {} : { next: input.next }),
|
|
23
|
+
requestId: input.requestId ?? "req_test",
|
|
24
|
+
}), { status: input.status, headers: { "content-type": PROBLEM_CONTENT_TYPE } });
|
|
25
|
+
}
|
|
@@ -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,6 +44,7 @@ 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>>;
|
|
46
48
|
unsupportedFeatures: string[];
|
|
47
49
|
};
|
|
48
50
|
export declare function stringField(value: Record<string, unknown> | null, key: string): string | null;
|
|
@@ -51,7 +53,9 @@ export declare function detectPlatformConfigs(input: Omit<BuildSettingsInput, "f
|
|
|
51
53
|
export type PlatformRoutingConversion = {
|
|
52
54
|
redirectLines: string[];
|
|
53
55
|
headerBlocks: string[];
|
|
56
|
+
crons: SpaceCronConfig[];
|
|
54
57
|
notes: string[];
|
|
55
58
|
};
|
|
56
59
|
export declare function convertPlatformRouting(configs: PlatformConfig[]): PlatformRoutingConversion;
|
|
60
|
+
export declare const BUILD_MARKER_FILE_NAMES: Set<string>;
|
|
57
61
|
export declare function detectBuildSettings(input: BuildSettingsInput): DetectedBuildSettings;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { load as parseYaml } from "js-yaml";
|
|
2
2
|
import { parse as parseToml } from "smol-toml";
|
|
3
|
+
import { SPACE_CONFIG_CAPS, spaceCronKey, spaceCronSchema, } from "../contracts/space-config.js";
|
|
3
4
|
export const BUILD_ANALYZER_CANDIDATE_ROOTS = ["apps", "packages", "sites", "docs"];
|
|
4
5
|
const PACKAGE_MANAGER_ONLY_FILES = new Set([
|
|
5
6
|
"package.json",
|
|
@@ -8,15 +9,19 @@ const PACKAGE_MANAGER_ONLY_FILES = new Set([
|
|
|
8
9
|
"pnpm-lock.yaml",
|
|
9
10
|
"yarn.lock",
|
|
10
11
|
"package-lock.json",
|
|
12
|
+
"npm-shrinkwrap.json",
|
|
11
13
|
"Gemfile",
|
|
12
14
|
"Gemfile.lock",
|
|
13
15
|
]);
|
|
16
|
+
const WORKSPACE_ROOT_FILES = new Set([
|
|
17
|
+
"pnpm-workspace.yaml",
|
|
18
|
+
"turbo.json",
|
|
19
|
+
"nx.json",
|
|
20
|
+
"lerna.json",
|
|
21
|
+
]);
|
|
14
22
|
export function hasWorkspaceSignal(fileNames, packageJsonText) {
|
|
15
23
|
const names = new Set(fileNames);
|
|
16
|
-
if (names.has(
|
|
17
|
-
names.has("turbo.json") ||
|
|
18
|
-
names.has("nx.json") ||
|
|
19
|
-
names.has("lerna.json")) {
|
|
24
|
+
if ([...WORKSPACE_ROOT_FILES].some((file) => names.has(file))) {
|
|
20
25
|
return true;
|
|
21
26
|
}
|
|
22
27
|
if (!packageJsonText) {
|
|
@@ -24,10 +29,14 @@ export function hasWorkspaceSignal(fileNames, packageJsonText) {
|
|
|
24
29
|
}
|
|
25
30
|
try {
|
|
26
31
|
const parsed = JSON.parse(packageJsonText);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
if (Array.isArray(parsed.workspaces)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (!parsed.workspaces || typeof parsed.workspaces !== "object") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
// SAFETY: The preceding non-null object check proves workspaces can carry an optional packages property.
|
|
39
|
+
return Array.isArray(parsed.workspaces.packages);
|
|
31
40
|
}
|
|
32
41
|
catch {
|
|
33
42
|
return false;
|
|
@@ -77,6 +86,7 @@ function parseJsonObject(value) {
|
|
|
77
86
|
}
|
|
78
87
|
try {
|
|
79
88
|
const parsed = JSON.parse(value);
|
|
89
|
+
// SAFETY: The conditional establishes parsed is a non-null, non-array object before returning it as a dictionary.
|
|
80
90
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
81
91
|
? parsed
|
|
82
92
|
: null;
|
|
@@ -97,6 +107,7 @@ function parseTomlObject(value) {
|
|
|
97
107
|
}
|
|
98
108
|
try {
|
|
99
109
|
const parsed = parseToml(value);
|
|
110
|
+
// SAFETY: The conditional establishes parsed is a non-null, non-array object before returning it as a dictionary.
|
|
100
111
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
101
112
|
? parsed
|
|
102
113
|
: null;
|
|
@@ -111,6 +122,7 @@ function parseYamlObject(value) {
|
|
|
111
122
|
}
|
|
112
123
|
try {
|
|
113
124
|
const parsed = parseYaml(value);
|
|
125
|
+
// SAFETY: The conditional establishes parsed is a non-null, non-array object before returning it as a dictionary.
|
|
114
126
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
115
127
|
? parsed
|
|
116
128
|
: null;
|
|
@@ -129,6 +141,7 @@ export function numberField(value, key) {
|
|
|
129
141
|
}
|
|
130
142
|
function objectField(value, key) {
|
|
131
143
|
const field = value?.[key];
|
|
144
|
+
// SAFETY: The conditional establishes field is a non-null, non-array object before returning it as a dictionary.
|
|
132
145
|
return field && typeof field === "object" && !Array.isArray(field)
|
|
133
146
|
? field
|
|
134
147
|
: null;
|
|
@@ -154,11 +167,11 @@ function vercelConfig(value) {
|
|
|
154
167
|
redirects: arrayField(config, "redirects"),
|
|
155
168
|
rewrites: arrayField(config, "rewrites"),
|
|
156
169
|
headers: arrayField(config, "headers"),
|
|
170
|
+
crons: arrayField(config, "crons"),
|
|
157
171
|
unsupportedFeatures: [
|
|
158
172
|
...("functions" in config ? ["Vercel functions are not converted."] : []),
|
|
159
173
|
...("middleware" in config ? ["Vercel middleware is not converted."] : []),
|
|
160
174
|
...("images" in config ? ["Vercel image optimization config is not converted."] : []),
|
|
161
|
-
...("crons" in config ? ["Vercel cron jobs are not converted."] : []),
|
|
162
175
|
],
|
|
163
176
|
};
|
|
164
177
|
}
|
|
@@ -178,6 +191,9 @@ function netlifyConfig(value) {
|
|
|
178
191
|
redirects: arrayField(config, "redirects"),
|
|
179
192
|
rewrites: [],
|
|
180
193
|
headers: arrayField(config, "headers"),
|
|
194
|
+
// Netlify has no scheduled-request config: its scheduled functions are
|
|
195
|
+
// declared in function code, which this importer does not read.
|
|
196
|
+
crons: [],
|
|
181
197
|
unsupportedFeatures: [
|
|
182
198
|
...("functions" in config ? ["Netlify functions are not converted."] : []),
|
|
183
199
|
...("edge_functions" in config ? ["Netlify edge functions are not converted."] : []),
|
|
@@ -223,6 +239,7 @@ function cloudflarePagesConfig(input) {
|
|
|
223
239
|
redirects: [],
|
|
224
240
|
rewrites: [],
|
|
225
241
|
headers: [],
|
|
242
|
+
crons: [],
|
|
226
243
|
unsupportedFeatures,
|
|
227
244
|
};
|
|
228
245
|
}
|
|
@@ -386,6 +403,7 @@ function convertVercelHeaderRule(rule, out) {
|
|
|
386
403
|
const entries = rule.headers;
|
|
387
404
|
const headers = (Array.isArray(entries) ? entries : [])
|
|
388
405
|
.map((entry) => {
|
|
406
|
+
// SAFETY: The conditional establishes entry is a non-null object before reading dictionary properties.
|
|
389
407
|
const header = entry && typeof entry === "object" ? entry : null;
|
|
390
408
|
const key = stringField(header, "key");
|
|
391
409
|
const value = header && typeof header.value === "string" ? header.value : null;
|
|
@@ -424,8 +442,47 @@ function convertNetlifyHeaderRule(rule, out) {
|
|
|
424
442
|
}
|
|
425
443
|
out.headerBlocks.push(headerBlock(matcher, headers));
|
|
426
444
|
}
|
|
445
|
+
/**
|
|
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.
|
|
455
|
+
*/
|
|
456
|
+
function convertVercelCron(rule, out) {
|
|
457
|
+
const path = stringField(rule, "path");
|
|
458
|
+
const schedule = stringField(rule, "schedule");
|
|
459
|
+
const label = `vercel.json cron ${path ?? "(missing path)"}`;
|
|
460
|
+
if (!path || !schedule) {
|
|
461
|
+
out.notes.push(`${label}: skipped — missing path or schedule.`);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const parsed = spaceCronSchema.safeParse({ path, schedule });
|
|
465
|
+
if (!parsed.success) {
|
|
466
|
+
out.notes.push(`${label}: skipped — ${parsed.error.issues.map((issue) => issue.message).join("; ")}`);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (out.crons.some((entry) => spaceCronKey(entry.path) === spaceCronKey(path))) {
|
|
470
|
+
out.notes.push(`${label}: skipped — another cron already schedules this path.`);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (out.crons.length >= SPACE_CONFIG_CAPS.crons) {
|
|
474
|
+
out.notes.push(`${label}: skipped — at most ${SPACE_CONFIG_CAPS.crons} crons are supported.`);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
out.crons.push(parsed.data);
|
|
478
|
+
}
|
|
427
479
|
export function convertPlatformRouting(configs) {
|
|
428
|
-
const out = {
|
|
480
|
+
const out = {
|
|
481
|
+
redirectLines: [],
|
|
482
|
+
headerBlocks: [],
|
|
483
|
+
crons: [],
|
|
484
|
+
notes: [],
|
|
485
|
+
};
|
|
429
486
|
for (const config of configs) {
|
|
430
487
|
if (config.platform === "vercel") {
|
|
431
488
|
for (const rule of config.redirects) {
|
|
@@ -437,6 +494,9 @@ export function convertPlatformRouting(configs) {
|
|
|
437
494
|
for (const rule of config.headers) {
|
|
438
495
|
convertVercelHeaderRule(rule, out);
|
|
439
496
|
}
|
|
497
|
+
for (const rule of config.crons) {
|
|
498
|
+
convertVercelCron(rule, out);
|
|
499
|
+
}
|
|
440
500
|
}
|
|
441
501
|
if (config.platform === "netlify") {
|
|
442
502
|
// Netlify expresses rewrites as 200-status redirects; both arrive in
|
|
@@ -670,6 +730,19 @@ const FRAMEWORKS = [
|
|
|
670
730
|
defaultBuildCommand: "bundle exec middleman build",
|
|
671
731
|
},
|
|
672
732
|
];
|
|
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.
|
|
736
|
+
export const BUILD_MARKER_FILE_NAMES = new Set([
|
|
737
|
+
...PACKAGE_MANAGER_ONLY_FILES,
|
|
738
|
+
...WORKSPACE_ROOT_FILES,
|
|
739
|
+
"vercel.json",
|
|
740
|
+
"netlify.toml",
|
|
741
|
+
"wrangler.toml",
|
|
742
|
+
"wrangler.json",
|
|
743
|
+
"wrangler.jsonc",
|
|
744
|
+
...FRAMEWORKS.flatMap((framework) => framework.configFiles ?? []),
|
|
745
|
+
]);
|
|
673
746
|
function frameworkForPreset(frameworkPreset) {
|
|
674
747
|
return FRAMEWORKS.find((framework) => framework.preset === frameworkPreset) ?? null;
|
|
675
748
|
}
|
|
@@ -711,7 +784,7 @@ function jekyllBuildCommand(files) {
|
|
|
711
784
|
function frameworkInstallCommand(frameworkPreset, files) {
|
|
712
785
|
return frameworkPreset === "jekyll" ? jekyllInstallCommand(files) : null;
|
|
713
786
|
}
|
|
714
|
-
function installCommandForManager(packageManager, packageJson) {
|
|
787
|
+
function installCommandForManager(packageManager, packageJson, files) {
|
|
715
788
|
switch (packageManager) {
|
|
716
789
|
case "bun":
|
|
717
790
|
return "bun install --frozen-lockfile";
|
|
@@ -720,7 +793,11 @@ function installCommandForManager(packageManager, packageJson) {
|
|
|
720
793
|
case "yarn":
|
|
721
794
|
return "corepack enable && (yarn install --immutable || yarn install --frozen-lockfile)";
|
|
722
795
|
default:
|
|
723
|
-
|
|
796
|
+
if (!packageJson)
|
|
797
|
+
return null;
|
|
798
|
+
return files.has("package-lock.json") || files.has("npm-shrinkwrap.json")
|
|
799
|
+
? "npm ci"
|
|
800
|
+
: "npm install";
|
|
724
801
|
}
|
|
725
802
|
}
|
|
726
803
|
function buildCommandForDefaults(packageJson, run, frameworkPreset, files) {
|
|
@@ -740,7 +817,7 @@ function packageDefaults(input, files) {
|
|
|
740
817
|
const run = packageManager === "npm" ? "npm run" : packageManager === "bun" ? "bun run" : packageManager;
|
|
741
818
|
const frameworkPreset = detectFrameworkPreset(files, dependencies);
|
|
742
819
|
const installCommands = [
|
|
743
|
-
installCommandForManager(packageManager, packageJson),
|
|
820
|
+
installCommandForManager(packageManager, packageJson, files),
|
|
744
821
|
frameworkInstallCommand(frameworkPreset, files),
|
|
745
822
|
].filter((command) => Boolean(command));
|
|
746
823
|
const jekyllConfig = input.jekyllYaml
|
|
@@ -768,6 +845,7 @@ export function detectBuildSettings(input) {
|
|
|
768
845
|
files.has("pnpm-lock.yaml") ? "pnpm-lock.yaml" : null,
|
|
769
846
|
files.has("yarn.lock") ? "yarn.lock" : null,
|
|
770
847
|
files.has("package-lock.json") ? "package-lock.json" : null,
|
|
848
|
+
files.has("npm-shrinkwrap.json") ? "npm-shrinkwrap.json" : null,
|
|
771
849
|
files.has("Gemfile") ? "Gemfile" : null,
|
|
772
850
|
files.has("Gemfile.lock") ? "Gemfile.lock" : null,
|
|
773
851
|
files.has("_config.yml") ? "_config.yml" : null,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
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.
|
|
5
|
+
*
|
|
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.
|
|
9
|
+
*/
|
|
10
|
+
export declare function canonicalJson(value: unknown): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
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.
|
|
5
|
+
*
|
|
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.
|
|
9
|
+
*/
|
|
10
|
+
export function canonicalJson(value) {
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
13
|
+
}
|
|
14
|
+
if (value && typeof value === "object") {
|
|
15
|
+
// SAFETY: This branch establishes value is a non-null object before enumerating its entries.
|
|
16
|
+
return `{${Object.entries(value)
|
|
17
|
+
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
18
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
|
|
19
|
+
.join(",")}}`;
|
|
20
|
+
}
|
|
21
|
+
return JSON.stringify(value);
|
|
22
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The Phoenix topic a Cast room's messages travel on.
|
|
8
|
+
*
|
|
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.
|
|
21
|
+
*/
|
|
22
|
+
export declare function castRoomTopic(resourceKey: string, roomKey: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Phoenix's own `Socket` appends `/websocket` to whatever endpoint it is given,
|
|
25
|
+
* so a configured URL that already carries the suffix would otherwise become
|
|
26
|
+
* `/websocket/websocket`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function phoenixEndpoint(url: string): string;
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* `~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.
|
|
10
|
+
*/
|
|
11
|
+
const ESCAPED = new Map([
|
|
12
|
+
["~", "~0"],
|
|
13
|
+
["/", "~1"],
|
|
14
|
+
[":", "~2"],
|
|
15
|
+
["\\", "~3"],
|
|
16
|
+
["%", "~4"],
|
|
17
|
+
["\0", "~5"],
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* The Phoenix topic a Cast room's messages travel on.
|
|
21
|
+
*
|
|
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.
|
|
34
|
+
*/
|
|
35
|
+
export function castRoomTopic(resourceKey, roomKey) {
|
|
36
|
+
let escaped = "";
|
|
37
|
+
for (const character of roomKey)
|
|
38
|
+
escaped += ESCAPED.get(character) ?? character;
|
|
39
|
+
return `room:${resourceKey}:${encodeURIComponent(escaped)}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Phoenix's own `Socket` appends `/websocket` to whatever endpoint it is given,
|
|
43
|
+
* so a configured URL that already carries the suffix would otherwise become
|
|
44
|
+
* `/websocket/websocket`.
|
|
45
|
+
*/
|
|
46
|
+
export function phoenixEndpoint(url) {
|
|
47
|
+
const parsed = new URL(url);
|
|
48
|
+
if (parsed.pathname.endsWith("/websocket")) {
|
|
49
|
+
parsed.pathname = parsed.pathname.slice(0, -"/websocket".length);
|
|
50
|
+
}
|
|
51
|
+
return parsed.href.replace(/\/$/, "");
|
|
52
|
+
}
|
|
@@ -1,9 +1 @@
|
|
|
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;
|
|
9
1
|
export declare function normalizeClaimToken(token: string | null | undefined): string | undefined;
|
|
@@ -1,18 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
+
// 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.
|
|
16
4
|
export function normalizeClaimToken(token) {
|
|
17
5
|
const trimmed = token?.trim();
|
|
18
6
|
if (!trimmed) {
|
|
@@ -10,6 +10,7 @@ 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,7 +16,7 @@ export type PolicyAuthorizeInput = {
|
|
|
16
16
|
resource: string | readonly string[];
|
|
17
17
|
conditions?: RequestCondition;
|
|
18
18
|
};
|
|
19
|
-
export declare const publishActions: ("spaces:read" | "spaces:write" | "spaces:
|
|
19
|
+
export declare const publishActions: ("spaces:read" | "spaces:write" | "spaces:publish" | "builds:trigger" | "spaces:create" | "versions:read" | "versions:promote" | "versions:download" | "builds:read" | "builds:create")[];
|
|
20
20
|
export declare function compileApiKeyPreset(preset: ApiKeyPreset, input: PolicyCompileInput): ApiCredentialPolicy;
|
|
21
21
|
/** Expand a named API-key preset into the finite permissions sent on the public wire contract. */
|
|
22
22
|
export declare function apiKeyPresetPermissions(preset: ApiKeyPreset): StatticAction[];
|