@spacefast/common 0.0.3 → 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 (159) 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 +829 -129
  8. package/dist/contracts/access.js +771 -148
  9. package/dist/contracts/activity.d.ts +5 -5
  10. package/dist/contracts/activity.js +28 -2
  11. package/dist/contracts/annotations.d.ts +3 -5
  12. package/dist/contracts/api-keys.d.ts +9 -10
  13. package/dist/contracts/api-keys.js +9 -6
  14. package/dist/contracts/archives.d.ts +22 -16
  15. package/dist/contracts/archives.js +11 -4
  16. package/dist/contracts/billing.d.ts +21 -30
  17. package/dist/contracts/builds.d.ts +21 -17
  18. package/dist/contracts/builds.js +12 -0
  19. package/dist/contracts/channels.d.ts +2 -2
  20. package/dist/contracts/common.d.ts +7 -5
  21. package/dist/contracts/common.js +4 -0
  22. package/dist/contracts/continuation.d.ts +63 -0
  23. package/dist/contracts/continuation.js +91 -0
  24. package/dist/contracts/countries.d.ts +2 -0
  25. package/dist/contracts/countries.js +261 -0
  26. package/dist/contracts/deployments.d.ts +34 -34
  27. package/dist/contracts/device-auth.d.ts +4 -11
  28. package/dist/contracts/device-auth.js +4 -0
  29. package/dist/contracts/docs.d.ts +119 -0
  30. package/dist/contracts/docs.js +51 -0
  31. package/dist/contracts/domains.d.ts +295 -253
  32. package/dist/contracts/domains.js +26 -4
  33. package/dist/contracts/enums.d.ts +17 -18
  34. package/dist/contracts/enums.js +25 -14
  35. package/dist/contracts/error-code-meta.d.ts +84 -0
  36. package/dist/contracts/error-code-meta.js +99 -10
  37. package/dist/contracts/error-codes.d.ts +1 -1
  38. package/dist/contracts/error-codes.js +17 -0
  39. package/dist/contracts/events.d.ts +13 -13
  40. package/dist/contracts/features.d.ts +93 -23
  41. package/dist/contracts/features.js +178 -13
  42. package/dist/contracts/git.d.ts +3 -9
  43. package/dist/contracts/ids.d.ts +5 -0
  44. package/dist/contracts/ids.js +5 -0
  45. package/dist/contracts/internal.d.ts +3 -3
  46. package/dist/contracts/mcp.d.ts +34 -36
  47. package/dist/contracts/me.d.ts +8 -8
  48. package/dist/contracts/me.js +1 -1
  49. package/dist/contracts/oauth-resources.d.ts +19 -8
  50. package/dist/contracts/oauth-resources.js +44 -13
  51. package/dist/contracts/operations.d.ts +2 -2
  52. package/dist/contracts/plan-policy.d.ts +4 -4
  53. package/dist/contracts/platform.d.ts +3 -105
  54. package/dist/contracts/platform.js +0 -50
  55. package/dist/contracts/push-new.d.ts +4 -4
  56. package/dist/contracts/quotas.d.ts +2 -0
  57. package/dist/contracts/quotas.js +8 -0
  58. package/dist/contracts/repository-connections.d.ts +1 -3
  59. package/dist/contracts/resources.d.ts +112 -28
  60. package/dist/contracts/resources.js +26 -2
  61. package/dist/contracts/routes.d.ts +0 -2
  62. package/dist/contracts/runtime-api.d.ts +365 -43
  63. package/dist/contracts/runtime-api.js +275 -6
  64. package/dist/contracts/sites.d.ts +45 -28
  65. package/dist/contracts/sites.js +21 -0
  66. package/dist/contracts/space-config.d.ts +201 -2
  67. package/dist/contracts/space-config.js +135 -5
  68. package/dist/contracts/spaces.d.ts +423 -162
  69. package/dist/contracts/spaces.js +94 -8
  70. package/dist/contracts/superadmin-emails.d.ts +7 -5
  71. package/dist/contracts/superadmin-emails.js +8 -0
  72. package/dist/contracts/superadmin-queues.d.ts +32 -32
  73. package/dist/contracts/superadmin-spaces.d.ts +418 -24
  74. package/dist/contracts/superadmin-spaces.js +24 -0
  75. package/dist/contracts/superadmin-tenants.d.ts +8 -8
  76. package/dist/contracts/superadmin.d.ts +17 -104
  77. package/dist/contracts/superadmin.js +6 -14
  78. package/dist/contracts/tags.d.ts +42 -41
  79. package/dist/contracts/tags.js +2 -1
  80. package/dist/contracts/teams.d.ts +40 -23
  81. package/dist/contracts/teams.js +20 -1
  82. package/dist/contracts/theme-json.d.ts +30 -0
  83. package/dist/contracts/theme-json.js +48 -0
  84. package/dist/contracts/transfers.d.ts +9 -9
  85. package/dist/contracts/variables.d.ts +14 -16
  86. package/dist/contracts/variables.js +0 -5
  87. package/dist/contracts/webhooks.d.ts +6 -4
  88. package/dist/contracts/webhooks.js +1 -1
  89. package/dist/contracts/zero.d.ts +13 -18
  90. package/dist/docs/agent-prose.d.ts +43 -3
  91. package/dist/docs/agent-prose.js +153 -10
  92. package/dist/docs/agent-setup.js +48 -15
  93. package/dist/docs/agent-solutions.d.ts +5 -5
  94. package/dist/docs/agent-solutions.js +2 -2
  95. package/dist/docs/catalog.d.ts +650 -0
  96. package/dist/docs/catalog.js +758 -0
  97. package/dist/docs/error-docs.d.ts +0 -2
  98. package/dist/docs/error-docs.js +89 -35
  99. package/dist/docs/index-build.d.ts +17 -0
  100. package/dist/docs/index-build.js +24 -0
  101. package/dist/docs/search.d.ts +54 -0
  102. package/dist/docs/search.js +96 -0
  103. package/dist/docs/skill-distribution.d.ts +5 -3
  104. package/dist/docs/skill-distribution.js +8 -8
  105. package/dist/utils/access-match.d.ts +39 -0
  106. package/dist/utils/access-match.js +296 -0
  107. package/dist/utils/auth-redirect.d.ts +3 -1
  108. package/dist/utils/auth-redirect.js +21 -1
  109. package/dist/utils/build-settings.d.ts +6 -0
  110. package/dist/utils/build-settings.js +429 -46
  111. package/dist/utils/claim-token.d.ts +8 -0
  112. package/dist/utils/claim-token.js +23 -2
  113. package/dist/utils/content-type.d.ts +1 -0
  114. package/dist/utils/content-type.js +1 -1
  115. package/dist/utils/credential-policy.d.ts +54 -0
  116. package/dist/utils/{access-policy.js → credential-policy.js} +28 -2
  117. package/dist/utils/gate-theme.d.ts +61 -0
  118. package/dist/utils/gate-theme.js +217 -0
  119. package/dist/utils/git-repository.d.ts +0 -1
  120. package/dist/utils/git-repository.js +0 -3
  121. package/dist/utils/id-hints.d.ts +8 -0
  122. package/dist/utils/id-hints.js +61 -0
  123. package/dist/utils/local-space-state.d.ts +91 -0
  124. package/dist/utils/local-space-state.js +251 -0
  125. package/dist/utils/oauth-signed-query.d.ts +8 -0
  126. package/dist/utils/oauth-signed-query.js +26 -0
  127. package/dist/utils/publish-detection.d.ts +0 -8
  128. package/dist/utils/publish-detection.js +0 -93
  129. package/dist/utils/publish-policy.d.ts +6 -5
  130. package/dist/utils/publish-policy.js +71 -7
  131. package/dist/utils/query-keys.d.ts +17 -6
  132. package/dist/utils/query-keys.js +27 -4
  133. package/dist/utils/runtime-paths.d.ts +0 -1
  134. package/dist/utils/runtime-paths.js +0 -1
  135. package/dist/utils/runtime-upload-batch.d.ts +18 -0
  136. package/dist/utils/runtime-upload-batch.js +90 -0
  137. package/dist/utils/runtime-upload.d.ts +2 -0
  138. package/dist/utils/runtime-upload.js +5 -0
  139. package/dist/utils/space-config.d.ts +4 -0
  140. package/dist/utils/space-config.js +10 -1
  141. package/dist/utils/space-theme.d.ts +2 -0
  142. package/dist/utils/space-theme.js +9 -0
  143. package/dist/utils/static-runtime-policy.d.ts +1 -1
  144. package/dist/utils/static-runtime-policy.js +47 -15
  145. package/dist/utils/upload-session.d.ts +1 -1
  146. package/dist/utils/wpcom-auth-redirects.d.ts +32 -2
  147. package/dist/utils/wpcom-auth-redirects.js +26 -10
  148. package/dist/vocabulary.d.ts +130 -30
  149. package/dist/vocabulary.js +77 -36
  150. package/package.json +16 -1
  151. package/dist/contracts/account.d.ts +0 -28
  152. package/dist/contracts/account.js +0 -54
  153. package/dist/contracts/intercom.d.ts +0 -6
  154. package/dist/contracts/intercom.js +0 -5
  155. package/dist/contracts/telemetry.d.ts +0 -2
  156. package/dist/contracts/telemetry.js +0 -1
  157. package/dist/utils/access-policy.d.ts +0 -39
  158. package/dist/utils/error-display.d.ts +0 -7
  159. package/dist/utils/error-display.js +0 -42
