@treeseed/sdk 0.6.1 → 0.6.2

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 (32) hide show
  1. package/dist/operations/services/bootstrap-runner.d.ts +33 -0
  2. package/dist/operations/services/bootstrap-runner.js +136 -0
  3. package/dist/operations/services/config-runtime.d.ts +27 -8
  4. package/dist/operations/services/config-runtime.js +297 -124
  5. package/dist/operations/services/github-api.d.ts +33 -0
  6. package/dist/operations/services/github-api.js +118 -4
  7. package/dist/operations/services/github-automation.d.ts +30 -0
  8. package/dist/operations/services/github-automation.js +107 -1
  9. package/dist/operations/services/project-platform.d.ts +38 -2
  10. package/dist/operations/services/project-platform.js +281 -9
  11. package/dist/operations/services/railway-deploy.d.ts +6 -2
  12. package/dist/operations/services/railway-deploy.js +26 -18
  13. package/dist/operations/services/runtime-tools.d.ts +0 -2
  14. package/dist/operations/services/runtime-tools.js +0 -2
  15. package/dist/platform/env.yaml +68 -96
  16. package/dist/platform/environment.js +51 -0
  17. package/dist/reconcile/bootstrap-systems.d.ts +32 -0
  18. package/dist/reconcile/bootstrap-systems.js +175 -0
  19. package/dist/reconcile/builtin-adapters.js +1 -9
  20. package/dist/reconcile/desired-state.js +16 -14
  21. package/dist/reconcile/engine.d.ts +9 -4
  22. package/dist/reconcile/engine.js +57 -14
  23. package/dist/reconcile/index.d.ts +1 -0
  24. package/dist/reconcile/index.js +1 -0
  25. package/dist/scripts/config-treeseed.js +30 -0
  26. package/dist/scripts/package-tools.js +0 -2
  27. package/dist/scripts/tenant-deploy.js +16 -36
  28. package/dist/scripts/test-cloudflare-local.js +0 -2
  29. package/dist/workflow/operations.js +23 -4
  30. package/dist/workflow.d.ts +5 -0
  31. package/package.json +1 -1
  32. package/templates/github/deploy.workflow.yml +15 -15
@@ -3,9 +3,10 @@ import { spawnSync } from 'node:child_process';
3
3
  import { mkdirSync, writeFileSync } from 'node:fs';
4
4
  import { dirname, resolve } from 'node:path';
5
5
  import { applyTreeseedEnvironmentToProcess } from '../operations/services/config-runtime.js';
6
- import { assertDeploymentInitialized, createBranchPreviewDeployTarget, createPersistentDeployTarget, deployTargetLabel, ensureGeneratedWranglerConfig, finalizeDeploymentState, loadDeployState, runRemoteD1Migrations, } from '../operations/services/deploy.js';
6
+ import { createBranchPreviewDeployTarget, createPersistentDeployTarget, deployTargetLabel, finalizeDeploymentState, } from '../operations/services/deploy.js';
7
+ import { prepareTenantCloudflareDeploy, runTenantDataMigration, runTenantWebBuild, runTenantWebPublish, } from '../operations/services/project-platform.js';
7
8
  import { currentManagedBranch, PRODUCTION_BRANCH, STAGING_BRANCH } from '../operations/services/git-workflow.js';
8
- import { loadCliDeployConfig, packageScriptPath, resolveWranglerBin } from '../operations/services/runtime-tools.js';
9
+ import { resolveWranglerBin } from '../operations/services/runtime-tools.js';
9
10
  import { runTenantDeployPreflight } from '../operations/services/save-deploy-preflight.js';
10
11
  const tenantRoot = process.cwd();
11
12
  const args = process.argv.slice(2);
@@ -165,14 +166,20 @@ async function main() {
165
166
  }
166
167
  if (scope === 'local') {
167
168
  runTenantDeployPreflight({ cwd: tenantRoot, scope: 'local' });
168
- runNodeScript(packageScriptPath('tenant-build'));
169
+ await runTenantWebBuild({ tenantRoot, scope: 'local', dryRun: options.dryRun, env: process.env });
169
170
  writeDeployReport({ ok: true, kind: 'success', scope, dryRun: options.dryRun, target: deployTargetLabel(target) });
170
171
  console.log('Treeseed local deploy completed as a build-only publish target.');
171
172
  return;
172
173
  }
