@treeseed/sdk 0.6.7 → 0.6.9

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 (49) hide show
  1. package/dist/copilot.d.ts +15 -0
  2. package/dist/copilot.js +75 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +18 -0
  5. package/dist/managed-dependencies.d.ts +56 -0
  6. package/dist/managed-dependencies.js +668 -0
  7. package/dist/operations/providers/default.js +30 -1
  8. package/dist/operations/services/commit-message-provider.d.ts +33 -0
  9. package/dist/operations/services/commit-message-provider.js +319 -0
  10. package/dist/operations/services/config-runtime.js +50 -23
  11. package/dist/operations/services/git-remote-policy.d.ts +9 -0
  12. package/dist/operations/services/git-remote-policy.js +55 -0
  13. package/dist/operations/services/git-workflow.js +22 -3
  14. package/dist/operations/services/github-api.js +9 -4
  15. package/dist/operations/services/knowledge-coop-launch.js +4 -0
  16. package/dist/operations/services/local-dev.js +7 -2
  17. package/dist/operations/services/package-reference-policy.d.ts +70 -0
  18. package/dist/operations/services/package-reference-policy.js +330 -0
  19. package/dist/operations/services/project-platform.d.ts +4 -0
  20. package/dist/operations/services/project-platform.js +28 -4
  21. package/dist/operations/services/railway-deploy.d.ts +4 -1
  22. package/dist/operations/services/railway-deploy.js +76 -38
  23. package/dist/operations/services/repository-save-orchestrator.d.ts +172 -0
  24. package/dist/operations/services/repository-save-orchestrator.js +1462 -0
  25. package/dist/operations/services/workspace-dependency-mode.d.ts +70 -0
  26. package/dist/operations/services/workspace-dependency-mode.js +404 -0
  27. package/dist/operations/services/workspace-preflight.js +5 -0
  28. package/dist/operations/services/workspace-save.js +10 -6
  29. package/dist/operations-registry.js +5 -0
  30. package/dist/operations-types.d.ts +1 -0
  31. package/dist/platform/books-data.js +4 -1
  32. package/dist/platform/env.yaml +6 -3
  33. package/dist/reconcile/builtin-adapters.js +37 -7
  34. package/dist/scripts/cleanup-markdown.js +4 -0
  35. package/dist/scripts/prepare.js +14 -0
  36. package/dist/scripts/publish-package.js +5 -0
  37. package/dist/scripts/tenant-workflow-action.js +11 -2
  38. package/dist/verification.js +46 -13
  39. package/dist/workflow/operations.d.ts +381 -55
  40. package/dist/workflow/operations.js +725 -261
  41. package/dist/workflow-state.d.ts +40 -1
  42. package/dist/workflow-state.js +220 -17
  43. package/dist/workflow-support.d.ts +3 -0
  44. package/dist/workflow-support.js +34 -0
  45. package/dist/workflow.d.ts +19 -3
  46. package/dist/workflow.js +3 -3
  47. package/dist/wrangler-d1.js +6 -1
  48. package/package.json +17 -1
  49. package/templates/github/deploy.workflow.yml +59 -14
@@ -325,6 +325,31 @@ function ensureCloudflareDnsRecord(env, zoneId, record) {
325
325
  }
326
326
  })?.result ?? null;
327
327
  }
