hammerkit 1.6.0-alpha.3 → 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.
- package/README.md +4 -4
- package/build.schema.json +49 -2
- package/dist/cache/backends/local-cache-backend.js +58 -1
- package/dist/cache/backends/registry/registry-client.js +264 -0
- package/dist/cache/backends/registry/registry-credentials.js +81 -0
- package/dist/cache/backends/registry/registry-reference.js +36 -0
- package/dist/cache/backends/registry-cache-backend.js +145 -0
- package/dist/cache/backends/s3-cache-backend.js +99 -46
- package/dist/cache/cache-backend-registry.js +2 -0
- package/dist/cache/cache-inventory.js +84 -0
- package/dist/cache/cache-sync.js +77 -0
- package/dist/cache/explain.js +214 -0
- package/dist/cache/last-resolved.js +48 -0
- package/dist/cache/read-only.js +15 -0
- package/dist/cache/retention.js +61 -0
- package/dist/cli.js +43 -0
- package/dist/docker/using-container.js +5 -1
- package/dist/executer/archive-task-entry.js +21 -0
- package/dist/executer/await-completed-dependencies.js +32 -14
- package/dist/executer/cache-metadata.js +9 -2
- package/dist/executer/docker-service.js +2 -1
- package/dist/executer/docker-task.js +2 -1
- package/dist/executer/dry-run.js +73 -0
- package/dist/executer/event-cache.js +6 -5
- package/dist/executer/execute-command.js +21 -3
- package/dist/executer/execute-work-service.js +10 -1
- package/dist/executer/execute-work-task.js +95 -25
- package/dist/executer/execute-work-tree.js +26 -1
- package/dist/executer/get-scheduler-execute-result.js +4 -1
- package/dist/executer/lazy-resolver.js +15 -3
- package/dist/executer/reset-work-tree.js +2 -0
- package/dist/executer/run-summary.js +102 -0
- package/dist/executer/scheduler/enqueue-next.js +15 -60
- package/dist/executer/scheduler/state-key.js +78 -0
- package/dist/kubernetes/await-running-state.js +73 -19
- package/dist/log.js +16 -6
- package/dist/logging/live-logger.js +2 -1
- package/dist/optimizer/calculate-checksum.js +8 -2
- package/dist/optimizer/get-work-cache-stats.js +66 -16
- package/dist/optimizer/work-task-cache-description.js +34 -5
- package/dist/planner/project-root.js +26 -0
- package/dist/planner/utils/append-work-service.js +2 -1
- package/dist/planner/utils/append-work-task.js +7 -1
- package/dist/planner/utils/parse-work-source.js +35 -30
- package/dist/planner/utils/portable-path.js +30 -0
- package/dist/planner/work-cache-id.js +2 -2
- package/dist/planner/work-graph-serializer.js +103 -0
- package/dist/planner/work-instance-id.js +15 -0
- package/dist/planner/work-runtime-docker.js +53 -15
- package/dist/planner/work-runtime-kubernetes.js +35 -3
- package/dist/planner/work-runtime-local.js +9 -3
- package/dist/planner/work-service-id.js +16 -4
- package/dist/program.js +235 -1
- package/dist/schema/build-file-base-task-schema.js +4 -0
- package/dist/schema/cache-schema.js +21 -2
- package/dist/schema/duration-schema.js +18 -0
- package/dist/schema/reference-parser.js +3 -0
- package/dist/schema/size-schema.js +18 -0
- package/dist/schema/work-scope-parser.js +15 -2
- package/dist/utils/units.js +87 -0
- package/package.json +15 -4
- package/.agents/FEEDBACK.md +0 -74
- package/.agents/skills/before-commit/SKILL.md +0 -101
- package/.agents/skills/do-the-work/SKILL.md +0 -64
- package/.agents/skills/fix-ci/SKILL.md +0 -98
- package/.agents/skills/speckit-git-commit/SKILL.md +0 -53
- package/.agents/skills/speckit-git-feature/SKILL.md +0 -72
- package/.agents/skills/speckit-git-initialize/SKILL.md +0 -54
- package/.agents/skills/speckit-git-remote/SKILL.md +0 -50
- package/.agents/skills/speckit-git-validate/SKILL.md +0 -54
- package/.devcontainer/Dockerfile +0 -5
- package/.devcontainer/devcontainer.json +0 -40
- package/.gitbook.yaml +0 -5
- package/.specify/extensions/.registry +0 -37
- package/.specify/extensions/git/README.md +0 -100
- package/.specify/extensions/git/commands/speckit.git.commit.md +0 -48
- package/.specify/extensions/git/commands/speckit.git.feature.md +0 -67
- package/.specify/extensions/git/commands/speckit.git.initialize.md +0 -49
- package/.specify/extensions/git/commands/speckit.git.remote.md +0 -45
- package/.specify/extensions/git/commands/speckit.git.validate.md +0 -49
- package/.specify/extensions/git/config-template.yml +0 -62
- package/.specify/extensions/git/extension.yml +0 -140
- package/.specify/extensions/git/git-config.yml +0 -62
- package/.specify/extensions.yml +0 -149
- package/.specify/feature.json +0 -3
- package/.specify/init-options.json +0 -10
- package/.specify/integration.json +0 -15
- package/.specify/integrations/claude.manifest.json +0 -16
- package/.specify/integrations/speckit.manifest.json +0 -17
- package/.specify/memory/constitution.md +0 -162
- package/.specify/templates/checklist-template.md +0 -40
- package/.specify/templates/constitution-template.md +0 -50
- package/.specify/templates/plan-template.md +0 -113
- package/.specify/templates/spec-template.md +0 -131
- package/.specify/templates/tasks-template.md +0 -252
- package/.specify/workflows/speckit/workflow.yml +0 -77
- package/.specify/workflows/workflow-registry.json +0 -13
- package/AGENTS.md +0 -94
- package/Dockerfile +0 -25
- package/FOLLOWUPS.md +0 -34
- package/SUMMARY.md +0 -68
- package/VALUES.md +0 -53
- package/dist/cache/auto-pull.spec.js +0 -123
- package/dist/cache/backends/local-cache-backend.spec.js +0 -72
- package/dist/cache/backends/s3-cache-backend.spec.js +0 -118
- package/dist/cache/cache-backend-registry.spec.js +0 -22
- package/dist/cache/resolve-cache.spec.js +0 -52
- package/dist/cli.spec.js +0 -23
- package/dist/console/get-logger.spec.js +0 -23
- package/dist/docker/get-default-platform.spec.js +0 -29
- package/dist/docker/longest-common-prefix.spec.js +0 -26
- package/dist/docker/package.spec.js +0 -145
- package/dist/docker/remove-container.spec.js +0 -32
- package/dist/docker/using-container.spec.js +0 -90
- package/dist/environment/merge-environment-variables.spec.js +0 -24
- package/dist/environment/replace-env-variables.spec.js +0 -47
- package/dist/executer/abort.spec.js +0 -18
- package/dist/executer/environment-mock.spec.js +0 -22
- package/dist/executer/execute.spec.js +0 -51
- package/dist/executer/get-service-containers.spec.js +0 -59
- package/dist/executer/get-service-env-hints.spec.js +0 -65
- package/dist/executer/label-values.spec.js +0 -61
- package/dist/executer/lazy-resolver.spec.js +0 -20
- package/dist/executer/process-manager.spec.js +0 -54
- package/dist/executer/scheduler/enqueue-next.spec.js +0 -136
- package/dist/executer/state-resolver.spec.js +0 -37
- package/dist/executer/state.spec.js +0 -66
- package/dist/executer/states.spec.js +0 -14
- package/dist/file/get-file-context.spec.js +0 -178
- package/dist/file/move-files.spec.js +0 -48
- package/dist/kubernetes/ensure-http-route.spec.js +0 -70
- package/dist/kubernetes/ensure-namespace.spec.js +0 -39
- package/dist/kubernetes/resolve-pod-name.spec.js +0 -81
- package/dist/log.spec.js +0 -131
- package/dist/logging/grouped-logger.spec.js +0 -84
- package/dist/logging/interactive-logger.spec.js +0 -57
- package/dist/logging/live-logger.spec.js +0 -37
- package/dist/logging/loggers.spec.js +0 -154
- package/dist/optimizer/calculate-checksum.spec.js +0 -34
- package/dist/optimizer/get-cache-directory.spec.js +0 -59
- package/dist/optimizer/get-work-cache-stats.spec.js +0 -79
- package/dist/optimizer/work-task-cache-description.spec.js +0 -45
- package/dist/parser/default-build-file.spec.js +0 -48
- package/dist/parser/parse-label-arguments.spec.js +0 -27
- package/dist/parser/read-build-file.spec.js +0 -89
- package/dist/parser/read-env-file.spec.js +0 -42
- package/dist/planner/utils/append-work-service.spec.js +0 -71
- package/dist/planner/utils/get-container-user.spec.js +0 -30
- package/dist/planner/utils/get-default-kube-config.spec.js +0 -11
- package/dist/planner/utils/normalize-path.spec.js +0 -24
- package/dist/planner/utils/parse-work-command.spec.js +0 -21
- package/dist/planner/utils/parse-work-generate.spec.js +0 -45
- package/dist/planner/utils/parse-work-mount.spec.js +0 -62
- package/dist/planner/utils/parse-work-mounts.spec.js +0 -19
- package/dist/planner/utils/parse-work-port.spec.js +0 -24
- package/dist/planner/utils/parse-work-ports.spec.js +0 -24
- package/dist/planner/utils/parse-work-source.spec.js +0 -58
- package/dist/planner/utils/parse-work-volume.spec.js +0 -18
- package/dist/planner/utils/plan-work-tasks.spec.js +0 -30
- package/dist/planner/utils/plan-work-volume.spec.js +0 -33
- package/dist/planner/utils/split-name.spec.js +0 -16
- package/dist/planner/utils/template-value.spec.js +0 -14
- package/dist/planner/validate.spec.js +0 -251
- package/dist/planner/work-cache-id.spec.js +0 -173
- package/dist/planner/work-runtime-local.spec.js +0 -193
- package/dist/program-commands.spec.js +0 -84
- package/dist/program.spec.js +0 -43
- package/dist/schema/build-file-schema.spec.js +0 -61
- package/dist/schema/doc-build-files.spec.js +0 -223
- package/dist/schema/parse-error.spec.js +0 -18
- package/dist/schema/reference-parser.spec.js +0 -83
- package/dist/schema/schema-parser.spec.js +0 -80
- package/dist/schema/type-guards.spec.js +0 -23
- package/dist/schema/work-scope-parser.spec.js +0 -66
- package/dist/service/update-service-status.spec.js +0 -27
- package/dist/testing/ensure-local-registry.js +0 -134
- package/dist/testing/example-test-suite.js +0 -63
- package/dist/testing/expect.js +0 -82
- package/dist/testing/get-test-suite.js +0 -9
- package/dist/testing/http-get.js +0 -21
- package/dist/testing/integration/cache.spec.js +0 -49
- package/dist/testing/integration/cancellation.spec.js +0 -45
- package/dist/testing/integration/clean.spec.js +0 -43
- package/dist/testing/integration/cmd.spec.js +0 -15
- package/dist/testing/integration/concurrency.spec.js +0 -19
- package/dist/testing/integration/docker-package-registry.spec.js +0 -44
- package/dist/testing/integration/docker-service-crash.spec.js +0 -36
- package/dist/testing/integration/docker.spec.js +0 -17
- package/dist/testing/integration/env.spec.js +0 -48
- package/dist/testing/integration/environment.spec.js +0 -97
- package/dist/testing/integration/error.spec.js +0 -19
- package/dist/testing/integration/export.spec.js +0 -22
- package/dist/testing/integration/extend.spec.js +0 -29
- package/dist/testing/integration/glob.spec.js +0 -40
- package/dist/testing/integration/include.spec.js +0 -31
- package/dist/testing/integration/invalid.spec.js +0 -17
- package/dist/testing/integration/invalid_loop.spec.js +0 -56
- package/dist/testing/integration/k8s-state-cache.spec.js +0 -41
- package/dist/testing/integration/kubernetes-service.spec.js +0 -148
- package/dist/testing/integration/kubernetes.spec.js +0 -99
- package/dist/testing/integration/labels.spec.js +0 -42
- package/dist/testing/integration/local.spec.js +0 -14
- package/dist/testing/integration/monorepo.spec.js +0 -15
- package/dist/testing/integration/multi-line.spec.js +0 -17
- package/dist/testing/integration/reference.spec.js +0 -21
- package/dist/testing/integration/services.spec.js +0 -38
- package/dist/testing/integration/store.spec.js +0 -52
- package/dist/testing/integration/unknown.spec.js +0 -28
- package/dist/testing/integration/validate.spec.js +0 -160
- package/dist/testing/integration/watch-dependency.spec.js +0 -33
- package/dist/testing/integration/watch.spec.js +0 -45
- package/dist/testing/requires-kubernetes.js +0 -18
- package/dist/testing/requires-linux-containers.js +0 -18
- package/dist/testing/requires-windows-containers.js +0 -18
- package/dist/testing/test-case.js +0 -75
- package/dist/testing/test-streams.js +0 -28
- package/dist/testing/test-suite-setup.js +0 -3
- package/dist/testing/test-suite.js +0 -3
- package/dist/testing/testing-timeout.js +0 -32
- package/dist/utils/abort-event.spec.js +0 -46
- package/dist/utils/buffer-context.spec.js +0 -38
- package/dist/utils/ci.spec.js +0 -53
- package/dist/utils/debouncer.spec.js +0 -58
- package/dist/utils/emitter.spec.js +0 -34
- package/dist/utils/empty-writable.spec.js +0 -18
- package/dist/utils/environment-config.spec.js +0 -26
- package/dist/utils/fail-never.spec.js +0 -9
- package/dist/utils/sleep.spec.js +0 -22
- package/dist/version.spec.js +0 -12
- package/specs/build-file/caches/spec.md +0 -114
- package/specs/build-file/environment-variables/spec.md +0 -82
- package/specs/build-file/includes/spec.md +0 -60
- package/specs/build-file/references/spec.md +0 -56
- package/specs/build-file/spec.md +0 -62
- package/specs/cli/clean/spec.md +0 -85
- package/specs/cli/down/spec.md +0 -85
- package/specs/cli/execute/spec.md +0 -101
- package/specs/cli/init/spec.md +0 -64
- package/specs/cli/ls/spec.md +0 -66
- package/specs/cli/package/spec.md +0 -110
- package/specs/cli/spec.md +0 -73
- package/specs/cli/store-restore/spec.md +0 -74
- package/specs/cli/up/spec.md +0 -80
- package/specs/cli/validate/spec.md +0 -70
- package/specs/labels/spec.md +0 -91
- package/specs/service/container/spec.md +0 -120
- package/specs/service/kubernetes/spec.md +0 -97
- package/specs/service/spec.md +0 -105
- package/specs/task/caching/spec.md +0 -107
- package/specs/task/container/spec.md +0 -118
- package/specs/task/dependencies/spec.md +0 -70
- package/specs/task/extending/spec.md +0 -74
- package/specs/task/kubernetes/spec.md +0 -125
- package/specs/task/needs/spec.md +0 -59
- package/specs/task/spec.md +0 -87
- package/specs/task/watching/spec.md +0 -69
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serializeWorkGraph = void 0;
|
|
4
|
+
const plan_work_tasks_1 = require("./utils/plan-work-tasks");
|
|
5
|
+
const validate_1 = require("./validate");
|
|
6
|
+
function buildGraph(workTree) {
|
|
7
|
+
const taskItems = Array.from((0, plan_work_tasks_1.iterateWorkTasks)(workTree)).sort((a, b) => a.name.localeCompare(b.name));
|
|
8
|
+
const serviceItems = Array.from((0, plan_work_tasks_1.iterateWorkServices)(workTree)).sort((a, b) => a.name.localeCompare(b.name));
|
|
9
|
+
const nodes = [];
|
|
10
|
+
const taskNodeId = new Map();
|
|
11
|
+
const serviceNodeId = new Map();
|
|
12
|
+
taskItems.forEach((task, index) => {
|
|
13
|
+
const id = `t${index}`;
|
|
14
|
+
taskNodeId.set(task.name, id);
|
|
15
|
+
nodes.push({ id, name: task.name, kind: 'task' });
|
|
16
|
+
});
|
|
17
|
+
serviceItems.forEach((service, index) => {
|
|
18
|
+
const id = `s${index}`;
|
|
19
|
+
serviceNodeId.set(service.name, id);
|
|
20
|
+
nodes.push({ id, name: service.name, kind: 'service' });
|
|
21
|
+
});
|
|
22
|
+
const edges = [];
|
|
23
|
+
for (const task of taskItems) {
|
|
24
|
+
const from = taskNodeId.get(task.name);
|
|
25
|
+
if (!from) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
for (const dep of task.deps) {
|
|
29
|
+
const to = taskNodeId.get(dep.name);
|
|
30
|
+
if (to) {
|
|
31
|
+
edges.push({ from, to, kind: 'deps' });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const need of task.needs) {
|
|
35
|
+
const to = serviceNodeId.get(need.service.name);
|
|
36
|
+
if (to) {
|
|
37
|
+
edges.push({ from, to, kind: 'needs' });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || a.kind.localeCompare(b.kind));
|
|
42
|
+
let cycle = null;
|
|
43
|
+
for (const task of taskItems) {
|
|
44
|
+
const path = (0, validate_1.hasDependencyCycle)(task, []);
|
|
45
|
+
if (path && path.length > 0) {
|
|
46
|
+
cycle = path.map((n) => n.name);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { nodes, edges, cycle };
|
|
51
|
+
}
|
|
52
|
+
function mermaidLabel(name) {
|
|
53
|
+
return name.replace(/"/g, "'");
|
|
54
|
+
}
|
|
55
|
+
function renderMermaid(nodes, edges, cycle) {
|
|
56
|
+
const lines = ['graph TD'];
|
|
57
|
+
for (const node of nodes) {
|
|
58
|
+
// tasks are rectangles, services are stadium-shaped, so the two are visually distinct
|
|
59
|
+
lines.push(node.kind === 'task'
|
|
60
|
+
? ` ${node.id}["${mermaidLabel(node.name)}"]`
|
|
61
|
+
: ` ${node.id}(["${mermaidLabel(node.name)}"])`);
|
|
62
|
+
}
|
|
63
|
+
for (const edge of edges) {
|
|
64
|
+
// deps are solid arrows, needs are dotted — distinct edge types
|
|
65
|
+
lines.push(edge.kind === 'deps' ? ` ${edge.from} --> ${edge.to}` : ` ${edge.from} -.-> ${edge.to}`);
|
|
66
|
+
}
|
|
67
|
+
if (cycle) {
|
|
68
|
+
lines.push(` %% cycle detected: ${cycle.join(' -> ')}`);
|
|
69
|
+
}
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
}
|
|
72
|
+
function dotId(id) {
|
|
73
|
+
return `"${id}"`;
|
|
74
|
+
}
|
|
75
|
+
function dotLabel(name) {
|
|
76
|
+
return name.replace(/"/g, '\\"');
|
|
77
|
+
}
|
|
78
|
+
function renderDot(nodes, edges, cycle) {
|
|
79
|
+
const lines = ['digraph hammerkit {'];
|
|
80
|
+
if (cycle) {
|
|
81
|
+
lines.push(` // cycle detected: ${cycle.join(' -> ')}`);
|
|
82
|
+
}
|
|
83
|
+
for (const node of nodes) {
|
|
84
|
+
const shape = node.kind === 'task' ? 'box' : 'ellipse';
|
|
85
|
+
lines.push(` ${dotId(node.id)} [label="${dotLabel(node.name)}" shape=${shape}];`);
|
|
86
|
+
}
|
|
87
|
+
for (const edge of edges) {
|
|
88
|
+
const style = edge.kind === 'needs' ? ' [style=dotted]' : '';
|
|
89
|
+
lines.push(` ${dotId(edge.from)} -> ${dotId(edge.to)}${style};`);
|
|
90
|
+
}
|
|
91
|
+
lines.push('}');
|
|
92
|
+
return lines.join('\n');
|
|
93
|
+
}
|
|
94
|
+
// Serialize the planner's already-built work graph (tasks, services, deps and
|
|
95
|
+
// needs edges) into a renderable format. A cyclic graph still renders and marks
|
|
96
|
+
// the cycle rather than crashing.
|
|
97
|
+
function serializeWorkGraph(workTree, format) {
|
|
98
|
+
const { nodes, edges, cycle } = buildGraph(workTree);
|
|
99
|
+
const output = format === 'dot' ? renderDot(nodes, edges, cycle) : renderMermaid(nodes, edges, cycle);
|
|
100
|
+
return { output, cycle };
|
|
101
|
+
}
|
|
102
|
+
exports.serializeWorkGraph = serializeWorkGraph;
|
|
103
|
+
//# sourceMappingURL=work-graph-serializer.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getWorkInstanceId = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
// `id()` is the portable cache identity: identical for the same task in every
|
|
6
|
+
// checkout, so a shared cache hits across machines. Machine-local runtime state
|
|
7
|
+
// (docker container labels, the cache staging directory) must instead stay
|
|
8
|
+
// scoped to one checkout — two worktrees of the same repo on one host would
|
|
9
|
+
// otherwise see each other's paused containers and staging files. The instance
|
|
10
|
+
// id folds the absolute project root into the portable id for that purpose.
|
|
11
|
+
function getWorkInstanceId(item) {
|
|
12
|
+
return (0, crypto_1.createHash)('sha1').update(`${item.id()}\0${item.data.projectRoot}`).digest('hex');
|
|
13
|
+
}
|
|
14
|
+
exports.getWorkInstanceId = getWorkInstanceId;
|
|
15
|
+
//# sourceMappingURL=work-instance-id.js.map
|
|
@@ -9,14 +9,16 @@ const using_container_1 = require("../docker/using-container");
|
|
|
9
9
|
const event_cache_1 = require("../executer/event-cache");
|
|
10
10
|
const get_docker_executor_1 = require("../executer/get-docker-executor");
|
|
11
11
|
const path_1 = require("path");
|
|
12
|
+
const tar_1 = require("tar");
|
|
12
13
|
const plan_work_volume_1 = require("./utils/plan-work-volume");
|
|
14
|
+
const work_instance_id_1 = require("./work-instance-id");
|
|
13
15
|
function dockerTaskRuntime(task, workEnvironment) {
|
|
14
16
|
const docker = (0, execute_docker_1.getContainerCli)(workEnvironment);
|
|
15
17
|
return {
|
|
16
18
|
async initialize(state) {
|
|
17
19
|
const currentTasks = await docker.listContainers({
|
|
18
20
|
filters: {
|
|
19
|
-
label: [`hammerkit-id=${
|
|
21
|
+
label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(task)}`],
|
|
20
22
|
},
|
|
21
23
|
});
|
|
22
24
|
const currentTask = currentTasks[0];
|
|
@@ -40,7 +42,7 @@ function dockerTaskRuntime(task, workEnvironment) {
|
|
|
40
42
|
const containers = await docker.listContainers({
|
|
41
43
|
all: true,
|
|
42
44
|
filters: {
|
|
43
|
-
label: [`hammerkit-id=${
|
|
45
|
+
label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(task)}`],
|
|
44
46
|
},
|
|
45
47
|
});
|
|
46
48
|
for (const container of containers) {
|
|
@@ -66,11 +68,11 @@ function dockerTaskRuntime(task, workEnvironment) {
|
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
70
|
},
|
|
69
|
-
async currentStateKey() {
|
|
71
|
+
async currentStateKey(environment) {
|
|
70
72
|
const containers = await docker.listContainers({
|
|
71
73
|
all: true,
|
|
72
74
|
filters: {
|
|
73
|
-
label: [`hammerkit-id=${
|
|
75
|
+
label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(task)}`],
|
|
74
76
|
},
|
|
75
77
|
});
|
|
76
78
|
const container = containers[0];
|
|
@@ -80,11 +82,26 @@ function dockerTaskRuntime(task, workEnvironment) {
|
|
|
80
82
|
if (!container.Labels['hammerkit-state']) {
|
|
81
83
|
return null;
|
|
82
84
|
}
|
|
85
|
+
// outputs removed since the run (a deleted export, a pruned volume)
|
|
86
|
+
// leave nothing to reuse
|
|
87
|
+
for (const generate of task.data.generates.filter((g) => !g.inherited)) {
|
|
88
|
+
const present = isOnHost(generate)
|
|
89
|
+
? await environment.file.exists(generate.path)
|
|
90
|
+
: !!(await (0, get_docker_executor_1.existsVolume)(docker, generate.volumeName));
|
|
91
|
+
if (!present) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
83
95
|
return container.Labels['hammerkit-state'];
|
|
84
96
|
},
|
|
85
97
|
};
|
|
86
98
|
}
|
|
87
99
|
exports.dockerTaskRuntime = dockerTaskRuntime;
|
|
100
|
+
// File outputs are bind mounts of host files and exported directories are
|
|
101
|
+
// copied to the host, so both must be present on the host, not only in a volume.
|
|
102
|
+
function isOnHost(generate) {
|
|
103
|
+
return generate.isFile || generate.export;
|
|
104
|
+
}
|
|
88
105
|
async function restoreContainer(docker, environment, item, path) {
|
|
89
106
|
await (0, using_container_1.usingContainer)(docker, item, {
|
|
90
107
|
abortSignal: environment.abortCtrl.signal,
|
|
@@ -95,15 +112,22 @@ async function restoreContainer(docker, environment, item, path) {
|
|
|
95
112
|
WorkingDir: (0, execute_docker_1.convertToPosixPath)(item.data.cwd),
|
|
96
113
|
Labels: {
|
|
97
114
|
app: 'hammerkit',
|
|
98
|
-
'hammerkit-id':
|
|
115
|
+
'hammerkit-id': (0, work_instance_id_1.getWorkInstanceId)(item),
|
|
99
116
|
'hammerkit-pid': process.pid.toString(),
|
|
100
117
|
'hammerkit-type': 'task',
|
|
101
118
|
},
|
|
102
119
|
HostConfig: {
|
|
103
120
|
AutoRemove: true,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
121
|
+
// mount the same volumes archiveContainer reads from, so the restored
|
|
122
|
+
// archives land in the task's output volumes rather than in this
|
|
123
|
+
// throwaway container's filesystem
|
|
124
|
+
Binds: item.data.type === 'container-service'
|
|
125
|
+
? item.data.volumes
|
|
126
|
+
.filter((v) => !v.inherited)
|
|
127
|
+
.map((v) => `${v.name}:${(0, execute_docker_1.convertToPosixPath)(v.containerPath)}`)
|
|
128
|
+
: item.data.generates
|
|
129
|
+
.filter((v) => !v.inherited && !v.isFile)
|
|
130
|
+
.map((v) => `${v.volumeName}:${(0, execute_docker_1.convertToPosixPath)(v.path)}`),
|
|
107
131
|
},
|
|
108
132
|
}, null, async (container) => {
|
|
109
133
|
for (const generate of (0, event_cache_1.getArchivePaths)(item.data, path)) {
|
|
@@ -114,6 +138,14 @@ async function restoreContainer(docker, environment, item, path) {
|
|
|
114
138
|
}
|
|
115
139
|
}
|
|
116
140
|
});
|
|
141
|
+
if (item.data.type === 'container-task') {
|
|
142
|
+
const onHost = new Set(item.data.generates.filter((g) => !g.inherited && isOnHost(g)).map((g) => g.path));
|
|
143
|
+
for (const generate of (0, event_cache_1.getArchivePaths)(item.data, path)) {
|
|
144
|
+
if (onHost.has(generate.path) && (await environment.file.exists(generate.filename))) {
|
|
145
|
+
await (0, tar_1.extract)({ file: generate.filename, cwd: (0, path_1.dirname)(generate.path) });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
117
149
|
}
|
|
118
150
|
function dockerServiceRuntime(service, workEnvironment) {
|
|
119
151
|
const docker = (0, execute_docker_1.getContainerCli)(workEnvironment);
|
|
@@ -121,7 +153,7 @@ function dockerServiceRuntime(service, workEnvironment) {
|
|
|
121
153
|
async initialize(state) {
|
|
122
154
|
const currentServices = await docker.listContainers({
|
|
123
155
|
filters: {
|
|
124
|
-
label: [`hammerkit-id=${
|
|
156
|
+
label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(service)}`],
|
|
125
157
|
},
|
|
126
158
|
});
|
|
127
159
|
const currentService = currentServices[0];
|
|
@@ -143,7 +175,7 @@ function dockerServiceRuntime(service, workEnvironment) {
|
|
|
143
175
|
const containers = await docker.listContainers({
|
|
144
176
|
all: true,
|
|
145
177
|
filters: {
|
|
146
|
-
label: [`hammerkit-id=${
|
|
178
|
+
label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(service)}`],
|
|
147
179
|
},
|
|
148
180
|
});
|
|
149
181
|
for (const container of containers) {
|
|
@@ -173,7 +205,7 @@ function dockerServiceRuntime(service, workEnvironment) {
|
|
|
173
205
|
const containers = await docker.listContainers({
|
|
174
206
|
all: true,
|
|
175
207
|
filters: {
|
|
176
|
-
label: [`hammerkit-id=${
|
|
208
|
+
label: [`hammerkit-id=${(0, work_instance_id_1.getWorkInstanceId)(service)}`],
|
|
177
209
|
},
|
|
178
210
|
});
|
|
179
211
|
const container = containers[0];
|
|
@@ -198,7 +230,7 @@ async function archiveContainer(docker, environment, item, path) {
|
|
|
198
230
|
WorkingDir: (0, execute_docker_1.convertToPosixPath)(item.data.cwd),
|
|
199
231
|
Labels: {
|
|
200
232
|
app: 'hammerkit',
|
|
201
|
-
'hammerkit-id':
|
|
233
|
+
'hammerkit-id': (0, work_instance_id_1.getWorkInstanceId)(item),
|
|
202
234
|
'hammerkit-pid': process.pid.toString(),
|
|
203
235
|
'hammerkit-type': 'task',
|
|
204
236
|
},
|
|
@@ -213,10 +245,16 @@ async function archiveContainer(docker, environment, item, path) {
|
|
|
213
245
|
.map((v) => `${v.volumeName}:${(0, execute_docker_1.convertToPosixPath)(v.path)}`),
|
|
214
246
|
},
|
|
215
247
|
}, null, async (container) => {
|
|
248
|
+
const hostFiles = new Set(item.data.type === 'container-task'
|
|
249
|
+
? item.data.generates.filter((g) => !g.inherited && g.isFile).map((g) => g.path)
|
|
250
|
+
: []);
|
|
216
251
|
for (const generatedArchive of (0, event_cache_1.getArchivePaths)(item.data, path)) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
252
|
+
// a file output is a bind mount of a host file, so archive it from the host
|
|
253
|
+
const readable = hostFiles.has(generatedArchive.path)
|
|
254
|
+
? (0, tar_1.create)({ cwd: (0, path_1.dirname)(generatedArchive.path) }, [(0, path_1.basename)(generatedArchive.path)])
|
|
255
|
+
: await container.getArchive({
|
|
256
|
+
path: generatedArchive.path,
|
|
257
|
+
});
|
|
220
258
|
await environment.file.writeStream(generatedArchive.filename, readable);
|
|
221
259
|
}
|
|
222
260
|
});
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.kubernetesForwardRuntime = exports.kubernetesServiceRuntime = exports.kubernetesTaskRuntime = void 0;
|
|
7
|
+
const abort_1 = require("../executer/abort");
|
|
7
8
|
const kubernetes_service_1 = require("../executer/kubernetes-service");
|
|
8
9
|
const kubernetes_instance_1 = require("../kubernetes/kubernetes-instance");
|
|
9
10
|
const replace_env_variables_1 = require("../environment/replace-env-variables");
|
|
@@ -27,6 +28,10 @@ function statusCodeOf(e) {
|
|
|
27
28
|
const err = e;
|
|
28
29
|
return err?.statusCode ?? err?.response?.statusCode ?? err?.body?.code;
|
|
29
30
|
}
|
|
31
|
+
async function listJobNames(instance, kubernetes, task) {
|
|
32
|
+
const jobs = await instance.batchApi.listNamespacedJob(kubernetes.namespace, undefined, undefined, undefined, undefined, `hammerkit.dev/id=${task.id()}`);
|
|
33
|
+
return jobs.body.items.flatMap((job) => (job.metadata?.name ? [job.metadata.name] : []));
|
|
34
|
+
}
|
|
30
35
|
function kubernetesTaskRuntime(task, kubernetes) {
|
|
31
36
|
const instance = (0, kubernetes_instance_1.createKubernetesInstances)(kubernetes);
|
|
32
37
|
return {
|
|
@@ -44,6 +49,11 @@ function kubernetesTaskRuntime(task, kubernetes) {
|
|
|
44
49
|
const persistence = await (0, volumes_1.getKubernetesPersistence)(task);
|
|
45
50
|
await (0, ensure_namespace_1.ensureNamespace)(instance, kubernetes.namespace);
|
|
46
51
|
await (0, ensure_persistent_data_1.ensurePersistentData)(instance, kubernetes, environment, task, persistence);
|
|
52
|
+
// The last job of a run stays: its state label is what currentStateKey
|
|
53
|
+
// reads. Jobs from an earlier run of this task go before this one starts.
|
|
54
|
+
for (const name of await listJobNames(instance, kubernetes, task)) {
|
|
55
|
+
await (0, await_running_state_1.deleteJobAndWait)(instance, kubernetes, name, options.abort);
|
|
56
|
+
}
|
|
47
57
|
const podName = `${task.name}-${options.stateKey}`;
|
|
48
58
|
let i = 0;
|
|
49
59
|
for (const cmd of task.data.cmds) {
|
|
@@ -95,12 +105,19 @@ function kubernetesTaskRuntime(task, kubernetes) {
|
|
|
95
105
|
try {
|
|
96
106
|
const pod = await (0, apply_1.apply)(instance, spec);
|
|
97
107
|
if (!pod.status?.succeeded) {
|
|
98
|
-
await (0, await_running_state_1.
|
|
108
|
+
await (0, await_running_state_1.awaitJobCompletion)(instance, kubernetes, spec.metadata.name, options.abort);
|
|
99
109
|
}
|
|
100
110
|
task.status.write('debug', 'pod completed');
|
|
101
|
-
|
|
111
|
+
if (i < task.data.cmds.length) {
|
|
112
|
+
await (0, await_running_state_1.deleteJobAndWait)(instance, kubernetes, podName, options.abort);
|
|
113
|
+
}
|
|
102
114
|
}
|
|
103
115
|
catch (e) {
|
|
116
|
+
if (e instanceof abort_1.AbortError) {
|
|
117
|
+
options.state.set({ stateKey: options.stateKey, type: 'canceled' });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
await (0, await_running_state_1.deleteJob)(instance, kubernetes, podName).catch(() => undefined);
|
|
104
121
|
options.state.set({
|
|
105
122
|
stateKey: options.stateKey,
|
|
106
123
|
type: 'error',
|
|
@@ -139,7 +156,22 @@ function kubernetesTaskRuntime(task, kubernetes) {
|
|
|
139
156
|
const completedStates = jobs.body.items
|
|
140
157
|
.filter((j) => j.status?.succeeded && j.metadata?.labels?.['hammerkit.dev/state'])
|
|
141
158
|
.map((j) => j.metadata.labels['hammerkit.dev/state']);
|
|
142
|
-
|
|
159
|
+
const stateKey = completedStates[completedStates.length - 1] ?? null;
|
|
160
|
+
if (stateKey === null || !task.data.generates.some((g) => !g.inherited)) {
|
|
161
|
+
return stateKey;
|
|
162
|
+
}
|
|
163
|
+
// outputs live in the task's claim; without it the finished job left
|
|
164
|
+
// nothing to reuse
|
|
165
|
+
try {
|
|
166
|
+
const claim = await instance.coreApi.readNamespacedPersistentVolumeClaim((0, volumes_1.getVolumeName)(task), kubernetes.namespace);
|
|
167
|
+
return claim.body.metadata?.deletionTimestamp ? null : stateKey;
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
if (statusCodeOf(e) === 404) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
throw e;
|
|
174
|
+
}
|
|
143
175
|
},
|
|
144
176
|
};
|
|
145
177
|
}
|
|
@@ -71,10 +71,16 @@ function getLocalWorkRuntime(task) {
|
|
|
71
71
|
},
|
|
72
72
|
async currentStateKey(environment) {
|
|
73
73
|
const stateFileName = getStateFilename(task);
|
|
74
|
-
if (await environment.file.exists(stateFileName)) {
|
|
75
|
-
return
|
|
74
|
+
if (!(await environment.file.exists(stateFileName))) {
|
|
75
|
+
return null;
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
// outputs deleted since the run (rm -rf dist) leave nothing to reuse
|
|
78
|
+
for (const generate of task.data.generates) {
|
|
79
|
+
if (!generate.inherited && !(await environment.file.exists(generate.path))) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return await environment.file.read(stateFileName);
|
|
78
84
|
},
|
|
79
85
|
async remove(environment) {
|
|
80
86
|
for (const generate of task.data.generates) {
|
|
@@ -3,19 +3,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getWorkServiceId = void 0;
|
|
4
4
|
const crypto_1 = require("crypto");
|
|
5
5
|
const work_service_1 = require("./work-service");
|
|
6
|
+
const portable_path_1 = require("./utils/portable-path");
|
|
7
|
+
const plan_work_volume_1 = require("./utils/plan-work-volume");
|
|
8
|
+
// Paths are made project-relative (portablePath) so a service id is identical
|
|
9
|
+
// wherever the project is checked out, like a task id.
|
|
6
10
|
function getWorkServiceId(service) {
|
|
11
|
+
const portable = (path) => (0, portable_path_1.portablePath)(service.projectRoot, path);
|
|
7
12
|
const jsonData = JSON.stringify((0, work_service_1.isContainerWorkService)(service)
|
|
8
13
|
? {
|
|
9
|
-
cwd: service.cwd
|
|
14
|
+
cwd: portable(service.cwd),
|
|
10
15
|
image: service.image,
|
|
11
|
-
volumes: service.volumes
|
|
12
|
-
|
|
16
|
+
volumes: service.volumes
|
|
17
|
+
.map((v) =>
|
|
18
|
+
// a derived volume name hashes the absolute path, so only an
|
|
19
|
+
// explicitly declared name participates
|
|
20
|
+
v.name === (0, plan_work_volume_1.getVolumeName)(v.containerPath)
|
|
21
|
+
? portable(v.containerPath)
|
|
22
|
+
: `${v.name}:${portable(v.containerPath)}`)
|
|
23
|
+
.sort(),
|
|
24
|
+
src: service.src.map((s) => portable(s.absolutePath)).sort(),
|
|
13
25
|
mounts: service.mounts.map((m) => m.mount).sort(),
|
|
14
26
|
}
|
|
15
27
|
: {
|
|
16
28
|
context: service.context,
|
|
17
29
|
selector: service.selector,
|
|
18
|
-
kubeconfig: service.kubeconfig,
|
|
30
|
+
kubeconfig: portable(service.kubeconfig),
|
|
19
31
|
});
|
|
20
32
|
return (0, crypto_1.createHash)('sha1').update(jsonData).digest('hex');
|
|
21
33
|
}
|