@treeseed/sdk 0.6.23 → 0.6.25

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.
@@ -283,6 +283,22 @@ export declare function listD1Databases(tenantRoot: any, env: any): any;
283
283
  export declare function listQueues(tenantRoot: any, env: any): any;
284
284
  export declare function listR2Buckets(tenantRoot: any, env: any): any;
285
285
  export declare function listPagesProjects(tenantRoot: any, env: any): any;
286
+ export declare function buildCloudflarePagesFunctionBindings(state: any): {
287
+ r2_buckets?: {
288
+ [x: number]: {
289
+ name: any;
290
+ };
291
+ } | undefined;
292
+ d1_databases?: {
293
+ [x: number]: {
294
+ id: any;
295
+ };
296
+ } | undefined;
297
+ kv_namespaces?: {
298
+ [k: string]: any;
299
+ } | undefined;
300
+ };
301
+ export declare function mergeCloudflarePagesDeploymentConfig(config?: {}, bindings?: {}): {};
286
302
  export declare function buildProvisioningSummary(deployConfig: any, state: any, target: any): {
287
303
  target: any;
288
304
  identity: any;
@@ -967,6 +983,9 @@ export declare function verifyProvisionedCloudflareResources(tenantRoot: any, op
967
983
  queue: boolean;
968
984
  dlq: boolean;
969
985
  r2: boolean;
986
+ pagesFormGuardKvBinding: any;
987
+ pagesD1Binding: any;
988
+ pagesR2Binding: any;
970
989
  webCache: boolean;
971
990
  };
972
991
  state: any;
@@ -878,18 +878,53 @@ function listPagesProjects(tenantRoot, env) {
878
878
  });
879
879
  return Array.isArray(payload?.result) ? payload.result : [];
880
880
  }
