cloving 1.0.1 → 1.2.0

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 (309) hide show
  1. package/README.md +251 -20
  2. package/assets/watch-fonts/fira-code-OFL.md +93 -0
  3. package/assets/watch-fonts/fira-code-latin-400-normal.woff2 +0 -0
  4. package/assets/watch-fonts/fira-code-latin-500-normal.woff2 +0 -0
  5. package/assets/watch-fonts/heebo-OFL.md +93 -0
  6. package/assets/watch-fonts/heebo-latin-500-normal.woff2 +0 -0
  7. package/assets/watch-fonts/heebo-latin-700-normal.woff2 +0 -0
  8. package/assets/watch-fonts/heebo-latin-800-normal.woff2 +0 -0
  9. package/assets/watch-fonts/manifest.json +112 -0
  10. package/assets/watch-fonts/space-grotesk-OFL.md +93 -0
  11. package/assets/watch-fonts/space-grotesk-latin-400-normal.woff2 +0 -0
  12. package/assets/watch-fonts/space-grotesk-latin-500-normal.woff2 +0 -0
  13. package/assets/watch-fonts/space-grotesk-latin-600-normal.woff2 +0 -0
  14. package/assets/watch-fonts/space-grotesk-latin-700-normal.woff2 +0 -0
  15. package/dist/src/authority/commands.d.ts +1 -0
  16. package/dist/src/authority/commands.js +33 -4
  17. package/dist/src/authority/dispatch-decision.d.ts +1 -0
  18. package/dist/src/authority/dispatch-decision.js +7 -4
  19. package/dist/src/authority/events.js +2 -2
  20. package/dist/src/authority/handoff-chain.d.ts +24 -0
  21. package/dist/src/authority/handoff-chain.js +46 -0
  22. package/dist/src/authority/reconciliation.d.ts +3 -0
  23. package/dist/src/authority/reconciliation.js +21 -5
  24. package/dist/src/authority/transitions.d.ts +1 -1
  25. package/dist/src/authority/transitions.js +5 -0
  26. package/dist/src/broker/actor/authority-queries.d.ts +4 -0
  27. package/dist/src/broker/actor/authority-queries.js +51 -2
  28. package/dist/src/broker/actor/authority-session.d.ts +2 -0
  29. package/dist/src/broker/actor/authority-session.js +3 -4
  30. package/dist/src/broker/actor/delivery-intents.d.ts +1 -0
  31. package/dist/src/broker/actor/effect-delivery.js +9 -0
  32. package/dist/src/broker/authority-admission.js +13 -5
  33. package/dist/src/chats/analysis-batches.d.ts +42 -0
  34. package/dist/src/chats/analysis-batches.js +97 -0
  35. package/dist/src/chats/analysis-checkpoint.d.ts +29 -0
  36. package/dist/src/chats/analysis-checkpoint.js +31 -0
  37. package/dist/src/chats/analysis-contract.d.ts +80 -0
  38. package/dist/src/chats/analysis-contract.js +16 -0
  39. package/dist/src/chats/analysis-data.d.ts +3 -0
  40. package/dist/src/chats/analysis-data.js +85 -0
  41. package/dist/src/chats/analysis-input.d.ts +18 -0
  42. package/dist/src/chats/analysis-input.js +97 -0
  43. package/dist/src/chats/analysis-links.d.ts +2 -0
  44. package/dist/src/chats/analysis-links.js +25 -0
  45. package/dist/src/chats/analysis-merge.d.ts +16 -0
  46. package/dist/src/chats/analysis-merge.js +134 -0
  47. package/dist/src/chats/analysis-prompt.d.ts +18 -0
  48. package/dist/src/chats/analysis-prompt.js +42 -0
  49. package/dist/src/chats/analysis-proposal.d.ts +4 -0
  50. package/dist/src/chats/analysis-proposal.js +41 -0
  51. package/dist/src/chats/analysis-provenance.d.ts +4 -0
  52. package/dist/src/chats/analysis-provenance.js +99 -0
  53. package/dist/src/chats/analysis-shape.d.ts +4 -0
  54. package/dist/src/chats/analysis-shape.js +235 -0
  55. package/dist/src/chats/analysis.d.ts +3 -0
  56. package/dist/src/chats/analysis.js +42 -0
  57. package/dist/src/chats/corpus-input.d.ts +5 -0
  58. package/dist/src/chats/corpus-input.js +288 -0
  59. package/dist/src/chats/destination.js +3 -1
  60. package/dist/src/chats/import.d.ts +8 -6
  61. package/dist/src/chats/import.js +195 -23
  62. package/dist/src/chats/input-file.d.ts +3 -0
  63. package/dist/src/chats/input-file.js +32 -0
  64. package/dist/src/chats/normalize.d.ts +11 -2
  65. package/dist/src/chats/normalize.js +18 -2
  66. package/dist/src/chats/previous-import.d.ts +5 -0
  67. package/dist/src/chats/previous-import.js +34 -0
  68. package/dist/src/chats/reader.d.ts +7 -1
  69. package/dist/src/chats/reader.js +46 -15
  70. package/dist/src/chats/redact-import.d.ts +14 -0
  71. package/dist/src/chats/redact-import.js +141 -0
  72. package/dist/src/chats/redaction.d.ts +41 -0
  73. package/dist/src/chats/redaction.js +161 -0
  74. package/dist/src/chats/reference-mapping.d.ts +16 -0
  75. package/dist/src/chats/reference-mapping.js +153 -0
  76. package/dist/src/chats/revision-input.d.ts +3 -0
  77. package/dist/src/chats/revision-input.js +180 -0
  78. package/dist/src/chats/revision.d.ts +48 -0
  79. package/dist/src/chats/revision.js +94 -0
  80. package/dist/src/chats/selection.d.ts +25 -0
  81. package/dist/src/chats/selection.js +109 -0
  82. package/dist/src/chats/source.d.ts +23 -0
  83. package/dist/src/chats/source.js +135 -0
  84. package/dist/src/chats/structure.d.ts +57 -0
  85. package/dist/src/chats/structure.js +216 -0
  86. package/dist/src/chats/workflow-generation-projection.d.ts +8 -0
  87. package/dist/src/chats/workflow-generation-projection.js +75 -0
  88. package/dist/src/chats/workflow-generation-source.d.ts +25 -0
  89. package/dist/src/chats/workflow-generation-source.js +124 -0
  90. package/dist/src/chats/workflow-generation-text.d.ts +23 -0
  91. package/dist/src/chats/workflow-generation-text.js +74 -0
  92. package/dist/src/chats/workflow-generation.d.ts +69 -0
  93. package/dist/src/chats/workflow-generation.js +142 -0
  94. package/dist/src/cli/arguments.d.ts +8 -0
  95. package/dist/src/cli/arguments.js +94 -13
  96. package/dist/src/cli/client.d.ts +1 -0
  97. package/dist/src/cli/client.js +38 -1
  98. package/dist/src/cli/commands.d.ts +3 -0
  99. package/dist/src/cli/commands.js +29 -11
  100. package/dist/src/cli/main.d.ts +2 -1
  101. package/dist/src/cli/main.js +61 -14
  102. package/dist/src/cli/render.d.ts +3 -0
  103. package/dist/src/cli/render.js +122 -7
  104. package/dist/src/cli/usage-render.d.ts +3 -0
  105. package/dist/src/cli/usage-render.js +28 -0
  106. package/dist/src/cli/usage.d.ts +3 -2
  107. package/dist/src/cli/usage.js +42 -7
  108. package/dist/src/cli/watch-actions.d.ts +10 -0
  109. package/dist/src/cli/watch-actions.js +58 -0
  110. package/dist/src/cli/watch-board-view.d.ts +44 -1
  111. package/dist/src/cli/watch-board-view.js +137 -18
  112. package/dist/src/cli/watch-board.d.ts +7 -1
  113. package/dist/src/cli/watch-board.js +48 -11
  114. package/dist/src/cli/watch-card-client.d.ts +1 -0
  115. package/dist/src/cli/watch-card-client.js +44 -0
  116. package/dist/src/cli/watch-card-forms.d.ts +2 -0
  117. package/dist/src/cli/watch-card-forms.js +33 -0
  118. package/dist/src/cli/watch-card-reply.d.ts +5 -0
  119. package/dist/src/cli/watch-card-reply.js +34 -0
  120. package/dist/src/cli/watch-card-view.d.ts +40 -0
  121. package/dist/src/cli/watch-card-view.js +86 -0
  122. package/dist/src/cli/watch-client.d.ts +1 -0
  123. package/dist/src/cli/watch-client.js +122 -0
  124. package/dist/src/cli/watch-model.d.ts +10 -4
  125. package/dist/src/cli/watch-model.js +61 -67
  126. package/dist/src/cli/watch-page.d.ts +1 -1
  127. package/dist/src/cli/watch-page.js +89 -86
  128. package/dist/src/cli/watch-phases.js +3 -3
  129. package/dist/src/cli/watch-pr-board.d.ts +3 -0
  130. package/dist/src/cli/watch-pr-board.js +26 -0
  131. package/dist/src/cli/watch-pr-command.d.ts +18 -0
  132. package/dist/src/cli/watch-pr-command.js +74 -0
  133. package/dist/src/cli/watch-pr-identity.d.ts +32 -0
  134. package/dist/src/cli/watch-pr-identity.js +87 -0
  135. package/dist/src/cli/watch-pr-observer.d.ts +20 -0
  136. package/dist/src/cli/watch-pr-observer.js +150 -0
  137. package/dist/src/cli/watch-pr-settings.d.ts +8 -0
  138. package/dist/src/cli/watch-pr-settings.js +47 -0
  139. package/dist/src/cli/watch-pull-request.d.ts +10 -0
  140. package/dist/src/cli/watch-pull-request.js +130 -0
  141. package/dist/src/cli/watch-reads.d.ts +7 -1
  142. package/dist/src/cli/watch-reads.js +44 -18
  143. package/dist/src/cli/watch-render.d.ts +29 -2
  144. package/dist/src/cli/watch-render.js +11 -3
  145. package/dist/src/cli/watch-run-input.d.ts +14 -0
  146. package/dist/src/cli/watch-run-input.js +49 -0
  147. package/dist/src/cli/watch-server.d.ts +3 -0
  148. package/dist/src/cli/watch-server.js +77 -8
  149. package/dist/src/cli/watch-start-view.d.ts +18 -0
  150. package/dist/src/cli/watch-start-view.js +77 -0
  151. package/dist/src/cli/watch-turn.d.ts +1 -0
  152. package/dist/src/cli/watch-turn.js +8 -3
  153. package/dist/src/cli/watch-usage.d.ts +6 -0
  154. package/dist/src/cli/watch-usage.js +65 -0
  155. package/dist/src/cli/watch-view.d.ts +22 -0
  156. package/dist/src/cli/watch-view.js +111 -19
  157. package/dist/src/cli.js +48 -6
  158. package/dist/src/daemon/analysis-launch.d.ts +5 -0
  159. package/dist/src/daemon/analysis-launch.js +81 -0
  160. package/dist/src/daemon/analysis-token.d.ts +6 -0
  161. package/dist/src/daemon/analysis-token.js +88 -0
  162. package/dist/src/daemon/analysis-worker.d.ts +20 -0
  163. package/dist/src/daemon/analysis-worker.js +271 -0
  164. package/dist/src/daemon/chats-batch-binding.d.ts +23 -0
  165. package/dist/src/daemon/chats-batch-binding.js +99 -0
  166. package/dist/src/daemon/chats-capture.d.ts +18 -0
  167. package/dist/src/daemon/chats-capture.js +194 -0
  168. package/dist/src/daemon/chats-checkpoints.d.ts +41 -0
  169. package/dist/src/daemon/chats-checkpoints.js +186 -0
  170. package/dist/src/daemon/chats-files.d.ts +42 -0
  171. package/dist/src/daemon/chats-files.js +252 -0
  172. package/dist/src/daemon/chats-follow-up.d.ts +38 -0
  173. package/dist/src/daemon/chats-follow-up.js +180 -0
  174. package/dist/src/daemon/chats-question.d.ts +15 -0
  175. package/dist/src/daemon/chats-question.js +93 -0
  176. package/dist/src/daemon/chats-worker.d.ts +13 -0
  177. package/dist/src/daemon/chats-worker.js +201 -0
  178. package/dist/src/daemon/claude-turn.d.ts +11 -0
  179. package/dist/src/daemon/claude-turn.js +96 -15
  180. package/dist/src/daemon/claude-worker.d.ts +2 -1
  181. package/dist/src/daemon/claude-worker.js +92 -45
  182. package/dist/src/daemon/codex-stream.js +16 -0
  183. package/dist/src/daemon/daemon.d.ts +8 -1
  184. package/dist/src/daemon/daemon.js +51 -14
  185. package/dist/src/daemon/delivery-router.js +6 -0
  186. package/dist/src/daemon/github-calls.d.ts +4 -0
  187. package/dist/src/daemon/github-calls.js +16 -7
  188. package/dist/src/daemon/github-ci.d.ts +6 -0
  189. package/dist/src/daemon/github-ci.js +170 -0
  190. package/dist/src/daemon/github-merge-call.d.ts +14 -0
  191. package/dist/src/daemon/github-merge-call.js +73 -0
  192. package/dist/src/daemon/github-merge.d.ts +3 -5
  193. package/dist/src/daemon/github-merge.js +52 -95
  194. package/dist/src/daemon/github-methods.d.ts +28 -0
  195. package/dist/src/daemon/github-methods.js +84 -16
  196. package/dist/src/daemon/github-operation.d.ts +2 -0
  197. package/dist/src/daemon/github-retry.d.ts +6 -0
  198. package/dist/src/daemon/github-retry.js +49 -0
  199. package/dist/src/daemon/github-ticket-status.d.ts +3 -0
  200. package/dist/src/daemon/github-ticket-status.js +294 -0
  201. package/dist/src/daemon/github-worker.js +2 -1
  202. package/dist/src/daemon/handoff-worker.d.ts +10 -0
  203. package/dist/src/daemon/handoff-worker.js +232 -0
  204. package/dist/src/daemon/issue-queue.d.ts +2 -0
  205. package/dist/src/daemon/issue-queue.js +186 -43
  206. package/dist/src/daemon/notify-journal.d.ts +18 -0
  207. package/dist/src/daemon/notify-journal.js +108 -0
  208. package/dist/src/daemon/notify-message.js +2 -1
  209. package/dist/src/daemon/notify.js +176 -37
  210. package/dist/src/daemon/operation-journal.d.ts +1 -0
  211. package/dist/src/daemon/operation-journal.js +4 -2
  212. package/dist/src/daemon/retrying-turn.d.ts +12 -0
  213. package/dist/src/daemon/retrying-turn.js +231 -0
  214. package/dist/src/daemon/ticket-dependency-waits.d.ts +18 -0
  215. package/dist/src/daemon/ticket-dependency-waits.js +60 -0
  216. package/dist/src/daemon/ticket-registration.d.ts +8 -4
  217. package/dist/src/daemon/ticket-registration.js +76 -22
  218. package/dist/src/daemon/ticket-source.d.ts +4 -1
  219. package/dist/src/daemon/ticket-source.js +8 -2
  220. package/dist/src/decision-visibility.d.ts +2 -0
  221. package/dist/src/decision-visibility.js +10 -0
  222. package/dist/src/execution-pin.d.ts +4 -0
  223. package/dist/src/execution-pin.js +23 -2
  224. package/dist/src/host/start-delivery.d.ts +2 -1
  225. package/dist/src/host/start-delivery.js +1 -1
  226. package/dist/src/host/step-bridge-host.d.ts +1 -0
  227. package/dist/src/host/step-bridge-host.js +16 -0
  228. package/dist/src/host/world-reconciliation.d.ts +3 -0
  229. package/dist/src/host/world-reconciliation.js +9 -0
  230. package/dist/src/host/world-runs.js +14 -1
  231. package/dist/src/issue-identity.d.ts +6 -0
  232. package/dist/src/issue-identity.js +23 -0
  233. package/dist/src/service/queue-runs.d.ts +3 -0
  234. package/dist/src/service/queue-runs.js +39 -0
  235. package/dist/src/service/registry.d.ts +8 -2
  236. package/dist/src/service/registry.js +20 -8
  237. package/dist/src/service/replies.d.ts +2 -0
  238. package/dist/src/service/run-handlers.js +54 -30
  239. package/dist/src/service/service.d.ts +3 -0
  240. package/dist/src/service/service.js +3 -1
  241. package/dist/src/service/submission-handler.d.ts +1 -0
  242. package/dist/src/service/submission-handler.js +92 -13
  243. package/dist/src/service/worker-totals.d.ts +3 -0
  244. package/dist/src/service/worker-totals.js +52 -0
  245. package/dist/src/step-bridge.d.ts +9 -0
  246. package/dist/src/steps/handoff.d.ts +2 -0
  247. package/dist/src/steps/handoff.js +13 -0
  248. package/dist/src/ticket-dependencies.d.ts +1 -0
  249. package/dist/src/ticket-dependencies.js +15 -0
  250. package/dist/src/ticket-status.d.ts +8 -0
  251. package/dist/src/ticket-status.js +48 -0
  252. package/dist/src/token-prices.d.ts +21 -0
  253. package/dist/src/token-prices.js +108 -0
  254. package/dist/src/worker-usage.d.ts +31 -0
  255. package/dist/src/worker-usage.js +44 -0
  256. package/dist/src/workflow-admission.d.ts +1 -0
  257. package/dist/src/workflow-candidate.d.ts +29 -0
  258. package/dist/src/workflow-candidate.js +60 -0
  259. package/dist/src/workflow-contracts.d.ts +4 -0
  260. package/dist/src/workflow-contracts.js +32 -5
  261. package/dist/src/workflow-declaration.d.ts +12 -1
  262. package/dist/src/workflow-declaration.js +71 -13
  263. package/dist/src/workflow-discovery.d.ts +18 -0
  264. package/dist/src/workflow-discovery.js +33 -0
  265. package/dist/src/workflow-refusals.d.ts +6 -0
  266. package/dist/src/workflow-refusals.js +39 -0
  267. package/dist/src/workflows.d.ts +3 -1
  268. package/dist/src/workflows.js +2 -0
  269. package/dist/tools/admit-workflow.d.ts +13 -0
  270. package/dist/tools/admit-workflow.js +94 -0
  271. package/dist/tools/self-host-processes.js +7 -2
  272. package/dist/tools/self-host-project.d.ts +17 -0
  273. package/dist/tools/self-host-project.js +177 -0
  274. package/dist/tools/self-host-workflows.d.ts +1 -1
  275. package/dist/tools/self-host-workflows.js +2 -2
  276. package/dist/tools/self-host.js +30 -84
  277. package/dist/tools/self-update-host.d.ts +7 -0
  278. package/dist/tools/self-update-host.js +48 -1
  279. package/dist/tools/self-update.d.ts +1 -0
  280. package/dist/tools/self-update.js +85 -13
  281. package/dist/tools/workflow-bundle-dependencies.d.ts +5 -0
  282. package/dist/tools/workflow-bundle-dependencies.js +93 -0
  283. package/dist/tools/workflow-bundles.d.ts +20 -0
  284. package/dist/tools/workflow-bundles.js +173 -0
  285. package/dist/tools/workflow-candidate-counts.d.ts +8 -0
  286. package/dist/tools/workflow-candidate-counts.js +251 -0
  287. package/dist/tools/workflow-candidate-program.d.ts +11 -0
  288. package/dist/tools/workflow-candidate-program.js +78 -0
  289. package/dist/tools/workflow-candidate-source.d.ts +6 -0
  290. package/dist/tools/workflow-candidate-source.js +107 -0
  291. package/dist/tools/workflow-candidate-syntax.d.ts +15 -0
  292. package/dist/tools/workflow-candidate-syntax.js +219 -0
  293. package/dist/tools/workflow-folder-capture.d.ts +17 -0
  294. package/dist/tools/workflow-folder-capture.js +216 -0
  295. package/dist/tools/workflow-folders.d.ts +33 -0
  296. package/dist/tools/workflow-folders.js +150 -0
  297. package/dist/tools/workflow-source-effects.d.ts +10 -0
  298. package/dist/tools/workflow-source-effects.js +213 -0
  299. package/dist/workflows/analyze-chats.d.ts +15 -0
  300. package/dist/workflows/analyze-chats.declaration.json +22 -0
  301. package/dist/workflows/analyze-chats.js +250 -0
  302. package/dist/workflows/develop-prd.js +71 -3
  303. package/dist/workflows/develop-ticket.d.ts +1 -7
  304. package/dist/workflows/develop-ticket.js +47 -141
  305. package/dist/workflows/issue-labels.d.ts +3 -1
  306. package/dist/workflows/issue-labels.js +34 -0
  307. package/dist/workflows/ticket-prompts.d.ts +35 -0
  308. package/dist/workflows/ticket-prompts.js +143 -0
  309. package/package.json +25 -5
