hammerkit 1.6.0 → 1.7.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 (256) hide show
  1. package/README.md +4 -4
  2. package/build.schema.json +49 -2
  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 +5 -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 +2 -1
  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-work-service.js +10 -1
  27. package/dist/executer/execute-work-task.js +95 -25
  28. package/dist/executer/execute-work-tree.js +26 -1
  29. package/dist/executer/get-scheduler-execute-result.js +4 -1
  30. package/dist/executer/lazy-resolver.js +15 -3
  31. package/dist/executer/reset-work-tree.js +2 -0
  32. package/dist/executer/run-summary.js +102 -0
  33. package/dist/executer/scheduler/enqueue-next.js +15 -60
  34. package/dist/executer/scheduler/state-key.js +78 -0
  35. package/dist/kubernetes/await-running-state.js +73 -19
  36. package/dist/log.js +16 -6
  37. package/dist/logging/live-logger.js +2 -1
  38. package/dist/optimizer/calculate-checksum.js +8 -2
  39. package/dist/optimizer/get-work-cache-stats.js +66 -16
  40. package/dist/optimizer/work-task-cache-description.js +34 -5
  41. package/dist/planner/project-root.js +26 -0
  42. package/dist/planner/utils/append-work-service.js +2 -1
  43. package/dist/planner/utils/append-work-task.js +7 -1
  44. package/dist/planner/utils/parse-work-source.js +35 -30
  45. package/dist/planner/utils/portable-path.js +30 -0
  46. package/dist/planner/work-cache-id.js +2 -2
  47. package/dist/planner/work-graph-serializer.js +103 -0
  48. package/dist/planner/work-instance-id.js +15 -0
  49. package/dist/planner/work-runtime-docker.js +53 -15
  50. package/dist/planner/work-runtime-kubernetes.js +35 -3
  51. package/dist/planner/work-runtime-local.js +9 -3
  52. package/dist/planner/work-service-id.js +16 -4
  53. package/dist/program.js +235 -1
  54. package/dist/schema/build-file-base-task-schema.js +4 -0
  55. package/dist/schema/cache-schema.js +21 -2
  56. package/dist/schema/duration-schema.js +18 -0
  57. package/dist/schema/reference-parser.js +3 -0
  58. package/dist/schema/size-schema.js +18 -0
  59. package/dist/schema/work-scope-parser.js +15 -2
  60. package/dist/utils/units.js +87 -0
  61. package/package.json +12 -1
  62. package/.agents/FEEDBACK.md +0 -74
  63. package/.agents/skills/before-commit/SKILL.md +0 -101
  64. package/.agents/skills/do-the-work/SKILL.md +0 -64
  65. package/.agents/skills/fix-ci/SKILL.md +0 -98
  66. package/.agents/skills/speckit-git-commit/SKILL.md +0 -53
  67. package/.agents/skills/speckit-git-feature/SKILL.md +0 -72
  68. package/.agents/skills/speckit-git-initialize/SKILL.md +0 -54
  69. package/.agents/skills/speckit-git-remote/SKILL.md +0 -50
  70. package/.agents/skills/speckit-git-validate/SKILL.md +0 -54
  71. package/.devcontainer/Dockerfile +0 -5
  72. package/.devcontainer/devcontainer.json +0 -40
  73. package/.gitbook.yaml +0 -5
  74. package/.specify/extensions/.registry +0 -37
  75. package/.specify/extensions/git/README.md +0 -100
  76. package/.specify/extensions/git/commands/speckit.git.commit.md +0 -48
  77. package/.specify/extensions/git/commands/speckit.git.feature.md +0 -67
  78. package/.specify/extensions/git/commands/speckit.git.initialize.md +0 -49
  79. package/.specify/extensions/git/commands/speckit.git.remote.md +0 -45
  80. package/.specify/extensions/git/commands/speckit.git.validate.md +0 -49
  81. package/.specify/extensions/git/config-template.yml +0 -62
  82. package/.specify/extensions/git/extension.yml +0 -140
  83. package/.specify/extensions/git/git-config.yml +0 -62
  84. package/.specify/extensions.yml +0 -149
  85. package/.specify/feature.json +0 -3
  86. package/.specify/init-options.json +0 -10
  87. package/.specify/integration.json +0 -15
  88. package/.specify/integrations/claude.manifest.json +0 -16
  89. package/.specify/integrations/speckit.manifest.json +0 -17
  90. package/.specify/memory/constitution.md +0 -162
  91. package/.specify/templates/checklist-template.md +0 -40
  92. package/.specify/templates/constitution-template.md +0 -50
  93. package/.specify/templates/plan-template.md +0 -113
  94. package/.specify/templates/spec-template.md +0 -131
  95. package/.specify/templates/tasks-template.md +0 -252
  96. package/.specify/workflows/speckit/workflow.yml +0 -77
  97. package/.specify/workflows/workflow-registry.json +0 -13
  98. package/AGENTS.md +0 -94
  99. package/Dockerfile +0 -32
  100. package/FOLLOWUPS.md +0 -34
  101. package/SUMMARY.md +0 -68
  102. package/VALUES.md +0 -53
  103. package/dist/cache/auto-pull.spec.js +0 -123
  104. package/dist/cache/backends/local-cache-backend.spec.js +0 -72
  105. package/dist/cache/backends/s3-cache-backend.spec.js +0 -118
  106. package/dist/cache/cache-backend-registry.spec.js +0 -22
  107. package/dist/cache/resolve-cache.spec.js +0 -52
  108. package/dist/cli.spec.js +0 -23
  109. package/dist/console/get-logger.spec.js +0 -23
  110. package/dist/docker/get-default-platform.spec.js +0 -29
  111. package/dist/docker/longest-common-prefix.spec.js +0 -26
  112. package/dist/docker/package.spec.js +0 -145
  113. package/dist/docker/remove-container.spec.js +0 -32
  114. package/dist/docker/using-container.spec.js +0 -90
  115. package/dist/environment/merge-environment-variables.spec.js +0 -24
  116. package/dist/environment/replace-env-variables.spec.js +0 -47
  117. package/dist/executer/abort.spec.js +0 -18
  118. package/dist/executer/environment-mock.spec.js +0 -22
  119. package/dist/executer/execute.spec.js +0 -51
  120. package/dist/executer/get-service-containers.spec.js +0 -59
  121. package/dist/executer/get-service-env-hints.spec.js +0 -65
  122. package/dist/executer/label-values.spec.js +0 -61
  123. package/dist/executer/lazy-resolver.spec.js +0 -20
  124. package/dist/executer/process-manager.spec.js +0 -54
  125. package/dist/executer/scheduler/enqueue-next.spec.js +0 -136
  126. package/dist/executer/state-resolver.spec.js +0 -37
  127. package/dist/executer/state.spec.js +0 -66
  128. package/dist/executer/states.spec.js +0 -14
  129. package/dist/file/get-file-context.spec.js +0 -178
  130. package/dist/file/move-files.spec.js +0 -48
  131. package/dist/kubernetes/ensure-http-route.spec.js +0 -70
  132. package/dist/kubernetes/ensure-namespace.spec.js +0 -39
  133. package/dist/kubernetes/resolve-pod-name.spec.js +0 -81
  134. package/dist/log.spec.js +0 -131
  135. package/dist/logging/grouped-logger.spec.js +0 -84
  136. package/dist/logging/interactive-logger.spec.js +0 -57
  137. package/dist/logging/live-logger.spec.js +0 -37
  138. package/dist/logging/loggers.spec.js +0 -154
  139. package/dist/optimizer/calculate-checksum.spec.js +0 -34
  140. package/dist/optimizer/get-cache-directory.spec.js +0 -59
  141. package/dist/optimizer/get-work-cache-stats.spec.js +0 -79
  142. package/dist/optimizer/work-task-cache-description.spec.js +0 -45
  143. package/dist/parser/default-build-file.spec.js +0 -48
  144. package/dist/parser/parse-label-arguments.spec.js +0 -27
  145. package/dist/parser/read-build-file.spec.js +0 -89
  146. package/dist/parser/read-env-file.spec.js +0 -42
  147. package/dist/planner/utils/append-work-service.spec.js +0 -71
  148. package/dist/planner/utils/get-container-user.spec.js +0 -30
  149. package/dist/planner/utils/get-default-kube-config.spec.js +0 -11
  150. package/dist/planner/utils/normalize-path.spec.js +0 -24
  151. package/dist/planner/utils/parse-work-command.spec.js +0 -21
  152. package/dist/planner/utils/parse-work-generate.spec.js +0 -45
  153. package/dist/planner/utils/parse-work-mount.spec.js +0 -62
  154. package/dist/planner/utils/parse-work-mounts.spec.js +0 -19
  155. package/dist/planner/utils/parse-work-port.spec.js +0 -24
  156. package/dist/planner/utils/parse-work-ports.spec.js +0 -24
  157. package/dist/planner/utils/parse-work-source.spec.js +0 -58
  158. package/dist/planner/utils/parse-work-volume.spec.js +0 -18
  159. package/dist/planner/utils/plan-work-tasks.spec.js +0 -30
  160. package/dist/planner/utils/plan-work-volume.spec.js +0 -33
  161. package/dist/planner/utils/split-name.spec.js +0 -16
  162. package/dist/planner/utils/template-value.spec.js +0 -14
  163. package/dist/planner/validate.spec.js +0 -251
  164. package/dist/planner/work-cache-id.spec.js +0 -173
  165. package/dist/planner/work-runtime-local.spec.js +0 -193
  166. package/dist/program-commands.spec.js +0 -84
  167. package/dist/program.spec.js +0 -43
  168. package/dist/schema/build-file-schema.spec.js +0 -61
  169. package/dist/schema/doc-build-files.spec.js +0 -223
  170. package/dist/schema/parse-error.spec.js +0 -18
  171. package/dist/schema/reference-parser.spec.js +0 -83
  172. package/dist/schema/schema-parser.spec.js +0 -80
  173. package/dist/schema/type-guards.spec.js +0 -23
  174. package/dist/schema/work-scope-parser.spec.js +0 -66
  175. package/dist/service/update-service-status.spec.js +0 -27
  176. package/dist/testing/ensure-local-registry.js +0 -134
  177. package/dist/testing/example-test-suite.js +0 -63
  178. package/dist/testing/expect.js +0 -82
  179. package/dist/testing/get-test-suite.js +0 -9
  180. package/dist/testing/http-get.js +0 -21
  181. package/dist/testing/integration/cache.spec.js +0 -49
  182. package/dist/testing/integration/cancellation.spec.js +0 -45
  183. package/dist/testing/integration/clean.spec.js +0 -43
  184. package/dist/testing/integration/cmd.spec.js +0 -15
  185. package/dist/testing/integration/concurrency.spec.js +0 -19
  186. package/dist/testing/integration/docker-package-registry.spec.js +0 -44
  187. package/dist/testing/integration/docker-service-crash.spec.js +0 -36
  188. package/dist/testing/integration/docker.spec.js +0 -18
  189. package/dist/testing/integration/env.spec.js +0 -48
  190. package/dist/testing/integration/environment.spec.js +0 -97
  191. package/dist/testing/integration/error.spec.js +0 -19
  192. package/dist/testing/integration/export.spec.js +0 -22
  193. package/dist/testing/integration/extend.spec.js +0 -29
  194. package/dist/testing/integration/glob.spec.js +0 -40
  195. package/dist/testing/integration/include.spec.js +0 -31
  196. package/dist/testing/integration/invalid.spec.js +0 -17
  197. package/dist/testing/integration/invalid_loop.spec.js +0 -56
  198. package/dist/testing/integration/k8s-state-cache.spec.js +0 -41
  199. package/dist/testing/integration/kubernetes-service.spec.js +0 -148
  200. package/dist/testing/integration/kubernetes.spec.js +0 -99
  201. package/dist/testing/integration/labels.spec.js +0 -42
  202. package/dist/testing/integration/local.spec.js +0 -14
  203. package/dist/testing/integration/monorepo.spec.js +0 -15
  204. package/dist/testing/integration/multi-line.spec.js +0 -17
  205. package/dist/testing/integration/reference.spec.js +0 -21
  206. package/dist/testing/integration/services.spec.js +0 -38
  207. package/dist/testing/integration/store.spec.js +0 -52
  208. package/dist/testing/integration/unknown.spec.js +0 -28
  209. package/dist/testing/integration/validate.spec.js +0 -160
  210. package/dist/testing/integration/watch-dependency.spec.js +0 -33
  211. package/dist/testing/integration/watch.spec.js +0 -45
  212. package/dist/testing/requires-kubernetes.js +0 -18
  213. package/dist/testing/requires-linux-containers.js +0 -18
  214. package/dist/testing/requires-windows-containers.js +0 -18
  215. package/dist/testing/test-case.js +0 -75
  216. package/dist/testing/test-streams.js +0 -28
  217. package/dist/testing/test-suite-setup.js +0 -3
  218. package/dist/testing/test-suite.js +0 -3
  219. package/dist/testing/testing-timeout.js +0 -32
  220. package/dist/utils/abort-event.spec.js +0 -46
  221. package/dist/utils/buffer-context.spec.js +0 -38
  222. package/dist/utils/ci.spec.js +0 -53
  223. package/dist/utils/debouncer.spec.js +0 -58
  224. package/dist/utils/emitter.spec.js +0 -34
  225. package/dist/utils/empty-writable.spec.js +0 -18
  226. package/dist/utils/environment-config.spec.js +0 -26
  227. package/dist/utils/fail-never.spec.js +0 -9
  228. package/dist/utils/sleep.spec.js +0 -22
  229. package/dist/version.spec.js +0 -12
  230. package/specs/build-file/caches/spec.md +0 -114
  231. package/specs/build-file/environment-variables/spec.md +0 -82
  232. package/specs/build-file/includes/spec.md +0 -60
  233. package/specs/build-file/references/spec.md +0 -56
  234. package/specs/build-file/spec.md +0 -62
  235. package/specs/cli/clean/spec.md +0 -85
  236. package/specs/cli/down/spec.md +0 -85
  237. package/specs/cli/execute/spec.md +0 -101
  238. package/specs/cli/init/spec.md +0 -64
  239. package/specs/cli/ls/spec.md +0 -66
  240. package/specs/cli/package/spec.md +0 -110
  241. package/specs/cli/spec.md +0 -73
  242. package/specs/cli/store-restore/spec.md +0 -74
  243. package/specs/cli/up/spec.md +0 -80
  244. package/specs/cli/validate/spec.md +0 -70
  245. package/specs/labels/spec.md +0 -91
  246. package/specs/service/container/spec.md +0 -120
  247. package/specs/service/kubernetes/spec.md +0 -97
  248. package/specs/service/spec.md +0 -105
  249. package/specs/task/caching/spec.md +0 -107
  250. package/specs/task/container/spec.md +0 -118
  251. package/specs/task/dependencies/spec.md +0 -70
  252. package/specs/task/extending/spec.md +0 -74
  253. package/specs/task/kubernetes/spec.md +0 -125
  254. package/specs/task/needs/spec.md +0 -59
  255. package/specs/task/spec.md +0 -87
  256. package/specs/task/watching/spec.md +0 -69
@@ -1,83 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const test_case_1 = require("../testing/test-case");
4
- const program_1 = require("../program");
5
- const path_1 = require("path");
6
- // Drive the full parse pipeline (schema-parser -> parseReferences -> getWorkContext)
7
- // over an in-memory build file and assert on the resulting work tree. This exercises
8
- // reference resolution: deps, needs, label merging and schema `extend`.
9
- async function withCli(buildFile, scope, fn) {
10
- const testCase = (0, test_case_1.createTestCase)('reference-parser', { '.hammerkit.yaml': buildFile });
11
- await testCase.setup(async (cwd, environment) => {
12
- const cli = await (0, program_1.createCli)((0, path_1.join)(cwd, '.hammerkit.yaml'), environment, scope);
13
- await fn(cli);
14
- });
15
- }
16
- describe('reference-parser', () => {
17
- it('resolves task dependencies', async () => {
18
- await withCli({
19
- tasks: {
20
- install: { cmds: ['npm install'] },
21
- build: { cmds: ['tsc'], deps: ['install'] },
22
- },
23
- }, { taskName: 'build' }, (cli) => {
24
- const build = cli.task('build');
25
- expect(build.deps.map((d) => d.name)).toEqual(['install']);
26
- });
27
- });
28
- it('resolves service needs', async () => {
29
- await withCli({
30
- services: {
31
- api: { image: 'nginx', ports: [] },
32
- },
33
- tasks: {
34
- build: { cmds: ['tsc'], needs: ['api'] },
35
- },
36
- }, { taskName: 'build' }, (cli) => {
37
- const build = cli.task('build');
38
- expect(build.needs.map((n) => n.service.name)).toEqual(['api']);
39
- });
40
- });
41
- it('merges build-file labels with task labels', async () => {
42
- await withCli({
43
- labels: { app: 'web' },
44
- tasks: {
45
- build: { cmds: ['tsc'], labels: { tier: 'backend' } },
46
- },
47
- }, { taskName: 'build' }, (cli) => {
48
- expect(cli.task('build').data.labels).toEqual({ app: ['web'], tier: ['backend'] });
49
- });
50
- });
51
- it('applies schema extend (image, cmds, labels)', async () => {
52
- await withCli({
53
- tasks: {
54
- base: { image: 'node', cmds: ['a'] },
55
- child: { extend: 'base', cmds: ['b'] },
56
- },
57
- }, { taskName: 'child' }, (cli) => {
58
- const child = cli.task('child').data;
59
- // child inherits the base image and is therefore a container task
60
- expect(child.type).toBe('container-task');
61
- expect(child.image).toBe('node');
62
- // extendArray prepends the extended task's cmds before the child's own
63
- expect(child.cmds).toHaveLength(2);
64
- });
65
- });
66
- it('throws when a dependency cannot be found', async () => {
67
- const testCase = (0, test_case_1.createTestCase)('reference-parser', {
68
- '.hammerkit.yaml': { tasks: { build: { cmds: ['tsc'], deps: ['missing'] } } },
69
- });
70
- await testCase.setup(async (cwd, environment) => {
71
- await expect((0, program_1.createCli)((0, path_1.join)(cwd, '.hammerkit.yaml'), environment, { taskName: 'build' })).rejects.toThrow('unable to find missing');
72
- });
73
- });
74
- it('throws when a needed service cannot be found', async () => {
75
- const testCase = (0, test_case_1.createTestCase)('reference-parser', {
76
- '.hammerkit.yaml': { tasks: { build: { cmds: ['tsc'], needs: ['missingsvc'] } } },
77
- });
78
- await testCase.setup(async (cwd, environment) => {
79
- await expect((0, program_1.createCli)((0, path_1.join)(cwd, '.hammerkit.yaml'), environment, { taskName: 'build' })).rejects.toThrow('unable to find missingsvc');
80
- });
81
- });
82
- });
83
- //# sourceMappingURL=reference-parser.spec.js.map
@@ -1,80 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const path_1 = require("path");
4
- const os_1 = require("os");
5
- const fs_1 = require("fs");
6
- const environment_mock_1 = require("../executer/environment-mock");
7
- const schema_parser_1 = require("./schema-parser");
8
- const parse_error_1 = require("./parse-error");
9
- describe('createParseContext', () => {
10
- let scratch;
11
- beforeEach(() => {
12
- scratch = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'hammerkit-schema-'));
13
- });
14
- afterEach(() => {
15
- (0, fs_1.rmSync)(scratch, { recursive: true, force: true });
16
- });
17
- function writeBuildFile(content) {
18
- const file = (0, path_1.join)(scratch, '.hammerkit.yaml');
19
- (0, fs_1.writeFileSync)(file, content);
20
- return file;
21
- }
22
- it('parses a valid build file', async () => {
23
- const env = (0, environment_mock_1.environmentMock)(scratch);
24
- const file = writeBuildFile('tasks:\n build:\n cmds:\n - echo hi\n');
25
- const { scope } = await (0, schema_parser_1.createParseContext)(file, env);
26
- expect(scope.schema.tasks?.build).toBeDefined();
27
- });
28
- it('rejects an unknown top-level key (strict schema)', async () => {
29
- const env = (0, environment_mock_1.environmentMock)(scratch);
30
- const file = writeBuildFile('bogus: true\ntasks:\n build:\n cmds:\n - echo hi\n');
31
- await expect((0, schema_parser_1.createParseContext)(file, env)).rejects.toBeInstanceOf(parse_error_1.ParseError);
32
- });
33
- it('rejects a task with an invalid command shape', async () => {
34
- const env = (0, environment_mock_1.environmentMock)(scratch);
35
- const file = writeBuildFile('tasks:\n build:\n cmds: 42\n');
36
- await expect((0, schema_parser_1.createParseContext)(file, env)).rejects.toBeInstanceOf(parse_error_1.ParseError);
37
- });
38
- it('resolves a reference to another build file', async () => {
39
- const env = (0, environment_mock_1.environmentMock)(scratch);
40
- const sub = (0, path_1.join)(scratch, 'sub');
41
- (0, fs_1.writeFileSync)((0, path_1.join)(scratch, '.hammerkit.yaml'), 'references:\n lib: sub\ntasks:\n build:\n cmds:\n - echo root\n');
42
- // create the referenced build file
43
- (0, fs_1.mkdirSync)(sub);
44
- (0, fs_1.writeFileSync)((0, path_1.join)(sub, '.hammerkit.yaml'), 'tasks:\n lib-build:\n cmds:\n - echo lib\n');
45
- const { scope } = await (0, schema_parser_1.createParseContext)((0, path_1.join)(scratch, '.hammerkit.yaml'), env);
46
- expect(scope.references['lib']).toBeDefined();
47
- expect(scope.references['lib'].scope.schema.tasks?.['lib-build']).toBeDefined();
48
- });
49
- it('resolves an include (sibling build file merged under a name)', async () => {
50
- const env = (0, environment_mock_1.environmentMock)(scratch);
51
- (0, fs_1.writeFileSync)((0, path_1.join)(scratch, 'common.yaml'), 'tasks:\n shared:\n cmds:\n - echo s\n');
52
- (0, fs_1.writeFileSync)((0, path_1.join)(scratch, '.hammerkit.yaml'), 'includes:\n npm: common.yaml\n');
53
- const { scope } = await (0, schema_parser_1.createParseContext)((0, path_1.join)(scratch, '.hammerkit.yaml'), env);
54
- expect(scope.references['npm']).toBeDefined();
55
- expect(scope.references['npm'].type).toBe('include');
56
- expect(scope.references['npm'].scope.schema.tasks?.['shared']).toBeDefined();
57
- });
58
- it('throws when an include and a reference share the same name', async () => {
59
- const env = (0, environment_mock_1.environmentMock)(scratch);
60
- const sub = (0, path_1.join)(scratch, 'sub');
61
- (0, fs_1.mkdirSync)(sub);
62
- (0, fs_1.writeFileSync)((0, path_1.join)(sub, '.hammerkit.yaml'), 'tasks:\n x:\n cmds:\n - echo x\n');
63
- (0, fs_1.writeFileSync)((0, path_1.join)(scratch, 'inc.yaml'), 'tasks:\n y:\n cmds:\n - echo y\n');
64
- (0, fs_1.writeFileSync)((0, path_1.join)(scratch, '.hammerkit.yaml'), 'references:\n lib: sub\nincludes:\n lib: inc.yaml\n');
65
- await expect((0, schema_parser_1.createParseContext)((0, path_1.join)(scratch, '.hammerkit.yaml'), env)).rejects.toThrow(/lib already exists/);
66
- });
67
- it('reuses an already-parsed build file when reached again (cache hit)', async () => {
68
- const env = (0, environment_mock_1.environmentMock)(scratch);
69
- const sub = (0, path_1.join)(scratch, 'sub');
70
- (0, fs_1.mkdirSync)(sub);
71
- (0, fs_1.writeFileSync)((0, path_1.join)(sub, '.hammerkit.yaml'), 'tasks:\n s:\n cmds:\n - echo s\n');
72
- (0, fs_1.writeFileSync)((0, path_1.join)(scratch, '.hammerkit.yaml'), 'references:\n a: sub\n b: sub\n');
73
- const { ctx, scope } = await (0, schema_parser_1.createParseContext)((0, path_1.join)(scratch, '.hammerkit.yaml'), env);
74
- // Both references point at sub/.hammerkit.yaml and must end up at the same
75
- // parsed ParseScope (the second appendBuildFile hits the ctx.files cache).
76
- expect(scope.references['a'].scope).toBe(scope.references['b'].scope);
77
- expect(Object.keys(ctx.files)).toHaveLength(2); // root + sub (sub cached on second visit)
78
- });
79
- });
80
- //# sourceMappingURL=schema-parser.spec.js.map
@@ -1,23 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const build_file_service_schema_1 = require("./build-file-service-schema");
4
- const build_file_task_schema_1 = require("./build-file-task-schema");
5
- describe('service/task type guards', () => {
6
- it('isBuildFileContainerSchema discriminates by `image` presence', () => {
7
- expect((0, build_file_service_schema_1.isBuildFileContainerSchema)({ image: 'node' })).toBe(true);
8
- expect((0, build_file_service_schema_1.isBuildFileContainerSchema)({ selector: { name: 'n', type: 't' } })).toBe(false);
9
- });
10
- it('isBuildFileKubernetesServiceSchema discriminates by `selector` presence', () => {
11
- expect((0, build_file_service_schema_1.isBuildFileKubernetesServiceSchema)({ selector: { name: 'n', type: 't' } })).toBe(true);
12
- expect((0, build_file_service_schema_1.isBuildFileKubernetesServiceSchema)({ image: 'node' })).toBe(false);
13
- });
14
- it('isBuildFileContainerTaskSchema returns true iff the task has an image', () => {
15
- expect((0, build_file_task_schema_1.isBuildFileContainerTaskSchema)({ image: 'node' })).toBe(true);
16
- expect((0, build_file_task_schema_1.isBuildFileContainerTaskSchema)({ cmds: ['x'] })).toBe(false);
17
- });
18
- it('isBuildFileLocalTaskSchema returns true iff the task has no image', () => {
19
- expect((0, build_file_task_schema_1.isBuildFileLocalTaskSchema)({ cmds: ['x'] })).toBe(true);
20
- expect((0, build_file_task_schema_1.isBuildFileLocalTaskSchema)({ image: 'node' })).toBe(false);
21
- });
22
- });
23
- //# sourceMappingURL=type-guards.spec.js.map
@@ -1,66 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const work_scope_parser_1 = require("./work-scope-parser");
4
- const test_case_1 = require("../testing/test-case");
5
- const program_1 = require("../program");
6
- const path_1 = require("path");
7
- async function withCli(buildFile, scope, fn) {
8
- const testCase = (0, test_case_1.createTestCase)('work-scope-parser', { '.hammerkit.yaml': buildFile });
9
- await testCase.setup(async (cwd, environment) => {
10
- const cli = await (0, program_1.createCli)((0, path_1.join)(cwd, '.hammerkit.yaml'), environment, scope);
11
- await fn(cli);
12
- });
13
- }
14
- describe('work-scope-parser', () => {
15
- describe('defaultEnvironment', () => {
16
- it('falls back to docker when no default environment is declared', () => {
17
- expect((0, work_scope_parser_1.defaultEnvironment)({ environments: {} })).toEqual({ type: 'docker' });
18
- });
19
- it('uses a declared docker default environment', () => {
20
- const context = {
21
- environments: { default: { schema: { docker: { host: 'tcp://remote:2375' } } } },
22
- };
23
- expect((0, work_scope_parser_1.defaultEnvironment)(context)).toEqual({ type: 'docker', host: 'tcp://remote:2375' });
24
- });
25
- it('uses a declared kubernetes default environment', () => {
26
- const context = {
27
- environments: { default: { schema: { kubernetes: { context: 'prod' } } } },
28
- };
29
- expect((0, work_scope_parser_1.defaultEnvironment)(context)).toEqual({
30
- type: 'kubernetes',
31
- context: 'prod',
32
- ingresses: [],
33
- namespace: 'default',
34
- });
35
- });
36
- });
37
- describe('getWorkContext (via createCli)', () => {
38
- it('includes only the requested task and its dependencies', async () => {
39
- await withCli({
40
- tasks: {
41
- install: { cmds: ['npm install'] },
42
- build: { cmds: ['tsc'], deps: ['install'] },
43
- lint: { cmds: ['eslint'] },
44
- },
45
- }, { taskName: 'build' }, (cli) => {
46
- const names = cli
47
- .tasks()
48
- .map((t) => t.item.name)
49
- .sort();
50
- expect(names).toEqual(['build', 'install']);
51
- expect(() => cli.task('lint')).toThrow('unable to find task lint');
52
- });
53
- });
54
- it('filters tasks by label', async () => {
55
- await withCli({
56
- tasks: {
57
- build: { cmds: ['tsc'], labels: { app: 'web' } },
58
- lint: { cmds: ['eslint'], labels: { app: 'tool' } },
59
- },
60
- }, { filterLabels: { app: ['web'] } }, (cli) => {
61
- expect(cli.tasks().map((t) => t.item.name)).toEqual(['build']);
62
- });
63
- });
64
- });
65
- });
66
- //# sourceMappingURL=work-scope-parser.spec.js.map
@@ -1,27 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const update_service_status_1 = require("./update-service-status");
4
- function fakeService(initialize) {
5
- return {
6
- state: { current: { type: 'pending' } },
7
- runtime: { initialize },
8
- };
9
- }
10
- describe('updateServiceStatus', () => {
11
- it('calls initialize(state) on every service runtime', async () => {
12
- const a = vi.fn().mockResolvedValue(undefined);
13
- const b = vi.fn().mockResolvedValue(undefined);
14
- const workTree = {
15
- services: { svcA: fakeService(a), svcB: fakeService(b) },
16
- tasks: {},
17
- };
18
- await (0, update_service_status_1.updateServiceStatus)(workTree);
19
- expect(a).toHaveBeenCalledTimes(1);
20
- expect(b).toHaveBeenCalledTimes(1);
21
- });
22
- it('is a no-op when the work tree has no services', async () => {
23
- const workTree = { services: {}, tasks: {} };
24
- await expect((0, update_service_status_1.updateServiceStatus)(workTree)).resolves.toBeUndefined();
25
- });
26
- });
27
- //# sourceMappingURL=update-service-status.spec.js.map
@@ -1,134 +0,0 @@
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.ensureLocalRegistry = void 0;
7
- const dockerode_1 = __importDefault(require("dockerode"));
8
- const REGISTRY_NAME = 'hammerkit-test-registry';
9
- const REGISTRY_IMAGE = 'registry:2';
10
- // Small image with busybox wget, used to talk to the registry from the daemon's
11
- // network (see why below). alpine is already pulled by the package suites.
12
- const PROBE_IMAGE = 'alpine:3.19';
13
- const MANAGED_PORT = Number(process.env.HAMMERKIT_TEST_REGISTRY_PORT) || 5000;
14
- function registryAddressFromEnv() {
15
- const fromEnv = process.env.REGISTRY;
16
- if (!fromEnv) {
17
- return null;
18
- }
19
- const match = /^([^:]+):(\d+)$/.exec(fromEnv);
20
- if (!match) {
21
- throw new Error(`REGISTRY must be of the form host:port (got "${fromEnv}")`);
22
- }
23
- return { host: match[1], hostPort: parseInt(match[2], 10) };
24
- }
25
- async function ensureImage(docker, image) {
26
- try {
27
- await docker.getImage(image).inspect();
28
- return;
29
- }
30
- catch {
31
- // not present locally — pull it below
32
- }
33
- await new Promise((resolve, reject) => {
34
- docker.pull(image, (err, stream) => {
35
- if (err || !stream) {
36
- reject(err ?? new Error('no pull stream'));
37
- return;
38
- }
39
- docker.modem.followProgress(stream, (e) => (e ? reject(e) : resolve()));
40
- });
41
- });
42
- }
43
- // Run a throwaway container on the *host* network and return its exit code.
44
- // The registry is reachable at 127.0.0.1:<port> from the daemon's network (see
45
- // ensureLocalRegistry), but not necessarily from this (host) process — on Docker
46
- // Desktop the daemon is in a VM — so registry HTTP is done from here instead.
47
- async function runOnHostNet(docker, shellCmd) {
48
- await ensureImage(docker, PROBE_IMAGE);
49
- const container = await docker.createContainer({
50
- Image: PROBE_IMAGE,
51
- Cmd: ['sh', '-c', shellCmd],
52
- HostConfig: { NetworkMode: 'host', AutoRemove: false },
53
- });
54
- try {
55
- await container.start();
56
- const res = await container.wait();
57
- return res.StatusCode ?? 1;
58
- }
59
- finally {
60
- await container.remove({ force: true }).catch(() => undefined);
61
- }
62
- }
63
- /**
64
- * Returns a docker registry suitable for `cli.package({ registry, push: true, ... })`.
65
- *
66
- * Resolution order:
67
- * 1. `REGISTRY=host:port` env var (used by CI; just probed for liveness, never managed).
68
- * 2. A managed `registry:2` container on the daemon's own network (host networking).
69
- *
70
- * Host networking matters: the daemon performs the push, and an HTTP push needs a
71
- * `127.0.0.1`/`localhost` address (the only registries docker treats as insecure
72
- * without daemon config). On Docker Desktop the daemon runs in a VM, so a
73
- * host-*published* port is not reachable from it via 127.0.0.1 (`docker push
74
- * 127.0.0.1:<published>` → connection refused). Putting the registry on the
75
- * daemon's network makes 127.0.0.1:<port> resolve to it. Because this process
76
- * (on the host) then can't see the registry, liveness and tag lookups run inside
77
- * a throwaway container on the same network — see `listsTag`.
78
- *
79
- * Callers must invoke `cleanup()`. For the env-var case it is a no-op.
80
- */
81
- async function ensureLocalRegistry(docker) {
82
- const d = docker ?? new dockerode_1.default();
83
- const fromEnv = registryAddressFromEnv();
84
- let host;
85
- let hostPort;
86
- let cleanup = async () => undefined;
87
- if (fromEnv) {
88
- host = fromEnv.host;
89
- hostPort = fromEnv.hostPort;
90
- }
91
- else {
92
- host = '127.0.0.1';
93
- hostPort = MANAGED_PORT;
94
- await ensureImage(d, REGISTRY_IMAGE);
95
- const existing = await d.listContainers({ all: true, filters: { name: [REGISTRY_NAME] } });
96
- if (existing.length === 0) {
97
- const container = await d.createContainer({
98
- Image: REGISTRY_IMAGE,
99
- name: REGISTRY_NAME,
100
- Env: [`REGISTRY_HTTP_ADDR=0.0.0.0:${MANAGED_PORT}`],
101
- HostConfig: { AutoRemove: true, NetworkMode: 'host' },
102
- });
103
- await container.start();
104
- }
105
- else if (existing[0].State !== 'running') {
106
- await d.getContainer(existing[0].Id).start();
107
- }
108
- cleanup = async () => {
109
- const c = (await d.listContainers({ all: true, filters: { name: [REGISTRY_NAME] } }))[0];
110
- if (c) {
111
- await d
112
- .getContainer(c.Id)
113
- .remove({ force: true })
114
- .catch(() => undefined);
115
- }
116
- };
117
- }
118
- const base = `http://${host}:${hostPort}`;
119
- const reachable = await runOnHostNet(d, `for i in $(seq 1 60); do wget -q -O /dev/null ${base}/v2/ && exit 0; sleep 1; done; exit 1`);
120
- if (reachable !== 0) {
121
- throw new Error(`docker registry at ${host}:${hostPort} did not become reachable in time`);
122
- }
123
- return {
124
- host,
125
- hostPort,
126
- cleanup,
127
- async listsTag(repository, tag) {
128
- const code = await runOnHostNet(d, `wget -qO- ${base}/v2/${repository}/tags/list | grep -q '"${tag}"'`);
129
- return code === 0;
130
- },
131
- };
132
- }
133
- exports.ensureLocalRegistry = ensureLocalRegistry;
134
- //# sourceMappingURL=ensure-local-registry.js.map
@@ -1,63 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ExampleTestSuite = void 0;
4
- const path_1 = require("path");
5
- const get_file_context_1 = require("../file/get-file-context");
6
- const work_item_status_1 = require("../planner/work-item-status");
7
- const program_1 = require("../program");
8
- const log_1 = require("../log");
9
- const test_streams_1 = require("./test-streams");
10
- class ExampleTestSuite {
11
- constructor(exampleName, files) {
12
- this.files = files;
13
- this.tests = [];
14
- this.exampleDirectory = (0, path_1.join)(__dirname, '../../examples/', exampleName);
15
- this.path = (0, path_1.join)(process.cwd(), 'temp', exampleName);
16
- this.file = (0, get_file_context_1.getFileContext)(this.path);
17
- }
18
- async close() {
19
- for (const test of this.tests) {
20
- test.close();
21
- await this.file.remove(test.cwd);
22
- }
23
- }
24
- async setup(scope) {
25
- await this.file.remove(this.path);
26
- await this.file.createDirectory(this.path);
27
- const statusStream = (0, test_streams_1.memoryStream)();
28
- const environment = {
29
- processEnvs: { ...process.env, ...(scope.envs ?? {}) },
30
- abortCtrl: new AbortController(),
31
- cwd: this.path,
32
- file: this.file,
33
- console: (0, log_1.consoleContext)((0, test_streams_1.emptyStream)()),
34
- status: (0, work_item_status_1.statusConsole)(statusStream.stream),
35
- stdout: (0, test_streams_1.emptyStream)(),
36
- stderr: (0, test_streams_1.emptyStream)(),
37
- stdoutColumns: 80,
38
- };
39
- for (const file of this.files) {
40
- await this.file.copy((0, path_1.join)(this.exampleDirectory, file), (0, path_1.join)(this.path, file));
41
- }
42
- const fileName = (0, path_1.join)(this.path, '.hammerkit.yaml');
43
- this.tests.push({
44
- cwd: this.path,
45
- close() {
46
- environment.abortCtrl.abort();
47
- },
48
- });
49
- const cli = await (0, program_1.createCli)(fileName, environment, scope);
50
- // clear the backend cache too: the default `local` backend persists under
51
- // ~/.hammerkit/remote-cache outside the test's temp dir, so without this a
52
- // prior run's result would leak in and make cache assertions flaky.
53
- await cli.clean({ cache: true });
54
- // reset cli clean stats
55
- environment.status = (0, work_item_status_1.statusConsole)(statusStream.stream);
56
- return {
57
- cli,
58
- environment,
59
- };
60
- }
61
- }
62
- exports.ExampleTestSuite = ExampleTestSuite;
63
- //# sourceMappingURL=example-test-suite.js.map
@@ -1,82 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.expectContainsLog = exports.expectLog = exports.expectSuccessfulResult = exports.expectSuccessfulExecution = void 0;
4
- const plan_work_tasks_1 = require("../planner/utils/plan-work-tasks");
5
- const get_scheduler_execute_result_1 = require("../executer/get-scheduler-execute-result");
6
- const testing_timeout_1 = require("./testing-timeout");
7
- async function expectSuccessfulExecution(exec, env) {
8
- const badResult = new Promise((resolve) => {
9
- exec.state.on('expected-status', (workTree) => {
10
- if (env.abortCtrl.signal.aborted) {
11
- return;
12
- }
13
- const hasErrorTask = Object.values(workTree.tasks).some((n) => n.state.current.type === 'error' || n.state.current.type === 'crash');
14
- const hasErrorService = Object.values(workTree.services).some((n) => n.state.current.type === 'error' || n.state.current.type === 'end');
15
- if (!hasErrorTask && !hasErrorService) {
16
- return;
17
- }
18
- const stateResult = (0, get_scheduler_execute_result_1.getSchedulerExecuteResult)(workTree);
19
- if (!stateResult.success) {
20
- resolve(stateResult);
21
- env.abortCtrl.abort();
22
- }
23
- });
24
- });
25
- const result = await Promise.race([badResult, (0, testing_timeout_1.testingTimeout)(exec)]);
26
- await expectSuccessfulResult(result, env);
27
- }
28
- exports.expectSuccessfulExecution = expectSuccessfulExecution;
29
- async function expectSuccessfulResult(result, env) {
30
- if (!result.success) {
31
- for (const task of (0, plan_work_tasks_1.iterateWorkTasks)(result.state)) {
32
- if (task.state.current.type !== 'completed') {
33
- expect({
34
- cacheId: task.id(),
35
- status: task.state.current.type,
36
- updates: Array.from(task.status.read()).map((s) => `${s.level}: ${s.message}`),
37
- logs: Array.from(task.status.logs()).map((l) => `${l.console}: ${l.message}`),
38
- errorMessage: task.state.current.type === 'error' ? task.state.current.errorMessage : undefined,
39
- needs: task.needs.map((need) => ({
40
- name: need.name,
41
- updates: Array.from(need.service.status.read()).map((s) => `${s.level}: ${s.message}`),
42
- logs: Array.from(need.service.status.logs()).map((l) => `${l.console}: ${l.message}`),
43
- })),
44
- }).toEqual({
45
- cacheId: task.id(),
46
- status: 'completed',
47
- });
48
- }
49
- }
50
- }
51
- const status = Array.from(env.status.read());
52
- const errorStatus = status
53
- .filter((s) => s.level === 'warn' || s.level === 'error')
54
- .map((s) => `${s.context.type}:${s.context.name} - ${s.level} ${s.message}`);
55
- expect(errorStatus).toEqual([]);
56
- }
57
- exports.expectSuccessfulResult = expectSuccessfulResult;
58
- function getTaskState(state, name) {
59
- const tasks = Object.values(state.tasks).find((n) => n.name === name);
60
- if (!tasks) {
61
- throw new Error(`could not find task ${name}`);
62
- }
63
- return tasks;
64
- }
65
- async function expectLog(result, env, name, message) {
66
- const state = getTaskState(result.state, name);
67
- const logs = Array.from(state.status.logs()).map((n) => n.message);
68
- expect(logs).toContain(message);
69
- }
70
- exports.expectLog = expectLog;
71
- async function expectContainsLog(result, env, name, message) {
72
- const state = getTaskState(result.state, name);
73
- const logs = state.status.logs();
74
- for (const log of logs) {
75
- if (log.message.indexOf(message) >= 0) {
76
- return;
77
- }
78
- }
79
- expect(Array.from(logs)).toContain(message);
80
- }
81
- exports.expectContainsLog = expectContainsLog;
82
- //# sourceMappingURL=expect.js.map
@@ -1,9 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getTestSuite = void 0;
4
- const example_test_suite_1 = require("./example-test-suite");
5
- function getTestSuite(exampleName, files) {
6
- return new example_test_suite_1.ExampleTestSuite(exampleName, files);
7
- }
8
- exports.getTestSuite = getTestSuite;
9
- //# sourceMappingURL=get-test-suite.js.map
@@ -1,21 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.httpGet = void 0;
4
- const http_1 = require("http");
5
- function httpGet(host, port, path, timeoutMs = 10000) {
6
- return new Promise((resolve, reject) => {
7
- const req = (0, http_1.request)({ host, port, path, method: 'GET', timeout: timeoutMs }, (res) => {
8
- const chunks = [];
9
- res.on('data', (chunk) => chunks.push(chunk));
10
- res.on('end', () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString('utf8') }));
11
- res.on('error', reject);
12
- });
13
- req.on('error', reject);
14
- req.on('timeout', () => {
15
- req.destroy(new Error(`timeout after ${timeoutMs}ms`));
16
- });
17
- req.end();
18
- });
19
- }
20
- exports.httpGet = httpGet;
21
- //# sourceMappingURL=http-get.js.map
@@ -1,49 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const expect_1 = require("../expect");
4
- const get_test_suite_1 = require("../get-test-suite");
5
- const path_1 = require("path");
6
- const enqueue_next_1 = require("../../executer/scheduler/enqueue-next");
7
- const read_build_file_1 = require("../../parser/read-build-file");
8
- const requires_linux_containers_1 = require("../requires-linux-containers");
9
- describe('cache', () => {
10
- const suite = (0, get_test_suite_1.getTestSuite)('cache', ['.hammerkit.yaml', 'package.json', 'package-lock.json']);
11
- afterAll(() => suite.close());
12
- async function testCache(action, expectInvalidate) {
13
- const setupBefore = await suite.setup({ taskName: 'example' });
14
- const result = await setupBefore.cli.exec().start();
15
- await (0, expect_1.expectSuccessfulResult)(result, setupBefore.environment);
16
- await action(setupBefore.environment);
17
- const setupAfter = await suite.setup({ taskName: 'example' });
18
- const task = setupAfter.cli.task('example');
19
- const checksumUpToDate = await (0, enqueue_next_1.checkCacheState)(task, 'checksum', setupAfter.environment);
20
- const modifyDateUpToDate = await (0, enqueue_next_1.checkCacheState)(task, 'modify-date', setupAfter.environment);
21
- if (expectInvalidate) {
22
- expect(checksumUpToDate.cached).toBeFalsy();
23
- expect(modifyDateUpToDate.cached).toBeFalsy();
24
- }
25
- else {
26
- expect(checksumUpToDate.cached).toBeTruthy();
27
- expect(modifyDateUpToDate.cached).toBeTruthy();
28
- }
29
- }
30
- it('should run invalid cache on src file change', (0, requires_linux_containers_1.requiresLinuxContainers)(async () => {
31
- await testCache(async (environment) => {
32
- await environment.file.appendFile((0, path_1.join)(environment.cwd, 'package.json'), '\n');
33
- }, true);
34
- }));
35
- it('should mount generations of dependant tasks', (0, requires_linux_containers_1.requiresLinuxContainers)(async () => {
36
- const { cli, environment } = await suite.setup({ taskName: 'dependant' });
37
- const result = await cli.runExec();
38
- await (0, expect_1.expectSuccessfulResult)(result, environment);
39
- await (0, expect_1.expectLog)(result, environment, `dependant`, 'node_modules');
40
- }));
41
- it('should invalid cache on image change', (0, requires_linux_containers_1.requiresLinuxContainers)(async () => {
42
- await testCache(async (environment) => {
43
- const buildFile = await (0, read_build_file_1.read)('.hammerkit.yaml', environment);
44
- buildFile.tasks['example'].image = '15.0.0';
45
- await (0, read_build_file_1.write)('.hammerkit.yaml', buildFile, environment);
46
- }, true);
47
- }));
48
- });
49
- //# sourceMappingURL=cache.spec.js.map