@spacefast/common 0.0.12 → 0.0.13
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/dist/contracts/access.d.ts +386 -1201
- package/dist/contracts/access.js +240 -1057
- package/dist/contracts/activity.d.ts +1 -1
- package/dist/contracts/activity.js +6 -14
- package/dist/contracts/api-keys.d.ts +16 -0
- package/dist/contracts/api-keys.js +2 -0
- package/dist/contracts/archives.d.ts +1 -2
- package/dist/contracts/archives.js +4 -6
- package/dist/contracts/comments.d.ts +58 -85
- package/dist/contracts/comments.js +86 -67
- package/dist/contracts/countries.js +2 -7
- package/dist/contracts/data-source-routes.d.ts +20 -0
- package/dist/contracts/data-source-routes.js +15 -0
- package/dist/contracts/device-auth.d.ts +7 -3
- package/dist/contracts/enums.d.ts +2 -4
- package/dist/contracts/enums.js +3 -8
- package/dist/contracts/error-code-meta.d.ts +17 -20
- package/dist/contracts/error-code-meta.js +8 -5
- package/dist/contracts/error-codes.d.ts +1 -1
- package/dist/contracts/error-codes.js +4 -5
- package/dist/contracts/events.d.ts +13 -13
- package/dist/contracts/execution.d.ts +212 -0
- package/dist/contracts/execution.js +288 -0
- package/dist/contracts/feature-lifecycle.d.ts +21 -167
- package/dist/contracts/feature-lifecycle.js +27 -173
- package/dist/contracts/features.d.ts +13 -115
- package/dist/contracts/features.js +1 -31
- package/dist/contracts/functions.d.ts +729 -0
- package/dist/contracts/functions.js +485 -0
- package/dist/contracts/git.d.ts +1 -1
- package/dist/contracts/grants.d.ts +1158 -0
- package/dist/contracts/grants.js +696 -0
- package/dist/contracts/ids.d.ts +5 -3
- package/dist/contracts/ids.js +5 -3
- package/dist/contracts/mcp.d.ts +6 -6
- package/dist/contracts/oauth-resources.d.ts +5 -5
- package/dist/contracts/oauth-resources.js +1 -0
- package/dist/contracts/operations.d.ts +2 -2
- package/dist/contracts/pages.d.ts +6 -14
- package/dist/contracts/pages.js +6 -23
- package/dist/contracts/plan-policy.d.ts +1 -8
- package/dist/contracts/plan-policy.js +1 -4
- package/dist/contracts/platform.d.ts +78 -30
- package/dist/contracts/platform.js +59 -12
- package/dist/contracts/push-new.d.ts +26 -2
- package/dist/contracts/push-new.js +8 -0
- package/dist/contracts/quotas.d.ts +0 -1
- package/dist/contracts/quotas.js +0 -5
- package/dist/contracts/repository-connections.d.ts +1 -1
- package/dist/contracts/resources.d.ts +10 -66
- package/dist/contracts/resources.js +4 -10
- package/dist/contracts/runtime-api.d.ts +558 -265
- package/dist/contracts/runtime-api.js +178 -91
- package/dist/contracts/runtime-app.d.ts +138 -0
- package/dist/contracts/runtime-app.js +21 -0
- package/dist/contracts/sf-config-v1.d.ts +5 -0
- package/dist/contracts/sites.d.ts +5 -0
- package/dist/contracts/sites.js +12 -1
- package/dist/contracts/space-config.d.ts +46 -116
- package/dist/contracts/space-config.js +320 -24
- package/dist/contracts/spaces.d.ts +253 -158
- package/dist/contracts/spaces.js +68 -54
- package/dist/contracts/superadmin-emails.d.ts +2 -2
- package/dist/contracts/superadmin-spaces.d.ts +15 -130
- package/dist/contracts/superadmin-spaces.js +7 -0
- package/dist/contracts/tags.d.ts +23 -23
- package/dist/contracts/teams.d.ts +9 -9
- package/dist/contracts/transfers.d.ts +0 -1
- package/dist/contracts/zero.d.ts +195 -273
- package/dist/contracts/zero.js +78 -206
- package/dist/dashboard-paths/index.d.ts +2 -2
- package/dist/dashboard-paths/index.js +1 -0
- package/dist/docs/agent-prose.js +10 -8
- package/dist/docs/agent-setup.d.ts +2 -2
- package/dist/docs/agent-setup.js +11 -9
- package/dist/docs/agent-solutions.js +1 -1
- package/dist/docs/catalog.d.ts +28 -28
- package/dist/docs/catalog.js +29 -29
- package/dist/docs/error-docs.js +23 -27
- package/dist/utils/asset-fingerprint.d.ts +11 -0
- package/dist/utils/asset-fingerprint.js +46 -0
- package/dist/utils/auth-redirect.d.ts +1 -3
- package/dist/utils/auth-redirect.js +1 -21
- package/dist/utils/build-settings.d.ts +2 -0
- package/dist/utils/build-settings.js +61 -9
- package/dist/utils/credential-policy.d.ts +1 -1
- package/dist/utils/credential-policy.js +4 -0
- package/dist/utils/egress-policy.fixtures.json +1 -1
- package/dist/utils/grants.d.ts +53 -0
- package/dist/utils/grants.js +160 -0
- package/dist/utils/local-space-state.js +1 -0
- package/dist/utils/pages.d.ts +11 -5
- package/dist/utils/pages.js +19 -32
- package/dist/utils/publish-policy.d.ts +34 -4
- package/dist/utils/publish-policy.js +132 -61
- package/dist/utils/query-keys.d.ts +1 -6
- package/dist/utils/query-keys.js +1 -6
- package/dist/utils/space-config.d.ts +5 -1
- package/dist/utils/space-config.js +35 -0
- package/dist/utils/static-runtime-policy.d.ts +5 -0
- package/dist/utils/static-runtime-policy.js +54 -10
- package/dist/utils/upload-session.d.ts +3 -0
- package/dist/utils/upload-session.js +31 -0
- package/package.json +2 -1
- package/dist/utils/access-grant.d.ts +0 -14
- package/dist/utils/access-grant.js +0 -88
- package/dist/utils/access-match.d.ts +0 -46
- package/dist/utils/access-match.fixtures.json +0 -513
- package/dist/utils/access-match.js +0 -312
- package/dist/utils/access-verdict.fixtures.json +0 -421
- package/dist/utils/sf-config-v1.d.ts +0 -2
- package/dist/utils/sf-config-v1.fixtures.json +0 -9
- package/dist/utils/sf-config-v1.js +0 -551
|
@@ -17,8 +17,8 @@ export declare const SPACE_CONFIG_CAPS: {
|
|
|
17
17
|
readonly injectSnippetBytes: number;
|
|
18
18
|
/** Max snippets per `inject` array. */
|
|
19
19
|
readonly injectSnippets: 16;
|
|
20
|
-
/** Max
|
|
21
|
-
readonly
|
|
20
|
+
/** Max public WordPress sources exposed to repository builds. */
|
|
21
|
+
readonly dataSources: 32;
|
|
22
22
|
/** Max `theme` color token length. */
|
|
23
23
|
readonly themeColorChars: 64;
|
|
24
24
|
/** Max `theme.logo` URL/path length. */
|
|
@@ -63,6 +63,17 @@ export declare const spacePlacementConfigSchema: z.ZodObject<{
|
|
|
63
63
|
burstable: z.ZodOptional<z.ZodBoolean>;
|
|
64
64
|
}, z.core.$strict>;
|
|
65
65
|
export type SpacePlacementConfig = z.infer<typeof spacePlacementConfigSchema>;
|
|
66
|
+
export declare const dataSourceNameSchema: z.ZodString;
|
|
67
|
+
export declare const wordpressDataSourceUrlSchema: z.ZodString;
|
|
68
|
+
export declare const wordpressDataSourceSchema: z.ZodObject<{
|
|
69
|
+
kind: z.ZodLiteral<"wordpress">;
|
|
70
|
+
url: z.ZodString;
|
|
71
|
+
}, z.core.$strict>;
|
|
72
|
+
export type WordPressDataSource = z.infer<typeof wordpressDataSourceSchema>;
|
|
73
|
+
export declare const spaceDataSourcesSchema: z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
74
|
+
kind: z.ZodLiteral<"wordpress">;
|
|
75
|
+
url: z.ZodString;
|
|
76
|
+
}, z.core.$strict>>>;
|
|
66
77
|
export declare const spaceConfigSchema: z.ZodObject<{
|
|
67
78
|
index: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<false>]>>;
|
|
68
79
|
fallback: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
@@ -141,70 +152,38 @@ export declare const spaceConfigSchema: z.ZodObject<{
|
|
|
141
152
|
bodyEnd: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
142
153
|
noscript: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
143
154
|
}, z.core.$strip>>;
|
|
155
|
+
dataSources: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
156
|
+
kind: z.ZodLiteral<"wordpress">;
|
|
157
|
+
url: z.ZodString;
|
|
158
|
+
}, z.core.$strict>>>>;
|
|
159
|
+
defaultDataSource: z.ZodOptional<z.ZodString>;
|
|
144
160
|
}, z.core.$strip>;
|
|
145
161
|
export type SpaceConfig = z.infer<typeof spaceConfigSchema>;
|
|
162
|
+
export declare function compareDataSourceNames(left: string, right: string): number;
|
|
163
|
+
export declare function resolveDefaultDataSourceName(config: Pick<SpaceConfig, "dataSources" | "defaultDataSource"> | null | undefined): string | null;
|
|
146
164
|
export declare function spaceSuperpowersEnabled(config: SpaceConfig | null | undefined): boolean;
|
|
147
|
-
export declare const spaceAccessConfigSchema: z.ZodObject<{
|
|
148
|
-
rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
149
|
-
id: z.ZodOptional<z.ZodString>;
|
|
150
|
-
match: z.ZodDefault<z.ZodObject<{
|
|
151
|
-
host: z.ZodOptional<z.ZodString>;
|
|
152
|
-
hostPattern: z.ZodOptional<z.ZodString>;
|
|
153
|
-
hostTemplate: z.ZodOptional<z.ZodString>;
|
|
154
|
-
pathPattern: z.ZodOptional<z.ZodString>;
|
|
155
|
-
channel: z.ZodOptional<z.ZodString>;
|
|
156
|
-
ipCidrs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
157
|
-
agent: z.ZodOptional<z.ZodString>;
|
|
158
|
-
country: z.ZodOptional<z.ZodString>;
|
|
159
|
-
countryNotIn: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
160
|
-
header: z.ZodOptional<z.ZodObject<{
|
|
161
|
-
name: z.ZodString;
|
|
162
|
-
value: z.ZodString;
|
|
163
|
-
}, z.core.$strip>>;
|
|
164
|
-
}, z.core.$strip>>;
|
|
165
|
-
effect: z.ZodEnum<{
|
|
166
|
-
allow: "allow";
|
|
167
|
-
deny: "deny";
|
|
168
|
-
challenge: "challenge";
|
|
169
|
-
}>;
|
|
170
|
-
auth: z.ZodOptional<z.ZodObject<{
|
|
171
|
-
requiredGrants: z.ZodArray<z.ZodString>;
|
|
172
|
-
issuers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
173
|
-
kid: z.ZodString;
|
|
174
|
-
alg: z.ZodLiteral<"EdDSA">;
|
|
175
|
-
publicKey: z.ZodString;
|
|
176
|
-
grantNamespaces: z.ZodArray<z.ZodString>;
|
|
177
|
-
}, z.core.$strip>>>;
|
|
178
|
-
acquire: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
179
|
-
type: z.ZodLiteral<"password">;
|
|
180
|
-
ref: z.ZodString;
|
|
181
|
-
transport: z.ZodEnum<{
|
|
182
|
-
basic: "basic";
|
|
183
|
-
form: "form";
|
|
184
|
-
}>;
|
|
185
|
-
username: z.ZodOptional<z.ZodString>;
|
|
186
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
187
|
-
type: z.ZodLiteral<"login">;
|
|
188
|
-
url: z.ZodString;
|
|
189
|
-
label: z.ZodOptional<z.ZodString>;
|
|
190
|
-
}, z.core.$strip>], "type">>>;
|
|
191
|
-
}, z.core.$strip>>;
|
|
192
|
-
managedBy: z.ZodOptional<z.ZodEnum<{
|
|
193
|
-
sharing: "sharing";
|
|
194
|
-
firewall: "firewall";
|
|
195
|
-
file_share: "file_share";
|
|
196
|
-
cast_reviewer: "cast_reviewer";
|
|
197
|
-
team_default: "team_default";
|
|
198
|
-
}>>;
|
|
199
|
-
expiresAt: z.ZodOptional<z.ZodNumber>;
|
|
200
|
-
reasonCode: z.ZodOptional<z.ZodString>;
|
|
201
|
-
message: z.ZodOptional<z.ZodString>;
|
|
202
|
-
}, z.core.$strip>>>;
|
|
203
|
-
}, z.core.$strip>;
|
|
204
165
|
export declare const spacePublishOptionsSchema: z.ZodObject<{
|
|
205
166
|
fingerprint: z.ZodOptional<z.ZodBoolean>;
|
|
206
167
|
}, z.core.$strip>;
|
|
207
168
|
export type SpacePublishOptions = z.infer<typeof spacePublishOptionsSchema>;
|
|
169
|
+
export declare const spaceConfigFileAccessSchema: z.ZodObject<{
|
|
170
|
+
public: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
171
|
+
}, z.core.$strict>;
|
|
172
|
+
export type SpaceConfigFileAccess = z.infer<typeof spaceConfigFileAccessSchema>;
|
|
173
|
+
/**
|
|
174
|
+
* Overrides for the worker `sf publish` compiles.
|
|
175
|
+
*
|
|
176
|
+
* Every field is optional because the normal case is detection: a project with
|
|
177
|
+
* `handler.ts` or an OpenNext build needs no configuration at all. These exist
|
|
178
|
+
* for the layouts detection cannot guess — a worker somewhere unusual, or a
|
|
179
|
+
* compatibility date the author wants pinned rather than defaulted.
|
|
180
|
+
*/
|
|
181
|
+
export declare const spaceFunctionsOptionsSchema: z.ZodObject<{
|
|
182
|
+
entry: z.ZodOptional<z.ZodString>;
|
|
183
|
+
database: z.ZodDefault<z.ZodBoolean>;
|
|
184
|
+
compatibilityDate: z.ZodOptional<z.ZodString>;
|
|
185
|
+
}, z.core.$strip>;
|
|
186
|
+
export type SpaceFunctionsOptions = z.infer<typeof spaceFunctionsOptionsSchema>;
|
|
208
187
|
export declare const spaceConfigFileSchema: z.ZodObject<{
|
|
209
188
|
index: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<false>]>>;
|
|
210
189
|
fallback: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
@@ -285,66 +264,17 @@ export declare const spaceConfigFileSchema: z.ZodObject<{
|
|
|
285
264
|
}, z.core.$strip>>;
|
|
286
265
|
$schema: z.ZodOptional<z.ZodString>;
|
|
287
266
|
space: z.ZodOptional<z.ZodString>;
|
|
288
|
-
access: z.ZodOptional<z.ZodObject<{
|
|
289
|
-
rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
290
|
-
id: z.ZodOptional<z.ZodString>;
|
|
291
|
-
match: z.ZodDefault<z.ZodObject<{
|
|
292
|
-
host: z.ZodOptional<z.ZodString>;
|
|
293
|
-
hostPattern: z.ZodOptional<z.ZodString>;
|
|
294
|
-
hostTemplate: z.ZodOptional<z.ZodString>;
|
|
295
|
-
pathPattern: z.ZodOptional<z.ZodString>;
|
|
296
|
-
channel: z.ZodOptional<z.ZodString>;
|
|
297
|
-
ipCidrs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
298
|
-
agent: z.ZodOptional<z.ZodString>;
|
|
299
|
-
country: z.ZodOptional<z.ZodString>;
|
|
300
|
-
countryNotIn: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
301
|
-
header: z.ZodOptional<z.ZodObject<{
|
|
302
|
-
name: z.ZodString;
|
|
303
|
-
value: z.ZodString;
|
|
304
|
-
}, z.core.$strip>>;
|
|
305
|
-
}, z.core.$strip>>;
|
|
306
|
-
effect: z.ZodEnum<{
|
|
307
|
-
allow: "allow";
|
|
308
|
-
deny: "deny";
|
|
309
|
-
challenge: "challenge";
|
|
310
|
-
}>;
|
|
311
|
-
auth: z.ZodOptional<z.ZodObject<{
|
|
312
|
-
requiredGrants: z.ZodArray<z.ZodString>;
|
|
313
|
-
issuers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
314
|
-
kid: z.ZodString;
|
|
315
|
-
alg: z.ZodLiteral<"EdDSA">;
|
|
316
|
-
publicKey: z.ZodString;
|
|
317
|
-
grantNamespaces: z.ZodArray<z.ZodString>;
|
|
318
|
-
}, z.core.$strip>>>;
|
|
319
|
-
acquire: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
320
|
-
type: z.ZodLiteral<"password">;
|
|
321
|
-
ref: z.ZodString;
|
|
322
|
-
transport: z.ZodEnum<{
|
|
323
|
-
basic: "basic";
|
|
324
|
-
form: "form";
|
|
325
|
-
}>;
|
|
326
|
-
username: z.ZodOptional<z.ZodString>;
|
|
327
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
328
|
-
type: z.ZodLiteral<"login">;
|
|
329
|
-
url: z.ZodString;
|
|
330
|
-
label: z.ZodOptional<z.ZodString>;
|
|
331
|
-
}, z.core.$strip>], "type">>>;
|
|
332
|
-
}, z.core.$strip>>;
|
|
333
|
-
managedBy: z.ZodOptional<z.ZodEnum<{
|
|
334
|
-
sharing: "sharing";
|
|
335
|
-
firewall: "firewall";
|
|
336
|
-
file_share: "file_share";
|
|
337
|
-
cast_reviewer: "cast_reviewer";
|
|
338
|
-
team_default: "team_default";
|
|
339
|
-
}>>;
|
|
340
|
-
expiresAt: z.ZodOptional<z.ZodNumber>;
|
|
341
|
-
reasonCode: z.ZodOptional<z.ZodString>;
|
|
342
|
-
message: z.ZodOptional<z.ZodString>;
|
|
343
|
-
}, z.core.$strip>>>;
|
|
344
|
-
}, z.core.$strip>>;
|
|
345
267
|
publish: z.ZodOptional<z.ZodObject<{
|
|
346
268
|
fingerprint: z.ZodOptional<z.ZodBoolean>;
|
|
347
269
|
}, z.core.$strip>>;
|
|
270
|
+
access: z.ZodOptional<z.ZodObject<{
|
|
271
|
+
public: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
272
|
+
}, z.core.$strict>>;
|
|
273
|
+
functions: z.ZodOptional<z.ZodObject<{
|
|
274
|
+
entry: z.ZodOptional<z.ZodString>;
|
|
275
|
+
database: z.ZodDefault<z.ZodBoolean>;
|
|
276
|
+
compatibilityDate: z.ZodOptional<z.ZodString>;
|
|
277
|
+
}, z.core.$strip>>;
|
|
348
278
|
}, z.core.$strip>;
|
|
349
279
|
export type SpaceConfigFile = z.infer<typeof spaceConfigFileSchema>;
|
|
350
280
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { BRAND } from "../brand.js";
|
|
3
|
-
import { runtimeRuleSchema } from "./access.js";
|
|
4
3
|
import { normalizedSiteClass } from "./enums.js";
|
|
4
|
+
import { GRANT_RESOURCE_PATTERN_LIMIT, normalizeGrantResourcePattern } from "./grants.js";
|
|
5
5
|
export const SPACE_CONFIG_CANONICAL_FILE = "sf.jsonc";
|
|
6
6
|
export const SPACE_CONFIG_ALIAS_FILES = [
|
|
7
7
|
"spacefast.jsonc",
|
|
@@ -35,8 +35,8 @@ export const SPACE_CONFIG_CAPS = {
|
|
|
35
35
|
injectSnippetBytes: 8 * 1024,
|
|
36
36
|
/** Max snippets per `inject` array. */
|
|
37
37
|
injectSnippets: 16,
|
|
38
|
-
/** Max
|
|
39
|
-
|
|
38
|
+
/** Max public WordPress sources exposed to repository builds. */
|
|
39
|
+
dataSources: 32,
|
|
40
40
|
/** Max `theme` color token length. */
|
|
41
41
|
themeColorChars: 64,
|
|
42
42
|
/** Max `theme.logo` URL/path length. */
|
|
@@ -138,11 +138,220 @@ export const spacePlacementConfigSchema = z
|
|
|
138
138
|
})
|
|
139
139
|
.strict()
|
|
140
140
|
.describe("Placement controls for this space.");
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
const RESERVED_DATA_SOURCE_NAMES = new Set(["__proto__", "constructor", "prototype"]);
|
|
142
|
+
export const dataSourceNameSchema = z
|
|
143
|
+
.string()
|
|
144
|
+
.min(1)
|
|
145
|
+
.max(128)
|
|
146
|
+
.regex(/^[A-Za-z_][A-Za-z0-9_]*$/, {
|
|
147
|
+
message: "Use a non-reserved name that starts with a letter or underscore and contains only letters, numbers, and underscores.",
|
|
148
|
+
})
|
|
149
|
+
.refine((name) => !RESERVED_DATA_SOURCE_NAMES.has(name), {
|
|
150
|
+
message: "Use a non-reserved name that starts with a letter or underscore and contains only letters, numbers, and underscores.",
|
|
151
|
+
})
|
|
152
|
+
.describe("Stable source identifier: 1-128 letters, numbers, or underscores; starts with a letter or underscore; excludes __proto__, constructor, and prototype.")
|
|
153
|
+
.meta({ not: { enum: [...RESERVED_DATA_SOURCE_NAMES] } });
|
|
154
|
+
const WORDPRESS_DATA_SOURCE_URL_MESSAGE = "WordPress data source URLs must use lowercase https:// with a valid DNS name, canonical dotted-decimal IPv4 address, or bracketed IPv6 address. Whitespace, controls, malformed percent encoding, encoded percent signs, slashes or backslashes, credentials, queries, fragments, trailing-dot hosts, and port 0 are not allowed.";
|
|
155
|
+
const WORDPRESS_DATA_SOURCE_URL_PATTERN = /^https:\/\/[^/?#@%\\\s]+(?:\/[^?#\\\s]*)?$/u;
|
|
156
|
+
const WORDPRESS_DATA_SOURCE_WHITESPACE_PATTERN = /\p{White_Space}/u;
|
|
157
|
+
const DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
158
|
+
const IPV6_LITERAL_PATTERN = /^[0-9a-f:]+$/i;
|
|
159
|
+
const DECIMAL_PORT_PATTERN = /^\d{1,5}$/;
|
|
160
|
+
const IPV4_OCTET_PATTERN = /^(?:0|[1-9]\d{0,2})$/;
|
|
161
|
+
function hasAsciiControlCharacter(value) {
|
|
162
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
163
|
+
const code = value.charCodeAt(index);
|
|
164
|
+
if (code <= 0x1f || (code >= 0x7f && code <= 0x9f))
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
function isCanonicalIpv4Address(value) {
|
|
170
|
+
const octets = value.split(".");
|
|
171
|
+
return (octets.length === 4 &&
|
|
172
|
+
octets.every((octet) => IPV4_OCTET_PATTERN.test(octet) && Number.parseInt(octet, 10) <= 255));
|
|
173
|
+
}
|
|
174
|
+
function isValidDnsHostname(value) {
|
|
175
|
+
if (value.length === 0 || value.length > 253 || value.endsWith("."))
|
|
176
|
+
return false;
|
|
177
|
+
const labels = value.split(".");
|
|
178
|
+
return labels.every((label) => label.length > 0 && label.length <= 63 && DNS_LABEL_PATTERN.test(label));
|
|
179
|
+
}
|
|
180
|
+
function parseWordPressAuthority(authority) {
|
|
181
|
+
if (!authority || authority.includes("@") || authority.includes("%"))
|
|
182
|
+
return null;
|
|
183
|
+
let rawHostname;
|
|
184
|
+
let rawPort = null;
|
|
185
|
+
let ipv6 = false;
|
|
186
|
+
if (authority.startsWith("[")) {
|
|
187
|
+
const closingBracket = authority.indexOf("]");
|
|
188
|
+
if (closingBracket <= 1 || authority.indexOf("]", closingBracket + 1) !== -1)
|
|
189
|
+
return null;
|
|
190
|
+
rawHostname = authority.slice(1, closingBracket);
|
|
191
|
+
const suffix = authority.slice(closingBracket + 1);
|
|
192
|
+
if (suffix) {
|
|
193
|
+
if (!suffix.startsWith(":"))
|
|
194
|
+
return null;
|
|
195
|
+
rawPort = suffix.slice(1);
|
|
196
|
+
}
|
|
197
|
+
ipv6 = true;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
if (authority.includes("[") || authority.includes("]"))
|
|
201
|
+
return null;
|
|
202
|
+
const firstColon = authority.indexOf(":");
|
|
203
|
+
if (firstColon !== authority.lastIndexOf(":"))
|
|
204
|
+
return null;
|
|
205
|
+
if (firstColon === -1) {
|
|
206
|
+
rawHostname = authority;
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
rawHostname = authority.slice(0, firstColon);
|
|
210
|
+
rawPort = authority.slice(firstColon + 1);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (!rawHostname || rawHostname.endsWith("."))
|
|
214
|
+
return null;
|
|
215
|
+
if (rawPort !== null) {
|
|
216
|
+
if (!DECIMAL_PORT_PATTERN.test(rawPort))
|
|
217
|
+
return null;
|
|
218
|
+
const port = Number.parseInt(rawPort, 10);
|
|
219
|
+
if (port === 0 || port > 65_535)
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
const url = new URL(`https://${authority}/`);
|
|
224
|
+
if (ipv6) {
|
|
225
|
+
if (!IPV6_LITERAL_PATTERN.test(rawHostname))
|
|
226
|
+
return null;
|
|
227
|
+
if (!url.hostname.startsWith("[") || !url.hostname.endsWith("]"))
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
const normalizedHostname = url.hostname;
|
|
232
|
+
if (normalizedHostname.startsWith("[") || normalizedHostname.endsWith("]"))
|
|
233
|
+
return null;
|
|
234
|
+
if (isCanonicalIpv4Address(normalizedHostname)) {
|
|
235
|
+
if (rawHostname !== normalizedHostname || !isCanonicalIpv4Address(rawHostname))
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
else if (!isValidDnsHostname(normalizedHostname)) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return url.origin;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function canonicalizeWordPressPath(rawPath) {
|
|
249
|
+
if (!rawPath)
|
|
250
|
+
return "";
|
|
251
|
+
// Reject encoded percents so validation has one authoritative decode layer.
|
|
252
|
+
// Encoded path separators are never valid in a WordPress source base path.
|
|
253
|
+
if (/%(?:25|2f|5c)/i.test(rawPath))
|
|
254
|
+
return null;
|
|
255
|
+
let decodedPath = rawPath;
|
|
256
|
+
if (rawPath.includes("%")) {
|
|
257
|
+
const encodedSegments = rawPath.split("/");
|
|
258
|
+
let nextPath;
|
|
259
|
+
try {
|
|
260
|
+
nextPath = decodeURIComponent(rawPath);
|
|
261
|
+
void encodeURIComponent(nextPath);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
const decodedSegments = nextPath.split("/");
|
|
267
|
+
if (decodedSegments.some((segment, index) => (segment === "." || segment === "..") && encodedSegments[index] !== segment)) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
if (hasAsciiControlCharacter(nextPath) ||
|
|
271
|
+
nextPath.includes("\\") ||
|
|
272
|
+
WORDPRESS_DATA_SOURCE_WHITESPACE_PATTERN.test(nextPath.replaceAll(" ", ""))) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
decodedPath = nextPath;
|
|
276
|
+
}
|
|
277
|
+
const url = new URL("https://path.invalid/");
|
|
278
|
+
url.pathname = decodedPath;
|
|
279
|
+
return url.pathname === "/" ? "" : url.pathname.replace(/\/+$/, "");
|
|
280
|
+
}
|
|
281
|
+
function canonicalWordPressDataSourceUrl(value) {
|
|
282
|
+
if (!value.startsWith("https://") ||
|
|
283
|
+
hasAsciiControlCharacter(value) ||
|
|
284
|
+
WORDPRESS_DATA_SOURCE_WHITESPACE_PATTERN.test(value) ||
|
|
285
|
+
value.includes("\\") ||
|
|
286
|
+
value.includes("?") ||
|
|
287
|
+
value.includes("#")) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
const remainder = value.slice("https://".length);
|
|
291
|
+
const pathIndex = remainder.indexOf("/");
|
|
292
|
+
const authority = pathIndex === -1 ? remainder : remainder.slice(0, pathIndex);
|
|
293
|
+
const rawPath = pathIndex === -1 ? "" : remainder.slice(pathIndex);
|
|
294
|
+
const origin = parseWordPressAuthority(authority);
|
|
295
|
+
const pathname = canonicalizeWordPressPath(rawPath);
|
|
296
|
+
return origin && pathname !== null ? `${origin}${pathname}` : null;
|
|
297
|
+
}
|
|
298
|
+
export const wordpressDataSourceUrlSchema = z
|
|
299
|
+
.string()
|
|
300
|
+
.min(1)
|
|
301
|
+
.max(2048)
|
|
302
|
+
.refine((value) => !hasAsciiControlCharacter(value), {
|
|
303
|
+
message: WORDPRESS_DATA_SOURCE_URL_MESSAGE,
|
|
304
|
+
})
|
|
305
|
+
.regex(WORDPRESS_DATA_SOURCE_URL_PATTERN, { message: WORDPRESS_DATA_SOURCE_URL_MESSAGE })
|
|
306
|
+
.url({ message: WORDPRESS_DATA_SOURCE_URL_MESSAGE })
|
|
307
|
+
.refine((value) => canonicalWordPressDataSourceUrl(value) !== null, {
|
|
308
|
+
message: WORDPRESS_DATA_SOURCE_URL_MESSAGE,
|
|
309
|
+
})
|
|
310
|
+
.overwrite((value) => canonicalWordPressDataSourceUrl(value) ?? value)
|
|
311
|
+
.max(2048)
|
|
312
|
+
.describe("Canonical public WordPress URL: lowercase https:// scheme; IDNA-normalized DNS labels (1-63 characters each, 253 total, alphanumeric edges, alphanumeric or hyphen interior), canonical dotted-decimal IPv4, or bracketed hexadecimal IPv6; optional nonzero port and UTF-8 path. Percent escapes are validated and canonicalized; encoded percent signs, slashes, backslashes, controls, invalid UTF-8, whitespace other than encoded ASCII path spaces, credentials, queries, fragments, and trailing-dot hosts are rejected. Default port, host case, IDNs, dot segments, path encoding, and trailing path slashes are normalized before storage.");
|
|
313
|
+
export const wordpressDataSourceSchema = z
|
|
314
|
+
.object({
|
|
315
|
+
kind: z.literal("wordpress"),
|
|
316
|
+
url: wordpressDataSourceUrlSchema,
|
|
317
|
+
})
|
|
318
|
+
.strict()
|
|
319
|
+
.describe("A public WordPress site exposed to repository builds as a content source.");
|
|
320
|
+
function rejectReservedDataSourceRecordKeys(value) {
|
|
321
|
+
if (typeof value === "object" &&
|
|
322
|
+
value !== null &&
|
|
323
|
+
!Array.isArray(value) &&
|
|
324
|
+
Object.keys(value).some((name) => RESERVED_DATA_SOURCE_NAMES.has(name))) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
return value;
|
|
328
|
+
}
|
|
329
|
+
export const spaceDataSourcesSchema = z
|
|
330
|
+
.preprocess(rejectReservedDataSourceRecordKeys, z
|
|
331
|
+
.record(dataSourceNameSchema, wordpressDataSourceSchema)
|
|
332
|
+
.refine((sources) => Object.keys(sources).length <= SPACE_CONFIG_CAPS.dataSources, {
|
|
333
|
+
message: `dataSources supports up to ${SPACE_CONFIG_CAPS.dataSources} entries.`,
|
|
334
|
+
}))
|
|
335
|
+
.meta({
|
|
336
|
+
maxProperties: SPACE_CONFIG_CAPS.dataSources,
|
|
337
|
+
description: `Named public WordPress sources. Supports at most ${SPACE_CONFIG_CAPS.dataSources} entries.`,
|
|
338
|
+
});
|
|
339
|
+
function refineDefaultDataSource(config, ctx) {
|
|
340
|
+
if (config.defaultDataSource !== undefined &&
|
|
341
|
+
!Object.prototype.hasOwnProperty.call(config.dataSources ?? {}, config.defaultDataSource)) {
|
|
342
|
+
ctx.addIssue({
|
|
343
|
+
code: "custom",
|
|
344
|
+
message: "defaultDataSource must name a configured dataSources entry.",
|
|
345
|
+
path: ["defaultDataSource"],
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Shared SpaceConfig fields (internal-docs/platform.md "SpaceConfig"): the
|
|
350
|
+
// declarative serving config also accepted by `sf.jsonc`. Cloud-only overlay
|
|
351
|
+
// fields such as pre-build data sources are added to `spaceConfigSchema` below.
|
|
143
352
|
// Meta caps are the spec parser-safety numbers (title 300, description 1000,
|
|
144
353
|
// image 2000); plan policy may lower them but never raise them.
|
|
145
|
-
|
|
354
|
+
const spaceConfigBaseSchema = z.object({
|
|
146
355
|
index: z
|
|
147
356
|
.union([z.string().min(1), z.literal(false)])
|
|
148
357
|
.optional()
|
|
@@ -254,26 +463,31 @@ export const spaceConfigSchema = z.object({
|
|
|
254
463
|
})
|
|
255
464
|
.optional(),
|
|
256
465
|
});
|
|
466
|
+
export const spaceConfigSchema = spaceConfigBaseSchema
|
|
467
|
+
.extend({
|
|
468
|
+
dataSources: spaceDataSourcesSchema
|
|
469
|
+
.optional()
|
|
470
|
+
.describe(`Named public WordPress sources exposed to repository builds; at most ${SPACE_CONFIG_CAPS.dataSources} entries.`),
|
|
471
|
+
defaultDataSource: dataSourceNameSchema
|
|
472
|
+
.optional()
|
|
473
|
+
.describe("Name of the configured source exposed through WORDPRESS_URL. Must match a dataSources entry; when omitted, the first source in bytewise name order is used."),
|
|
474
|
+
})
|
|
475
|
+
.superRefine(refineDefaultDataSource);
|
|
476
|
+
export function compareDataSourceNames(left, right) {
|
|
477
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
478
|
+
}
|
|
479
|
+
export function resolveDefaultDataSourceName(config) {
|
|
480
|
+
const names = Object.keys(config?.dataSources ?? {}).toSorted(compareDataSourceNames);
|
|
481
|
+
const configuredDefault = config?.defaultDataSource;
|
|
482
|
+
return configuredDefault &&
|
|
483
|
+
Object.prototype.hasOwnProperty.call(config?.dataSources ?? {}, configuredDefault)
|
|
484
|
+
? configuredDefault
|
|
485
|
+
: (names[0] ?? null);
|
|
486
|
+
}
|
|
257
487
|
export function spaceSuperpowersEnabled(config) {
|
|
258
488
|
const superpowers = config?.superpowers;
|
|
259
489
|
return superpowers?.enabled !== false && superpowers?.disable_all_injection !== true;
|
|
260
490
|
}
|
|
261
|
-
// The declarative file-lane access/firewall block (internal-docs/
|
|
262
|
-
// internal-docs/access-plan.html, "file" provenance): the SAME unified Rule shape
|
|
263
|
-
// the policy document and the cloud lane use — no parallel contract. Rules
|
|
264
|
-
// committed here compile at publish finalize and version + roll back WITH the
|
|
265
|
-
// content version; live edits without a republish belong to the cloud lane
|
|
266
|
-
// (`sf access`). The file lane lives ONLY in the config FILE — the API `config`
|
|
267
|
-
// overlay never carries it (single-home, no drift).
|
|
268
|
-
export const spaceAccessConfigSchema = z
|
|
269
|
-
.object({
|
|
270
|
-
rules: z
|
|
271
|
-
.array(runtimeRuleSchema)
|
|
272
|
-
.max(SPACE_CONFIG_CAPS.accessRules)
|
|
273
|
-
.default([])
|
|
274
|
-
.describe("Ordered unified access/firewall rules; first match wins."),
|
|
275
|
-
})
|
|
276
|
-
.describe("Declarative access lane: rules compiled at publish that version and roll back with the site.");
|
|
277
491
|
// The `sf.jsonc` FILE shape (internal-docs/platform.md "Space
|
|
278
492
|
// Configuration Files"): SpaceConfig plus the editor `$schema` pointer and the
|
|
279
493
|
// optional `space` link (slug or `spc_` id) resolved by CLI/publish flows.
|
|
@@ -293,7 +507,88 @@ export const spacePublishOptionsSchema = z
|
|
|
293
507
|
"Originals keep serving unchanged. Default off."),
|
|
294
508
|
})
|
|
295
509
|
.describe("Client publish-time options consumed by the Spacefast CLI, not the runtime.");
|
|
296
|
-
|
|
510
|
+
const spaceConfigPublicPatternSchema = z
|
|
511
|
+
.string()
|
|
512
|
+
.min(1)
|
|
513
|
+
.max(1025)
|
|
514
|
+
.transform((candidate, context) => {
|
|
515
|
+
const excluded = candidate.startsWith("!");
|
|
516
|
+
try {
|
|
517
|
+
const normalized = normalizeGrantResourcePattern(excluded ? candidate.slice(1) : candidate);
|
|
518
|
+
return excluded ? `!${normalized}` : normalized;
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
context.addIssue({
|
|
522
|
+
code: "custom",
|
|
523
|
+
message: error instanceof Error ? error.message : "Invalid access.public resource pattern.",
|
|
524
|
+
});
|
|
525
|
+
return z.NEVER;
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
export const spaceConfigFileAccessSchema = z
|
|
529
|
+
.object({
|
|
530
|
+
public: z
|
|
531
|
+
.array(spaceConfigPublicPatternSchema)
|
|
532
|
+
.max(GRANT_RESOURCE_PATTERN_LIMIT)
|
|
533
|
+
.describe("Always-public live paths. Prefix exclusions with !; * and ** are whole-segment wildcards."),
|
|
534
|
+
})
|
|
535
|
+
.strict()
|
|
536
|
+
.superRefine((value, context) => {
|
|
537
|
+
const seen = new Set();
|
|
538
|
+
let inclusionCount = 0;
|
|
539
|
+
value.public.forEach((pattern, index) => {
|
|
540
|
+
if (seen.has(pattern)) {
|
|
541
|
+
context.addIssue({
|
|
542
|
+
code: "custom",
|
|
543
|
+
path: ["public", index],
|
|
544
|
+
message: "access.public patterns must be unique.",
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
seen.add(pattern);
|
|
548
|
+
if (!pattern.startsWith("!"))
|
|
549
|
+
inclusionCount += 1;
|
|
550
|
+
});
|
|
551
|
+
if (value.public.length > 0 && inclusionCount === 0) {
|
|
552
|
+
context.addIssue({
|
|
553
|
+
code: "custom",
|
|
554
|
+
path: ["public"],
|
|
555
|
+
message: "access.public requires at least one inclusion pattern.",
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
})
|
|
559
|
+
.describe("File-only public access declaration. It is inactive until the Space is claimed.");
|
|
560
|
+
/**
|
|
561
|
+
* Overrides for the worker `sf publish` compiles.
|
|
562
|
+
*
|
|
563
|
+
* Every field is optional because the normal case is detection: a project with
|
|
564
|
+
* `handler.ts` or an OpenNext build needs no configuration at all. These exist
|
|
565
|
+
* for the layouts detection cannot guess — a worker somewhere unusual, or a
|
|
566
|
+
* compatibility date the author wants pinned rather than defaulted.
|
|
567
|
+
*/
|
|
568
|
+
export const spaceFunctionsOptionsSchema = z
|
|
569
|
+
.object({
|
|
570
|
+
entry: z
|
|
571
|
+
.string()
|
|
572
|
+
.optional()
|
|
573
|
+
.describe("Path to the worker entry module, relative to the published directory. " +
|
|
574
|
+
"Detected automatically when absent; naming one that does not exist is an error " +
|
|
575
|
+
"rather than a silent static publish."),
|
|
576
|
+
database: z
|
|
577
|
+
.boolean()
|
|
578
|
+
.default(false)
|
|
579
|
+
.describe("Give the worker `env.DB`, a D1-shaped binding to this space's database. " +
|
|
580
|
+
"Declared rather than detected: a capability an author did not ask for is " +
|
|
581
|
+
"authority they cannot see, and reading it out of minified build output " +
|
|
582
|
+
"would be a guess. Off by default, so a worker reaches nothing until it says so."),
|
|
583
|
+
compatibilityDate: z
|
|
584
|
+
.string()
|
|
585
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
586
|
+
.optional()
|
|
587
|
+
.describe("Runtime compatibility date this worker was written against. Pinning it makes " +
|
|
588
|
+
"rollback honest: promoting an old version restores the semantics it was built with."),
|
|
589
|
+
})
|
|
590
|
+
.describe("Overrides for the worker compiled at publish time.");
|
|
591
|
+
export const spaceConfigFileSchema = spaceConfigBaseSchema.extend({
|
|
297
592
|
$schema: z
|
|
298
593
|
.string()
|
|
299
594
|
.optional()
|
|
@@ -303,8 +598,9 @@ export const spaceConfigFileSchema = spaceConfigSchema.extend({
|
|
|
303
598
|
.min(1)
|
|
304
599
|
.optional()
|
|
305
600
|
.describe("Optional space link: a space slug or spc_ id this project publishes to."),
|
|
306
|
-
access: spaceAccessConfigSchema.optional(),
|
|
307
601
|
publish: spacePublishOptionsSchema.optional(),
|
|
602
|
+
access: spaceConfigFileAccessSchema.optional(),
|
|
603
|
+
functions: spaceFunctionsOptionsSchema.optional(),
|
|
308
604
|
});
|
|
309
605
|
/**
|
|
310
606
|
* The published JSON Schema artifact for `sf.jsonc`, generated from the
|