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,145 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const test_case_1 = require("../testing/test-case");
4
- describe('docker/package', () => {
5
- it('should allow sub paths', async () => {
6
- const testCase = (0, test_case_1.createTestCase)('package', {
7
- 'package.json': '{ "dependencies": { "pg": "^8.7.1" } }',
8
- 'sub/package.json': '{ "dependencies": { "sub": "file://.." } }',
9
- '.hammerkit.yaml': {
10
- tasks: {
11
- install: {
12
- image: 'node:20-alpine',
13
- cmds: ['npm install'],
14
- src: ['package.json'],
15
- generates: ['node_modules'],
16
- },
17
- },
18
- },
19
- 'sub/.hammerkit.yaml': {
20
- tasks: {
21
- install: {
22
- deps: ['sub:install'],
23
- image: 'node:20-alpine',
24
- cmds: ['npm install'],
25
- src: ['package.json'],
26
- generates: ['node_modules'],
27
- },
28
- },
29
- references: {
30
- sub: '..',
31
- },
32
- services: {
33
- api: {
34
- deps: ['install'],
35
- image: 'node:20-alpine',
36
- labels: { app: 'api' },
37
- cmd: 'node index.js',
38
- src: ['index.js'],
39
- ports: ['3000'],
40
- },
41
- },
42
- },
43
- 'sub/index.js': `console.log('hi')`,
44
- });
45
- await testCase.cli({ filterLabels: { app: ['api'] } }, async (cli) => {
46
- await cli.package({
47
- registry: 'localhost:5000',
48
- push: false,
49
- overrideUser: false,
50
- username: null,
51
- password: null,
52
- });
53
- });
54
- });
55
- it('should package and push', async () => {
56
- const testCase = (0, test_case_1.createTestCase)('package', {
57
- '.hammerkit.yaml': {
58
- tasks: {
59
- install: {
60
- image: 'node:20-alpine',
61
- cmds: ['npm install'],
62
- src: ['package.json'],
63
- generates: ['node_modules'],
64
- },
65
- },
66
- services: {
67
- postgres: {
68
- image: 'postgres:16-alpine',
69
- envs: {
70
- POSTGRES_USER: 'postgres',
71
- POSTGRES_DB: 'demo',
72
- POSTGRES_PASSWORD: '$POSTGRES_PASSWORD',
73
- },
74
- ports: [':5432'],
75
- volumes: ['postgres-db:/var/lib/postgresql/data'],
76
- },
77
- registry: {
78
- image: 'registry:2',
79
- labels: { app: 'registry' },
80
- ports: ['5500:5000'],
81
- },
82
- api: {
83
- deps: ['install'],
84
- needs: ['postgres'],
85
- image: 'node:20-alpine',
86
- labels: { app: 'api' },
87
- envs: {
88
- DATABASE_URL: '$DATABASE_URL',
89
- },
90
- cmd: 'node index.js',
91
- src: ['index.js'],
92
- ports: ['3000'],
93
- },
94
- },
95
- },
96
- 'package.json': '{ "dependencies": { "pg": "^8.7.1" } }',
97
- '.env': `POSTGRES_PASSWORD=123456
98
- DATABASE_URL=postgres://postgres:123456@postgres/demo`,
99
- 'index.js': `const { Client } = require('pg')
100
- const { createServer } = require('http')
101
-
102
- const client = new Client(process.env.DATABASE_URL)
103
-
104
- const server = createServer(async (req, res) => {
105
- const result = await client.query('SELECT $1::text as message', ['Hello world!'])
106
- res.writeHead(200)
107
- res.end(result.rows[0].message)
108
- })
109
-
110
- async function main() {
111
- await client.connect()
112
- server.listen(3000)
113
- }
114
-
115
- process.on('SIGINT', async function () {
116
- server.close()
117
- await client.end()
118
- })
119
-
120
- main().catch((err) => {
121
- console.error(err)
122
- process.exit(1)
123
- })
124
- `,
125
- });
126
- await testCase.cli({ filterLabels: { app: ['registry'] } }, async (cli) => {
127
- await cli.runUp({ daemon: true });
128
- try {
129
- await testCase.cli({ filterLabels: { app: ['api'] } }, async (cli) => {
130
- await cli.package({
131
- registry: 'localhost:5500',
132
- push: true,
133
- overrideUser: true,
134
- username: null,
135
- password: null,
136
- });
137
- });
138
- }
139
- finally {
140
- await cli.runDown();
141
- }
142
- });
143
- });
144
- });
145
- //# sourceMappingURL=package.spec.js.map
@@ -1,32 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const remove_container_1 = require("./remove-container");
4
- function makeContainer(remove) {
5
- return { remove };
6
- }
7
- describe('removeContainer', () => {
8
- it('removes the container on first try', async () => {
9
- const remove = vi.fn().mockResolvedValue(undefined);
10
- await (0, remove_container_1.removeContainer)(makeContainer(remove));
11
- expect(remove).toHaveBeenCalledTimes(1);
12
- expect(remove).toHaveBeenCalledWith({ force: true });
13
- });
14
- it('swallows 404 (already gone)', async () => {
15
- const err = Object.assign(new Error('not found'), { statusCode: 404 });
16
- const remove = vi.fn().mockRejectedValueOnce(err);
17
- await expect((0, remove_container_1.removeContainer)(makeContainer(remove))).resolves.toBeUndefined();
18
- expect(remove).toHaveBeenCalledTimes(1);
19
- });
20
- it('retries on 409 (conflict)', async () => {
21
- const conflict = Object.assign(new Error('conflict'), { statusCode: 409 });
22
- const remove = vi.fn().mockRejectedValueOnce(conflict).mockResolvedValueOnce(undefined);
23
- await (0, remove_container_1.removeContainer)(makeContainer(remove));
24
- expect(remove).toHaveBeenCalledTimes(2);
25
- }, 5000);
26
- it('rethrows unexpected errors', async () => {
27
- const err = Object.assign(new Error('boom'), { statusCode: 500 });
28
- const remove = vi.fn().mockRejectedValueOnce(err);
29
- await expect((0, remove_container_1.removeContainer)(makeContainer(remove))).rejects.toThrow('boom');
30
- });
31
- });
32
- //# sourceMappingURL=remove-container.spec.js.map
@@ -1,90 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const using_container_1 = require("./using-container");
4
- function makeContainer(id) {
5
- return {
6
- id,
7
- start: vi.fn().mockResolvedValue(undefined),
8
- pause: vi.fn().mockResolvedValue(undefined),
9
- remove: vi.fn().mockResolvedValue(undefined),
10
- };
11
- }
12
- function makeDocker(newContainer, listed = []) {
13
- const containersById = { [newContainer.id]: newContainer };
14
- for (const entry of listed) {
15
- containersById[entry.Id] = makeContainer(entry.Id);
16
- }
17
- return {
18
- docker: {
19
- createContainer: vi.fn().mockResolvedValue(newContainer),
20
- listContainers: vi.fn().mockResolvedValue(listed),
21
- getContainer: vi.fn((id) => containersById[id] ?? makeContainer(id)),
22
- },
23
- containersById,
24
- };
25
- }
26
- function makeItem(id) {
27
- return {
28
- id: () => id,
29
- name: id,
30
- status: { write: vi.fn() },
31
- data: { type: 'container-task', image: 'alpine' },
32
- needs: [],
33
- deps: [],
34
- requiredBy: [],
35
- };
36
- }
37
- describe('usingContainer', () => {
38
- it('pauses the container on success when stateKey is set', async () => {
39
- const created = makeContainer('new-cid');
40
- const { docker } = makeDocker(created);
41
- const item = makeItem('task-1');
42
- await (0, using_container_1.usingContainer)(docker, item, {}, 'state-A', async () => true);
43
- expect(created.pause).toHaveBeenCalledTimes(1);
44
- expect(created.remove).not.toHaveBeenCalled();
45
- });
46
- it('removes containers with a different stateKey and keeps the current one', async () => {
47
- const created = makeContainer('new-cid');
48
- const stale = { Id: 'old-cid', Labels: { 'hammerkit-state': 'state-B' } };
49
- const same = { Id: 'new-cid', Labels: { 'hammerkit-state': 'state-A' } };
50
- const { docker, containersById } = makeDocker(created, [stale, same]);
51
- const item = makeItem('task-1');
52
- await (0, using_container_1.usingContainer)(docker, item, {}, 'state-A', async () => true);
53
- expect(containersById['old-cid'].remove).toHaveBeenCalledTimes(1);
54
- expect(created.remove).not.toHaveBeenCalled();
55
- });
56
- it('removes the container when the callback returns false (cache miss on failure)', async () => {
57
- const created = makeContainer('new-cid');
58
- const { docker } = makeDocker(created);
59
- const item = makeItem('task-1');
60
- await (0, using_container_1.usingContainer)(docker, item, {}, 'state-A', async () => false);
61
- expect(created.pause).not.toHaveBeenCalled();
62
- expect(created.remove).toHaveBeenCalledTimes(1);
63
- });
64
- it('removes the container when the callback throws', async () => {
65
- const created = makeContainer('new-cid');
66
- const { docker } = makeDocker(created);
67
- const item = makeItem('task-1');
68
- await expect((0, using_container_1.usingContainer)(docker, item, {}, 'state-A', async () => {
69
- throw new Error('boom');
70
- })).rejects.toThrow('boom');
71
- expect(created.pause).not.toHaveBeenCalled();
72
- expect(created.remove).toHaveBeenCalledTimes(1);
73
- });
74
- it('always removes the container when no stateKey is given', async () => {
75
- const created = makeContainer('new-cid');
76
- const { docker } = makeDocker(created);
77
- const item = makeItem('task-1');
78
- await (0, using_container_1.usingContainer)(docker, item, {}, null, async () => true);
79
- expect(created.pause).not.toHaveBeenCalled();
80
- expect(created.remove).toHaveBeenCalledTimes(1);
81
- });
82
- it('propagates the callback return value', async () => {
83
- const created = makeContainer('new-cid');
84
- const { docker } = makeDocker(created);
85
- const item = makeItem('task-1');
86
- const result = await (0, using_container_1.usingContainer)(docker, item, {}, null, async () => 'payload');
87
- expect(result).toBe('payload');
88
- });
89
- });
90
- //# sourceMappingURL=using-container.spec.js.map
@@ -1,24 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const merge_environment_variables_1 = require("./merge-environment-variables");
4
- describe('mergeEnvironmentVariables', () => {
5
- it('copies the base map and stringifies values', () => {
6
- expect((0, merge_environment_variables_1.mergeEnvironmentVariables)({ A: 'one', B: 2 }, null)).toEqual({ A: 'one', B: '2' });
7
- });
8
- it('returns an empty map when both inputs are nullish', () => {
9
- expect((0, merge_environment_variables_1.mergeEnvironmentVariables)(null, undefined)).toEqual({});
10
- });
11
- it('adds extension keys that the base does not define', () => {
12
- expect((0, merge_environment_variables_1.mergeEnvironmentVariables)({ A: '1' }, { B: '2' })).toEqual({ A: '1', B: '2' });
13
- });
14
- it('keeps the base value when both maps define the same key (base wins)', () => {
15
- expect((0, merge_environment_variables_1.mergeEnvironmentVariables)({ A: 'base' }, { A: 'override' })).toEqual({ A: 'base' });
16
- });
17
- it('drops falsy extension values', () => {
18
- expect((0, merge_environment_variables_1.mergeEnvironmentVariables)({}, { A: '', B: 0 })).toEqual({});
19
- });
20
- it('handles an undefined base with an extension', () => {
21
- expect((0, merge_environment_variables_1.mergeEnvironmentVariables)(undefined, { A: '1' })).toEqual({ A: '1' });
22
- });
23
- });
24
- //# sourceMappingURL=merge-environment-variables.spec.js.map
@@ -1,47 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const replace_env_variables_1 = require("./replace-env-variables");
4
- function env(processEnvs) {
5
- return { processEnvs };
6
- }
7
- function ctx(envFiles) {
8
- return { envFiles };
9
- }
10
- describe('replace-env-variables', () => {
11
- describe('buildEnvironmentVariables', () => {
12
- it('keeps literal values as variables', () => {
13
- const result = (0, replace_env_variables_1.buildEnvironmentVariables)({ NODE_ENV: 'production' }, env({}), ctx({}));
14
- expect(result.variables).toEqual({ NODE_ENV: 'production' });
15
- expect(result.replacements).toEqual([]);
16
- });
17
- it('resolves $NAME references from processEnvs', () => {
18
- const result = (0, replace_env_variables_1.buildEnvironmentVariables)({ TOKEN: '$SECRET' }, env({ SECRET: 'abc' }), ctx({}));
19
- expect(result.variables).toEqual({});
20
- expect(result.replacements).toEqual([{ key: 'TOKEN', name: 'SECRET', available: true, value: 'abc' }]);
21
- });
22
- it('falls back to env files when not in processEnvs', () => {
23
- const result = (0, replace_env_variables_1.buildEnvironmentVariables)({ TOKEN: '$SECRET' }, env({}), ctx({ '/proj': { SECRET: 'from-file' } }));
24
- expect(result.replacements).toEqual([{ key: 'TOKEN', name: 'SECRET', available: true, value: 'from-file' }]);
25
- });
26
- it('marks unresolved references as unavailable', () => {
27
- const result = (0, replace_env_variables_1.buildEnvironmentVariables)({ TOKEN: '$MISSING' }, env({}), ctx({}));
28
- expect(result.replacements).toEqual([{ key: 'TOKEN', name: 'MISSING', available: false, value: null }]);
29
- });
30
- });
31
- describe('getEnvironmentVariables', () => {
32
- it('merges variables with resolved replacements', () => {
33
- const result = (0, replace_env_variables_1.getEnvironmentVariables)({
34
- variables: { NODE_ENV: 'production' },
35
- replacements: [{ key: 'TOKEN', name: 'SECRET', available: true, value: 'abc' }],
36
- });
37
- expect(result).toEqual({ NODE_ENV: 'production', TOKEN: 'abc' });
38
- });
39
- it('throws when a replacement is unavailable', () => {
40
- expect(() => (0, replace_env_variables_1.getEnvironmentVariables)({
41
- variables: {},
42
- replacements: [{ key: 'TOKEN', name: 'SECRET', available: false, value: null }],
43
- })).toThrow('missing environment variable SECRET');
44
- });
45
- });
46
- });
47
- //# sourceMappingURL=replace-env-variables.spec.js.map
@@ -1,18 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const abort_1 = require("./abort");
4
- describe('checkForAbort', () => {
5
- it('throws AbortError when the signal is already aborted', () => {
6
- const ctrl = new AbortController();
7
- ctrl.abort();
8
- expect(() => (0, abort_1.checkForAbort)(ctrl.signal)).toThrow(abort_1.AbortError);
9
- });
10
- it('does nothing when the signal is live', () => {
11
- const ctrl = new AbortController();
12
- expect(() => (0, abort_1.checkForAbort)(ctrl.signal)).not.toThrow();
13
- });
14
- it('AbortError extends Error', () => {
15
- expect(new abort_1.AbortError()).toBeInstanceOf(Error);
16
- });
17
- });
18
- //# sourceMappingURL=abort.spec.js.map
@@ -1,22 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const environment_mock_1 = require("./environment-mock");
4
- describe('environmentMock', () => {
5
- it('returns an Environment with the given cwd and silent streams', () => {
6
- const env = (0, environment_mock_1.environmentMock)('/tmp/x');
7
- expect(env.cwd).toBe('/tmp/x');
8
- expect(env.file).toBeDefined();
9
- expect(env.console).toBeDefined();
10
- expect(env.status).toBeDefined();
11
- expect(env.stdout).toBeDefined();
12
- expect(env.stderr).toBeDefined();
13
- expect(env.abortCtrl).toBeInstanceOf(AbortController);
14
- expect(env.processEnvs).toEqual({});
15
- expect(env.stdoutColumns).toBe(80);
16
- // writing to silent streams must not throw
17
- env.stdout.write('hello\n');
18
- env.stderr.write('world\n');
19
- env.console.info('info');
20
- });
21
- });
22
- //# sourceMappingURL=environment-mock.spec.js.map
@@ -1,51 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const get_test_suite_1 = require("../testing/get-test-suite");
4
- const expect_1 = require("../testing/expect");
5
- describe('execute', () => {
6
- const suite = (0, get_test_suite_1.getTestSuite)('hello-world-node', ['.hammerkit.yaml', 'package.json', 'index.js']);
7
- afterAll(() => suite.close());
8
- // These watch-restart cases are unreliable on Windows hosted runners: the
9
- // re-run after a source change either hangs (completed/cached task) or times
10
- // out intermittently (crashed task), because file-watching + repeated node
11
- // spawns don't settle within the limit there. The watch feature is covered on
12
- // Linux and macOS; skip both on win32 until the Windows watch path is fixed.
13
- const itExceptWindows = process.platform === 'win32' ? it.skip : it;
14
- itExceptWindows('should restart watching task if once completed', async () => {
15
- const { cli, environment } = await suite.setup({ taskName: 'api' });
16
- const exec = await cli.exec({ watch: true });
17
- let count = 0;
18
- exec.state.on('test-status', (state) => {
19
- if (state.tasks['api'].state.current.type === 'completed') {
20
- count++;
21
- if (count === 1) {
22
- environment.file.appendFile(`${environment.cwd}/index.js`, '\n');
23
- }
24
- else if (count === 2) {
25
- environment.abortCtrl.abort();
26
- }
27
- }
28
- });
29
- const result = await exec.start();
30
- await (0, expect_1.expectSuccessfulResult)(result, environment);
31
- });
32
- itExceptWindows('should restart watching task if once failed', async () => {
33
- const { cli, environment } = await suite.setup({ taskName: 'api_crashing' });
34
- const exec = await cli.exec({ watch: true });
35
- let count = 0;
36
- exec.state.on('test-status', (state) => {
37
- if (state.tasks['api_crashing'].state.current.type === 'crash') {
38
- count++;
39
- if (count === 1) {
40
- environment.file.appendFile(`${environment.cwd}/index.js`, '\n');
41
- }
42
- else if (count === 2) {
43
- environment.abortCtrl.abort();
44
- }
45
- }
46
- });
47
- const result = await exec.start();
48
- expect(result.success).toBeFalsy();
49
- });
50
- });
51
- //# sourceMappingURL=execute.spec.js.map
@@ -1,59 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const get_service_containers_1 = require("./get-service-containers");
4
- const docker_task_1 = require("./docker-task");
5
- const state_1 = require("./state");
6
- function makeNeed(name, state) {
7
- const service = {
8
- id: () => name,
9
- name,
10
- state: new state_1.State(state),
11
- };
12
- return { name, service };
13
- }
14
- describe('get-service-containers', () => {
15
- it('skips needs that are not running', () => {
16
- const needs = [
17
- makeNeed('db', { type: 'pending', stateKey: null }),
18
- makeNeed('api', { type: 'starting', stateKey: null }),
19
- ];
20
- expect((0, get_service_containers_1.getServiceContainers)(needs)).toEqual({});
21
- });
22
- it('collects dns for running services', () => {
23
- const dns = { containerId: 'abc' };
24
- const needs = [makeNeed('db', { type: 'running', stateKey: 'k', remote: null, dns })];
25
- expect((0, get_service_containers_1.getServiceContainers)(needs)).toEqual({ db: dns });
26
- });
27
- it('mixes host and container dns', () => {
28
- const containerDns = { containerId: 'cid' };
29
- const hostDns = { host: '10.0.0.1' };
30
- const needs = [
31
- makeNeed('db', { type: 'running', stateKey: 'k1', remote: null, dns: containerDns }),
32
- makeNeed('cache', { type: 'running', stateKey: 'k2', remote: null, dns: hostDns }),
33
- ];
34
- expect((0, get_service_containers_1.getServiceContainers)(needs)).toEqual({ db: containerDns, cache: hostDns });
35
- });
36
- });
37
- describe('getNeedsNetwork', () => {
38
- it('returns empty links and hosts when there are no needs', () => {
39
- const result = (0, docker_task_1.getNeedsNetwork)({}, []);
40
- expect(result).toEqual({ links: [], hosts: [] });
41
- });
42
- it('maps container dns to docker Links', () => {
43
- const needs = [makeNeed('db', { type: 'running', stateKey: 'k', remote: null, dns: { containerId: 'cid' } })];
44
- const result = (0, docker_task_1.getNeedsNetwork)({ db: { containerId: 'cid' } }, needs);
45
- expect(result.links).toEqual(['cid:db']);
46
- expect(result.hosts).toEqual([]);
47
- });
48
- it('maps host dns to docker ExtraHosts', () => {
49
- const needs = [makeNeed('api', { type: 'running', stateKey: 'k', remote: null, dns: { host: '127.0.0.1' } })];
50
- const result = (0, docker_task_1.getNeedsNetwork)({ api: { host: '127.0.0.1' } }, needs);
51
- expect(result.hosts).toEqual(['api:127.0.0.1']);
52
- expect(result.links).toEqual([]);
53
- });
54
- it('throws when a container service has no containerId', () => {
55
- const needs = [makeNeed('db', { type: 'pending', stateKey: null })];
56
- expect(() => (0, docker_task_1.getNeedsNetwork)({ db: { containerId: '' } }, needs)).toThrow(/service db is not running/);
57
- });
58
- });
59
- //# sourceMappingURL=get-service-containers.spec.js.map
@@ -1,65 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const get_service_env_hints_1 = require("./get-service-env-hints");
4
- const state_1 = require("./state");
5
- function makeNeed(name, state, ports = []) {
6
- const service = {
7
- id: () => name,
8
- name,
9
- data: { ports },
10
- state: new state_1.State(state),
11
- };
12
- return { name, service };
13
- }
14
- describe('getServiceEnvHints', () => {
15
- it('returns nothing for non-running needs', () => {
16
- const needs = [makeNeed('db', { type: 'pending', stateKey: null })];
17
- expect((0, get_service_env_hints_1.getServiceEnvHints)(needs)).toEqual({});
18
- });
19
- it('emits a localhost host hint for docker container services', () => {
20
- const needs = [
21
- makeNeed('db', { type: 'running', stateKey: 'k', remote: null, dns: { containerId: 'abc' } }, [
22
- { hostPort: 5432, containerPort: 5432 },
23
- ]),
24
- ];
25
- expect((0, get_service_env_hints_1.getServiceEnvHints)(needs)).toEqual({
26
- HAMMERKIT_DB_HOST: '127.0.0.1',
27
- HAMMERKIT_DB_PORT: '5432',
28
- HAMMERKIT_DB_PORT_5432: '5432',
29
- });
30
- });
31
- it('uses the dns host for host-style services', () => {
32
- const needs = [
33
- makeNeed('api', { type: 'running', stateKey: 'k', remote: null, dns: { host: '10.0.0.7' } }, [
34
- { hostPort: null, containerPort: 8080 },
35
- ]),
36
- ];
37
- expect((0, get_service_env_hints_1.getServiceEnvHints)(needs)).toEqual({
38
- HAMMERKIT_API_HOST: '10.0.0.7',
39
- HAMMERKIT_API_PORT: '8080',
40
- HAMMERKIT_API_PORT_8080: '8080',
41
- });
42
- });
43
- it('exposes every published port under its container-port suffix', () => {
44
- const needs = [
45
- makeNeed('web', { type: 'running', stateKey: 'k', remote: null, dns: { containerId: 'x' } }, [
46
- { hostPort: 8080, containerPort: 80 },
47
- { hostPort: 8443, containerPort: 443 },
48
- ]),
49
- ];
50
- const env = (0, get_service_env_hints_1.getServiceEnvHints)(needs);
51
- expect(env).toEqual({
52
- HAMMERKIT_WEB_HOST: '127.0.0.1',
53
- HAMMERKIT_WEB_PORT: '8080',
54
- HAMMERKIT_WEB_PORT_80: '8080',
55
- HAMMERKIT_WEB_PORT_443: '8443',
56
- });
57
- });
58
- it('sanitizes weird names into env-safe keys', () => {
59
- const needs = [
60
- makeNeed('my.service:1', { type: 'running', stateKey: 'k', remote: null, dns: { containerId: 'x' } }, []),
61
- ];
62
- expect((0, get_service_env_hints_1.getServiceEnvHints)(needs)).toEqual({ HAMMERKIT_MY_SERVICE_1_HOST: '127.0.0.1' });
63
- });
64
- });
65
- //# sourceMappingURL=get-service-env-hints.spec.js.map
@@ -1,61 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const label_values_1 = require("./label-values");
4
- describe('label-values', () => {
5
- describe('hasLabels', () => {
6
- it('is false for an empty object', () => {
7
- expect((0, label_values_1.hasLabels)({})).toBe(false);
8
- });
9
- it('is true when at least one key is present', () => {
10
- expect((0, label_values_1.hasLabels)({ app: ['web'] })).toBe(true);
11
- });
12
- });
13
- describe('matchesAnyLabel', () => {
14
- it('matches when a filter value is present in the labels', () => {
15
- expect((0, label_values_1.matchesAnyLabel)({ app: ['web'] }, { app: ['web', 'api'] })).toBe(true);
16
- });
17
- it('does not match when the value is absent', () => {
18
- expect((0, label_values_1.matchesAnyLabel)({ app: ['web'] }, { app: ['api'] })).toBe(false);
19
- });
20
- it('skips label keys that are missing or empty on the value', () => {
21
- expect((0, label_values_1.matchesAnyLabel)({ app: ['web'] }, {})).toBe(false);
22
- expect((0, label_values_1.matchesAnyLabel)({ app: ['web'] }, { app: [] })).toBe(false);
23
- });
24
- it('returns false for an empty filter', () => {
25
- expect((0, label_values_1.matchesAnyLabel)({}, { app: ['web'] })).toBe(false);
26
- });
27
- });
28
- describe('appliesToLabels', () => {
29
- it('applies when there is no filter and no exclude', () => {
30
- expect((0, label_values_1.appliesToLabels)({ app: ['web'] }, {})).toBe(true);
31
- });
32
- it('applies only when the filter matches', () => {
33
- expect((0, label_values_1.appliesToLabels)({ app: ['web'] }, { filterLabels: { app: ['web'] } })).toBe(true);
34
- expect((0, label_values_1.appliesToLabels)({ app: ['web'] }, { filterLabels: { app: ['api'] } })).toBe(false);
35
- });
36
- it('does not apply when an exclude matches', () => {
37
- expect((0, label_values_1.appliesToLabels)({ app: ['web'] }, { excludeLabels: { app: ['web'] } })).toBe(false);
38
- });
39
- it('exclude wins over a matching filter', () => {
40
- expect((0, label_values_1.appliesToLabels)({ app: ['web'], tier: ['frontend'] }, { filterLabels: { app: ['web'] }, excludeLabels: { tier: ['frontend'] } })).toBe(false);
41
- });
42
- });
43
- describe('mergeLabels', () => {
44
- it('coerces scalar values into string arrays', () => {
45
- expect((0, label_values_1.mergeLabels)({ app: 'web', port: 8080 })).toEqual({ app: ['web'], port: ['8080'] });
46
- });
47
- it('keeps array values as-is', () => {
48
- expect((0, label_values_1.mergeLabels)({ app: ['web', 'api'] })).toEqual({ app: ['web', 'api'] });
49
- });
50
- it('merges multiple sources and dedupes values', () => {
51
- expect((0, label_values_1.mergeLabels)({ app: 'web' }, { app: ['web', 'api'] }, { tier: 'frontend' })).toEqual({
52
- app: ['web', 'api'],
53
- tier: ['frontend'],
54
- });
55
- });
56
- it('ignores null and undefined sources', () => {
57
- expect((0, label_values_1.mergeLabels)(null, undefined, { app: 'web' })).toEqual({ app: ['web'] });
58
- });
59
- });
60
- });
61
- //# sourceMappingURL=label-values.spec.js.map
@@ -1,20 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const lazy_resolver_1 = require("./lazy-resolver");
4
- describe('lazyResolver', () => {
5
- it('calls the factory once and memoizes the result', () => {
6
- const factory = vi.fn(() => 'value');
7
- const resolve = (0, lazy_resolver_1.lazyResolver)(factory);
8
- expect(resolve()).toBe('value');
9
- expect(resolve()).toBe('value');
10
- expect(factory).toHaveBeenCalledTimes(1);
11
- });
12
- it('memoizes a falsy result so the factory still runs only once', () => {
13
- const factory = vi.fn(() => 0);
14
- const resolve = (0, lazy_resolver_1.lazyResolver)(factory);
15
- expect(resolve()).toBe(0);
16
- expect(resolve()).toBe(0);
17
- expect(factory).toHaveBeenCalledTimes(1);
18
- });
19
- });
20
- //# sourceMappingURL=lazy-resolver.spec.js.map