@spacefast/common 0.0.5 → 0.0.7

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 (183) hide show
  1. package/brand-assets/spacefast-favicon.svg +16 -0
  2. package/brand-assets/spacefast-sf-full-bleed.svg +13 -0
  3. package/brand-assets/spacefast-wordmark.svg +5 -0
  4. package/dist/agents/connect-targets.d.ts +138 -0
  5. package/dist/agents/connect-targets.js +243 -0
  6. package/dist/brand-assets-build.d.ts +26 -0
  7. package/dist/brand-assets-build.js +81 -0
  8. package/dist/brand-assets.d.ts +42 -0
  9. package/dist/brand-assets.js +30 -0
  10. package/dist/brand.d.ts +1 -0
  11. package/dist/brand.js +5 -0
  12. package/dist/config/domains.d.ts +12 -6
  13. package/dist/config/domains.js +30 -38
  14. package/dist/contracts/access.d.ts +967 -124
  15. package/dist/contracts/access.js +804 -148
  16. package/dist/contracts/activity.d.ts +1 -1
  17. package/dist/contracts/activity.js +28 -2
  18. package/dist/contracts/annotations.d.ts +2 -4
  19. package/dist/contracts/api-keys.d.ts +126 -4
  20. package/dist/contracts/api-keys.js +74 -6
  21. package/dist/contracts/archives.d.ts +8 -2
  22. package/dist/contracts/archives.js +11 -4
  23. package/dist/contracts/billing.d.ts +0 -9
  24. package/dist/contracts/builds.d.ts +65 -21
  25. package/dist/contracts/builds.js +29 -0
  26. package/dist/contracts/common.d.ts +2 -0
  27. package/dist/contracts/common.js +4 -0
  28. package/dist/contracts/continuation.d.ts +0 -3
  29. package/dist/contracts/countries.d.ts +2 -0
  30. package/dist/contracts/countries.js +261 -0
  31. package/dist/contracts/deployments.d.ts +34 -34
  32. package/dist/contracts/device-auth.d.ts +4 -11
  33. package/dist/contracts/device-auth.js +4 -0
  34. package/dist/contracts/docs.d.ts +119 -0
  35. package/dist/contracts/docs.js +51 -0
  36. package/dist/contracts/domains.d.ts +96 -54
  37. package/dist/contracts/domains.js +26 -4
  38. package/dist/contracts/enums.d.ts +15 -16
  39. package/dist/contracts/enums.js +26 -14
  40. package/dist/contracts/error-code-meta.d.ts +184 -0
  41. package/dist/contracts/error-code-meta.js +124 -10
  42. package/dist/contracts/error-codes.d.ts +1 -1
  43. package/dist/contracts/error-codes.js +42 -0
  44. package/dist/contracts/events.d.ts +1 -1
  45. package/dist/contracts/feature-lifecycle-core.d.ts +36 -0
  46. package/dist/contracts/feature-lifecycle-core.js +220 -0
  47. package/dist/contracts/feature-lifecycle.d.ts +7 -0
  48. package/dist/contracts/feature-lifecycle.js +31 -0
  49. package/dist/contracts/features.d.ts +76 -6
  50. package/dist/contracts/features.js +178 -13
  51. package/dist/contracts/generated-feature-launch-entries.d.ts +2 -0
  52. package/dist/contracts/generated-feature-launch-entries.js +518 -0
  53. package/dist/contracts/git.d.ts +2 -8
  54. package/dist/contracts/ids.d.ts +9 -0
  55. package/dist/contracts/ids.js +13 -0
  56. package/dist/contracts/mcp.d.ts +8 -10
  57. package/dist/contracts/me.js +1 -1
  58. package/dist/contracts/oauth-resources.d.ts +19 -8
  59. package/dist/contracts/oauth-resources.js +44 -13
  60. package/dist/contracts/operations.d.ts +161 -1
  61. package/dist/contracts/operations.js +49 -1
  62. package/dist/contracts/platform.d.ts +6 -102
  63. package/dist/contracts/platform.js +6 -50
  64. package/dist/contracts/publishes.d.ts +130 -0
  65. package/dist/contracts/publishes.js +145 -0
  66. package/dist/contracts/push-new.d.ts +4 -4
  67. package/dist/contracts/quotas.d.ts +2 -0
  68. package/dist/contracts/quotas.js +8 -0
  69. package/dist/contracts/repository-connections.d.ts +9 -8
  70. package/dist/contracts/repository-connections.js +13 -0
  71. package/dist/contracts/resources.d.ts +99 -13
  72. package/dist/contracts/resources.js +36 -4
  73. package/dist/contracts/routes.d.ts +0 -2
  74. package/dist/contracts/runtime-api.d.ts +363 -39
  75. package/dist/contracts/runtime-api.js +276 -6
  76. package/dist/contracts/sf-config-v1.d.ts +75 -0
  77. package/dist/contracts/sf-config-v1.js +6 -0
  78. package/dist/contracts/sites.d.ts +19 -2
  79. package/dist/contracts/sites.js +21 -0
  80. package/dist/contracts/space-config.d.ts +201 -2
  81. package/dist/contracts/space-config.js +135 -5
  82. package/dist/contracts/spaces.d.ts +376 -196
  83. package/dist/contracts/spaces.js +62 -10
  84. package/dist/contracts/superadmin-emails.d.ts +4 -2
  85. package/dist/contracts/superadmin-emails.js +8 -0
  86. package/dist/contracts/superadmin-queues.d.ts +26 -26
  87. package/dist/contracts/superadmin-spaces.d.ts +172 -42
  88. package/dist/contracts/superadmin-tenants.d.ts +6 -6
  89. package/dist/contracts/superadmin-tenants.js +6 -11
  90. package/dist/contracts/superadmin.d.ts +6 -93
  91. package/dist/contracts/superadmin.js +6 -14
  92. package/dist/contracts/tags.d.ts +10 -9
  93. package/dist/contracts/tags.js +2 -1
  94. package/dist/contracts/teams.d.ts +26 -30
  95. package/dist/contracts/teams.js +11 -17
  96. package/dist/contracts/theme-json.d.ts +30 -0
  97. package/dist/contracts/theme-json.js +48 -0
  98. package/dist/contracts/variables.d.ts +0 -2
  99. package/dist/contracts/variables.js +0 -5
  100. package/dist/contracts/webhooks.d.ts +2 -0
  101. package/dist/contracts/webhooks.js +1 -1
  102. package/dist/contracts/zero.d.ts +5 -10
  103. package/dist/docs/agent-prose.d.ts +43 -3
  104. package/dist/docs/agent-prose.js +146 -7
  105. package/dist/docs/agent-setup.d.ts +51 -0
  106. package/dist/docs/agent-setup.js +211 -14
  107. package/dist/docs/agent-solutions.d.ts +5 -5
  108. package/dist/docs/agent-solutions.js +2 -2
  109. package/dist/docs/catalog.d.ts +650 -0
  110. package/dist/docs/catalog.js +758 -0
  111. package/dist/docs/error-docs.d.ts +0 -2
  112. package/dist/docs/error-docs.js +189 -35
  113. package/dist/docs/index-build.d.ts +17 -0
  114. package/dist/docs/index-build.js +24 -0
  115. package/dist/docs/search.d.ts +54 -0
  116. package/dist/docs/search.js +96 -0
  117. package/dist/docs/skill-distribution.d.ts +5 -3
  118. package/dist/docs/skill-distribution.js +8 -8
  119. package/dist/slug-policy/blocklist.d.ts +4 -0
  120. package/dist/slug-policy/blocklist.js +122 -0
  121. package/dist/slug-policy/index.d.ts +17 -0
  122. package/dist/slug-policy/index.js +86 -0
  123. package/dist/utils/access-match.d.ts +39 -0
  124. package/dist/utils/access-match.js +296 -0
  125. package/dist/utils/auth-redirect.d.ts +3 -1
  126. package/dist/utils/auth-redirect.js +21 -1
  127. package/dist/utils/build-settings.d.ts +6 -0
  128. package/dist/utils/build-settings.js +429 -46
  129. package/dist/utils/claim-token.d.ts +8 -0
  130. package/dist/utils/claim-token.js +23 -2
  131. package/dist/utils/concurrency.d.ts +1 -0
  132. package/dist/utils/concurrency.js +22 -0
  133. package/dist/utils/content-type.d.ts +1 -0
  134. package/dist/utils/content-type.js +1 -1
  135. package/dist/utils/credential-policy.d.ts +54 -0
  136. package/dist/utils/{access-policy.js → credential-policy.js} +28 -2
  137. package/dist/utils/email.d.ts +1 -0
  138. package/dist/utils/email.js +3 -0
  139. package/dist/utils/gate-theme.d.ts +61 -0
  140. package/dist/utils/gate-theme.js +217 -0
  141. package/dist/utils/git-repository.d.ts +0 -1
  142. package/dist/utils/git-repository.js +0 -3
  143. package/dist/utils/id-hints.d.ts +8 -0
  144. package/dist/utils/id-hints.js +61 -0
  145. package/dist/utils/local-space-state.d.ts +91 -0
  146. package/dist/utils/local-space-state.js +251 -0
  147. package/dist/utils/oauth-signed-query.d.ts +8 -0
  148. package/dist/utils/oauth-signed-query.js +26 -0
  149. package/dist/utils/publish-detection.d.ts +0 -8
  150. package/dist/utils/publish-detection.js +0 -93
  151. package/dist/utils/publish-policy.d.ts +6 -5
  152. package/dist/utils/publish-policy.js +71 -7
  153. package/dist/utils/query-keys.d.ts +16 -4
  154. package/dist/utils/query-keys.js +31 -4
  155. package/dist/utils/runtime-paths.d.ts +0 -1
  156. package/dist/utils/runtime-paths.js +0 -1
  157. package/dist/utils/runtime-upload-batch.d.ts +18 -0
  158. package/dist/utils/runtime-upload-batch.js +90 -0
  159. package/dist/utils/runtime-upload.d.ts +2 -0
  160. package/dist/utils/runtime-upload.js +5 -0
  161. package/dist/utils/sf-config-v1.d.ts +2 -0
  162. package/dist/utils/sf-config-v1.js +542 -0
  163. package/dist/utils/space-config.d.ts +4 -0
  164. package/dist/utils/space-config.js +10 -1
  165. package/dist/utils/space-theme.d.ts +2 -0
  166. package/dist/utils/space-theme.js +9 -0
  167. package/dist/utils/static-runtime-policy.d.ts +0 -21
  168. package/dist/utils/static-runtime-policy.js +19 -103
  169. package/dist/utils/upload-session.d.ts +1 -1
  170. package/dist/utils/wpcom-auth-redirects.d.ts +32 -2
  171. package/dist/utils/wpcom-auth-redirects.js +26 -10
  172. package/dist/vocabulary.d.ts +130 -30
  173. package/dist/vocabulary.js +77 -36
  174. package/package.json +35 -1
  175. package/dist/contracts/account.d.ts +0 -28
  176. package/dist/contracts/account.js +0 -54
  177. package/dist/contracts/intercom.d.ts +0 -6
  178. package/dist/contracts/intercom.js +0 -5
  179. package/dist/contracts/telemetry.d.ts +0 -2
  180. package/dist/contracts/telemetry.js +0 -1
  181. package/dist/utils/access-policy.d.ts +0 -39
  182. package/dist/utils/error-display.d.ts +0 -7
  183. package/dist/utils/error-display.js +0 -42
@@ -1,5 +1,5 @@
1
1
  import { SPACEFAST_ACCESS_POLICY_VERSION, parseStatticResource, } from "../contracts/api-keys.js";
2
- const publishActions = [
2
+ export const publishActions = [
3
3
  "spaces:read",
4
4
  "spaces:create",
5
5
  "spaces:write",
@@ -35,6 +35,9 @@ const domainActions = [
35
35
  function teamResource(input) {
36
36
  return `stt://tenant/${input.tenantId}/principal/team/${input.teamId}/*`;
37
37
  }
38
+ function spaceResource(input) {
39
+ return `stt://tenant/${input.tenantId}/space/${input.spaceId}`;
40
+ }
38
41
  export function compileApiKeyPreset(preset, input) {
39
42
  const resource = teamResource(input);
40
43
  switch (preset) {
@@ -88,6 +91,9 @@ export function compileTeamRolePolicy(input) {
88
91
  return denyAllPolicy();
89
92
  }
90
93
  }
94
+ export function compileSpaceContinuationPolicy(input) {
95
+ return allowPolicy(publishActions.filter((action) => action !== "spaces:create"), [spaceResource(input)]);
96
+ }
91
97
  export function allowPolicy(actions, resources) {
92
98
  return {
93
99
  version: SPACEFAST_ACCESS_POLICY_VERSION,
@@ -145,6 +151,7 @@ export function grantedTenantsFromPolicy(policy) {
145
151
  // Used to scope list queries; specific actions still go through `policyAllows`.
146
152
  export function grantedPrincipalsFromPolicy(policy, input) {
147
153
  const principalIds = new Set();
154
+ const spaceIds = new Set();
148
155
  let all = false;
149
156
  for (const statement of policy.statements) {
150
157
  if (statement.effect !== "allow") {
@@ -169,9 +176,28 @@ export function grantedPrincipalsFromPolicy(policy, input) {
169
176
  principalIds.add(parsed.principalId);
170
177
  }
171
178
  }
179
+ else if (parsed.kind === "space") {
180
+ spaceIds.add(parsed.spaceId);
181
+ }
172
182
  }
173
183
  }
174
- return { all, principalIds: [...principalIds] };
184
+ return {
185
+ all,
186
+ principalIds: [...principalIds],
187
+ grants: [
188
+ ...[...principalIds].map((principalId) => ({
189
+ kind: "principal",
190
+ tenantId: input.tenantId,
191
+ principalType: input.principalType,
192
+ principalId,
193
+ })),
194
+ ...[...spaceIds].map((spaceId) => ({
195
+ kind: "space",
196
+ tenantId: input.tenantId,
197
+ spaceId,
198
+ })),
199
+ ],
200
+ };
175
201
  }
176
202
  // Whether the policy's allow statements reach a resource subtree at all —
177
203
  // either a statement resource covers the base, or it lives inside the base.
@@ -0,0 +1 @@
1
+ export declare function canonicalizeEmail(value: string): string;
@@ -0,0 +1,3 @@
1
+ export function canonicalizeEmail(value) {
2
+ return value.trim().toLowerCase().normalize("NFC");
3
+ }
@@ -0,0 +1,61 @@
1
+ import { type SpacePagesTheme } from "../contracts/space-config.js";
2
+ export type GateThemeDiagnostic = {
3
+ severity: "info" | "warning" | "error";
4
+ code: string;
5
+ message: string;
6
+ path?: string | undefined;
7
+ details?: Record<string, unknown> | undefined;
8
+ };
9
+ /** Bound on the raw `theme.json` / `sf.theme.jsonc` file size at finalize. */
10
+ export declare const GATE_THEME_FILE_MAX_BYTES: number;
11
+ /** Bound on the compiled additive CSS custom-property block. */
12
+ export declare const GATE_THEME_VARS_MAX_BYTES: number;
13
+ export type ParsedThemeJson = {
14
+ /** Gate-page tokens derivable from named palette slugs (accent/primary/background). */
15
+ theme: Partial<SpacePagesTheme>;
16
+ /** `--wp--preset--color--{slug}` / `--wp--preset--font-family--{slug}` declarations. */
17
+ themeVars: string | null;
18
+ diagnostics: GateThemeDiagnostic[];
19
+ };
20
+ /**
21
+ * Parses a `theme.json` file's `settings.color.palette` /
22
+ * `settings.typography.fontFamilies` into gate-page tokens (accent/background
23
+ * derived from conventionally-named slugs: `accent`|`primary`, `background`)
24
+ * plus a full CSS custom-property block for every safe palette/font-family
25
+ * entry. Any problem (oversized, invalid JSON, invalid shape) warns and
26
+ * yields an empty result — the next tier down still applies.
27
+ */
28
+ export declare function parseThemeJsonFile(raw: string): ParsedThemeJson;
29
+ export type ParsedSfThemeJsonc = {
30
+ theme: Partial<SpacePagesTheme>;
31
+ diagnostics: GateThemeDiagnostic[];
32
+ };
33
+ /**
34
+ * Parses the `sf.theme.jsonc` escape hatch: same JSONC tolerance and the same
35
+ * validated token grammar as `spacefast.jsonc "pages.theme"`
36
+ * (`spacePagesThemeSchema`), just from a dedicated file. Any problem warns
37
+ * and yields an empty result.
38
+ */
39
+ export declare function parseSfThemeJsonc(raw: string): ParsedSfThemeJsonc;
40
+ export type CompiledGateTheme = {
41
+ /** Merged accent/background/logo/name/fontFamily; empty object when every tier is unset. */
42
+ theme: Partial<SpacePagesTheme>;
43
+ /** Additive `--wp--preset--*` declarations from theme.json (advanced surface for custom pages). */
44
+ themeVars: string | null;
45
+ diagnostics: GateThemeDiagnostic[];
46
+ };
47
+ /**
48
+ * Compiles the full tier-3 cascade into the flat token shape the runtime
49
+ * already reads. `base` is the existing free-tier `spacefast.jsonc
50
+ * "pages.theme"` (already validated by `spaceConfigSchema` upstream — passed
51
+ * through as-is). `sf.theme.jsonc` only applies when `advancedThemingEntitled`
52
+ * is true; when it is present but NOT entitled, the tier is skipped with an
53
+ * info diagnostic (fail-safe by omission — the compiled artifact simply
54
+ * reflects the lower, entitled tiers, per access-plan §9).
55
+ */
56
+ export declare function mergeGateTheme(input: {
57
+ base: SpacePagesTheme | null;
58
+ themeJsonRaw: string | null;
59
+ sfThemeJsoncRaw: string | null;
60
+ advancedThemingEntitled: boolean;
61
+ }): CompiledGateTheme;
@@ -0,0 +1,217 @@
1
+ import { sfThemeJsoncSchema } from "../contracts/space-config.js";
2
+ import { themeJsonSchema } from "../contracts/theme-json.js";
3
+ import { stripJsonComments } from "./space-config.js";
4
+ import { isSafeThemeColor, isSafeThemeFontFamily } from "./space-theme.js";
5
+ /** Bound on the raw `theme.json` / `sf.theme.jsonc` file size at finalize. */
6
+ export const GATE_THEME_FILE_MAX_BYTES = 64 * 1024;
7
+ /** Bound on the compiled additive CSS custom-property block. */
8
+ export const GATE_THEME_VARS_MAX_BYTES = 8 * 1024;
9
+ /**
10
+ * Parses a `theme.json` file's `settings.color.palette` /
11
+ * `settings.typography.fontFamilies` into gate-page tokens (accent/background
12
+ * derived from conventionally-named slugs: `accent`|`primary`, `background`)
13
+ * plus a full CSS custom-property block for every safe palette/font-family
14
+ * entry. Any problem (oversized, invalid JSON, invalid shape) warns and
15
+ * yields an empty result — the next tier down still applies.
16
+ */
17
+ export function parseThemeJsonFile(raw) {
18
+ const empty = { theme: {}, themeVars: null, diagnostics: [] };
19
+ if (Buffer.byteLength(raw) > GATE_THEME_FILE_MAX_BYTES) {
20
+ return {
21
+ ...empty,
22
+ diagnostics: [
23
+ {
24
+ severity: "warning",
25
+ code: "theme_json_too_large",
26
+ message: `theme.json exceeds the ${GATE_THEME_FILE_MAX_BYTES} byte limit and was ignored.`,
27
+ path: "theme.json",
28
+ },
29
+ ],
30
+ };
31
+ }
32
+ let candidate;
33
+ try {
34
+ candidate = JSON.parse(raw);
35
+ }
36
+ catch (error) {
37
+ return {
38
+ ...empty,
39
+ diagnostics: [
40
+ {
41
+ severity: "warning",
42
+ code: "theme_json_invalid",
43
+ message: `theme.json is not valid JSON: ${error instanceof Error ? error.message : "parse error"}.`,
44
+ path: "theme.json",
45
+ },
46
+ ],
47
+ };
48
+ }
49
+ const parsed = themeJsonSchema.safeParse(candidate);
50
+ if (!parsed.success) {
51
+ return {
52
+ ...empty,
53
+ diagnostics: [
54
+ {
55
+ severity: "warning",
56
+ code: "theme_json_invalid",
57
+ message: "theme.json does not match the supported settings.color.palette/settings.typography.fontFamilies shape.",
58
+ path: "theme.json",
59
+ },
60
+ ],
61
+ };
62
+ }
63
+ const diagnostics = [];
64
+ const declarations = [];
65
+ const theme = {};
66
+ const palette = parsed.data.settings?.color?.palette ?? [];
67
+ for (const entry of palette) {
68
+ if (!isSafeThemeColor(entry.color)) {
69
+ diagnostics.push({
70
+ severity: "warning",
71
+ code: "theme_json_value_invalid",
72
+ message: `theme.json settings.color.palette[slug=${entry.slug}] is not a safe color and was dropped.`,
73
+ path: `theme.json:settings.color.palette.${entry.slug}`,
74
+ });
75
+ continue;
76
+ }
77
+ declarations.push(`--wp--preset--color--${entry.slug}:${entry.color.trim()}`);
78
+ if (entry.slug === "accent" || entry.slug === "primary") {
79
+ theme.accent ??= entry.color.trim();
80
+ }
81
+ if (entry.slug === "background") {
82
+ theme.background = entry.color.trim();
83
+ }
84
+ }
85
+ const fontFamilies = parsed.data.settings?.typography?.fontFamilies ?? [];
86
+ fontFamilies.forEach((entry, index) => {
87
+ if (!isSafeThemeFontFamily(entry.fontFamily)) {
88
+ diagnostics.push({
89
+ severity: "warning",
90
+ code: "theme_json_value_invalid",
91
+ message: `theme.json settings.typography.fontFamilies[slug=${entry.slug}] is not a safe font-family stack and was dropped.`,
92
+ path: `theme.json:settings.typography.fontFamilies.${entry.slug}`,
93
+ });
94
+ return;
95
+ }
96
+ declarations.push(`--wp--preset--font-family--${entry.slug}:${entry.fontFamily.trim()}`);
97
+ if (index === 0) {
98
+ theme.fontFamily ??= entry.fontFamily.trim();
99
+ }
100
+ });
101
+ let themeVars = declarations.join(";");
102
+ while (Buffer.byteLength(themeVars) > GATE_THEME_VARS_MAX_BYTES && declarations.length > 0) {
103
+ declarations.pop();
104
+ diagnostics.push({
105
+ severity: "warning",
106
+ code: "theme_json_value_invalid",
107
+ message: `theme.json compiled CSS exceeds ${GATE_THEME_VARS_MAX_BYTES} bytes; trailing tokens were dropped.`,
108
+ path: "theme.json",
109
+ });
110
+ themeVars = declarations.join(";");
111
+ }
112
+ return {
113
+ theme,
114
+ themeVars: themeVars.length > 0 ? themeVars : null,
115
+ diagnostics,
116
+ };
117
+ }
118
+ /**
119
+ * Parses the `sf.theme.jsonc` escape hatch: same JSONC tolerance and the same
120
+ * validated token grammar as `spacefast.jsonc "pages.theme"`
121
+ * (`spacePagesThemeSchema`), just from a dedicated file. Any problem warns
122
+ * and yields an empty result.
123
+ */
124
+ export function parseSfThemeJsonc(raw) {
125
+ if (Buffer.byteLength(raw) > GATE_THEME_FILE_MAX_BYTES) {
126
+ return {
127
+ theme: {},
128
+ diagnostics: [
129
+ {
130
+ severity: "warning",
131
+ code: "sf_theme_jsonc_too_large",
132
+ message: `sf.theme.jsonc exceeds the ${GATE_THEME_FILE_MAX_BYTES} byte limit and was ignored.`,
133
+ path: "sf.theme.jsonc",
134
+ },
135
+ ],
136
+ };
137
+ }
138
+ let candidate;
139
+ try {
140
+ candidate = JSON.parse(stripJsonComments(raw));
141
+ }
142
+ catch (error) {
143
+ return {
144
+ theme: {},
145
+ diagnostics: [
146
+ {
147
+ severity: "warning",
148
+ code: "sf_theme_jsonc_invalid",
149
+ message: `sf.theme.jsonc is not valid JSONC: ${error instanceof Error ? error.message : "parse error"}.`,
150
+ path: "sf.theme.jsonc",
151
+ },
152
+ ],
153
+ };
154
+ }
155
+ const parsed = sfThemeJsoncSchema.safeParse(candidate);
156
+ if (!parsed.success) {
157
+ return {
158
+ theme: {},
159
+ diagnostics: [
160
+ {
161
+ severity: "warning",
162
+ code: "sf_theme_jsonc_invalid",
163
+ message: "sf.theme.jsonc does not match the gate-page theme token shape.",
164
+ path: "sf.theme.jsonc",
165
+ },
166
+ ],
167
+ };
168
+ }
169
+ return { theme: parsed.data, diagnostics: [] };
170
+ }
171
+ function pickDefined(target, source) {
172
+ for (const key of ["accent", "background", "logo", "name", "fontFamily"]) {
173
+ const value = source[key];
174
+ if (value !== undefined) {
175
+ target[key] = value;
176
+ }
177
+ }
178
+ }
179
+ /**
180
+ * Compiles the full tier-3 cascade into the flat token shape the runtime
181
+ * already reads. `base` is the existing free-tier `spacefast.jsonc
182
+ * "pages.theme"` (already validated by `spaceConfigSchema` upstream — passed
183
+ * through as-is). `sf.theme.jsonc` only applies when `advancedThemingEntitled`
184
+ * is true; when it is present but NOT entitled, the tier is skipped with an
185
+ * info diagnostic (fail-safe by omission — the compiled artifact simply
186
+ * reflects the lower, entitled tiers, per access-plan §9).
187
+ */
188
+ export function mergeGateTheme(input) {
189
+ const diagnostics = [];
190
+ const merged = {};
191
+ if (input.base) {
192
+ pickDefined(merged, input.base);
193
+ }
194
+ let themeVars = null;
195
+ if (input.themeJsonRaw !== null) {
196
+ const parsed = parseThemeJsonFile(input.themeJsonRaw);
197
+ diagnostics.push(...parsed.diagnostics);
198
+ pickDefined(merged, parsed.theme);
199
+ themeVars = parsed.themeVars;
200
+ }
201
+ if (input.sfThemeJsoncRaw !== null) {
202
+ if (!input.advancedThemingEntitled) {
203
+ diagnostics.push({
204
+ severity: "info",
205
+ code: "sf_theme_jsonc_not_entitled",
206
+ message: "sf.theme.jsonc is a paid (advanced theming) tier; this plan is not entitled, so it was ignored.",
207
+ path: "sf.theme.jsonc",
208
+ });
209
+ }
210
+ else {
211
+ const parsed = parseSfThemeJsonc(input.sfThemeJsoncRaw);
212
+ diagnostics.push(...parsed.diagnostics);
213
+ pickDefined(merged, parsed.theme);
214
+ }
215
+ }
216
+ return { theme: merged, themeVars, diagnostics };
217
+ }
@@ -1,5 +1,4 @@
1
1
  export type GitRepositoryProvider = "github" | "gitlab" | "bitbucket" | "git";
2
- export declare function isGitRepositoryRemote(value: string): boolean;
3
2
  export declare function gitProviderFromRepositoryRemote(value: string): GitRepositoryProvider;
4
3
  export declare function repositoryFullNameFromGitRemote(value: string): string | null;
5
4
  export declare function safeRepositoryUrlFromGitRemote(value: string): string | null;
@@ -42,9 +42,6 @@ function parseGitRemote(value) {
42
42
  return null;
43
43
  }
44
44
  }
45
- export function isGitRepositoryRemote(value) {
46
- return parseGitRemote(value) !== null;
47
- }
48
45
  export function gitProviderFromRepositoryRemote(value) {
49
46
  const hostname = parseGitRemote(value)?.hostname;
50
47
  if (!hostname)
@@ -0,0 +1,8 @@
1
+ export declare const HINT_SCAN_CAP = 200;
2
+ export declare const HINT_MAX_CANDIDATES = 5;
3
+ export declare function levenshtein(a: string, b: string): number;
4
+ export declare function refCloseness(needle: string, candidate: string): number;
5
+ export declare function rankCandidates<T>(ref: string, rows: T[], keysOf: (row: T) => Array<string | null | undefined>, options?: {
6
+ max?: number;
7
+ tiebreak?: (a: T, b: T) => number;
8
+ }): T[];
@@ -0,0 +1,61 @@
1
+ // Shared not-found self-correction (W1). One source for the closest-candidate
2
+ // ranking used by every resource's 404 hint builder (spaces, versions, teams,
3
+ // domains). Resources differ only in which caller-scoped rows they feed in and
4
+ // which keys they expose for matching; keeping the levenshtein/closeness/ranking
5
+ // here prevents per-resource drift. Callers are responsible for scoping the rows
6
+ // to the caller principal — this module never touches the DB or auth.
7
+ // How many caller-scoped rows a hint builder will scan to rank candidates. Above
8
+ // this the collection is treated as too large to rank for free, so the builder
9
+ // returns only the recovery hint (no candidate ids).
10
+ export const HINT_SCAN_CAP = 200;
11
+ export const HINT_MAX_CANDIDATES = 5;
12
+ export function levenshtein(a, b) {
13
+ const rows = a.length + 1;
14
+ const cols = b.length + 1;
15
+ let previous = Array.from({ length: cols }, (_, index) => index);
16
+ for (let i = 1; i < rows; i++) {
17
+ const current = [i, ...Array.from({ length: cols - 1 }, () => 0)];
18
+ for (let j = 1; j < cols; j++) {
19
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
20
+ current[j] = Math.min((current[j - 1] ?? 0) + 1, (previous[j] ?? 0) + 1, (previous[j - 1] ?? 0) + cost);
21
+ }
22
+ previous = current;
23
+ }
24
+ return previous[cols - 1] ?? 0;
25
+ }
26
+ // Returns a small finite score the closer `candidate` is to `needle`, or
27
+ // +Infinity when they are not genuinely close. Exact match = 0; substring
28
+ // containment = 1; otherwise a typo within a length-bounded edit distance.
29
+ export function refCloseness(needle, candidate) {
30
+ if (!candidate)
31
+ return Number.POSITIVE_INFINITY;
32
+ if (candidate === needle)
33
+ return 0;
34
+ if (candidate.includes(needle) || needle.includes(candidate))
35
+ return 1;
36
+ const distance = levenshtein(needle, candidate);
37
+ // Only surface genuinely close typos: bound the allowed distance by the shorter
38
+ // string so short refs don't match everything.
39
+ const tolerance = Math.max(2, Math.floor(Math.min(needle.length, candidate.length) / 3));
40
+ return distance <= tolerance ? 1 + distance : Number.POSITIVE_INFINITY;
41
+ }
42
+ // Rank `rows` by how close any of their keys are to `ref`, returning at most
43
+ // `max` closest rows. A row whose every key is far (or absent) is dropped, so
44
+ // only genuine near-matches surface. Ties break by the optional comparator
45
+ // (e.g. recency); keys are matched case-insensitively.
46
+ export function rankCandidates(ref, rows, keysOf, options) {
47
+ const needle = ref.toLowerCase();
48
+ const max = options?.max ?? HINT_MAX_CANDIDATES;
49
+ return rows
50
+ .map((row) => {
51
+ const scores = keysOf(row).map((key) => key ? refCloseness(needle, key.toLowerCase()) : Number.POSITIVE_INFINITY);
52
+ return {
53
+ row,
54
+ score: scores.length > 0 ? Math.min(...scores) : Number.POSITIVE_INFINITY,
55
+ };
56
+ })
57
+ .filter((entry) => entry.score < Number.POSITIVE_INFINITY)
58
+ .toSorted((left, right) => left.score - right.score || (options?.tiebreak ? options.tiebreak(left.row, right.row) : 0))
59
+ .slice(0, max)
60
+ .map((entry) => entry.row);
61
+ }
@@ -0,0 +1,91 @@
1
+ import { z } from "zod";
2
+ export declare const LOCAL_SPACE_STATE_DIR_NAME = ".spacefast";
3
+ export declare const LEGACY_LOCAL_SPACE_STATE_DIR_NAME = ".stattic";
4
+ export declare const LOCAL_SPACE_LINK_FILE_NAME = "space.json";
5
+ export declare const LOCAL_SPACE_STATE_FILE_NAME = "state.json";
6
+ /**
7
+ * Local Spacefast checkout contract.
8
+ *
9
+ * `.spacefast/space.json` is the committable selection axis: it names which
10
+ * space a checkout targets (`space`) and may carry non-secret provider/team
11
+ * coordinates (`team`, `apiBaseUrl`). It must not carry credentials.
12
+ *
13
+ * `.spacefast/state.json` is the local credential/cache axis: it records the
14
+ * resolved `spaceId`, provider `apiUrl`, anonymous `claimToken` before claim,
15
+ * durable `accessToken` after continuation, and small publish caches such as
16
+ * `lastVersionId`. It is secret, machine-written, gitignored, and merge-written
17
+ * so newer tools preserve unknown fields from older/newer tools.
18
+ */
19
+ export declare const localSpaceLinkSchema: z.ZodObject<{
20
+ team: z.ZodOptional<z.ZodString>;
21
+ space: z.ZodString;
22
+ apiBaseUrl: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$loose>;
24
+ export declare const localSpaceStateSchema: z.ZodObject<{
25
+ spaceId: z.ZodOptional<z.ZodString>;
26
+ apiUrl: z.ZodOptional<z.ZodString>;
27
+ teamSlug: z.ZodOptional<z.ZodString>;
28
+ claimToken: z.ZodOptional<z.ZodString>;
29
+ claimUrl: z.ZodOptional<z.ZodString>;
30
+ expiresAt: z.ZodOptional<z.ZodString>;
31
+ accessToken: z.ZodOptional<z.ZodString>;
32
+ lastVersionId: z.ZodOptional<z.ZodString>;
33
+ lastVersionUrl: z.ZodOptional<z.ZodString>;
34
+ lastSpaceUrl: z.ZodOptional<z.ZodString>;
35
+ }, z.core.$loose>;
36
+ export type LocalSpaceLink = z.infer<typeof localSpaceLinkSchema>;
37
+ export type LocalSpaceStateFile = z.infer<typeof localSpaceStateSchema>;
38
+ export type DiscoveredLocalSpaceState = {
39
+ root: string;
40
+ statePath: string | null;
41
+ linkPath: string | null;
42
+ link: LocalSpaceLink | null;
43
+ state: LocalSpaceStateFile | null;
44
+ spaceId: string | undefined;
45
+ claimToken: string | undefined;
46
+ accessToken: string | undefined;
47
+ apiUrl: string | undefined;
48
+ claimUrl: string | undefined;
49
+ expiresAt: string | undefined;
50
+ lastVersionId: string | undefined;
51
+ };
52
+ export type SaveLocalSpaceStateInput = {
53
+ spaceId: string;
54
+ apiUrl: string;
55
+ teamSlug?: string | undefined;
56
+ claimToken?: string | undefined;
57
+ claimUrl?: string | undefined;
58
+ expiresAt?: string | undefined;
59
+ accessToken?: string | undefined;
60
+ lastVersionId?: string | undefined;
61
+ lastVersionUrl?: string | undefined;
62
+ lastSpaceUrl?: string | undefined;
63
+ };
64
+ export declare function localSpaceStateDir(root: string): string;
65
+ export declare function localSpaceStatePath(root: string): string;
66
+ export declare function localSpaceLinkPath(root: string): string;
67
+ export declare function readLocalSpaceLink(root: string): Promise<LocalSpaceLink | null>;
68
+ /**
69
+ * Like readLocalSpaceLink, but reports WHICH file matched — callers surface the
70
+ * legacy `.stattic/space.json` path to users so migrations stay visible.
71
+ */
72
+ export declare function readLocalSpaceLinkWithPath(root: string): Promise<{
73
+ link: LocalSpaceLink;
74
+ path: string;
75
+ } | null>;
76
+ export declare function readLocalSpaceStateAt(root: string): Promise<DiscoveredLocalSpaceState | null>;
77
+ /**
78
+ * Walk up from `startDir` to the filesystem root (or `boundary`, when given),
79
+ * returning the first non-null result of `probe`. Shared by every "nearest
80
+ * checkout marker" lookup (state+link discovery here, the CLI's link-only
81
+ * discovery) so the traversal rules live in one place.
82
+ */
83
+ export declare function findUpward<T>(probe: (dir: string) => Promise<T | null>, startDir?: string, boundary?: string): Promise<T | null>;
84
+ export declare function discoverLocalSpaceState(startDir?: string, boundary?: string): Promise<DiscoveredLocalSpaceState | null>;
85
+ export declare function writeLocalSpaceLink(root: string, link: LocalSpaceLink): Promise<string>;
86
+ export declare function writeLocalSpaceState(root: string, state: LocalSpaceStateFile): Promise<string>;
87
+ export declare function saveLocalSpaceState(root: string, input: SaveLocalSpaceStateInput): Promise<{
88
+ statePath: string;
89
+ linkPath: string;
90
+ wroteLink: boolean;
91
+ }>;