@spacefast/common 0.0.11 → 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.
Files changed (160) hide show
  1. package/dist/agents/connect-targets.d.ts +4 -4
  2. package/dist/agents/connect-targets.js +3 -3
  3. package/dist/agents/private-key-oauth.d.ts +37 -0
  4. package/dist/agents/private-key-oauth.js +158 -0
  5. package/dist/config/domains.js +1 -1
  6. package/dist/config/index.d.ts +1 -0
  7. package/dist/config/index.js +1 -0
  8. package/dist/config/postgres-budget.d.ts +126 -0
  9. package/dist/config/postgres-budget.js +135 -0
  10. package/dist/config/postgres-budget.json +50 -0
  11. package/dist/contracts/access.d.ts +386 -1183
  12. package/dist/contracts/access.js +240 -1018
  13. package/dist/contracts/activity.d.ts +1 -1
  14. package/dist/contracts/activity.js +11 -19
  15. package/dist/contracts/api-keys.d.ts +69 -45
  16. package/dist/contracts/api-keys.js +3 -0
  17. package/dist/contracts/archives.d.ts +9 -10
  18. package/dist/contracts/archives.js +4 -6
  19. package/dist/contracts/builds.d.ts +8 -8
  20. package/dist/contracts/builds.js +28 -48
  21. package/dist/contracts/channels.js +1 -2
  22. package/dist/contracts/comments.d.ts +102 -93
  23. package/dist/contracts/comments.js +121 -63
  24. package/dist/contracts/common.d.ts +4 -3
  25. package/dist/contracts/common.js +2 -0
  26. package/dist/contracts/countries.js +2 -7
  27. package/dist/contracts/data-source-routes.d.ts +20 -0
  28. package/dist/contracts/data-source-routes.js +15 -0
  29. package/dist/contracts/deployments.d.ts +3 -3
  30. package/dist/contracts/device-auth.d.ts +21 -15
  31. package/dist/contracts/docs.d.ts +5 -5
  32. package/dist/contracts/domains.d.ts +261 -1330
  33. package/dist/contracts/domains.js +39 -301
  34. package/dist/contracts/email-preferences.d.ts +50 -0
  35. package/dist/contracts/email-preferences.js +44 -0
  36. package/dist/contracts/enums.d.ts +3 -5
  37. package/dist/contracts/enums.js +3 -8
  38. package/dist/contracts/error-code-meta.d.ts +99 -33
  39. package/dist/contracts/error-code-meta.js +36 -9
  40. package/dist/contracts/error-codes.d.ts +1 -1
  41. package/dist/contracts/error-codes.js +28 -11
  42. package/dist/contracts/execution.d.ts +212 -0
  43. package/dist/contracts/execution.js +288 -0
  44. package/dist/contracts/feature-lifecycle.d.ts +498 -2
  45. package/dist/contracts/feature-lifecycle.js +539 -2
  46. package/dist/contracts/features.d.ts +184 -74
  47. package/dist/contracts/features.js +4 -303
  48. package/dist/contracts/functions.d.ts +729 -0
  49. package/dist/contracts/functions.js +485 -0
  50. package/dist/contracts/git.d.ts +1 -1
  51. package/dist/contracts/grants.d.ts +1158 -0
  52. package/dist/contracts/grants.js +696 -0
  53. package/dist/contracts/ids.d.ts +6 -5
  54. package/dist/contracts/ids.js +6 -5
  55. package/dist/contracts/internal.d.ts +3 -3
  56. package/dist/contracts/mcp.d.ts +12 -12
  57. package/dist/contracts/me.js +1 -1
  58. package/dist/contracts/mounts.d.ts +142 -0
  59. package/dist/contracts/mounts.js +56 -0
  60. package/dist/contracts/oauth-resources.d.ts +5 -5
  61. package/dist/contracts/oauth-resources.js +2 -0
  62. package/dist/contracts/operations.d.ts +4 -4
  63. package/dist/contracts/pages.d.ts +6 -14
  64. package/dist/contracts/pages.js +11 -23
  65. package/dist/contracts/plan-policy.d.ts +2 -9
  66. package/dist/contracts/plan-policy.js +1 -4
  67. package/dist/contracts/platform.d.ts +80 -32
  68. package/dist/contracts/platform.js +59 -12
  69. package/dist/contracts/push-new.d.ts +26 -2
  70. package/dist/contracts/push-new.js +8 -0
  71. package/dist/contracts/quotas.d.ts +0 -1
  72. package/dist/contracts/quotas.js +0 -5
  73. package/dist/contracts/repository-connections.d.ts +1 -1
  74. package/dist/contracts/resources.d.ts +21 -85
  75. package/dist/contracts/resources.js +17 -47
  76. package/dist/contracts/runtime-api.d.ts +799 -192
  77. package/dist/contracts/runtime-api.js +231 -100
  78. package/dist/contracts/runtime-app.d.ts +138 -0
  79. package/dist/contracts/runtime-app.js +21 -0
  80. package/dist/contracts/sf-config-v1.d.ts +5 -0
  81. package/dist/contracts/sf-config-v1.js +1 -1
  82. package/dist/contracts/sites.d.ts +17 -12
  83. package/dist/contracts/sites.js +30 -12
  84. package/dist/contracts/space-config.d.ts +46 -117
  85. package/dist/contracts/space-config.js +323 -25
  86. package/dist/contracts/spaces.d.ts +392 -256
  87. package/dist/contracts/spaces.js +163 -83
  88. package/dist/contracts/superadmin-emails.d.ts +27 -3
  89. package/dist/contracts/superadmin-emails.js +10 -15
  90. package/dist/contracts/superadmin-runtime.d.ts +2 -2
  91. package/dist/contracts/superadmin-spaces.d.ts +19 -142
  92. package/dist/contracts/superadmin-spaces.js +7 -0
  93. package/dist/contracts/tags.d.ts +66 -36
  94. package/dist/contracts/tags.js +195 -11
  95. package/dist/contracts/teams.d.ts +9 -9
  96. package/dist/contracts/transfers.d.ts +1 -2
  97. package/dist/contracts/variables.d.ts +4 -4
  98. package/dist/contracts/variables.js +4 -3
  99. package/dist/contracts/webhooks.d.ts +2 -2
  100. package/dist/contracts/zero.d.ts +437 -193
  101. package/dist/contracts/zero.js +149 -158
  102. package/dist/dashboard-paths/index.d.ts +3 -3
  103. package/dist/dashboard-paths/index.js +1 -0
  104. package/dist/docs/agent-prose.js +10 -8
  105. package/dist/docs/agent-setup.d.ts +6 -4
  106. package/dist/docs/agent-setup.js +26 -16
  107. package/dist/docs/agent-solutions.js +1 -1
  108. package/dist/docs/catalog.d.ts +35 -37
  109. package/dist/docs/catalog.js +36 -38
  110. package/dist/docs/error-docs.js +108 -44
  111. package/dist/utils/asset-fingerprint.d.ts +11 -0
  112. package/dist/utils/asset-fingerprint.js +46 -0
  113. package/dist/utils/auth-redirect.d.ts +1 -3
  114. package/dist/utils/auth-redirect.js +1 -21
  115. package/dist/utils/build-settings.d.ts +2 -0
  116. package/dist/utils/build-settings.js +61 -9
  117. package/dist/utils/credential-policy.d.ts +1 -1
  118. package/dist/utils/credential-policy.js +4 -0
  119. package/dist/utils/dns-instructions.d.ts +29 -0
  120. package/dist/utils/dns-instructions.js +78 -0
  121. package/dist/utils/domain-diagnostics.d.ts +29 -0
  122. package/dist/utils/domain-diagnostics.js +63 -0
  123. package/dist/utils/egress-policy.fixtures.json +51 -0
  124. package/dist/utils/grants.d.ts +53 -0
  125. package/dist/utils/grants.js +160 -0
  126. package/dist/utils/id-hints.d.ts +1 -0
  127. package/dist/utils/id-hints.js +55 -13
  128. package/dist/utils/local-space-state.js +1 -0
  129. package/dist/utils/page-colors.d.ts +50 -0
  130. package/dist/utils/page-colors.js +167 -0
  131. package/dist/utils/page-fonts.d.ts +2 -0
  132. package/dist/utils/page-fonts.js +4 -0
  133. package/dist/utils/pages.d.ts +19 -6
  134. package/dist/utils/pages.js +122 -49
  135. package/dist/utils/production-runtime.d.ts +18 -0
  136. package/dist/utils/production-runtime.js +28 -0
  137. package/dist/utils/publish-policy.d.ts +34 -4
  138. package/dist/utils/publish-policy.js +132 -61
  139. package/dist/utils/query-keys.d.ts +7 -13
  140. package/dist/utils/query-keys.js +12 -18
  141. package/dist/utils/source-archive-policy.d.ts +2 -0
  142. package/dist/utils/source-archive-policy.js +2 -0
  143. package/dist/utils/space-config.d.ts +5 -1
  144. package/dist/utils/space-config.js +35 -0
  145. package/dist/utils/static-runtime-policy.d.ts +5 -0
  146. package/dist/utils/static-runtime-policy.fixtures.json +57 -0
  147. package/dist/utils/static-runtime-policy.js +54 -10
  148. package/dist/utils/upload-session.d.ts +3 -0
  149. package/dist/utils/upload-session.js +31 -0
  150. package/package.json +6 -4
  151. package/dist/contracts/annotations.d.ts +0 -327
  152. package/dist/contracts/annotations.js +0 -235
  153. package/dist/contracts/generated-feature-launch-entries.d.ts +0 -2
  154. package/dist/contracts/generated-feature-launch-entries.js +0 -518
  155. package/dist/contracts/publishes.d.ts +0 -135
  156. package/dist/contracts/publishes.js +0 -162
  157. package/dist/utils/access-match.d.ts +0 -39
  158. package/dist/utils/access-match.js +0 -296
  159. package/dist/utils/sf-config-v1.d.ts +0 -2
  160. package/dist/utils/sf-config-v1.js +0 -542
@@ -1,162 +0,0 @@
1
- import { z } from "zod";
2
- import { registeredIdSchema } from "./ids.js";
3
- export const publishStates = [
4
- "awaiting_upload",
5
- "uploaded",
6
- "parked",
7
- "materializing",
8
- "succeeded",
9
- "failed",
10
- "cancelled",
11
- "expired",
12
- ];
13
- export const publishStateSchema = z.enum(publishStates);
14
- export const publishIdSchema = registeredIdSchema("pub");
15
- const spaceIdSchema = registeredIdSchema("spc");
16
- const versionIdSchema = registeredIdSchema("ver");
17
- export const publishAdmissionSchema = z.object({
18
- id: publishIdSchema,
19
- idempotencyKey: z.string().min(1),
20
- requestFingerprint: z.string().min(1),
21
- });
22
- const cancellableActionSchema = z.object({
23
- kind: z.literal("cancel_publish"),
24
- publishId: publishIdSchema,
25
- });
26
- export const publishSchema = z.discriminatedUnion("state", [
27
- z
28
- .object({
29
- id: publishIdSchema,
30
- state: z.literal("awaiting_upload"),
31
- target: z
32
- .object({
33
- spaceId: spaceIdSchema,
34
- baseVersionId: versionIdSchema.nullable(),
35
- })
36
- .optional(),
37
- counts: z
38
- .object({
39
- received: z.number().int().nonnegative(),
40
- missing: z.number().int().positive(),
41
- })
42
- .optional(),
43
- expiresAt: z.string().datetime().optional(),
44
- upload: z
45
- .object({
46
- missing: z
47
- .array(z.object({
48
- path: z.string(),
49
- sha256: z
50
- .string()
51
- .regex(/^[0-9a-f]{64}$/)
52
- .nullable(),
53
- size: z.number().int().nonnegative(),
54
- url: z.string().url(),
55
- headers: z.record(z.string(), z.string()),
56
- expiresAt: z.string().datetime(),
57
- }))
58
- .min(1),
59
- })
60
- .optional(),
61
- })
62
- .refine((publish) => publish.counts === undefined ||
63
- publish.upload === undefined ||
64
- publish.counts.missing === publish.upload.missing.length, {
65
- message: "Upload instructions must match the missing upload count.",
66
- path: ["upload", "missing"],
67
- })
68
- .refine((publish) => publish.counts !== undefined || publish.upload !== undefined, {
69
- message: "An awaiting_upload publish must carry counts or upload instructions.",
70
- path: ["counts"],
71
- }),
72
- z.object({ id: publishIdSchema, state: z.literal("uploaded") }),
73
- z
74
- .object({
75
- id: publishIdSchema,
76
- state: z.literal("parked"),
77
- reason: z.literal("capacity"),
78
- queuePosition: z.number().int().positive().optional(),
79
- nextCheckAfterSeconds: z.number().int().positive(),
80
- actions: z.tuple([cancellableActionSchema]),
81
- })
82
- .refine((publish) => publish.actions.every((action) => action.publishId === publish.id), {
83
- message: "Cancel actions must reference the enclosing publish.",
84
- path: ["actions"],
85
- }),
86
- z.object({
87
- id: publishIdSchema,
88
- state: z.literal("materializing"),
89
- operation: z.object({
90
- id: registeredIdSchema("op"),
91
- kind: z.literal("publish.finalize"),
92
- status: z.enum(["queued", "running"]),
93
- }),
94
- }),
95
- z.object({
96
- id: publishIdSchema,
97
- state: z.literal("succeeded"),
98
- outcome: z.enum(["created", "updated", "unchanged"]),
99
- capturedBaseVersionId: versionIdSchema.nullable().optional(),
100
- claim: z
101
- .object({
102
- token: z.string().min(1),
103
- url: z.string().url(),
104
- expiresAt: z.string().datetime(),
105
- })
106
- .optional(),
107
- result: z.object({
108
- space: z.object({ id: spaceIdSchema, liveUrl: z.string().url() }),
109
- version: z.object({ id: versionIdSchema, immutableUrl: z.string().url() }),
110
- live: z.object({
111
- previousVersionId: versionIdSchema.nullable(),
112
- versionId: versionIdSchema,
113
- }),
114
- }),
115
- }),
116
- z.object({ id: publishIdSchema, state: z.literal("failed"), errorCode: z.string().min(1) }),
117
- z.object({ id: publishIdSchema, state: z.literal("cancelled") }),
118
- z.object({ id: publishIdSchema, state: z.literal("expired") }),
119
- ]);
120
- // Cancellation may win during materialization only before the Version exists;
121
- // afterwards it is not cancelable and the outcome is unchanged.
122
- const allowedTransitions = {
123
- awaiting_upload: ["uploaded", "cancelled", "expired"],
124
- uploaded: ["parked", "materializing", "cancelled", "expired"],
125
- parked: ["materializing", "cancelled", "expired"],
126
- materializing: ["succeeded", "failed", "cancelled", "expired"],
127
- succeeded: [],
128
- failed: [],
129
- cancelled: [],
130
- expired: [],
131
- };
132
- export function canTransitionPublish(from, to) {
133
- return allowedTransitions[from].some((candidate) => candidate === to);
134
- }
135
- export function transitionPublish(publish, next) {
136
- if (publish.id !== next.id ||
137
- publish.idempotencyKey !== next.idempotencyKey ||
138
- publish.requestFingerprint !== next.requestFingerprint) {
139
- throw new Error("A publish transition cannot change the durable publish identity.");
140
- }
141
- if (!canTransitionPublish(publish.state, next.state)) {
142
- throw new Error(`Invalid publish transition: ${publish.state} -> ${next.state}`);
143
- }
144
- // Project through the wire schema so fields from the previous variant (e.g. a
145
- // parked `reason` spread into a materializing publish) never leak downstream.
146
- return {
147
- ...publishSchema.parse(next),
148
- idempotencyKey: next.idempotencyKey,
149
- requestFingerprint: next.requestFingerprint,
150
- };
151
- }
152
- export function convergePublishAdmission(existing, attempted) {
153
- if (existing === undefined)
154
- return { outcome: "created", publish: attempted };
155
- if (existing.idempotencyKey !== attempted.idempotencyKey) {
156
- throw new Error("Admission convergence requires one idempotency key.");
157
- }
158
- if (existing.requestFingerprint !== attempted.requestFingerprint) {
159
- return { outcome: "conflict", publish: existing };
160
- }
161
- return { outcome: "replayed", publish: existing };
162
- }
@@ -1,39 +0,0 @@
1
- export declare function filterGrantsByNamespace(grants: string[], namespaces: string[]): string[];
2
- export declare function grantGlobMatches(pattern: string, grant: string): boolean;
3
- export declare function grantsIntersect(grants: string[], requiredGrants: string[]): boolean;
4
- export declare function grantsSatisfyRule(input: {
5
- grants: string[];
6
- requiredGrants: string[];
7
- grantNamespaces: string[];
8
- }): boolean;
9
- export declare function canonicalizeHost(host: string): string;
10
- export declare function hostGlobMatches(pattern: string, host: string): boolean;
11
- export declare function hostTemplateMatches(template: string, host: string): boolean;
12
- export declare function pathGlobMatches(pattern: string, path: string): boolean;
13
- export declare function agentMatches(needle: string, agent: string): boolean;
14
- export declare function ipInCidr(ip: string, cidr: string): boolean;
15
- export declare function ipInAnyCidr(ip: string, cidrs: string[]): boolean;
16
- export type RuleMatchContext = {
17
- host: string;
18
- path: string;
19
- channel?: string | undefined;
20
- ip?: string | undefined;
21
- agent?: string | undefined;
22
- country?: string | undefined;
23
- headers?: Record<string, string> | undefined;
24
- };
25
- export type RuleMatchInput = {
26
- host?: string | undefined;
27
- hostPattern?: string | undefined;
28
- hostTemplate?: string | undefined;
29
- pathPattern?: string | undefined;
30
- channel?: string | undefined;
31
- ipCidrs?: string[] | undefined;
32
- agent?: string | undefined;
33
- country?: string | undefined;
34
- header?: {
35
- name: string;
36
- value: string;
37
- } | undefined;
38
- };
39
- export declare function ruleMatchApplies(match: RuleMatchInput, context: RuleMatchContext): boolean;
@@ -1,296 +0,0 @@
1
- // The ONE TS implementation of the runtime's grant-satisfaction kernel
2
- // (internal-docs/access-plan.html X-9): glob-aware `grants ∩ requiredGrants`
3
- // intersection plus signing-key grant-namespace filtering. The PHP evaluator
4
- // (runtime/engine/runtime/access-rules.php `_stattic_unified_grants_intersect`
5
- // / `_stattic_unified_filter_grants_by_namespace`) is the enforcer of record;
6
- // this matcher exists so the cloud's authorize pre-flight (and, later,
7
- // `access simulate`) never mints a token the runtime would reject.
8
- //
9
- // Parity is pinned by a shared JSON corpus (`access-match.fixtures.json`),
10
- // asserted against this module in packages/common and against the PHP in the
11
- // control plane's php-policy-parity test — the one sanctioned
12
- // generated↔source drift-guard pattern. Do not change semantics here without
13
- // extending the corpus.
14
- // Drops every grant not prefixed by one of the signing key's namespaces. This
15
- // bounds a leaked external key's blast radius to exactly the grant namespaces
16
- // the owner pointed it at (and bounds the runtime-local pw: key to `pw:`).
17
- export function filterGrantsByNamespace(grants, namespaces) {
18
- const kept = [];
19
- for (const grant of grants) {
20
- if (typeof grant !== "string" || grant === "") {
21
- continue;
22
- }
23
- for (const namespace of namespaces) {
24
- if (typeof namespace === "string" && namespace !== "" && grant.startsWith(namespace)) {
25
- kept.push(grant);
26
- break;
27
- }
28
- }
29
- }
30
- return kept;
31
- }
32
- // `*` in a requiredGrant matches any run of characters (grants have no path
33
- // structure to anchor on, unlike paths). Exact match when no `*` present.
34
- export function grantGlobMatches(pattern, grant) {
35
- if (!pattern.includes("*")) {
36
- return pattern === grant;
37
- }
38
- const regex = pattern
39
- .split("*")
40
- .map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
41
- .join(".*");
42
- return new RegExp(`^${regex}$`).test(grant);
43
- }
44
- // Glob-aware intersection: a visitor passes when ANY verified concrete grant
45
- // matches ANY requiredGrant. requiredGrants may carry `*` wildcards (e.g.
46
- // "email:*@acme.com"); verified grants are concrete.
47
- export function grantsIntersect(grants, requiredGrants) {
48
- for (const required of requiredGrants) {
49
- if (typeof required !== "string" || required === "") {
50
- continue;
51
- }
52
- for (const grant of grants) {
53
- if (typeof grant !== "string" || grant === "") {
54
- continue;
55
- }
56
- if (grantGlobMatches(required, grant)) {
57
- return true;
58
- }
59
- }
60
- }
61
- return false;
62
- }
63
- // The composed satisfaction test the authorize pre-flight runs: namespace-drop
64
- // the visitor's grants by the signing key, then intersect with the rule's
65
- // requiredGrants — exactly what the runtime does after verifying a signature.
66
- export function grantsSatisfyRule(input) {
67
- return grantsIntersect(filterGrantsByNamespace(input.grants, input.grantNamespaces), input.requiredGrants);
68
- }
69
- // ---------------------------------------------------------------------------
70
- // Rule-match evaluation (the `match` half of the kernel)
71
- // ---------------------------------------------------------------------------
72
- //
73
- // TS mirror of the PHP `_stattic_unified_rule_matches` /
74
- // `_stattic_unified_path_glob_matches` / `_stattic_unified_ip_in_cidr` family
75
- // (runtime/engine/runtime/access-rules.php). Powers `access simulate` and the
76
- // authorize pre-flight's path scoping. Parity is pinned by the same shared
77
- // corpus (`access-match.fixtures.json`, sections `pathGlob` + `cidr`) asserted
78
- // against the PHP in php-policy-parity.test.ts.
79
- // Mirrors `_spacefast_canonicalize_host`.
80
- export function canonicalizeHost(host) {
81
- return host.trim().toLowerCase().replace(/\.+$/, "");
82
- }
83
- export function hostGlobMatches(pattern, host) {
84
- const regex = pattern
85
- .split("*")
86
- .map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
87
- .join(".*");
88
- return new RegExp(`^${regex}$`, "i").test(canonicalizeHost(host));
89
- }
90
- export function hostTemplateMatches(template, host) {
91
- let regex = "";
92
- let index = 0;
93
- while (index < template.length) {
94
- const char = template[index];
95
- if (char === "*") {
96
- regex += ".*";
97
- index += 1;
98
- continue;
99
- }
100
- if (char === ":") {
101
- const name = template.slice(index + 1).match(/^[A-Za-z][A-Za-z0-9_]*/)?.[0] ?? "";
102
- if (!name) {
103
- return false;
104
- }
105
- regex += "[^.]+";
106
- index += name.length + 1;
107
- continue;
108
- }
109
- regex += char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
110
- index += 1;
111
- }
112
- return new RegExp(`^${regex}$`, "i").test(canonicalizeHost(host));
113
- }
114
- // Glob path matcher: "/docs/**" (any depth), "/docs/*" (one segment), exact
115
- // otherwise. Empty pattern matches everything.
116
- export function pathGlobMatches(pattern, path) {
117
- if (pattern === "") {
118
- return true;
119
- }
120
- if (!pattern.includes("*")) {
121
- return pattern === path;
122
- }
123
- let regex = "";
124
- let i = 0;
125
- while (i < pattern.length) {
126
- const char = pattern[i];
127
- if (char === "*") {
128
- if (pattern[i + 1] === "*") {
129
- regex += ".*";
130
- i += 2;
131
- continue;
132
- }
133
- regex += "[^/]*";
134
- i += 1;
135
- continue;
136
- }
137
- regex += char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
138
- i += 1;
139
- }
140
- return new RegExp(`^${regex}$`).test(path);
141
- }
142
- // Case-insensitive substring match on the user agent; empty needle matches.
143
- export function agentMatches(needle, agent) {
144
- if (needle === "") {
145
- return true;
146
- }
147
- return agent.toLowerCase().includes(needle.toLowerCase());
148
- }
149
- // inet_pton equivalent: dotted-quad IPv4 or RFC 4291 IPv6 (including "::"
150
- // compression and an embedded IPv4 tail) → bytes, or null when unparseable.
151
- function ipToBytes(ip) {
152
- if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
153
- const parts = ip.split(".").map(Number);
154
- if (parts.some((part) => part > 255)) {
155
- return null;
156
- }
157
- return Uint8Array.from(parts);
158
- }
159
- if (!ip.includes(":")) {
160
- return null;
161
- }
162
- let head = ip;
163
- let v4Tail = null;
164
- const lastColon = ip.lastIndexOf(":");
165
- const tail = ip.slice(lastColon + 1);
166
- if (tail.includes(".")) {
167
- const tailBytes = ipToBytes(tail);
168
- if (!tailBytes || tailBytes.length !== 4) {
169
- return null;
170
- }
171
- v4Tail = [...tailBytes];
172
- head = ip.slice(0, lastColon + 1) + "0:0"; // placeholder groups, replaced below
173
- }
174
- const doubleColonSplits = head.split("::");
175
- if (doubleColonSplits.length > 2) {
176
- return null;
177
- }
178
- const parseGroups = (raw) => {
179
- if (raw === "") {
180
- return [];
181
- }
182
- const groups = [];
183
- for (const part of raw.split(":")) {
184
- if (!/^[0-9a-fA-F]{1,4}$/.test(part)) {
185
- return null;
186
- }
187
- groups.push(Number.parseInt(part, 16));
188
- }
189
- return groups;
190
- };
191
- let groups;
192
- if (doubleColonSplits.length === 2) {
193
- const left = parseGroups(doubleColonSplits[0] ?? "");
194
- const right = parseGroups(doubleColonSplits[1] ?? "");
195
- if (left === null || right === null || left.length + right.length > 8) {
196
- return null;
197
- }
198
- groups = [...left, ...Array(8 - left.length - right.length).fill(0), ...right];
199
- }
200
- else {
201
- groups = parseGroups(head);
202
- if (groups === null || groups.length !== 8) {
203
- return null;
204
- }
205
- }
206
- const bytes = new Uint8Array(16);
207
- groups.forEach((group, index) => {
208
- bytes[index * 2] = group >> 8;
209
- bytes[index * 2 + 1] = group & 0xff;
210
- });
211
- if (v4Tail) {
212
- bytes[12] = v4Tail[0];
213
- bytes[13] = v4Tail[1];
214
- bytes[14] = v4Tail[2];
215
- bytes[15] = v4Tail[3];
216
- }
217
- return bytes;
218
- }
219
- // Mirrors `_stattic_unified_ip_in_cidr`: a bare address (no "/") is an exact
220
- // string compare; mixed families never match; malformed input never matches.
221
- export function ipInCidr(ip, cidr) {
222
- if (cidr === "") {
223
- return false;
224
- }
225
- if (!cidr.includes("/")) {
226
- return ip === cidr;
227
- }
228
- const [subnet = "", bitsRaw = ""] = cidr.split("/", 2);
229
- const ipBytes = ipToBytes(ip);
230
- const subnetBytes = ipToBytes(subnet);
231
- if (!ipBytes || !subnetBytes || ipBytes.length !== subnetBytes.length) {
232
- return false;
233
- }
234
- if (!/^\d+$/.test(bitsRaw)) {
235
- return false;
236
- }
237
- const bits = Number.parseInt(bitsRaw, 10);
238
- const maxBits = ipBytes.length * 8;
239
- if (bits < 0 || bits > maxBits) {
240
- return false;
241
- }
242
- const fullBytes = Math.floor(bits / 8);
243
- for (let index = 0; index < fullBytes; index += 1) {
244
- if (ipBytes[index] !== subnetBytes[index]) {
245
- return false;
246
- }
247
- }
248
- const remainder = bits % 8;
249
- if (remainder === 0) {
250
- return true;
251
- }
252
- const mask = (0xff << (8 - remainder)) & 0xff;
253
- return (ipBytes[fullBytes] & mask) === (subnetBytes[fullBytes] & mask);
254
- }
255
- export function ipInAnyCidr(ip, cidrs) {
256
- if (ip === "") {
257
- return false;
258
- }
259
- return cidrs.some((cidr) => typeof cidr === "string" && ipInCidr(ip, cidr));
260
- }
261
- // AND-combined match — mirrors `_stattic_unified_rule_matches` exactly. An
262
- // empty match matches everything in scope.
263
- export function ruleMatchApplies(match, context) {
264
- if (match.host !== undefined && canonicalizeHost(match.host) !== canonicalizeHost(context.host)) {
265
- return false;
266
- }
267
- if (match.hostPattern !== undefined && !hostGlobMatches(match.hostPattern, context.host)) {
268
- return false;
269
- }
270
- if (match.hostTemplate !== undefined && !hostTemplateMatches(match.hostTemplate, context.host)) {
271
- return false;
272
- }
273
- if (match.pathPattern !== undefined && !pathGlobMatches(match.pathPattern, context.path)) {
274
- return false;
275
- }
276
- if (match.channel !== undefined && match.channel !== (context.channel ?? "")) {
277
- return false;
278
- }
279
- if (match.ipCidrs !== undefined && !ipInAnyCidr(context.ip ?? "", match.ipCidrs)) {
280
- return false;
281
- }
282
- if (match.agent !== undefined && !agentMatches(match.agent, context.agent ?? "")) {
283
- return false;
284
- }
285
- if (match.country !== undefined && match.country.toUpperCase() !== (context.country ?? "")) {
286
- return false;
287
- }
288
- if (match.header !== undefined) {
289
- const name = match.header.name.toLowerCase();
290
- const actual = Object.entries(context.headers ?? {}).find(([key]) => key.toLowerCase() === name)?.[1];
291
- if (match.header.name === "" || (actual ?? "") !== match.header.value) {
292
- return false;
293
- }
294
- }
295
- return true;
296
- }
@@ -1,2 +0,0 @@
1
- import { type CompileSfConfigV1Options, type SfConfigV1CompileResult } from "../contracts/sf-config-v1.js";
2
- export declare function compileSfConfigV1(source: string, options?: CompileSfConfigV1Options): SfConfigV1CompileResult;