328
+ function unquoteDnsTxtContent(value) {
329
+ const trimmed = value.trim();
330
+ return trimmed.length >= 2 && trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
331
+ }
332
+ function dnsRecordContentMatches(actual, expected, type) {
333
+ const actualText = String(actual ?? "");
334
+ const expectedText = String(expected ?? "");
335
+ if (String(type ?? "").toUpperCase() === "TXT") {
336
+ return unquoteDnsTxtContent(actualText) === unquoteDnsTxtContent(expectedText);
337
+ }
338
+ return actualText === expectedText;
339
+ }
340
+ function dnsRecordProxiedMatches(actual, expected) {
341
+ return expected.proxied === void 0 || Boolean(actual?.proxied) === Boolean(expected.proxied);
342
+ }
343
+ function dnsRecordIdentityMatches(actual, expected) {
344
+ return actual?.name === expected.name && String(actual?.type ?? "").toUpperCase() === expected.type;
345
+ }
346
+ function dnsRecordMatches(actual, expected) {
347
+ return Boolean(actual) && dnsRecordIdentityMatches(actual, expected) && dnsRecordContentMatches(actual?.content, expected.content, expected.type) && dnsRecordProxiedMatches(actual, expected);
348
+ }
349
+ function dnsRecordsFromCurrentResult(input) {
350
+ const records = input.result?.state?.records;
351
+ return Array.isArray(records) ? records.filter((entry) => Boolean(entry) && typeof entry === "object") : [];
352
+ }
328
353
  function getCloudflarePagesDomain(env, projectName, domain) {
329
354
  const accountId = env.CLOUDFLARE_ACCOUNT_ID;
330
355
  if (!accountId) {
@@ -1600,7 +1625,7 @@ function observeDnsRecordUnit(input) {
1600
1625
  (record) => listCloudflareDnsRecords(env, zoneId, record.name).find((entry) => entry?.name === record.name && entry?.type === record.type) ?? null
1601
1626
  ) : [];
1602
1627
  const matches = desiredRecords.length > 0 && liveRecords.every(
1603
- (entry, index) => Boolean(entry) && entry?.content === desiredRecords[index]?.content && (desiredRecords[index]?.proxied === void 0 || Boolean(entry?.proxied) === Boolean(desiredRecords[index]?.proxied))
1628
+ (entry, index) => dnsRecordMatches(entry, desiredRecords[index])
1604
1629
  );
1605
1630
  return {
1606
1631
  exists: desiredRecords.length > 0 && liveRecords.every(Boolean),
@@ -1673,14 +1698,19 @@ function verifyDnsRecordUnit(input) {
1673
1698
  ]);
1674
1699
  }
1675
1700
  const checks = desiredRecords.map((record, index) => {
1676
- const live = listCloudflareDnsRecords(env, zoneId, record.name).find((entry) => entry?.name === record.name && entry?.type === record.type) ?? null;
1677
- const proxiedMatches = record.proxied === void 0 ? true : Boolean(live?.proxied) === Boolean(record.proxied);
1701
+ const live = listCloudflareDnsRecords(env, zoneId, record.name).find((entry) => dnsRecordIdentityMatches(entry, record)) ?? null;
1702
+ const reconciled = dnsRecordsFromCurrentResult(input).find(
1703
+ (entry) => dnsRecordIdentityMatches(entry, record)
1704
+ ) ?? null;
1705
+ const effective = dnsRecordMatches(live, record) ? live : reconciled ?? live;
1706
+ const contentMatches = dnsRecordContentMatches(effective?.content, record.content, record.type);
1707
+ const proxiedMatches = dnsRecordProxiedMatches(effective, record);
1678
1708
  return verificationCheck(`dns-record:${index + 1}`, `DNS record ${record.type} ${record.name} matches the desired value`, "api", {
1679
- exists: Boolean(live?.id),
1680
- configured: live?.content === record.content && proxiedMatches,
1709
+ exists: Boolean(effective?.id),
1710
+ configured: contentMatches && proxiedMatches,
1681
1711
  expected: `${record.type} ${record.name} -> ${record.content}${record.proxied === void 0 ? "" : ` proxied=${record.proxied}`}`,
1682
- observed: live ? `${live.type} ${live.name} -> ${live.content}${typeof live.proxied === "boolean" ? ` proxied=${live.proxied}` : ""}` : null,
1683
- issues: live?.id ? live.content === record.content && proxiedMatches ? [] : [`DNS record ${record.name} does not match the expected value.`] : [`DNS record ${record.type} ${record.name} is missing.`]
1712
+ observed: effective ? `${effective.type} ${effective.name} -> ${effective.content}${typeof effective.proxied === "boolean" ? ` proxied=${effective.proxied}` : ""}` : null,
1713
+ issues: effective?.id ? contentMatches && proxiedMatches ? [] : [`DNS record ${record.name} does not match the expected value.`] : [`DNS record ${record.type} ${record.name} is missing.`]
1684
1714
  });
1685
1715
  });
1686
1716
  return summarizeVerification(input.unit.unitId, checks);
@@ -336,6 +336,10 @@ async function runCli(argv = process.argv.slice(2)) {
336
336
  const mode = args.check ? 'check' : 'write';
337
337
  const targets = await collectMarkdownTargets(args.targets);
338
338
  if (targets.length === 0) {
339
+ if (mode === 'check') {
340
+ console.log('No Markdown files found for cleanup.');
341
+ return 0;
342
+ }
339
343
  console.error('No Markdown files found for cleanup.');
340
344
  return 1;
341
345
  }
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from 'node:child_process';
4
+
5
+ if (process.env.TREESEED_SKIP_PACKAGE_PREPARE === '1') {
6
+ process.exit(0);
7
+ }
8
+
9
+ const result = spawnSync('npm', ['run', 'build:dist'], {
10
+ stdio: 'inherit',
11
+ shell: process.platform === 'win32',
12
+ });
13
+
14
+ process.exit(result.status ?? 1);
@@ -3,6 +3,11 @@ import { resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  const packageRoot = resolve(fileURLToPath(new URL('..', import.meta.url)));
5
5
  const extraArgs = process.argv.slice(2);
6
+ const tagName = process.env.GITHUB_REF_NAME;
7
+ if (tagName && !/^\d+\.\d+\.\d+$/.test(tagName)) {
8
+ console.error(`Refusing to publish @treeseed/sdk from non-stable tag "${tagName}".`);
9
+ process.exit(1);
10
+ }
6
11
  const npmArgs = ['publish', '.', '--access', 'public'];
7
12
  if (process.env.GITHUB_ACTIONS === 'true') {
8
13
  npmArgs.push('--provenance');
@@ -75,7 +75,16 @@ async function main() {
75
75
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
76
76
  }
77
77
  }
78
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
78
+ function isCliEntrypoint() {
79
+ if (!process.argv[1]) {
80
+ return false;
81
+ }
82
+ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
83
+ return true;
84
+ }
85
+ return /(?:^|\/)tenant-workflow-action\.(?:ts|js)$/u.test(process.argv[1]);
86
+ }
87
+ if (isCliEntrypoint()) {
79
88
  await main();
80
89
  }
81
- export { parseArgs };
90
+ export { isCliEntrypoint, parseArgs };
@@ -3,6 +3,8 @@ import * as childProcess from "node:child_process";
3
3
  import { basename, relative, resolve } from "node:path";
4
4
  import { tmpdir } from "node:os";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { createTreeseedManagedToolEnv, resolveTreeseedToolBinary } from "./managed-dependencies.js";
7
+ const defaultActUbuntuLatestImage = "catthehacker/ubuntu:act-latest";
6
8
  function defaultWrite(message, stream = "stdout") {
7
9
  if (!message) return;
8
10
  (stream === "stderr" ? process.stderr : process.stdout).write(`${message}
@@ -11,7 +13,7 @@ function defaultWrite(message, stream = "stdout") {
11
13
  function run(command, args, cwd) {
12
14
  const result = childProcess.spawnSync(command, args, {
13
15
  cwd,
14
- env: process.env,
16
+ env: createTreeseedManagedToolEnv(process.env),
15
17
  stdio: "inherit"
16
18
  });
17
19
  return result.status ?? 1;
@@ -19,7 +21,7 @@ function run(command, args, cwd) {
19
21
  function check(command, args, cwd) {
20
22
  const result = childProcess.spawnSync(command, args, {
21
23
  cwd,
22
- env: process.env,
24
+ env: createTreeseedManagedToolEnv(process.env),
23
25
  stdio: "pipe",
24
26
  encoding: "utf8"
25
27
  });
@@ -39,8 +41,31 @@ function readPackageManifest(packageJsonPath) {
39
41
  return null;
40
42
  }
41
43
  }
44
+ function createActArgs(eventName, workflowPath) {
45
+ const image = process.env.TREESEED_VERIFY_ACT_UBUNTU_LATEST_IMAGE?.trim() || defaultActUbuntuLatestImage;
46
+ const args = ["act", eventName, "-W", workflowPath, "-j", "verify"];
47
+ if (image) {
48
+ args.push("-P", `ubuntu-latest=${image}`);
49
+ }
50
+ return args;
51
+ }
42
52
  function createWorkspaceActWorkflow(options) {
43
53
  const relativePackageRoot = relative(options.workspaceRoot, options.packageRoot).replace(/\\/g, "/");
54
+ const siblingLinkCommands = options.localTreeseedSiblingDependencies.map((packageName) => {
55
+ const [, packageShortName] = packageName.split("/");
56
+ const packageDir = `packages/${packageShortName}`;
57
+ const packageScope = packageName.split("/")[0];
58
+ const linkParent = `node_modules/${packageScope}`;
59
+ const linkTarget = relative(
60
+ resolve(options.packageRoot, linkParent),
61
+ resolve(options.workspaceRoot, packageDir)
62
+ ).replace(/\\/g, "/");
63
+ return [
64
+ `mkdir -p ${linkParent}`,
65
+ `rm -rf ${linkParent}/${packageShortName}`,
66
+ `ln -s ${linkTarget} ${linkParent}/${packageShortName}`
67
+ ].join("\n");
68
+ }).join("\n");
44
69
  const siblingPreparationCommands = options.localTreeseedSiblingDependencies.map((packageName) => {
45
70
  const packageDir = `packages/${packageName.split("/")[1]}`;
46
71
  const manifest = readPackageManifest(resolve(options.workspaceRoot, packageDir, "package.json"));
@@ -49,9 +74,9 @@ function createWorkspaceActWorkflow(options) {
49
74
  }
50
75
  const commands = [
51
76
  `if test -f ${packageDir}/package-lock.json; then`,
52
- ` npm --prefix ${packageDir} ci`,
77
+ ` npm --prefix ${packageDir} ci --workspaces=false`,
53
78
  "else",
54
- ` npm --prefix ${packageDir} install --no-audit --no-fund`,
79
+ ` npm --prefix ${packageDir} install --workspaces=false --no-audit --no-fund`,
55
80
  "fi"
56
81
  ];
57
82
  if (manifest.scripts?.["build:dist"]) {
@@ -101,20 +126,26 @@ ${siblingPreparationCommands.split("\n").map((line) => ` ${line}`).join
101
126
 
102
127
  ` : ""} - name: Install dependencies
103
128
  run: |
129
+ node -e "const fs = require('fs'); const p = JSON.parse(fs.readFileSync('package.json', 'utf8')); if (p.scripts) delete p.scripts.prepare; fs.writeFileSync('package.json', JSON.stringify(p, null, '\\t') + '\\n');"
104
130
  if test -f package-lock.json; then
105
- npm ci
131
+ npm ci --workspaces=false
106
132
  else
107
- npm install --no-audit --no-fund
133
+ npm install --workspaces=false --no-audit --no-fund
108
134
  fi
135
+ git checkout -- package.json || true
136
+
137
+ ${siblingLinkCommands ? ` - name: Link local Treeseed dependencies
138
+ run: |
139
+ ${siblingLinkCommands.split("\n").map((line) => ` ${line}`).join("\n")}
109
140
 
110
- - name: Verify package
141
+ ` : ""} - name: Verify package
111
142
  run: npm run verify:direct
112
143
  `,
113
144
  "utf8"
114
145
  );
115
146
  return {
116
147
  cwd: options.workspaceRoot,
117
- args: ["act", options.eventName, "-W", workflowPath, "-j", "verify"]
148
+ args: createActArgs(options.eventName, workflowPath)
118
149
  };
119
150
  }
120
151
  function findWorkspaceRoot(packageRoot) {
@@ -186,7 +217,8 @@ function getTreeseedVerifyDriverStatus(options = {}) {
186
217
  const workflowPresent = existsSync(workflowPath);
187
218
  const workspace = resolveLocalWorkspaceContext(packageRoot);
188
219
  const checkCommand = options.checkCommand ?? check;
189
- const ghAct = checkCommand("gh", ["act", "--version"], packageRoot);
220
+ const gh = options.checkCommand ? "gh" : resolveTreeseedToolBinary("gh") ?? "gh";
221
+ const ghAct = checkCommand(gh, ["act", "--version"], packageRoot);
190
222
  const docker = checkCommand("docker", ["info"], packageRoot);
191
223
  const prefersDirectForLocalWorkspace = !inGitHubActions && driver === "auto" && workspace.localTreeseedSiblingDependencies.length > 0;
192
224
  return {
@@ -211,6 +243,7 @@ function runTreeseedVerifyDriver(options = {}) {
211
243
  const status = getTreeseedVerifyDriverStatus(options);
212
244
  const runCommand = options.runCommand ?? run;
213
245
  const checkCommand = options.checkCommand ?? check;
246
+ const gh = options.runCommand || options.checkCommand ? "gh" : resolveTreeseedToolBinary("gh") ?? "gh";
214
247
  if (status.driver === "direct" || status.inGitHubActions) {
215
248
  return runCommand("npm", ["run", "verify:direct"], status.packageRoot);
216
249
  }
@@ -220,7 +253,7 @@ function runTreeseedVerifyDriver(options = {}) {
220
253
  return 1;
221
254
  }
222
255
  if (!status.ghActAvailable) {
223
- const detail = checkCommand("gh", ["act", "--version"], status.packageRoot).detail;
256
+ const detail = checkCommand(gh, ["act", "--version"], status.packageRoot).detail;
224
257
  write(detail || "Treeseed verify requires `gh act` when TREESEED_VERIFY_DRIVER=act.", "stderr");
225
258
  return 1;
226
259
  }
@@ -236,15 +269,15 @@ function runTreeseedVerifyDriver(options = {}) {
236
269
  eventName: status.eventName,
237
270
  localTreeseedSiblingDependencies: status.localTreeseedSiblingDependencies
238
271
  });
239
- return runCommand("gh", workspaceAct.args, workspaceAct.cwd);
272
+ return runCommand(gh, workspaceAct.args, workspaceAct.cwd);
240
273
  }
241
- return runCommand("gh", ["act", status.eventName, "-W", ".github/workflows/verify.yml", "-j", "verify"], status.packageRoot);
274
+ return runCommand(gh, createActArgs(status.eventName, ".github/workflows/verify.yml"), status.packageRoot);
242
275
  }
243
276
  if (status.prefersDirectForLocalWorkspace) {
244
277
  return runCommand("npm", ["run", "verify:direct"], status.packageRoot);
245
278
  }
246
279
  if (status.canUseAct) {
247
- return runCommand("gh", ["act", status.eventName, "-W", ".github/workflows/verify.yml", "-j", "verify"], status.packageRoot);
280
+ return runCommand(gh, createActArgs(status.eventName, ".github/workflows/verify.yml"), status.packageRoot);
248
281
  }
249
282
  if (!status.workflowPresent) {
250
283
  write("Treeseed verify warning: package-local verify workflow is missing; falling back to verify:direct.", "stderr");