174
+ let context;
173
175
  try {
174
- assertDeploymentInitialized(tenantRoot, { target });
175
- runTenantDeployPreflight({ cwd: tenantRoot, scope });
176
+ context = prepareTenantCloudflareDeploy({
177
+ tenantRoot,
178
+ scope,
179
+ target,
180
+ dryRun: options.dryRun,
181
+ env: process.env,
182
+ });
176
183
  }
177
184
  catch (error) {
178
185
  writeDeployReport({
@@ -183,43 +190,16 @@ async function main() {
183
190
  });
184
191
  throw error;
185
192
  }
186
- const { wranglerPath } = ensureGeneratedWranglerConfig(tenantRoot, { target });
187
- const deployConfig = loadCliDeployConfig(tenantRoot);
188
- const deployState = loadDeployState(tenantRoot, deployConfig, { target });
189
- const pagesProjectName = target.kind === 'persistent' ? deployState.pages?.projectName ?? null : null;
190
- const pagesBranchName = target.kind === 'persistent'
191
- ? (target.scope === 'prod'
192
- ? deployState.pages?.productionBranch ?? 'main'
193
- : deployState.pages?.stagingBranch ?? 'staging')
194
- : null;
195
193
  if (shouldRun('migrate')) {
196
- const result = runRemoteD1Migrations(tenantRoot, { dryRun: options.dryRun, target });
194
+ const result = await runTenantDataMigration(context);
197
195
  console.log(`${options.dryRun ? 'Planned' : 'Applied'} remote migrations for ${result.databaseName}.`);
198
196
  }
199
197
  if (shouldRun('build')) {
200
- if (options.dryRun) {
201
- console.log('Dry run: skipped tenant build.');
202
- }
203
- else {
204
- runNodeScript(packageScriptPath('tenant-build'));
205
- }
198
+ await runTenantWebBuild(context);
206
199
  }
207
200
  if (shouldRun('publish')) {
208
- if (options.dryRun) {
209
- if (pagesProjectName) {
210
- console.log(`Dry run: would deploy ${deployTargetLabel(target)} to Pages project ${pagesProjectName} from ${resolve(tenantRoot, 'dist')}.`);
211
- }
212
- else {
213
- console.log(`Dry run: would deploy ${deployTargetLabel(target)} with generated Wrangler config at ${resolve(wranglerPath)}.`);
214
- }
215
- }
216
- else {
217
- if (pagesProjectName) {
218
- runWranglerPagesDeploy(pagesProjectName, pagesBranchName, resolve(tenantRoot, 'dist'));
219
- }
220
- else {
221
- runWranglerDeploy(wranglerPath);
222
- }
201
+ await runTenantWebPublish(context);
202
+ if (!options.dryRun) {
223
203
  finalizeDeploymentState(tenantRoot, { target });
224
204
  }
225
205
  }
@@ -136,8 +136,6 @@ async function main() {
136
136
  persistTo: PERSIST_TO,
137
137
  envOverrides: {
138
138
  TREESEED_LOCAL_DEV_MODE: 'cloudflare',
139
- TREESEED_FORMS_LOCAL_BYPASS_TURNSTILE: 'true',
140
- TREESEED_PUBLIC_FORMS_LOCAL_BYPASS_TURNSTILE: 'true',
141
139
  TREESEED_FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS: 'false',
142
140
  TREESEED_FORMS_LOCAL_USE_MAILPIT: 'true',
143
141
  TREESEED_MAILPIT_SMTP_HOST: '127.0.0.1',
@@ -89,7 +89,7 @@ import {
89
89
  workspaceRoot
90
90
  } from "../operations/services/workspace-tools.js";
91
91
  import { resolveTreeseedWorkflowState } from "../workflow-state.js";
92
- import { createTreeseedReconcileRegistry, deriveTreeseedDesiredUnits, planTreeseedReconciliation, reconcileTreeseedTarget } from "../reconcile/index.js";
92
+ import { createTreeseedReconcileRegistry, deriveTreeseedDesiredUnits, filterTreeseedDesiredUnitsByBootstrapSystems, planTreeseedReconciliation, resolveTreeseedBootstrapSelection, reconcileTreeseedTarget } from "../reconcile/index.js";
93
93
  import {
94
94
  acquireWorkflowLock,
95
95
  createWorkflowRunJournal,
@@ -1031,6 +1031,9 @@ async function workflowConfig(helpers, input = {}) {
1031
1031
  const bootstrapOnly = input.bootstrap === true;
1032
1032
  const bootstrapPreflight = bootstrapOnly && input.preflight === true;
1033
1033
  const nonInteractive = input.nonInteractive === true;
1034
+ const bootstrapSystemsInput = input.systems;
1035
+ const skipUnavailable = input.skipUnavailable;
1036
+ const bootstrapExecution = input.bootstrapExecution ?? "parallel";
1034
1037
  const repairs = input.repair === false ? [] : resolveTreeseedWorkflowState(tenantRoot).deployConfigPresent ? applyTreeseedSafeRepairs(tenantRoot) : [];
1035
1038
  const toolHealth = ensureTreeseedActVerificationTooling({
1036
1039
  tenantRoot,
@@ -1143,8 +1146,18 @@ async function workflowConfig(helpers, input = {}) {
1143
1146
  maybePrint(helpers.write, `Deriving desired units for ${scope}...`);
1144
1147
  const target = createPersistentDeployTarget(scope);
1145
1148
  const derived = deriveTreeseedDesiredUnits({ tenantRoot, target });
1149
+ const selection = resolveTreeseedBootstrapSelection({
1150
+ deployConfig: derived.deployConfig,
1151
+ env: contextSnapshot.valuesByScope[scope] ?? helpers.context.env ?? process.env,
1152
+ systems: bootstrapSystemsInput,
1153
+ skipUnavailable
1154
+ });
1155
+ const selectedUnits = filterTreeseedDesiredUnitsByBootstrapSystems(
1156
+ derived.units,
1157
+ selection.runnable.filter((system) => system !== "github")
1158
+ );
1146
1159
  const registry = createTreeseedReconcileRegistry(derived.deployConfig);
1147
- const capabilityMatrix = derived.units.map((unit) => {
1160
+ const capabilityMatrix = selectedUnits.map((unit) => {
1148
1161
  const adapter = registry.get(unit.unitType, unit.provider);
1149
1162
  return {
1150
1163
  unitId: unit.unitId,
@@ -1170,10 +1183,12 @@ async function workflowConfig(helpers, input = {}) {
1170
1183
  tenantRoot,
1171
1184
  target,
1172
1185
  env: helpers.context.env,
1186
+ systems: selection.runnable.filter((system) => system !== "github"),
1173
1187
  write: (line) => maybePrint(helpers.write, line)
1174
1188
  });
1175
1189
  return {
1176
1190
  scope,
1191
+ bootstrapSystems: selection,
1177
1192
  resourceInventory: buildProvisioningSummary(derived.deployConfig, loadDeployState(tenantRoot, derived.deployConfig, { target }), target),
1178
1193
  capabilityMatrix: await Promise.all(capabilityMatrix.map(async (entry) => ({
1179
1194
  ...entry,
@@ -1204,7 +1219,8 @@ async function workflowConfig(helpers, input = {}) {
1204
1219
  secretSession,
1205
1220
  context: contextSnapshot,
1206
1221
  resourceInventoryByScope: Object.fromEntries(plansByScope.map((entry) => [entry.scope, entry.resourceInventory])),
1207
- verificationPreflight: plansByScope
1222
+ verificationPreflight: plansByScope,
1223
+ bootstrapSystemsByScope: Object.fromEntries(plansByScope.map((entry) => [entry.scope, entry.bootstrapSystems]))
1208
1224
  },
1209
1225
  {
1210
1226
  nextSteps: createNextSteps([
@@ -1250,7 +1266,10 @@ async function workflowConfig(helpers, input = {}) {
1250
1266
  sync,
1251
1267
  env: helpers.context.env,
1252
1268
  checkConnections: bootstrapOnly || sync !== "none" || scopes.some((scope) => scope !== "local"),
1253
- onProgress: (line) => maybePrint(helpers.write, line)
1269
+ systems: bootstrapSystemsInput,
1270
+ skipUnavailable,
1271
+ bootstrapExecution,
1272
+ onProgress: (line, stream) => maybePrint(helpers.write, line, stream)
1254
1273
  });
1255
1274
  const refreshedContext = collectTreeseedConfigContext({
1256
1275
  tenantRoot,
@@ -126,9 +126,14 @@ export type TreeseedSwitchInput = {
126
126
  dryRun?: boolean;
127
127
  };
128
128
  export type TreeseedConfigScope = 'all' | 'local' | 'staging' | 'prod';
129
+ export type TreeseedBootstrapSystem = 'all' | 'github' | 'data' | 'web' | 'api' | 'agents';
130
+ export type TreeseedBootstrapExecution = 'parallel' | 'sequential';
129
131
  export type TreeseedConfigInput = {
130
132
  target?: TreeseedConfigScope[] | TreeseedConfigScope;
131
133
  environment?: TreeseedConfigScope[] | TreeseedConfigScope;
134
+ systems?: TreeseedBootstrapSystem[] | TreeseedBootstrapSystem;
135
+ skipUnavailable?: boolean;
136
+ bootstrapExecution?: TreeseedBootstrapExecution;
132
137
  syncProviders?: 'none' | 'github' | 'cloudflare' | 'railway' | 'all';
133
138
  sync?: 'none' | 'github' | 'cloudflare' | 'railway' | 'all';
134
139
  bootstrap?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -188,12 +188,12 @@ __WORKING_DIRECTORY_BLOCK__
188
188
  TREESEED_EDITORIAL_PREVIEW_SECRET: ${{ secrets.TREESEED_EDITORIAL_PREVIEW_SECRET }}
189
189
  TREESEED_PUBLIC_TURNSTILE_SITE_KEY: ${{ vars.TREESEED_PUBLIC_TURNSTILE_SITE_KEY }}
190
190
  TREESEED_TURNSTILE_SECRET_KEY: ${{ secrets.TREESEED_TURNSTILE_SECRET_KEY }}
191
- TREESEED_SMTP_HOST: ${{ secrets.TREESEED_SMTP_HOST }}
192
- TREESEED_SMTP_PORT: ${{ secrets.TREESEED_SMTP_PORT }}
193
- TREESEED_SMTP_USERNAME: ${{ secrets.TREESEED_SMTP_USERNAME }}
191
+ TREESEED_SMTP_HOST: ${{ vars.TREESEED_SMTP_HOST }}
192
+ TREESEED_SMTP_PORT: ${{ vars.TREESEED_SMTP_PORT }}
193
+ TREESEED_SMTP_USERNAME: ${{ vars.TREESEED_SMTP_USERNAME }}
194
194
  TREESEED_SMTP_PASSWORD: ${{ secrets.TREESEED_SMTP_PASSWORD }}
195
- TREESEED_SMTP_FROM: ${{ secrets.TREESEED_SMTP_FROM }}
196
- TREESEED_SMTP_REPLY_TO: ${{ secrets.TREESEED_SMTP_REPLY_TO }}
195
+ TREESEED_SMTP_FROM: ${{ vars.TREESEED_SMTP_FROM }}
196
+ TREESEED_SMTP_REPLY_TO: ${{ vars.TREESEED_SMTP_REPLY_TO }}
197
197
  RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }}
198
198
  TREESEED_MARKET_API_BASE_URL: ${{ vars.TREESEED_MARKET_API_BASE_URL }}
199
199
  TREESEED_PROJECT_ID: ${{ inputs.project_id || vars.TREESEED_PROJECT_ID }}
@@ -262,12 +262,12 @@ __WORKING_DIRECTORY_BLOCK__
262
262
  TREESEED_EDITORIAL_PREVIEW_SECRET: ${{ secrets.TREESEED_EDITORIAL_PREVIEW_SECRET }}
263
263
  TREESEED_PUBLIC_TURNSTILE_SITE_KEY: ${{ vars.TREESEED_PUBLIC_TURNSTILE_SITE_KEY }}
264
264
  TREESEED_TURNSTILE_SECRET_KEY: ${{ secrets.TREESEED_TURNSTILE_SECRET_KEY }}
265
- TREESEED_SMTP_HOST: ${{ secrets.TREESEED_SMTP_HOST }}
266
- TREESEED_SMTP_PORT: ${{ secrets.TREESEED_SMTP_PORT }}
267
- TREESEED_SMTP_USERNAME: ${{ secrets.TREESEED_SMTP_USERNAME }}
265
+ TREESEED_SMTP_HOST: ${{ vars.TREESEED_SMTP_HOST }}
266
+ TREESEED_SMTP_PORT: ${{ vars.TREESEED_SMTP_PORT }}
267
+ TREESEED_SMTP_USERNAME: ${{ vars.TREESEED_SMTP_USERNAME }}
268
268
  TREESEED_SMTP_PASSWORD: ${{ secrets.TREESEED_SMTP_PASSWORD }}
269
- TREESEED_SMTP_FROM: ${{ secrets.TREESEED_SMTP_FROM }}
270
- TREESEED_SMTP_REPLY_TO: ${{ secrets.TREESEED_SMTP_REPLY_TO }}
269
+ TREESEED_SMTP_FROM: ${{ vars.TREESEED_SMTP_FROM }}
270
+ TREESEED_SMTP_REPLY_TO: ${{ vars.TREESEED_SMTP_REPLY_TO }}
271
271
  RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }}
272
272
  TREESEED_MARKET_API_BASE_URL: ${{ vars.TREESEED_MARKET_API_BASE_URL }}
273
273
  TREESEED_PROJECT_ID: ${{ inputs.project_id || vars.TREESEED_PROJECT_ID }}
@@ -347,12 +347,12 @@ __WORKING_DIRECTORY_BLOCK__
347
347
  TREESEED_EDITORIAL_PREVIEW_SECRET: ${{ secrets.TREESEED_EDITORIAL_PREVIEW_SECRET }}
348
348
  TREESEED_PUBLIC_TURNSTILE_SITE_KEY: ${{ vars.TREESEED_PUBLIC_TURNSTILE_SITE_KEY }}
349
349
  TREESEED_TURNSTILE_SECRET_KEY: ${{ secrets.TREESEED_TURNSTILE_SECRET_KEY }}
350
- TREESEED_SMTP_HOST: ${{ secrets.TREESEED_SMTP_HOST }}
351
- TREESEED_SMTP_PORT: ${{ secrets.TREESEED_SMTP_PORT }}
352
- TREESEED_SMTP_USERNAME: ${{ secrets.TREESEED_SMTP_USERNAME }}
350
+ TREESEED_SMTP_HOST: ${{ vars.TREESEED_SMTP_HOST }}
351
+ TREESEED_SMTP_PORT: ${{ vars.TREESEED_SMTP_PORT }}
352
+ TREESEED_SMTP_USERNAME: ${{ vars.TREESEED_SMTP_USERNAME }}
353
353
  TREESEED_SMTP_PASSWORD: ${{ secrets.TREESEED_SMTP_PASSWORD }}
354
- TREESEED_SMTP_FROM: ${{ secrets.TREESEED_SMTP_FROM }}
355
- TREESEED_SMTP_REPLY_TO: ${{ secrets.TREESEED_SMTP_REPLY_TO }}
354
+ TREESEED_SMTP_FROM: ${{ vars.TREESEED_SMTP_FROM }}
355
+ TREESEED_SMTP_REPLY_TO: ${{ vars.TREESEED_SMTP_REPLY_TO }}
356
356
  RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }}
357
357
  TREESEED_MARKET_API_BASE_URL: ${{ vars.TREESEED_MARKET_API_BASE_URL }}
358
358
  TREESEED_PROJECT_ID: ${{ inputs.project_id || vars.TREESEED_PROJECT_ID }}