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,24 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const parse_work_port_1 = require("./parse-work-port");
4
- describe('parse-work-port', () => {
5
- it('should parse 5432', () => {
6
- expect((0, parse_work_port_1.parseWorkPort)('5432')).toEqual({
7
- hostPort: 5432,
8
- containerPort: 5432,
9
- });
10
- });
11
- it('should parse 5432:5433', () => {
12
- expect((0, parse_work_port_1.parseWorkPort)('5432:5433')).toEqual({
13
- hostPort: 5432,
14
- containerPort: 5433,
15
- });
16
- });
17
- it('throws for a port with more than two parts', () => {
18
- expect(() => (0, parse_work_port_1.parseWorkPort)('1:2:3')).toThrow('invalid port 1:2:3');
19
- });
20
- it('throws for a non-numeric port', () => {
21
- expect(() => (0, parse_work_port_1.parseWorkPort)('http')).toThrow('invalid port http');
22
- });
23
- });
24
- //# sourceMappingURL=parse-work-port.spec.js.map
@@ -1,24 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const parse_work_ports_1 = require("./parse-work-ports");
4
- const noEnvs = { variables: {}, replacements: [] };
5
- describe('parse-work-ports', () => {
6
- it('returns an empty list when the service declares no ports', () => {
7
- const schema = { image: 'postgres:12-alpine' };
8
- expect((0, parse_work_ports_1.parseWorkPorts)(schema, noEnvs)).toEqual([]);
9
- });
10
- it('parses numeric and string ports', () => {
11
- const schema = { image: 'node', ports: [5432, '8080:80'] };
12
- expect((0, parse_work_ports_1.parseWorkPorts)(schema, noEnvs)).toEqual([
13
- { hostPort: 5432, containerPort: 5432 },
14
- { hostPort: 8080, containerPort: 80 },
15
- ]);
16
- });
17
- it('templates env vars in port values', () => {
18
- const schema = { image: 'node', ports: ['$PORT:80'] };
19
- expect((0, parse_work_ports_1.parseWorkPorts)(schema, { variables: { PORT: '9090' }, replacements: [] })).toEqual([
20
- { hostPort: 9090, containerPort: 80 },
21
- ]);
22
- });
23
- });
24
- //# sourceMappingURL=parse-work-ports.spec.js.map
@@ -1,58 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const path_1 = require("path");
4
- const parse_work_source_1 = require("./parse-work-source");
5
- const noEnvs = { variables: {}, replacements: [] };
6
- // parseWorkSource builds absolutePath via path.join and the no-wildcard matcher
7
- // compares with String.startsWith, so both are native-separated. Author the
8
- // expectations in posix and convert to the host separator. No-op on posix.
9
- const toNative = (value) => value.split(path_1.posix.sep).join(path_1.sep);
10
- describe('parse-work-source', () => {
11
- it('returns an empty list for null/undefined sources', () => {
12
- expect((0, parse_work_source_1.parseWorkSource)('/proj', null, noEnvs)).toEqual([]);
13
- expect((0, parse_work_source_1.parseWorkSource)('/proj', undefined, noEnvs)).toEqual([]);
14
- });
15
- describe('without a wildcard', () => {
16
- it('treats a path with an extension as a file', () => {
17
- const [src] = (0, parse_work_source_1.parseWorkSource)('/proj', ['dist/app.js'], noEnvs);
18
- expect(src.absolutePath).toBe(toNative('/proj/dist/app.js'));
19
- expect(src.isFile).toBe(true);
20
- expect(src.matcher(toNative('/proj/dist/app.js'), '/proj')).toBe(true);
21
- expect(src.matcher(toNative('/proj/dist/other.js'), '/proj')).toBe(false);
22
- });
23
- it('treats a path without an extension as a directory', () => {
24
- const [src] = (0, parse_work_source_1.parseWorkSource)('/proj', ['src'], noEnvs);
25
- expect(src.absolutePath).toBe(toNative('/proj/src'));
26
- expect(src.isFile).toBe(false);
27
- expect(src.matcher(toNative('/proj/src/index.ts'), '/proj')).toBe(true);
28
- });
29
- });
30
- describe('with a leading wildcard', () => {
31
- it('anchors the absolute path to cwd and matches relative paths', () => {
32
- const [src] = (0, parse_work_source_1.parseWorkSource)('/proj', ['*.spec.ts'], noEnvs);
33
- expect(src.absolutePath).toBe('/proj');
34
- expect(src.isFile).toBe(false);
35
- expect(src.matcher('/proj/a.spec.ts', '/proj')).toBe(true);
36
- expect(src.matcher('/proj/a.ts', '/proj')).toBe(false);
37
- });
38
- });
39
- describe('with a mid-string wildcard', () => {
40
- it('uses the prefix for the absolute path and globs against the full path', () => {
41
- const [src] = (0, parse_work_source_1.parseWorkSource)('/proj', ['src/*.ts'], noEnvs);
42
- expect(src.absolutePath).toBe(toNative('/proj/src/'));
43
- expect(src.isFile).toBe(false);
44
- expect(src.matcher('/proj/src/a.ts', '/proj')).toBe(true);
45
- expect(src.matcher('/proj/src/sub/a.ts', '/proj')).toBe(false);
46
- });
47
- });
48
- describe('createSource', () => {
49
- it('builds a prefix matcher from an absolute path', () => {
50
- const src = (0, parse_work_source_1.createSource)('/proj/dist');
51
- expect(src.absolutePath).toBe('/proj/dist');
52
- expect(src.source).toBe('/proj/dist');
53
- expect(src.matcher('/proj/dist/app.js', '/proj')).toBe(true);
54
- expect(src.matcher('/proj/other', '/proj')).toBe(false);
55
- });
56
- });
57
- });
58
- //# sourceMappingURL=parse-work-source.spec.js.map
@@ -1,18 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const parse_work_volume_1 = require("./parse-work-volume");
4
- describe('parse-work-volume', () => {
5
- it('should parse projdata:/usr/data', () => {
6
- expect((0, parse_work_volume_1.parseWorkVolume)('/home/user/proj', 'projdata:/usr/data', { replacements: [], variables: {} })).toEqual({
7
- name: 'projdata',
8
- containerPath: '/usr/data',
9
- resetOnChange: false,
10
- export: false,
11
- inherited: null,
12
- });
13
- });
14
- it('throws for a volume string that is not name:path', () => {
15
- expect(() => (0, parse_work_volume_1.parseWorkVolume)('/home/user/proj', 'a:b:c', { replacements: [], variables: {} })).toThrow('invalid volume a:b:c');
16
- });
17
- });
18
- //# sourceMappingURL=parse-work-volume.spec.js.map
@@ -1,30 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const plan_work_tasks_1 = require("./plan-work-tasks");
4
- const ok = { state: { current: { type: 'completed' } } };
5
- const err = { state: { current: { type: 'error' } } };
6
- const tree = (services, tasks) => ({ services, tasks });
7
- describe('plan-work-tasks iterators and error helpers', () => {
8
- it('iterateWorkServices yields every service value', () => {
9
- const t = tree({ a: { id: 'A' }, b: { id: 'B' } }, {});
10
- expect([...(0, plan_work_tasks_1.iterateWorkServices)(t)].map((s) => s.id)).toEqual(['A', 'B']);
11
- });
12
- it('iterateWorkTasks yields every task value', () => {
13
- const t = tree({}, { a: { id: 'A' } });
14
- expect([...(0, plan_work_tasks_1.iterateWorkTasks)(t)].map((s) => s.id)).toEqual(['A']);
15
- });
16
- it('hasErrorTask is true iff a task is in error', () => {
17
- expect((0, plan_work_tasks_1.hasErrorTask)(tree({}, { a: ok }))).toBe(false);
18
- expect((0, plan_work_tasks_1.hasErrorTask)(tree({}, { a: ok, b: err }))).toBe(true);
19
- });
20
- it('hasErrorService is true iff a service is in error', () => {
21
- expect((0, plan_work_tasks_1.hasErrorService)(tree({ a: ok }, {}))).toBe(false);
22
- expect((0, plan_work_tasks_1.hasErrorService)(tree({ a: err }, {}))).toBe(true);
23
- });
24
- it('hasError covers both services and tasks', () => {
25
- expect((0, plan_work_tasks_1.hasError)(tree({}, {}))).toBe(false);
26
- expect((0, plan_work_tasks_1.hasError)(tree({ a: err }, {}))).toBe(true);
27
- expect((0, plan_work_tasks_1.hasError)(tree({}, { a: err }))).toBe(true);
28
- });
29
- });
30
- //# sourceMappingURL=plan-work-tasks.spec.js.map
@@ -1,33 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const plan_work_volume_1 = require("./plan-work-volume");
4
- describe('plan-work-volume', () => {
5
- describe('generateId / getVolumeName', () => {
6
- it('hashes the input deterministically (sha1)', () => {
7
- expect((0, plan_work_volume_1.generateId)('a')).toBe((0, plan_work_volume_1.generateId)('a'));
8
- expect((0, plan_work_volume_1.generateId)('a')).not.toBe((0, plan_work_volume_1.generateId)('b'));
9
- expect((0, plan_work_volume_1.getVolumeName)('x')).toMatch(/^hammerkit-[a-f0-9]{40}$/);
10
- });
11
- });
12
- describe('getContainerVolumes', () => {
13
- const task = {
14
- generates: [
15
- { path: '/out/a', volumeName: 'na', inherited: null, resetOnChange: true, export: false, isFile: false },
16
- { path: '/out/b', volumeName: 'nb', inherited: null, resetOnChange: false, export: true, isFile: false },
17
- ],
18
- };
19
- it('emits a named volume for every generate that has no matching mount', () => {
20
- const volumes = (0, plan_work_volume_1.getContainerVolumes)(task, []);
21
- expect(volumes.map((v) => v.containerPath)).toEqual(['/out/a', '/out/b']);
22
- expect(volumes[0].resetOnChange).toBe(true);
23
- expect(volumes[1].export).toBe(true);
24
- expect(volumes[0].name).toMatch(/^hammerkit-/);
25
- });
26
- it('skips a generate whose path is already mounted (mount wins)', () => {
27
- const mounts = [{ containerPath: '/out/a' }];
28
- const volumes = (0, plan_work_volume_1.getContainerVolumes)(task, mounts);
29
- expect(volumes.map((v) => v.containerPath)).toEqual(['/out/b']);
30
- });
31
- });
32
- });
33
- //# sourceMappingURL=plan-work-volume.spec.js.map
@@ -1,16 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const split_name_1 = require("./split-name");
4
- describe('split-name', () => {
5
- it('should split test:bar', () => {
6
- const result = (0, split_name_1.splitName)('test:bar');
7
- expect(result.name).toEqual('bar');
8
- expect(result.prefix).toEqual('test');
9
- });
10
- it('should not split test', () => {
11
- const result = (0, split_name_1.splitName)('test');
12
- expect(result.name).toEqual('test');
13
- expect(result.prefix).toEqual(undefined);
14
- });
15
- });
16
- //# sourceMappingURL=split-name.spec.js.map
@@ -1,14 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const template_value_1 = require("./template-value");
4
- describe('template-value', () => {
5
- it('should template string value', () => {
6
- const value = (0, template_value_1.templateValue)('echo $VAR', { variables: { VAR: 'hello' }, replacements: [] });
7
- expect(value).toEqual('echo hello');
8
- });
9
- it('should leave missing envs', () => {
10
- const value = (0, template_value_1.templateValue)('echo $VAR', { variables: {}, replacements: [] });
11
- expect(value).toEqual('echo $VAR');
12
- });
13
- });
14
- //# sourceMappingURL=template-value.spec.js.map
@@ -1,251 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const validate_1 = require("./validate");
4
- function makeTask(name) {
5
- const item = {
6
- id: () => name,
7
- name,
8
- status: {},
9
- data: { type: 'local-task', name },
10
- needs: [],
11
- deps: [],
12
- requiredBy: [],
13
- state: {},
14
- runtime: {},
15
- };
16
- return item;
17
- }
18
- function makeService(name) {
19
- const item = {
20
- id: () => name,
21
- name,
22
- status: {},
23
- data: { type: 'container-service', name },
24
- needs: [],
25
- deps: [],
26
- requiredBy: [],
27
- state: {},
28
- runtime: {},
29
- };
30
- return item;
31
- }
32
- describe('validate', () => {
33
- describe('hasDependencyCycle', () => {
34
- it('returns null for an item with no deps', () => {
35
- const t = makeTask('a');
36
- expect((0, validate_1.hasDependencyCycle)(t, [])).toBeNull();
37
- });
38
- it('returns null for a linear chain', () => {
39
- const a = makeTask('a');
40
- const b = makeTask('b');
41
- const c = makeTask('c');
42
- a.deps.push(b);
43
- b.deps.push(c);
44
- expect((0, validate_1.hasDependencyCycle)(a, [])).toBeNull();
45
- });
46
- it('detects a direct self-cycle', () => {
47
- const a = makeTask('a');
48
- a.deps.push(a);
49
- const cycle = (0, validate_1.hasDependencyCycle)(a, []);
50
- expect(cycle).not.toBeNull();
51
- expect(cycle.map((i) => i.name)).toEqual(['a', 'a']);
52
- });
53
- it('detects an indirect cycle', () => {
54
- const a = makeTask('a');
55
- const b = makeTask('b');
56
- const c = makeTask('c');
57
- a.deps.push(b);
58
- b.deps.push(c);
59
- c.deps.push(a);
60
- const cycle = (0, validate_1.hasDependencyCycle)(a, []);
61
- expect(cycle).not.toBeNull();
62
- expect(cycle.map((i) => i.name)).toEqual(['a', 'b', 'c', 'a']);
63
- });
64
- it('does not flag a diamond as a cycle', () => {
65
- const root = makeTask('root');
66
- const left = makeTask('left');
67
- const right = makeTask('right');
68
- const leaf = makeTask('leaf');
69
- root.deps.push(left, right);
70
- left.deps.push(leaf);
71
- right.deps.push(leaf);
72
- expect((0, validate_1.hasDependencyCycle)(root, [])).toBeNull();
73
- });
74
- });
75
- describe('hasNeedCycle', () => {
76
- it('returns null when no needs', () => {
77
- const s = makeService('db');
78
- expect((0, validate_1.hasNeedCycle)(s, [])).toBeNull();
79
- });
80
- it('detects a need cycle across services', () => {
81
- const a = makeService('a');
82
- const b = makeService('b');
83
- a.needs.push({ name: 'b', service: b });
84
- b.needs.push({ name: 'a', service: a });
85
- const cycle = (0, validate_1.hasNeedCycle)(a, []);
86
- expect(cycle).not.toBeNull();
87
- expect(cycle.map((i) => i.name)).toEqual(['a', 'b', 'a']);
88
- });
89
- it('detects a self-need cycle', () => {
90
- const a = makeService('a');
91
- a.needs.push({ name: 'a', service: a });
92
- const cycle = (0, validate_1.hasNeedCycle)(a, []);
93
- expect(cycle).not.toBeNull();
94
- expect(cycle.map((i) => i.name)).toEqual(['a', 'a']);
95
- });
96
- });
97
- describe('hasMixedCycle', () => {
98
- it('returns null for an acyclic task → service → task chain', () => {
99
- const t1 = makeTask('build');
100
- const s = makeService('db');
101
- const t2 = makeTask('migrate');
102
- t1.needs.push({ name: 'db', service: s });
103
- s.deps.push(t2);
104
- expect((0, validate_1.hasMixedCycle)(t1, [])).toBeNull();
105
- });
106
- it('detects a cycle that alternates deps and needs', () => {
107
- const t = makeTask('api-build');
108
- const s = makeService('api');
109
- t.needs.push({ name: 'api', service: s });
110
- s.deps.push(t);
111
- const cycle = (0, validate_1.hasMixedCycle)(t, []);
112
- expect(cycle).not.toBeNull();
113
- expect(cycle.map((i) => i.name)).toEqual(['api-build', 'api', 'api-build']);
114
- });
115
- it('detects a long mixed cycle', () => {
116
- const a = makeTask('a');
117
- const b = makeService('b');
118
- const c = makeTask('c');
119
- const d = makeService('d');
120
- a.needs.push({ name: 'b', service: b });
121
- b.deps.push(c);
122
- c.needs.push({ name: 'd', service: d });
123
- d.deps.push(a);
124
- const cycle = (0, validate_1.hasMixedCycle)(a, []);
125
- expect(cycle).not.toBeNull();
126
- expect(cycle.map((i) => i.name)).toEqual(['a', 'b', 'c', 'd', 'a']);
127
- });
128
- });
129
- });
130
- function taskItem(name, data) {
131
- const item = {
132
- id: () => name,
133
- name,
134
- status: {},
135
- data: { type: 'local-task', name, description: 'a task', cmds: [{}], src: [], ...data },
136
- needs: [],
137
- deps: [],
138
- requiredBy: [],
139
- state: {},
140
- runtime: {},
141
- };
142
- return item;
143
- }
144
- function serviceItem(name, data) {
145
- const item = {
146
- id: () => name,
147
- name,
148
- status: {},
149
- data: { type: 'container-service', name, description: 'a service', healthcheck: {}, mounts: [], ...data },
150
- needs: [],
151
- deps: [],
152
- requiredBy: [],
153
- state: {},
154
- runtime: {},
155
- };
156
- return item;
157
- }
158
- function workTree(items) {
159
- return {
160
- tasks: Object.fromEntries((items.tasks ?? []).map((t) => [t.name, t])),
161
- services: Object.fromEntries((items.services ?? []).map((s) => [s.name, s])),
162
- environment: {},
163
- };
164
- }
165
- // Environment whose file.exists only returns true for the listed paths.
166
- function fakeEnv(existing = []) {
167
- return { file: { exists: vi.fn(async (p) => existing.includes(p)) } };
168
- }
169
- async function collect(gen) {
170
- const out = [];
171
- for await (const v of gen) {
172
- out.push(v);
173
- }
174
- return out;
175
- }
176
- describe('validate (generator)', () => {
177
- it('warns about a task without a description', async () => {
178
- const tree = workTree({ tasks: [taskItem('build', { description: null })] });
179
- const result = await collect((0, validate_1.validate)(tree, fakeEnv()));
180
- expect(result).toContainEqual(expect.objectContaining({ type: 'warn', message: 'missing description' }));
181
- });
182
- it('warns about an empty task (no cmds and no deps)', async () => {
183
- const tree = workTree({ tasks: [taskItem('noop', { cmds: [] })] });
184
- const result = await collect((0, validate_1.validate)(tree, fakeEnv()));
185
- expect(result).toContainEqual(expect.objectContaining({ type: 'warn', message: 'task is empty' }));
186
- });
187
- it('warns about a missing src path', async () => {
188
- const tree = workTree({ tasks: [taskItem('build', { src: [{ absolutePath: '/nope' }] })] });
189
- const result = await collect((0, validate_1.validate)(tree, fakeEnv([])));
190
- expect(result).toContainEqual(expect.objectContaining({ type: 'warn', message: 'src /nope does not exist' }));
191
- });
192
- it('does not warn when the src path exists', async () => {
193
- const tree = workTree({ tasks: [taskItem('build', { src: [{ absolutePath: '/yes' }] })] });
194
- const result = await collect((0, validate_1.validate)(tree, fakeEnv(['/yes'])));
195
- expect(result.map((r) => r.message)).not.toContain('src /yes does not exist');
196
- });
197
- it('emits a cycle error through the generator', async () => {
198
- const a = taskItem('a', {});
199
- a.deps.push(a);
200
- const tree = workTree({ tasks: [a] });
201
- const result = await collect((0, validate_1.validate)(tree, fakeEnv()));
202
- expect(result).toContainEqual(expect.objectContaining({ type: 'error', message: 'task cycle detected a -> a' }));
203
- });
204
- it('warns about a container service missing a healthcheck', async () => {
205
- const tree = workTree({ services: [serviceItem('db', { healthcheck: null })] });
206
- const result = await collect((0, validate_1.validate)(tree, fakeEnv()));
207
- expect(result).toContainEqual(expect.objectContaining({ type: 'warn', message: 'missing healthcheck' }));
208
- });
209
- it('warns about a container service mount that does not exist', async () => {
210
- const tree = workTree({
211
- services: [serviceItem('db', { mounts: [{ localPath: '/data' }] })],
212
- });
213
- const result = await collect((0, validate_1.validate)(tree, fakeEnv([])));
214
- expect(result).toContainEqual(expect.objectContaining({ type: 'warn', message: 'mount /data does not exist' }));
215
- });
216
- it('warns when a kubernetes service kubeconfig is missing', async () => {
217
- const tree = workTree({
218
- services: [
219
- serviceItem('cluster', {
220
- type: 'kubernetes-service',
221
- kubeconfig: '/missing/kubeconfig',
222
- context: 'prod',
223
- }),
224
- ],
225
- });
226
- const result = await collect((0, validate_1.validate)(tree, fakeEnv([])));
227
- expect(result).toContainEqual(expect.objectContaining({ type: 'warn', message: 'kubeconfig /missing/kubeconfig does not exist' }));
228
- });
229
- });
230
- describe('checkIfContextExists', () => {
231
- function kubeconfigEnv(yaml) {
232
- return {
233
- file: { read: vi.fn(async () => yaml) },
234
- status: { context: () => ({ write: vi.fn() }) },
235
- };
236
- }
237
- const service = { kubeconfig: '/kube/config', context: 'prod' };
238
- it('is true when the context is present', async () => {
239
- const env = kubeconfigEnv('contexts:\n - name: prod\n - name: dev\n');
240
- expect(await (0, validate_1.checkIfContextExists)(service, env)).toBe(true);
241
- });
242
- it('is false when the context is absent', async () => {
243
- const env = kubeconfigEnv('contexts:\n - name: dev\n');
244
- expect(await (0, validate_1.checkIfContextExists)(service, env)).toBe(false);
245
- });
246
- it('is false when there are no contexts', async () => {
247
- const env = kubeconfigEnv('clusters:\n - name: c1\n');
248
- expect(await (0, validate_1.checkIfContextExists)(service, env)).toBe(false);
249
- });
250
- });
251
- //# sourceMappingURL=validate.spec.js.map
@@ -1,173 +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
- async function compareTasks(first, second, taskName, expectEqual) {
7
- const testCase = (0, test_case_1.createTestCase)('work-task-id', {
8
- 'first.yaml': first,
9
- 'second.yaml': second,
10
- });
11
- await testCase.setup(async (cwd, environment) => {
12
- const firstCli = await (0, program_1.createCli)((0, path_1.join)(cwd, 'first.yaml'), environment, { taskName });
13
- const secondCli = await (0, program_1.createCli)((0, path_1.join)(cwd, 'second.yaml'), environment, { taskName });
14
- const firstTask = firstCli.task(taskName);
15
- const secondTask = secondCli.task(taskName);
16
- if (expectEqual) {
17
- expect(firstTask.id()).toEqual(secondTask.id());
18
- }
19
- else {
20
- expect(firstTask.id()).not.toEqual(secondTask.id());
21
- }
22
- });
23
- }
24
- describe('work-cache-id', () => {
25
- it('should be the same for different mount order', async () => {
26
- await compareTasks({
27
- tasks: {
28
- example: {
29
- image: 'node',
30
- mounts: ['$PWD/.npm:/.npm', '$PWD/.config:/.config'],
31
- },
32
- },
33
- }, {
34
- tasks: {
35
- example: {
36
- image: 'node',
37
- mounts: ['$PWD/.config:/.config', '$PWD/.npm:/.npm'],
38
- },
39
- },
40
- }, 'example', true);
41
- });
42
- it('should be the same for different object key order', async () => {
43
- await compareTasks({
44
- tasks: {
45
- example: {
46
- image: 'node:alpine',
47
- cmds: ['npm ci'],
48
- },
49
- },
50
- }, {
51
- tasks: {
52
- example: {
53
- cmds: ['npm ci'],
54
- image: 'node:alpine',
55
- },
56
- },
57
- }, 'example', true);
58
- });
59
- it('should be the same with additional description', async () => {
60
- await compareTasks({
61
- tasks: {
62
- example: {
63
- image: 'node:alpine',
64
- cmds: ['npm ci'],
65
- },
66
- },
67
- }, {
68
- tasks: {
69
- example: {
70
- image: 'node:alpine',
71
- cmds: ['npm ci'],
72
- description: 'should not matter',
73
- },
74
- },
75
- }, 'example', true);
76
- });
77
- it('should be not be the same for different images', async () => {
78
- await compareTasks({
79
- tasks: {
80
- example: {
81
- image: 'node:alpine',
82
- },
83
- },
84
- }, {
85
- tasks: {
86
- example: {
87
- image: 'node',
88
- },
89
- },
90
- }, 'example', false);
91
- });
92
- it('should be not be the same for different cmd order', async () => {
93
- await compareTasks({
94
- tasks: {
95
- example: {
96
- cmds: ['npm ci', 'npm run build'],
97
- },
98
- },
99
- }, {
100
- tasks: {
101
- example: {
102
- cmds: ['npm run build', 'npm ci'],
103
- },
104
- },
105
- }, 'example', false);
106
- });
107
- it('should be be the same for different env key order', async () => {
108
- await compareTasks({
109
- tasks: {
110
- example: {
111
- envs: {
112
- NODE_VERSION: '16.6.0',
113
- NPM_VERSION: '6.0.0',
114
- },
115
- cmds: ['npm ci', 'npm run build'],
116
- },
117
- },
118
- }, {
119
- tasks: {
120
- example: {
121
- envs: {
122
- NPM_VERSION: '6.0.0',
123
- NODE_VERSION: '16.6.0',
124
- },
125
- cmds: ['npm ci', 'npm run build'],
126
- },
127
- },
128
- }, 'example', true);
129
- });
130
- it('should be be the same for deps in different order', async () => {
131
- await compareTasks({
132
- tasks: {
133
- install: {
134
- cmds: ['npm install'],
135
- },
136
- build: {
137
- cmds: ['tsc'],
138
- },
139
- example: {
140
- deps: ['install', 'build'],
141
- },
142
- },
143
- }, {
144
- tasks: {
145
- install: {
146
- cmds: ['npm install'],
147
- },
148
- build: {
149
- cmds: ['tsc'],
150
- },
151
- example: {
152
- deps: ['build', 'install'],
153
- },
154
- },
155
- }, 'example', true);
156
- });
157
- it('should be be the same for sources in different order', async () => {
158
- await compareTasks({
159
- tasks: {
160
- example: {
161
- src: ['package.json', 'package-lock.json'],
162
- },
163
- },
164
- }, {
165
- tasks: {
166
- example: {
167
- src: ['package-lock.json', 'package.json'],
168
- },
169
- },
170
- }, 'example', true);
171
- });
172
- });
173
- //# sourceMappingURL=work-cache-id.spec.js.map