@treeseed/sdk 0.4.5 → 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.
@@ -6,30 +6,14 @@ import {
6
6
  createPersistentDeployTarget,
7
7
  loadDeployState
8
8
  } from "./operations/services/deploy.js";
9
- import { PRODUCTION_BRANCH, STAGING_BRANCH } from "./operations/services/git-workflow.js";
10
9
  import { loadCliDeployConfig } from "./operations/services/runtime-tools.js";
11
10
  import { collectCliPreflight } from "./operations/services/workspace-preflight.js";
12
- import { currentBranch, gitStatusPorcelain, repoRoot } from "./operations/services/workspace-save.js";
11
+ import { gitStatusPorcelain } from "./operations/services/workspace-save.js";
13
12
  import { isWorkspaceRoot } from "./operations/services/workspace-tools.js";
14
- function safeResolveRepoRoot(cwd) {
15
- try {
16
- return repoRoot(cwd);
17
- } catch {
18
- return null;
19
- }
20
- }
21
- function branchRoleFor(branchName) {
22
- if (!branchName) return "none";
23
- if (branchName === STAGING_BRANCH) return "staging";
24
- if (branchName === PRODUCTION_BRANCH) return "main";
25
- return "feature";
26
- }
27
- function environmentForBranchRole(branchRole) {
28
- if (branchRole === "staging") return "staging";
29
- if (branchRole === "main") return "prod";
30
- if (branchRole === "feature") return "local";
31
- return "none";
32
- }
13
+ import {
14
+ resolveTreeseedWorkflowPaths,
15
+ workflowEnvironmentForBranchRole
16
+ } from "./workflow/policy.js";
33
17
  function emptyPersistentEnvironments() {
34
18
  return {
35
19
  local: { initialized: false, lastValidatedAt: null, lastDeploymentTimestamp: null, lastDeployedUrl: null },
@@ -37,25 +21,54 @@ function emptyPersistentEnvironments() {
37
21
  prod: { initialized: false, lastValidatedAt: null, lastDeploymentTimestamp: null, lastDeployedUrl: null }
38
22
  };
39
23
  }
24
+ function readinessForEnvironment(state, scope) {
25
+ const blockers = [];
26
+ const warnings = [];
27
+ if (!state.deployConfigPresent) {
28
+ blockers.push("Missing treeseed.site.yaml.");
29
+ }
30
+ if (!state.files.machineConfig) {
31
+ blockers.push("Missing Treeseed machine config.");
32
+ }
33
+ if (scope === "local") {
34
+ if (!state.files.envLocal) {
35
+ blockers.push("Missing .env.local.");
36
+ }
37
+ if (!state.files.devVars) {
38
+ warnings.push("Missing .dev.vars.");
39
+ }
40
+ } else {
41
+ if (!state.persistentEnvironments[scope].initialized) {
42
+ blockers.push(`Environment ${scope} is not initialized.`);
43
+ }
44
+ }
45
+ return {
46
+ ready: blockers.length === 0,
47
+ blockers,
48
+ warnings
49
+ };
50
+ }
40
51
  function resolveTreeseedWorkflowState(cwd) {
41
- const workspaceRoot = isWorkspaceRoot(cwd);
42
- const treeseedConfigPath = resolve(cwd, "treeseed.site.yaml");
52
+ const resolved = resolveTreeseedWorkflowPaths(cwd);
53
+ const effectiveCwd = resolved.cwd;
54
+ const workspaceRoot = isWorkspaceRoot(effectiveCwd);
55
+ const treeseedConfigPath = resolve(effectiveCwd, "treeseed.site.yaml");
43
56
  const tenantRoot = existsSync(treeseedConfigPath);
44
- const root = safeResolveRepoRoot(cwd);
45
- const branchName = root ? currentBranch(root) || null : null;
46
- const branchRole = branchRoleFor(branchName);
57
+ const root = resolved.repoRoot;
58
+ const branchName = resolved.branchName;
59
+ const branchRole = resolved.branchRole;
47
60
  const dirtyWorktree = root ? gitStatusPorcelain(root).length > 0 : false;
48
- const preflight = collectCliPreflight({ cwd, requireAuth: false });
49
- const { configPath, keyPath } = getTreeseedMachineConfigPaths(cwd);
61
+ const preflight = collectCliPreflight({ cwd: effectiveCwd, requireAuth: false });
62
+ const { configPath, keyPath } = getTreeseedMachineConfigPaths(effectiveCwd);
50
63
  const state = {
51
- cwd,
64
+ cwd: effectiveCwd,
52
65
  workspaceRoot,
53
66
  tenantRoot,
54
67
  deployConfigPresent: tenantRoot,
55
68
  repoRoot: root,
56
69
  branchName,
57
70
  branchRole,
58
- environment: environmentForBranchRole(branchRole),
71
+ environment: workflowEnvironmentForBranchRole(branchRole),
59
72
  dirtyWorktree,
60
73
  preview: {
61
74
  enabled: false,
@@ -86,14 +99,19 @@ function resolveTreeseedWorkflowState(cwd) {
86
99
  devVars: existsSync(resolve(cwd, ".dev.vars"))
87
100
  },
88
101
  releaseReady: branchRole === "staging" && !dirtyWorktree,
102
+ readiness: {
103
+ local: { ready: false, blockers: [], warnings: [] },
104
+ staging: { ready: false, blockers: [], warnings: [] },
105
+ prod: { ready: false, blockers: [], warnings: [] }
106
+ },
89
107
  rollbackCandidates: [],
90
108
  recommendations: []
91
109
  };
92
110
  if (tenantRoot) {
93
111
  try {
94
- const deployConfig = loadCliDeployConfig(cwd);
112
+ const deployConfig = loadCliDeployConfig(effectiveCwd);
95
113
  for (const scope of ["local", "staging", "prod"]) {
96
- const deployState = loadDeployState(cwd, deployConfig, { target: createPersistentDeployTarget(scope) });
114
+ const deployState = loadDeployState(effectiveCwd, deployConfig, { target: createPersistentDeployTarget(scope) });
97
115
  state.persistentEnvironments[scope] = {
98
116
  initialized: deployState.readiness?.initialized === true || scope === "local",
99
117
  lastValidatedAt: deployState.readiness?.lastValidatedAt ?? deployState.readiness?.initializedAt ?? null,
@@ -123,7 +141,7 @@ function resolveTreeseedWorkflowState(cwd) {
123
141
  }
124
142
  }
125
143
  if (branchRole === "feature" && branchName) {
126
- const previewState = loadDeployState(cwd, deployConfig, { target: createBranchPreviewDeployTarget(branchName) });
144
+ const previewState = loadDeployState(effectiveCwd, deployConfig, { target: createBranchPreviewDeployTarget(branchName) });
127
145
  state.preview = {
128
146
  enabled: previewState.previewEnabled === true || previewState.readiness?.initialized === true,
129
147
  url: previewState.lastDeployedUrl ?? null,
@@ -133,6 +151,9 @@ function resolveTreeseedWorkflowState(cwd) {
133
151
  } catch {
134
152
  }
135
153
  }
154
+ state.readiness.local = readinessForEnvironment(state, "local");
155
+ state.readiness.staging = readinessForEnvironment(state, "staging");
156
+ state.readiness.prod = readinessForEnvironment(state, "prod");
136
157
  state.recommendations = recommendTreeseedNextSteps(state);
137
158
  return state;
138
159
  }
@@ -150,13 +171,10 @@ function recommendTreeseedNextSteps(state) {
150
171
  return recommendations;
151
172
  }
152
173
  if (state.branchRole === "feature") {
153
- if (state.dirtyWorktree) {
154
- recommendations.push({ operation: "save", reason: "Persist, verify, and push the current task branch before staging or closing it.", input: { message: "describe your change" } });
155
- } else {
156
- recommendations.push({ operation: "stage", reason: "Merge this task branch into staging and clean up branch artifacts.", input: { message: "describe the resolution" } });
157
- }
174
+ recommendations.push({ operation: "stage", reason: "Merge this task branch into staging and clean up branch artifacts.", input: { message: "describe the resolution" } });
175
+ recommendations.push({ operation: "save", reason: "Persist, verify, and push the current task branch before or independently of staging it.", input: { message: "describe your change" } });
158
176
  if (state.preview.enabled && state.branchName) {
159
- recommendations.push({ operation: "save", reason: "Save refreshes the branch preview deployment when one is enabled.", input: { message: "describe your change" } });
177
+ recommendations.push({ operation: "save", reason: "Save refreshes the branch preview deployment when one is enabled.", input: { message: "describe your change", preview: true } });
160
178
  } else {
161
179
  recommendations.push({ operation: "dev", reason: "Use the local environment for iterative work on this feature branch." });
162
180
  }
@@ -35,18 +35,21 @@ export type TreeseedSaveInput = {
35
35
  hotfix?: boolean;
36
36
  verify?: boolean;
37
37
  refreshPreview?: boolean;
38
+ preview?: boolean;
38
39
  rebase?: boolean;
39
40
  };
40
41
  export type TreeseedCloseInput = {
41
42
  message: string;
42
43
  deletePreview?: boolean;
43
44
  deleteBranch?: boolean;
45
+ autoSave?: boolean;
44
46
  };
45
47
  export type TreeseedStageInput = {
46
48
  message: string;
47
49
  waitForStaging?: boolean;
48
50
  deletePreview?: boolean;
49
51
  deleteBranch?: boolean;
52
+ autoSave?: boolean;
50
53
  };
51
54
  export type TreeseedSwitchInput = {
52
55
  branch?: string;
package/dist/workflow.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { resolveTreeseedWorkflowPaths } from "./workflow/policy.js";
1
2
  import {
2
3
  TreeseedWorkflowError,
3
4
  workflowClose,
@@ -28,7 +29,7 @@ class TreeseedWorkflowSdk {
28
29
  };
29
30
  return {
30
31
  context,
31
- cwd: () => context.cwd ?? process.cwd(),
32
+ cwd: () => resolveTreeseedWorkflowPaths(context.cwd ?? process.cwd()).cwd,
32
33
  write: context.write ?? defaultWrite,
33
34
  runStatus: async () => this.status(),
34
35
  runTasks: async () => this.tasks()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {