hammerkit 1.7.0 → 1.9.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 (53) hide show
  1. package/README.md +3 -3
  2. package/build.schema.json +19 -13
  3. package/dist/docker/package.js +13 -3
  4. package/dist/docker/remove-orphaned-containers.js +53 -0
  5. package/dist/docker/run-labels.js +14 -0
  6. package/dist/docker/using-container.js +25 -26
  7. package/dist/executer/check-readiness.js +4 -4
  8. package/dist/executer/container-task-state.js +34 -0
  9. package/dist/executer/docker-service.js +23 -6
  10. package/dist/executer/docker-task.js +26 -4
  11. package/dist/executer/event-cache.js +8 -0
  12. package/dist/executer/execute-command.js +36 -6
  13. package/dist/executer/execute-docker.js +18 -2
  14. package/dist/executer/execute-work-service.js +4 -0
  15. package/dist/executer/execute-work-task.js +17 -2
  16. package/dist/executer/execution-steps.js +3 -8
  17. package/dist/executer/get-container-binds.js +13 -1
  18. package/dist/executer/main-process-user.js +52 -0
  19. package/dist/index.js +15 -3
  20. package/dist/kubernetes/apply.js +6 -7
  21. package/dist/kubernetes/await-running-state.js +48 -44
  22. package/dist/kubernetes/ensure-http-route.js +7 -10
  23. package/dist/kubernetes/ensure-kubernetes-deployment-exists.js +27 -6
  24. package/dist/kubernetes/ensure-namespace.js +9 -10
  25. package/dist/kubernetes/ensure-persistent-data.js +6 -5
  26. package/dist/kubernetes/get-service-ip.js +2 -2
  27. package/dist/kubernetes/kubernetes-instance.js +0 -4
  28. package/dist/kubernetes/remove-persistent-data.js +16 -6
  29. package/dist/kubernetes/resolve-pod-name.js +7 -7
  30. package/dist/planner/unreferenced-build-file-envs.js +35 -0
  31. package/dist/planner/utils/append-work-environment.js +1 -1
  32. package/dist/planner/utils/append-work-service.js +26 -2
  33. package/dist/planner/utils/get-outputs-to-reset.js +20 -0
  34. package/dist/planner/utils/parse-work-generate.js +2 -2
  35. package/dist/planner/validate.js +10 -0
  36. package/dist/planner/work-healthcheck.js +8 -0
  37. package/dist/planner/work-instance-id.js +3 -3
  38. package/dist/planner/work-runtime-docker.js +19 -19
  39. package/dist/planner/work-runtime-kubernetes.js +66 -48
  40. package/dist/planner/work-runtime-local.js +22 -5
  41. package/dist/planner/work-service-id.js +3 -0
  42. package/dist/planner/work-service.js +27 -1
  43. package/dist/program.js +6 -6
  44. package/dist/schema/build-file-container-service-schema.js +8 -0
  45. package/dist/schema/build-file-environment-schema-http-route.js +15 -0
  46. package/dist/schema/build-file-environment-schema.js +8 -2
  47. package/dist/schema/build-file-schema.js +1 -1
  48. package/dist/schema/build-file-service-container-healthcheck.js +4 -0
  49. package/dist/schema/envs-schema.js +1 -1
  50. package/dist/utils/abort-on-signals.js +17 -0
  51. package/package.json +9 -9
  52. package/dist/kubernetes/ensure-ingress.js +0 -54
  53. package/dist/schema/build-file-environment-schema-ingress.js +0 -14
@@ -1,6 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isContainerWorkService = void 0;
3
+ exports.getHealthcheckCommand = exports.getServiceCommand = exports.getServiceWorkingDir = exports.isContainerWorkService = void 0;
4
4
  const isContainerWorkService = (svc) => 'image' in svc && svc.type === 'container-service';
5
5
  exports.isContainerWorkService = isContainerWorkService;
