hammerkit 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/README.md +7 -7
  2. package/build.schema.json +59 -15
  3. package/dist/cache/backends/local-cache-backend.js +58 -1
  4. package/dist/cache/backends/registry/registry-client.js +264 -0
  5. package/dist/cache/backends/registry/registry-credentials.js +81 -0
  6. package/dist/cache/backends/registry/registry-reference.js +36 -0
  7. package/dist/cache/backends/registry-cache-backend.js +145 -0
  8. package/dist/cache/backends/s3-cache-backend.js +99 -46
  9. package/dist/cache/cache-backend-registry.js +2 -0
  10. package/dist/cache/cache-inventory.js +84 -0
  11. package/dist/cache/cache-sync.js +77 -0
  12. package/dist/cache/explain.js +214 -0
  13. package/dist/cache/last-resolved.js +48 -0
  14. package/dist/cache/read-only.js +15 -0
  15. package/dist/cache/retention.js +61 -0
  16. package/dist/cli.js +43 -0
  17. package/dist/docker/using-container.js +17 -1
  18. package/dist/executer/archive-task-entry.js +21 -0
  19. package/dist/executer/await-completed-dependencies.js +32 -14
  20. package/dist/executer/cache-metadata.js +9 -2
  21. package/dist/executer/docker-service.js +2 -1
  22. package/dist/executer/docker-task.js +18 -3
  23. package/dist/executer/dry-run.js +73 -0
  24. package/dist/executer/event-cache.js +6 -5
  25. package/dist/executer/execute-command.js +21 -3
  26. package/dist/executer/execute-docker.js +18 -2
  27. package/dist/executer/execute-work-service.js +10 -1
  28. package/dist/executer/execute-work-task.js +111 -26
  29. package/dist/executer/execute-work-tree.js +26 -1
  30. package/dist/executer/execution-steps.js +3 -8
  31. package/dist/executer/get-container-binds.js +13 -1
  32. package/dist/executer/get-scheduler-execute-result.js +4 -1
  33. package/dist/executer/lazy-resolver.js +15 -3
  34. package/dist/executer/reset-work-tree.js +2 -0
  35. package/dist/executer/run-summary.js +102 -0
  36. package/dist/executer/scheduler/enqueue-next.js +15 -60
  37. package/dist/executer/scheduler/state-key.js +78 -0
  38. package/dist/index.js +14 -1
  39. package/dist/kubernetes/apply.js +6 -7
  40. package/dist/kubernetes/await-running-state.js +100 -60
  41. package/dist/kubernetes/ensure-http-route.js +7 -10
  42. package/dist/kubernetes/ensure-namespace.js +9 -10
  43. package/dist/kubernetes/ensure-persistent-data.js +6 -5
  44. package/dist/kubernetes/get-service-ip.js +2 -2
  45. package/dist/kubernetes/kubernetes-instance.js +0 -4
  46. package/dist/kubernetes/remove-persistent-data.js +16 -6
  47. package/dist/kubernetes/resolve-pod-name.js +7 -7
  48. package/dist/log.js +16 -6
  49. package/dist/logging/live-logger.js +2 -1
  50. package/dist/optimizer/calculate-checksum.js +8 -2
  51. package/dist/optimizer/get-work-cache-stats.js +66 -16
  52. package/dist/optimizer/work-task-cache-description.js +34 -5
  53. package/dist/planner/project-root.js +26 -0
  54. package/dist/planner/utils/append-work-environment.js +1 -1
  55. package/dist/planner/utils/append-work-service.js +2 -1
  56. package/dist/planner/utils/append-work-task.js +7 -1
  57. package/dist/planner/utils/get-outputs-to-reset.js +20 -0
  58. package/dist/planner/utils/parse-work-generate.js +2 -2
  59. package/dist/planner/utils/parse-work-source.js +35 -30
  60. package/dist/planner/utils/portable-path.js +30 -0
  61. package/dist/planner/work-cache-id.js +2 -2
  62. package/dist/planner/work-graph-serializer.js +103 -0
  63. package/dist/planner/work-instance-id.js +15 -0
  64. package/dist/planner/work-runtime-docker.js +58 -15
  65. package/dist/planner/work-runtime-kubernetes.js +95 -45
  66. package/dist/planner/work-runtime-local.js +31 -8
  67. package/dist/planner/work-service-id.js +16 -4
  68. package/dist/program.js +235 -1
  69. package/dist/schema/build-file-base-task-schema.js +4 -0
  70. package/dist/schema/build-file-environment-schema-http-route.js +15 -0
  71. package/dist/schema/build-file-environment-schema.js +8 -2
  72. package/dist/schema/build-file-schema.js +1 -1
  73. package/dist/schema/cache-schema.js +21 -2
  74. package/dist/schema/duration-schema.js +18 -0
  75. package/dist/schema/envs-schema.js +1 -1
  76. package/dist/schema/reference-parser.js +3 -0
  77. package/dist/schema/size-schema.js +18 -0
  78. package/dist/schema/work-scope-parser.js +15 -2
  79. package/dist/utils/units.js +87 -0
  80. package/package.json +20 -9
  81. package/.agents/FEEDBACK.md +0 -74
  82. package/.agents/skills/before-commit/SKILL.md +0 -101
  83. package/.agents/skills/do-the-work/SKILL.md +0 -64
  84. package/.agents/skills/fix-ci/SKILL.md +0 -98
  85. package/.agents/skills/speckit-git-commit/SKILL.md +0 -53
  86. package/.agents/skills/speckit-git-feature/SKILL.md +0 -72
  87. package/.agents/skills/speckit-git-initialize/SKILL.md +0 -54
  88. package/.agents/skills/speckit-git-remote/SKILL.md +0 -50
  89. package/.agents/skills/speckit-git-validate/SKILL.md +0 -54
  90. package/.devcontainer/Dockerfile +0 -5
  91. package/.devcontainer/devcontainer.json +0 -40
  92. package/.gitbook.yaml +0 -5
  93. package/.specify/extensions/.registry +0 -37
  94. package/.specify/extensions/git/README.md +0 -100
  95. package/.specify/extensions/git/commands/speckit.git.commit.md +0 -48
  96. package/.specify/extensions/git/commands/speckit.git.feature.md +0 -67
  97. package/.specify/extensions/git/commands/speckit.git.initialize.md +0 -49
  98. package/.specify/extensions/git/commands/speckit.git.remote.md +0 -45
  99. package/.specify/extensions/git/commands/speckit.git.validate.md +0 -49
  100. package/.specify/extensions/git/config-template.yml +0 -62
  101. package/.specify/extensions/git/extension.yml +0 -140
  102. package/.specify/extensions/git/git-config.yml +0 -62
  103. package/.specify/extensions.yml +0 -149
  104. package/.specify/feature.json +0 -3
  105. package/.specify/init-options.json +0 -10
  106. package/.specify/integration.json +0 -15
  107. package/.specify/integrations/claude.manifest.json +0 -16
  108. package/.specify/integrations/speckit.manifest.json +0 -17
  109. package/.specify/memory/constitution.md +0 -162
  110. package/.specify/templates/checklist-template.md +0 -40
  111. package/.specify/templates/constitution-template.md +0 -50
  112. package/.specify/templates/plan-template.md +0 -113
  113. package/.specify/templates/spec-template.md +0 -131
  114. package/.specify/templates/tasks-template.md +0 -252
  115. package/.specify/workflows/speckit/workflow.yml +0 -77
  116. package/.specify/workflows/workflow-registry.json +0 -13
  117. package/AGENTS.md +0 -94
  118. package/Dockerfile +0 -32
  119. package/FOLLOWUPS.md +0 -34
  120. package/SUMMARY.md +0 -68
  121. package/VALUES.md +0 -53
  122. package/dist/cache/auto-pull.spec.js +0 -123
  123. package/dist/cache/backends/local-cache-backend.spec.js +0 -72
  124. package/dist/cache/backends/s3-cache-backend.spec.js +0 -118
  125. package/dist/cache/cache-backend-registry.spec.js +0 -22
  126. package/dist/cache/resolve-cache.spec.js +0 -52
  127. package/dist/cli.spec.js +0 -23
  128. package/dist/console/get-logger.spec.js +0 -23
  129. package/dist/docker/get-default-platform.spec.js +0 -29
  130. package/dist/docker/longest-common-prefix.spec.js +0 -26
  131. package/dist/docker/package.spec.js +0 -145
  132. package/dist/docker/remove-container.spec.js +0 -32
  133. package/dist/docker/using-container.spec.js +0 -90
  134. package/dist/environment/merge-environment-variables.spec.js +0 -24
  135. package/dist/environment/replace-env-variables.spec.js +0 -47
  136. package/dist/executer/abort.spec.js +0 -18
  137. package/dist/executer/environment-mock.spec.js +0 -22
  138. package/dist/executer/execute.spec.js +0 -51
  139. package/dist/executer/get-service-containers.spec.js +0 -59
  140. package/dist/executer/get-service-env-hints.spec.js +0 -65
  141. package/dist/executer/label-values.spec.js +0 -61
  142. package/dist/executer/lazy-resolver.spec.js +0 -20
  143. package/dist/executer/process-manager.spec.js +0 -54
  144. package/dist/executer/scheduler/enqueue-next.spec.js +0 -136
  145. package/dist/executer/state-resolver.spec.js +0 -37
  146. package/dist/executer/state.spec.js +0 -66
  147. package/dist/executer/states.spec.js +0 -14
  148. package/dist/file/get-file-context.spec.js +0 -178
  149. package/dist/file/move-files.spec.js +0 -48
  150. package/dist/kubernetes/ensure-http-route.spec.js +0 -70
  151. package/dist/kubernetes/ensure-ingress.js +0 -54
  152. package/dist/kubernetes/ensure-namespace.spec.js +0 -39
  153. package/dist/kubernetes/resolve-pod-name.spec.js +0 -81
  154. package/dist/log.spec.js +0 -131
  155. package/dist/logging/grouped-logger.spec.js +0 -84
  156. package/dist/logging/interactive-logger.spec.js +0 -57
  157. package/dist/logging/live-logger.spec.js +0 -37
  158. package/dist/logging/loggers.spec.js +0 -154
  159. package/dist/optimizer/calculate-checksum.spec.js +0 -34
  160. package/dist/optimizer/get-cache-directory.spec.js +0 -59
  161. package/dist/optimizer/get-work-cache-stats.spec.js +0 -79
  162. package/dist/optimizer/work-task-cache-description.spec.js +0 -45
  163. package/dist/parser/default-build-file.spec.js +0 -48
  164. package/dist/parser/parse-label-arguments.spec.js +0 -27
  165. package/dist/parser/read-build-file.spec.js +0 -89
  166. package/dist/parser/read-env-file.spec.js +0 -42
  167. package/dist/planner/utils/append-work-service.spec.js +0 -71
  168. package/dist/planner/utils/get-container-user.spec.js +0 -30
  169. package/dist/planner/utils/get-default-kube-config.spec.js +0 -11
  170. package/dist/planner/utils/normalize-path.spec.js +0 -24
  171. package/dist/planner/utils/parse-work-command.spec.js +0 -21
  172. package/dist/planner/utils/parse-work-generate.spec.js +0 -45
  173. package/dist/planner/utils/parse-work-mount.spec.js +0 -62
  174. package/dist/planner/utils/parse-work-mounts.spec.js +0 -19
  175. package/dist/planner/utils/parse-work-port.spec.js +0 -24
  176. package/dist/planner/utils/parse-work-ports.spec.js +0 -24
  177. package/dist/planner/utils/parse-work-source.spec.js +0 -58
  178. package/dist/planner/utils/parse-work-volume.spec.js +0 -18
  179. package/dist/planner/utils/plan-work-tasks.spec.js +0 -30
  180. package/dist/planner/utils/plan-work-volume.spec.js +0 -33
  181. package/dist/planner/utils/split-name.spec.js +0 -16
  182. package/dist/planner/utils/template-value.spec.js +0 -14
  183. package/dist/planner/validate.spec.js +0 -251
  184. package/dist/planner/work-cache-id.spec.js +0 -173
  185. package/dist/planner/work-runtime-local.spec.js +0 -193
  186. package/dist/program-commands.spec.js +0 -84
  187. package/dist/program.spec.js +0 -43
  188. package/dist/schema/build-file-environment-schema-ingress.js +0 -14
  189. package/dist/schema/build-file-schema.spec.js +0 -61
  190. package/dist/schema/doc-build-files.spec.js +0 -223
  191. package/dist/schema/parse-error.spec.js +0 -18
  192. package/dist/schema/reference-parser.spec.js +0 -83
  193. package/dist/schema/schema-parser.spec.js +0 -80
  194. package/dist/schema/type-guards.spec.js +0 -23
  195. package/dist/schema/work-scope-parser.spec.js +0 -66
  196. package/dist/service/update-service-status.spec.js +0 -27
  197. package/dist/testing/ensure-local-registry.js +0 -134
  198. package/dist/testing/example-test-suite.js +0 -63
  199. package/dist/testing/expect.js +0 -82
  200. package/dist/testing/get-test-suite.js +0 -9
  201. package/dist/testing/http-get.js +0 -21
  202. package/dist/testing/integration/cache.spec.js +0 -49
  203. package/dist/testing/integration/cancellation.spec.js +0 -45
  204. package/dist/testing/integration/clean.spec.js +0 -43
  205. package/dist/testing/integration/cmd.spec.js +0 -15
  206. package/dist/testing/integration/concurrency.spec.js +0 -19
  207. package/dist/testing/integration/docker-package-registry.spec.js +0 -44
  208. package/dist/testing/integration/docker-service-crash.spec.js +0 -36
  209. package/dist/testing/integration/docker.spec.js +0 -18
  210. package/dist/testing/integration/env.spec.js +0 -48
  211. package/dist/testing/integration/environment.spec.js +0 -97
  212. package/dist/testing/integration/error.spec.js +0 -19
  213. package/dist/testing/integration/export.spec.js +0 -22
  214. package/dist/testing/integration/extend.spec.js +0 -29
  215. package/dist/testing/integration/glob.spec.js +0 -40
  216. package/dist/testing/integration/include.spec.js +0 -31
  217. package/dist/testing/integration/invalid.spec.js +0 -17
  218. package/dist/testing/integration/invalid_loop.spec.js +0 -56
  219. package/dist/testing/integration/k8s-state-cache.spec.js +0 -41
  220. package/dist/testing/integration/kubernetes-service.spec.js +0 -148
  221. package/dist/testing/integration/kubernetes.spec.js +0 -99
  222. package/dist/testing/integration/labels.spec.js +0 -42
  223. package/dist/testing/integration/local.spec.js +0 -14
  224. package/dist/testing/integration/monorepo.spec.js +0 -15
  225. package/dist/testing/integration/multi-line.spec.js +0 -17
  226. package/dist/testing/integration/reference.spec.js +0 -21
  227. package/dist/testing/integration/services.spec.js +0 -38
  228. package/dist/testing/integration/store.spec.js +0 -52
  229. package/dist/testing/integration/unknown.spec.js +0 -28
  230. package/dist/testing/integration/validate.spec.js +0 -160
  231. package/dist/testing/integration/watch-dependency.spec.js +0 -33
  232. package/dist/testing/integration/watch.spec.js +0 -45
  233. package/dist/testing/requires-kubernetes.js +0 -18
  234. package/dist/testing/requires-linux-containers.js +0 -18
  235. package/dist/testing/requires-windows-containers.js +0 -18
  236. package/dist/testing/test-case.js +0 -75
  237. package/dist/testing/test-streams.js +0 -28
  238. package/dist/testing/test-suite-setup.js +0 -3
  239. package/dist/testing/test-suite.js +0 -3
  240. package/dist/testing/testing-timeout.js +0 -32
  241. package/dist/utils/abort-event.spec.js +0 -46
  242. package/dist/utils/buffer-context.spec.js +0 -38
  243. package/dist/utils/ci.spec.js +0 -53
  244. package/dist/utils/debouncer.spec.js +0 -58
  245. package/dist/utils/emitter.spec.js +0 -34
  246. package/dist/utils/empty-writable.spec.js +0 -18
  247. package/dist/utils/environment-config.spec.js +0 -26
  248. package/dist/utils/fail-never.spec.js +0 -9
  249. package/dist/utils/sleep.spec.js +0 -22
  250. package/dist/version.spec.js +0 -12
  251. package/specs/build-file/caches/spec.md +0 -114
  252. package/specs/build-file/environment-variables/spec.md +0 -82
  253. package/specs/build-file/includes/spec.md +0 -60
  254. package/specs/build-file/references/spec.md +0 -56
  255. package/specs/build-file/spec.md +0 -62
  256. package/specs/cli/clean/spec.md +0 -85
  257. package/specs/cli/down/spec.md +0 -85
  258. package/specs/cli/execute/spec.md +0 -101
  259. package/specs/cli/init/spec.md +0 -64
  260. package/specs/cli/ls/spec.md +0 -66
  261. package/specs/cli/package/spec.md +0 -110
  262. package/specs/cli/spec.md +0 -73
  263. package/specs/cli/store-restore/spec.md +0 -74
  264. package/specs/cli/up/spec.md +0 -80
  265. package/specs/cli/validate/spec.md +0 -70
  266. package/specs/labels/spec.md +0 -91
  267. package/specs/service/container/spec.md +0 -120
  268. package/specs/service/kubernetes/spec.md +0 -97
  269. package/specs/service/spec.md +0 -105
  270. package/specs/task/caching/spec.md +0 -107
  271. package/specs/task/container/spec.md +0 -118
  272. package/specs/task/dependencies/spec.md +0 -70
  273. package/specs/task/extending/spec.md +0 -74
  274. package/specs/task/kubernetes/spec.md +0 -125
  275. package/specs/task/needs/spec.md +0 -59
  276. package/specs/task/spec.md +0 -87
  277. package/specs/task/watching/spec.md +0 -69
@@ -1,84 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const test_case_1 = require("./testing/test-case");
4
- // Fast unit-suite coverage for program.ts: every command that does not actually
5
- // run a container (ls, validate, clean --cache, init, --file, unknown task).
6
- // The slow container-running cases (run, package, up/down) live in
7
- // `program.spec.ts` and run in the integration suite.
8
- //
9
- // shell() forwards args to commander via `[env.cwd, ...args]`, and commander's
10
- // default `from: 'node'` consumes the first two slots as node+script. So args
11
- // must start with a script-name placeholder (matching how production receives
12
- // process.argv); the real command starts at index 1.
13
- const HK = 'hammerkit';
14
- describe('program commands (fast)', () => {
15
- it('ls lists tasks and services without running them', async () => {
16
- const t = (0, test_case_1.createTestCase)('cmd-ls', {
17
- '.hammerkit.yaml': {
18
- services: { api: { image: 'nginx', ports: [] } },
19
- tasks: {
20
- build: { cmds: ['true'], description: 'build it', labels: { tier: 'backend' } },
21
- },
22
- },
23
- });
24
- await t.shell([HK, 'ls']);
25
- });
26
- it('ls --filter narrows by label', async () => {
27
- const t = (0, test_case_1.createTestCase)('cmd-ls-filter', {
28
- '.hammerkit.yaml': {
29
- tasks: {
30
- build: { cmds: ['true'], labels: { app: 'web' } },
31
- lint: { cmds: ['true'], labels: { app: 'tool' } },
32
- },
33
- },
34
- });
35
- await t.shell([HK, 'ls', '--filter', 'app=web']);
36
- });
37
- it('validate returns cleanly for a healthy build file', async () => {
38
- const t = (0, test_case_1.createTestCase)('cmd-validate-ok', {
39
- '.hammerkit.yaml': {
40
- tasks: { build: { description: 'build it', cmds: ['true'] } },
41
- },
42
- });
43
- await t.shell([HK, 'validate']);
44
- });
45
- it('validate exits non-zero when a cycle is detected', async () => {
46
- const t = (0, test_case_1.createTestCase)('cmd-validate-cycle', {
47
- '.hammerkit.yaml': {
48
- tasks: {
49
- a: { description: 'a', cmds: ['true'], deps: ['b'] },
50
- b: { description: 'b', cmds: ['true'], deps: ['a'] },
51
- },
52
- },
53
- });
54
- await expect(t.shell([HK, 'validate'])).rejects.toThrow('Detected errors');
55
- });
56
- it('clean --cache clears the backend cache (no container ops)', async () => {
57
- const t = (0, test_case_1.createTestCase)('cmd-clean-cache', {
58
- '.hammerkit.yaml': {
59
- tasks: { build: { cmds: ['true'] } },
60
- },
61
- });
62
- await t.shell([HK, 'clean', '--cache']);
63
- });
64
- it('init writes a default .hammerkit.yaml when none exists', async () => {
65
- // empty fixture so getBuildFilename finds nothing; only the `init` command is registered then
66
- const t = (0, test_case_1.createTestCase)('cmd-init', {});
67
- await t.shell([HK, 'init']);
68
- });
69
- it('errors when the requested task does not exist', async () => {
70
- const t = (0, test_case_1.createTestCase)('cmd-unknown-task', {
71
- '.hammerkit.yaml': { tasks: { example: { cmds: ['true'] } } },
72
- });
73
- await expect(t.shell([HK, 'run', 'no-such-task'])).rejects.toThrow('No tasks found');
74
- });
75
- it('--file selects a non-default build file', async () => {
76
- const t = (0, test_case_1.createTestCase)('cmd-file-arg', {
77
- 'custom.yaml': {
78
- tasks: { only: { description: 'only', cmds: ['true'] } },
79
- },
80
- });
81
- await t.shell([HK, '--file', 'custom.yaml', 'ls']);
82
- });
83
- });
84
- //# sourceMappingURL=program-commands.spec.js.map
@@ -1,43 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const test_case_1 = require("./testing/test-case");
4
- describe('program', () => {
5
- it('should run when cache is up to date and --no-cache arg is passed', async () => {
6
- const testCase = (0, test_case_1.createTestCase)('no-cache', {
7
- 'package.json': '{ "dependencies": { "hammerkit": "^1.5.0" } }',
8
- '.hammerkit.yaml': {
9
- tasks: {
10
- example: {
11
- description: 'install npm packages',
12
- image: 'node:16.6.0-alpine',
13
- // On linux hammerkit runs the container as the host uid:gid (see
14
- // get-container-user) so generated files aren't root-owned. That uid
15
- // has no home dir in the image, so npm falls back to the unwritable
16
- // /.npm and fails with EACCES. Point HOME at a writable dir — what a
17
- // real non-root user would do — so the task works regardless of uid
18
- // (it only "passed" elsewhere because those ran the container as root).
19
- envs: { HOME: '/tmp' },
20
- src: ['package.json'],
21
- generates: ['node_modules'],
22
- cmds: ['npm install'],
23
- },
24
- },
25
- },
26
- });
27
- await testCase.shell(['run', 'example', '--cache', 'none']);
28
- }, 120000);
29
- it('should warn about invalid task name', async () => {
30
- const testCase = (0, test_case_1.createTestCase)('wrong-name', {
31
- '.hammerkit.yaml': {
32
- tasks: {
33
- example: {
34
- image: 'node:16.6.0-alpine',
35
- cmds: ['npm ci'],
36
- },
37
- },
38
- },
39
- });
40
- await expect(testCase.shell(['run', 'example2'])).rejects.toThrow('No tasks found');
41
- });
42
- });
43
- //# sourceMappingURL=program.spec.js.map
@@ -1,14 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildFileEnvironmentSchemaIngress = void 0;
4
- const zod_1 = require("zod");
5
- exports.buildFileEnvironmentSchemaIngress = (0, zod_1.object)({
6
- kind: zod_1.z.enum(['ingress', 'httproute']).optional(),
7
- host: (0, zod_1.string)(),
8
- service: (0, zod_1.string)(),
9
- servicePort: (0, zod_1.number)().optional(),
10
- path: (0, zod_1.string)().optional(),
11
- gateway: (0, zod_1.string)().optional(),
12
- gatewayNamespace: (0, zod_1.string)().optional(),
13
- }).strict();
14
- //# sourceMappingURL=build-file-environment-schema-ingress.js.map
@@ -1,61 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const build_file_schema_1 = require("./build-file-schema");
4
- function parse(input) {
5
- return build_file_schema_1.buildFileSchema.safeParseAsync(input);
6
- }
7
- describe('buildFileSchema', () => {
8
- describe('container services', () => {
9
- it('accepts a service without ports (reachable via needs/DNS)', async () => {
10
- const result = await parse({
11
- services: { postgres: { image: 'postgres:12-alpine', healthcheck: { cmd: 'pg_isready -U postgres' } } },
12
- });
13
- expect(result.success).toBe(true);
14
- });
15
- it('accepts a service with numeric and string ports', async () => {
16
- const result = await parse({ services: { api: { image: 'node:alpine', ports: [':3000', 8080] } } });
17
- expect(result.success).toBe(true);
18
- });
19
- it('rejects a service that is neither a container nor a kubernetes service', async () => {
20
- const result = await parse({ services: { broken: { ports: [80] } } });
21
- expect(result.success).toBe(false);
22
- });
23
- });
24
- describe('kubernetes services', () => {
25
- it('accepts a service with a selector', async () => {
26
- const result = await parse({
27
- services: { db: { selector: { type: 'deployment', name: 'db' }, ports: [5432] } },
28
- });
29
- expect(result.success).toBe(true);
30
- });
31
- });
32
- describe('continuous tasks', () => {
33
- it('accepts a container task marked continuous', async () => {
34
- const result = await parse({ tasks: { serve: { image: 'node:16', continuous: true, cmds: ['ng serve'] } } });
35
- expect(result.success).toBe(true);
36
- });
37
- it('accepts a local task marked continuous', async () => {
38
- const result = await parse({ tasks: { serve: { continuous: true, cmds: ['ng serve'] } } });
39
- expect(result.success).toBe(true);
40
- });
41
- it('rejects a non-boolean continuous', async () => {
42
- const result = await parse({ tasks: { serve: { continuous: 'yes', cmds: ['x'] } } });
43
- expect(result.success).toBe(false);
44
- });
45
- });
46
- describe('strictness', () => {
47
- it('rejects unknown top-level keys', async () => {
48
- const result = await parse({ bogus: true });
49
- expect(result.success).toBe(false);
50
- });
51
- it('rejects unknown task keys', async () => {
52
- const result = await parse({ tasks: { build: { cmds: ['x'], bogus: 1 } } });
53
- expect(result.success).toBe(false);
54
- });
55
- it('accepts an empty build file', async () => {
56
- const result = await parse({});
57
- expect(result.success).toBe(true);
58
- });
59
- });
60
- });
61
- //# sourceMappingURL=build-file-schema.spec.js.map
@@ -1,223 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fs_1 = require("fs");
4
- const path_1 = require("path");
5
- const yaml_1 = require("yaml");
6
- const build_file_schema_1 = require("./build-file-schema");
7
- /*
8
- * Documentation coverage: every hammerkit build-file example that ships in the
9
- * docs must stay valid against the real schema, so the docs cannot silently
10
- * drift to config the tool would reject.
11
- *
12
- * The harness is self-discovering: it walks docs/ at collection time and turns
13
- * every fenced ```yaml block into its own assertion. A block is treated as a
14
- * hammerkit build file when it parses to an object whose top-level keys are all
15
- * part of the schema (envs/tasks/services/...). Anything else (a GitLab/GitHub
16
- * CI snippet, a design-doc proposal) is "foreign" and must be listed in
17
- * FOREIGN_BLOCKS with a reason — a foreign block that is NOT listed fails the
18
- * audit, so a new build-file example is validated rather than quietly ignored,
19
- * and a typo'd example (unknown key) is surfaced instead of skipped.
20
- */
21
- const repoRoot = (0, path_1.join)(__dirname, '..', '..');
22
- const docsDir = (0, path_1.join)(repoRoot, 'docs');
23
- const bestPracticesDir = (0, path_1.join)(repoRoot, 'best-practices');
24
- // Top-level keys the build-file schema understands. Mirrors buildFileSchema's
25
- // shape; kept here only to classify "is this block a hammerkit build file at all".
26
- const KNOWN_TOP_KEYS = new Set([
27
- 'envs',
28
- 'tasks',
29
- 'services',
30
- 'references',
31
- 'includes',
32
- 'environments',
33
- 'caches',
34
- 'labels',
35
- ]);
36
- // A doc block can declare itself intentionally invalid by putting this comment
37
- // on its first line; the harness then asserts the schema REJECTS it.
38
- const EXPECT_INVALID = '# hammerkit:expect-invalid';
39
- // Directories that document a specific past version, not the current build-file
40
- // reference. Their snippets are expected to use the syntax of their release and
41
- // must not be validated against today's schema.
42
- const HISTORICAL_DIRS = ['release-blog', 'change-log'];
43
- // yaml blocks in docs/ that are deliberately NOT hammerkit build files. Each is
44
- // matched by a unique snippet of its body. Keep the reason — these are the only
45
- // blocks allowed to be unrecognized.
46
- const FOREIGN_BLOCKS = [
47
- {
48
- file: (0, path_1.join)('installation.md'),
49
- snippet: 'DOCKER_DRIVER',
50
- reason: 'GitLab CI (.gitlab-ci.yml) example, not a hammerkit build file',
51
- },
52
- {
53
- file: (0, path_1.join)('installation.md'),
54
- snippet: 'no0dles/hammerkit-github-action',
55
- reason: 'GitHub Actions workflow example, not a hammerkit build file',
56
- },
57
- {
58
- file: (0, path_1.join)('cli', 'store-restore.md'),
59
- snippet: 'before_script',
60
- reason: 'GitLab CI (gitlab-ci.yml) caching example, not a hammerkit build file',
61
- },
62
- {
63
- file: (0, path_1.join)('guides', 'ci-caching.md'),
64
- snippet: 'actions/cache@v4',
65
- reason: 'GitHub Actions workflow example, not a hammerkit build file',
66
- },
67
- {
68
- file: (0, path_1.join)('contribution', 'secret-managers.md'),
69
- snippet: 'secret://vault',
70
- reason: 'design-doc proposal for an unimplemented secrets: provider; not valid against the current schema',
71
- },
72
- ];
73
- function walkMarkdown(dir) {
74
- const out = [];
75
- for (const entry of (0, fs_1.readdirSync)(dir, { withFileTypes: true })) {
76
- const full = (0, path_1.join)(dir, entry.name);
77
- if (entry.isDirectory()) {
78
- out.push(...walkMarkdown(full));
79
- }
80
- else if (entry.name.endsWith('.md')) {
81
- out.push(full);
82
- }
83
- }
84
- return out;
85
- }
86
- // Extract the body of every fenced ```yaml / ```yml block. GitBook's
87
- // `{% code %}` wrappers and `{% hint %}`/`{% tabs %}` around a block are
88
- // irrelevant — only the fence matters.
89
- function extractYamlBlocks(content) {
90
- const lines = content.split(/\r?\n/);
91
- const blocks = [];
92
- for (let i = 0; i < lines.length; i++) {
93
- if (!/^\s*```ya?ml\s*$/.test(lines[i])) {
94
- continue;
95
- }
96
- const start = i + 1;
97
- const body = [];
98
- let j = start;
99
- while (j < lines.length && !/^\s*```\s*$/.test(lines[j])) {
100
- body.push(lines[j]);
101
- j++;
102
- }
103
- blocks.push({ body: body.join('\n'), startLine: start + 1 });
104
- i = j;
105
- }
106
- return blocks;
107
- }
108
- function firstNonEmptyLine(body) {
109
- for (const line of body.split(/\r?\n/)) {
110
- if (line.trim() !== '') {
111
- return line.trim();
112
- }
113
- }
114
- return '';
115
- }
116
- // A block is a hammerkit build file when it parses to a non-empty object whose
117
- // top-level keys are all part of the schema.
118
- function isBuildFileShaped(parsed) {
119
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
120
- return false;
121
- }
122
- const keys = Object.keys(parsed);
123
- return keys.length > 0 && keys.every((k) => KNOWN_TOP_KEYS.has(k));
124
- }
125
- const docFiles = walkMarkdown(docsDir).sort();
126
- const allBlocks = [];
127
- for (const file of docFiles) {
128
- const relFile = (0, path_1.relative)(docsDir, file);
129
- const topDir = relFile.split(/[\\/]/)[0];
130
- if (HISTORICAL_DIRS.includes(topDir)) {
131
- continue;
132
- }
133
- extractYamlBlocks((0, fs_1.readFileSync)(file, 'utf8')).forEach((block, index) => {
134
- allBlocks.push({ ...block, relFile, index });
135
- });
136
- }
137
- const foreignMatches = new Set();
138
- let buildFileBlockCount = 0;
139
- describe('docs build-file examples', () => {
140
- // Group assertions per doc file for readable failures.
141
- const byFile = new Map();
142
- for (const block of allBlocks) {
143
- const list = byFile.get(block.relFile) ?? [];
144
- list.push(block);
145
- byFile.set(block.relFile, list);
146
- }
147
- for (const [relFile, blocks] of byFile) {
148
- describe(relFile, () => {
149
- blocks.forEach((block) => {
150
- const label = `yaml block at line ${block.startLine}`;
151
- const foreignIndex = FOREIGN_BLOCKS.findIndex((f) => f.file === relFile && block.body.includes(f.snippet));
152
- if (foreignIndex >= 0) {
153
- foreignMatches.add(foreignIndex);
154
- it(`${label} is a documented non-hammerkit block`, () => {
155
- // Listed in FOREIGN_BLOCKS with a reason — nothing to validate.
156
- expect(FOREIGN_BLOCKS[foreignIndex].reason).toBeTruthy();
157
- });
158
- return;
159
- }
160
- if (firstNonEmptyLine(block.body) === EXPECT_INVALID) {
161
- it(`${label} is rejected by the schema (marked expect-invalid)`, async () => {
162
- const parsed = (0, yaml_1.parse)(block.body);
163
- const result = await build_file_schema_1.buildFileSchema.safeParseAsync(parsed);
164
- expect(result.success).toBe(false);
165
- });
166
- buildFileBlockCount++;
167
- return;
168
- }
169
- it(`${label} is a valid build file`, async () => {
170
- let parsed;
171
- try {
172
- parsed = (0, yaml_1.parse)(block.body);
173
- }
174
- catch (e) {
175
- throw new Error(`${relFile}:${block.startLine} is not valid YAML: ${e.message}`);
176
- }
177
- if (!isBuildFileShaped(parsed)) {
178
- const keys = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
179
- ? Object.keys(parsed).join(', ')
180
- : typeof parsed;
181
- throw new Error(`${relFile}:${block.startLine} is not a recognized hammerkit build file (top-level: ${keys}).\n` +
182
- `If this is intentionally foreign yaml (a CI config, a proposal), add it to FOREIGN_BLOCKS with a reason.\n` +
183
- `Otherwise fix the example so its top-level keys are one of: ${[...KNOWN_TOP_KEYS].join(', ')}.`);
184
- }
185
- const result = await build_file_schema_1.buildFileSchema.safeParseAsync(parsed);
186
- if (!result.success) {
187
- throw new Error(`${relFile}:${block.startLine} fails the build-file schema:\n` +
188
- result.error.issues.map((iss) => ` - ${iss.path.join('.')}: ${iss.message}`).join('\n'));
189
- }
190
- });
191
- buildFileBlockCount++;
192
- });
193
- });
194
- }
195
- // Guard against a regex/discovery regression silently matching nothing, and
196
- // against a build-file example dropping out of validation unnoticed.
197
- it('discovers the documented build-file examples', () => {
198
- expect(buildFileBlockCount).toBeGreaterThanOrEqual(60);
199
- });
200
- // Every documented exclusion must still match a real block, so stale entries
201
- // are removed when the doc changes.
202
- it('has no stale FOREIGN_BLOCKS entries', () => {
203
- const unmatched = FOREIGN_BLOCKS.filter((_, i) => !foreignMatches.has(i)).map((f) => `${f.file} (${f.snippet})`);
204
- expect(unmatched).toEqual([]);
205
- });
206
- });
207
- describe('best-practices build configs', () => {
208
- const files = (0, fs_1.readdirSync)(bestPracticesDir)
209
- .filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'))
210
- .sort();
211
- it('finds the best-practices templates', () => {
212
- expect(files.length).toBeGreaterThanOrEqual(10);
213
- });
214
- it.each(files)('%s is a valid build file', async (name) => {
215
- const parsed = (0, yaml_1.parse)((0, fs_1.readFileSync)((0, path_1.join)(bestPracticesDir, name), 'utf8'));
216
- const result = await build_file_schema_1.buildFileSchema.safeParseAsync(parsed);
217
- if (!result.success) {
218
- throw new Error(`best-practices/${name} fails the build-file schema:\n` +
219
- result.error.issues.map((iss) => ` - ${iss.path.join('.')}: ${iss.message}`).join('\n'));
220
- }
221
- });
222
- });
223
- //# sourceMappingURL=doc-build-files.spec.js.map
@@ -1,18 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const zod_1 = require("zod");
4
- const parse_error_1 = require("./parse-error");
5
- describe('ParseError', () => {
6
- it('wraps a ZodError with the offending build-file path', () => {
7
- const result = zod_1.z.object({ name: zod_1.z.string() }).safeParse({});
8
- if (result.success) {
9
- throw new Error('precondition: expected the safeParse to fail');
10
- }
11
- const err = new parse_error_1.ParseError(result.error, '/work/.hammerkit.yaml');
12
- expect(err).toBeInstanceOf(Error);
13
- expect(err.zod).toBe(result.error);
14
- expect(err.buildFilePath).toBe('/work/.hammerkit.yaml');
15
- expect(err.message).toBe('parse error');
16
- });
17
- });
18
- //# sourceMappingURL=parse-error.spec.js.map
@@ -1,83 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const test_case_1 = require("../testing/test-case");
4
- const program_1 = require("../program");
5
- const path_1 = require("path");
6
- // Drive the full parse pipeline (schema-parser -> parseReferences -> getWorkContext)
7
- // over an in-memory build file and assert on the resulting work tree. This exercises
8
- // reference resolution: deps, needs, label merging and schema `extend`.
9
- async function withCli(buildFile, scope, fn) {
10
- const testCase = (0, test_case_1.createTestCase)('reference-parser', { '.hammerkit.yaml': buildFile });
11
- await testCase.setup(async (cwd, environment) => {
12
- const cli = await (0, program_1.createCli)((0, path_1.join)(cwd, '.hammerkit.yaml'), environment, scope);
13
- await fn(cli);
14
- });
15
- }
16
- describe('reference-parser', () => {
17
- it('resolves task dependencies', async () => {
18
- await withCli({
19
- tasks: {
20
- install: { cmds: ['npm install'] },
21
- build: { cmds: ['tsc'], deps: ['install'] },
22
- },
23
- }, { taskName: 'build' }, (cli) => {
24
- const build = cli.task('build');
25
- expect(build.deps.map((d) => d.name)).toEqual(['install']);
26
- });
27
- });
28
- it('resolves service needs', async () => {
29
- await withCli({
30
- services: {
31
- api: { image: 'nginx', ports: [] },
32
- },
33
- tasks: {
34
- build: { cmds: ['tsc'], needs: ['api'] },
35
- },
36
- }, { taskName: 'build' }, (cli) => {
37
- const build = cli.task('build');
38
- expect(build.needs.map((n) => n.service.name)).toEqual(['api']);
39
- });
40
- });
41
- it('merges build-file labels with task labels', async () => {
42
- await withCli({
43
- labels: { app: 'web' },
44
- tasks: {
45
- build: { cmds: ['tsc'], labels: { tier: 'backend' } },
46
- },
47
- }, { taskName: 'build' }, (cli) => {
48
- expect(cli.task('build').data.labels).toEqual({ app: ['web'], tier: ['backend'] });
49
- });
50
- });
51
- it('applies schema extend (image, cmds, labels)', async () => {
52
- await withCli({
53
- tasks: {
54
- base: { image: 'node', cmds: ['a'] },
55
- child: { extend: 'base', cmds: ['b'] },
56
- },
57
- }, { taskName: 'child' }, (cli) => {
58
- const child = cli.task('child').data;
59
- // child inherits the base image and is therefore a container task
60
- expect(child.type).toBe('container-task');
61
- expect(child.image).toBe('node');
62
- // extendArray prepends the extended task's cmds before the child's own
63
- expect(child.cmds).toHaveLength(2);
64
- });
65
- });
66
- it('throws when a dependency cannot be found', async () => {
67
- const testCase = (0, test_case_1.createTestCase)('reference-parser', {
68
- '.hammerkit.yaml': { tasks: { build: { cmds: ['tsc'], deps: ['missing'] } } },
69
- });
70
- await testCase.setup(async (cwd, environment) => {
71
- await expect((0, program_1.createCli)((0, path_1.join)(cwd, '.hammerkit.yaml'), environment, { taskName: 'build' })).rejects.toThrow('unable to find missing');
72
- });
73
- });
74
- it('throws when a needed service cannot be found', async () => {
75
- const testCase = (0, test_case_1.createTestCase)('reference-parser', {
76
- '.hammerkit.yaml': { tasks: { build: { cmds: ['tsc'], needs: ['missingsvc'] } } },
77
- });
78
- await testCase.setup(async (cwd, environment) => {
79
- await expect((0, program_1.createCli)((0, path_1.join)(cwd, '.hammerkit.yaml'), environment, { taskName: 'build' })).rejects.toThrow('unable to find missingsvc');
80
- });
81
- });
82
- });
83
- //# sourceMappingURL=reference-parser.spec.js.map
@@ -1,80 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const path_1 = require("path");
4
- const os_1 = require("os");
5
- const fs_1 = require("fs");
6
- const environment_mock_1 = require("../executer/environment-mock");
7
- const schema_parser_1 = require("./schema-parser");
8
- const parse_error_1 = require("./parse-error");
9
- describe('createParseContext', () => {
10
- let scratch;
11
- beforeEach(() => {
12
- scratch = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'hammerkit-schema-'));
13
- });
14
- afterEach(() => {
15
- (0, fs_1.rmSync)(scratch, { recursive: true, force: true });
16
- });
17
- function writeBuildFile(content) {
18
- const file = (0, path_1.join)(scratch, '.hammerkit.yaml');
19
- (0, fs_1.writeFileSync)(file, content);
20
- return file;
21
- }
22
- it('parses a valid build file', async () => {
23
- const env = (0, environment_mock_1.environmentMock)(scratch);
24
- const file = writeBuildFile('tasks:\n build:\n cmds:\n - echo hi\n');
25
- const { scope } = await (0, schema_parser_1.createParseContext)(file, env);
26
- expect(scope.schema.tasks?.build).toBeDefined();
27
- });
28
- it('rejects an unknown top-level key (strict schema)', async () => {
29
- const env = (0, environment_mock_1.environmentMock)(scratch);
30
- const file = writeBuildFile('bogus: true\ntasks:\n build:\n cmds:\n - echo hi\n');
31
- await expect((0, schema_parser_1.createParseContext)(file, env)).rejects.toBeInstanceOf(parse_error_1.ParseError);
32
- });
33
- it('rejects a task with an invalid command shape', async () => {
34
- const env = (0, environment_mock_1.environmentMock)(scratch);
35
- const file = writeBuildFile('tasks:\n build:\n cmds: 42\n');
36
- await expect((0, schema_parser_1.createParseContext)(file, env)).rejects.toBeInstanceOf(parse_error_1.ParseError);
37
- });
38
- it('resolves a reference to another build file', async () => {
39
- const env = (0, environment_mock_1.environmentMock)(scratch);
40
- const sub = (0, path_1.join)(scratch, 'sub');
41
- (0, fs_1.writeFileSync)((0, path_1.join)(scratch, '.hammerkit.yaml'), 'references:\n lib: sub\ntasks:\n build:\n cmds:\n - echo root\n');
42
- // create the referenced build file
43
- (0, fs_1.mkdirSync)(sub);
44
- (0, fs_1.writeFileSync)((0, path_1.join)(sub, '.hammerkit.yaml'), 'tasks:\n lib-build:\n cmds:\n - echo lib\n');
45
- const { scope } = await (0, schema_parser_1.createParseContext)((0, path_1.join)(scratch, '.hammerkit.yaml'), env);
46
- expect(scope.references['lib']).toBeDefined();
47
- expect(scope.references['lib'].scope.schema.tasks?.['lib-build']).toBeDefined();
48
- });
49
- it('resolves an include (sibling build file merged under a name)', async () => {
50
- const env = (0, environment_mock_1.environmentMock)(scratch);
51
- (0, fs_1.writeFileSync)((0, path_1.join)(scratch, 'common.yaml'), 'tasks:\n shared:\n cmds:\n - echo s\n');
52
- (0, fs_1.writeFileSync)((0, path_1.join)(scratch, '.hammerkit.yaml'), 'includes:\n npm: common.yaml\n');
53
- const { scope } = await (0, schema_parser_1.createParseContext)((0, path_1.join)(scratch, '.hammerkit.yaml'), env);
54
- expect(scope.references['npm']).toBeDefined();
55
- expect(scope.references['npm'].type).toBe('include');
56
- expect(scope.references['npm'].scope.schema.tasks?.['shared']).toBeDefined();
57
- });
58
- it('throws when an include and a reference share the same name', async () => {
59
- const env = (0, environment_mock_1.environmentMock)(scratch);
60
- const sub = (0, path_1.join)(scratch, 'sub');
61
- (0, fs_1.mkdirSync)(sub);
62
- (0, fs_1.writeFileSync)((0, path_1.join)(sub, '.hammerkit.yaml'), 'tasks:\n x:\n cmds:\n - echo x\n');
63
- (0, fs_1.writeFileSync)((0, path_1.join)(scratch, 'inc.yaml'), 'tasks:\n y:\n cmds:\n - echo y\n');
64
- (0, fs_1.writeFileSync)((0, path_1.join)(scratch, '.hammerkit.yaml'), 'references:\n lib: sub\nincludes:\n lib: inc.yaml\n');
65
- await expect((0, schema_parser_1.createParseContext)((0, path_1.join)(scratch, '.hammerkit.yaml'), env)).rejects.toThrow(/lib already exists/);
66
- });
67
- it('reuses an already-parsed build file when reached again (cache hit)', async () => {
68
- const env = (0, environment_mock_1.environmentMock)(scratch);
69
- const sub = (0, path_1.join)(scratch, 'sub');
70
- (0, fs_1.mkdirSync)(sub);
71
- (0, fs_1.writeFileSync)((0, path_1.join)(sub, '.hammerkit.yaml'), 'tasks:\n s:\n cmds:\n - echo s\n');
72
- (0, fs_1.writeFileSync)((0, path_1.join)(scratch, '.hammerkit.yaml'), 'references:\n a: sub\n b: sub\n');
73
- const { ctx, scope } = await (0, schema_parser_1.createParseContext)((0, path_1.join)(scratch, '.hammerkit.yaml'), env);
74
- // Both references point at sub/.hammerkit.yaml and must end up at the same
75
- // parsed ParseScope (the second appendBuildFile hits the ctx.files cache).
76
- expect(scope.references['a'].scope).toBe(scope.references['b'].scope);
77
- expect(Object.keys(ctx.files)).toHaveLength(2); // root + sub (sub cached on second visit)
78
- });
79
- });
80
- //# sourceMappingURL=schema-parser.spec.js.map
@@ -1,23 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const build_file_service_schema_1 = require("./build-file-service-schema");
4
- const build_file_task_schema_1 = require("./build-file-task-schema");
5
- describe('service/task type guards', () => {
6
- it('isBuildFileContainerSchema discriminates by `image` presence', () => {
7
- expect((0, build_file_service_schema_1.isBuildFileContainerSchema)({ image: 'node' })).toBe(true);
8
- expect((0, build_file_service_schema_1.isBuildFileContainerSchema)({ selector: { name: 'n', type: 't' } })).toBe(false);
9
- });
10
- it('isBuildFileKubernetesServiceSchema discriminates by `selector` presence', () => {
11
- expect((0, build_file_service_schema_1.isBuildFileKubernetesServiceSchema)({ selector: { name: 'n', type: 't' } })).toBe(true);
12
- expect((0, build_file_service_schema_1.isBuildFileKubernetesServiceSchema)({ image: 'node' })).toBe(false);
13
- });
14
- it('isBuildFileContainerTaskSchema returns true iff the task has an image', () => {
15
- expect((0, build_file_task_schema_1.isBuildFileContainerTaskSchema)({ image: 'node' })).toBe(true);
16
- expect((0, build_file_task_schema_1.isBuildFileContainerTaskSchema)({ cmds: ['x'] })).toBe(false);
17
- });
18
- it('isBuildFileLocalTaskSchema returns true iff the task has no image', () => {
19
- expect((0, build_file_task_schema_1.isBuildFileLocalTaskSchema)({ cmds: ['x'] })).toBe(true);
20
- expect((0, build_file_task_schema_1.isBuildFileLocalTaskSchema)({ image: 'node' })).toBe(false);
21
- });
22
- });
23
- //# sourceMappingURL=type-guards.spec.js.map