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
@@ -1,75 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createTestCase = exports.getTestRunId = void 0;
4
- const path_1 = require("path");
5
- const get_file_context_1 = require("../file/get-file-context");
6
- const log_1 = require("../log");
7
- const work_item_status_1 = require("../planner/work-item-status");
8
- const program_1 = require("../program");
9
- const yaml_1 = require("yaml");
10
- const run_program_1 = require("../run-program");
11
- const test_streams_1 = require("./test-streams");
12
- function getTestRunId(name) {
13
- const base = process.env.HAMMERKIT_TEST_RUN_ID ?? `local-${process.pid}`;
14
- return `${base}-${name}`
15
- .toLowerCase()
16
- .replace(/[^a-z0-9-]+/g, '-')
17
- .replace(/^-+|-+$/g, '')
18
- .slice(0, 63);
19
- }
20
- exports.getTestRunId = getTestRunId;
21
- function createTestCase(name, files) {
22
- const runId = getTestRunId(name);
23
- return {
24
- runId,
25
- async shell(args) {
26
- await this.setup(async (cwd, env) => {
27
- await (0, run_program_1.runProgram)(env, args, true);
28
- });
29
- },
30
- async setup(fn) {
31
- const path = (0, path_1.join)(process.cwd(), 'temp', name);
32
- const file = (0, get_file_context_1.getFileContext)(path);
33
- const statusStream = (0, test_streams_1.memoryStream)();
34
- try {
35
- await file.remove(path);
36
- await file.createDirectory(path);
37
- const environment = {
38
- processEnvs: { ...process.env, HAMMERKIT_TEST_RUN_ID: runId },
39
- abortCtrl: new AbortController(),
40
- cwd: path,
41
- file,
42
- console: (0, log_1.consoleContext)((0, test_streams_1.emptyStream)()),
43
- status: (0, work_item_status_1.statusConsole)(statusStream.stream),
44
- stdout: (0, test_streams_1.emptyStream)(),
45
- stderr: (0, test_streams_1.emptyStream)(),
46
- stdoutColumns: 80,
47
- };
48
- for (const [fileName, fileContent] of Object.entries(files)) {
49
- await file.createDirectory((0, path_1.dirname)((0, path_1.join)(path, fileName)));
50
- await file.writeFile((0, path_1.join)(path, fileName), typeof fileContent === 'string' ? fileContent : (0, yaml_1.stringify)(fileContent));
51
- }
52
- await fn(path, environment);
53
- }
54
- catch (e) {
55
- process.stdout.write(statusStream.read());
56
- throw e;
57
- }
58
- },
59
- async cli(scope, fn) {
60
- return this.setup(async (cwd, environment) => {
61
- const fileName = (0, path_1.join)(cwd, '.hammerkit.yaml');
62
- environment.processEnvs = { ...environment.processEnvs, ...(scope.envs ?? {}) };
63
- const cli = await (0, program_1.createCli)(fileName, environment, scope);
64
- // also clear the persistent backend cache (~/.hammerkit/remote-cache)
65
- // so results don't leak between test runs on the same machine.
66
- await cli.clean({ cache: true });
67
- if (fn) {
68
- await fn(cli, environment);
69
- }
70
- });
71
- },
72
- };
73
- }
74
- exports.createTestCase = createTestCase;
75
- //# sourceMappingURL=test-case.js.map
@@ -1,28 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.memoryStream = exports.emptyStream = void 0;
4
- const stream_1 = require("stream");
5
- function emptyStream() {
6
- const stream = new stream_1.Writable();
7
- stream._write = function (_chunk, _encoding, done) {
8
- done();
9
- };
10
- return stream;
11
- }
12
- exports.emptyStream = emptyStream;
13
- function memoryStream() {
14
- const stream = new stream_1.Writable();
15
- const chunks = [];
16
- stream._write = function (chunk, encoding, done) {
17
- chunks.push(chunk.toString());
18
- done();
19
- };
20
- return {
21
- stream,
22
- read() {
23
- return chunks.join('');
24
- },
25
- };
26
- }
27
- exports.memoryStream = memoryStream;
28
- //# sourceMappingURL=test-streams.js.map
@@ -1,3 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=test-suite-setup.js.map
@@ -1,3 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=test-suite.js.map
@@ -1,32 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.testingTimeout = void 0;
4
- const plan_work_tasks_1 = require("../planner/utils/plan-work-tasks");
5
- async function testingTimeout(exec, timeout) {
6
- const timer = setTimeout(() => {
7
- for (const task of (0, plan_work_tasks_1.iterateWorkTasks)(exec.state.current)) {
8
- printWorkItem(task);
9
- }
10
- for (const service of (0, plan_work_tasks_1.iterateWorkServices)(exec.state.current)) {
11
- printWorkItem(service);
12
- }
13
- }, (timeout || 45 * 1000) - 2000);
14
- try {
15
- return await exec.start();
16
- }
17
- finally {
18
- clearTimeout(timer);
19
- }
20
- }
21
- exports.testingTimeout = testingTimeout;
22
- function printWorkItem(task) {
23
- const messages = [` - ${task.name}: ${task.state.current.type}`];
24
- for (const log of task.status.logs()) {
25
- messages.push(` console ${log.console}: ${log.message}`);
26
- }
27
- for (const log of task.status.read()) {
28
- messages.push(` status ${log.level}: ${log.message}`);
29
- }
30
- process.stdout.write(messages.join('\n'));
31
- }
32
- //# sourceMappingURL=testing-timeout.js.map
@@ -1,46 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const abort_event_1 = require("./abort-event");
4
- describe('abort-event', () => {
5
- describe('listenOnAbort', () => {
6
- it('fires immediately when the signal is already aborted', () => {
7
- const ctrl = new AbortController();
8
- ctrl.abort();
9
- const cb = vi.fn();
10
- const handle = (0, abort_event_1.listenOnAbort)(ctrl.signal, cb);
11
- expect(cb).toHaveBeenCalledTimes(1);
12
- // close on a pre-aborted listener is a no-op; calling it must not throw.
13
- expect(() => handle.close()).not.toThrow();
14
- });
15
- it('fires when the signal is aborted later', () => {
16
- const ctrl = new AbortController();
17
- const cb = vi.fn();
18
- (0, abort_event_1.listenOnAbort)(ctrl.signal, cb);
19
- expect(cb).not.toHaveBeenCalled();
20
- ctrl.abort();
21
- expect(cb).toHaveBeenCalledTimes(1);
22
- });
23
- it('does not fire after close()', () => {
24
- const ctrl = new AbortController();
25
- const cb = vi.fn();
26
- const handle = (0, abort_event_1.listenOnAbort)(ctrl.signal, cb);
27
- handle.close();
28
- ctrl.abort();
29
- expect(cb).not.toHaveBeenCalled();
30
- });
31
- });
32
- describe('waitOnAbort', () => {
33
- it('resolves immediately when already aborted', async () => {
34
- const ctrl = new AbortController();
35
- ctrl.abort();
36
- await expect((0, abort_event_1.waitOnAbort)(ctrl.signal)).resolves.toBeUndefined();
37
- });
38
- it('resolves when the signal is aborted later', async () => {
39
- const ctrl = new AbortController();
40
- const pending = (0, abort_event_1.waitOnAbort)(ctrl.signal);
41
- ctrl.abort();
42
- await expect(pending).resolves.toBeUndefined();
43
- });
44
- });
45
- });
46
- //# sourceMappingURL=abort-event.spec.js.map
@@ -1,38 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const buffer_context_1 = require("./buffer-context");
4
- describe('BufferContext', () => {
5
- it('returns null for current of an unknown id', () => {
6
- const ctx = new buffer_context_1.BufferContext(10);
7
- expect(ctx.current('missing')).toBeNull();
8
- });
9
- it('returns an empty array for get of an unknown id', () => {
10
- const ctx = new buffer_context_1.BufferContext(10);
11
- expect(ctx.get('missing')).toEqual([]);
12
- });
13
- it('tracks the latest value per id', () => {
14
- const ctx = new buffer_context_1.BufferContext(10);
15
- ctx.add('a', 1);
16
- ctx.add('a', 2);
17
- ctx.add('b', 9);
18
- expect(ctx.current('a')).toBe(2);
19
- expect(ctx.current('b')).toBe(9);
20
- });
21
- it('keeps per-id and global buffers', () => {
22
- const ctx = new buffer_context_1.BufferContext(10);
23
- ctx.add('a', 1);
24
- ctx.add('b', 2);
25
- expect(ctx.get('a')).toEqual([1]);
26
- expect(ctx.get('b')).toEqual([2]);
27
- expect(ctx.get()).toEqual([1, 2]);
28
- });
29
- it('evicts the oldest global entry once an id exceeds the limit', () => {
30
- const ctx = new buffer_context_1.BufferContext(2);
31
- ctx.add('a', 1);
32
- ctx.add('a', 2);
33
- ctx.add('a', 3);
34
- expect(ctx.get()).toEqual([2, 3]);
35
- expect(ctx.current('a')).toBe(3);
36
- });
37
- });
38
- //# sourceMappingURL=buffer-context.spec.js.map
@@ -1,53 +0,0 @@
1
- "use strict";
2
- // Each `isCI` arm is evaluated once at module load, so the only way to cover
3
- // every branch of the OR chain is to load the module repeatedly under different
4
- // process.env. vi.resetModules + a fresh dynamic import gives us that.
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
- Object.defineProperty(o, "default", { enumerable: true, value: v });
18
- }) : function(o, v) {
19
- o["default"] = v;
20
- });
21
- var __importStar = (this && this.__importStar) || function (mod) {
22
- if (mod && mod.__esModule) return mod;
23
- var result = {};
24
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
25
- __setModuleDefault(result, mod);
26
- return result;
27
- };
28
- const KEYS = ['CI', 'CONTINUOUS_INTEGRATION', 'BUILD_NUMBER', 'RUN_ID'];
29
- async function loadIsCI(set) {
30
- for (const k of KEYS)
31
- delete process.env[k];
32
- if (set)
33
- Object.assign(process.env, set);
34
- vi.resetModules();
35
- return (await Promise.resolve().then(() => __importStar(require('./ci')))).isCI;
36
- }
37
- describe('isCI', () => {
38
- const original = { ...process.env };
39
- afterEach(() => {
40
- for (const k of KEYS)
41
- delete process.env[k];
42
- for (const k of KEYS)
43
- if (k in original)
44
- process.env[k] = original[k];
45
- });
46
- it('is false when no CI env var is set', async () => {
47
- expect(await loadIsCI()).toBe(false);
48
- });
49
- it.each(KEYS)('is true when %s is set', async (key) => {
50
- expect(await loadIsCI({ [key]: '1' })).toBe(true);
51
- });
52
- });
53
- //# sourceMappingURL=ci.spec.js.map
@@ -1,58 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const debouncer_1 = require("./debouncer");
4
- const sleep_1 = require("./sleep");
5
- describe('utils/debouncer', () => {
6
- it('should not trigger before start', async () => {
7
- let called = false;
8
- new debouncer_1.Debouncer(() => {
9
- called = true;
10
- }, 10);
11
- await (0, sleep_1.sleep)(20);
12
- expect(called).toBeFalsy();
13
- });
14
- it('should trigger after start', async () => {
15
- let called = false;
16
- const debouncer = new debouncer_1.Debouncer(() => {
17
- called = true;
18
- }, 10);
19
- debouncer.start();
20
- await (0, sleep_1.sleep)(20);
21
- expect(called).toBeTruthy();
22
- });
23
- it('should delay trigger after bounce', async () => {
24
- let called = false;
25
- const debouncer = new debouncer_1.Debouncer(() => {
26
- called = true;
27
- }, 20);
28
- debouncer.start();
29
- await (0, sleep_1.sleep)(10);
30
- debouncer.bounce();
31
- await (0, sleep_1.sleep)(15);
32
- expect(called).toBeFalsy();
33
- await (0, sleep_1.sleep)(5);
34
- expect(called).toBeTruthy();
35
- });
36
- it('should cancel trigger after clear', async () => {
37
- let called = false;
38
- const debouncer = new debouncer_1.Debouncer(() => {
39
- called = true;
40
- }, 20);
41
- debouncer.start();
42
- await (0, sleep_1.sleep)(10);
43
- debouncer.clear();
44
- await (0, sleep_1.sleep)(15);
45
- expect(called).toBeFalsy();
46
- });
47
- it('should trigger on flush', async () => {
48
- let called = false;
49
- const debouncer = new debouncer_1.Debouncer(() => {
50
- called = true;
51
- }, 20);
52
- debouncer.start();
53
- await (0, sleep_1.sleep)(5);
54
- debouncer.flush();
55
- expect(called).toBeTruthy();
56
- });
57
- });
58
- //# sourceMappingURL=debouncer.spec.js.map
@@ -1,34 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const emitter_1 = require("./emitter");
4
- describe('emitter', () => {
5
- it('invokes every registered listener on emit', () => {
6
- const e = (0, emitter_1.emitter)();
7
- const a = vi.fn();
8
- const b = vi.fn();
9
- e.on(a);
10
- e.on(b);
11
- e.emit(42);
12
- expect(a).toHaveBeenCalledWith(42);
13
- expect(b).toHaveBeenCalledWith(42);
14
- });
15
- it('emit with no listeners is a no-op', () => {
16
- const e = (0, emitter_1.emitter)();
17
- expect(() => e.emit('noop')).not.toThrow();
18
- });
19
- it('close() removes the listener from future emits', () => {
20
- const e = (0, emitter_1.emitter)();
21
- const cb = vi.fn();
22
- const handle = e.on(cb);
23
- handle.close();
24
- e.emit(1);
25
- expect(cb).not.toHaveBeenCalled();
26
- });
27
- it('close() called twice is safe (no listener to remove)', () => {
28
- const e = (0, emitter_1.emitter)();
29
- const handle = e.on(vi.fn());
30
- handle.close();
31
- expect(() => handle.close()).not.toThrow();
32
- });
33
- });
34
- //# sourceMappingURL=emitter.spec.js.map
@@ -1,18 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const empty_writable_1 = require("./empty-writable");
4
- describe('emptyWritable', () => {
5
- it('accepts writes and invokes the callback', async () => {
6
- const stream = (0, empty_writable_1.emptyWritable)();
7
- await new Promise((resolve, reject) => {
8
- stream.write('hello', (err) => (err ? reject(err) : resolve()));
9
- });
10
- });
11
- it('ends cleanly after writes', async () => {
12
- const stream = (0, empty_writable_1.emptyWritable)();
13
- stream.write('a');
14
- stream.write('b');
15
- await new Promise((resolve) => stream.end(resolve));
16
- });
17
- });
18
- //# sourceMappingURL=empty-writable.spec.js.map
@@ -1,26 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const environment_config_1 = require("./environment-config");
4
- const VAR = 'HAMMERKIT_TEST_ENV_CONFIG';
5
- describe('getEnvironmentConfig', () => {
6
- afterEach(() => {
7
- delete process.env[VAR];
8
- });
9
- it('returns the default when the variable is unset', () => {
10
- delete process.env[VAR];
11
- expect((0, environment_config_1.getEnvironmentConfig)(VAR, 7)).toBe(7);
12
- });
13
- it('parses an integer env value', () => {
14
- process.env[VAR] = '42';
15
- expect((0, environment_config_1.getEnvironmentConfig)(VAR, 7)).toBe(42);
16
- });
17
- it('falls back to the default when the env value is not a number', () => {
18
- process.env[VAR] = 'not-a-number';
19
- expect((0, environment_config_1.getEnvironmentConfig)(VAR, 7)).toBe(7);
20
- });
21
- it('treats an empty string env value as not-a-number and uses the default', () => {
22
- process.env[VAR] = '';
23
- expect((0, environment_config_1.getEnvironmentConfig)(VAR, 5)).toBe(5);
24
- });
25
- });
26
- //# sourceMappingURL=environment-config.spec.js.map
@@ -1,9 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fail_never_1 = require("./fail-never");
4
- describe('failNever', () => {
5
- it('throws an error with the given message', () => {
6
- expect(() => (0, fail_never_1.failNever)('value', 'unexpected')).toThrow('unexpected');
7
- });
8
- });
9
- //# sourceMappingURL=fail-never.spec.js.map
@@ -1,22 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const sleep_1 = require("./sleep");
4
- describe('utils/sleep', () => {
5
- it('should resolve not before timeout', () => {
6
- let called = false;
7
- (0, sleep_1.sleep)(10).then(() => {
8
- called = true;
9
- });
10
- expect(called).toBeFalsy();
11
- });
12
- it('should resolve after timeout', async () => {
13
- let called = false;
14
- (0, sleep_1.sleep)(10).then(() => {
15
- called = true;
16
- });
17
- expect(called).toBeFalsy();
18
- await (0, sleep_1.sleep)(10);
19
- expect(called).toBeTruthy();
20
- });
21
- });
22
- //# sourceMappingURL=sleep.spec.js.map
@@ -1,12 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const version_1 = require("./version");
4
- // eslint-disable-next-line @typescript-eslint/no-var-requires
5
- const pkg = require('../package.json');
6
- describe('getVersion', () => {
7
- it('returns the version from package.json', () => {
8
- expect((0, version_1.getVersion)()).toBe(pkg.version);
9
- expect((0, version_1.getVersion)()).toMatch(/^\d+\.\d+\.\d+/);
10
- });
11
- });
12
- //# sourceMappingURL=version.spec.js.map
@@ -1,114 +0,0 @@
1
- # Feature Specification: Build File: Caches
2
-
3
- **Feature Branch**: `001-current-state-specs`
4
-
5
- **Created**: 2026-05-30
6
-
7
- **Status**: Current State (reverse-specified from docs, hammerkit 1.6.0)
8
-
9
- **Input**: Existing behavior documented in `docs/build-file/caches.md`
10
-
11
- ## User Scenarios & Testing *(mandatory)*
12
-
13
- ### User Story 1 - Skip unchanged tasks using the default local cache (Priority: P1)
14
-
15
- A developer runs hammerkit for the first time. Results are stored in the local cache (`~/.hammerkit/remote-cache`). On subsequent runs, hammerkit compares source file checksums and skips tasks whose inputs have not changed, saving build time without any configuration.
16
-
17
- **Why this priority**: Local caching is the out-of-the-box experience for every hammerkit user; it is always active.
18
-
19
- **Independent Test**: Run a task twice without modifying source files. Confirm the second run skips execution. Modify a source file and confirm the task runs again.
20
-
21
- **Acceptance Scenarios**:
22
-
23
- 1. **Given** a task with `src:` files that have not changed since the last run, **When** the task is invoked, **Then** hammerkit skips execution and reports the task as up-to-date.
24
- 2. **Given** a task with `src:` files where at least one has changed, **When** the task is invoked, **Then** hammerkit executes the task commands.
25
-
26
- ### User Story 2 - Share build results across machines via a remote cache (Priority: P1)
27
-
28
- A team uses an S3 bucket as a shared cache. When a developer or CI job completes a task, the result is pushed to S3. The next machine to request the same task pulls the cached result and skips execution, dramatically reducing CI time.
29
-
30
- **Why this priority**: Remote caching is the primary new capability in 1.6.0 and a key differentiator for team and CI workflows.
31
-
32
- **Independent Test**: Declare an S3-backed cache, run a task on machine A and confirm the result is pushed. On machine B (with empty local cache), run the same task and confirm it is pulled and skipped.
33
-
34
- **Acceptance Scenarios**:
35
-
36
- 1. **Given** a named cache with `type: s3` is declared and a task references it, **When** a task completes successfully, **Then** the result is pushed to the configured S3 bucket.
37
- 2. **Given** the S3 bucket contains a cached result for the current source checksum, **When** the task is invoked on a machine without a local result, **Then** hammerkit pulls from the bucket and skips task execution.
38
- 3. **Given** the S3 backend is unreachable, **When** a task runs, **Then** hammerkit emits a warning and continues — the pull or push failure does NOT fail the build.
39
-
40
- ### User Story 3 - Override the default cache for all tasks (Priority: P2)
41
-
42
- A team wants all tasks to use a remote S3 bucket without modifying each task individually. They declare a `caches.default` entry in the build file, redirecting the implicit default cache to their S3 bucket.
43
-
44
- **Why this priority**: This is the recommended way to enable remote caching for an entire project with a single configuration change.
45
-
46
- **Independent Test**: Declare `caches.default` pointing to S3, run several tasks that do not explicitly reference any cache, and confirm each pushes results to the S3 bucket.
47
-
48
- **Acceptance Scenarios**:
49
-
50
- 1. **Given** `caches.default` is declared with an S3 backend, **When** a task that does not reference any named cache runs and succeeds, **Then** its result is pushed to the S3 bucket specified in `caches.default`.
51
-
52
- ### User Story 4 - Choose a change-detection method for a cache (Priority: P2)
53
-
54
- A developer with a large source tree wants faster change detection. They configure a named cache to use `modify-date` instead of `checksum`. A CI workflow that must always rebuild configures a cache with `none` method.
55
-
56
- **Why this priority**: Method selection lets users trade precision for speed or disable skipping entirely for correctness-critical pipelines.
57
-
58
- **Independent Test**: Configure a task with `method: none`; run it twice and confirm it always executes. Configure a task with `method: modify-date`; run it twice without touching files and confirm it is skipped.
59
-
60
- **Acceptance Scenarios**:
61
-
62
- 1. **Given** a cache with `method: none`, **When** any task using that cache is invoked, **Then** the task always executes, regardless of source file state.
63
- 2. **Given** a cache with `method: checksum` (default), **When** source file content is identical to the previous run, **Then** the task is skipped.
64
- 3. **Given** a cache with `method: modify-date`, **When** source file modification timestamps are unchanged, **Then** the task is skipped without computing checksums.
65
-
66
- ### Edge Cases
67
-
68
- - Two built-in caches are always available: `default` (local backend, checksum method) and `none` (disables skipping).
69
- - Declaring `caches.default` overrides the built-in `default` cache for all tasks that do not explicitly reference a cache.
70
- - The `--cache` CLI flag overrides the method for implicit (non-explicitly-referenced) tasks at runtime and takes precedence over what `caches.default` declares for the method.
71
- - S3 credentials are never stored in the build file; they are read from the AWS SDK credential chain.
72
- - Azure Blob Storage is explicitly unsupported through the built-in `s3` backend.
73
- - Any S3-compatible service (MinIO, Cloudflare R2, GCS via HMAC keys) can be used via the `endpoint` field.
74
- - `forcePathStyle` defaults to `true` when a custom `endpoint` is set.
75
-
76
- ## Requirements *(mandatory)*
77
-
78
- ### Functional Requirements
79
-
80
- - **FR-001**: The build file MUST support a top-level `caches:` map where each entry declares a named cache with a `method` and a `backend`.
81
- - **FR-002**: Each cache entry MUST support a `method` field with values `checksum` (default), `modify-date`, or `none`.
82
- - **FR-003**: Each cache entry MUST support a `backend` field specifying the storage backend type (`local` or `s3`).
83
- - **FR-004**: The `local` backend MUST accept an optional `path` field; when omitted it MUST default to `~/.hammerkit/remote-cache`.
84
- - **FR-005**: The `s3` backend MUST require a `bucket` field and accept optional `region`, `endpoint`, `prefix`, and `forcePathStyle` fields.
85
- - **FR-006**: S3 credentials MUST be sourced from the AWS SDK credential chain and MUST NOT be stored in the build file.
86
- - **FR-007**: Before a task runs, the system MUST attempt to pull a cached result from the backend if no local result is present; if the sources are unchanged, the task MUST be skipped.
87
- - **FR-008**: After a task completes successfully, the system MUST push the result to the configured backend.
88
- - **FR-009**: Backend errors during pull or push MUST emit a warning and MUST NOT cause the build to fail.
89
- - **FR-010**: Declaring `caches.default` MUST override the built-in default cache for all tasks that do not explicitly reference a named cache.
90
- - **FR-011**: A built-in `none` cache MUST always be available, which disables task skipping entirely.
91
- - **FR-012**: A task MUST be able to reference a named cache by setting `cache.name` in its task configuration.
92
-
93
- ### Key Entities
94
-
95
- - **Cache**: A named entry in the `caches:` map; contains a `method` (change detection strategy) and a `backend` (storage configuration).
96
- - **Local Backend**: Stores cache entries on the local filesystem; configurable via a `path` field.
97
- - **S3 Backend**: Stores cache entries in an S3-compatible bucket; configurable via `bucket`, `region`, `endpoint`, `prefix`, and `forcePathStyle`.
98
-
99
- ## Success Criteria *(mandatory)*
100
-
101
- ### Measurable Outcomes
102
-
103
- - **SC-001**: An unchanged task is skipped on the second invocation when using the default local cache with `checksum` method.
104
- - **SC-002**: A task result pushed from one machine is pulled and skips execution on a second machine using the same S3-backed cache configuration.
105
- - **SC-003**: A build completes successfully even when the configured S3 backend is unreachable, with a warning visible in the output.
106
- - **SC-004**: Declaring `caches.default` with an S3 backend causes all tasks (that do not explicitly reference a different cache) to push their results to that S3 bucket.
107
- - **SC-005**: A task using `method: none` executes on every invocation regardless of whether source files have changed.
108
-
109
- ## Assumptions
110
-
111
- - The `cache.name` field on a task (referencing a named cache) is the task-level hook into this feature; full task-level caching options are covered by the task/caching spec page.
112
- - The `--cache` CLI flag controls the method for tasks using the implicit default cache; it does not affect tasks that explicitly reference a named cache by name.
113
- - "Pulling" a cached result means restoring the task's `generates:` outputs so downstream tasks can use them without re-running the producing task.
114
- - The `checksum` method hashes file content; `modify-date` compares filesystem mtime; both apply to the files listed in the task's `src:` field.