@@ -1,3 +1,22 @@
1
+ export function lockRequiresInstall(before, after) {
2
+ try {
3
+ return !isDeepStrictEqual(lockPackages(before), lockPackages(after));
4
+ }
5
+ catch {
6
+ return true;
7
+ }
8
+ }
9
+ function lockPackages(text) {
10
+ const lock = JSON.parse(text);
11
+ if (typeof lock !== "object" || lock === null || !("packages" in lock))
12
+ throw new Error("invalid package lock");
13
+ const packages = lock.packages;
14
+ if (typeof packages !== "object" ||
15
+ packages === null ||
16
+ Array.isArray(packages))
17
+ throw new Error("invalid package lock packages");
18
+ return Object.fromEntries(Object.entries(packages).filter(([path]) => path !== ""));
19
+ }
1
20
  async function clean(git) {
2
21
  const status = await git([
3
22
  "status",
@@ -9,6 +28,24 @@ async function clean(git) {
9
28
  return false;
10
29
  return !/^[a-zS] /m.test(await git(["ls-files", "-v"]));
11
30
  }
31
+ async function installRequired(git, update) {
32
+ try {
33
+ await git([
34
+ "diff",
35
+ "--exit-code",
36
+ update.head,
37
+ update.target,
38
+ "--",
39
+ "package-lock.json",
40
+ ]);
41
+ return false;
42
+ }
43
+ catch {
44
+ const before = await git(["show", `${update.head}:package-lock.json`]);
45
+ const after = await git(["show", `${update.target}:package-lock.json`]);
46
+ return lockRequiresInstall(before, after);
47
+ }
48
+ }
12
49
  async function candidate(settings) {
13
50
  const git = settings.git;
14
51
  await git(["fetch", "origin"]);
@@ -26,10 +63,40 @@ async function candidate(settings) {
26
63
  await git(["merge-base", "--is-ancestor", head, target]);
27
64
  if (!(await clean(git)))
28
65
  return;
66
+ await markPending(settings.directory);
29
67
  if (await settings.active())
30
- return;
68
+ return "waiting";
31
69
  return { branch, head, target };
32
70
  }
71
+ async function markPending(directory, publication = "initial") {
72
+ if (publication === "renew") {
73
+ const timestamp = Number(await readFile(join(directory, "pending-self-update"), "utf8"));
74
+ if (timestamp > 0 && Date.now() - timestamp < 30 * 60 * 1000)
75
+ return;
76
+ }
77
+ await mkdir(directory, { recursive: true });
78
+ const temporary = join(directory, `pending-self-update-${randomUUID()}.tmp`);
79
+ try {
80
+ await writeFile(temporary, `${String(Date.now())}\n`, {
81
+ flag: "wx",
82
+ mode: 0o600,
83
+ flush: true,
84
+ });
85
+ if (publication === "renew") {
86
+ await rename(temporary, join(directory, "pending-self-update"));
87
+ return;
88
+ }
89
+ await link(temporary, join(directory, "pending-self-update")).catch((error) => {
90
+ if (!(error instanceof Error) ||
91
+ !("code" in error) ||
92
+ error.code !== "EEXIST")
93
+ throw error;
94
+ });
95
+ }
96
+ finally {
97
+ await rm(temporary, { force: true });
98
+ }
99
+ }
33
100
  async function failedTargets(directory) {
34
101
  try {
35
102
  return (await readFile(join(directory, "failed-self-updates"), "utf8")).split("\n");
@@ -70,21 +137,16 @@ export function selfUpdater(settings) {
70
137
  if (disabled || checking)
71
138
  return;
72
139
  checking = true;
140
+ let waiting = false;
73
141
  try {
74
142
  const update = await candidate(settings).catch(() => undefined);
75
- if (!update)
143
+ if (update === "waiting") {
144
+ waiting = true;
76
145
  return;
77
- try {
78
- await settings.git([
79
- "diff",
80
- "--exit-code",
81
- update.head,
82
- update.target,
83
- "--",
84
- "package-lock.json",
85
- ]);
86
146
  }
87
- catch {
147
+ if (!update)
148
+ return;
149
+ if (await installRequired(settings.git, update).catch(() => true)) {
88
150
  disabled = true;
89
151
  settings.error("Self-update disabled: package-lock.json changed; update and install by hand.\n");
90
152
  return;
@@ -92,14 +154,24 @@ export function selfUpdater(settings) {
92
154
  const failed = await failedTargets(settings.directory).catch(() => undefined);
93
155
  if (!failed || failed.includes(update.target))
94
156
  return;
157
+ await markPending(settings.directory, "renew");
158
+ waiting = await settings.active().catch(() => true);
159
+ if (waiting)
160
+ return;
95
161
  disabled = true;
96
162
  return await updated(settings, update);
97
163
  }
98
164
  finally {
99
165
  checking = false;
166
+ if (!waiting)
167
+ await rm(join(settings.directory, "pending-self-update"), {
168
+ force: true,
169
+ });
100
170
  }
101
171
  },
102
172
  };
103
173
  }
104
- import { appendFile, mkdir, readFile } from "node:fs/promises";
174
+ import { randomUUID } from "node:crypto";
175
+ import { appendFile, link, mkdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
105
176
  import { join } from "node:path";
177
+ import { isDeepStrictEqual } from "node:util";
@@ -0,0 +1,5 @@
1
+ export declare function workflowPackageRoot(directory: string): Promise<string>;
2
+ export declare function workflowBundleDependencies(input: {
3
+ readonly product: string;
4
+ readonly packages: readonly string[];
5
+ }): Promise<string>;
@@ -0,0 +1,93 @@
1
+ import { createHash } from "node:crypto";
2
+ import { access, readFile, realpath } from "node:fs/promises";
3
+ import { findPackageJSON } from "node:module";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { record } from "../src/protocol.js";
6
+ export async function workflowPackageRoot(directory) {
7
+ const root = resolve(directory);
8
+ try {
9
+ await access(join(root, "package.json"));
10
+ return root;
11
+ }
12
+ catch (error) {
13
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
14
+ throw error;
15
+ }
16
+ const parent = dirname(root);
17
+ if (parent === root)
18
+ throw new Error("workflow-package-unavailable");
19
+ return workflowPackageRoot(parent);
20
+ }
21
+ function references(manifest) {
22
+ const dependencies = record(manifest["dependencies"]) ?? {};
23
+ const optional = record(manifest["optionalDependencies"]) ?? {};
24
+ const peers = record(manifest["peerDependencies"]) ?? {};
25
+ const peerMetadata = record(manifest["peerDependenciesMeta"]) ?? {};
26
+ return Object.keys({ ...dependencies, ...optional, ...peers })
27
+ .sort()
28
+ .map((name) => ({
29
+ name,
30
+ optional: Object.hasOwn(optional, name) ||
31
+ (!Object.hasOwn(dependencies, name) &&
32
+ record(peerMetadata[name])?.["optional"] === true),
33
+ }));
34
+ }
35
+ async function manifestFile(from, reference) {
36
+ try {
37
+ const file = findPackageJSON(reference.name, from);
38
+ if (file !== undefined)
39
+ return await realpath(file);
40
+ }
41
+ catch (error) {
42
+ if (!(error instanceof Error &&
43
+ "code" in error &&
44
+ error.code === "ERR_MODULE_NOT_FOUND"))
45
+ throw error;
46
+ }
47
+ if (reference.optional)
48
+ return undefined;
49
+ throw new Error("workflow-dependency-unavailable");
50
+ }
51
+ async function capturePackage(capture, input) {
52
+ const file = await manifestFile(input.from, input);
53
+ if (file === undefined)
54
+ return null;
55
+ const previous = capture.seen.get(file);
56
+ if (previous !== undefined)
57
+ return previous;
58
+ const manifest = await readFile(file, "utf8");
59
+ const parsed = JSON.parse(manifest);
60
+ const fields = record(parsed);
61
+ if (typeof fields?.["name"] !== "string" ||
62
+ typeof fields["version"] !== "string")
63
+ throw new Error("invalid-workflow-dependency");
64
+ const captured = { manifest, dependencies: {} };
65
+ const index = capture.packages.length;
66
+ capture.seen.set(file, index);
67
+ capture.packages.push(captured);
68
+ for (const reference of references(fields))
69
+ captured.dependencies[reference.name] = await capturePackage(capture, {
70
+ ...reference,
71
+ from: file,
72
+ });
73
+ return index;
74
+ }
75
+ export async function workflowBundleDependencies(input) {
76
+ const capture = { seen: new Map(), packages: [] };
77
+ const roots = {};
78
+ const manifest = join(await workflowPackageRoot(input.product), "package.json");
79
+ for (const name of [...input.packages].sort())
80
+ roots[name] = await capturePackage(capture, {
81
+ name,
82
+ from: manifest,
83
+ optional: false,
84
+ });
85
+ return JSON.stringify({
86
+ node: process.version,
87
+ product: createHash("sha256")
88
+ .update(await readFile(manifest))
89
+ .digest("hex"),
90
+ roots,
91
+ packages: capture.packages,
92
+ });
93
+ }
@@ -0,0 +1,20 @@
1
+ interface BundleInput {
2
+ readonly directory: string;
3
+ readonly repository: string;
4
+ readonly project: string;
5
+ }
6
+ interface BundleLookup {
7
+ readonly directory: string;
8
+ readonly repository: string;
9
+ readonly id: string;
10
+ }
11
+ type BundleResult = {
12
+ readonly kind: "available";
13
+ readonly directory: string;
14
+ } | {
15
+ readonly kind: "unavailable";
16
+ readonly code: "pinned-workflow-unavailable";
17
+ };
18
+ export declare function readRetainedWorkflowBundle(input: BundleLookup): Promise<BundleResult>;
19
+ export declare function retainWorkflowBundle(input: BundleInput): Promise<string>;
20
+ export {};
@@ -0,0 +1,173 @@
1
+ import { createHash } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import { lstat, mkdir, mkdtemp, open, readdir, realpath, rename, rm, writeFile, } from "node:fs/promises";
4
+ import { dirname, join } from "node:path";
5
+ const unavailable = {
6
+ kind: "unavailable",
7
+ code: "pinned-workflow-unavailable",
8
+ };
9
+ export async function readRetainedWorkflowBundle(input) {
10
+ if (!/^[a-f0-9]{64}$/.test(input.id))
11
+ return unavailable;
12
+ try {
13
+ const directory = join(input.directory, input.id);
14
+ if (!(await lstat(directory)).isDirectory())
15
+ return unavailable;
16
+ const repository = await realpath(input.repository);
17
+ const contents = bundleContents(repository, await projectFiles(directory));
18
+ const stored = await regularFile(join(directory, "bundle.json"));
19
+ if (digest(contents) !== input.id || stored.toString("utf8") !== contents)
20
+ return unavailable;
21
+ return { kind: "available", directory };
22
+ }
23
+ catch {
24
+ return unavailable;
25
+ }
26
+ }
27
+ function digest(bytes) {
28
+ return createHash("sha256").update(bytes).digest("hex");
29
+ }
30
+ async function regularFile(path) {
31
+ const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
32
+ try {
33
+ if (!(await file.stat()).isFile())
34
+ throw new Error("invalid-bundle-file");
35
+ return await file.readFile();
36
+ }
37
+ finally {
38
+ await file.close();
39
+ }
40
+ }
41
+ async function treeFiles(root, name) {
42
+ const path = join(root, name);
43
+ const entry = await lstat(path);
44
+ if (entry.isFile())
45
+ return [{ name, bytes: await regularFile(path) }];
46
+ if (!entry.isDirectory())
47
+ throw new Error("invalid-bundle-file");
48
+ const files = [];
49
+ for (const child of (await readdir(path)).sort())
50
+ files.push(...(await treeFiles(root, `${name}/${child}`)));
51
+ return files;
52
+ }
53
+ async function projectFiles(project) {
54
+ const files = [];
55
+ const names = (await readdir(project)).filter((name) => name.endsWith(".json") && name !== "bundle.json");
56
+ for (const name of [...names, "artifacts", "workflows"].sort())
57
+ files.push(...(await treeFiles(project, name)));
58
+ const required = [
59
+ "package.json",
60
+ "declarations.json",
61
+ "dependencies.json",
62
+ ...[
63
+ "client.mjs",
64
+ "flow.mjs",
65
+ "steps.mjs",
66
+ "manifest.json",
67
+ "flow.mjs.__wf_tmp.js.debug.json",
68
+ "steps.mjs.debug.json",
69
+ ].map((name) => `artifacts/${name}`),
70
+ ];
71
+ if (required.some((name) => !files.some((file) => file.name === name)))
72
+ throw new Error("incomplete-workflow-bundle");
73
+ return files;
74
+ }
75
+ function bundleContents(repository, files) {
76
+ return JSON.stringify({
77
+ schemaVersion: 1,
78
+ repository,
79
+ files: Object.fromEntries(files.map(({ name, bytes }) => [name, digest(bytes)])),
80
+ });
81
+ }
82
+ async function validatedId(input) {
83
+ if ((await readRetainedWorkflowBundle(input)).kind !== "available")
84
+ throw new Error("pinned-workflow-unavailable");
85
+ return input.id;
86
+ }
87
+ async function existingEntry(path) {
88
+ return lstat(path).catch((error) => {
89
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
90
+ return undefined;
91
+ throw error;
92
+ });
93
+ }
94
+ async function retainedBefore(input, contents) {
95
+ try {
96
+ const history = join(input.directory, ".history");
97
+ await mkdir(history, { recursive: true, mode: 0o700 });
98
+ if (!(await lstat(history)).isDirectory())
99
+ throw new Error("pinned-workflow-unavailable");
100
+ const directory = join(history, input.id);
101
+ const entry = await existingEntry(directory);
102
+ if (entry === undefined)
103
+ return false;
104
+ if (!entry.isDirectory())
105
+ throw new Error("pinned-workflow-unavailable");
106
+ const receipt = await regularFile(join(directory, "bundle.json"));
107
+ if (receipt.toString("utf8") !== contents)
108
+ throw new Error("pinned-workflow-unavailable");
109
+ return true;
110
+ }
111
+ catch {
112
+ throw new Error("pinned-workflow-unavailable");
113
+ }
114
+ }
115
+ async function rememberBundle(input, contents) {
116
+ const history = join(input.directory, ".history");
117
+ const temporary = await mkdtemp(join(history, ".pending-"));
118
+ try {
119
+ await writeFile(join(temporary, "bundle.json"), contents, {
120
+ flag: "wx",
121
+ mode: 0o600,
122
+ });
123
+ await publishDirectory(temporary, join(history, input.id));
124
+ await retainedBefore(input, contents);
125
+ }
126
+ finally {
127
+ await rm(temporary, { recursive: true, force: true });
128
+ }
129
+ }
130
+ async function publishDirectory(from, to) {
131
+ await rename(from, to).catch((error) => {
132
+ if (error instanceof Error &&
133
+ "code" in error &&
134
+ ["ENOTEMPTY", "EEXIST"].includes(String(error.code)))
135
+ return;
136
+ throw error;
137
+ });
138
+ }
139
+ export async function retainWorkflowBundle(input) {
140
+ const repository = await realpath(input.repository);
141
+ const files = await projectFiles(input.project);
142
+ const contents = bundleContents(repository, files);
143
+ const id = digest(contents);
144
+ await mkdir(input.directory, { recursive: true, mode: 0o700 });
145
+ const destination = join(input.directory, id);
146
+ const lookup = { ...input, id };
147
+ if (await retainedBefore(lookup, contents))
148
+ return validatedId(lookup);
149
+ if (await existingEntry(destination)) {
150
+ await validatedId(lookup);
151
+ await rememberBundle(lookup, contents);
152
+ return id;
153
+ }
154
+ const temporary = await mkdtemp(join(input.directory, ".pending-"));
155
+ try {
156
+ for (const { name, bytes } of files) {
157
+ const path = join(temporary, name);
158
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
159
+ await writeFile(path, bytes, { flag: "wx", mode: 0o600 });
160
+ }
161
+ await writeFile(join(temporary, "bundle.json"), contents, {
162
+ flag: "wx",
163
+ mode: 0o600,
164
+ });
165
+ await publishDirectory(temporary, destination);
166
+ }
167
+ finally {
168
+ await rm(temporary, { recursive: true, force: true });
169
+ }
170
+ await validatedId(lookup);
171
+ await rememberBundle(lookup, contents);
172
+ return id;
173
+ }
@@ -0,0 +1,8 @@
1
+ import ts from "typescript";
2
+ import { type CandidateSymbols } from "./workflow-candidate-syntax.ts";
3
+ interface Totals {
4
+ readonly knownCallCount: number;
5
+ readonly knownUnitTotal: number;
6
+ }
7
+ export declare function candidateEffectTotals(source: ts.SourceFile, symbols: CandidateSymbols): Totals;
8
+ export {};
@@ -0,0 +1,251 @@
1
+ import ts from "typescript";
2
+ import { protectedCall, handoffCall, propertyName, } from "./workflow-candidate-syntax.js";
3
+ import { referenceSymbol } from "./workflow-source-effects.js";
4
+ const zero = { knownCallCount: 0, knownUnitTotal: 0 };
5
+ function add(first, second) {
6
+ return {
7
+ knownCallCount: Math.min(Number.MAX_SAFE_INTEGER, first.knownCallCount + second.knownCallCount),
8
+ knownUnitTotal: Math.min(Number.MAX_SAFE_INTEGER, first.knownUnitTotal + second.knownUnitTotal),
9
+ };
10
+ }
11
+ function maximum(first, second) {
12
+ return {
13
+ knownCallCount: Math.max(first.knownCallCount, second.knownCallCount),
14
+ knownUnitTotal: Math.max(first.knownUnitTotal, second.knownUnitTotal),
15
+ };
16
+ }
17
+ function multiply(total, repetitions) {
18
+ return {
19
+ knownCallCount: Math.min(Number.MAX_SAFE_INTEGER, total.knownCallCount * repetitions),
20
+ knownUnitTotal: Math.min(Number.MAX_SAFE_INTEGER, total.knownUnitTotal * repetitions),
21
+ };
22
+ }
23
+ function units(node, symbols) {
24
+ if (handoffCall(node, symbols))
25
+ return 1;
26
+ const argument = node.arguments[0];
27
+ if (!argument || !ts.isObjectLiteralExpression(argument))
28
+ return 0;
29
+ const property = argument.properties.find((entry) => entry.name && propertyName(entry.name) === "units");
30
+ if (!property || !ts.isPropertyAssignment(property))
31
+ return 0;
32
+ const value = constantNumber(property.initializer);
33
+ return value !== undefined && Number.isSafeInteger(value) && value > 0
34
+ ? value
35
+ : 0;
36
+ }
37
+ function loopIncrement(node, variable) {
38
+ if ((ts.isPostfixUnaryExpression(node) || ts.isPrefixUnaryExpression(node)) &&
39
+ ts.isIdentifier(node.operand) &&
40
+ node.operand.text === variable)
41
+ return unaryIncrement(node.operator);
42
+ return assignmentIncrement(node, variable);
43
+ }
44
+ function unaryIncrement(operator) {
45
+ if (operator === ts.SyntaxKind.PlusPlusToken)
46
+ return 1;
47
+ if (operator === ts.SyntaxKind.MinusMinusToken)
48
+ return -1;
49
+ return undefined;
50
+ }
51
+ function assignmentIncrement(node, variable) {
52
+ if (!ts.isBinaryExpression(node) ||
53
+ !ts.isIdentifier(node.left) ||
54
+ node.left.text !== variable)
55
+ return undefined;
56
+ const amount = constantNumber(node.right);
57
+ if (amount === undefined)
58
+ return undefined;
59
+ if (node.operatorToken.kind === ts.SyntaxKind.PlusEqualsToken)
60
+ return amount;
61
+ if (node.operatorToken.kind === ts.SyntaxKind.MinusEqualsToken)
62
+ return -amount;
63
+ return undefined;
64
+ }
65
+ function constantNumber(node) {
66
+ if (ts.isParenthesizedExpression(node))
67
+ return constantNumber(node.expression);
68
+ if (ts.isNumericLiteral(node)) {
69
+ const value = Number(node.text);
70
+ return Number.isFinite(value) ? value : undefined;
71
+ }
72
+ if (!ts.isPrefixUnaryExpression(node))
73
+ return undefined;
74
+ const value = constantNumber(node.operand);
75
+ if (value === undefined)
76
+ return undefined;
77
+ if (node.operator === ts.SyntaxKind.PlusToken)
78
+ return value;
79
+ if (node.operator === ts.SyntaxKind.MinusToken)
80
+ return -value;
81
+ return undefined;
82
+ }
83
+ function rangeIterations(input) {
84
+ const { start, end, increment, operator } = input;
85
+ if (![
86
+ ts.SyntaxKind.LessThanToken,
87
+ ts.SyntaxKind.LessThanEqualsToken,
88
+ ts.SyntaxKind.GreaterThanToken,
89
+ ts.SyntaxKind.GreaterThanEqualsToken,
90
+ ].includes(operator))
91
+ return undefined;
92
+ let iterations = 0;
93
+ let value = start;
94
+ while (iterations < 101 && loopContinues(value, { end, operator })) {
95
+ iterations += 1;
96
+ value += increment;
97
+ }
98
+ return iterations;
99
+ }
100
+ function loopContinues(value, condition) {
101
+ const { end, operator } = condition;
102
+ if (operator === ts.SyntaxKind.LessThanToken)
103
+ return value < end;
104
+ if (operator === ts.SyntaxKind.LessThanEqualsToken)
105
+ return value <= end;
106
+ if (operator === ts.SyntaxKind.GreaterThanToken)
107
+ return value > end;
108
+ return value >= end;
109
+ }
110
+ function referencesCounter(node, context) {
111
+ if (!context.counter)
112
+ return false;
113
+ if (ts.isIdentifier(node) &&
114
+ referenceSymbol(node, { parent: node.parent, checker: context.checker }) ===
115
+ context.counter)
116
+ return true;
117
+ return (ts.forEachChild(node, (child) => referencesCounter(child, context) ? true : undefined) ?? false);
118
+ }
119
+ function assignmentTarget(node) {
120
+ if (ts.isBinaryExpression(node) &&
121
+ node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
122
+ node.operatorToken.kind <= ts.SyntaxKind.LastAssignment)
123
+ return node.left;
124
+ if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
125
+ unaryIncrement(node.operator) !== undefined)
126
+ return node.operand;
127
+ return undefined;
128
+ }
129
+ function writesCounter(node, context) {
130
+ if (ts.isVariableDeclaration(node) && node.initializer !== undefined)
131
+ return referencesCounter(node.name, context);
132
+ if (ts.isForOfStatement(node) || ts.isForInStatement(node))
133
+ return referencesCounter(node.initializer, context);
134
+ const target = assignmentTarget(node);
135
+ return target !== undefined && referencesCounter(target, context);
136
+ }
137
+ function uncertainLoopBody(node, context) {
138
+ if (ts.isBreakStatement(node) ||
139
+ ts.isContinueStatement(node) ||
140
+ ts.isReturnStatement(node) ||
141
+ ts.isThrowStatement(node) ||
142
+ writesCounter(node, context))
143
+ return true;
144
+ return (ts.forEachChild(node, (child) => uncertainLoopBody(child, context) ? true : undefined) ?? false);
145
+ }
146
+ function loopIterations(node, symbols) {
147
+ const { initializer, condition, incrementor } = node;
148
+ const declaration = loopStart(initializer);
149
+ if (!declaration)
150
+ return undefined;
151
+ if (!condition || !ts.isBinaryExpression(condition))
152
+ return undefined;
153
+ if (!ts.isIdentifier(condition.left) ||
154
+ condition.left.text !== declaration.variable.text)
155
+ return undefined;
156
+ if (!incrementor)
157
+ return undefined;
158
+ const end = constantNumber(condition.right);
159
+ if (end === undefined)
160
+ return undefined;
161
+ const increment = loopIncrement(incrementor, declaration.variable.text);
162
+ if (increment === undefined)
163
+ return undefined;
164
+ if (uncertainLoopBody(node.statement, {
165
+ checker: symbols.checker,
166
+ counter: symbols.checker.getSymbolAtLocation(declaration.variable),
167
+ }))
168
+ return undefined;
169
+ return rangeIterations({
170
+ start: declaration.start,
171
+ end,
172
+ increment,
173
+ operator: condition.operatorToken.kind,
174
+ });
175
+ }
176
+ function loopStart(initializer) {
177
+ if (!initializer ||
178
+ !ts.isVariableDeclarationList(initializer) ||
179
+ initializer.declarations.length !== 1)
180
+ return undefined;
181
+ const declaration = initializer.declarations[0];
182
+ if (!declaration ||
183
+ !ts.isIdentifier(declaration.name) ||
184
+ !declaration.initializer)
185
+ return undefined;
186
+ const start = constantNumber(declaration.initializer);
187
+ if (start === undefined)
188
+ return undefined;
189
+ return {
190
+ variable: declaration.name,
191
+ start,
192
+ };
193
+ }
194
+ export function candidateEffectTotals(source, symbols) {
195
+ function count(node) {
196
+ if (!node)
197
+ return zero;
198
+ if (ts.isIfStatement(node))
199
+ return add(count(node.expression), maximum(count(node.thenStatement), count(node.elseStatement)));
200
+ if (ts.isConditionalExpression(node))
201
+ return add(count(node.condition), maximum(count(node.whenTrue), count(node.whenFalse)));
202
+ if (ts.isForStatement(node)) {
203
+ const iterations = loopIterations(node, symbols) ?? 1;
204
+ const body = count(node.statement);
205
+ const headers = add(count(node.initializer), add(count(node.condition), count(node.incrementor)));
206
+ return add(headers, multiply(body, iterations));
207
+ }
208
+ if (ts.isForOfStatement(node) &&
209
+ ts.isArrayLiteralExpression(node.expression))
210
+ return add(count(node.expression), multiply(add(count(node.initializer), count(node.statement)), arrayIterations(node, symbols)));
211
+ if (ts.isSwitchStatement(node))
212
+ return add(count(node.expression), switchTotals(node.caseBlock, count));
213
+ let total = protectedCall(node, symbols)
214
+ ? {
215
+ knownCallCount: 1,
216
+ knownUnitTotal: units(node, symbols),
217
+ }
218
+ : zero;
219
+ ts.forEachChild(node, (child) => {
220
+ total = add(total, count(child));
221
+ });
222
+ return total;
223
+ }
224
+ return count(source);
225
+ }
226
+ function arrayIterations(node, symbols) {
227
+ if (!ts.isArrayLiteralExpression(node.expression))
228
+ return 1;
229
+ return uncertainLoopBody(node.statement, {
230
+ checker: symbols.checker,
231
+ counter: undefined,
232
+ })
233
+ ? 1
234
+ : node.expression.elements.length;
235
+ }
236
+ function switchTotals(block, count) {
237
+ let suffix = zero;
238
+ let largest = zero;
239
+ let conditions = zero;
240
+ for (const clause of [...block.clauses].reverse()) {
241
+ if (ts.isCaseClause(clause))
242
+ conditions = add(conditions, count(clause.expression));
243
+ const ends = clause.statements.some((node) => ts.isBreakStatement(node) ||
244
+ ts.isReturnStatement(node) ||
245
+ ts.isThrowStatement(node));
246
+ const statements = clause.statements.reduce((total, statement) => add(total, count(statement)), zero);
247
+ suffix = add(statements, ends ? zero : suffix);
248
+ largest = maximum(largest, suffix);
249
+ }
250
+ return add(conditions, largest);
251
+ }