hammerkit 1.6.0 → 1.8.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 (277) hide show
  1. package/README.md +7 -7
  2. package/build.schema.json +59 -15
  3. package/dist/cache/backends/local-cache-backend.js +58 -1
  4. package/dist/cache/backends/registry/registry-client.js +264 -0
  5. package/dist/cache/backends/registry/registry-credentials.js +81 -0
  6. package/dist/cache/backends/registry/registry-reference.js +36 -0
  7. package/dist/cache/backends/registry-cache-backend.js +145 -0
  8. package/dist/cache/backends/s3-cache-backend.js +99 -46
  9. package/dist/cache/cache-backend-registry.js +2 -0
  10. package/dist/cache/cache-inventory.js +84 -0
  11. package/dist/cache/cache-sync.js +77 -0
  12. package/dist/cache/explain.js +214 -0
  13. package/dist/cache/last-resolved.js +48 -0
  14. package/dist/cache/read-only.js +15 -0
  15. package/dist/cache/retention.js +61 -0
  16. package/dist/cli.js +43 -0
  17. package/dist/docker/using-container.js +17 -1
  18. package/dist/executer/archive-task-entry.js +21 -0
  19. package/dist/executer/await-completed-dependencies.js +32 -14
  20. package/dist/executer/cache-metadata.js +9 -2
  21. package/dist/executer/docker-service.js +2 -1
  22. package/dist/executer/docker-task.js +18 -3
  23. package/dist/executer/dry-run.js +73 -0
  24. package/dist/executer/event-cache.js +6 -5
  25. package/dist/executer/execute-command.js +21 -3
  26. package/dist/executer/execute-docker.js +18 -2
  27. package/dist/executer/execute-work-service.js +10 -1
  28. package/dist/executer/execute-work-task.js +111 -26
  29. package/dist/executer/execute-work-tree.js +26 -1
  30. package/dist/executer/execution-steps.js +3 -8
  31. package/dist/executer/get-container-binds.js +13 -1
  32. package/dist/executer/get-scheduler-execute-result.js +4 -1
  33. package/dist/executer/lazy-resolver.js +15 -3
  34. package/dist/executer/reset-work-tree.js +2 -0
  35. package/dist/executer/run-summary.js +102 -0
  36. package/dist/executer/scheduler/enqueue-next.js +15 -60
  37. package/dist/executer/scheduler/state-key.js +78 -0
  38. package/dist/index.js +14 -1
  39. package/dist/kubernetes/apply.js +6 -7
  40. package/dist/kubernetes/await-running-state.js +100 -60
  41. package/dist/kubernetes/ensure-http-route.js +7 -10
  42. package/dist/kubernetes/ensure-namespace.js +9 -10
  43. package/dist/kubernetes/ensure-persistent-data.js +6 -5
  44. package/dist/kubernetes/get-service-ip.js +2 -2
  45. package/dist/kubernetes/kubernetes-instance.js +0 -4
  46. package/dist/kubernetes/remove-persistent-data.js +16 -6
  47. package/dist/kubernetes/resolve-pod-name.js +7 -7
  48. package/dist/log.js +16 -6
  49. package/dist/logging/live-logger.js +2 -1
  50. package/dist/optimizer/calculate-checksum.js +8 -2
  51. package/dist/optimizer/get-work-cache-stats.js +66 -16
  52. package/dist/optimizer/work-task-cache-description.js +34 -5
  53. package/dist/planner/project-root.js +26 -0
  54. package/dist/planner/utils/append-work-environment.js +1 -1
  55. package/dist/planner/utils/append-work-service.js +2 -1
  56. package/dist/planner/utils/append-work-task.js +7 -1
  57. package/dist/planner/utils/get-outputs-to-reset.js +20 -0
  58. package/dist/planner/utils/parse-work-generate.js +2 -2
  59. package/dist/planner/utils/parse-work-source.js +35 -30
  60. package/dist/planner/utils/portable-path.js +30 -0
  61. package/dist/planner/work-cache-id.js +2 -2
  62. package/dist/planner/work-graph-serializer.js +103 -0
  63. package/dist/planner/work-instance-id.js +15 -0
  64. package/dist/planner/work-runtime-docker.js +58 -15
  65. package/dist/planner/work-runtime-kubernetes.js +95 -45
  66. package/dist/planner/work-runtime-local.js +31 -8
  67. package/dist/planner/work-service-id.js +16 -4
  68. package/dist/program.js +235 -1
  69. package/dist/schema/build-file-base-task-schema.js +4 -0
  70. package/dist/schema/build-file-environment-schema-http-route.js +15 -0
  71. package/dist/schema/build-file-environment-schema.js +8 -2
  72. package/dist/schema/build-file-schema.js +1 -1
  73. package/dist/schema/cache-schema.js +21 -2
  74. package/dist/schema/duration-schema.js +18 -0
  75. package/dist/schema/envs-schema.js +1 -1
  76. package/dist/schema/reference-parser.js +3 -0
  77. package/dist/schema/size-schema.js +18 -0
  78. package/dist/schema/work-scope-parser.js +15 -2
  79. package/dist/utils/units.js +87 -0
  80. package/package.json +20 -9
  81. package/.agents/FEEDBACK.md +0 -74
  82. package/.agents/skills/before-commit/SKILL.md +0 -101
  83. package/.agents/skills/do-the-work/SKILL.md +0 -64
  84. package/.agents/skills/fix-ci/SKILL.md +0 -98
  85. package/.agents/skills/speckit-git-commit/SKILL.md +0 -53
  86. package/.agents/skills/speckit-git-feature/SKILL.md +0 -72
  87. package/.agents/skills/speckit-git-initialize/SKILL.md +0 -54
  88. package/.agents/skills/speckit-git-remote/SKILL.md +0 -50
  89. package/.agents/skills/speckit-git-validate/SKILL.md +0 -54
  90. package/.devcontainer/Dockerfile +0 -5
  91. package/.devcontainer/devcontainer.json +0 -40
  92. package/.gitbook.yaml +0 -5
  93. package/.specify/extensions/.registry +0 -37
  94. package/.specify/extensions/git/README.md +0 -100
  95. package/.specify/extensions/git/commands/speckit.git.commit.md +0 -48
  96. package/.specify/extensions/git/commands/speckit.git.feature.md +0 -67
  97. package/.specify/extensions/git/commands/speckit.git.initialize.md +0 -49
  98. package/.specify/extensions/git/commands/speckit.git.remote.md +0 -45
  99. package/.specify/extensions/git/commands/speckit.git.validate.md +0 -49
  100. package/.specify/extensions/git/config-template.yml +0 -62
  101. package/.specify/extensions/git/extension.yml +0 -140
  102. package/.specify/extensions/git/git-config.yml +0 -62
  103. package/.specify/extensions.yml +0 -149
  104. package/.specify/feature.json +0 -3
  105. package/.specify/init-options.json +0 -10
  106. package/.specify/integration.json +0 -15
  107. package/.specify/integrations/claude.manifest.json +0 -16
  108. package/.specify/integrations/speckit.manifest.json +0 -17
  109. package/.specify/memory/constitution.md +0 -162
  110. package/.specify/templates/checklist-template.md +0 -40
  111. package/.specify/templates/constitution-template.md +0 -50
  112. package/.specify/templates/plan-template.md +0 -113
  113. package/.specify/templates/spec-template.md +0 -131
  114. package/.specify/templates/tasks-template.md +0 -252
  115. package/.specify/workflows/speckit/workflow.yml +0 -77
  116. package/.specify/workflows/workflow-registry.json +0 -13
  117. package/AGENTS.md +0 -94
  118. package/Dockerfile +0 -32
  119. package/FOLLOWUPS.md +0 -34
  120. package/SUMMARY.md +0 -68
  121. package/VALUES.md +0 -53
  122. package/dist/cache/auto-pull.spec.js +0 -123
  123. package/dist/cache/backends/local-cache-backend.spec.js +0 -72
  124. package/dist/cache/backends/s3-cache-backend.spec.js +0 -118
  125. package/dist/cache/cache-backend-registry.spec.js +0 -22
  126. package/dist/cache/resolve-cache.spec.js +0 -52
  127. package/dist/cli.spec.js +0 -23
  128. package/dist/console/get-logger.spec.js +0 -23
  129. package/dist/docker/get-default-platform.spec.js +0 -29
  130. package/dist/docker/longest-common-prefix.spec.js +0 -26
  131. package/dist/docker/package.spec.js +0 -145
  132. package/dist/docker/remove-container.spec.js +0 -32
  133. package/dist/docker/using-container.spec.js +0 -90
  134. package/dist/environment/merge-environment-variables.spec.js +0 -24
  135. package/dist/environment/replace-env-variables.spec.js +0 -47
  136. package/dist/executer/abort.spec.js +0 -18
  137. package/dist/executer/environment-mock.spec.js +0 -22
  138. package/dist/executer/execute.spec.js +0 -51
  139. package/dist/executer/get-service-containers.spec.js +0 -59
  140. package/dist/executer/get-service-env-hints.spec.js +0 -65
  141. package/dist/executer/label-values.spec.js +0 -61
  142. package/dist/executer/lazy-resolver.spec.js +0 -20
  143. package/dist/executer/process-manager.spec.js +0 -54
  144. package/dist/executer/scheduler/enqueue-next.spec.js +0 -136
  145. package/dist/executer/state-resolver.spec.js +0 -37
  146. package/dist/executer/state.spec.js +0 -66
  147. package/dist/executer/states.spec.js +0 -14
  148. package/dist/file/get-file-context.spec.js +0 -178
  149. package/dist/file/move-files.spec.js +0 -48
  150. package/dist/kubernetes/ensure-http-route.spec.js +0 -70
  151. package/dist/kubernetes/ensure-ingress.js +0 -54
  152. package/dist/kubernetes/ensure-namespace.spec.js +0 -39
  153. package/dist/kubernetes/resolve-pod-name.spec.js +0 -81
  154. package/dist/log.spec.js +0 -131
  155. package/dist/logging/grouped-logger.spec.js +0 -84
  156. package/dist/logging/interactive-logger.spec.js +0 -57
  157. package/dist/logging/live-logger.spec.js +0 -37
  158. package/dist/logging/loggers.spec.js +0 -154
  159. package/dist/optimizer/calculate-checksum.spec.js +0 -34
  160. package/dist/optimizer/get-cache-directory.spec.js +0 -59
  161. package/dist/optimizer/get-work-cache-stats.spec.js +0 -79
  162. package/dist/optimizer/work-task-cache-description.spec.js +0 -45
  163. package/dist/parser/default-build-file.spec.js +0 -48
  164. package/dist/parser/parse-label-arguments.spec.js +0 -27
  165. package/dist/parser/read-build-file.spec.js +0 -89
  166. package/dist/parser/read-env-file.spec.js +0 -42
  167. package/dist/planner/utils/append-work-service.spec.js +0 -71
  168. package/dist/planner/utils/get-container-user.spec.js +0 -30
  169. package/dist/planner/utils/get-default-kube-config.spec.js +0 -11
  170. package/dist/planner/utils/normalize-path.spec.js +0 -24
  171. package/dist/planner/utils/parse-work-command.spec.js +0 -21
  172. package/dist/planner/utils/parse-work-generate.spec.js +0 -45
  173. package/dist/planner/utils/parse-work-mount.spec.js +0 -62
  174. package/dist/planner/utils/parse-work-mounts.spec.js +0 -19
  175. package/dist/planner/utils/parse-work-port.spec.js +0 -24
  176. package/dist/planner/utils/parse-work-ports.spec.js +0 -24
  177. package/dist/planner/utils/parse-work-source.spec.js +0 -58
  178. package/dist/planner/utils/parse-work-volume.spec.js +0 -18
  179. package/dist/planner/utils/plan-work-tasks.spec.js +0 -30
  180. package/dist/planner/utils/plan-work-volume.spec.js +0 -33
  181. package/dist/planner/utils/split-name.spec.js +0 -16
  182. package/dist/planner/utils/template-value.spec.js +0 -14
  183. package/dist/planner/validate.spec.js +0 -251
  184. package/dist/planner/work-cache-id.spec.js +0 -173
  185. package/dist/planner/work-runtime-local.spec.js +0 -193
  186. package/dist/program-commands.spec.js +0 -84
  187. package/dist/program.spec.js +0 -43
  188. package/dist/schema/build-file-environment-schema-ingress.js +0 -14
  189. package/dist/schema/build-file-schema.spec.js +0 -61
  190. package/dist/schema/doc-build-files.spec.js +0 -223
  191. package/dist/schema/parse-error.spec.js +0 -18
  192. package/dist/schema/reference-parser.spec.js +0 -83
  193. package/dist/schema/schema-parser.spec.js +0 -80
  194. package/dist/schema/type-guards.spec.js +0 -23
  195. package/dist/schema/work-scope-parser.spec.js +0 -66
  196. package/dist/service/update-service-status.spec.js +0 -27
  197. package/dist/testing/ensure-local-registry.js +0 -134
  198. package/dist/testing/example-test-suite.js +0 -63
  199. package/dist/testing/expect.js +0 -82
  200. package/dist/testing/get-test-suite.js +0 -9
  201. package/dist/testing/http-get.js +0 -21
  202. package/dist/testing/integration/cache.spec.js +0 -49
  203. package/dist/testing/integration/cancellation.spec.js +0 -45
  204. package/dist/testing/integration/clean.spec.js +0 -43
  205. package/dist/testing/integration/cmd.spec.js +0 -15
  206. package/dist/testing/integration/concurrency.spec.js +0 -19
  207. package/dist/testing/integration/docker-package-registry.spec.js +0 -44
  208. package/dist/testing/integration/docker-service-crash.spec.js +0 -36
  209. package/dist/testing/integration/docker.spec.js +0 -18
  210. package/dist/testing/integration/env.spec.js +0 -48
  211. package/dist/testing/integration/environment.spec.js +0 -97
  212. package/dist/testing/integration/error.spec.js +0 -19
  213. package/dist/testing/integration/export.spec.js +0 -22
  214. package/dist/testing/integration/extend.spec.js +0 -29
  215. package/dist/testing/integration/glob.spec.js +0 -40
  216. package/dist/testing/integration/include.spec.js +0 -31
  217. package/dist/testing/integration/invalid.spec.js +0 -17
  218. package/dist/testing/integration/invalid_loop.spec.js +0 -56
  219. package/dist/testing/integration/k8s-state-cache.spec.js +0 -41
  220. package/dist/testing/integration/kubernetes-service.spec.js +0 -148
  221. package/dist/testing/integration/kubernetes.spec.js +0 -99
  222. package/dist/testing/integration/labels.spec.js +0 -42
  223. package/dist/testing/integration/local.spec.js +0 -14
  224. package/dist/testing/integration/monorepo.spec.js +0 -15
  225. package/dist/testing/integration/multi-line.spec.js +0 -17
  226. package/dist/testing/integration/reference.spec.js +0 -21
  227. package/dist/testing/integration/services.spec.js +0 -38
  228. package/dist/testing/integration/store.spec.js +0 -52
  229. package/dist/testing/integration/unknown.spec.js +0 -28
  230. package/dist/testing/integration/validate.spec.js +0 -160
  231. package/dist/testing/integration/watch-dependency.spec.js +0 -33
  232. package/dist/testing/integration/watch.spec.js +0 -45
  233. package/dist/testing/requires-kubernetes.js +0 -18
  234. package/dist/testing/requires-linux-containers.js +0 -18
  235. package/dist/testing/requires-windows-containers.js +0 -18
  236. package/dist/testing/test-case.js +0 -75
  237. package/dist/testing/test-streams.js +0 -28
  238. package/dist/testing/test-suite-setup.js +0 -3
  239. package/dist/testing/test-suite.js +0 -3
  240. package/dist/testing/testing-timeout.js +0 -32
  241. package/dist/utils/abort-event.spec.js +0 -46
  242. package/dist/utils/buffer-context.spec.js +0 -38
  243. package/dist/utils/ci.spec.js +0 -53
  244. package/dist/utils/debouncer.spec.js +0 -58
  245. package/dist/utils/emitter.spec.js +0 -34
  246. package/dist/utils/empty-writable.spec.js +0 -18
  247. package/dist/utils/environment-config.spec.js +0 -26
  248. package/dist/utils/fail-never.spec.js +0 -9
  249. package/dist/utils/sleep.spec.js +0 -22
  250. package/dist/version.spec.js +0 -12
  251. package/specs/build-file/caches/spec.md +0 -114
  252. package/specs/build-file/environment-variables/spec.md +0 -82
  253. package/specs/build-file/includes/spec.md +0 -60
  254. package/specs/build-file/references/spec.md +0 -56
  255. package/specs/build-file/spec.md +0 -62
  256. package/specs/cli/clean/spec.md +0 -85
  257. package/specs/cli/down/spec.md +0 -85
  258. package/specs/cli/execute/spec.md +0 -101
  259. package/specs/cli/init/spec.md +0 -64
  260. package/specs/cli/ls/spec.md +0 -66
  261. package/specs/cli/package/spec.md +0 -110
  262. package/specs/cli/spec.md +0 -73
  263. package/specs/cli/store-restore/spec.md +0 -74
  264. package/specs/cli/up/spec.md +0 -80
  265. package/specs/cli/validate/spec.md +0 -70
  266. package/specs/labels/spec.md +0 -91
  267. package/specs/service/container/spec.md +0 -120
  268. package/specs/service/kubernetes/spec.md +0 -97
  269. package/specs/service/spec.md +0 -105
  270. package/specs/task/caching/spec.md +0 -107
  271. package/specs/task/container/spec.md +0 -118
  272. package/specs/task/dependencies/spec.md +0 -70
  273. package/specs/task/extending/spec.md +0 -74
  274. package/specs/task/kubernetes/spec.md +0 -125
  275. package/specs/task/needs/spec.md +0 -59
  276. package/specs/task/spec.md +0 -87
  277. package/specs/task/watching/spec.md +0 -69
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.printRunSummary = exports.formatDuration = exports.summarizeRun = void 0;
7
+ const colors_1 = __importDefault(require("colors"));
8
+ const plan_work_tasks_1 = require("../planner/utils/plan-work-tasks");
9
+ const log_1 = require("../log");
10
+ function statusOf(state) {
11
+ switch (state.type) {
12
+ case 'completed':
13
+ return state.skipped ? 'skipped' : state.cached ? 'cached' : 'executed';
14
+ case 'error':
15
+ case 'crash':
16
+ return 'failed';
17
+ default:
18
+ // canceled, or never started (pending/starting/ready/running left over from
19
+ // an aborted run) — all surface as cancelled/skipped.
20
+ return 'cancelled';
21
+ }
22
+ }
23
+ // Derive the run summary purely from the final task states already on the work
24
+ // tree — nothing is re-run. Deterministically ordered by task name.
25
+ function summarizeRun(workTree, totalDuration) {
26
+ const tasks = [];
27
+ for (const task of (0, plan_work_tasks_1.iterateWorkTasks)(workTree)) {
28
+ const state = task.state.current;
29
+ const cause = state.type === 'completed' && state.missCauses?.length ? state.missCauses.join(', ') : undefined;
30
+ tasks.push({
31
+ taskId: task.id(),
32
+ taskName: task.name,
33
+ status: statusOf(state),
34
+ duration: state.type === 'completed' ? state.duration : 0,
35
+ cause,
36
+ });
37
+ }
38
+ tasks.sort((a, b) => a.taskName.localeCompare(b.taskName));
39
+ const executed = tasks.filter((t) => t.status === 'executed').length;
40
+ const cached = tasks.filter((t) => t.status === 'cached').length;
41
+ const skipped = tasks.filter((t) => t.status === 'skipped').length;
42
+ const failed = tasks.filter((t) => t.status === 'failed').length;
43
+ const cancelled = tasks.filter((t) => t.status === 'cancelled').length;
44
+ const ran = executed + cached;
45
+ return {
46
+ tasks,
47
+ executed,
48
+ cached,
49
+ skipped,
50
+ failed,
51
+ cancelled,
52
+ cacheHitRatio: ran === 0 ? 0 : cached / ran,
53
+ totalDuration,
54
+ };
55
+ }
56
+ exports.summarizeRun = summarizeRun;
57
+ function formatDuration(ms) {
58
+ if (ms < 1000) {
59
+ return `${ms}ms`;
60
+ }
61
+ return `${(ms / 1000).toFixed(1)}s`;
62
+ }
63
+ exports.formatDuration = formatDuration;
64
+ function colorStatus(status) {
65
+ const label = status.padEnd(9);
66
+ switch (status) {
67
+ case 'executed':
68
+ return colors_1.default.cyan(label);
69
+ case 'cached':
70
+ return colors_1.default.green(label);
71
+ case 'skipped':
72
+ return colors_1.default.grey(label);
73
+ case 'failed':
74
+ return colors_1.default.red(label);
75
+ case 'cancelled':
76
+ return colors_1.default.grey(label);
77
+ }
78
+ }
79
+ // Bound the name column so a single very long task name cannot blow out the
80
+ // width on large graphs.
81
+ const MAX_NAME_WIDTH = 60;
82
+ function printRunSummary(environment, summary) {
83
+ (0, log_1.printTitle)(environment, 'Summary');
84
+ if (summary.tasks.length === 0) {
85
+ environment.stdout.write(' nothing ran\n');
86
+ return;
87
+ }
88
+ const nameWidth = Math.min(MAX_NAME_WIDTH, summary.tasks.reduce((max, t) => Math.max(max, t.taskName.length), 0));
89
+ for (const task of summary.tasks) {
90
+ const name = task.taskName.length > nameWidth ? `${task.taskName.slice(0, nameWidth - 1)}…` : task.taskName.padEnd(nameWidth);
91
+ const cause = task.cause ? ` ${colors_1.default.grey(task.cause)}` : '';
92
+ environment.stdout.write(` ${name} ${colorStatus(task.status)} ${colors_1.default.grey(formatDuration(task.duration))}${cause}\n`);
93
+ }
94
+ const ratio = Math.round(summary.cacheHitRatio * 100);
95
+ const skippedSuffix = summary.skipped > 0 ? `, ${summary.skipped} skipped` : '';
96
+ const failedSuffix = summary.failed > 0 ? `, ${summary.failed} failed` : '';
97
+ const cancelledSuffix = summary.cancelled > 0 ? `, ${summary.cancelled} cancelled` : '';
98
+ environment.stdout.write(` ${summary.executed} executed, ${summary.cached} cached${skippedSuffix}${failedSuffix}${cancelledSuffix} ` +
99
+ `(${ratio}% cache hit), ${formatDuration(summary.totalDuration)} total\n`);
100
+ }
101
+ exports.printRunSummary = printRunSummary;
102
+ //# sourceMappingURL=run-summary.js.map
@@ -1,91 +1,46 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.checkCacheState = void 0;
4
- const get_work_cache_stats_1 = require("../../optimizer/get-work-cache-stats");
5
4
  const work_item_1 = require("../../planner/work-item");
