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,81 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const resolve_pod_name_1 = require("./resolve-pod-name");
4
- function podBody(items) {
5
- return {
6
- body: {
7
- items: items.map((p) => ({
8
- metadata: { name: p.name },
9
- status: {
10
- conditions: [{ type: 'Ready', status: p.ready ? 'True' : 'False' }],
11
- },
12
- })),
13
- },
14
- };
15
- }
16
- function makeCoreApi(opts = {}) {
17
- return {
18
- readNamespacedService: vi.fn().mockResolvedValue({ body: { spec: { selector: opts.service?.selector } } }),
19
- listNamespacedPod: vi.fn().mockResolvedValue(podBody(opts.pods ?? [])),
20
- };
21
- }
22
- function makeAppsApi(opts = {}) {
23
- return {
24
- readNamespacedDeployment: vi
25
- .fn()
26
- .mockResolvedValue({ body: { spec: { selector: { matchLabels: opts.deployment?.matchLabels } } } }),
27
- };
28
- }
29
- describe('resolvePodName', () => {
30
- it('returns the name directly for selector type "pod"', async () => {
31
- const core = makeCoreApi();
32
- const apps = makeAppsApi();
33
- await expect((0, resolve_pod_name_1.resolvePodName)(core, apps, 'ns', { type: 'pod', name: 'my-pod' })).resolves.toBe('my-pod');
34
- expect(core.listNamespacedPod).not.toHaveBeenCalled();
35
- });
36
- it('resolves a service to a ready pod', async () => {
37
- const core = makeCoreApi({
38
- service: { selector: { app: 'api' } },
39
- pods: [
40
- { name: 'api-old', ready: false },
41
- { name: 'api-new', ready: true },
42
- ],
43
- });
44
- const apps = makeAppsApi();
45
- await expect((0, resolve_pod_name_1.resolvePodName)(core, apps, 'ns', { type: 'service', name: 'api' })).resolves.toBe('api-new');
46
- expect(core.listNamespacedPod).toHaveBeenCalledWith('ns', undefined, undefined, undefined, undefined, 'app=api');
47
- });
48
- it('falls back to the first pod if none are ready', async () => {
49
- const core = makeCoreApi({
50
- service: { selector: { app: 'api' } },
51
- pods: [
52
- { name: 'api-1', ready: false },
53
- { name: 'api-2', ready: false },
54
- ],
55
- });
56
- const apps = makeAppsApi();
57
- await expect((0, resolve_pod_name_1.resolvePodName)(core, apps, 'ns', { type: 'service', name: 'api' })).resolves.toBe('api-1');
58
- });
59
- it('resolves a deployment via matchLabels', async () => {
60
- const core = makeCoreApi({ pods: [{ name: 'p', ready: true }] });
61
- const apps = makeAppsApi({ deployment: { matchLabels: { 'hammerkit.dev/id': 'x' } } });
62
- await expect((0, resolve_pod_name_1.resolvePodName)(core, apps, 'ns', { type: 'deployment', name: 'api' })).resolves.toBe('p');
63
- expect(core.listNamespacedPod).toHaveBeenCalledWith('ns', undefined, undefined, undefined, undefined, 'hammerkit.dev/id=x');
64
- });
65
- it('throws when a service has no selector', async () => {
66
- const core = makeCoreApi({ service: { selector: undefined } });
67
- const apps = makeAppsApi();
68
- await expect((0, resolve_pod_name_1.resolvePodName)(core, apps, 'ns', { type: 'service', name: 'api' })).rejects.toThrow(/no selector/);
69
- });
70
- it('throws when no pods match', async () => {
71
- const core = makeCoreApi({ service: { selector: { app: 'api' } }, pods: [] });
72
- const apps = makeAppsApi();
73
- await expect((0, resolve_pod_name_1.resolvePodName)(core, apps, 'ns', { type: 'service', name: 'api' })).rejects.toThrow(/no pod found/);
74
- });
75
- it('rejects unsupported selector types', async () => {
76
- const core = makeCoreApi();
77
- const apps = makeAppsApi();
78
- await expect((0, resolve_pod_name_1.resolvePodName)(core, apps, 'ns', { type: 'statefulset', name: 'x' })).rejects.toThrow(/unsupported selector type/);
79
- });
80
- });
81
- //# sourceMappingURL=resolve-pod-name.spec.js.map
package/dist/log.spec.js DELETED
@@ -1,131 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const stream_1 = require("stream");
4
- const log_1 = require("./log");
5
- const environment_mock_1 = require("./executer/environment-mock");
6
- function capture() {
7
- const chunks = [];
8
- const stream = new stream_1.Writable({
9
- write(chunk, _enc, cb) {
10
- chunks.push(chunk.toString());
11
- cb();
12
- },
13
- });
14
- return { stream, read: () => chunks.join('') };
15
- }
16
- describe('log helpers', () => {
17
- describe('getLogs', () => {
18
- it('splits on \\r?\\n and drops empty lines', () => {
19
- expect((0, log_1.getLogs)('a\r\nb\nc\n\n')).toEqual(['a', 'b', 'c']);
20
- });
21
- it('accepts a Buffer', () => {
22
- expect((0, log_1.getLogs)(Buffer.from('x\ny'))).toEqual(['x', 'y']);
23
- });
24
- });
25
- describe('getLogLevel', () => {
26
- it.each(['debug', 'info', 'warn', 'error'])('returns a string containing %s', (level) => {
27
- expect((0, log_1.getLogLevel)(level)).toContain(level);
28
- });
29
- });
30
- describe('getErrorMessage', () => {
31
- it('uses Error.message for Error instances', () => {
32
- expect((0, log_1.getErrorMessage)(new Error('boom'))).toBe('boom');
33
- });
34
- it('returns a string value unchanged', () => {
35
- expect((0, log_1.getErrorMessage)('plain')).toBe('plain');
36
- });
37
- it('falls back to "unknown error <value>" for other values', () => {
38
- expect((0, log_1.getErrorMessage)({ x: 1 })).toMatch(/^unknown error/);
39
- });
40
- });
41
- describe('consoleContext', () => {
42
- it('writes formatted lines for each level', () => {
43
- const cap = capture();
44
- const ctx = (0, log_1.consoleContext)(cap.stream);
45
- ctx.debug('d-msg');
46
- ctx.info('i-msg');
47
- ctx.warn('w-msg');
48
- ctx.error('e-msg');
49
- const out = cap.read();
50
- expect(out).toContain('d-msg');
51
- expect(out).toContain('i-msg');
52
- expect(out).toContain('w-msg');
53
- expect(out).toContain('e-msg');
54
- // 4 newlines, one per call
55
- expect(out.split('\n').filter((l) => l.length > 0)).toHaveLength(4);
56
- });
57
- });
58
- describe('getType / getNodeName / getMaxNameLength', () => {
59
- it('formats the type with the configured padding', () => {
60
- expect((0, log_1.getType)('cli')).toContain('cli');
61
- });
62
- it('pads name to a given length', () => {
63
- expect((0, log_1.getNodeName)('a', 4)).toContain('a');
64
- // pads with spaces to reach the requested length, then color codes
65
- // surround it — assert by inspecting the visible characters
66
- // ANSI color wrappers can only add chars, so the rendered string is at
67
- // least the requested width even when colors are enabled.
68
- expect((0, log_1.getNodeName)('a', 4).length).toBeGreaterThanOrEqual(4);
69
- });
70
- it('getMaxNameLength returns the longest name yielded', () => {
71
- const names = function* () {
72
- yield 'aa';
73
- yield 'aaaaa';
74
- yield 'a';
75
- };
76
- expect((0, log_1.getMaxNameLength)(names)).toBe(5);
77
- });
78
- it('getMaxNameLength returns 0 for an empty source', () => {
79
- expect((0, log_1.getMaxNameLength)(() => [])).toBe(0);
80
- });
81
- });
82
- describe('print helpers', () => {
83
- it('printItem with and without description', () => {
84
- const env = (0, environment_mock_1.environmentMock)('/tmp');
85
- const cap = capture();
86
- env.stdout = cap.stream;
87
- (0, log_1.printItem)(env, { name: 'a', description: null });
88
- (0, log_1.printItem)(env, { name: 'b', description: 'desc' });
89
- const out = cap.read();
90
- expect(out).toContain('• a\n');
91
- expect(out).toContain('• b');
92
- expect(out).toContain('desc');
93
- });
94
- it('printProperty writes "<name>: <value>"', () => {
95
- const env = (0, environment_mock_1.environmentMock)('/tmp');
96
- const cap = capture();
97
- env.stdout = cap.stream;
98
- (0, log_1.printProperty)(env, 'image', 'alpine');
99
- expect(cap.read()).toContain('image:');
100
- expect(cap.read()).toContain('alpine');
101
- });
102
- it('printTitle writes the title with a colon', () => {
103
- const env = (0, environment_mock_1.environmentMock)('/tmp');
104
- const cap = capture();
105
- env.stdout = cap.stream;
106
- (0, log_1.printTitle)(env, 'Tasks');
107
- expect(cap.read()).toContain('Tasks:');
108
- });
109
- });
110
- describe('formatDate', () => {
111
- it('produces a [yyyy-MM-DD HH:mm:ss.SSS] formatted timestamp', () => {
112
- // Use a fixed Date in UTC; the function uses local-time getters, so only
113
- // pattern-check the output rather than the literal value.
114
- const out = (0, log_1.formatDate)(new Date(2024, 0, 2, 3, 4, 5, 6));
115
- expect(out).toMatch(/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]/);
116
- });
117
- });
118
- describe('cursor', () => {
119
- it('hideCursor / showCursor write escape sequences', () => {
120
- const env = (0, environment_mock_1.environmentMock)('/tmp');
121
- const cap = capture();
122
- env.stdout = cap.stream;
123
- (0, log_1.hideCursor)(env);
124
- (0, log_1.showCursor)(env);
125
- const out = cap.read();
126
- expect(out).toContain('\x1B[?25l');
127
- expect(out).toContain('\x1B[?25h');
128
- });
129
- });
130
- });
131
- //# sourceMappingURL=log.spec.js.map
@@ -1,84 +0,0 @@
1
- "use strict";
2
- // Verifies that the grouped logger only flushes a task/service once it reaches
3
- // a terminal state, by mocking the underlying log helpers and iterators.
4
- Object.defineProperty(exports, "__esModule", { value: true });
5
- vi.mock('../log', () => ({
6
- writeWorkItemLogToConsole: vi.fn(),
7
- printWorkTreeResult: vi.fn().mockResolvedValue(undefined),
8
- getWorkItemMaxLength: vi.fn(() => 10),
9
- }));
10
- vi.mock('../planner/utils/plan-work-tasks', () => ({
11
- iterateWorkTasks: vi.fn(),
12
- iterateWorkServices: vi.fn(),
13
- }));
14
- const grouped_logger_1 = require("./grouped-logger");
15
- const state_1 = require("../executer/state");
16
- const plan_work_tasks_1 = require("../planner/utils/plan-work-tasks");
17
- const log_1 = require("../log");
18
- const mockedIterTasks = plan_work_tasks_1.iterateWorkTasks;
19
- const mockedIterServices = plan_work_tasks_1.iterateWorkServices;
20
- const mockedWrite = log_1.writeWorkItemLogToConsole;
21
- const mockedPrint = log_1.printWorkTreeResult;
22
- function fakeTask(name, type, logs = []) {
23
- return { name, state: { current: { type } }, status: { read: () => logs } };
24
- }
25
- describe('groupedLogger', () => {
26
- beforeEach(() => {
27
- mockedWrite.mockClear();
28
- mockedPrint.mockClear();
29
- mockedIterTasks.mockReset();
30
- mockedIterServices.mockReset();
31
- });
32
- it('flushes a task only once it reaches a terminal state and not again on re-emit', () => {
33
- const t = fakeTask('t1', 'pending', [{ id: 1 }]);
34
- mockedIterTasks.mockImplementation(function* () {
35
- yield t;
36
- });
37
- mockedIterServices.mockImplementation(function* () { });
38
- const state = new state_1.State({ services: {}, tasks: { t1: t } });
39
- (0, grouped_logger_1.groupedLogger)(state, {});
40
- state.set(state.current); // pending — should NOT flush
41
- expect(mockedWrite).not.toHaveBeenCalled();
42
- t.state.current.type = 'completed';
43
- state.set(state.current); // completed — flush once
44
- expect(mockedWrite).toHaveBeenCalledTimes(1);
45
- state.set(state.current); // already-flushed task — must not flush again
46
- expect(mockedWrite).toHaveBeenCalledTimes(1);
47
- });
48
- it.each(['crash', 'error', 'completed'])('flushes on terminal state %s', (terminal) => {
49
- const t = fakeTask('t1', terminal, [{ id: 1 }]);
50
- mockedIterTasks.mockImplementation(function* () {
51
- yield t;
52
- });
53
- mockedIterServices.mockImplementation(function* () { });
54
- const state = new state_1.State({ services: {}, tasks: { t1: t } });
55
- (0, grouped_logger_1.groupedLogger)(state, {});
56
- state.set(state.current);
57
- expect(mockedWrite).toHaveBeenCalledTimes(1);
58
- });
59
- it('flushes a service on type "end" and not again', () => {
60
- const s = fakeTask('svc1', 'starting', [{ id: 1 }]);
61
- mockedIterTasks.mockImplementation(function* () { });
62
- mockedIterServices.mockImplementation(function* () {
63
- yield s;
64
- });
65
- const state = new state_1.State({ services: { svc1: s }, tasks: {} });
66
- (0, grouped_logger_1.groupedLogger)(state, {});
67
- state.set(state.current); // not yet ended
68
- expect(mockedWrite).not.toHaveBeenCalled();
69
- s.state.current.type = 'end';
70
- state.set(state.current); // ended — flush
71
- expect(mockedWrite).toHaveBeenCalledTimes(1);
72
- state.set(state.current); // already flushed — no second call
73
- expect(mockedWrite).toHaveBeenCalledTimes(1);
74
- });
75
- it('complete() delegates to printWorkTreeResult', async () => {
76
- mockedIterTasks.mockImplementation(function* () { });
77
- mockedIterServices.mockImplementation(function* () { });
78
- const state = new state_1.State({ services: {}, tasks: {} });
79
- const logger = (0, grouped_logger_1.groupedLogger)(state, {});
80
- await logger.complete({ state: state.current, success: true }, {});
81
- expect(mockedPrint).toHaveBeenCalledTimes(1);
82
- });
83
- });
84
- //# sourceMappingURL=grouped-logger.spec.js.map
@@ -1,57 +0,0 @@
1
- "use strict";
2
- // interactiveLogger hides the cursor, polls writeWorkTreeStatus on a 100ms
3
- // ticker, and writes on every state change until complete() stops it.
4
- Object.defineProperty(exports, "__esModule", { value: true });
5
- vi.mock('../log', () => ({
6
- writeWorkTreeStatus: vi.fn(),
7
- printWorkTreeResult: vi.fn().mockResolvedValue(undefined),
8
- hideCursor: vi.fn(),
9
- showCursor: vi.fn(),
10
- }));
11
- vi.mock('readline', () => ({ clearScreenDown: vi.fn() }));
12
- const interactive_logger_1 = require("./interactive-logger");
13
- const state_1 = require("../executer/state");
14
- const log_1 = require("../log");
15
- const readline_1 = require("readline");
16
- const mockedWriteStatus = log_1.writeWorkTreeStatus;
17
- const mockedHide = log_1.hideCursor;
18
- const mockedShow = log_1.showCursor;
19
- const mockedClear = readline_1.clearScreenDown;
20
- const mockedPrint = log_1.printWorkTreeResult;
21
- describe('interactiveLogger', () => {
22
- beforeEach(() => {
23
- vi.useFakeTimers();
24
- mockedWriteStatus.mockClear();
25
- mockedHide.mockClear();
26
- mockedShow.mockClear();
27
- mockedClear.mockClear();
28
- mockedPrint.mockClear();
29
- });
30
- afterEach(() => {
31
- vi.useRealTimers();
32
- });
33
- it('hides the cursor, ticks every 100ms, and writes on state changes', async () => {
34
- const state = new state_1.State({ services: {}, tasks: {} });
35
- const env = { stdout: { write: vi.fn() } };
36
- const logger = (0, interactive_logger_1.interactiveLogger)(state, env);
37
- // hideCursor on construction; initial tick fired immediately
38
- expect(mockedHide).toHaveBeenCalledTimes(1);
39
- expect(mockedWriteStatus).toHaveBeenCalledTimes(1);
40
- // advance one tick window → one additional writeWorkTreeStatus
41
- vi.advanceTimersByTime(100);
42
- expect(mockedWriteStatus).toHaveBeenCalledTimes(2);
43
- // a state change writes status synchronously
44
- state.set({ services: {}, tasks: {} });
45
- expect(mockedWriteStatus).toHaveBeenCalledTimes(3);
46
- // complete() stops the ticker and restores the cursor
47
- await logger.complete({ state: state.current, success: true }, env);
48
- expect(mockedClear).toHaveBeenCalledWith(env.stdout);
49
- expect(mockedPrint).toHaveBeenCalledTimes(1);
50
- expect(mockedShow).toHaveBeenCalledTimes(1);
51
- // after complete, advancing time must NOT schedule further writes
52
- const after = mockedWriteStatus.mock.calls.length;
53
- vi.advanceTimersByTime(1000);
54
- expect(mockedWriteStatus.mock.calls.length).toBe(after);
55
- });
56
- });
57
- //# sourceMappingURL=interactive-logger.spec.js.map
@@ -1,37 +0,0 @@
1
- "use strict";
2
- // liveLogger forwards every status message verbatim to writeWorkItemLogToConsole.
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- vi.mock('../log', () => ({
5
- writeWorkItemLogToConsole: vi.fn(),
6
- printWorkTreeResult: vi.fn().mockResolvedValue(undefined),
7
- getWorkItemMaxLength: vi.fn(() => 10),
8
- }));
9
- const live_logger_1 = require("./live-logger");
10
- const state_1 = require("../executer/state");
11
- const log_1 = require("../log");
12
- const emitter_1 = require("../utils/emitter");
13
- const mockedWrite = log_1.writeWorkItemLogToConsole;
14
- const mockedPrint = log_1.printWorkTreeResult;
15
- describe('liveLogger', () => {
16
- beforeEach(() => {
17
- mockedWrite.mockClear();
18
- mockedPrint.mockClear();
19
- });
20
- it('forwards each status message to writeWorkItemLogToConsole', () => {
21
- const status = (0, emitter_1.emitter)();
22
- const env = { status };
23
- const state = new state_1.State({ services: {}, tasks: {} });
24
- (0, live_logger_1.liveLogger)(state, env);
25
- const msg = { type: 'status', message: 'hi' };
26
- status.emit(msg);
27
- expect(mockedWrite).toHaveBeenCalledWith(env, msg, 10);
28
- });
29
- it('complete() delegates to printWorkTreeResult', async () => {
30
- const status = (0, emitter_1.emitter)();
31
- const state = new state_1.State({ services: {}, tasks: {} });
32
- const logger = (0, live_logger_1.liveLogger)(state, { status });
33
- await logger.complete({ state: state.current, success: true }, {});
34
- expect(mockedPrint).toHaveBeenCalledTimes(1);
35
- });
36
- });
37
- //# sourceMappingURL=live-logger.spec.js.map
@@ -1,154 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const stream_1 = require("stream");
4
- const state_1 = require("../executer/state");
5
- const work_item_status_1 = require("../planner/work-item-status");
6
- const live_logger_1 = require("./live-logger");
7
- const grouped_logger_1 = require("./grouped-logger");
8
- const interactive_logger_1 = require("./interactive-logger");
9
- function memStream() {
10
- const chunks = [];
11
- const stream = new stream_1.Writable({
12
- write(c, _e, cb) {
13
- chunks.push(c.toString());
14
- cb();
15
- },
16
- });
17
- return Object.assign(stream, { read: () => chunks.join('') });
18
- }
19
- function noopStream() {
20
- return new stream_1.Writable({ write: (_c, _e, cb) => cb() });
21
- }
22
- function makeEnv(stdout = noopStream()) {
23
- return {
24
- cwd: '/tmp',
25
- file: {},
26
- processEnvs: {},
27
- abortCtrl: new AbortController(),
28
- console: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn(), log: vi.fn() },
29
- status: (0, work_item_status_1.statusConsole)(noopStream()),
30
- stdout,
31
- stderr: noopStream(),
32
- stdoutColumns: 80,
33
- };
34
- }
35
- function taskItem(env, name, state) {
36
- const data = { type: 'local-task', name, description: null, scope: { fileName: 'x' } };
37
- return {
38
- id: () => name,
39
- name,
40
- data,
41
- status: env.status.from(data),
42
- state: new state_1.State(state),
43
- needs: [],
44
- deps: [],
45
- requiredBy: [],
46
- runtime: {},
47
- };
48
- }
49
- function serviceItem(env, name, state) {
50
- const data = { type: 'container-service', name, ports: [] };
51
- return {
52
- id: () => name,
53
- name,
54
- data,
55
- status: env.status.from(data),
56
- state: new state_1.State(state),
57
- needs: [],
58
- deps: [],
59
- requiredBy: [],
60
- runtime: {},
61
- };
62
- }
63
- function workTree(items) {
64
- return {
65
- tasks: Object.fromEntries((items.tasks ?? []).map((t) => [t.name, t])),
66
- services: Object.fromEntries((items.services ?? []).map((s) => [s.name, s])),
67
- environment: { type: 'docker' },
68
- };
69
- }
70
- describe('loggers', () => {
71
- describe('liveLogger', () => {
72
- it('writes each status message and renders results on complete', async () => {
73
- const out = memStream();
74
- const env = makeEnv(out);
75
- const task = taskItem(env, 'build', { type: 'pending', stateKey: null });
76
- const tree = workTree({ tasks: [task] });
77
- const state = new state_1.State(tree);
78
- const logger = (0, live_logger_1.liveLogger)(state, env);
79
- // a write on the scoped console emits a message on env.status, which the
80
- // logger's listener formats into env.stdout
81
- task.status.write('info', 'compiling');
82
- const result = { state: tree, success: true };
83
- await logger.complete(result, env);
84
- expect(out.read()).toContain('compiling');
85
- });
86
- });
87
- describe('groupedLogger', () => {
88
- it("writes a completed task's logs on the next status update", async () => {
89
- const out = memStream();
90
- const env = makeEnv(out);
91
- const task = taskItem(env, 'build', { type: 'pending', stateKey: null });
92
- const tree = workTree({ tasks: [task] });
93
- const state = new state_1.State(tree);
94
- const logger = (0, grouped_logger_1.groupedLogger)(state, env);
95
- // buffer one info log, mark the task completed, then notify the parent state
96
- task.status.write('info', 'compiling');
97
- task.state.set({ type: 'completed', stateKey: 'k', duration: 5, cached: false });
98
- state.set(tree);
99
- await logger.complete({ state: tree, success: true }, env);
100
- expect(out.read()).toContain('compiling');
101
- });
102
- it("writes an ended service's logs on the next status update", async () => {
103
- const out = memStream();
104
- const env = makeEnv(out);
105
- const svc = serviceItem(env, 'db', { type: 'pending', stateKey: null });
106
- const tree = workTree({ services: [svc] });
107
- const state = new state_1.State(tree);
108
- const logger = (0, grouped_logger_1.groupedLogger)(state, env);
109
- svc.status.write('info', 'ready');
110
- svc.state.set({ type: 'end', reason: 'terminated', stateKey: 'k' });
111
- state.set(tree);
112
- await logger.complete({ state: tree, success: true }, env);
113
- expect(out.read()).toContain('ready');
114
- });
115
- it('does not duplicate a task that is already in the completed set', async () => {
116
- const out = memStream();
117
- const env = makeEnv(out);
118
- const task = taskItem(env, 'build', { type: 'pending', stateKey: null });
119
- const tree = workTree({ tasks: [task] });
120
- const state = new state_1.State(tree);
121
- const logger = (0, grouped_logger_1.groupedLogger)(state, env);
122
- task.status.write('info', 'once');
123
- task.state.set({ type: 'completed', stateKey: 'k', duration: 1, cached: false });
124
- state.set(tree);
125
- const after = out.read();
126
- // a second notification with no further log message should not append another copy
127
- state.set(tree);
128
- expect(out.read()).toEqual(after);
129
- await logger.complete({ state: tree, success: true }, env);
130
- });
131
- });
132
- describe('interactiveLogger', () => {
133
- beforeEach(() => vi.useFakeTimers());
134
- afterEach(() => vi.useRealTimers());
135
- it('hides the cursor, ticks, redraws on log-status, then cleans up on complete', async () => {
136
- const out = memStream();
137
- const env = makeEnv(out);
138
- const task = taskItem(env, 'build', { type: 'pending', stateKey: null });
139
- const tree = workTree({ tasks: [task] });
140
- const state = new state_1.State(tree);
141
- const logger = (0, interactive_logger_1.interactiveLogger)(state, env); // hideCursor + first tick + setTimeout
142
- // drive a state change to re-render
143
- task.state.set({ type: 'running', stateKey: 'k' });
144
- state.set(tree);
145
- // let one fake tick fire while running===true (covers the recursive branch)
146
- vi.advanceTimersByTime(150);
147
- await logger.complete({ state: tree, success: true }, env);
148
- // and one more after complete — covers the `if (!running) return` early exit
149
- vi.advanceTimersByTime(150);
150
- expect(out.read().length).toBeGreaterThan(0);
151
- });
152
- });
153
- });
154
- //# sourceMappingURL=loggers.spec.js.map
@@ -1,34 +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 crypto_1 = require("crypto");
7
- const environment_mock_1 = require("../executer/environment-mock");
8
- const calculate_checksum_1 = require("./calculate-checksum");
9
- describe('calculateChecksum', () => {
10
- let scratch;
11
- beforeEach(() => {
12
- scratch = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'hammerkit-checksum-'));
13
- });
14
- afterEach(() => {
15
- (0, fs_1.rmSync)(scratch, { recursive: true, force: true });
16
- });
17
- it('returns the sha1 of the file contents', async () => {
18
- const env = (0, environment_mock_1.environmentMock)(scratch);
19
- const file = (0, path_1.join)(scratch, 'a.txt');
20
- (0, fs_1.writeFileSync)(file, 'hello world');
21
- const expected = (0, crypto_1.createHash)('sha1').update('hello world').digest('hex');
22
- expect(await (0, calculate_checksum_1.calculateChecksum)(env, file)).toBe(expected);
23
- });
24
- it('is deterministic and content-sensitive', async () => {
25
- const env = (0, environment_mock_1.environmentMock)(scratch);
26
- const file = (0, path_1.join)(scratch, 'a.txt');
27
- (0, fs_1.writeFileSync)(file, 'one');
28
- const first = await (0, calculate_checksum_1.calculateChecksum)(env, file);
29
- expect(await (0, calculate_checksum_1.calculateChecksum)(env, file)).toBe(first);
30
- (0, fs_1.writeFileSync)(file, 'two');
31
- expect(await (0, calculate_checksum_1.calculateChecksum)(env, file)).not.toBe(first);
32
- });
33
- });
34
- //# sourceMappingURL=calculate-checksum.spec.js.map
@@ -1,59 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const path_1 = require("path");
4
- const get_cache_directory_1 = require("./get-cache-directory");
5
- function withPlatform(value, env, fn) {
6
- const originalPlatform = process.platform;
7
- const originalEnv = {};
8
- for (const k of Object.keys(env))
9
- originalEnv[k] = process.env[k];
10
- Object.defineProperty(process, 'platform', { value, configurable: true });
11
- for (const [k, v] of Object.entries(env)) {
12
- if (v === undefined)
13
- delete process.env[k];
14
- else
15
- process.env[k] = v;
16
- }
17
- try {
18
- return fn();
19
- }
20
- finally {
21
- Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
22
- for (const [k, v] of Object.entries(originalEnv)) {
23
- if (v === undefined)
24
- delete process.env[k];
25
- else
26
- process.env[k] = v;
27
- }
28
- }
29
- }
30
- describe('cache directory helpers', () => {
31
- it('builds the per-cache directory under the hammerkit directory', () => {
32
- const dir = (0, get_cache_directory_1.getCacheDirectory)('abc123');
33
- expect(dir.endsWith((0, path_1.join)('cache', 'abc123'))).toBe(true);
34
- });
35
- it('builds the description file path under the cache directory', () => {
36
- expect((0, get_cache_directory_1.getCacheDescriptionFile)('id').endsWith((0, path_1.join)('cache', 'id', 'description.json'))).toBe(true);
37
- });
38
- it('builds the stats file path under the cache directory', () => {
39
- expect((0, get_cache_directory_1.getCacheStatsFile)('id').endsWith((0, path_1.join)('cache', 'id', 'stats.json'))).toBe(true);
40
- });
41
- describe('getHammerkitDirectory', () => {
42
- it('uses %APPDATA% on Windows', () => {
43
- withPlatform('win32', { APPDATA: 'C:\\Users\\X\\AppData\\Roaming' }, () => {
44
- expect((0, get_cache_directory_1.getHammerkitDirectory)()).toBe((0, path_1.join)('C:\\Users\\X\\AppData\\Roaming', 'hammerkit'));
45
- });
46
- });
47
- it('uses ~/Library/Application Support on macOS', () => {
48
- withPlatform('darwin', { HOME: '/Users/x' }, () => {
49
- expect((0, get_cache_directory_1.getHammerkitDirectory)()).toBe((0, path_1.join)('/Users/x', 'Library', 'Application Support', 'hammerkit'));
50
- });
51
- });
52
- it('uses ~/.hammerkit on Linux', () => {
53
- withPlatform('linux', { HOME: '/home/x' }, () => {
54
- expect((0, get_cache_directory_1.getHammerkitDirectory)()).toBe((0, path_1.join)('/home/x', '.hammerkit'));
55
- });
56
- });
57
- });
58
- });
59
- //# sourceMappingURL=get-cache-directory.spec.js.map