@treeseed/sdk 0.4.6 → 0.4.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.
@@ -37,6 +37,7 @@ export interface TemplateProductDefinition extends SdkTemplateCatalogEntry {
37
37
  artifactRoot: string;
38
38
  artifactManifestPath: string;
39
39
  templateRoot: string;
40
+ fulfillmentMode: 'packaged' | 'git';
40
41
  }
41
42
  export interface ResolvedTemplateDefinition {
42
43
  product: TemplateProductDefinition;
@@ -82,6 +83,7 @@ export declare function serializeTemplateRegistryEntry(product: Pick<TemplatePro
82
83
  templateApiVersion: number;
83
84
  minCliVersion: string;
84
85
  minCoreVersion: string | undefined;
86
+ fulfillmentMode: "git" | "packaged";
85
87
  source: import("../../sdk-types.ts").SdkTemplateCatalogSource;
86
88
  };
87
89
  export declare function exportTemplateCatalogYaml(options?: TemplateCatalogOptions): Promise<string>;
@@ -1,4 +1,5 @@
1
- import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { spawnSync } from "node:child_process";
2
3
  import { basename, dirname, relative, resolve } from "node:path";
3
4
  import { RemoteTemplateCatalogClient } from "../../template-catalog.js";
4
5
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
@@ -53,10 +54,10 @@ function validateTemplateProductShape(product) {
53
54
  if (product.status !== "draft" && product.status !== "live" && product.status !== "archived") {
54
55
  throw new Error(`Template product ${product.id} uses unsupported status "${product.status}".`);
55
56
  }
56
- if (!existsSync(product.artifactManifestPath)) {
57
+ if (product.fulfillmentMode === "packaged" && !existsSync(product.artifactManifestPath)) {
57
58
  throw new Error(`Template product ${product.id} points to a missing artifact manifest: ${product.artifactManifestPath}`);
58
59
  }
59
- if (!existsSync(product.templateRoot)) {
60
+ if (product.fulfillmentMode === "packaged" && !existsSync(product.templateRoot)) {
60
61
  throw new Error(`Template product ${product.id} points to a missing template payload: ${product.templateRoot}`);
61
62
  }
62
63
  }
@@ -101,9 +102,55 @@ function normalizeTemplateProduct(remoteProduct) {
101
102
  contentPath: `${remoteProduct.fulfillment.source.repoUrl}#${remoteProduct.id}`,
102
103
  artifactRoot,
103
104
  artifactManifestPath: resolve(artifactRoot, "template.config.json"),
105
+ templateRoot: resolve(artifactRoot, "template"),
106
+ fulfillmentMode: remoteProduct.fulfillment.mode ?? "packaged"
107
+ };
108
+ }
109
+ function sanitizeCacheSegment(value) {
110
+ return value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "source";
111
+ }
112
+ function resolveTemplateSourceCacheRoot(product, options) {
113
+ const cachePath = resolveTreeseedTemplateCatalogCachePath(options.cwd ?? process.cwd());
114
+ return resolve(dirname(cachePath), "templates", sanitizeCacheSegment(product.id), sanitizeCacheSegment(product.fulfillment.source.ref));
115
+ }
116
+ function runGit(commandArgs, cwd) {
117
+ const result = spawnSync("git", commandArgs, {
118
+ cwd,
119
+ stdio: "pipe",
120
+ encoding: "utf8"
121
+ });
122
+ if (result.status !== 0) {
123
+ throw new Error(result.stderr?.trim() || result.stdout?.trim() || `git ${commandArgs.join(" ")} failed`);
124
+ }
125
+ }
126
+ function materializeGitTemplateSource(product, options) {
127
+ const cacheRoot = resolveTemplateSourceCacheRoot(product, options);
128
+ const repoRoot = resolve(cacheRoot, "repo");
129
+ const source = product.fulfillment.source;
130
+ if (!existsSync(resolve(repoRoot, ".git"))) {
131
+ rmSync(cacheRoot, { recursive: true, force: true });
132
+ mkdirSync(cacheRoot, { recursive: true });
133
+ runGit(["clone", "--no-checkout", source.repoUrl, repoRoot]);
134
+ }
135
+ runGit(["fetch", "--all", "--tags"], repoRoot);
136
+ runGit(["checkout", "--force", source.ref], repoRoot);
137
+ const artifactRoot = resolve(repoRoot, source.directory);
138
+ return {
139
+ artifactRoot,
140
+ manifestPath: resolve(artifactRoot, "template.config.json"),
104
141
  templateRoot: resolve(artifactRoot, "template")
105
142
  };
106
143
  }
144
+ function resolveTemplateDefinitionPaths(product, options) {
145
+ if (existsSync(product.artifactManifestPath) && existsSync(product.templateRoot)) {
146
+ return {
147
+ artifactRoot: product.artifactRoot,
148
+ manifestPath: product.artifactManifestPath,
149
+ templateRoot: product.templateRoot
150
+ };
151
+ }
152
+ return materializeGitTemplateSource(product, options);
153
+ }
107
154
  function readTemplateCatalogCache(cachePath) {
108
155
  if (!existsSync(cachePath)) {
109
156
  return null;
@@ -268,11 +315,12 @@ async function resolveTemplateDefinition(id, options = {}, category) {
268
315
  throw new Error(`Unable to resolve template "${id}" in category "${category}".`);
269
316
  }
270
317
  validateTemplateProductShape(product);
271
- const manifest = loadJsonFile(product.artifactManifestPath);
318
+ const resolvedPaths = resolveTemplateDefinitionPaths(product, options);
319
+ const manifest = loadJsonFile(resolvedPaths.manifestPath);
272
320
  const definition = {
273
321
  product,
274
- manifestPath: product.artifactManifestPath,
275
- templateRoot: product.templateRoot,
322
+ manifestPath: resolvedPaths.manifestPath,
323
+ templateRoot: resolvedPaths.templateRoot,
276
324
  manifest
277
325
  };
278
326
  validateTemplateManifest(definition);
@@ -387,6 +435,7 @@ function serializeTemplateRegistryEntry(product) {
387
435
  templateApiVersion: product.templateApiVersion,
388
436
  minCliVersion: product.minCliVersion,
389
437
  minCoreVersion: product.minCoreVersion,
438
+ fulfillmentMode: product.fulfillment.mode ?? "packaged",
390
439
  source: product.fulfillment.source
391
440
  };
392
441
  }
@@ -777,6 +777,7 @@ export interface SdkTemplateCatalogEntry {
777
777
  minCliVersion: string;
778
778
  minCoreVersion?: string;
779
779
  fulfillment: {
780
+ mode?: 'packaged' | 'git';
780
781
  source: SdkTemplateCatalogSource;
781
782
  hooksPolicy: 'builtin_only' | 'trusted_only' | 'disabled';
782
783
  supportsReconcile: boolean;
@@ -66,6 +66,7 @@ function normalizeTemplateCatalogEntry(value) {
66
66
  minCliVersion: expectString(record.minCliVersion, "minCliVersion"),
67
67
  minCoreVersion: optionalString(record.minCoreVersion),
68
68
  fulfillment: {
69
+ mode: optionalString(fulfillment.mode),
69
70
  source: {
70
71
  kind: "git",
71
72
  repoUrl: expectString(source.repoUrl, "fulfillment.source.repoUrl"),
@@ -1,6 +1,7 @@
1
1
  import { resolveTreeseedWorkflowState } from '../workflow-state.ts';
2
2
  import type { TreeseedCloseInput, TreeseedConfigInput, TreeseedDestroyInput, TreeseedReleaseInput, TreeseedSaveInput, TreeseedStageInput, TreeseedSwitchInput, TreeseedTaskBranchMetadata, TreeseedWorkflowContext, TreeseedWorkflowDevInput, TreeseedWorkflowNextStep, TreeseedWorkflowOperationId, TreeseedWorkflowResult } from '../workflow.ts';
3
3
  type WorkflowWrite = NonNullable<TreeseedWorkflowContext['write']>;
4
+ type WorkflowStatePayload = ReturnType<typeof resolveTreeseedWorkflowState>;
4
5
  export type TreeseedWorkflowErrorCode = 'validation_failed' | 'merge_conflict' | 'missing_runtime_auth' | 'deployment_timeout' | 'confirmation_required' | 'unsupported_transport' | 'unsupported_state';
5
6
  export declare class TreeseedWorkflowError extends Error {
6
7
  code: TreeseedWorkflowErrorCode;
@@ -29,7 +30,75 @@ export declare function workflowStatus(helpers: WorkflowOperationHelpers): Promi
29
30
  export declare function workflowTasks(helpers: WorkflowOperationHelpers): Promise<TreeseedWorkflowResult<{
30
31
  tasks: TreeseedTaskBranchMetadata[];
31
32
  }>>;
32
- export declare function workflowConfig(helpers: WorkflowOperationHelpers, input?: TreeseedConfigInput): Promise<{
33
+ export declare function workflowConfig(helpers: WorkflowOperationHelpers, input?: TreeseedConfigInput): Promise<TreeseedWorkflowResult<{
34
+ mode: string;
35
+ scopes: ("local" | "staging" | "prod")[];
36
+ sync: "all" | "cloudflare" | "railway" | "none" | "github";
37
+ configPath: string;
38
+ keyPath: string;
39
+ repairs: TreeseedRepairAction[];
40
+ preflight: {
41
+ ok: boolean;
42
+ requireAuth: boolean;
43
+ missingCommands: string[];
44
+ failingAuth: string[];
45
+ checks: {
46
+ commands: {
47
+ [k: string]: {
48
+ installed: boolean;
49
+ path: string | null;
50
+ };
51
+ };
52
+ auth: {};
53
+ };
54
+ };
55
+ toolHealth: {
56
+ githubCli: {
57
+ name: any;
58
+ available: any;
59
+ detail: any;
60
+ };
61
+ ghActExtension: {
62
+ name: any;
63
+ available: any;
64
+ detail: any;
65
+ };
66
+ dockerDaemon: {
67
+ name: any;
68
+ available: any;
69
+ detail: any;
70
+ };
71
+ actVerificationReady: any;
72
+ remediation: string[];
73
+ };
74
+ result: {
75
+ scopes: string[];
76
+ updated: never[];
77
+ synced: {};
78
+ initialized: never[];
79
+ connectionChecks: never[];
80
+ };
81
+ state: import("../workflow-state.ts").TreeseedWorkflowState;
82
+ readiness: {
83
+ local: {
84
+ ready: boolean;
85
+ blockers: string[];
86
+ warnings: string[];
87
+ };
88
+ staging: {
89
+ ready: boolean;
90
+ blockers: string[];
91
+ warnings: string[];
92
+ };
93
+ prod: {
94
+ ready: boolean;
95
+ blockers: string[];
96
+ warnings: string[];
97
+ };
98
+ };
99
+ } & {
100
+ finalState: WorkflowStatePayload;
101
+ }> | {
33
102
  ok: true;
34
103
  operation: "config";
35
104
  payload: {
@@ -78,9 +147,6 @@ export declare function workflowConfig(helpers: WorkflowOperationHelpers, input?
78
147
  remediation: string[];
79
148
  };
80
149
  keyPath?: undefined;
81
- configPath?: undefined;
82
- result?: undefined;
83
- state?: undefined;
84
150
  };
85
151
  nextSteps: TreeseedWorkflowNextStep[];
86
152
  } | {
@@ -128,189 +194,195 @@ export declare function workflowConfig(helpers: WorkflowOperationHelpers, input?
128
194
  };
129
195
  secretsRevealed?: undefined;
130
196
  reports?: undefined;
131
- configPath?: undefined;
132
- result?: undefined;
133
- state?: undefined;
134
197
  };
135
198
  nextSteps: TreeseedWorkflowNextStep[];
136
- } | {
137
- ok: true;
138
- operation: "config";
139
- payload: {
199
+ }>;
200
+ export declare function workflowSwitch(helpers: WorkflowOperationHelpers, input: TreeseedSwitchInput): Promise<TreeseedWorkflowResult<{
201
+ branchName: string;
202
+ created: boolean;
203
+ resumed: boolean;
204
+ previewRequested: boolean;
205
+ preview: {
206
+ enabled: boolean;
207
+ url: string | null;
208
+ lastDeploymentTimestamp: string | null;
209
+ };
210
+ previewResult: Record<string, unknown> | null;
211
+ preconditions: {
212
+ cleanWorktreeRequired: boolean;
213
+ baseBranch: string;
214
+ };
215
+ } & {
216
+ finalState: WorkflowStatePayload;
217
+ }>>;
218
+ export declare function workflowDev(helpers: WorkflowOperationHelpers, input?: TreeseedWorkflowDevInput): Promise<TreeseedWorkflowResult<{
219
+ watch: boolean;
220
+ background: boolean;
221
+ command: string;
222
+ args: string[];
223
+ cwd: string;
224
+ pid: number | null;
225
+ exitCode: null;
226
+ runtime: {
140
227
  mode: string;
141
- scopes: ("local" | "staging" | "prod")[];
142
- sync: "all" | "cloudflare" | "railway" | "none" | "github";
143
- configPath: string;
144
- keyPath: string;
145
- repairs: TreeseedRepairAction[];
146
- preflight: {
147
- ok: boolean;
148
- requireAuth: boolean;
149
- missingCommands: string[];
150
- failingAuth: string[];
151
- checks: {
152
- commands: {
153
- [k: string]: {
154
- installed: boolean;
155
- path: string | null;
156
- };
157
- };
158
- auth: {};
159
- };
160
- };
161
- toolHealth: {
162
- githubCli: {
163
- name: any;
164
- available: any;
165
- detail: any;
166
- };
167
- ghActExtension: {
168
- name: any;
169
- available: any;
170
- detail: any;
171
- };
172
- dockerDaemon: {
173
- name: any;
174
- available: any;
175
- detail: any;
176
- };
177
- actVerificationReady: any;
178
- remediation: string[];
179
- };
180
- result: {
181
- scopes: string[];
182
- updated: never[];
183
- synced: {};
184
- initialized: never[];
185
- connectionChecks: never[];
186
- };
187
- state: import("../workflow-state.ts").TreeseedWorkflowState;
188
- secretsRevealed?: undefined;
189
- reports?: undefined;
228
+ apiBaseUrl: string;
229
+ webUrl: string;
190
230
  };
191
- nextSteps: TreeseedWorkflowNextStep[];
192
- }>;
193
- export declare function workflowSwitch(helpers: WorkflowOperationHelpers, input: TreeseedSwitchInput): Promise<{
194
- ok: boolean;
195
- operation: string;
196
- payload: {
197
- branchName: string;
198
- created: boolean;
199
- resumed: boolean;
200
- previewRequested: boolean;
201
- preview: {
202
- enabled: boolean;
203
- url: string | null;
204
- lastDeploymentTimestamp: string | null;
205
- };
206
- previewResult: Record<string, unknown> | null;
207
- state: import("../workflow-state.ts").TreeseedWorkflowState;
231
+ readiness: {
232
+ ready: boolean;
233
+ blockers: string[];
234
+ warnings: string[];
208
235
  };
209
- nextSteps: TreeseedWorkflowNextStep[];
210
- }>;
211
- export declare function workflowDev(helpers: WorkflowOperationHelpers, input?: TreeseedWorkflowDevInput): Promise<{
212
- ok: true;
213
- operation: "dev";
214
- payload: {
215
- watch: boolean;
216
- background: boolean;
217
- command: string;
218
- args: string[];
219
- cwd: string;
220
- pid: number | null;
221
- exitCode: null;
236
+ } & {
237
+ finalState: WorkflowStatePayload;
238
+ }> | TreeseedWorkflowResult<{
239
+ watch: boolean;
240
+ background: boolean;
241
+ command: string;
242
+ args: string[];
243
+ cwd: string;
244
+ pid: null;
245
+ exitCode: number;
246
+ runtime: {
247
+ mode: string;
248
+ apiBaseUrl: string;
249
+ webUrl: string;
222
250
  };
223
- } | {
224
- ok: boolean;
225
- operation: "dev";
226
- payload: {
227
- watch: boolean;
228
- background: boolean;
229
- command: string;
230
- args: string[];
231
- cwd: string;
232
- pid: null;
233
- exitCode: number;
251
+ readiness: {
252
+ ready: boolean;
253
+ blockers: string[];
254
+ warnings: string[];
234
255
  };
235
- }>;
236
- export declare function workflowSave(helpers: WorkflowOperationHelpers, input: TreeseedSaveInput): Promise<{
237
- ok: boolean;
238
- operation: string;
239
- payload: {
256
+ } & {
257
+ finalState: WorkflowStatePayload;
258
+ }>>;
259
+ export declare function workflowSave(helpers: WorkflowOperationHelpers, input: TreeseedSaveInput): Promise<TreeseedWorkflowResult<{
260
+ branch: string;
261
+ scope: string;
262
+ hotfix: boolean;
263
+ message: string;
264
+ commitSha: string;
265
+ commitCreated: boolean;
266
+ noChanges: boolean;
267
+ branchSync: {
268
+ remoteBranchExisted: boolean;
269
+ pulledRebase: boolean;
270
+ pushed: boolean;
271
+ createdRemoteBranch: boolean;
272
+ conflicts: boolean;
273
+ };
274
+ previewAction: Record<string, unknown>;
275
+ mergeConflict: null;
276
+ } & {
277
+ finalState: WorkflowStatePayload;
278
+ }>>;
279
+ export declare function workflowClose(helpers: WorkflowOperationHelpers, input: TreeseedCloseInput): Promise<TreeseedWorkflowResult<{
280
+ branchName: string;
281
+ message: string;
282
+ autoSaved: boolean;
283
+ autoSaveResult: ({
240
284
  branch: string;
241
285
  scope: string;
242
286
  hotfix: boolean;
243
287
  message: string;
244
288
  commitSha: string;
245
- previewRefresh: Record<string, unknown> | null;
246
- };
247
- nextSteps: TreeseedWorkflowNextStep[];
248
- }>;
249
- export declare function workflowClose(helpers: WorkflowOperationHelpers, input: TreeseedCloseInput): Promise<{
250
- ok: boolean;
251
- operation: string;
252
- payload: {
253
- branchName: string;
254
- message: string;
255
- deprecatedTag: {
256
- tagName: string;
257
- head: string;
258
- };
259
- previewCleanup: {
260
- performed: boolean;
261
- state: any;
262
- } | {
263
- performed: boolean;
289
+ commitCreated: boolean;
290
+ noChanges: boolean;
291
+ branchSync: {
292
+ remoteBranchExisted: boolean;
293
+ pulledRebase: boolean;
294
+ pushed: boolean;
295
+ createdRemoteBranch: boolean;
296
+ conflicts: boolean;
264
297
  };
265
- remoteDeleted: boolean;
266
- localDeleted: boolean;
298
+ previewAction: Record<string, unknown>;
299
+ mergeConflict: null;
300
+ } & {
301
+ finalState: WorkflowStatePayload;
302
+ }) | null;
303
+ deprecatedTag: {
304
+ tagName: string;
305
+ head: string;
267
306
  };
268
- nextSteps: TreeseedWorkflowNextStep[];
269
- }>;
270
- export declare function workflowStage(helpers: WorkflowOperationHelpers, input: TreeseedStageInput): Promise<{
271
- ok: boolean;
272
- operation: string;
273
- payload: {
274
- branchName: string;
275
- mergeTarget: string;
307
+ previewCleanup: {
308
+ performed: boolean;
309
+ state: any;
310
+ } | {
311
+ performed: boolean;
312
+ };
313
+ remoteDeleted: boolean;
314
+ localDeleted: boolean;
315
+ finalBranch: string;
316
+ } & {
317
+ finalState: WorkflowStatePayload;
318
+ }>>;
319
+ export declare function workflowStage(helpers: WorkflowOperationHelpers, input: TreeseedStageInput): Promise<TreeseedWorkflowResult<{
320
+ branchName: string;
321
+ mergeTarget: string;
322
+ message: string;
323
+ autoSaved: boolean;
324
+ autoSaveResult: ({
325
+ branch: string;
326
+ scope: string;
327
+ hotfix: boolean;
276
328
  message: string;
277
- deprecatedTag: {
278
- tagName: string;
279
- head: string;
280
- };
281
- stagingWait: {
282
- status: string;
283
- reason: string;
284
- branch?: undefined;
285
- } | {
286
- status: string;
287
- branch: string;
288
- reason?: undefined;
289
- };
290
- previewCleanup: {
291
- performed: boolean;
292
- state: any;
293
- } | {
294
- performed: boolean;
329
+ commitSha: string;
330
+ commitCreated: boolean;
331
+ noChanges: boolean;
332
+ branchSync: {
333
+ remoteBranchExisted: boolean;
334
+ pulledRebase: boolean;
335
+ pushed: boolean;
336
+ createdRemoteBranch: boolean;
337
+ conflicts: boolean;
295
338
  };
296
- remoteDeleted: boolean;
297
- localDeleted: boolean;
339
+ previewAction: Record<string, unknown>;
340
+ mergeConflict: null;
341
+ } & {
342
+ finalState: WorkflowStatePayload;
343
+ }) | null;
344
+ deprecatedTag: {
345
+ tagName: string;
346
+ head: string;
298
347
  };
299
- nextSteps: TreeseedWorkflowNextStep[];
300
- }>;
301
- export declare function workflowRelease(helpers: WorkflowOperationHelpers, input: TreeseedReleaseInput): Promise<{
302
- ok: boolean;
303
- operation: string;
304
- payload: {
305
- level: "patch" | "major" | "minor";
306
- rootVersion: string;
307
- releaseTag: string;
308
- stagingBranch: string;
309
- productionBranch: string;
310
- touchedPackages: unknown[];
348
+ stagingWait: {
349
+ status: string;
350
+ reason: string;
351
+ branch?: undefined;
352
+ } | {
353
+ status: string;
354
+ branch: string;
355
+ reason?: undefined;
311
356
  };
312
- nextSteps: TreeseedWorkflowNextStep[];
313
- }>;
357
+ previewCleanup: {
358
+ performed: boolean;
359
+ state: any;
360
+ } | {
361
+ performed: boolean;
362
+ };
363
+ remoteDeleted: boolean;
364
+ localDeleted: boolean;
365
+ finalBranch: string;
366
+ } & {
367
+ finalState: WorkflowStatePayload;
368
+ }>>;
369
+ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input: TreeseedReleaseInput): Promise<TreeseedWorkflowResult<{
370
+ level: "patch" | "major" | "minor";
371
+ rootVersion: string;
372
+ releaseTag: string;
373
+ releasedCommit: string;
374
+ stagingBranch: string;
375
+ productionBranch: string;
376
+ touchedPackages: unknown[];
377
+ finalBranch: string;
378
+ pushStatus: {
379
+ stagingPushed: boolean;
380
+ productionPushed: boolean;
381
+ tagPushed: boolean;
382
+ };
383
+ } & {
384
+ finalState: WorkflowStatePayload;
385
+ }>>;
314
386
  export declare function workflowDestroy(helpers: WorkflowOperationHelpers, input: TreeseedDestroyInput): Promise<{
315
387
  ok: boolean;
316
388
  operation: string;