@@ -0,0 +1,251 @@
1
+ import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { z } from "zod";
4
+ export const LOCAL_SPACE_STATE_DIR_NAME = ".spacefast";
5
+ export const LEGACY_LOCAL_SPACE_STATE_DIR_NAME = ".stattic";
6
+ export const LOCAL_SPACE_LINK_FILE_NAME = "space.json";
7
+ export const LOCAL_SPACE_STATE_FILE_NAME = "state.json";
8
+ /**
9
+ * Local Spacefast checkout contract.
10
+ *
11
+ * `.spacefast/space.json` is the committable selection axis: it names which
12
+ * space a checkout targets (`space`) and may carry non-secret provider/team
13
+ * coordinates (`team`, `apiBaseUrl`). It must not carry credentials.
14
+ *
15
+ * `.spacefast/state.json` is the local credential/cache axis: it records the
16
+ * resolved `spaceId`, provider `apiUrl`, anonymous `claimToken` before claim,
17
+ * durable `accessToken` after continuation, and small publish caches such as
18
+ * `lastVersionId`. It is secret, machine-written, gitignored, and merge-written
19
+ * so newer tools preserve unknown fields from older/newer tools.
20
+ */
21
+ export const localSpaceLinkSchema = z
22
+ .object({
23
+ team: z.string().min(1).optional(),
24
+ space: z.string().min(1),
25
+ apiBaseUrl: z.string().min(1).optional(),
26
+ })
27
+ .loose();
28
+ export const localSpaceStateSchema = z
29
+ .object({
30
+ spaceId: z.string().min(1).optional(),
31
+ apiUrl: z.string().min(1).optional(),
32
+ teamSlug: z.string().min(1).optional(),
33
+ claimToken: z.string().min(1).optional(),
34
+ claimUrl: z.string().min(1).optional(),
35
+ expiresAt: z.string().min(1).optional(),
36
+ accessToken: z.string().min(1).optional(),
37
+ lastVersionId: z.string().min(1).optional(),
38
+ lastVersionUrl: z.string().min(1).optional(),
39
+ lastSpaceUrl: z.string().min(1).optional(),
40
+ })
41
+ .loose();
42
+ export function localSpaceStateDir(root) {
43
+ return path.join(root, LOCAL_SPACE_STATE_DIR_NAME);
44
+ }
45
+ export function localSpaceStatePath(root) {
46
+ return path.join(localSpaceStateDir(root), LOCAL_SPACE_STATE_FILE_NAME);
47
+ }
48
+ export function localSpaceLinkPath(root) {
49
+ return path.join(localSpaceStateDir(root), LOCAL_SPACE_LINK_FILE_NAME);
50
+ }
51
+ function legacyStatePath(root) {
52
+ return path.join(root, LEGACY_LOCAL_SPACE_STATE_DIR_NAME, LOCAL_SPACE_STATE_FILE_NAME);
53
+ }
54
+ function legacyLinkPath(root) {
55
+ return path.join(root, LEGACY_LOCAL_SPACE_STATE_DIR_NAME, LOCAL_SPACE_LINK_FILE_NAME);
56
+ }
57
+ async function readJsonFile(filePath) {
58
+ const raw = await readFile(filePath, "utf8").catch(() => undefined);
59
+ if (raw === undefined)
60
+ return undefined;
61
+ try {
62
+ return JSON.parse(raw);
63
+ }
64
+ catch {
65
+ return undefined;
66
+ }
67
+ }
68
+ function parseState(value) {
69
+ const parsed = localSpaceStateSchema.safeParse(value);
70
+ return parsed.success ? parsed.data : null;
71
+ }
72
+ function parseLink(value) {
73
+ const parsed = localSpaceLinkSchema.safeParse(value);
74
+ return parsed.success ? parsed.data : null;
75
+ }
76
+ function linkSpaceId(link) {
77
+ return link?.space.startsWith("spc_") ? link.space : undefined;
78
+ }
79
+ export async function readLocalSpaceLink(root) {
80
+ return (await readLocalSpaceLinkWithPath(root))?.link ?? null;
81
+ }
82
+ /**
83
+ * Like readLocalSpaceLink, but reports WHICH file matched — callers surface the
84
+ * legacy `.stattic/space.json` path to users so migrations stay visible.
85
+ */
86
+ export async function readLocalSpaceLinkWithPath(root) {
87
+ const primary = parseLink(await readJsonFile(localSpaceLinkPath(root)));
88
+ if (primary) {
89
+ return { link: primary, path: localSpaceLinkPath(root) };
90
+ }
91
+ const legacy = parseLink(await readJsonFile(legacyLinkPath(root)));
92
+ return legacy ? { link: legacy, path: legacyLinkPath(root) } : null;
93
+ }
94
+ export async function readLocalSpaceStateAt(root) {
95
+ const statePath = localSpaceStatePath(root);
96
+ const state = parseState(await readJsonFile(statePath)) ??
97
+ parseState(await readJsonFile(legacyStatePath(root)));
98
+ const linkPath = localSpaceLinkPath(root);
99
+ const link = parseLink(await readJsonFile(linkPath)) ?? parseLink(await readJsonFile(legacyLinkPath(root)));
100
+ if (!state && !link)
101
+ return null;
102
+ return {
103
+ root,
104
+ statePath: state ? statePath : null,
105
+ linkPath: link ? linkPath : null,
106
+ link,
107
+ state,
108
+ spaceId: state?.spaceId ?? linkSpaceId(link),
109
+ claimToken: state?.claimToken,
110
+ accessToken: state?.accessToken,
111
+ apiUrl: state?.apiUrl ?? link?.apiBaseUrl,
112
+ claimUrl: state?.claimUrl,
113
+ expiresAt: state?.expiresAt,
114
+ lastVersionId: state?.lastVersionId,
115
+ };
116
+ }
117
+ function isWithinBoundary(current, boundary) {
118
+ const relative = path.relative(boundary, current);
119
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
120
+ }
121
+ /**
122
+ * Walk up from `startDir` to the filesystem root (or `boundary`, when given),
123
+ * returning the first non-null result of `probe`. Shared by every "nearest
124
+ * checkout marker" lookup (state+link discovery here, the CLI's link-only
125
+ * discovery) so the traversal rules live in one place.
126
+ */
127
+ export async function findUpward(probe, startDir = process.cwd(), boundary) {
128
+ let current = path.resolve(startDir);
129
+ const resolvedBoundary = boundary ? path.resolve(boundary) : undefined;
130
+ if (resolvedBoundary && !isWithinBoundary(current, resolvedBoundary)) {
131
+ current = resolvedBoundary;
132
+ }
133
+ for (;;) {
134
+ const found = await probe(current);
135
+ if (found)
136
+ return found;
137
+ if (resolvedBoundary && current === resolvedBoundary)
138
+ return null;
139
+ const parent = path.dirname(current);
140
+ if (parent === current)
141
+ return null;
142
+ if (resolvedBoundary && !isWithinBoundary(parent, resolvedBoundary))
143
+ return null;
144
+ current = parent;
145
+ }
146
+ }
147
+ export async function discoverLocalSpaceState(startDir = process.cwd(), boundary) {
148
+ return findUpward(readLocalSpaceStateAt, startDir, boundary);
149
+ }
150
+ async function writeJsonAtomic(filePath, value, mode) {
151
+ await mkdir(path.dirname(filePath), { recursive: true });
152
+ const tempPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
153
+ try {
154
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\n`, {
155
+ encoding: "utf8",
156
+ ...(mode ? { mode } : {}),
157
+ });
158
+ if (mode) {
159
+ await chmod(tempPath, mode).catch(() => undefined);
160
+ }
161
+ await rename(tempPath, filePath);
162
+ if (mode) {
163
+ await chmod(filePath, mode).catch(() => undefined);
164
+ }
165
+ }
166
+ catch (error) {
167
+ await rm(tempPath, { force: true }).catch(() => undefined);
168
+ throw error;
169
+ }
170
+ }
171
+ async function ensureStateGitignored(root) {
172
+ const gitignorePath = path.join(root, ".gitignore");
173
+ const entry = `${LOCAL_SPACE_STATE_DIR_NAME}/${LOCAL_SPACE_STATE_FILE_NAME}`;
174
+ const raw = await readFile(gitignorePath, "utf8").catch(() => "");
175
+ const lines = raw.split(/\r?\n/);
176
+ if (lines.includes(entry))
177
+ return;
178
+ const prefix = raw.length > 0 && !raw.endsWith("\n") ? "\n" : "";
179
+ await writeFile(gitignorePath, `${raw}${prefix}${entry}\n`, "utf8");
180
+ }
181
+ function assignDefined(target, input) {
182
+ for (const [key, value] of Object.entries(input)) {
183
+ if (value === undefined) {
184
+ delete target[key];
185
+ }
186
+ else {
187
+ target[key] = value;
188
+ }
189
+ }
190
+ }
191
+ function assignPresent(target, input) {
192
+ for (const [key, value] of Object.entries(input)) {
193
+ if (value !== undefined) {
194
+ target[key] = value;
195
+ }
196
+ }
197
+ }
198
+ export async function writeLocalSpaceLink(root, link) {
199
+ const filePath = localSpaceLinkPath(root);
200
+ const payload = localSpaceLinkSchema.parse({
201
+ space: link.space,
202
+ ...(link.team ? { team: link.team } : {}),
203
+ ...(link.apiBaseUrl ? { apiBaseUrl: link.apiBaseUrl } : {}),
204
+ });
205
+ await writeJsonAtomic(filePath, payload);
206
+ return filePath;
207
+ }
208
+ export async function writeLocalSpaceState(root, state) {
209
+ const filePath = localSpaceStatePath(root);
210
+ const payload = localSpaceStateSchema.parse(state);
211
+ await writeJsonAtomic(filePath, payload, 0o600);
212
+ await ensureStateGitignored(root);
213
+ return filePath;
214
+ }
215
+ export async function saveLocalSpaceState(root, input) {
216
+ const existing = parseState(await readJsonFile(localSpaceStatePath(root))) ??
217
+ parseState(await readJsonFile(legacyStatePath(root))) ??
218
+ {};
219
+ const next = { ...existing };
220
+ assignPresent(next, {
221
+ spaceId: input.spaceId,
222
+ apiUrl: input.apiUrl,
223
+ teamSlug: input.teamSlug,
224
+ lastVersionId: input.lastVersionId,
225
+ lastVersionUrl: input.lastVersionUrl,
226
+ lastSpaceUrl: input.lastSpaceUrl,
227
+ });
228
+ if (input.accessToken) {
229
+ assignDefined(next, {
230
+ accessToken: input.accessToken,
231
+ claimToken: undefined,
232
+ claimUrl: undefined,
233
+ expiresAt: undefined,
234
+ });
235
+ }
236
+ else if (input.claimToken) {
237
+ assignDefined(next, {
238
+ claimToken: input.claimToken,
239
+ claimUrl: input.claimUrl,
240
+ expiresAt: input.expiresAt,
241
+ accessToken: undefined,
242
+ });
243
+ }
244
+ const statePath = await writeLocalSpaceState(root, next);
245
+ const existingLink = await readLocalSpaceLink(root);
246
+ const wroteLink = !existingLink;
247
+ if (!existingLink) {
248
+ await writeLocalSpaceLink(root, { space: input.spaceId, apiBaseUrl: input.apiUrl });
249
+ }
250
+ return { statePath, linkPath: localSpaceLinkPath(root), wroteLink };
251
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The signed OAuth authorize continuation carried by a query string, or null
3
+ * when there isn't one. Better Auth's signature covers every param it sent
4
+ * (the set is self-declared in `ba_param`) — anything extra that later joined
5
+ * the URL (a returnTo, a tracking param) must be dropped before the query is
6
+ * sent back, or verification fails.
7
+ */
8
+ export declare function signedOAuthQuerySubset(query: string): string | null;
@@ -0,0 +1,26 @@
1
+ // Better Auth's OAuth authorize endpoint hands interaction prompts (login,
2
+ // consent) to the dashboard by redirecting with the ENTIRE authorize query,
3
+ // HMAC-signed by the auth server (`sig` + `exp` + `ba_*` bookkeeping params).
4
+ // Verification hashes the query verbatim, so it must round-trip byte-exact.
5
+ /**
6
+ * The signed OAuth authorize continuation carried by a query string, or null
7
+ * when there isn't one. Better Auth's signature covers every param it sent
8
+ * (the set is self-declared in `ba_param`) — anything extra that later joined
9
+ * the URL (a returnTo, a tracking param) must be dropped before the query is
10
+ * sent back, or verification fails.
11
+ */
12
+ export function signedOAuthQuerySubset(query) {
13
+ const params = new URLSearchParams(query);
14
+ if (!params.get("client_id") || !params.get("sig"))
15
+ return null;
16
+ const signedNames = new Set(params.getAll("ba_param"));
17
+ if (signedNames.size === 0)
18
+ return null;
19
+ const signed = new URLSearchParams();
20
+ for (const [key, value] of params) {
21
+ if (key === "sig" || key === "ba_param" || signedNames.has(key)) {
22
+ signed.append(key, value);
23
+ }
24
+ }
25
+ return signed.toString();
26
+ }
@@ -1,10 +1,2 @@
1
- import type { SpaceConfig } from "../contracts/space-config.js";
2
- import type { SpaceSettingsPatch } from "./space-config.js";
3
1
  export declare function detectSpaFromPaths(paths: Iterable<string>): boolean;
4
2
  export declare function detectSpaFallbackFromPaths(paths: Iterable<string>): string | null;
5
- export type SpaceSettingsFromConfigJson = SpaceSettingsPatch & {
6
- title?: string;
7
- password?: string | null;
8
- };
9
- export declare function parseSpaceSettingsFromConfigJson(raw: string | undefined): SpaceSettingsFromConfigJson | null;
10
- export declare function spaceConfigFromSettings(base: SpaceConfig | null | undefined, patch: SpaceSettingsPatch): SpaceConfig;
@@ -1,6 +1,3 @@
1
- function isRecord(value) {
2
- return typeof value === "object" && value !== null && !Array.isArray(value);
3
- }
4
1
  export function detectSpaFromPaths(paths) {
5
2
  return detectSpaFallbackFromPaths(paths) !== null;
6
3
  }
@@ -26,93 +23,3 @@ export function detectSpaFallbackFromPaths(paths) {
26
23
  entry === "asset-manifest.json");
27
24
  return hasSpaAssets ? "/index.html" : null;
28
25
  }
29
- // Parses the legacy-vocabulary keys of a local `.stattic/config.json` file
30
- // (title/mode/spa/password/viewer). Platform SpaceConfig keys are handled
31
- // separately by the publish flow.
32
- export function parseSpaceSettingsFromConfigJson(raw) {
33
- if (!raw) {
34
- return null;
35
- }
36
- let parsedJson;
37
- try {
38
- parsedJson = JSON.parse(raw);
39
- }
40
- catch {
41
- return null;
42
- }
43
- if (!isRecord(parsedJson)) {
44
- return null;
45
- }
46
- const settings = {};
47
- if (parsedJson.title && typeof parsedJson.title === "string") {
48
- settings.title = parsedJson.title;
49
- }
50
- if (parsedJson.mode === "website" || parsedJson.mode === "files") {
51
- settings.mode = parsedJson.mode;
52
- }
53
- if (typeof parsedJson.spa === "boolean") {
54
- settings.spa = parsedJson.spa;
55
- }
56
- if (parsedJson.password === null || typeof parsedJson.password === "string") {
57
- settings.password = parsedJson.password;
58
- }
59
- if (isRecord(parsedJson.viewer)) {
60
- const viewer = {};
61
- for (const key of ["title", "description", "ogImagePath"]) {
62
- const value = parsedJson.viewer[key];
63
- if (value === null || typeof value === "string") {
64
- viewer[key] = value;
65
- }
66
- }
67
- if (Object.keys(viewer).length > 0) {
68
- settings.viewer = viewer;
69
- }
70
- }
71
- return Object.keys(settings).length > 0 ? settings : null;
72
- }
73
- // Compiles the legacy `.stattic/config.json` serving vocabulary (mode/spa/viewer)
74
- // onto a SpaceConfig overlay. Files mode = directory listing with no index; SPA =
75
- // the legacy "/index.html" fallback; viewer fields map onto meta. Only the publish flow
76
- // reads a legacy config.json, so this translation lives beside its parser.
77
- export function spaceConfigFromSettings(base, patch) {
78
- const config = { ...base };
79
- if (patch.mode === "files") {
80
- config.listing = true;
81
- config.index = false;
82
- }
83
- else if (patch.mode === "website") {
84
- delete config.listing;
85
- delete config.index;
86
- }
87
- if (patch.spa === true) {
88
- config.fallback = "/index.html";
89
- }
90
- else if (patch.spa === false) {
91
- delete config.fallback;
92
- }
93
- if (patch.viewer) {
94
- const meta = { ...config.meta };
95
- const entries = [
96
- ["title", patch.viewer.title],
97
- ["description", patch.viewer.description],
98
- ["image", patch.viewer.ogImagePath],
99
- ];
100
- for (const [key, value] of entries) {
101
- if (value === undefined)
102
- continue;
103
- if (value === null || value === "") {
104
- delete meta[key];
105
- }
106
- else {
107
- meta[key] = value;
108
- }
109
- }
110
- if (Object.keys(meta).length > 0) {
111
- config.meta = meta;
112
- }
113
- else {
114
- delete config.meta;
115
- }
116
- }
117
- return config;
118
- }
@@ -21,6 +21,7 @@ export declare function firstStaticUploadPathViolation(paths: string[], options?
21
21
  export type PublishManifestCaps = {
22
22
  maxFiles?: number | null | undefined;
23
23
  maxFileBytes?: number | null | undefined;
24
+ maxTotalBytes?: number | null | undefined;
24
25
  };
25
26
  export type PublishManifestError = {
26
27
  code: ErrorCode;
@@ -50,16 +51,16 @@ export declare function validatePublishManifest<T extends {
50
51
  path: string;
51
52
  size: number;
52
53
  }>(files: readonly T[], caps?: PublishManifestCaps): PublishManifestResult<T>;
53
- export declare function freePlanCanUploadPath(filePath: string, options?: {
54
+ export declare const ANONYMOUS_SERVE_CONTENT_TYPES: readonly ["text/*", "image/*", "font/*", "application/json", "application/manifest+json", "application/javascript", "application/gzip", "application/xml", "application/rss+xml", "application/atom+xml"];
55
+ export type FreePlanContentPolicyOptions = {
54
56
  claimedSpace?: boolean;
55
- }): boolean;
57
+ };
58
+ export declare function freePlanCanUploadPath(filePath: string, options?: FreePlanContentPolicyOptions): boolean;
56
59
  export declare function freePlanCanUploadContentType(contentType: string | undefined): boolean;
57
60
  export declare function filterFreePlanManifest<T extends {
58
61
  path: string;
59
62
  contentType?: string | undefined;
60
- }>(files: readonly T[], options?: {
61
- claimedSpace?: boolean;
62
- }): {
63
+ }>(files: readonly T[], options?: FreePlanContentPolicyOptions): {
63
64
  allowed: T[];
64
65
  ignored: string[];
65
66
  };
@@ -27,6 +27,9 @@ const IGNORED_EXACT_PATHS = new Set([
27
27
  ".env.test",
28
28
  ".npmrc",
29
29
  ".netrc",
30
+ // CLI checkout state (current dir name plus the legacy pre-rename one —
31
+ // user archives may contain either; both stay out of every publish).
32
+ ".spacefast/state.json",
30
33
  ".stattic/state.json",
31
34
  "_routes.json",
32
35
  ]);
@@ -52,14 +55,36 @@ const SENSITIVE_FILE_PATTERNS = [
52
55
  ];
53
56
  // Denied runtime private/control paths (spec "Static path policy").
54
57
  const STATIC_EXECUTION_CONTROL_FILES = new Set([".htaccess", ".user.ini"]);
55
- const STATIC_RUNTIME_CONTROL_SEGMENTS = new Set(["__spacefast", "__stattic"]);
56
- const STATIC_RUNTIME_CONTROL_PATHS = new Set([".well-known/stattic-runtime"]);
57
- const STATIC_RUNTIME_CONTROL_PREFIXES = [".stattic/storage", ".stattic/engine"];
58
+ // Reserved runtime namespace segments are matched by PREFIX: the runtime owns
59
+ // `/__spacefast/*` plus `__spacefast_generated/`, and the `__stattic` spelling
60
+ // (`/__stattic_probe` today, more `/__stattic/*` routes planned) stays
61
+ // reserved the same way. Mid-segment occurrences are ordinary content.
62
+ const STATIC_RUNTIME_CONTROL_SEGMENT_PREFIXES = ["__spacefast", "__stattic"];
63
+ // Both the current and the legacy control-path spellings stay denied: spaces
64
+ // published before the `.stattic` → `.spacefast` rename still serve from the
65
+ // legacy on-disk layout, and user archives may carry either name.
66
+ const STATIC_RUNTIME_CONTROL_PATHS = new Set([
67
+ ".well-known/spacefast-runtime",
68
+ ".well-known/stattic-runtime",
69
+ ]);
70
+ const STATIC_RUNTIME_CONTROL_PREFIXES = [
71
+ ".spacefast/storage",
72
+ ".spacefast/engine",
73
+ ".stattic/storage",
74
+ ".stattic/engine",
75
+ ];
58
76
  export function normalizePublishPath(relativePath) {
59
77
  return relativePath.replace(/\\/g, "/").replace(/^\/+/g, "");
60
78
  }
61
79
  export function publishPathIgnored(relativePath) {
62
80
  const path = normalizePublishPath(relativePath);
81
+ // The agent state dirs hold one secret (state.json) plus committable tooling
82
+ // (space.json). Drop only those files at any depth so a checkout archived one
83
+ // level too high never ships a claim token — but keep serving legitimate
84
+ // content in the same dirs (e.g. the legacy .stattic/routes.json convention).
85
+ if (/(?:^|\/)\.(?:spacefast|stattic)\/(?:state|space)\.json$/.test(path)) {
86
+ return true;
87
+ }
63
88
  if (IGNORED_EXACT_PATHS.has(path) || /(?:^|\/)\.next\/cache(?:\/|$)/.test(path)) {
64
89
  return true;
65
90
  }
@@ -154,7 +179,10 @@ export function staticUploadPathViolation(path, options = {}) {
154
179
  if (reservedRuntimeControlFiles?.has(lowerName) && lowerPath === lowerName) {
155
180
  return pathViolation("static_runtime_control_path_not_supported", "Static deploys cannot upload runtime control paths.", normalizedPath);
156
181
  }
157
- const controlSegment = segments.find((segment) => STATIC_RUNTIME_CONTROL_SEGMENTS.has(segment.toLowerCase()));
182
+ const controlSegment = segments.find((segment) => {
183
+ const lowerSegment = segment.toLowerCase();
184
+ return STATIC_RUNTIME_CONTROL_SEGMENT_PREFIXES.some((prefix) => lowerSegment.startsWith(prefix));
185
+ });
158
186
  if (controlSegment) {
159
187
  return pathViolation("static_runtime_control_path_not_supported", "Static deploys cannot upload runtime control paths.", normalizedPath);
160
188
  }
@@ -240,6 +268,13 @@ export function validatePublishManifest(files, caps = {}) {
240
268
  warnings: [],
241
269
  };
242
270
  }
271
+ // Checkout state and credential paths (.spacefast/, .git/, .env*, …) are
272
+ // silently dropped rather than rejected: agents that archive one level too
273
+ // high must never publish a claim token to the live site, and failing the
274
+ // whole manifest for it would punish the common accident.
275
+ if (publishPathIgnored(canonicalPath)) {
276
+ continue;
277
+ }
243
278
  // Case-only-differing paths are distinct (paths are byte-exact and
244
279
  // case-sensitive) but cannot round-trip case-insensitive filesystems.
245
280
  const lowercase = canonicalPath.toLowerCase();
@@ -271,13 +306,14 @@ export function validatePublishManifest(files, caps = {}) {
271
306
  warnings: [],
272
307
  };
273
308
  }
309
+ const maxTotalBytes = Math.min(MAX_VERSION_TOTAL_BYTES, caps.maxTotalBytes && caps.maxTotalBytes > 0 ? caps.maxTotalBytes : MAX_VERSION_TOTAL_BYTES);
274
310
  const totalBytes = canonicalFiles.reduce((sum, file) => sum + file.size, 0);
275
- if (totalBytes > MAX_VERSION_TOTAL_BYTES) {
311
+ if (totalBytes > maxTotalBytes) {
276
312
  return {
277
313
  error: {
278
314
  code: "version_total_bytes_exceeded",
279
- message: `Version exceeds the ${MAX_VERSION_TOTAL_BYTES} byte total size limit.`,
280
- details: { totalBytes, limit: MAX_VERSION_TOTAL_BYTES },
315
+ message: `Version exceeds the ${maxTotalBytes} byte total size limit for this plan.`,
316
+ details: { totalBytes, limit: maxTotalBytes },
281
317
  },
282
318
  files: null,
283
319
  warnings: [],
@@ -319,6 +355,25 @@ const FREE_PLAN_ALLOWED_UPLOAD_EXTENSIONS = new Set([
319
355
  ".ttf",
320
356
  ".otf",
321
357
  ]);
358
+ // THE anonymous content enforcement (accept-and-scan posture): uploads are
359
+ // accepted like any free publish, but until the space is claimed the runtime
360
+ // only serves these content types, pushed as the generic route-config
361
+ // `content_types` allowlist (exact types or `prefix/*` wildcards; the engine
362
+ // enforces the list without knowing it means "anonymous"). Web-page content
363
+ // only — no opaque octet-streams, archives (except scanned gzip, which
364
+ // pagefind search indexes depend on), documents, or media containers.
365
+ export const ANONYMOUS_SERVE_CONTENT_TYPES = [
366
+ "text/*",
367
+ "image/*",
368
+ "font/*",
369
+ "application/json",
370
+ "application/manifest+json",
371
+ "application/javascript",
372
+ "application/gzip",
373
+ "application/xml",
374
+ "application/rss+xml",
375
+ "application/atom+xml",
376
+ ];
322
377
  const LINE_SPLIT_PATTERN = /\r?\n/;
323
378
  const BASIC_AUTH_HEADER_PATTERN = /^basic-auth\s*:/i;
324
379
  function extensionForPath(filePath) {
@@ -333,6 +388,13 @@ function freePlanDeniedExecutablePath(filePath) {
333
388
  .split("/")
334
389
  .some((segment) => segment === "bin");
335
390
  }
391
+ // Acceptance is deliberately identical for claimed-free and unclaimed
392
+ // anonymous spaces (accept-and-scan posture): rejecting file types at upload
393
+ // adds no safety — the bytes reach us before any scan either way — so
394
+ // publishes are accepted, every file goes through the scan pipeline, and the
395
+ // SERVE-time content-type allowlist (ANONYMOUS_SERVE_CONTENT_TYPES via the
396
+ // route-config `content_types` policy) is what keeps opaque binaries dark
397
+ // until the space is claimed.
336
398
  export function freePlanCanUploadPath(filePath, options) {
337
399
  const path = normalizePublishPath(filePath);
338
400
  // Root Spacefast config files are platform machinery, not user content (spec "Space
@@ -363,6 +425,8 @@ export function freePlanCanUploadContentType(contentType) {
363
425
  normalized === "application/javascript" ||
364
426
  normalized === "application/json" ||
365
427
  normalized === "application/manifest+json" ||
428
+ // Gzip stays allowed for anonymous spaces too: pagefind search indexes
429
+ // ship as gzip fragments, and ClamAV decompresses gzip when scanning.
366
430
  normalized === "application/gzip" ||
367
431
  normalized === "image/svg+xml" ||
368
432
  normalized === "application/xml" ||
@@ -4,8 +4,9 @@ export declare const qk: {
4
4
  accountSessions: () => readonly ["account", "sessions"];
5
5
  accountConnections: () => readonly ["account", "connections"];
6
6
  accountInvitations: () => readonly ["account", "invitations"];
7
- apiTokens: () => readonly ["api-tokens"];
7
+ apiTokens: (teamId?: string) => readonly ["api-tokens"] | readonly ["api-tokens", string];
8
8
  agentAccounts: () => readonly ["agent-accounts"];
9
+ myShelf: () => readonly ["my-shelf"];
9
10
  mcpDashboard: () => readonly ["mcp", "dashboard"];
10
11
  mcpApproval: (id: string) => readonly ["mcp", "approval", string];
11
12
  teams: () => readonly ["teams", "list"];
@@ -21,20 +22,25 @@ export declare const qk: {
21
22
  teamDomains: (id: string) => readonly ["teams", string, "domains"];
22
23
  teamManagedDomains: (id: string) => readonly ["teams", string, "managed-domains"];
23
24
  domainBindings: () => readonly ["domain-bindings"];
24
- slugAvailability: (kind: "team" | "space", slug: string, teamId?: string, currentSlug?: string) => readonly ["slugs", "availability", "team" | "space", string, string, string];
25
+ slugAvailability: (kind: "team" | "space", slug: string, teamId?: string, currentSlug?: string) => readonly ["slugs", "availability", "space" | "team", string, string, string];
25
26
  teamVariables: (id: string) => readonly ["teams", string, "variables"];
26
27
  teamActivity: (id: string, limit: number) => readonly ["teams", string, "activity", number];
27
28
  space: (id: string) => readonly ["spaces", string];
28
29
  spaceByRef: (teamRef: string, spaceRef: string) => readonly ["spaces", "by-ref", string, string];
29
30
  spaceVersions: (id: string) => readonly ["spaces", string, "versions"];
30
31
  spaceCollaboration: (id: string) => readonly ["spaces", string, "collaboration"];
32
+ spaceCollaborationEmbedConfig: (id: string) => readonly ["spaces", string, "collaboration", "embed-config"];
31
33
  spaceAnnotations: (id: string) => readonly ["spaces", string, "annotations"];
32
34
  spaceActivity: (id: string, limit: number) => readonly ["spaces", string, "activity", number];
33
35
  spaceDeployments: (id: string) => readonly ["spaces", string, "deployments"];
34
36
  spaceTags: (id: string) => readonly ["spaces", string, "tags"];
35
- spaceTagVersion: (id: string, versionId: string) => readonly ["spaces", string, "tags", "versions", string];
37
+ spaceTagVersion: (id: string, versionId: string) => readonly ["spaces", string, "tags", string];
38
+ spaceTagDiff: (id: string, versionId: string) => readonly ["spaces", string, "tags", string, "diff"];
39
+ spaceTagReleases: (id: string) => readonly ["spaces", string, "tags", "releases"];
40
+ spaceTagsEffective: (id: string) => readonly ["spaces", string, "tags", "effective"];
36
41
  build: (id: string) => readonly ["builds", string];
37
- buildLogs: (id: string) => readonly ["builds", string, "logs"];
42
+ buildLogs: (id: string, limit?: number) => readonly ["builds", string, "logs"] | readonly ["builds", string, "logs", number];
43
+ spaceBuilds: (id: string) => readonly ["spaces", string, "builds"];
38
44
  spaceRepositoryConnection: (id: string, connectionType?: string) => readonly ["spaces", string, "repository-connection", string];
39
45
  repositoryInstallations: (spaceId: string) => readonly ["repository-connections", "installations", string];
40
46
  repositoryInstallationRepositories: (installationId: string) => readonly ["repository-connections", "installations", string, "repositories"];
@@ -44,7 +50,7 @@ export declare const qk: {
44
50
  webhook: (webhookId: string) => readonly ["webhooks", string];
45
51
  webhookDeliveries: (webhookId: string) => readonly ["webhooks", string, "deliveries"];
46
52
  spaceVersion: (id: string, versionId: string) => readonly ["spaces", string, "versions", string];
47
- spaceVersionAnnotations: (id: string, versionId: string, status?: string) => readonly ["spaces", string, "versions", string, "annotations", string];
53
+ spaceVersionAnnotations: (id: string, versionId: string, status?: string) => readonly ["spaces", string, "versions", string, "annotations"] | readonly ["spaces", string, "versions", string, "annotations", string];
48
54
  spaceVersionFiles: (id: string, versionId: string) => readonly ["spaces", string, "versions", string, "files"];
49
55
  spaceVersionFile: (id: string, versionId: string, path: string) => readonly ["spaces", string, "versions", string, "files", string];
50
56
  spaceVersionFileDirectUrl: (id: string, versionId: string, path: string) => readonly ["spaces", string, "versions", string, "files", string, "direct-url"];
@@ -63,8 +69,13 @@ export declare const qk: {
63
69
  spaceZeroRealtimeEvents: (id: string, limit: number) => readonly ["spaces", string, "zero", "realtime", "events", number];
64
70
  spaceVariables: (id: string) => readonly ["spaces", string, "variables"];
65
71
  spaceCertificates: (id: string) => readonly ["spaces", string, "runtime", "certificates"];
66
- spaceLogs: (id: string, kind: "build" | "access" | "runtime") => readonly ["spaces", string, "logs", "build" | "access" | "runtime"];
72
+ spaceLogs: (id: string, kind: "build" | "access" | "runtime") => readonly ["spaces", string, "logs", "build" | "runtime" | "access"];
67
73
  spaceAnalytics: (id: string, window: string) => readonly ["spaces", string, "analytics", string];
74
+ spaceFirewall: (id: string) => readonly ["spaces", string, "access", "firewall"];
75
+ spaceAccessViews: (id: string, since?: string) => readonly ["spaces", string, "access", "views", string];
76
+ teamServiceTokens: (teamId: string) => readonly ["teams", string, "access", "service-tokens"];
77
+ teamAccessDefaults: (teamId: string) => readonly ["teams", string, "access", "defaults"];
78
+ teamAccessConnections: (teamId: string) => readonly ["teams", string, "access", "connections"];
68
79
  domain: (teamId: string, ref: string) => readonly ["teams", string, "domains", string];
69
80
  domainDiagnostics: (teamId: string, domainId: string) => readonly ["teams", string, "domains", string, "diagnostics"];
70
81
  domainDnsCapabilities: (domainId: string) => readonly ["domains", string, "dns", "capabilities"];