@spacefast/common 0.0.23 → 0.0.24
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/config/domains.d.ts +1 -1
- package/dist/config/domains.js +1 -0
- 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 +121 -9
- package/dist/contracts/access.js +73 -9
- package/dist/contracts/activity.d.ts +1 -1
- package/dist/contracts/activity.js +0 -9
- package/dist/contracts/api-keys.d.ts +21 -21
- package/dist/contracts/beta.js +1 -1
- package/dist/contracts/billing.d.ts +1 -34
- package/dist/contracts/billing.js +4 -53
- package/dist/contracts/builds.d.ts +8 -8
- package/dist/contracts/builds.js +10 -3
- 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 +283 -54
- package/dist/contracts/comments.js +173 -52
- package/dist/contracts/common.d.ts +3 -3
- package/dist/contracts/device-auth.d.ts +6 -6
- package/dist/contracts/device-auth.js +1 -1
- package/dist/contracts/docs.d.ts +5 -5
- package/dist/contracts/domains.d.ts +36 -36
- package/dist/contracts/enums.d.ts +33 -30
- package/dist/contracts/enums.js +52 -38
- package/dist/contracts/error-code-meta.d.ts +37 -126
- package/dist/contracts/error-code-meta.js +12 -41
- package/dist/contracts/error-codes.d.ts +1 -1
- package/dist/contracts/error-codes.js +10 -32
- package/dist/contracts/execution.d.ts +35 -57
- package/dist/contracts/execution.js +41 -40
- 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 +8 -38
- package/dist/contracts/feature-lifecycle.js +16 -48
- package/dist/contracts/features.d.ts +15 -13
- package/dist/contracts/features.js +39 -38
- package/dist/contracts/functions.d.ts +97 -70
- package/dist/contracts/functions.js +101 -34
- package/dist/contracts/git.d.ts +1 -1
- package/dist/contracts/grants.d.ts +7 -3
- package/dist/contracts/grants.js +18 -9
- package/dist/contracts/internal.d.ts +14 -1
- package/dist/contracts/internal.js +17 -7
- 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 +75 -1
- package/dist/contracts/pages.d.ts +6 -1
- package/dist/contracts/pages.js +24 -1
- package/dist/contracts/plan-policy.d.ts +1 -3
- package/dist/contracts/plan-policy.js +1 -2
- package/dist/contracts/platform.d.ts +61 -148
- package/dist/contracts/platform.js +70 -126
- package/dist/contracts/publish-archive.d.ts +498 -0
- package/dist/contracts/publish-archive.js +25 -0
- package/dist/contracts/resources.d.ts +12 -24
- package/dist/contracts/resources.js +32 -12
- package/dist/contracts/runtime-api.d.ts +369 -175
- package/dist/contracts/runtime-api.js +372 -348
- package/dist/contracts/runtime-app.d.ts +127 -120
- package/dist/contracts/runtime-app.js +19 -10
- package/dist/contracts/runtime-db.d.ts +1 -1
- package/dist/contracts/runtime-services.d.ts +219 -0
- package/dist/contracts/runtime-services.js +437 -0
- package/dist/contracts/runtime-storage.d.ts +45 -32
- package/dist/contracts/runtime-storage.js +30 -8
- package/dist/contracts/space-config.d.ts +10 -16
- package/dist/contracts/space-config.js +14 -10
- package/dist/contracts/spaces.d.ts +241 -436
- package/dist/contracts/spaces.js +128 -145
- 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 +358 -89
- 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.d.ts +0 -32
- package/dist/contracts/superadmin.js +3 -11
- package/dist/contracts/tags.d.ts +9 -9
- package/dist/contracts/teams.d.ts +38 -0
- package/dist/contracts/teams.js +34 -16
- package/dist/contracts/transfers.d.ts +0 -49
- package/dist/contracts/transfers.js +2 -6
- 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 +6 -5
- package/dist/contracts/webhooks.d.ts +2 -2
- package/dist/contracts/zero.d.ts +87 -56
- package/dist/contracts/zero.js +82 -20
- package/dist/dashboard-paths/index.d.ts +1 -1
- package/dist/dashboard-paths/index.js +1 -0
- package/dist/docs/agent-prose.d.ts +1 -1
- package/dist/docs/agent-prose.js +57 -61
- package/dist/docs/agent-setup.js +40 -13
- package/dist/docs/catalog.d.ts +19 -19
- package/dist/docs/catalog.js +30 -19
- package/dist/docs/error-docs.js +63 -151
- package/dist/test-helpers/fetch-stub.d.ts +16 -0
- package/dist/test-helpers/fetch-stub.js +24 -0
- package/dist/utils/build-settings.js +9 -3
- package/dist/utils/canonical-json.d.ts +10 -0
- package/dist/utils/canonical-json.js +21 -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/credential-policy.d.ts +1 -1
- 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 +7 -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 +25 -2
- 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 +1 -5
- package/dist/utils/local-space-state.js +7 -26
- package/dist/utils/page-fonts.d.ts +2 -2
- package/dist/utils/page-fonts.js +22 -4
- package/dist/utils/pages.d.ts +1 -2
- package/dist/utils/pages.js +21 -5
- 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 +20 -0
- package/dist/utils/publish-policy.fixtures.json +397 -0
- package/dist/utils/publish-policy.js +36 -19
- package/dist/utils/push-new.d.ts +7 -0
- package/dist/utils/push-new.js +7 -0
- package/dist/utils/query-keys.d.ts +0 -1
- package/dist/utils/query-keys.js +0 -1
- 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 +3 -13
- package/dist/utils/runtime-upload.js +4 -14
- package/dist/utils/space-config.d.ts +0 -33
- package/dist/utils/space-config.js +5 -326
- 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/version-path.js +12 -3
- package/dist/vocabulary.d.ts +5 -3
- package/dist/vocabulary.js +13 -10
- package/package.json +10 -5
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CONFIG_PUBLIC_GRANT_SOURCE_REFERENCE, grantSchema, } from "../contracts/grants.js";
|
|
2
|
-
import { SPACE_CONFIG_CAPS, SPACE_CONFIG_CANONICAL_FILE,
|
|
2
|
+
import { SPACE_CONFIG_CAPS, SPACE_CONFIG_CANONICAL_FILE, } from "../contracts/space-config.js";
|
|
3
3
|
const TEXT_BYTES = new TextEncoder();
|
|
4
4
|
export const CONFIG_PUBLIC_GRANT_COMPILED_ID = "config:access.public";
|
|
5
5
|
function byteLength(value) {
|
|
@@ -107,6 +107,7 @@ function capIssues(candidate) {
|
|
|
107
107
|
["title", SPACE_CONFIG_CAPS.metaTitleChars],
|
|
108
108
|
["description", SPACE_CONFIG_CAPS.metaDescriptionChars],
|
|
109
109
|
["image", SPACE_CONFIG_CAPS.metaImageChars],
|
|
110
|
+
["favicon", SPACE_CONFIG_CAPS.metaImageChars],
|
|
110
111
|
];
|
|
111
112
|
for (const [key, limit] of caps) {
|
|
112
113
|
const value = meta[key];
|
|
@@ -123,75 +124,6 @@ function capIssues(candidate) {
|
|
|
123
124
|
}
|
|
124
125
|
return issues;
|
|
125
126
|
}
|
|
126
|
-
// File-only keys (`name`, `runtime`) never reach the API overlay, so their
|
|
127
|
-
// checks live beside the shared caps rather than inside them. Both run BEFORE
|
|
128
|
-
// zod so a bad declaration reports its stable code instead of a shape error.
|
|
129
|
-
const RUNTIME_KINDS = new Set(["zero", "functions"]);
|
|
130
|
-
function fileOnlyIssues(candidate) {
|
|
131
|
-
const issues = [];
|
|
132
|
-
const name = candidate.name;
|
|
133
|
-
if (typeof name === "string" && name.trim().length > SPACE_CONFIG_CAPS.nameChars) {
|
|
134
|
-
issues.push({
|
|
135
|
-
severity: "error",
|
|
136
|
-
code: "config_name_too_long",
|
|
137
|
-
message: `name supports up to ${SPACE_CONFIG_CAPS.nameChars} characters.`,
|
|
138
|
-
path: "name",
|
|
139
|
-
details: { length: name.trim().length, limit: SPACE_CONFIG_CAPS.nameChars },
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
const runtime = candidate.runtime;
|
|
143
|
-
if (runtime === undefined) {
|
|
144
|
-
return issues;
|
|
145
|
-
}
|
|
146
|
-
if (typeof runtime !== "object" || runtime === null || Array.isArray(runtime)) {
|
|
147
|
-
issues.push({
|
|
148
|
-
severity: "error",
|
|
149
|
-
code: "config_runtime_invalid_kind",
|
|
150
|
-
message: 'runtime must be an object declaring a kind of "zero" or "functions".',
|
|
151
|
-
path: "runtime",
|
|
152
|
-
});
|
|
153
|
-
return issues;
|
|
154
|
-
}
|
|
155
|
-
const declaration = runtime;
|
|
156
|
-
const kind = declaration.kind;
|
|
157
|
-
if (typeof kind !== "string" || !RUNTIME_KINDS.has(kind)) {
|
|
158
|
-
issues.push({
|
|
159
|
-
severity: "error",
|
|
160
|
-
code: "config_runtime_invalid_kind",
|
|
161
|
-
message: 'runtime.kind must be "zero" or "functions".',
|
|
162
|
-
path: "runtime.kind",
|
|
163
|
-
...(typeof kind === "string" ? { details: { kind } } : {}),
|
|
164
|
-
});
|
|
165
|
-
return issues;
|
|
166
|
-
}
|
|
167
|
-
if (kind === "zero") {
|
|
168
|
-
// Zero is never inferred, so a declaration that names neither entry has
|
|
169
|
-
// nothing to compile — failing here beats publishing static files under a
|
|
170
|
-
// config that promised a database app.
|
|
171
|
-
for (const key of ["server", "client"]) {
|
|
172
|
-
const value = declaration[key];
|
|
173
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
174
|
-
issues.push({
|
|
175
|
-
severity: "error",
|
|
176
|
-
code: "config_runtime_entry_missing",
|
|
177
|
-
message: `runtime.${key} is required and must name a module when runtime.kind is "zero".`,
|
|
178
|
-
path: `runtime.${key}`,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return issues;
|
|
183
|
-
}
|
|
184
|
-
const entry = declaration.entry;
|
|
185
|
-
if (entry !== undefined && (typeof entry !== "string" || entry.trim().length === 0)) {
|
|
186
|
-
issues.push({
|
|
187
|
-
severity: "error",
|
|
188
|
-
code: "config_runtime_entry_missing",
|
|
189
|
-
message: "runtime.entry must name a module when present; omit it to detect the worker instead.",
|
|
190
|
-
path: "runtime.entry",
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
return issues;
|
|
194
|
-
}
|
|
195
127
|
/**
|
|
196
128
|
* Keys a committed `sf.jsonc` owns outright: they are facts about the content
|
|
197
129
|
* of a version (who may read it, what it is called, what runs it), so they roll
|
|
@@ -233,20 +165,9 @@ export function spaceConfigOverlayIssue(config) {
|
|
|
233
165
|
}
|
|
234
166
|
return capIssues(config)[0] ?? null;
|
|
235
167
|
}
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
// OUR routing vocabulary. These keys are real sf.jsonc keys, owned end to end
|
|
240
|
-
// by the routing compiler (crates/stattic-runtime-core): the strict v1 compiler
|
|
241
|
-
// validates them and the merge folds them in behind `_redirects`/`_headers`.
|
|
242
|
-
// This lane resolves the serving config, so it neither validates nor carries
|
|
243
|
-
// them — it just leaves them alone.
|
|
244
|
-
const ROUTING_RULE_KEYS = new Set(["redirects", "rewrites", "headers"]);
|
|
245
|
-
// Replaced by the `runtime` declaration; never silently ignored like other
|
|
246
|
-
// unknown keys, because it is the one key whose loss changes what gets served.
|
|
247
|
-
const REMOVED_FUNCTIONS_KEY = "functions";
|
|
248
|
-
const KNOWN_FILE_KEYS = new Set(Object.keys(spaceConfigFileSchema.shape));
|
|
249
|
-
// Strips JSONC comments and trailing commas (string-aware single pass).
|
|
168
|
+
// Strips JSONC comments and trailing commas (string-aware single pass). The
|
|
169
|
+
// config file itself is parsed by the crate; this stays for the OTHER JSONC
|
|
170
|
+
// files the CLI reads (agent configs, exported platform env).
|
|
250
171
|
export function stripJsonComments(raw) {
|
|
251
172
|
let output = "";
|
|
252
173
|
let inString = false;
|
|
@@ -326,245 +247,3 @@ export function stripJsonComments(raw) {
|
|
|
326
247
|
}
|
|
327
248
|
return output;
|
|
328
249
|
}
|
|
329
|
-
/**
|
|
330
|
-
* Parses a raw `sf.jsonc` at finalize: 256 KB cap, JSONC tolerance,
|
|
331
|
-
* routing-key rejection, unknown-key diagnostics, shared shape caps. Errors
|
|
332
|
-
* fail the version; warnings ride version diagnostics.
|
|
333
|
-
*/
|
|
334
|
-
export function parseSpaceConfigFile(raw, options = {}) {
|
|
335
|
-
const configPath = options.path ?? SPACE_CONFIG_CANONICAL_FILE;
|
|
336
|
-
if (byteLength(raw) > SPACE_CONFIG_CAPS.fileBytes) {
|
|
337
|
-
return {
|
|
338
|
-
config: null,
|
|
339
|
-
issues: [
|
|
340
|
-
{
|
|
341
|
-
severity: "error",
|
|
342
|
-
code: "config_file_too_large",
|
|
343
|
-
message: `${configPath} supports up to ${SPACE_CONFIG_CAPS.fileBytes} bytes.`,
|
|
344
|
-
path: configPath,
|
|
345
|
-
details: { size: byteLength(raw), limit: SPACE_CONFIG_CAPS.fileBytes },
|
|
346
|
-
},
|
|
347
|
-
],
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
let candidate;
|
|
351
|
-
try {
|
|
352
|
-
candidate = JSON.parse(stripJsonComments(raw));
|
|
353
|
-
}
|
|
354
|
-
catch (error) {
|
|
355
|
-
return {
|
|
356
|
-
config: null,
|
|
357
|
-
issues: [
|
|
358
|
-
{
|
|
359
|
-
severity: "error",
|
|
360
|
-
code: "config_invalid",
|
|
361
|
-
message: `${configPath} is not valid JSONC: ${error instanceof Error ? error.message : "parse error"}.`,
|
|
362
|
-
path: configPath,
|
|
363
|
-
},
|
|
364
|
-
],
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) {
|
|
368
|
-
return {
|
|
369
|
-
config: null,
|
|
370
|
-
issues: [
|
|
371
|
-
{
|
|
372
|
-
severity: "error",
|
|
373
|
-
code: "config_invalid",
|
|
374
|
-
message: `${configPath} must contain a JSON object.`,
|
|
375
|
-
path: configPath,
|
|
376
|
-
},
|
|
377
|
-
],
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
const record = candidate;
|
|
381
|
-
const issues = [];
|
|
382
|
-
for (const key of Object.keys(record)) {
|
|
383
|
-
if (KNOWN_FILE_KEYS.has(key)) {
|
|
384
|
-
continue;
|
|
385
|
-
}
|
|
386
|
-
if (ROUTING_RULE_KEYS.has(key)) {
|
|
387
|
-
delete record[key];
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
if (key === REMOVED_FUNCTIONS_KEY) {
|
|
391
|
-
// The runtime declaration replaced this key outright (spec "The old
|
|
392
|
-
// top-level functions.* key is removed in the same cutover"): warning-and-
|
|
393
|
-
// strip would publish static files under a config that asked for a
|
|
394
|
-
// worker, so it blocks with its own code instead.
|
|
395
|
-
issues.push({
|
|
396
|
-
severity: "error",
|
|
397
|
-
code: "config_functions_key_removed",
|
|
398
|
-
message: `The "${REMOVED_FUNCTIONS_KEY}" key was removed from ${configPath}; declare runtime.kind "functions" instead.`,
|
|
399
|
-
path: key,
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
else if (ROUTING_KEYS.has(key)) {
|
|
403
|
-
issues.push({
|
|
404
|
-
severity: "error",
|
|
405
|
-
code: "config_invalid",
|
|
406
|
-
message: `Routing never lives in ${configPath}; move "${key}" into _redirects/_headers.`,
|
|
407
|
-
path: key,
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
else {
|
|
411
|
-
issues.push({
|
|
412
|
-
severity: "warning",
|
|
413
|
-
code: "config_invalid",
|
|
414
|
-
message: `Unknown ${configPath} key "${key}" was ignored.`,
|
|
415
|
-
path: key,
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
delete record[key];
|
|
419
|
-
}
|
|
420
|
-
issues.push(...capIssues(record), ...fileOnlyIssues(record));
|
|
421
|
-
if (issues.some((issue) => issue.severity === "error")) {
|
|
422
|
-
return { config: null, issues };
|
|
423
|
-
}
|
|
424
|
-
const parsed = spaceConfigFileSchema.safeParse(record);
|
|
425
|
-
if (!parsed.success) {
|
|
426
|
-
for (const issue of parsed.error.issues) {
|
|
427
|
-
issues.push({
|
|
428
|
-
severity: "error",
|
|
429
|
-
code: "config_invalid",
|
|
430
|
-
message: issue.message,
|
|
431
|
-
path: issue.path.map(String).join(".") || configPath,
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
return { config: null, issues };
|
|
435
|
-
}
|
|
436
|
-
return { config: parsed.data, issues };
|
|
437
|
-
}
|
|
438
|
-
// Mirrors the runtime's private-file rules (_stattic_runtime_file_private):
|
|
439
|
-
// convention/config files and dot-segment paths never serve, so they don't
|
|
440
|
-
// count toward root HTML inference below.
|
|
441
|
-
const PRIVATE_SERVING_FILES = new Set([
|
|
442
|
-
"_redirects",
|
|
443
|
-
"_headers",
|
|
444
|
-
"_config.json",
|
|
445
|
-
"_routes.json",
|
|
446
|
-
...SPACE_CONFIG_PRIVATE_FILES,
|
|
447
|
-
]);
|
|
448
|
-
function privateServingPath(path) {
|
|
449
|
-
if (PRIVATE_SERVING_FILES.has(path) ||
|
|
450
|
-
path === ".well-known/spacefast-runtime" ||
|
|
451
|
-
path === ".well-known/stattic-runtime") {
|
|
452
|
-
return true;
|
|
453
|
-
}
|
|
454
|
-
const segments = path.toLowerCase().split("/");
|
|
455
|
-
return segments.some((segment, index) => !(index === 0 && segment === ".well-known") && segment.startsWith("."));
|
|
456
|
-
}
|
|
457
|
-
function rootHtmlPaths(manifestPaths) {
|
|
458
|
-
const htmlPaths = [];
|
|
459
|
-
for (const path of manifestPaths) {
|
|
460
|
-
if (path === "" || privateServingPath(path)) {
|
|
461
|
-
continue;
|
|
462
|
-
}
|
|
463
|
-
if (!path.includes("/") && /\.html?$/i.test(path)) {
|
|
464
|
-
htmlPaths.push(path);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
return htmlPaths.toSorted((left, right) => left.localeCompare(right));
|
|
468
|
-
}
|
|
469
|
-
function normalizeFallback(fallback) {
|
|
470
|
-
if (fallback === undefined) {
|
|
471
|
-
return null;
|
|
472
|
-
}
|
|
473
|
-
if (typeof fallback === "string") {
|
|
474
|
-
return { path: fallback.replace(/^\/+/, ""), status: 200 };
|
|
475
|
-
}
|
|
476
|
-
const status = fallback.status === 404 ? 404 : 200;
|
|
477
|
-
return { path: fallback.path.replace(/^\/+/, ""), status };
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* Resolves the effective SpaceConfig for a version at finalize (or
|
|
481
|
-
* config-only regeneration). `manifestPaths` is the canonical committed file
|
|
482
|
-
* set; `fileConfig` the parsed sf.jsonc (null when absent); `overlay`
|
|
483
|
-
* the stored space config overlay (null when nothing overridden).
|
|
484
|
-
*/
|
|
485
|
-
export function resolveEffectiveSpaceConfig(input) {
|
|
486
|
-
const rootHtml = rootHtmlPaths(input.manifestPaths);
|
|
487
|
-
const hasRootIndex = rootHtml.includes("index.html");
|
|
488
|
-
const inferredRootHtmlIndex = hasRootIndex ? "index.html" : (rootHtml[0] ?? null);
|
|
489
|
-
const explicitServingShape = input.fileConfig?.index !== undefined ||
|
|
490
|
-
input.fileConfig?.listing !== undefined ||
|
|
491
|
-
input.overlay?.index !== undefined ||
|
|
492
|
-
input.overlay?.listing !== undefined;
|
|
493
|
-
// Content-inferred defaults (spec: the old website/files modes are exactly
|
|
494
|
-
// these two default bundles; never API fields). A Functions version is an
|
|
495
|
-
// application even when it has no root HTML: unresolved requests, including
|
|
496
|
-
// `/`, belong to its worker. Static versions retain the content inference —
|
|
497
|
-
// a root index.html means website mode, any other root HTML becomes the
|
|
498
|
-
// homepage rewrite, and no root HTML means files mode.
|
|
499
|
-
const functionsApplication = input.runtimeKind === "functions";
|
|
500
|
-
const defaults = inferredRootHtmlIndex
|
|
501
|
-
? { index: inferredRootHtmlIndex, listing: false }
|
|
502
|
-
: { index: false, listing: !functionsApplication };
|
|
503
|
-
const merged = { ...defaults };
|
|
504
|
-
for (const layer of [input.fileConfig, input.overlay]) {
|
|
505
|
-
if (!layer) {
|
|
506
|
-
continue;
|
|
507
|
-
}
|
|
508
|
-
for (const key of Object.keys(spaceConfigSchema.shape)) {
|
|
509
|
-
const value = layer[key];
|
|
510
|
-
if (value !== undefined) {
|
|
511
|
-
merged[key] = value;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
const issues = [];
|
|
516
|
-
const fallback = normalizeFallback(merged.fallback);
|
|
517
|
-
// Invariant (spec "Serving resolution"): a fallback must point at a
|
|
518
|
-
// committed file — no implicit extension guessing, fail at finalize.
|
|
519
|
-
if (fallback && !input.manifestPaths.has(fallback.path)) {
|
|
520
|
-
issues.push({
|
|
521
|
-
severity: "error",
|
|
522
|
-
code: "config_invalid",
|
|
523
|
-
message: `fallback path "/${fallback.path}" is not a committed file in this version.`,
|
|
524
|
-
path: "fallback.path",
|
|
525
|
-
details: { path: fallback.path },
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
const listing = merged.listing ?? (!functionsApplication && !inferredRootHtmlIndex);
|
|
529
|
-
const serving = {
|
|
530
|
-
index: merged.index ?? inferredRootHtmlIndex ?? false,
|
|
531
|
-
fallback,
|
|
532
|
-
// Clean URLs (runtime W7.1): an explicit config value wins; the default is
|
|
533
|
-
// ON for plain static sites and OFF when a 200-status SPA fallback owns
|
|
534
|
-
// extensionless routes (the fallback answers them instead). The runtime
|
|
535
|
-
// resolver (management.php) applies the same default for artifacts
|
|
536
|
-
// finalized without the key.
|
|
537
|
-
cleanUrls: merged.cleanUrls ?? !(fallback && fallback.status === 200),
|
|
538
|
-
listing,
|
|
539
|
-
// The single-file viewer rides the listing bundle (artifact-style spaces).
|
|
540
|
-
viewer: listing,
|
|
541
|
-
meta: merged.meta ?? null,
|
|
542
|
-
pageTheme: merged.theme && Object.keys(merged.theme).length > 0 ? merged.theme : null,
|
|
543
|
-
experimentalGutenberg: merged.experimental_gutenberg === true,
|
|
544
|
-
inject: merged.inject ?? null,
|
|
545
|
-
};
|
|
546
|
-
if (inferredRootHtmlIndex !== null && !hasRootIndex && serving.index === inferredRootHtmlIndex) {
|
|
547
|
-
// Surfaced on the publish receipt and version diagnostics so the CLI and
|
|
548
|
-
// agents learn the rewrite without probing the live URL.
|
|
549
|
-
issues.push({
|
|
550
|
-
severity: "info",
|
|
551
|
-
code: "single_html_index",
|
|
552
|
-
message: `${inferredRootHtmlIndex} is the root HTML file selected for this version, so it serves as the homepage: / rewrites to /${inferredRootHtmlIndex}.`,
|
|
553
|
-
path: "index",
|
|
554
|
-
details: { path: inferredRootHtmlIndex, rootHtmlCount: rootHtml.length },
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
if (inferredRootHtmlIndex === null &&
|
|
558
|
-
serving.index === false &&
|
|
559
|
-
serving.listing &&
|
|
560
|
-
!explicitServingShape) {
|
|
561
|
-
issues.push({
|
|
562
|
-
severity: "info",
|
|
563
|
-
code: "files_mode_inferred",
|
|
564
|
-
message: "No HTML files were found in the root of this version, so it serves in files mode with directory listing enabled.",
|
|
565
|
-
path: "listing",
|
|
566
|
-
details: { rootHtmlCount: 0 },
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
return { config: merged, serving, issues };
|
|
570
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const STORAGE_OBJECT_MAX_FILE_BYTES: number;
|
|
2
|
-
export declare const STORAGE_OBJECT_MAX_OWNER_BYTES: number;
|
|
3
2
|
export declare const STORAGE_CONTENT_SNIFF_BYTES = 512;
|
|
3
|
+
export declare const STORAGE_ANON_DAILY_BYTES: number;
|
|
4
4
|
export declare const STORAGE_BLOCKED_CONTENT_TYPES: string[];
|
|
5
5
|
export declare const STORAGE_BLOCKED_CONTENT_TYPE_FRAGMENTS: string[];
|
|
6
6
|
export declare const STORAGE_BLOCKED_MAGIC_HEX: string[];
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
// `bun --filter @spacefast/control-plane runtime:codegen-policy`. Both execute
|
|
6
6
|
// the same storage-policy.fixtures.json corpus.
|
|
7
7
|
export const STORAGE_OBJECT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
8
|
-
export const STORAGE_OBJECT_MAX_OWNER_BYTES = 100 * 1024 * 1024;
|
|
9
8
|
export const STORAGE_CONTENT_SNIFF_BYTES = 512;
|
|
9
|
+
// Anonymous commenters share a per-space rolling daily upload budget; identified
|
|
10
|
+
// principals and Functions relays are accountable and carry no budget.
|
|
11
|
+
export const STORAGE_ANON_DAILY_BYTES = 200 * 1024 * 1024;
|
|
10
12
|
export const STORAGE_BLOCKED_CONTENT_TYPES = [
|
|
11
13
|
"application/java-archive",
|
|
12
14
|
"application/java-vm",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class StructuredError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly details: Record<string, unknown> | undefined;
|
|
4
|
+
readonly exitCode: number;
|
|
5
|
+
readonly type: string | undefined;
|
|
6
|
+
constructor(input: {
|
|
7
|
+
code: string;
|
|
8
|
+
message: string;
|
|
9
|
+
details?: Record<string, unknown> | undefined;
|
|
10
|
+
exitCode?: number | undefined;
|
|
11
|
+
type?: string | undefined;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export declare function structuredValidationError(message: string, details?: Record<string, unknown>): StructuredError;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CLI_EXIT_CODES } from "../contracts/cli.js";
|
|
2
|
+
export class StructuredError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
details;
|
|
5
|
+
exitCode;
|
|
6
|
+
type;
|
|
7
|
+
constructor(input) {
|
|
8
|
+
super(input.message);
|
|
9
|
+
this.name = "StructuredError";
|
|
10
|
+
this.code = input.code;
|
|
11
|
+
this.details = input.details;
|
|
12
|
+
this.exitCode = input.exitCode ?? CLI_EXIT_CODES.validation;
|
|
13
|
+
this.type = input.type;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function structuredValidationError(message, details) {
|
|
17
|
+
return new StructuredError({
|
|
18
|
+
code: "validation_error",
|
|
19
|
+
message,
|
|
20
|
+
...(details ? { details } : {}),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -3,12 +3,20 @@
|
|
|
3
3
|
// what manifest paths, finalize's normalized paths, route-map keys, and dedup
|
|
4
4
|
// keys all mean. The PHP runtime applies the same transform (rawurldecode once
|
|
5
5
|
// + Normalizer::normalize) at upload and serve time.
|
|
6
|
+
// oxlint-disable-next-line no-control-regex -- matching control characters is the point: they are never part of a servable path.
|
|
6
7
|
const CONTROL_CHARS_PATTERN = /[\u0000-\u001f\u007f]/;
|
|
7
8
|
// Structural normalization shared by lookups and intake: forward slashes,
|
|
8
|
-
// no leading slashes, no empty/"."/".." segments.
|
|
9
|
-
// to already-canonical stored paths (NFC is
|
|
9
|
+
// no leading slashes, no control characters, no empty/"."/".." segments.
|
|
10
|
+
// Idempotent; safe to apply to already-canonical stored paths (NFC is
|
|
11
|
+
// idempotent, no percent-decoding).
|
|
10
12
|
export function normalizeVersionPath(input) {
|
|
11
13
|
const normalized = input.replace(/\\/g, "/").replace(/^\/+/, "").normalize("NFC");
|
|
14
|
+
// Here rather than only in canonicalizeVersionPath: publish-policy's
|
|
15
|
+
// staticUploadPathViolation validates with this function, so a control
|
|
16
|
+
// character in an uploaded path used to pass the policy unclassified.
|
|
17
|
+
if (CONTROL_CHARS_PATTERN.test(normalized)) {
|
|
18
|
+
throw new Error(`invalid_version_path:${input}`);
|
|
19
|
+
}
|
|
12
20
|
const segments = [];
|
|
13
21
|
for (const segment of normalized.split("/")) {
|
|
14
22
|
if (!segment || segment === ".") {
|
|
@@ -41,7 +49,8 @@ export function canonicalizeVersionPath(input) {
|
|
|
41
49
|
const canonical = decoded.normalize("NFC");
|
|
42
50
|
// Backslashes as separators are denied at intake (spec "Static path
|
|
43
51
|
// policy"); only client-side helpers translate Windows separators.
|
|
44
|
-
|
|
52
|
+
// Control characters are rejected by normalizeVersionPath below.
|
|
53
|
+
if (canonical.includes("\\")) {
|
|
45
54
|
throw new Error(`invalid_version_path:${input}`);
|
|
46
55
|
}
|
|
47
56
|
return normalizeVersionPath(canonical);
|
package/dist/vocabulary.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export type TermContext =
|
|
|
7
7
|
"platform-api"
|
|
8
8
|
/** The GitHub integration vocabulary (push deploys, deployment status). */
|
|
9
9
|
| "github-integration"
|
|
10
|
+
/** Internal operator surfaces (superadmin, ops scripts, fleet inventory). */
|
|
11
|
+
| "operator"
|
|
10
12
|
/** A fixed marketing phrase, spelled out verbatim in `allowedPhrases`. */
|
|
11
13
|
| "fixed-phrase";
|
|
12
14
|
/** A single canonical product concept and the words allowed to express it. */
|
|
@@ -38,8 +40,8 @@ export declare const CONCEPTS: {
|
|
|
38
40
|
readonly id: "space";
|
|
39
41
|
readonly canonicalNoun: "space";
|
|
40
42
|
readonly aliases: readonly [];
|
|
41
|
-
readonly externalTerms: readonly [
|
|
42
|
-
readonly deprecated: readonly ["project"];
|
|
43
|
+
readonly externalTerms: readonly [];
|
|
44
|
+
readonly deprecated: readonly ["project", "site"];
|
|
43
45
|
readonly description: string;
|
|
44
46
|
};
|
|
45
47
|
readonly publish: {
|
|
@@ -137,7 +139,7 @@ export declare const EXTERNAL_TERM_ALLOWLIST: readonly [{
|
|
|
137
139
|
}, {
|
|
138
140
|
readonly term: "site";
|
|
139
141
|
readonly concept: "space";
|
|
140
|
-
readonly allowedContexts: readonly ["
|
|
142
|
+
readonly allowedContexts: readonly ["operator"];
|
|
141
143
|
readonly replacement: "space";
|
|
142
144
|
readonly reason: string;
|
|
143
145
|
}, {
|
package/dist/vocabulary.js
CHANGED
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
// It is additive — wiring consumers (CLI, MCP, dashboard, docs generators) onto
|
|
9
9
|
// it happens in later chunks. The decisions encoded here are LOCKED:
|
|
10
10
|
//
|
|
11
|
-
// - `space` is the primitive noun
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// - `space` is the primitive noun everywhere, user surfaces and platform API
|
|
12
|
+
// alike. A space IS a site: there is no second host entity to name. `site`
|
|
13
|
+
// survives only as WP.Cloud's own word for the box, in operator surfaces
|
|
14
|
+
// and the warm-pool inventory.
|
|
14
15
|
// - `publish` is the verb. AMENDED 2026-07-02: `deploy` / `deployments` are
|
|
15
16
|
// first-class ALIASES of `publish` / `versions` (see DEPLOY_ALIASES) — they
|
|
16
17
|
// stay listed and documented, always with the sanctioned disambiguation
|
|
@@ -30,10 +31,11 @@ export const CONCEPTS = {
|
|
|
30
31
|
id: "space",
|
|
31
32
|
canonicalNoun: "space",
|
|
32
33
|
aliases: [],
|
|
33
|
-
externalTerms: [
|
|
34
|
-
deprecated: ["project"],
|
|
35
|
-
description: "The primitive noun everywhere
|
|
36
|
-
"
|
|
34
|
+
externalTerms: [],
|
|
35
|
+
deprecated: ["project", "site"],
|
|
36
|
+
description: "The primitive noun everywhere, user surfaces and platform API alike. A " +
|
|
37
|
+
"space IS a site, so 'site' names nothing of ours; 'project' is never " +
|
|
38
|
+
"the primitive.",
|
|
37
39
|
},
|
|
38
40
|
publish: {
|
|
39
41
|
id: "publish",
|
|
@@ -129,10 +131,11 @@ export const EXTERNAL_TERM_ALLOWLIST = [
|
|
|
129
131
|
{
|
|
130
132
|
term: "site",
|
|
131
133
|
concept: "space",
|
|
132
|
-
allowedContexts: ["
|
|
134
|
+
allowedContexts: ["operator"],
|
|
133
135
|
replacement: "space",
|
|
134
|
-
reason: "'
|
|
135
|
-
"surfaces
|
|
136
|
+
reason: "A space IS a site, so 'site' is no longer a Spacefast noun — it is only " +
|
|
137
|
+
"WP.Cloud's word for the box, which operator surfaces and warm-pool " +
|
|
138
|
+
"inventory may use. Every customer-facing surface says 'space'.",
|
|
136
139
|
},
|
|
137
140
|
{
|
|
138
141
|
term: "WP.Cloud",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spacefast/common",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Shared TypeScript contracts and utilities for Spacefast packages.",
|
|
6
6
|
"repository": {
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"../../apps/www/dist/ai.txt",
|
|
28
28
|
"../../apps/www/dist/llms-full.txt",
|
|
29
29
|
"../../apps/www/dist/llms.txt",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
30
|
+
"../cli/skill.template.md",
|
|
31
|
+
"../cli/skill.references.template.md",
|
|
32
|
+
"../cli/scripts/generate-skill.mjs",
|
|
32
33
|
"../../e2e-tests/fixtures/zero-all-features/api-finalize-request.json",
|
|
33
34
|
"../../e2e-tests/fixtures/zero-all-features/runtime-compiler-finalize-input.json",
|
|
34
35
|
"../../runtime/engine/shared/fonts",
|
|
35
|
-
"
|
|
36
|
-
"../cli/references.md"
|
|
36
|
+
"src/docs/agent-prose.ts"
|
|
37
37
|
]
|
|
38
38
|
},
|
|
39
39
|
"exports": {
|
|
@@ -75,6 +75,11 @@
|
|
|
75
75
|
"import": "./dist/utils/*.js",
|
|
76
76
|
"default": "./dist/utils/*.js"
|
|
77
77
|
},
|
|
78
|
+
"./test-helpers/*": {
|
|
79
|
+
"types": "./dist/test-helpers/*.d.ts",
|
|
80
|
+
"import": "./dist/test-helpers/*.js",
|
|
81
|
+
"default": "./dist/test-helpers/*.js"
|
|
82
|
+
},
|
|
78
83
|
"./slug-policy": {
|
|
79
84
|
"types": "./dist/slug-policy/index.d.ts",
|
|
80
85
|
"import": "./dist/slug-policy/index.js",
|