6
+ // The container's working directory: the declared `workdir`, else the build
7
+ // file's directory (the default every runtime applied before `workdir`).
8
+ const getServiceWorkingDir = (service) => service.workdir ?? service.cwd;
9
+ exports.getServiceWorkingDir = getServiceWorkingDir;
10
+ // The container's entrypoint and command for a service: with a `shell`, its
11
+ // `cmd` runs as `<shell> -c "<cmd>"` in place of the image entrypoint; without
12
+ // one the tokenized `cmd` becomes the command and the image entrypoint stays.
13
+ function getServiceCommand(service) {
14
+ if (!service.cmd) {
15
+ return { entrypoint: null, cmd: null };
16
+ }
17
+ if (service.shell) {
18
+ return { entrypoint: [service.shell, '-c'], cmd: [service.cmd.cmd] };
19
+ }
20
+ return { entrypoint: null, cmd: [service.cmd.parsed.command, ...service.cmd.parsed.args] };
21
+ }
22
+ exports.getServiceCommand = getServiceCommand;
23
+ // The healthcheck as an exec command, through the service's `shell` if it has one.
24
+ function getHealthcheckCommand(service) {
25
+ if (!service.healthcheck) {
26
+ return null;
27
+ }
28
+ const cmd = service.healthcheck.cmd;
29
+ return service.shell ? [service.shell, '-c', cmd.cmd] : [cmd.parsed.command, ...cmd.parsed.args];
30
+ }
31
+ exports.getHealthcheckCommand = getHealthcheckCommand;
6
32
  //# sourceMappingURL=work-service.js.map
package/dist/program.js CHANGED
@@ -228,13 +228,13 @@ async function getProgram(environment, argv) {
228
228
  if (validation.type === 'error') {
229
229
  errors++;
230
230
  }
231
- for (const [buildFilename, errors] of Object.entries(fileErrors)) {
232
- environment.stdout.write(`${colors_1.default.underline(colors_1.default.gray(buildFilename))}\n`);
233
- for (const error of errors) {
234
- environment.stdout.write(` ${colors_1.default.underline(colors_1.default.blue(error.type))} at ${colors_1.default.gray(error.item.name)} ${error.message}\n`);
235
- }
236
- environment.stdout.write('\n');
231
+ }
232
+ for (const [buildFilename, errors] of Object.entries(fileErrors)) {
233
+ environment.stdout.write(`${colors_1.default.underline(colors_1.default.gray(buildFilename))}\n`);
234
+ for (const error of errors) {
235
+ environment.stdout.write(` ${colors_1.default.underline(colors_1.default.blue(error.type))} at ${colors_1.default.gray(error.item.name)} ${error.message}\n`);
237
236
  }
237
+ environment.stdout.write('\n');
238
238
  }
239
239
  if (errors !== 0) {
240
240
  program.error('Detected errors in the hammerkit configuration', { exitCode: 1 });
@@ -18,6 +18,14 @@ exports.buildFileContainerServiceSchema = (0, zod_1.object)({
18
18
  src: (0, zod_1.array)((0, zod_1.string)()).optional(),
19
19
  needs: (0, zod_1.array)(build_file_need_schema_1.buildFileNeedSchema).optional(),
20
20
  cmd: (0, zod_1.string)().optional(),
21
+ // Absolute path inside the container. Without it the service runs in the
22
+ // build file's directory, like a task; images whose entrypoint expects their
23
+ // own WORKDIR (relative `./dist/…` paths, `npm start`) need it.
24
+ workdir: (0, zod_1.string)().optional(),
25
+ // Runs `cmd` and the healthcheck as `<shell> -c "<cmd>"` (replacing the
26
+ // image entrypoint for `cmd`), like a task's `shell`. Without it both are
27
+ // split into exec-form arguments.
28
+ shell: (0, zod_1.string)().optional(),
21
29
  volumes: (0, zod_1.array)((0, zod_1.string)()).optional(),
22
30
  labels: labels_schema_1.labelsSchema.optional(),
23
31
  continuous: (0, zod_1.boolean)().optional(),
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildFileEnvironmentSchemaHttpRoute = void 0;
4
+ const zod_1 = require("zod");
5
+ // A Gateway API HTTPRoute exposing a service of the environment under a hostname.
6
+ exports.buildFileEnvironmentSchemaHttpRoute = (0, zod_1.object)({
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
+ // the parent Gateway the route attaches to
12
+ gateway: (0, zod_1.string)(),
13
+ gatewayNamespace: (0, zod_1.string)().optional(),
14
+ }).strict();
15
+ //# sourceMappingURL=build-file-environment-schema-http-route.js.map
@@ -2,12 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildFileEnvironmentSchema = exports.buildFileDockerEnvironmentSchema = exports.buildFileKubernetesEnvironmentSchema = void 0;
4
4
  const zod_1 = require("zod");
5
- const build_file_environment_schema_ingress_1 = require("./build-file-environment-schema-ingress");
5
+ const build_file_environment_schema_http_route_1 = require("./build-file-environment-schema-http-route");
6
6
  exports.buildFileKubernetesEnvironmentSchema = (0, zod_1.object)({
7
7
  namespace: (0, zod_1.string)().optional(),
8
8
  context: (0, zod_1.string)(),
9
9
  kubeconfig: (0, zod_1.string)().optional(),
10
- ingresses: (0, zod_1.array)(build_file_environment_schema_ingress_1.buildFileEnvironmentSchemaIngress).optional(),
10
+ httpRoutes: (0, zod_1.array)(build_file_environment_schema_http_route_1.buildFileEnvironmentSchemaHttpRoute).optional(),
11
+ // Ingress support was removed in 1.8.0. Fail instead of dropping the routes.
12
+ ingresses: (0, zod_1.never)({
13
+ errorMap: () => ({
14
+ message: 'ingresses were removed in 1.8.0: declare Gateway API routes under httpRoutes, each with a gateway',
15
+ }),
16
+ }).optional(),
11
17
  });
12
18
  exports.buildFileDockerEnvironmentSchema = (0, zod_1.object)({
13
19
  host: (0, zod_1.string)().optional(),
@@ -19,5 +19,5 @@ exports.buildFileSchema = (0, zod_1.object)({
19
19
  labels: labels_schema_1.labelsSchema.optional(),
20
20
  })
21
21
  .strict()
22
- .describe('Build file with support for containerization\nhttps://no0dles.gitbook.io/hammerkit/build-file');
22
+ .describe('Build file with support for containerization\nhttps://hammerkit.dev/docs/build-file');
23
23
  //# sourceMappingURL=build-file-schema.js.map
@@ -2,7 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildFileServiceContainerHealthcheck = void 0;
4
4
  const zod_1 = require("zod");
5
+ const duration_schema_1 = require("./duration-schema");
5
6
  exports.buildFileServiceContainerHealthcheck = (0, zod_1.object)({
6
7
  cmd: (0, zod_1.string)(),
8
+ // How long the service may take to pass `cmd` once its container runs
9
+ // (default 20s, HAMMERKIT_HEALTHCHECK_TIMEOUT changes the default).
10
+ timeout: duration_schema_1.durationSchema.optional(),
7
11
  });
8
12
  //# sourceMappingURL=build-file-service-container-healthcheck.js.map
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.envsSchema = void 0;
4
4
  const zod_1 = require("zod");
5
- exports.envsSchema = (0, zod_1.record)((0, zod_1.union)([(0, zod_1.string)(), (0, zod_1.number)()])).describe('Environment values for the current build task\nhttps://no0dles.gitbook.io/hammerkit/build-file/environment-variables');
5
+ exports.envsSchema = (0, zod_1.record)((0, zod_1.union)([(0, zod_1.string)(), (0, zod_1.number)()])).describe('Environment values for the current build task\nhttps://hammerkit.dev/docs/build-file/environment-variables');
6
6
  //# sourceMappingURL=envs-schema.js.map
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.abortOnSignals = exports.ABORT_SIGNALS = void 0;
4
+ // Ctrl-C (SIGINT), a CI job being cancelled or a supervisor stopping hammerkit
5
+ // (SIGTERM) and a closed terminal (SIGHUP) all stop the run the same way: abort,
6
+ // so services are removed and local tasks' process groups are killed. Without a
7
+ // handler Node exits on SIGTERM/SIGHUP at once and leaves both behind.
8
+ exports.ABORT_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP'];
9
+ function abortOnSignals(source, abortCtrl) {
10
+ for (const signal of exports.ABORT_SIGNALS) {
11
+ source.on(signal, () => {
12
+ abortCtrl.abort();
13
+ });
14
+ }
15
+ }
16
+ exports.abortOnSignals = abortOnSignals;
17
+ //# sourceMappingURL=abort-on-signals.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hammerkit",
3
3
  "description": "Containerized build tool with incremental caching that runs the same build file locally, in Docker and on Kubernetes.",
4
- "version": "1.7.0",
4
+ "version": "1.9.0",
5
5
  "keywords": [
6
6
  "build",
7
7
  "build-tool",
@@ -12,7 +12,7 @@
12
12
  "monorepo",
13
13
  "task-runner"
14
14
  ],
15
- "homepage": "https://no0dles.gitbook.io/hammerkit/",
15
+ "homepage": "https://hammerkit.dev",
16
16
  "bin": "dist/index.js",
17
17
  "files": [
18
18
  "dist",
@@ -47,15 +47,15 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@aws-sdk/client-s3": "^3.470.0",
50
- "@kubernetes/client-node": "^0.20.0",
51
- "chokidar": "^3.5.3",
50
+ "@kubernetes/client-node": "^2.0.0",
51
+ "chokidar": "^3.6.0",
52
52
  "colors": "^1.4.0",
53
53
  "commander": "^11.1.0",
54
- "dockerode": "^4.0.2",
54
+ "dockerode": "^4.0.12",
55
55
  "find-process": "^1.4.7",
56
- "minimatch": "^6.1.5",
57
- "tar": "^6.2.0",
58
- "yaml": "^2.3.4",
56
+ "minimatch": "^6.2.3",
57
+ "tar": "^7.5.22",
58
+ "yaml": "^2.9.1",
59
59
  "zod": "^3.22.4"
60
60
  },
61
61
  "devDependencies": {
@@ -64,7 +64,7 @@
64
64
  "@types/dockerode": "^3.3.23",
65
65
  "@types/minimatch": "^5.1.2",
66
66
  "@types/node": "^24.0.0",
67
- "@types/tar": "^6.1.3",
67
+ "@types/ws": "^8.18.2",
68
68
  "@typescript-eslint/eslint-plugin": "^6.16.0",
69
69
  "@typescript-eslint/parser": "^6.16.0",
70
70
  "@vitest/coverage-istanbul": "^2.1.8",
@@ -1,54 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ensureIngress = void 0;
4
- const apply_1 = require("./apply");
5
- const resources_1 = require("./resources");
6
- async function ensureIngress(instance, env, ingress, service) {
7
- const resource = {
8
- kind: 'Ingress',
9
- apiVersion: 'networking.k8s.io/v1',
10
- metadata: {
11
- name: ingress.host,
12
- namespace: env.namespace,
13
- annotations: {
14
- 'cert-manager.io/cluster-issuer': 'hammerkit-issuer',
15
- },
16
- labels: {
17
- 'hammerkit.dev/id': service.id(),
18
- },
19
- },
20
- spec: {
21
- ingressClassName: 'nginx',
22
- rules: [
23
- {
24
- host: ingress.host,
25
- http: {
26
- paths: [
27
- {
28
- path: ingress.path ?? '/',
29
- pathType: 'Prefix',
30
- backend: {
31
- service: {
32
- name: (0, resources_1.getResourceName)(service),
33
- port: {
34
- number: ingress.servicePort ?? service.data.ports[0].containerPort,
35
- },
36
- },
37
- },
38
- },
39
- ],
40
- },
41
- },
42
- ],
43
- tls: [
44
- {
45
- secretName: `${ingress.host}-tls`,
46
- hosts: [ingress.host],
47
- },
48
- ],
49
- },
50
- };
51
- return await (0, apply_1.apply)(instance, resource);
52
- }
53
- exports.ensureIngress = ensureIngress;
54
- //# sourceMappingURL=ensure-ingress.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