6
5
  const get_cache_directory_1 = require("../../optimizer/get-cache-directory");
7
6
  const cache_metadata_1 = require("../cache-metadata");
8
7
  const work_task_cache_description_1 = require("../../optimizer/work-task-cache-description");
9
8
  const log_1 = require("../../log");
10
- const crypto_1 = require("crypto");
11
- function resolveEffective(item, fallbackMethod) {
12
- const declared = item.data.caching;
13
- if (declared.implicit) {
14
- return { ...declared, method: fallbackMethod };
15
- }
16
- return declared;
17
- }
18
- // Fold a task's own state key together with the state keys of its dependencies.
19
- // A change anywhere in the dependency subtree must change the resulting key, so
20
- // that a downstream task is invalidated when an upstream source changes — even
21
- // if the downstream task's own sources are untouched.
22
- function combineStateKeys(ownKey, depKeys) {
23
- if (depKeys.length === 0) {
24
- return ownKey;
25
- }
26
- // Sort so the key is independent of dependency declaration order.
27
- const sorted = [...depKeys].sort();
28
- return (0, crypto_1.createHash)('md5')
29
- .update([ownKey, ...sorted].join(','))
30
- .digest('hex');
31
- }
32
- // Compute an item's effective state key from its source files plus the effective
33
- // state keys of its dependencies, recursively. This is derived purely from the
34
- // work tree and the filesystem, so it does not depend on scheduler/run state —
35
- // important because checkCacheState runs before dependencies are awaited.
36
- async function resolveEffectiveStateKey(item, defaultCacheMethod, environment, memo) {
37
- const existing = memo.get(item.id());
38
- if (existing !== undefined) {
39
- return existing;
40
- }
41
- const resolved = resolveEffective(item, defaultCacheMethod);
42
- // Always hash with a concrete method; `none` only disables the cache *check*
43
- // for that task, its sources still influence what depends on it.
44
- const method = resolved.method === 'none' ? defaultCacheMethod : resolved.method;
45
- const stats = await (0, get_work_cache_stats_1.getWorkCacheStats)(item.data, environment);
46
- const ownKey = (0, get_work_cache_stats_1.getStateKey)(stats, method);
47
- const depKeys = [];
48
- for (const dep of item.deps) {
49
- depKeys.push(await resolveEffectiveStateKey(dep, defaultCacheMethod, environment, memo));
50
- }
51
- const combined = combineStateKeys(ownKey, depKeys);
52
- memo.set(item.id(), combined);
53
- return combined;
54
- }
9
+ const state_key_1 = require("./state-key");
10
+ const work_instance_id_1 = require("../../planner/work-instance-id");
55
11
  async function checkCacheState(item, defaultCacheMethod, environment) {
56
- const resolved = resolveEffective(item, defaultCacheMethod);
57
- const currentStats = await (0, get_work_cache_stats_1.getWorkCacheStats)(item.data, environment);
58
- const ownKey = (0, get_work_cache_stats_1.getStateKey)(currentStats, resolved.method === 'none' ? defaultCacheMethod : resolved.method);
59
- const memo = new Map();
60
- const depKeys = [];
61
- for (const dep of item.deps) {
62
- depKeys.push(await resolveEffectiveStateKey(dep, defaultCacheMethod, environment, memo));
12
+ const { stateKey, stats: currentStats, resolved, provable, unmatched, } = await (0, state_key_1.computeStateKey)(item, defaultCacheMethod, environment);
13
+ for (const src of unmatched) {
14
+ item.status.write('warn', `src "${src.source}" matches no files`);
63
15
  }
64
- const stateKey = combineStateKeys(ownKey, depKeys);
65
16
  if (resolved.method === 'none') {
66
17
  item.status.write('debug', `${item.name} is skipping cache check, because caching is disabled`);
67
- return { cached: false, stateKey, resolved };
18
+ return { cached: false, stateKey, resolved, provable };
19
+ }
20
+ if ((0, work_item_1.isWorkTaskItem)(item) && !provable) {
21
+ item.status.write('debug', `${item.name} always runs, it or one of its dependencies has no src files`);
22
+ return { cached: false, stateKey, resolved, provable };
68
23
  }
69
24
  if ((0, work_item_1.isWorkTaskItem)(item)) {
70
25
  const runtimeStateKey = await item.runtime.currentStateKey(environment);
71
26
  if (runtimeStateKey === stateKey) {
72
- return { cached: true, stateKey, resolved };
27
+ return { cached: true, stateKey, resolved, provable };
73
28
  }
74
29
  try {
75
- const cacheDir = (0, get_cache_directory_1.getCacheDirectory)(item.id());
30
+ const cacheDir = (0, get_cache_directory_1.getCacheDirectory)((0, work_instance_id_1.getWorkInstanceId)(item));
76
31
  const pulled = await resolved.backend.pull(item.id(), stateKey, cacheDir, environment);
77
32
  if (pulled) {
78
33
  item.status.write('info', `${item.name} pulled from cache "${resolved.name}" (${resolved.backend.type})`);
79
34
  await item.runtime.restore(environment, cacheDir);
80
- await (0, cache_metadata_1.writeCacheMetadata)(environment, item.id(), currentStats, (0, work_task_cache_description_1.getWorkTaskCacheDescription)(item.data));
81
- return { cached: true, stateKey, resolved };
35
+ await (0, cache_metadata_1.writeCacheMetadata)(environment, item, currentStats, (0, work_task_cache_description_1.getWorkTaskCacheDescription)(item));
36
+ return { cached: true, stateKey, resolved, provable };
82
37
  }
83
38
  }
84
39
  catch (e) {
85
40
  item.status.write('warn', `${item.name} failed to pull from cache "${resolved.name}": ${(0, log_1.getErrorMessage)(e)} — continuing with local execution`);
86
41
  }
87
42
  }
88
- return { cached: false, stateKey, resolved };
43
+ return { cached: false, stateKey, resolved, provable };
89
44
  }
90
45
  exports.checkCacheState = checkCacheState;
91
46
  //# sourceMappingURL=enqueue-next.js.map
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeStateKey = exports.combineStateKeys = exports.resolveEffective = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const get_work_cache_stats_1 = require("../../optimizer/get-work-cache-stats");
6
+ // The state-key engine is shared by the scheduler's hit/miss decision
7
+ // (`checkCacheState`) and the read-only cache-explain engine, so a prediction
8
+ // can never diverge from the decision it predicts — there is one implementation.
9
+ function resolveEffective(item, fallbackMethod) {
10
+ const declared = item.data.caching;
11
+ if (declared.implicit) {
12
+ return { ...declared, method: fallbackMethod };
13
+ }
14
+ return declared;
15
+ }
16
+ exports.resolveEffective = resolveEffective;
17
+ // A task whose inputs cannot be observed cannot be proven up to date, so it
18
+ // always runs, and so does everything depending on it (specs/task SC-001). That
19
+ // is a task that declares no `src` of its own — the sources it inherits from its
20
+ // dependencies say nothing about the files its own commands read — and a task
21
+ // whose `src` entries all match no file (a typo, a moved path). Services are not
22
+ // affected.
23
+ function isOwnProvable(item, unmatched) {
24
+ // unmatched ⊆ declared, so this is also false when nothing is declared
25
+ return unmatched.length < item.data.src.filter((src) => !src.inherited).length;
26
+ }
27
+ // Fold a task's own state key together with the state keys of its dependencies.
28
+ // A change anywhere in the dependency subtree must change the resulting key, so
29
+ // that a downstream task is invalidated when an upstream source changes — even
30
+ // if the downstream task's own sources are untouched.
31
+ function combineStateKeys(ownKey, depKeys) {
32
+ if (depKeys.length === 0) {
33
+ return ownKey;
34
+ }
35
+ // Sort so the key is independent of dependency declaration order.
36
+ const sorted = [...depKeys].sort();
37
+ return (0, crypto_1.createHash)('md5')
38
+ .update([ownKey, ...sorted].join(','))
39
+ .digest('hex');
40
+ }
41
+ exports.combineStateKeys = combineStateKeys;
42
+ // `none` only disables the cache *check* for a task; its sources still influence
43
+ // what depends on it, so a concrete method is always used for the hash itself.
44
+ function concreteMethod(resolved, defaultCacheMethod) {
45
+ return resolved.method === 'none' ? defaultCacheMethod : resolved.method;
46
+ }
47
+ // Compute an item's effective state key from its source files plus the effective
48
+ // state keys of its dependencies, recursively. This is derived purely from the
49
+ // work tree and the filesystem, so it does not depend on scheduler/run state —
50
+ // important because it runs before dependencies are awaited.
51
+ async function resolveEffectiveStateKey(item, defaultCacheMethod, environment, memo) {
52
+ const existing = memo.get(item.id());
53
+ if (existing !== undefined) {
54
+ return existing;
55
+ }
56
+ const { stateKey, provable } = await computeStateKey(item, defaultCacheMethod, environment, memo);
57
+ const effective = { stateKey, provable };
58
+ memo.set(item.id(), effective);
59
+ return effective;
60
+ }
61
+ // Compute the combined state key for a single item together with the source
62
+ // stats it was derived from, sharing one filesystem walk with callers that also
63
+ // need the stats (the scheduler, the explain engine).
64
+ async function computeStateKey(item, defaultCacheMethod, environment, memo = new Map()) {
65
+ const resolved = resolveEffective(item, defaultCacheMethod);
66
+ const { stats, unmatched } = await (0, get_work_cache_stats_1.getWorkItemCacheStats)(item, environment);
67
+ const ownKey = (0, get_work_cache_stats_1.getStateKey)(stats, concreteMethod(resolved, defaultCacheMethod));
68
+ const depKeys = [];
69
+ let provable = isOwnProvable(item, unmatched);
70
+ for (const dep of item.deps) {
71
+ const effective = await resolveEffectiveStateKey(dep, defaultCacheMethod, environment, memo);
72
+ depKeys.push(effective.stateKey);
73
+ provable = provable && effective.provable;
74
+ }
75
+ return { stateKey: combineStateKeys(ownKey, depKeys), stats, resolved, provable, unmatched };
76
+ }
77
+ exports.computeStateKey = computeStateKey;
78
+ //# sourceMappingURL=state-key.js.map
package/dist/index.js CHANGED
@@ -10,6 +10,16 @@ const abortCtrl = new AbortController();
10
10
  process.on('SIGINT', function () {
11
11
  abortCtrl.abort();
12
12
  });
13
+ let settled = false;
14
+ // A run that never settles (a task waiting on something that can't happen)
15
+ // lets the event loop drain, and Node then exits 0: CI would report success
16
+ // for a build that never finished. Fail loudly instead.
17
+ process.on('beforeExit', () => {
18
+ if (!settled) {
19
+ process.stderr.write('hammerkit stopped before the run finished\n');
20
+ process.exitCode = 1;
21
+ }
22
+ });
13
23
  (0, run_program_1.runProgram)({
14
24
  cwd: process.cwd(),
15
25
  abortCtrl,
@@ -20,7 +30,10 @@ process.on('SIGINT', function () {
20
30
  stdout: process.stdout,
21
31
  stderr: process.stderr,
22
32
  stdoutColumns: process.stdout.columns,
23
- }, process.argv, false).catch(() => {
33
+ }, process.argv, false).then(() => {
34
+ settled = true;
35
+ }, () => {
36
+ settled = true;
24
37
  process.exit(1);
25
38
  });
26
39
  //# sourceMappingURL=index.js.map
@@ -1,21 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.apply = exports.statusCodeOf = void 0;
4
+ // The client throws an ApiException carrying the HTTP status as `code`; watch
5
+ // errors carry it as `statusCode`.
4
6
  function statusCodeOf(e) {
5
7
  const err = e;
6
- return err?.statusCode ?? err?.response?.statusCode ?? err?.body?.code;
8
+ return typeof err?.code === 'number' ? err.code : err?.statusCode;
7
9
  }
8
10
  exports.statusCodeOf = statusCodeOf;
9
11
  async function apply(instance, spec) {
10
12
  try {
11
13
  await instance.objectApi.read(spec);
12
- const response = await instance.objectApi.patch(spec);
13
- return response.body;
14
+ return await instance.objectApi.patch(spec);
14
15
  }
15
16
  catch (e) {
16
17
  try {
17
- const response = await instance.objectApi.create(spec);
18
- return response.body;
18
+ return await instance.objectApi.create(spec);
19
19
  }
20
20
  catch (createError) {
21
21
  // Two work items can apply the same object concurrently (e.g. a volume
@@ -23,8 +23,7 @@ async function apply(instance, spec) {
23
23
  // lost the create race, the object now exists — read it back instead of
24
24
  // failing the whole item.
25
25
  if (statusCodeOf(createError) === 409) {
26
- const response = await instance.objectApi.read(spec);
27
- return response.body;
26
+ return await instance.objectApi.read(spec);
28
27
  }
29
28
  throw createError;
30
29
  }
@@ -1,71 +1,111 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.awaitDeployRunningState = exports.awaitJobState = exports.awaitRunningState = void 0;
4
- function awaitRunningState(instance, env, name, phase) {
5
- return new Promise((resolve, reject) => {
6
- const req = instance.watch.watch(`/api/v1/namespaces/${env.namespace}/pods`, {}, (type, obj) => {
7
- if (obj.metadata.name === name && obj.metadata.namespace === env.namespace && obj.status.phase === phase) {
8
- req.then((r) => r.abort());
9
- resolve();
10
- }
11
- }, (err) => {
12
- if (err.message === 'aborted') {
13
- return;
14
- }
15
- if (err) {
16
- reject(err);
17
- }
18
- else {
19
- resolve();
20
- }
21
- });
22
- });
3
+ exports.deleteJobAndWait = exports.awaitDeployRunningState = exports.deleteJob = exports.awaitJobCompletion = exports.awaitRunningState = void 0;
4
+ const abort_1 = require("../executer/abort");
5
+ const abort_event_1 = require("../utils/abort-event");
6
+ const apply_1 = require("./apply");
7
+ const sleep_1 = require("../utils/sleep");
8
+ // The waits below poll instead of using the client's Watch: Watch aborts every
9
+ // request after 30s, which a pod or deployment still pulling its image outlasts.
10
+ async function awaitRunningState(instance, env, name, phase, pollInterval = 1000) {
11
+ for (;;) {
12
+ const pod = await instance.coreApi.readNamespacedPod({ name, namespace: env.namespace });
13
+ const current = pod.status?.phase;
14
+ if (current === phase) {
15
+ return;
16
+ }
17
+ if (current === 'Succeeded' || current === 'Failed') {
18
+ throw new Error(`pod ${name} ended in phase ${current} before reaching ${phase}`);
19
+ }
20
+ await (0, sleep_1.sleep)(pollInterval);
21
+ }
23
22
  }
24
23
  exports.awaitRunningState = awaitRunningState;
25
- function awaitJobState(instance, env, name) {
26
- return new Promise((resolve, reject) => {
27
- const req = instance.watch.watch(`/apis/batch/v1/namespaces/${env.namespace}/jobs/${name}`, {}, (type, obj) => {
28
- if (obj && obj.metadata?.name === name && obj.metadata?.namespace === env.namespace && obj.status?.succeeded) {
29
- req.then((r) => r.abort());
30
- resolve();
31
- }
32
- }, (err) => {
33
- if (err && err.message === 'aborted') {
34
- return;
35
- }
36
- if (err) {
37
- reject(err);
38
- }
39
- else {
40
- resolve();
41
- }
24
+ // Wait for a job to finish by polling its status: resolves once it succeeded,
25
+ // rejects when it failed (its `Failed` condition, i.e. retries are exhausted).
26
+ // On abort the job is deleted — with its pods — and an AbortError is thrown, so
27
+ // a cancelled or timed-out task leaves nothing running on the cluster.
28
+ async function awaitJobCompletion(instance, env, name, abort, pollInterval = 1000) {
29
+ for (;;) {
30
+ if (abort.aborted) {
31
+ await deleteJob(instance, env, name);
32
+ throw new abort_1.AbortError();
33
+ }
34
+ const job = await instance.batchApi.readNamespacedJobStatus({ name, namespace: env.namespace });
35
+ const status = job.status;
36
+ if (status?.succeeded) {
37
+ return;
38
+ }
39
+ const failed = status?.conditions?.find((c) => c.type === 'Failed' && c.status === 'True');
40
+ if (failed) {
41
+ throw new Error(`job ${name} failed: ${failed.message ?? failed.reason ?? 'unknown reason'}`);
42
+ }
43
+ await sleep(pollInterval, abort);
44
+ }
45
+ }
46
+ exports.awaitJobCompletion = awaitJobCompletion;
47
+ function sleep(ms, abort) {
48
+ if (abort.aborted) {
49
+ return Promise.resolve();
50
+ }
51
+ return new Promise((resolve) => {
52
+ const timer = setTimeout(() => {
53
+ listener.close();
54
+ resolve();
55
+ }, ms);
56
+ // not aborted yet, so this callback can only fire later, after assignment
57
+ const listener = (0, abort_event_1.listenOnAbort)(abort, () => {
58
+ clearTimeout(timer);
59
+ resolve();
42
60
  });
43
61
  });
44
62
  }
45
- exports.awaitJobState = awaitJobState;
46
- function awaitDeployRunningState(instance, env, name) {
47
- return new Promise((resolve, reject) => {
48
- const req = instance.watch.watch(`/apis/apps/v1/namespaces/${env.namespace}/deployments`, {}, (type, obj) => {
49
- if ((obj &&
50
- obj.metadata?.name === name &&
51
- obj.metadata?.namespace === env.namespace &&
52
- obj.status?.readyReplicas) ??
53
- 0 > 0) {
54
- req.then((r) => r.abort());
55
- resolve();
56
- }
57
- }, (err) => {
58
- if (err && err.message === 'aborted') {
59
- return;
60
- }
61
- if (err) {
62
- reject(err);
63
- }
64
- else {
65
- resolve();
66
- }
63
+ async function deleteJob(instance, env, name) {
64
+ try {
65
+ await instance.batchApi.deleteNamespacedJob({
66
+ name,
67
+ namespace: env.namespace,
68
+ gracePeriodSeconds: 0,
69
+ propagationPolicy: 'Background',
67
70
  });
68
- });
71
+ }
72
+ catch (e) {
73
+ if ((0, apply_1.statusCodeOf)(e) !== 404) {
74
+ throw e;
75
+ }
76
+ }
77
+ }
78
+ exports.deleteJob = deleteJob;
79
+ async function awaitDeployRunningState(instance, env, name, pollInterval = 1000) {
80
+ for (;;) {
81
+ const deployment = await instance.appsApi.readNamespacedDeployment({ name, namespace: env.namespace });
82
+ if ((deployment.status?.readyReplicas ?? 0) > 0) {
83
+ return;
84
+ }
85
+ await (0, sleep_1.sleep)(pollInterval);
86
+ }
69
87
  }
70
88
  exports.awaitDeployRunningState = awaitDeployRunningState;
89
+ // Delete a job and wait until it is gone: its name is reused for the task's next
90
+ // command (and the next run with the same state), and creating it while the old
91
+ // one is still being deleted would pick up the old job.
92
+ async function deleteJobAndWait(instance, env, name, abort, pollInterval = 500) {
93
+ await deleteJob(instance, env, name);
94
+ for (;;) {
95
+ try {
96
+ await instance.batchApi.readNamespacedJob({ name, namespace: env.namespace });
97
+ }
98
+ catch (e) {
99
+ if ((0, apply_1.statusCodeOf)(e) === 404) {
100
+ return;
101
+ }
102
+ throw e;
103
+ }
104
+ if (abort.aborted) {
105
+ throw new abort_1.AbortError();
106
+ }
107
+ await sleep(pollInterval, abort);
108
+ }
109
+ }
110
+ exports.deleteJobAndWait = deleteJobAndWait;
71
111
  //# sourceMappingURL=await-running-state.js.map
@@ -5,15 +5,12 @@ const apply_1 = require("./apply");
5
5
  const resources_1 = require("./resources");
6
6
  exports.HTTP_ROUTE_API_VERSION = 'gateway.networking.k8s.io/v1';
7
7
  exports.HTTP_ROUTE_KIND = 'HTTPRoute';
8
- async function ensureHttpRoute(instance, env, ingress, service) {
9
- if (!ingress.gateway) {
10
- throw new Error(`httproute for ${ingress.host} requires a "gateway" (the parent Gateway name)`);
11
- }
8
+ async function ensureHttpRoute(instance, env, route, service) {
12
9
  const resource = {
13
10
  apiVersion: exports.HTTP_ROUTE_API_VERSION,
14
11
  kind: exports.HTTP_ROUTE_KIND,
15
12
  metadata: {
16
- name: ingress.host,
13
+ name: route.host,
17
14
  namespace: env.namespace,
18
15
  labels: {
19
16
  'hammerkit.dev/id': service.id(),
@@ -22,25 +19,25 @@ async function ensureHttpRoute(instance, env, ingress, service) {
22
19
  spec: {
23
20
  parentRefs: [
24
21
  {
25
- name: ingress.gateway,
26
- ...(ingress.gatewayNamespace ? { namespace: ingress.gatewayNamespace } : {}),
22
+ name: route.gateway,
23
+ ...(route.gatewayNamespace ? { namespace: route.gatewayNamespace } : {}),
27
24
  },
28
25
  ],
29
- hostnames: [ingress.host],
26
+ hostnames: [route.host],
30
27
  rules: [
31
28
  {
32
29
  matches: [
33
30
  {
34
31
  path: {
35
32
  type: 'PathPrefix',
36
- value: ingress.path ?? '/',
33
+ value: route.path ?? '/',
37
34
  },
38
35
  },
39
36
  ],
40
37
  backendRefs: [
41
38
  {
42
39
  name: (0, resources_1.getResourceName)(service),
43
- port: ingress.servicePort ?? service.data.ports[0].containerPort,
40
+ port: route.servicePort ?? service.data.ports[0].containerPort,
44
41
  },
45
42
  ],
46
43
  },
@@ -1,10 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureNamespace = void 0;
4
- function statusCodeOf(e) {
5
- const err = e;
6
- return err?.statusCode ?? err?.response?.statusCode ?? err?.body?.code;
7
- }
4
+ const apply_1 = require("./apply");
8
5
  /**
9
6
  * Idempotently make sure `namespace` exists. hammerkit does not otherwise
10
7
  * create namespaces — deployments/services/jobs are applied straight into
@@ -16,25 +13,27 @@ function statusCodeOf(e) {
16
13
  */
17
14
  async function ensureNamespace(instance, namespace) {
18
15
  try {
19
- await instance.coreApi.readNamespace(namespace);
16
+ await instance.coreApi.readNamespace({ name: namespace });
20
17
  return;
21
18
  }
22
19
  catch (e) {
23
- if (statusCodeOf(e) !== 404) {
20
+ if ((0, apply_1.statusCodeOf)(e) !== 404) {
24
21
  throw e;
25
22
  }
26
23
  }
27
24
  try {
28
25
  await instance.coreApi.createNamespace({
29
- metadata: {
30
- name: namespace,
31
- labels: { 'hammerkit.dev/managed': 'true' },
26
+ body: {
27
+ metadata: {
28
+ name: namespace,
29
+ labels: { 'hammerkit.dev/managed': 'true' },
30
+ },
32
31
  },
33
32
  });
34
33
  }
35
34
  catch (e) {
36
35
  // another worker created it between our read and create
37
- if (statusCodeOf(e) !== 409) {
36
+ if ((0, apply_1.statusCodeOf)(e) !== 409) {
38
37
  throw e;
39
38
  }
40
39
  }