881
- function ensurePagesProjectCompatibility(accountId, projectName, env, currentProject = null) {
881
+ function buildCloudflarePagesFunctionBindings(state) {
882
+ const kvNamespaces = Object.fromEntries(
883
+ Object.entries(state.kvNamespaces ?? {}).map(([key, namespace]) => {
884
+ const binding = namespace?.binding ?? key;
885
+ const namespaceId = namespace?.id;
886
+ return binding && namespaceId && !isPlaceholderResourceId(namespaceId) ? [binding, { namespace_id: namespaceId }] : null;
887
+ }).filter(Boolean)
888
+ );
889
+ const database = state.d1Databases?.SITE_DATA_DB;
890
+ const d1Databases = database?.binding && database?.databaseId && !isPlaceholderResourceId(database.databaseId) ? { [database.binding]: { id: database.databaseId } } : {};
891
+ const contentBinding = state.content?.r2Binding;
892
+ const contentBucketName = state.content?.bucketName;
893
+ const r2Buckets = contentBinding && contentBucketName ? { [contentBinding]: { name: contentBucketName } } : {};
894
+ return {
895
+ ...Object.keys(kvNamespaces).length ? { kv_namespaces: kvNamespaces } : {},
896
+ ...Object.keys(d1Databases).length ? { d1_databases: d1Databases } : {},
897
+ ...Object.keys(r2Buckets).length ? { r2_buckets: r2Buckets } : {}
898
+ };
899
+ }
900
+ function mergeCloudflarePagesDeploymentConfig(config = {}, bindings = {}) {
901
+ return Object.entries(bindings).reduce((merged, [key, value]) => ({
902
+ ...merged,
903
+ [key]: {
904
+ ...merged[key] ?? {},
905
+ ...value
906
+ }
907
+ }), { ...config });
908
+ }
909
+ function ensurePagesProjectCompatibility(accountId, projectName, env, currentProject = null, options = {}) {
882
910
  if (!accountId || !projectName) {
883
911
  return;
884
912
  }
885
913
  const projectPath = `/accounts/${encodeURIComponent(accountId)}/pages/projects/${encodeURIComponent(projectName)}`;
886
914
  const latestProject = cloudflareApiRequest(projectPath, { env, allowFailure: true })?.result ?? currentProject;
887
915
  const currentConfigs = latestProject?.deployment_configs ?? {};
916
+ const target = options.target;
917
+ const targetConfigKey = target?.kind === "persistent" && target.scope === "prod" ? "production" : "preview";
918
+ const bindings = options.state ? buildCloudflarePagesFunctionBindings(options.state) : {};
888
919
  const mergeCompatibility = (config = {}) => ({
889
920
  ...config,
890
921
  compatibility_date: config.compatibility_date ?? DEFAULT_COMPATIBILITY_DATE,
891
922
  compatibility_flags: [.../* @__PURE__ */ new Set([...config.compatibility_flags ?? [], ...DEFAULT_COMPATIBILITY_FLAGS])]
892
923
  });
924
+ const mergeTarget = (key, config = {}) => {
925
+ const compatible = mergeCompatibility(config);
926
+ return key === targetConfigKey && Object.keys(bindings).length ? mergeCloudflarePagesDeploymentConfig(compatible, bindings) : compatible;
927
+ };
893
928
  cloudflareApiRequest(
894
929
  projectPath,
895
930
  {
@@ -898,8 +933,8 @@ function ensurePagesProjectCompatibility(accountId, projectName, env, currentPro
898
933
  body: {
899
934
  deployment_configs: {
900
935
  ...currentConfigs,
901
- preview: mergeCompatibility(currentConfigs.preview),
902
- production: mergeCompatibility(currentConfigs.production)
936
+ preview: mergeTarget("preview", currentConfigs.preview),
937
+ production: mergeTarget("production", currentConfigs.production)
903
938
  }
904
939
  }
905
940
  }
@@ -1835,7 +1870,7 @@ function provisionCloudflareResources(tenantRoot, options = {}) {
1835
1870
  const exists = pagesProjects.find((entry) => entry?.name === current.projectName);
1836
1871
  if (exists) {
1837
1872
  current.url = exists.subdomain ? `https://${exists.subdomain}` : current.url ?? `https://${current.projectName}.pages.dev`;
1838
- ensurePagesProjectCompatibility(env.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "", current.projectName, env, exists);
1873
+ ensurePagesProjectCompatibility(env.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "", current.projectName, env, exists, { state, target });
1839
1874
  return;
1840
1875
  }
1841
1876
  if (dryRun) {
@@ -1854,7 +1889,7 @@ function provisionCloudflareResources(tenantRoot, options = {}) {
1854
1889
  capture: true,
1855
1890
  env
1856
1891
  });
1857
- ensurePagesProjectCompatibility(env.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "", current.projectName, env);
1892
+ ensurePagesProjectCompatibility(env.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "", current.projectName, env, null, { state, target });
1858
1893
  current.url = `https://${current.projectName}.pages.dev`;
1859
1894
  };
1860
1895
  ensureKv("FORM_GUARD_KV");
@@ -1931,13 +1966,25 @@ function verifyProvisionedCloudflareResources(tenantRoot, options = {}) {
1931
1966
  const queues = dryRun ? [] : listQueues(tenantRoot, env);
1932
1967
  const buckets = dryRun ? [] : listR2Buckets(tenantRoot, env);
1933
1968
  const pagesProjects = dryRun ? [] : listPagesProjects(tenantRoot, env);
1969
+ const livePages = pagesProjects.find((entry) => entry?.name === state.pages?.projectName);
1970
+ const pagesProject = dryRun || !env.CLOUDFLARE_ACCOUNT_ID || !state.pages?.projectName ? livePages : cloudflareApiRequest(
1971
+ `/accounts/${encodeURIComponent(env.CLOUDFLARE_ACCOUNT_ID)}/pages/projects/${encodeURIComponent(state.pages.projectName)}`,
1972
+ { env, allowFailure: true }
1973
+ )?.result ?? livePages;
1974
+ const pagesConfigKey = target.kind === "persistent" && target.scope === "prod" ? "production" : "preview";
1975
+ const pagesConfig = pagesProject?.deployment_configs?.[pagesConfigKey] ?? {};
1976
+ const pagesBindings = buildCloudflarePagesFunctionBindings(state);
1977
+ const pageBindingConfigured = (configKey, binding, expected) => pagesConfig?.[configKey]?.[binding] && Object.entries(expected).every(([key, value]) => pagesConfig[configKey][binding]?.[key] === value);
1934
1978
  const checks = {
1935
- pages: Boolean(state.pages?.projectName && pagesProjects.find((entry) => entry?.name === state.pages.projectName)),
1979
+ pages: Boolean(state.pages?.projectName && (livePages || pagesProject?.name === state.pages.projectName)),
1936
1980
  formGuardKv: Boolean(state.kvNamespaces?.FORM_GUARD_KV?.name && kvNamespaces.find((entry) => entry?.title === state.kvNamespaces.FORM_GUARD_KV.name)),
1937
1981
  d1: Boolean(state.d1Databases?.SITE_DATA_DB?.databaseName && d1Databases.find((entry) => entry?.name === state.d1Databases.SITE_DATA_DB.databaseName)),
1938
1982
  queue: Boolean(state.queues?.agentWork?.name && queues.find((entry) => queueName(entry) === state.queues.agentWork.name)),
1939
1983
  dlq: !state.queues?.agentWork?.dlqName || Boolean(queues.find((entry) => queueName(entry) === state.queues.agentWork.dlqName)),
1940
1984
  r2: Boolean(state.content?.bucketName && buckets.find((entry) => entry?.name === state.content.bucketName)),
1985
+ pagesFormGuardKvBinding: !pagesBindings.kv_namespaces?.FORM_GUARD_KV || pageBindingConfigured("kv_namespaces", "FORM_GUARD_KV", pagesBindings.kv_namespaces.FORM_GUARD_KV),
1986
+ pagesD1Binding: !pagesBindings.d1_databases?.SITE_DATA_DB || pageBindingConfigured("d1_databases", "SITE_DATA_DB", pagesBindings.d1_databases.SITE_DATA_DB),
1987
+ pagesR2Binding: !state.content?.r2Binding || !pagesBindings.r2_buckets?.[state.content.r2Binding] || pageBindingConfigured("r2_buckets", state.content.r2Binding, pagesBindings.r2_buckets[state.content.r2Binding]),
1941
1988
  webCache: !shouldManageCloudflareWebCacheRules(deployConfig, target) || state.webCache?.rulesManaged === true
1942
1989
  };
1943
1990
  const ok = dryRun ? true : Object.values(checks).every(Boolean);
@@ -1955,7 +2002,6 @@ function verifyProvisionedCloudflareResources(tenantRoot, options = {}) {
1955
2002
  const liveDlq = queues.find((entry) => queueName(entry) === state.queues.agentWork.dlqName);
1956
2003
  state.queues.agentWork.dlqId = queueId(liveDlq) ?? state.queues.agentWork.dlqId ?? null;
1957
2004
  }
1958
- const livePages = pagesProjects.find((entry) => entry?.name === state.pages?.projectName);
1959
2005
  if (state.pages) {
1960
2006
  const configuredWebUrl = resolveConfiguredSurfaceBaseUrl(deployConfig, target, "web");
1961
2007
  if (configuredWebUrl) {
@@ -2136,6 +2182,7 @@ function printDestroySummary(result) {
2136
2182
  }
2137
2183
  export {
2138
2184
  assertDeploymentInitialized,
2185
+ buildCloudflarePagesFunctionBindings,
2139
2186
  buildProvisioningSummary,
2140
2187
  buildPublicVars,
2141
2188
  buildSecretMap,
@@ -2160,6 +2207,7 @@ export {
2160
2207
  loadDeployState,
2161
2208
  markDeploymentInitialized,
2162
2209
  markManagedServicesInitialized,
2210
+ mergeCloudflarePagesDeploymentConfig,
2163
2211
  normalizePersistentScope,
2164
2212
  printDeploySummary,
2165
2213
  printDestroySummary,
@@ -9,7 +9,7 @@ import { collectInternalDevReferenceIssues } from "./package-reference-policy.js
9
9
  import { collectTreeseedEnvironmentContext, resolveTreeseedMachineEnvironmentValues, validateTreeseedCommandEnvironment } from "./config-runtime.js";
10
10
  import { loadDeployState } from "./deploy.js";
11
11
  import { loadCliDeployConfig } from "./runtime-tools.js";
12
- import { run, workspacePackages } from "./workspace-tools.js";
12
+ import { packagesWithScript, run, workspacePackages } from "./workspace-tools.js";
13
13
  const RELEASE_CANDIDATE_CACHE_DIR = ".treeseed/workflow/release-candidates";
14
14
  const STABLE_SEMVER = /^\d+\.\d+\.\d+$/u;
15
15
  const REHEARSAL_IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
@@ -224,6 +224,11 @@ function rehearsalVerifyScript(root) {
224
224
  }
225
225
  return null;
226
226
  }
227
+ function buildRehearsalWorkspacePackageArtifacts(root) {
228
+ for (const pkg of packagesWithScript("build:dist", root)) {
229
+ run("npm", ["--prefix", pkg.dir, "run", "build:dist"], { cwd: root, timeoutMs: 3e5 });
230
+ }
231
+ }
227
232
  function runProductionDependencyRehearsal(root, plannedVersions, selectedPackageNames, failures) {
228
233
  if (getGitHubAutomationMode() === "stub" || process.env.TREESEED_RELEASE_CANDIDATE_REHEARSAL_MODE === "skip") {
229
234
  return "Skipped clean install rehearsal in stub/skip mode.";
@@ -235,7 +240,8 @@ function runProductionDependencyRehearsal(root, plannedVersions, selectedPackage
235
240
  tempParent = copied.tempParent;
236
241
  applyPlannedStableMetadata(copied.tempRoot, plannedVersions);
237
242
  run("npm", ["install", "--package-lock-only", "--ignore-scripts"], { cwd: copied.tempRoot, timeoutMs: 3e5 });
238
- run("npm", ["ci"], { cwd: copied.tempRoot, timeoutMs: 6e5 });
243
+ run("npm", ["ci", "--ignore-scripts"], { cwd: copied.tempRoot, timeoutMs: 6e5 });
244
+ buildRehearsalWorkspacePackageArtifacts(copied.tempRoot);
239
245
  const scriptName = rehearsalVerifyScript(copied.tempRoot);
240
246
  if (scriptName) {
241
247
  run("npm", ["run", scriptName], { cwd: copied.tempRoot, timeoutMs: 9e5 });
@@ -3,6 +3,7 @@ import { relative, resolve } from "node:path";
3
3
  import { collectTreeseedEnvironmentContext, resolveTreeseedMachineEnvironmentValues } from "../operations/services/config-runtime.js";
4
4
  import {
5
5
  buildPublicVars,
6
+ buildCloudflarePagesFunctionBindings,
6
7
  buildProvisioningSummary,
7
8
  buildSecretMap,
8
9
  cloudflareApiRequest,
@@ -16,6 +17,7 @@ import {
16
17
  listPagesProjects,
17
18
  listQueues,
18
19
  listR2Buckets,
20
+ mergeCloudflarePagesDeploymentConfig,
19
21
  loadDeployState,
20
22
  queueId,
21
23
  queueName,
@@ -595,7 +597,7 @@ function syncPagesEnvironmentVariablesForTarget(input, { dryRun = false } = {})
595
597
  const mergedDeploymentConfigs = {
596
598
  ...deploymentConfigs,
597
599
  [branchConfigKey]: {
598
- ...currentBranchConfig,
600
+ ...mergeCloudflarePagesDeploymentConfig(currentBranchConfig, buildCloudflarePagesFunctionBindings(state)),
599
601
  env_vars: {
600
602
  ...currentBranchConfig?.env_vars ?? {},
601
603
  ...envVars
@@ -1070,6 +1072,11 @@ function verifyCloudflareUnitOnce(input, postconditions) {
1070
1072
  const branchKey = input.context.target.kind === "persistent" && input.context.target.scope === "prod" ? "production" : "preview";
1071
1073
  const branchConfig = project?.deployment_configs?.[branchKey] ?? {};
1072
1074
  const envVars = branchConfig?.env_vars && typeof branchConfig.env_vars === "object" ? branchConfig.env_vars : {};
1075
+ const pageBindings = buildCloudflarePagesFunctionBindings(state);
1076
+ const pageBindingConfigured = (configKey, binding, expected) => {
1077
+ const observed = branchConfig?.[configKey]?.[binding];
1078
+ return Boolean(observed && Object.entries(expected).every(([key, value]) => observed?.[key] === value));
1079
+ };
1073
1080
  const sync = collectCloudflareEnvironmentSync(input);
1074
1081
  const expectedVars = Object.entries(sync.vars).filter(([, value]) => typeof value === "string" && value.length > 0);
1075
1082
  const checks = [
@@ -1106,6 +1113,33 @@ function verifyCloudflareUnitOnce(input, postconditions) {
1106
1113
  issues: envVars[name] ? [] : [`Pages secret ${name} is missing from the ${branchKey} deployment config.`]
1107
1114
  }));
1108
1115
  }
1116
+ for (const [binding, expected] of Object.entries(pageBindings.kv_namespaces ?? {})) {
1117
+ checks.push(verificationCheck(`pages.kv:${binding}`, `Pages KV binding ${binding} points at the expected namespace`, "api", {
1118
+ exists: Boolean(branchConfig?.kv_namespaces?.[binding]),
1119
+ configured: pageBindingConfigured("kv_namespaces", binding, expected),
1120
+ expected,
1121
+ observed: branchConfig?.kv_namespaces?.[binding] ?? null,
1122
+ issues: pageBindingConfigured("kv_namespaces", binding, expected) ? [] : [`Pages KV binding ${binding} is missing or points at the wrong namespace for ${branchKey}.`]
1123
+ }));
1124
+ }
1125
+ for (const [binding, expected] of Object.entries(pageBindings.d1_databases ?? {})) {
1126
+ checks.push(verificationCheck(`pages.d1:${binding}`, `Pages D1 binding ${binding} points at the expected database`, "api", {
1127
+ exists: Boolean(branchConfig?.d1_databases?.[binding]),
1128
+ configured: pageBindingConfigured("d1_databases", binding, expected),
1129
+ expected,
1130
+ observed: branchConfig?.d1_databases?.[binding] ?? null,
1131
+ issues: pageBindingConfigured("d1_databases", binding, expected) ? [] : [`Pages D1 binding ${binding} is missing or points at the wrong database for ${branchKey}.`]
1132
+ }));
1133
+ }
1134
+ for (const [binding, expected] of Object.entries(pageBindings.r2_buckets ?? {})) {
1135
+ checks.push(verificationCheck(`pages.r2:${binding}`, `Pages R2 binding ${binding} points at the expected bucket`, "api", {
1136
+ exists: Boolean(branchConfig?.r2_buckets?.[binding]),
1137
+ configured: pageBindingConfigured("r2_buckets", binding, expected),
1138
+ expected,
1139
+ observed: branchConfig?.r2_buckets?.[binding] ?? null,
1140
+ issues: pageBindingConfigured("r2_buckets", binding, expected) ? [] : [`Pages R2 binding ${binding} is missing or points at the wrong bucket for ${branchKey}.`]
1141
+ }));
1142
+ }
1109
1143
  return summarizeVerification(input.unit.unitId, checks);
1110
1144
  }
1111
1145
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.6.23",
3
+ "version": "0.6.25",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {