@spacefast/common 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/brand-assets/spacefast-favicon.svg +16 -0
- package/brand-assets/spacefast-sf-full-bleed.svg +13 -0
- package/brand-assets/spacefast-wordmark.svg +5 -0
- package/dist/agents/connect-targets.d.ts +6 -1
- package/dist/agents/connect-targets.js +19 -0
- package/dist/brand-assets-build.d.ts +26 -0
- package/dist/brand-assets-build.js +81 -0
- package/dist/brand-assets.d.ts +42 -0
- package/dist/brand-assets.js +30 -0
- package/dist/contracts/access.d.ts +143 -0
- package/dist/contracts/access.js +33 -0
- package/dist/contracts/api-keys.d.ts +123 -0
- package/dist/contracts/api-keys.js +65 -0
- package/dist/contracts/builds.d.ts +40 -0
- package/dist/contracts/builds.js +17 -0
- package/dist/contracts/enums.js +5 -4
- package/dist/contracts/error-code-meta.d.ts +116 -4
- package/dist/contracts/error-code-meta.js +29 -1
- package/dist/contracts/error-codes.d.ts +1 -1
- package/dist/contracts/error-codes.js +28 -0
- package/dist/contracts/feature-lifecycle-core.d.ts +36 -0
- package/dist/contracts/feature-lifecycle-core.js +220 -0
- package/dist/contracts/feature-lifecycle.d.ts +7 -0
- package/dist/contracts/feature-lifecycle.js +31 -0
- package/dist/contracts/generated-feature-launch-entries.d.ts +2 -0
- package/dist/contracts/generated-feature-launch-entries.js +518 -0
- package/dist/contracts/ids.d.ts +4 -0
- package/dist/contracts/ids.js +8 -0
- package/dist/contracts/operations.d.ts +161 -1
- package/dist/contracts/operations.js +49 -1
- package/dist/contracts/platform.d.ts +6 -0
- package/dist/contracts/platform.js +6 -0
- package/dist/contracts/publishes.d.ts +130 -0
- package/dist/contracts/publishes.js +145 -0
- package/dist/contracts/repository-connections.d.ts +3 -0
- package/dist/contracts/repository-connections.js +13 -0
- package/dist/contracts/resources.d.ts +2 -0
- package/dist/contracts/resources.js +10 -2
- package/dist/contracts/runtime-api.d.ts +2 -0
- package/dist/contracts/runtime-api.js +1 -0
- package/dist/contracts/sf-config-v1.d.ts +75 -0
- package/dist/contracts/sf-config-v1.js +6 -0
- package/dist/contracts/spaces.d.ts +12 -3
- package/dist/contracts/spaces.js +22 -2
- package/dist/contracts/superadmin-spaces.d.ts +2 -0
- package/dist/contracts/superadmin-tenants.d.ts +6 -6
- package/dist/contracts/superadmin-tenants.js +6 -11
- package/dist/contracts/teams.d.ts +8 -11
- package/dist/contracts/teams.js +11 -16
- package/dist/docs/agent-setup.d.ts +51 -0
- package/dist/docs/agent-setup.js +168 -3
- package/dist/docs/error-docs.js +113 -1
- package/dist/slug-policy/blocklist.d.ts +4 -0
- package/dist/slug-policy/blocklist.js +122 -0
- package/dist/slug-policy/index.d.ts +17 -0
- package/dist/slug-policy/index.js +86 -0
- package/dist/utils/concurrency.d.ts +1 -0
- package/dist/utils/concurrency.js +22 -0
- package/dist/utils/email.d.ts +1 -0
- package/dist/utils/email.js +3 -0
- package/dist/utils/query-keys.d.ts +1 -0
- package/dist/utils/query-keys.js +4 -0
- package/dist/utils/sf-config-v1.d.ts +2 -0
- package/dist/utils/sf-config-v1.js +542 -0
- package/package.json +20 -1
package/dist/docs/error-docs.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// and pages never go missing. apps/www renders the HTML pages and writes the
|
|
6
6
|
// `.md` twins from the same entries.
|
|
7
7
|
import { BRAND } from "../brand.js";
|
|
8
|
-
import {
|
|
8
|
+
import { errorDocsUrl } from "../contracts/error-codes.js";
|
|
9
9
|
// Restore upgrade-oriented recovery copy here when paid plan checkout is live.
|
|
10
10
|
export const ERROR_DOCS = {
|
|
11
11
|
abuse_report_invalid_transition: {
|
|
@@ -48,6 +48,10 @@ export const ERROR_DOCS = {
|
|
|
48
48
|
summary: "Too many unclaimed anonymous spaces are already active for this client.",
|
|
49
49
|
fix: "Log in to publish into an authenticated space, or claim/delete an existing anonymous space before creating another.",
|
|
50
50
|
},
|
|
51
|
+
api_key_scope_underivable: {
|
|
52
|
+
summary: "A concrete access policy could not be derived from the requested scope and permissions.",
|
|
53
|
+
fix: "Point scope at a resource you can already reach (an existing space owned by a team) and request permissions your credential already has.",
|
|
54
|
+
},
|
|
51
55
|
archive_body_required: {
|
|
52
56
|
summary: "This request must carry an archive body but none was provided.",
|
|
53
57
|
fix: "Send the zip bytes as the request body with the matching content-type header.",
|
|
@@ -424,6 +428,10 @@ export const ERROR_DOCS = {
|
|
|
424
428
|
summary: "The version upload declares the same file path twice.",
|
|
425
429
|
fix: "Deduplicate the file list so every path appears once.",
|
|
426
430
|
},
|
|
431
|
+
enforcement_policy_overlap: {
|
|
432
|
+
summary: "Enforcement policy severity ranges overlap within a category.",
|
|
433
|
+
fix: "Adjust the category ranges so each severity is covered by at most one policy.",
|
|
434
|
+
},
|
|
427
435
|
expired_token: {
|
|
428
436
|
summary: "The token has expired.",
|
|
429
437
|
fix: "Request a new token and retry.",
|
|
@@ -768,6 +776,10 @@ export const ERROR_DOCS = {
|
|
|
768
776
|
summary: "The uploaded archive exceeds the maximum allowed size.",
|
|
769
777
|
fix: "Reduce the archive size or use a version upload session for big publishes.",
|
|
770
778
|
},
|
|
779
|
+
publish_base_changed: {
|
|
780
|
+
summary: "Someone published new space settings after the base you loaded, so saving would overwrite their change.",
|
|
781
|
+
fix: "Reload the latest settings, review the difference, and save again; pass force only to overwrite deliberately.",
|
|
782
|
+
},
|
|
771
783
|
publish_config_unsupported: {
|
|
772
784
|
summary: "The publish carries configuration this endpoint does not support.",
|
|
773
785
|
fix: "Move the configuration into sf.jsonc or the space settings.",
|
|
@@ -928,6 +940,18 @@ export const ERROR_DOCS = {
|
|
|
928
940
|
summary: "The device login is being polled faster than the advised interval.",
|
|
929
941
|
fix: "Increase the polling interval; aggressive polling invalidates the flow.",
|
|
930
942
|
},
|
|
943
|
+
slug_invalid: {
|
|
944
|
+
summary: "The slug does not meet the structural rules for managed names.",
|
|
945
|
+
fix: "Use lowercase letters, numbers, and single dashes within the allowed length; the error message identifies the failed rule.",
|
|
946
|
+
},
|
|
947
|
+
slug_reserved: {
|
|
948
|
+
summary: "The slug is reserved for infrastructure, product, or impersonation protection.",
|
|
949
|
+
fix: "Choose a different name that is not reserved by the shared slug policy.",
|
|
950
|
+
},
|
|
951
|
+
slug_unavailable: {
|
|
952
|
+
summary: "The slug is already in use within the relevant ownership scope.",
|
|
953
|
+
fix: "Choose another slug or use the available suggestion returned by the API.",
|
|
954
|
+
},
|
|
931
955
|
space_already_claimed: {
|
|
932
956
|
summary: "The space was already claimed; a claim can only succeed once.",
|
|
933
957
|
fix: "If you are the agent that published this space, exchange your claim token once at POST /v1/anonymous-claim/exchange for a durable access token and keep publishing to the same spaceId. Otherwise ask the owner for access — do not publish a duplicate space.",
|
|
@@ -952,6 +976,10 @@ export const ERROR_DOCS = {
|
|
|
952
976
|
summary: "The space still has an active version, which blocks this operation.",
|
|
953
977
|
fix: "Delete or supersede the active version first.",
|
|
954
978
|
},
|
|
979
|
+
space_hostname_identity_missing: {
|
|
980
|
+
summary: "The space is missing its persisted default hostname identity.",
|
|
981
|
+
fix: "Contact support. Retrying cannot safely recreate a permanent hostname identity.",
|
|
982
|
+
},
|
|
955
983
|
space_import_archive_invalid: {
|
|
956
984
|
summary: `The import archive is not a valid ${BRAND.productName} export.`,
|
|
957
985
|
fix: "Export the source space again and upload the unmodified archive.",
|
|
@@ -1272,6 +1300,10 @@ export const ERROR_DOCS = {
|
|
|
1272
1300
|
summary: "Zero endpoints are activating.",
|
|
1273
1301
|
fix: "Wait for the dedicated runtime move to complete, then retry the request.",
|
|
1274
1302
|
},
|
|
1303
|
+
zero_ai_unavailable: {
|
|
1304
|
+
summary: "The local Zero AI service is not configured.",
|
|
1305
|
+
fix: "Configure an AI handler for zero dev before calling the AI service.",
|
|
1306
|
+
},
|
|
1275
1307
|
zero_artifact_abi_mismatch: {
|
|
1276
1308
|
summary: "The Zero endpoint artifact was built for a different runner ABI.",
|
|
1277
1309
|
fix: `Rebuild and republish the Zero project with the current ${BRAND.productName} CLI and runtime.`,
|
|
@@ -1284,6 +1316,10 @@ export const ERROR_DOCS = {
|
|
|
1284
1316
|
summary: "The Zero endpoint artifact could not be parsed.",
|
|
1285
1317
|
fix: "Rebuild the Zero project; do not edit generated artifact JSON by hand.",
|
|
1286
1318
|
},
|
|
1319
|
+
zero_artifact_missing: {
|
|
1320
|
+
summary: "A Zero route points to an endpoint artifact that is missing.",
|
|
1321
|
+
fix: "Declare the endpoint in zero_endpoints, then rebuild and publish again.",
|
|
1322
|
+
},
|
|
1287
1323
|
zero_artifact_path_invalid: {
|
|
1288
1324
|
summary: "The Zero endpoint artifact path is invalid.",
|
|
1289
1325
|
fix: "Rebuild the Zero routes so all artifact paths stay inside the version bundle.",
|
|
@@ -1292,6 +1328,10 @@ export const ERROR_DOCS = {
|
|
|
1292
1328
|
summary: "The Zero endpoint artifact could not be read by the runtime.",
|
|
1293
1329
|
fix: "Verify the version finalized with all generated Zero files present.",
|
|
1294
1330
|
},
|
|
1331
|
+
zero_artifact_untrusted: {
|
|
1332
|
+
summary: "A Zero route points to an uploaded artifact that the runtime did not compile.",
|
|
1333
|
+
fix: "Declare the endpoint in zero_endpoints so finalize generates trusted bytecode.",
|
|
1334
|
+
},
|
|
1295
1335
|
zero_auth_unavailable: {
|
|
1296
1336
|
summary: "Hosted Zero auth is not configured for this deployment.",
|
|
1297
1337
|
fix: "Finalize the Zero version with hosted auth URLs or use guest/local auth.",
|
|
@@ -1332,6 +1372,10 @@ export const ERROR_DOCS = {
|
|
|
1332
1372
|
summary: "The Zero endpoint bytecode file could not be read.",
|
|
1333
1373
|
fix: "Verify the version finalized with the generated bytecode files present.",
|
|
1334
1374
|
},
|
|
1375
|
+
zero_capabilities_encode_failed: {
|
|
1376
|
+
summary: "The runtime could not encode the Zero endpoint capabilities.",
|
|
1377
|
+
fix: "Rebuild with valid capability metadata; report the failure if it persists.",
|
|
1378
|
+
},
|
|
1335
1379
|
zero_client_bundle_not_loaded: {
|
|
1336
1380
|
summary: "The local Zero dev client bundle has not been generated or loaded.",
|
|
1337
1381
|
fix: "Restart zero dev or run a fresh local build before loading the client route.",
|
|
@@ -1404,6 +1448,22 @@ export const ERROR_DOCS = {
|
|
|
1404
1448
|
summary: "The Zero database dump query parameters are invalid.",
|
|
1405
1449
|
fix: "Use valid table, limit, and cursor parameters.",
|
|
1406
1450
|
},
|
|
1451
|
+
zero_endpoint_compile_failed: {
|
|
1452
|
+
summary: "The runtime could not compile the Zero endpoint bytecode.",
|
|
1453
|
+
fix: "Inspect the compile diagnostics, fix the endpoint source, and publish again.",
|
|
1454
|
+
},
|
|
1455
|
+
zero_endpoint_conflict: {
|
|
1456
|
+
summary: "Two Zero routes resolve to the same runtime path.",
|
|
1457
|
+
fix: "Remove the duplicate generated or explicit lookup route and publish again.",
|
|
1458
|
+
},
|
|
1459
|
+
zero_endpoint_duplicate: {
|
|
1460
|
+
summary: "Two Zero endpoints declare the same method and route.",
|
|
1461
|
+
fix: "Give each endpoint a unique method and route before publishing again.",
|
|
1462
|
+
},
|
|
1463
|
+
zero_endpoint_id_duplicate: {
|
|
1464
|
+
summary: "Two Zero endpoints declare the same endpoint id.",
|
|
1465
|
+
fix: "Give every endpoint a unique endpoint id before publishing again.",
|
|
1466
|
+
},
|
|
1407
1467
|
zero_endpoint_index_invalid: {
|
|
1408
1468
|
summary: "The Zero endpoint index does not contain valid endpoint mappings.",
|
|
1409
1469
|
fix: "Rebuild and republish the Zero project.",
|
|
@@ -1420,6 +1480,10 @@ export const ERROR_DOCS = {
|
|
|
1420
1480
|
summary: "The Zero endpoint index could not be read.",
|
|
1421
1481
|
fix: "Verify the version finalized with zero/endpoints-index.json present.",
|
|
1422
1482
|
},
|
|
1483
|
+
zero_endpoint_invalid: {
|
|
1484
|
+
summary: "A Zero endpoint definition or its database metadata is invalid.",
|
|
1485
|
+
fix: "Fix the endpoint method, route, source, capabilities, or database metadata and publish again.",
|
|
1486
|
+
},
|
|
1423
1487
|
zero_endpoint_mismatch: {
|
|
1424
1488
|
summary: "The requested Zero endpoint does not match the endpoint artifact.",
|
|
1425
1489
|
fix: "Rebuild routes and artifacts together and redeploy the version.",
|
|
@@ -1428,6 +1492,14 @@ export const ERROR_DOCS = {
|
|
|
1428
1492
|
summary: "No Zero endpoint artifact exists for this endpoint id.",
|
|
1429
1493
|
fix: "Check the route path and rebuild the Zero endpoint index.",
|
|
1430
1494
|
},
|
|
1495
|
+
zero_endpoints_invalid: {
|
|
1496
|
+
summary: "The Zero endpoints payload is invalid.",
|
|
1497
|
+
fix: "Send an endpoint array within the supported count and size limits.",
|
|
1498
|
+
},
|
|
1499
|
+
zero_endpoints_too_many: {
|
|
1500
|
+
summary: "The Zero endpoints payload exceeds the supported endpoint count.",
|
|
1501
|
+
fix: "Reduce the project to at most 128 Zero endpoints, then publish again.",
|
|
1502
|
+
},
|
|
1431
1503
|
zero_requires_dedicated_plan: {
|
|
1432
1504
|
summary: "Zero endpoints require a plan with dedicated runtime isolation.",
|
|
1433
1505
|
fix: "Upgrade the team to a plan that includes Zero and dedicated runtime, then publish again.",
|
|
@@ -1464,10 +1536,18 @@ export const ERROR_DOCS = {
|
|
|
1464
1536
|
summary: "The Zero log query parameters are invalid.",
|
|
1465
1537
|
fix: "Use valid cursor, limit, mutationName, and requestId parameters.",
|
|
1466
1538
|
},
|
|
1539
|
+
zero_lookup_invalid: {
|
|
1540
|
+
summary: "The Zero lookup map contains an invalid path or action.",
|
|
1541
|
+
fix: "Use normalized relative paths and valid finalized Zero actions.",
|
|
1542
|
+
},
|
|
1467
1543
|
zero_method_not_allowed: {
|
|
1468
1544
|
summary: "The request method is not allowed for this Zero endpoint.",
|
|
1469
1545
|
fix: "Call the endpoint with one of the methods declared by the Zero route.",
|
|
1470
1546
|
},
|
|
1547
|
+
zero_migration_failed: {
|
|
1548
|
+
summary: "The Zero database migration command failed during finalize.",
|
|
1549
|
+
fix: "Inspect the migration and database logs, fix the schema issue, and publish again.",
|
|
1550
|
+
},
|
|
1471
1551
|
zero_query_name_missing: {
|
|
1472
1552
|
summary: "A Zero realtime query subscription did not include a query name.",
|
|
1473
1553
|
fix: "Send a query.subscribe frame with a valid query name.",
|
|
@@ -1520,6 +1600,22 @@ export const ERROR_DOCS = {
|
|
|
1520
1600
|
summary: "The Zero endpoint response exceeds the runtime size limit.",
|
|
1521
1601
|
fix: "Return a smaller response body or paginate the data.",
|
|
1522
1602
|
},
|
|
1603
|
+
zero_routes_invalid: {
|
|
1604
|
+
summary: "The Zero routes artifact is invalid.",
|
|
1605
|
+
fix: "Rebuild the generated routes and publish the version again.",
|
|
1606
|
+
},
|
|
1607
|
+
zero_run_compile_failed: {
|
|
1608
|
+
summary: "The runtime could not compile a Zero run handler.",
|
|
1609
|
+
fix: "Inspect the compile diagnostics, fix the run-handler source, and publish again.",
|
|
1610
|
+
},
|
|
1611
|
+
zero_run_duplicate: {
|
|
1612
|
+
summary: "Two Zero run handlers declare the same run id.",
|
|
1613
|
+
fix: "Give every run handler a unique run id before publishing again.",
|
|
1614
|
+
},
|
|
1615
|
+
zero_run_invalid: {
|
|
1616
|
+
summary: "A Zero run-handler definition is invalid.",
|
|
1617
|
+
fix: "Fix the run id, source, capabilities, or database metadata and publish again.",
|
|
1618
|
+
},
|
|
1523
1619
|
zero_run_operation_unsupported: {
|
|
1524
1620
|
summary: "The hosted Zero runtime does not support this run operation yet.",
|
|
1525
1621
|
fix: "Use a generated endpoint or deploy a generated run handler for this operation.",
|
|
@@ -1564,6 +1660,22 @@ export const ERROR_DOCS = {
|
|
|
1564
1660
|
summary: "The Zero runner process could not be started.",
|
|
1565
1661
|
fix: "Verify the runner binary is installed and executable in the runtime.",
|
|
1566
1662
|
},
|
|
1663
|
+
zero_runs_invalid: {
|
|
1664
|
+
summary: "The Zero run-handler payload is invalid.",
|
|
1665
|
+
fix: "Send a valid array of generated run handlers and publish again.",
|
|
1666
|
+
},
|
|
1667
|
+
zero_runs_require_runtime_compiler: {
|
|
1668
|
+
summary: "Zero run handlers require the Rust runtime compiler.",
|
|
1669
|
+
fix: "Install the current runtime compiler and finalize the version again.",
|
|
1670
|
+
},
|
|
1671
|
+
zero_runs_too_many: {
|
|
1672
|
+
summary: "The Zero run-handler payload exceeds the supported handler count.",
|
|
1673
|
+
fix: "Reduce the project to at most 128 Zero run handlers, then publish again.",
|
|
1674
|
+
},
|
|
1675
|
+
zero_shopify_unavailable: {
|
|
1676
|
+
summary: "The local Zero Shopify service is not configured.",
|
|
1677
|
+
fix: "Configure a Shopify handler for zero dev before calling the Shopify service.",
|
|
1678
|
+
},
|
|
1567
1679
|
zero_source_compile_failed: {
|
|
1568
1680
|
summary: "The Zero endpoint source fallback failed to compile.",
|
|
1569
1681
|
fix: "Fix the endpoint source and rebuild bytecode.",
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const INFRA_SLUG_BLOCKLIST: readonly ["www", "www1", "www2", "www3", "web", "mail", "webmail", "smtp", "imap", "pop", "mx", "mx1", "mx2", "ns", "ns1", "ns2", "ns3", "ns4", "dns", "ftp", "ssh", "vpn", "git", "svn", "cdn", "assets", "static", "img", "images", "files", "media", "local", "localhost", "host", "server", "remote", "portal", "owa", "exchange", "gov", "nic", "whois"];
|
|
2
|
+
export declare const PRODUCT_PLATFORM_SLUG_BLOCKLIST: readonly ["spacefast", "stattic", "viewfast", "view-fast", "sf", "my", "api", "app", "apps", "admin", "superadmin", "dashboard", "console", "login", "logout", "auth", "oauth", "sso", "signup", "signin", "register", "claim", "billing", "checkout", "mcp", "cli", "sdk", "cast", "preview", "previews", "internal", "ops", "handbook"];
|
|
3
|
+
export declare const TRADEMARK_EXACT_SLUG_BLOCKLIST: readonly ["automattic", "wordpress", "woocommerce", "jetpack", "wpcloud", "google", "facebook", "instagram", "twitter", "x", "tiktok", "youtube", "amazon", "apple", "microsoft", "netflix", "paypal", "stripe", "visa", "mastercard", "coinbase", "metamask", "openai", "anthropic", "claude", "github", "gitlab", "vercel", "netlify", "cloudflare"];
|
|
4
|
+
export declare const SUBSTRING_SLUG_BLOCKLIST: readonly ["spacefast", "stattic", "automattic", "wordpress", "paypal"];
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Infrastructure hostnames reserved by WordPress.com managed-subdomain policy.
|
|
2
|
+
export const INFRA_SLUG_BLOCKLIST = [
|
|
3
|
+
"www",
|
|
4
|
+
"www1",
|
|
5
|
+
"www2",
|
|
6
|
+
"www3",
|
|
7
|
+
"web",
|
|
8
|
+
"mail",
|
|
9
|
+
"webmail",
|
|
10
|
+
"smtp",
|
|
11
|
+
"imap",
|
|
12
|
+
"pop",
|
|
13
|
+
"mx",
|
|
14
|
+
"mx1",
|
|
15
|
+
"mx2",
|
|
16
|
+
"ns",
|
|
17
|
+
"ns1",
|
|
18
|
+
"ns2",
|
|
19
|
+
"ns3",
|
|
20
|
+
"ns4",
|
|
21
|
+
"dns",
|
|
22
|
+
"ftp",
|
|
23
|
+
"ssh",
|
|
24
|
+
"vpn",
|
|
25
|
+
"git",
|
|
26
|
+
"svn",
|
|
27
|
+
"cdn",
|
|
28
|
+
"assets",
|
|
29
|
+
"static",
|
|
30
|
+
"img",
|
|
31
|
+
"images",
|
|
32
|
+
"files",
|
|
33
|
+
"media",
|
|
34
|
+
"local",
|
|
35
|
+
"localhost",
|
|
36
|
+
"host",
|
|
37
|
+
"server",
|
|
38
|
+
"remote",
|
|
39
|
+
"portal",
|
|
40
|
+
"owa",
|
|
41
|
+
"exchange",
|
|
42
|
+
"gov",
|
|
43
|
+
"nic",
|
|
44
|
+
"whois",
|
|
45
|
+
];
|
|
46
|
+
// Spacefast product, platform, and public surface names.
|
|
47
|
+
export const PRODUCT_PLATFORM_SLUG_BLOCKLIST = [
|
|
48
|
+
"spacefast",
|
|
49
|
+
"stattic",
|
|
50
|
+
"viewfast",
|
|
51
|
+
"view-fast",
|
|
52
|
+
"sf",
|
|
53
|
+
"my",
|
|
54
|
+
"api",
|
|
55
|
+
"app",
|
|
56
|
+
"apps",
|
|
57
|
+
"admin",
|
|
58
|
+
"superadmin",
|
|
59
|
+
"dashboard",
|
|
60
|
+
"console",
|
|
61
|
+
"login",
|
|
62
|
+
"logout",
|
|
63
|
+
"auth",
|
|
64
|
+
"oauth",
|
|
65
|
+
"sso",
|
|
66
|
+
"signup",
|
|
67
|
+
"signin",
|
|
68
|
+
"register",
|
|
69
|
+
"claim",
|
|
70
|
+
"billing",
|
|
71
|
+
"checkout",
|
|
72
|
+
"mcp",
|
|
73
|
+
"cli",
|
|
74
|
+
"sdk",
|
|
75
|
+
"cast",
|
|
76
|
+
"preview",
|
|
77
|
+
"previews",
|
|
78
|
+
"internal",
|
|
79
|
+
"ops",
|
|
80
|
+
"handbook",
|
|
81
|
+
];
|
|
82
|
+
// Exact trademark and impersonation names reserved in every managed namespace.
|
|
83
|
+
export const TRADEMARK_EXACT_SLUG_BLOCKLIST = [
|
|
84
|
+
"automattic",
|
|
85
|
+
"wordpress",
|
|
86
|
+
"woocommerce",
|
|
87
|
+
"jetpack",
|
|
88
|
+
"wpcloud",
|
|
89
|
+
"google",
|
|
90
|
+
"facebook",
|
|
91
|
+
"instagram",
|
|
92
|
+
"twitter",
|
|
93
|
+
"x",
|
|
94
|
+
"tiktok",
|
|
95
|
+
"youtube",
|
|
96
|
+
"amazon",
|
|
97
|
+
"apple",
|
|
98
|
+
"microsoft",
|
|
99
|
+
"netflix",
|
|
100
|
+
"paypal",
|
|
101
|
+
"stripe",
|
|
102
|
+
"visa",
|
|
103
|
+
"mastercard",
|
|
104
|
+
"coinbase",
|
|
105
|
+
"metamask",
|
|
106
|
+
"openai",
|
|
107
|
+
"anthropic",
|
|
108
|
+
"claude",
|
|
109
|
+
"github",
|
|
110
|
+
"gitlab",
|
|
111
|
+
"vercel",
|
|
112
|
+
"netlify",
|
|
113
|
+
"cloudflare",
|
|
114
|
+
];
|
|
115
|
+
// Minimal substring rules for high-risk product and impersonation strings.
|
|
116
|
+
export const SUBSTRING_SLUG_BLOCKLIST = [
|
|
117
|
+
"spacefast",
|
|
118
|
+
"stattic",
|
|
119
|
+
"automattic",
|
|
120
|
+
"wordpress",
|
|
121
|
+
"paypal",
|
|
122
|
+
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { INFRA_SLUG_BLOCKLIST, PRODUCT_PLATFORM_SLUG_BLOCKLIST, SUBSTRING_SLUG_BLOCKLIST, TRADEMARK_EXACT_SLUG_BLOCKLIST, } from "./blocklist.js";
|
|
2
|
+
export type SlugPolicyOptions = {
|
|
3
|
+
maxLength?: number;
|
|
4
|
+
};
|
|
5
|
+
export type SlugPolicyVerdict = {
|
|
6
|
+
ok: true;
|
|
7
|
+
normalized: string;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
reason: "slug_invalid" | "slug_reserved";
|
|
11
|
+
detail: string;
|
|
12
|
+
};
|
|
13
|
+
export declare const SLUG_MIN_LENGTH = 3;
|
|
14
|
+
export declare const TEAM_SLUG_MAX_LENGTH = 63;
|
|
15
|
+
export declare const SPACE_SLUG_MAX_LENGTH = 55;
|
|
16
|
+
export declare function evaluateSlugCandidate(input: string, options?: SlugPolicyOptions): SlugPolicyVerdict;
|
|
17
|
+
export declare function slugPolicyFailureSurvivesSuffix(normalized: string): boolean;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { MANAGEMENT_LABEL_PREFIX } from "../config/domains.js";
|
|
2
|
+
import { INFRA_SLUG_BLOCKLIST, PRODUCT_PLATFORM_SLUG_BLOCKLIST, SUBSTRING_SLUG_BLOCKLIST, TRADEMARK_EXACT_SLUG_BLOCKLIST, } from "./blocklist.js";
|
|
3
|
+
export { INFRA_SLUG_BLOCKLIST, PRODUCT_PLATFORM_SLUG_BLOCKLIST, SUBSTRING_SLUG_BLOCKLIST, TRADEMARK_EXACT_SLUG_BLOCKLIST, } from "./blocklist.js";
|
|
4
|
+
export const SLUG_MIN_LENGTH = 3;
|
|
5
|
+
export const TEAM_SLUG_MAX_LENGTH = 63;
|
|
6
|
+
export const SPACE_SLUG_MAX_LENGTH = 55;
|
|
7
|
+
const infraReservedSlugs = new Set(INFRA_SLUG_BLOCKLIST);
|
|
8
|
+
const productPlatformReservedSlugs = new Set(PRODUCT_PLATFORM_SLUG_BLOCKLIST);
|
|
9
|
+
const trademarkExactReservedSlugs = new Set(TRADEMARK_EXACT_SLUG_BLOCKLIST);
|
|
10
|
+
export function evaluateSlugCandidate(input, options = {}) {
|
|
11
|
+
const maxLength = options.maxLength ?? TEAM_SLUG_MAX_LENGTH;
|
|
12
|
+
// Explicit user choices are rejected rather than silently reshaped. Case,
|
|
13
|
+
// surrounding whitespace, and diacritics are the only accepted canonicalization.
|
|
14
|
+
const casefolded = input
|
|
15
|
+
.trim()
|
|
16
|
+
.normalize("NFD")
|
|
17
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
18
|
+
.toLowerCase();
|
|
19
|
+
if (!/^[a-z0-9-]+$/.test(casefolded)) {
|
|
20
|
+
return invalid("Slug may only contain lowercase letters, numbers, and dashes.");
|
|
21
|
+
}
|
|
22
|
+
const structuralCandidate = casefolded;
|
|
23
|
+
if (structuralCandidate.includes("--")) {
|
|
24
|
+
return invalid("Slug cannot contain -- because it is the version/branch hostname separator.");
|
|
25
|
+
}
|
|
26
|
+
const normalized = casefolded.replace(/^-+|-+$/g, "");
|
|
27
|
+
const structuralVerdict = evaluateStructuralRules(normalized, structuralCandidate, maxLength);
|
|
28
|
+
if (!structuralVerdict.ok) {
|
|
29
|
+
return structuralVerdict;
|
|
30
|
+
}
|
|
31
|
+
const reservedVerdict = evaluateReservedRules(normalized);
|
|
32
|
+
if (!reservedVerdict.ok) {
|
|
33
|
+
return reservedVerdict;
|
|
34
|
+
}
|
|
35
|
+
return { ok: true, normalized };
|
|
36
|
+
}
|
|
37
|
+
function evaluateStructuralRules(normalized, structuralCandidate, maxLength) {
|
|
38
|
+
if (normalized.length < SLUG_MIN_LENGTH) {
|
|
39
|
+
return invalid(`Slug must be at least ${SLUG_MIN_LENGTH} characters.`);
|
|
40
|
+
}
|
|
41
|
+
if (normalized.length > maxLength) {
|
|
42
|
+
return invalid(`Slug must be ${maxLength} characters or fewer.`);
|
|
43
|
+
}
|
|
44
|
+
if (structuralCandidate.startsWith("-") || structuralCandidate.endsWith("-")) {
|
|
45
|
+
return invalid("Slug cannot start or end with a dash.");
|
|
46
|
+
}
|
|
47
|
+
if (/^[0-9]+$/.test(normalized)) {
|
|
48
|
+
return invalid("Slug cannot be all numeric.");
|
|
49
|
+
}
|
|
50
|
+
if (normalized.startsWith(MANAGEMENT_LABEL_PREFIX)) {
|
|
51
|
+
return reserved(`Slug cannot start with reserved prefix ${MANAGEMENT_LABEL_PREFIX}.`);
|
|
52
|
+
}
|
|
53
|
+
return { ok: true, normalized };
|
|
54
|
+
}
|
|
55
|
+
// A substring reservation survives numeric suffixing (`paypal-2` still
|
|
56
|
+
// contains `paypal`). Suggestion generators use this structured signal to
|
|
57
|
+
// abandon the base instead of exhausting every suffix.
|
|
58
|
+
function matchingReservedSubstring(normalized) {
|
|
59
|
+
return SUBSTRING_SLUG_BLOCKLIST.find((reservedSubstring) => normalized.includes(reservedSubstring));
|
|
60
|
+
}
|
|
61
|
+
export function slugPolicyFailureSurvivesSuffix(normalized) {
|
|
62
|
+
return (matchingReservedSubstring(normalized) !== undefined ||
|
|
63
|
+
normalized.startsWith(MANAGEMENT_LABEL_PREFIX));
|
|
64
|
+
}
|
|
65
|
+
function evaluateReservedRules(normalized) {
|
|
66
|
+
if (infraReservedSlugs.has(normalized)) {
|
|
67
|
+
return reserved("Slug is reserved for infrastructure.");
|
|
68
|
+
}
|
|
69
|
+
if (productPlatformReservedSlugs.has(normalized)) {
|
|
70
|
+
return reserved("Slug is reserved for Spacefast product and platform use.");
|
|
71
|
+
}
|
|
72
|
+
if (trademarkExactReservedSlugs.has(normalized)) {
|
|
73
|
+
return reserved("Slug is reserved for trademark and impersonation protection.");
|
|
74
|
+
}
|
|
75
|
+
const substringRule = matchingReservedSubstring(normalized);
|
|
76
|
+
if (substringRule) {
|
|
77
|
+
return reserved(`Slug contains reserved substring ${substringRule}.`);
|
|
78
|
+
}
|
|
79
|
+
return { ok: true, normalized };
|
|
80
|
+
}
|
|
81
|
+
function invalid(detail) {
|
|
82
|
+
return { ok: false, reason: "slug_invalid", detail };
|
|
83
|
+
}
|
|
84
|
+
function reserved(detail) {
|
|
85
|
+
return { ok: false, reason: "slug_reserved", detail };
|
|
86
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
1
|
export declare function mapConcurrent<T, R>(items: readonly T[], limit: number, mapper: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
2
|
+
export declare function findConcurrent<T>(items: readonly T[], limit: number, predicate: (item: T, index: number) => Promise<boolean>): Promise<T | undefined>;
|
|
@@ -16,3 +16,25 @@ export async function mapConcurrent(items, limit, mapper) {
|
|
|
16
16
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
17
17
|
return results;
|
|
18
18
|
}
|
|
19
|
+
// Runs predicates in bounded batches while preserving Array.find semantics:
|
|
20
|
+
// the earliest matching item wins, and only a rejection before that match is
|
|
21
|
+
// observable. Later batches are never started after a match is found.
|
|
22
|
+
export async function findConcurrent(items, limit, predicate) {
|
|
23
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
24
|
+
throw new Error("Concurrency limit must be a positive integer.");
|
|
25
|
+
}
|
|
26
|
+
for (let offset = 0; offset < items.length; offset += limit) {
|
|
27
|
+
const batch = items.slice(offset, offset + limit);
|
|
28
|
+
// eslint-disable-next-line no-await-in-loop -- batches are sequential so a match stops later work.
|
|
29
|
+
const results = await Promise.allSettled(batch.map((item, index) => predicate(item, offset + index)));
|
|
30
|
+
for (const [index, result] of results.entries()) {
|
|
31
|
+
if (result.status === "rejected") {
|
|
32
|
+
throw result.reason;
|
|
33
|
+
}
|
|
34
|
+
if (result.value) {
|
|
35
|
+
return batch[index];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function canonicalizeEmail(value: string): string;
|
|
@@ -28,6 +28,7 @@ export declare const qk: {
|
|
|
28
28
|
space: (id: string) => readonly ["spaces", string];
|
|
29
29
|
spaceByRef: (teamRef: string, spaceRef: string) => readonly ["spaces", "by-ref", string, string];
|
|
30
30
|
spaceVersions: (id: string) => readonly ["spaces", string, "versions"];
|
|
31
|
+
spaceVersionsHistory: (id: string) => readonly ["spaces", string, "versions", "history"];
|
|
31
32
|
spaceCollaboration: (id: string) => readonly ["spaces", string, "collaboration"];
|
|
32
33
|
spaceCollaborationEmbedConfig: (id: string) => readonly ["spaces", string, "collaboration", "embed-config"];
|
|
33
34
|
spaceAnnotations: (id: string) => readonly ["spaces", string, "annotations"];
|
package/dist/utils/query-keys.js
CHANGED
|
@@ -33,6 +33,10 @@ export const qk = {
|
|
|
33
33
|
space: (id) => ["spaces", id],
|
|
34
34
|
spaceByRef: (teamRef, spaceRef) => ["spaces", "by-ref", teamRef, spaceRef],
|
|
35
35
|
spaceVersions: (id) => ["spaces", id, "versions"],
|
|
36
|
+
// The cursor-paginated history read (distinct cache shape from the plain,
|
|
37
|
+
// capped-at-50 `spaceVersions` list above) — nested under the same prefix so
|
|
38
|
+
// `invalidateQueries({ queryKey: qk.spaceVersions(id) })` still reaches it.
|
|
39
|
+
spaceVersionsHistory: (id) => [...qk.spaceVersions(id), "history"],
|
|
36
40
|
spaceCollaboration: (id) => ["spaces", id, "collaboration"],
|
|
37
41
|
spaceCollaborationEmbedConfig: (id) => ["spaces", id, "collaboration", "embed-config"],
|
|
38
42
|
spaceAnnotations: (id) => ["spaces", id, "annotations"],
|