hammerkit 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +12 -1
- 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 -32
- 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 -18
- 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
package/dist/program.js
CHANGED
|
@@ -10,6 +10,10 @@ const ci_1 = require("./utils/ci");
|
|
|
10
10
|
const parse_label_arguments_1 = require("./parser/parse-label-arguments");
|
|
11
11
|
const cli_1 = require("./cli");
|
|
12
12
|
const log_1 = require("./log");
|
|
13
|
+
const empty_writable_1 = require("./utils/empty-writable");
|
|
14
|
+
const explain_1 = require("./cache/explain");
|
|
15
|
+
const run_summary_1 = require("./executer/run-summary");
|
|
16
|
+
const dry_run_1 = require("./executer/dry-run");
|
|
13
17
|
const label_values_1 = require("./executer/label-values");
|
|
14
18
|
const default_build_file_1 = require("./parser/default-build-file");
|
|
15
19
|
const schema_parser_1 = require("./schema/schema-parser");
|
|
@@ -17,6 +21,9 @@ const work_scope_parser_1 = require("./schema/work-scope-parser");
|
|
|
17
21
|
const reference_parser_1 = require("./schema/reference-parser");
|
|
18
22
|
const colors_1 = __importDefault(require("colors"));
|
|
19
23
|
const version_1 = require("./version");
|
|
24
|
+
const read_only_1 = require("./cache/read-only");
|
|
25
|
+
const units_1 = require("./utils/units");
|
|
26
|
+
const cache_inventory_1 = require("./cache/cache-inventory");
|
|
20
27
|
async function createCli(fileName, environment, workScope) {
|
|
21
28
|
const { ctx, scope } = await (0, schema_parser_1.createParseContext)(fileName, environment);
|
|
22
29
|
const referencedScope = await (0, reference_parser_1.parseReferences)(ctx, scope, environment);
|
|
@@ -233,6 +240,179 @@ async function getProgram(environment, argv) {
|
|
|
233
240
|
program.error('Detected errors in the hammerkit configuration', { exitCode: 1 });
|
|
234
241
|
}
|
|
235
242
|
});
|
|
243
|
+
program
|
|
244
|
+
.command('graph [task]')
|
|
245
|
+
.description('serialize the build graph (tasks, services, deps/needs edges) as mermaid or dot')
|
|
246
|
+
.addOption(new commander_1.Option('-f, --filter <labels...>', 'filter task and services with labels'))
|
|
247
|
+
.addOption(new commander_1.Option('-e, --exclude <labels...>', 'exclude task and services with labels'))
|
|
248
|
+
.addOption(new commander_1.Option('--env <name>', 'environment'))
|
|
249
|
+
.addOption(new commander_1.Option('--format <format>', 'output format').default('mermaid').choices(['mermaid', 'dot']))
|
|
250
|
+
.action(async (task, options) => {
|
|
251
|
+
try {
|
|
252
|
+
const cli = await createCli(fileName, environment, task ? { taskName: task, environmentName: options.env ?? null } : parseWorkLabelScope(options));
|
|
253
|
+
const serialization = cli.graph(options.format);
|
|
254
|
+
environment.stdout.write(`${serialization.output}\n`);
|
|
255
|
+
if (serialization.cycle) {
|
|
256
|
+
environment.console.warn(`cycle detected: ${serialization.cycle.join(' -> ')}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (e) {
|
|
260
|
+
if (e instanceof commander_1.CommanderError) {
|
|
261
|
+
throw e;
|
|
262
|
+
}
|
|
263
|
+
environment.console.error((0, log_1.getErrorMessage)(e));
|
|
264
|
+
program.error('Graph was not successful', { exitCode: 1 });
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
program
|
|
268
|
+
.command('explain [task]')
|
|
269
|
+
.description('explain whether tasks would be a cache hit or miss, without running them')
|
|
270
|
+
.addOption(new commander_1.Option('-f, --filter <labels...>', 'filter task and services with labels'))
|
|
271
|
+
.addOption(new commander_1.Option('-e, --exclude <labels...>', 'exclude task and services with labels'))
|
|
272
|
+
.addOption(new commander_1.Option('--env <name>', 'environment'))
|
|
273
|
+
.addOption(new commander_1.Option('--cache <method>', 'caching method to compare')
|
|
274
|
+
.default('checksum')
|
|
275
|
+
.choices(['checksum', 'modify-date', 'none']))
|
|
276
|
+
.addOption(new commander_1.Option('--json', 'emit the explanation as JSON').default(false))
|
|
277
|
+
.action(async (task, options) => {
|
|
278
|
+
try {
|
|
279
|
+
const cli = await createCli(fileName, environment, task ? { taskName: task, environmentName: options.env ?? null } : parseWorkLabelScope(options));
|
|
280
|
+
const explanations = await cli.explain({ cacheDefault: options.cache });
|
|
281
|
+
if (options.json) {
|
|
282
|
+
environment.stdout.write(`${JSON.stringify(explanations, null, 2)}\n`);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
for (const explanation of explanations) {
|
|
286
|
+
const label = explanation.status === 'hit'
|
|
287
|
+
? colors_1.default.green('cache hit')
|
|
288
|
+
: explanation.status === 'uncacheable'
|
|
289
|
+
? colors_1.default.grey('uncacheable')
|
|
290
|
+
: colors_1.default.yellow('cache miss');
|
|
291
|
+
environment.stdout.write(`• ${explanation.taskName}: ${label}\n`);
|
|
292
|
+
for (const cause of explanation.causes) {
|
|
293
|
+
(0, log_1.printProperty)(environment, 'cause', (0, explain_1.describeCause)(cause));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (e) {
|
|
298
|
+
if (e instanceof commander_1.CommanderError) {
|
|
299
|
+
throw e;
|
|
300
|
+
}
|
|
301
|
+
environment.console.error((0, log_1.getErrorMessage)(e));
|
|
302
|
+
program.error('Explain was not successful', { exitCode: 1 });
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
const cacheCommand = program.command('cache').description('inspect, prune and move cache entries');
|
|
306
|
+
for (const direction of ['pull', 'push']) {
|
|
307
|
+
cacheCommand
|
|
308
|
+
.command(`${direction} [task]`)
|
|
309
|
+
.description(direction === 'pull'
|
|
310
|
+
? 'fetch the cache entries of the current state from a remote cache, without running tasks'
|
|
311
|
+
: 'upload the locally cached entries of the current state to a remote cache, without running tasks')
|
|
312
|
+
.addOption(new commander_1.Option('--remote <name>', 'remote cache declared in the caches block').makeOptionMandatory())
|
|
313
|
+
.addOption(new commander_1.Option('-f, --filter <labels...>', 'filter task and services with labels'))
|
|
314
|
+
.addOption(new commander_1.Option('-e, --exclude <labels...>', 'exclude task and services with labels'))
|
|
315
|
+
.addOption(new commander_1.Option('--env <name>', 'environment'))
|
|
316
|
+
.addOption(new commander_1.Option('--cache <method>', 'caching method to compare')
|
|
317
|
+
.default('checksum')
|
|
318
|
+
.choices(['checksum', 'modify-date', 'none']))
|
|
319
|
+
.action(async (task, options) => {
|
|
320
|
+
try {
|
|
321
|
+
if (direction === 'push' && (0, read_only_1.isCacheReadOnly)(false, environment.processEnvs)) {
|
|
322
|
+
program.error(`cache is read-only (${read_only_1.CACHE_READ_ONLY_ENV} is set), refusing to push`, { exitCode: 1 });
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const cli = await createCli(fileName, environment, task ? { taskName: task, environmentName: options.env ?? null } : parseWorkLabelScope(options));
|
|
326
|
+
const results = await cli.syncCache({ direction, remote: options.remote, cacheDefault: options.cache });
|
|
327
|
+
const labels = {
|
|
328
|
+
transferred: colors_1.default.green(direction === 'pull' ? 'pulled' : 'pushed'),
|
|
329
|
+
present: colors_1.default.grey('already present'),
|
|
330
|
+
missing: colors_1.default.yellow(direction === 'pull' ? 'not in remote' : 'not in local cache'),
|
|
331
|
+
skipped: colors_1.default.grey('skipped'),
|
|
332
|
+
};
|
|
333
|
+
for (const result of results) {
|
|
334
|
+
environment.stdout.write(`• ${result.taskName}: ${labels[result.status]}\n`);
|
|
335
|
+
}
|
|
336
|
+
const moved = results.filter((r) => r.status === 'transferred').length;
|
|
337
|
+
environment.stdout.write(`${moved}/${results.length} entries ${direction === 'pull' ? 'pulled' : 'pushed'}\n`);
|
|
338
|
+
}
|
|
339
|
+
catch (e) {
|
|
340
|
+
if (e instanceof commander_1.CommanderError) {
|
|
341
|
+
throw e;
|
|
342
|
+
}
|
|
343
|
+
environment.console.error((0, log_1.getErrorMessage)(e));
|
|
344
|
+
program.error(`Cache ${direction} was not successful: ${(0, log_1.getErrorMessage)(e)}`, { exitCode: 1 });
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
const describeEntry = (entry) => `${entry.taskName ?? colors_1.default.grey(entry.taskId.substring(0, 12))} ${colors_1.default.grey(entry.stateKey.substring(0, 12))} ` +
|
|
349
|
+
`${entry.size === null ? '?' : (0, units_1.formatSize)(entry.size)}` +
|
|
350
|
+
(entry.lastAccessedAt ?? entry.createdAt
|
|
351
|
+
? colors_1.default.grey(` last used ${(0, units_1.formatDuration)(Date.now() - (entry.lastAccessedAt ?? entry.createdAt ?? 0))} ago`)
|
|
352
|
+
: '');
|
|
353
|
+
const totalSize = (entries) => (0, units_1.formatSize)(entries.reduce((sum, entry) => sum + (entry.size ?? 0), 0));
|
|
354
|
+
const countOf = (count) => `${count} ${count === 1 ? 'entry' : 'entries'}`;
|
|
355
|
+
cacheCommand
|
|
356
|
+
.command('ls')
|
|
357
|
+
.description('list the entries of a cache (default: the local default cache)')
|
|
358
|
+
.addOption(new commander_1.Option('--remote <name>', 'cache declared in the caches block to list'))
|
|
359
|
+
.addOption(new commander_1.Option('--json', 'emit the entries as JSON').default(false))
|
|
360
|
+
.action(async (options) => {
|
|
361
|
+
try {
|
|
362
|
+
const cli = await createCli(fileName, environment, parseWorkLabelScope({}));
|
|
363
|
+
const entries = await cli.listCache(options.remote);
|
|
364
|
+
if (options.json) {
|
|
365
|
+
environment.stdout.write(`${JSON.stringify(entries, null, 2)}\n`);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
for (const entry of entries) {
|
|
369
|
+
environment.stdout.write(`• ${describeEntry(entry)}\n`);
|
|
370
|
+
}
|
|
371
|
+
environment.stdout.write(`${countOf(entries.length)}, ${totalSize(entries)} total\n`);
|
|
372
|
+
}
|
|
373
|
+
catch (e) {
|
|
374
|
+
if (e instanceof commander_1.CommanderError) {
|
|
375
|
+
throw e;
|
|
376
|
+
}
|
|
377
|
+
program.error(`Cache ls was not successful: ${(0, log_1.getErrorMessage)(e)}`, { exitCode: 1 });
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
cacheCommand
|
|
381
|
+
.command('prune')
|
|
382
|
+
.description('remove cache entries by retention policy (default: the local default cache)')
|
|
383
|
+
.addOption(new commander_1.Option('--remote <name>', 'cache declared in the caches block to prune'))
|
|
384
|
+
.addOption(new commander_1.Option('--max-age <duration>', 'remove entries not used within this long (e.g. 30d)').argParser(units_1.parseDuration))
|
|
385
|
+
.addOption(new commander_1.Option('--max-size <size>', 'remove least recently used entries above this size (e.g. 5Gi)').argParser(units_1.parseSize))
|
|
386
|
+
.addOption(new commander_1.Option('--keep <count>', 'keep only the newest versions per task').argParser((v) => parseInt(v, 10)))
|
|
387
|
+
.addOption(new commander_1.Option('--dry-run', 'show what would be removed without removing it').default(false))
|
|
388
|
+
.action(async (options) => {
|
|
389
|
+
try {
|
|
390
|
+
const cli = await createCli(fileName, environment, parseWorkLabelScope({}));
|
|
391
|
+
const policy = cli.retentionPolicy(options.remote, {
|
|
392
|
+
maxAge: options.maxAge,
|
|
393
|
+
maxSize: options.maxSize,
|
|
394
|
+
keepPerTask: options.keep,
|
|
395
|
+
});
|
|
396
|
+
if (!(0, cache_inventory_1.hasPolicy)(policy)) {
|
|
397
|
+
program.error('no retention policy: pass --max-age, --max-size or --keep, or declare retention on the cache', { exitCode: 1 });
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const plan = await cli.pruneCache(options.remote, policy, options.dryRun);
|
|
401
|
+
for (const entry of plan.evict) {
|
|
402
|
+
environment.stdout.write(`• ${options.dryRun ? 'would remove' : 'removed'} ${describeEntry(entry)}\n`);
|
|
403
|
+
}
|
|
404
|
+
for (const note of plan.unavailable) {
|
|
405
|
+
environment.console.warn(note);
|
|
406
|
+
}
|
|
407
|
+
environment.stdout.write(`${options.dryRun ? 'would remove' : 'removed'} ${countOf(plan.evict.length)} (${totalSize(plan.evict)}), ` + `kept ${countOf(plan.keep.length)} (${totalSize(plan.keep)})\n`);
|
|
408
|
+
}
|
|
409
|
+
catch (e) {
|
|
410
|
+
if (e instanceof commander_1.CommanderError) {
|
|
411
|
+
throw e;
|
|
412
|
+
}
|
|
413
|
+
program.error(`Cache prune was not successful: ${(0, log_1.getErrorMessage)(e)}`, { exitCode: 1 });
|
|
414
|
+
}
|
|
415
|
+
});
|
|
236
416
|
program
|
|
237
417
|
.command('up')
|
|
238
418
|
.description('start services(s)')
|
|
@@ -248,6 +428,8 @@ async function getProgram(environment, argv) {
|
|
|
248
428
|
.addOption(new commander_1.Option('--cache <method>', 'caching method to compare')
|
|
249
429
|
.default('checksum')
|
|
250
430
|
.choices(['checksum', 'modify-date', 'none']))
|
|
431
|
+
.addOption(new commander_1.Option('--cache-read-only', `restore from cache backends but never push to them (or set ${read_only_1.CACHE_READ_ONLY_ENV}=1)`).default(false))
|
|
432
|
+
.addOption(new commander_1.Option('--timeout <duration>', 'fail tasks without their own timeout after this long (e.g. 10m)').argParser(units_1.parseDuration))
|
|
251
433
|
.action(async (options) => {
|
|
252
434
|
try {
|
|
253
435
|
const scope = parseWorkLabelScope(options);
|
|
@@ -258,6 +440,8 @@ async function getProgram(environment, argv) {
|
|
|
258
440
|
workers: options.concurrency,
|
|
259
441
|
logMode: options.log,
|
|
260
442
|
daemon: options.daemon,
|
|
443
|
+
cacheReadOnly: (0, read_only_1.isCacheReadOnly)(options.cacheReadOnly, environment.processEnvs),
|
|
444
|
+
timeout: options.timeout ?? null,
|
|
261
445
|
});
|
|
262
446
|
if (!result.success) {
|
|
263
447
|
program.error('Up was not successful', { exitCode: 1 });
|
|
@@ -311,22 +495,72 @@ async function getProgram(environment, argv) {
|
|
|
311
495
|
.addOption(new commander_1.Option('--cache <method>', 'caching method to compare')
|
|
312
496
|
.default('checksum')
|
|
313
497
|
.choices(['checksum', 'modify-date', 'none']))
|
|
498
|
+
.addOption(new commander_1.Option('--no-summary', 'do not print the end-of-run summary'))
|
|
499
|
+
.addOption(new commander_1.Option('--summary-json', 'emit the end-of-run summary as JSON').default(false))
|
|
500
|
+
.addOption(new commander_1.Option('--explain', 'print the cache-miss cause when a task rebuilds').default(false))
|
|
501
|
+
.addOption(new commander_1.Option('--dry-run', 'print the execution plan with predicted cache hits/misses without running').default(false))
|
|
502
|
+
.addOption(new commander_1.Option('--cache-read-only', `restore from cache backends but never push to them (or set ${read_only_1.CACHE_READ_ONLY_ENV}=1)`).default(false))
|
|
503
|
+
.addOption(new commander_1.Option('--timeout <duration>', 'fail tasks without their own timeout after this long (e.g. 10m)').argParser(units_1.parseDuration))
|
|
504
|
+
.addOption(new commander_1.Option('--no-skip-deps', 'run dependencies even when every task needing them is a cache hit'))
|
|
314
505
|
.action(async (task, options) => {
|
|
315
506
|
try {
|
|
316
|
-
|
|
507
|
+
// For machine-readable JSON, suppress the human progress logger (which
|
|
508
|
+
// also writes to stdout) so the only thing on stdout is the JSON.
|
|
509
|
+
const runEnvironment = options.summaryJson
|
|
510
|
+
? { ...environment, stdout: (0, empty_writable_1.emptyWritable)(), console: (0, log_1.consoleContext)((0, empty_writable_1.emptyWritable)()) }
|
|
511
|
+
: environment;
|
|
512
|
+
const cli = await createCli(fileName, runEnvironment, task ? { taskName: task, environmentName: options.env } : parseWorkLabelScope(options));
|
|
317
513
|
if (cli.tasks().length === 0) {
|
|
318
514
|
program.error('No tasks found', { exitCode: 127 });
|
|
319
515
|
return;
|
|
320
516
|
}
|
|
517
|
+
if (options.dryRun) {
|
|
518
|
+
const plan = await cli.dryRun({ cacheDefault: options.cache });
|
|
519
|
+
if (plan.cycle) {
|
|
520
|
+
program.error(`task cycle detected ${plan.cycle.join(' -> ')}`, { exitCode: 1 });
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
(0, dry_run_1.printDryRun)(environment, plan);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const runStart = Date.now();
|
|
321
527
|
const result = await cli.runExec({
|
|
322
528
|
cacheDefault: options.cache,
|
|
323
529
|
watch: options.watch,
|
|
324
530
|
workers: options.concurrency,
|
|
325
531
|
logMode: options.log,
|
|
532
|
+
explain: options.explain,
|
|
533
|
+
cacheReadOnly: (0, read_only_1.isCacheReadOnly)(options.cacheReadOnly, environment.processEnvs),
|
|
534
|
+
timeout: options.timeout ?? null,
|
|
535
|
+
skipDeps: options.skipDeps !== false,
|
|
326
536
|
});
|
|
537
|
+
// Reporting only: the summary never changes the exit code or behavior.
|
|
538
|
+
if (!options.watch) {
|
|
539
|
+
const summary = (0, run_summary_1.summarizeRun)(result.state, Date.now() - runStart);
|
|
540
|
+
if (options.summaryJson) {
|
|
541
|
+
environment.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
|
|
542
|
+
}
|
|
543
|
+
else if (options.summary !== false) {
|
|
544
|
+
(0, run_summary_1.printRunSummary)(environment, summary);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
327
547
|
if (!result.success) {
|
|
328
548
|
program.error('Execution was not successful', { exitCode: 1 });
|
|
329
549
|
}
|
|
550
|
+
// Declared retention keeps local caches bounded without anyone having
|
|
551
|
+
// to remember `cache prune`. Never on remotes, never in read-only mode.
|
|
552
|
+
if (!options.watch && !(0, read_only_1.isCacheReadOnly)(options.cacheReadOnly, environment.processEnvs)) {
|
|
553
|
+
try {
|
|
554
|
+
for (const { cacheName, plan } of await cli.autoPrune()) {
|
|
555
|
+
if (plan.evict.length > 0) {
|
|
556
|
+
runEnvironment.console.info(`pruned ${plan.evict.length} old ${plan.evict.length === 1 ? 'entry' : 'entries'} from cache "${cacheName}"`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
catch (e) {
|
|
561
|
+
runEnvironment.console.warn(`automatic cache prune failed: ${(0, log_1.getErrorMessage)(e)}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
330
564
|
}
|
|
331
565
|
catch (e) {
|
|
332
566
|
if (e instanceof commander_1.CommanderError) {
|
|
@@ -8,6 +8,7 @@ const labels_schema_1 = require("./labels-schema");
|
|
|
8
8
|
const cache_schema_1 = require("./cache-schema");
|
|
9
9
|
const build_file_task_command_schema_1 = require("./build-file-task-command-schema");
|
|
10
10
|
const build_file_volume_schema_1 = require("./build-file-volume-schema");
|
|
11
|
+
const duration_schema_1 = require("./duration-schema");
|
|
11
12
|
exports.buildFileBaseTaskSchema = (0, zod_1.object)({
|
|
12
13
|
deps: (0, zod_1.array)((0, zod_1.string)()).optional(),
|
|
13
14
|
needs: (0, zod_1.array)(build_file_need_schema_1.buildFileNeedSchema).optional(),
|
|
@@ -23,5 +24,8 @@ exports.buildFileBaseTaskSchema = (0, zod_1.object)({
|
|
|
23
24
|
// A task that watches its own sources and restarts itself (e.g. `ng serve`,
|
|
24
25
|
// `tsc -w`). In watch mode hammerkit then does not watch/restart it itself.
|
|
25
26
|
continuous: (0, zod_1.boolean)().optional(),
|
|
27
|
+
// Maximum execution time, e.g. `30s`, `10m`, `1h30m`. The task fails when it
|
|
28
|
+
// runs longer. Does not affect the cache key (ADR-0002).
|
|
29
|
+
timeout: duration_schema_1.durationSchema.optional(),
|
|
26
30
|
});
|
|
27
31
|
//# sourceMappingURL=build-file-base-task-schema.js.map
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.cacheSchema = exports.taskCacheRefSchema = exports.buildFileCacheSchema = exports.cacheBackendSchema = exports.cacheBackendS3Schema = exports.cacheBackendLocalSchema = exports.cacheMethodSchema = void 0;
|
|
3
|
+
exports.cacheSchema = exports.taskCacheRefSchema = exports.buildFileCacheSchema = exports.cacheRetentionSchema = exports.cacheBackendSchema = exports.cacheBackendRegistrySchema = exports.cacheBackendS3Schema = exports.cacheBackendLocalSchema = exports.cacheMethodSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
+
const duration_schema_1 = require("./duration-schema");
|
|
6
|
+
const size_schema_1 = require("./size-schema");
|
|
5
7
|
exports.cacheMethodSchema = (0, zod_1.union)([(0, zod_1.literal)('checksum'), (0, zod_1.literal)('modify-date'), (0, zod_1.literal)('none')]);
|
|
6
8
|
exports.cacheBackendLocalSchema = (0, zod_1.object)({
|
|
7
9
|
type: (0, zod_1.literal)('local'),
|
|
@@ -15,10 +17,27 @@ exports.cacheBackendS3Schema = (0, zod_1.object)({
|
|
|
15
17
|
prefix: (0, zod_1.string)().optional(),
|
|
16
18
|
forcePathStyle: zod_1.z.boolean().optional(),
|
|
17
19
|
}).strict();
|
|
18
|
-
exports.
|
|
20
|
+
exports.cacheBackendRegistrySchema = (0, zod_1.object)({
|
|
21
|
+
type: (0, zod_1.literal)('registry'),
|
|
22
|
+
// repository to store entries in, e.g. `ghcr.io/org/hammerkit-cache`
|
|
23
|
+
repository: (0, zod_1.string)(),
|
|
24
|
+
// talk plain http (defaults to true only for localhost registries)
|
|
25
|
+
insecure: zod_1.z.boolean().optional(),
|
|
26
|
+
}).strict();
|
|
27
|
+
exports.cacheBackendSchema = (0, zod_1.union)([exports.cacheBackendLocalSchema, exports.cacheBackendS3Schema, exports.cacheBackendRegistrySchema]);
|
|
28
|
+
exports.cacheRetentionSchema = (0, zod_1.object)({
|
|
29
|
+
// evict entries not used within this long, e.g. `30d`
|
|
30
|
+
maxAge: duration_schema_1.durationSchema.optional(),
|
|
31
|
+
// evict least recently used entries until the cache is at most this big, e.g. `5Gi`
|
|
32
|
+
maxSize: size_schema_1.sizeSchema.optional(),
|
|
33
|
+
// keep only the newest N versions per task
|
|
34
|
+
keepPerTask: zod_1.z.number().int().positive().optional(),
|
|
35
|
+
}).strict();
|
|
19
36
|
exports.buildFileCacheSchema = (0, zod_1.object)({
|
|
20
37
|
method: exports.cacheMethodSchema,
|
|
21
38
|
backend: exports.cacheBackendSchema,
|
|
39
|
+
// applied by `cache prune`, and automatically after a run for local caches
|
|
40
|
+
retention: exports.cacheRetentionSchema.optional(),
|
|
22
41
|
}).strict();
|
|
23
42
|
exports.taskCacheRefSchema = (0, zod_1.union)([
|
|
24
43
|
exports.cacheMethodSchema,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.durationSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const units_1 = require("../utils/units");
|
|
6
|
+
function isDuration(value) {
|
|
7
|
+
try {
|
|
8
|
+
(0, units_1.parseDuration)(value);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.durationSchema = (0, zod_1.string)().refine(isDuration, (value) => ({
|
|
16
|
+
message: `invalid duration "${value}", expected e.g. 30s, 5m, 1h30m or 30d`,
|
|
17
|
+
}));
|
|
18
|
+
//# sourceMappingURL=duration-schema.js.map
|
|
@@ -8,6 +8,7 @@ const read_env_file_1 = require("../parser/read-env-file");
|
|
|
8
8
|
const label_values_1 = require("../executer/label-values");
|
|
9
9
|
const merge_environment_variables_1 = require("../environment/merge-environment-variables");
|
|
10
10
|
const resolve_cache_1 = require("../cache/resolve-cache");
|
|
11
|
+
const project_root_1 = require("../planner/project-root");
|
|
11
12
|
function combinePath(namePrefix, name) {
|
|
12
13
|
return namePrefix.length > 0 ? `${namePrefix}:${name}` : name;
|
|
13
14
|
}
|
|
@@ -20,6 +21,7 @@ async function parseReferences(ctx, mainScope, environment) {
|
|
|
20
21
|
envFiles: {},
|
|
21
22
|
environments: {},
|
|
22
23
|
caches: {},
|
|
24
|
+
projectRoot: await (0, project_root_1.findProjectRoot)(mainScope.cwd, environment),
|
|
23
25
|
};
|
|
24
26
|
for (const file of Object.values(ctx.files)) {
|
|
25
27
|
if (!reference.envFiles[file.cwd]) {
|
|
@@ -97,6 +99,7 @@ function applySchemaExtension(reference, task) {
|
|
|
97
99
|
applySchemaExtension(reference, extend);
|
|
98
100
|
task.schema.shell = task.schema.shell ?? extend.schema.shell;
|
|
99
101
|
task.schema.cache = task.schema.cache ?? extend.schema.cache;
|
|
102
|
+
task.schema.timeout = task.schema.timeout ?? extend.schema.timeout;
|
|
100
103
|
task.schema.cmds = extendArray(task.schema.cmds, extend.schema.cmds);
|
|
101
104
|
task.schema.src = extendArray(task.schema.src, extend.schema.src);
|
|
102
105
|
task.schema.generates = extendArray(task.schema.generates, extend.schema.generates);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sizeSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const units_1 = require("../utils/units");
|
|
6
|
+
function isSize(value) {
|
|
7
|
+
try {
|
|
8
|
+
(0, units_1.parseSize)(value);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.sizeSchema = (0, zod_1.string)().refine(isSize, (value) => ({
|
|
16
|
+
message: `invalid size "${value}", expected e.g. 500Mi, 5Gi or 2G`,
|
|
17
|
+
}));
|
|
18
|
+
//# sourceMappingURL=size-schema.js.map
|
|
@@ -17,8 +17,20 @@ function defaultEnvironment(context) {
|
|
|
17
17
|
}
|
|
18
18
|
exports.defaultEnvironment = defaultEnvironment;
|
|
19
19
|
function getWorkContext(context, scope, environment) {
|
|
20
|
-
const workTree = {
|
|
21
|
-
|
|
20
|
+
const workTree = {
|
|
21
|
+
services: {},
|
|
22
|
+
environment: defaultEnvironment(context),
|
|
23
|
+
tasks: {},
|
|
24
|
+
caches: context.caches,
|
|
25
|
+
};
|
|
26
|
+
const filteredWorkTree = {
|
|
27
|
+
services: {},
|
|
28
|
+
environment: defaultEnvironment(context),
|
|
29
|
+
tasks: {},
|
|
30
|
+
caches: context.caches,
|
|
31
|
+
};
|
|
32
|
+
const requested = [];
|
|
33
|
+
filteredWorkTree.requested = requested;
|
|
22
34
|
for (const [envName, env] of Object.entries(context.environments)) {
|
|
23
35
|
if (envName === scope.environmentName) {
|
|
24
36
|
filteredWorkTree.environment = (0, append_work_environment_1.appendWorkEnvironment)(env);
|
|
@@ -28,6 +40,7 @@ function getWorkContext(context, scope, environment) {
|
|
|
28
40
|
for (const task of Object.values(context.tasks)) {
|
|
29
41
|
const item = (0, append_work_task_1.appendWorkTask)(workTree, task.cwd, task, environment, context);
|
|
30
42
|
if (matchesTaskWorkScope(item, scope)) {
|
|
43
|
+
requested.push(item.name);
|
|
31
44
|
applyTask(filteredWorkTree, item);
|
|
32
45
|
}
|
|
33
46
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatSize = exports.parseSize = exports.formatDuration = exports.parseDuration = void 0;
|
|
4
|
+
const DURATION_UNITS = {
|
|
5
|
+
ms: 1,
|
|
6
|
+
s: 1000,
|
|
7
|
+
m: 60000,
|
|
8
|
+
h: 3600000,
|
|
9
|
+
d: 86400000,
|
|
10
|
+
};
|
|
11
|
+
// Parse a duration such as `500ms`, `30s`, `5m`, `1h30m` or `30d` into
|
|
12
|
+
// milliseconds. Every component needs a unit, and the total must be positive.
|
|
13
|
+
function parseDuration(value) {
|
|
14
|
+
const pattern = /(\d+(?:\.\d+)?)(ms|s|m|h|d)/y;
|
|
15
|
+
let total = 0;
|
|
16
|
+
let offset = 0;
|
|
17
|
+
while (offset < value.length) {
|
|
18
|
+
pattern.lastIndex = offset;
|
|
19
|
+
const match = pattern.exec(value);
|
|
20
|
+
if (!match) {
|
|
21
|
+
throw new Error(`invalid duration "${value}", expected e.g. 30s, 5m, 1h30m or 30d`);
|
|
22
|
+
}
|
|
23
|
+
total += parseFloat(match[1]) * DURATION_UNITS[match[2]];
|
|
24
|
+
offset = pattern.lastIndex;
|
|
25
|
+
}
|
|
26
|
+
if (total <= 0) {
|
|
27
|
+
throw new Error(`invalid duration "${value}", expected e.g. 30s, 5m, 1h30m or 30d`);
|
|
28
|
+
}
|
|
29
|
+
return Math.round(total);
|
|
30
|
+
}
|
|
31
|
+
exports.parseDuration = parseDuration;
|
|
32
|
+
function formatDuration(ms) {
|
|
33
|
+
if (ms < 1000) {
|
|
34
|
+
return `${ms}ms`;
|
|
35
|
+
}
|
|
36
|
+
let remaining = Math.round(ms / 1000);
|
|
37
|
+
const parts = [];
|
|
38
|
+
for (const [unit, seconds] of [
|
|
39
|
+
['d', 86400],
|
|
40
|
+
['h', 3600],
|
|
41
|
+
['m', 60],
|
|
42
|
+
['s', 1],
|
|
43
|
+
]) {
|
|
44
|
+
if (remaining >= seconds) {
|
|
45
|
+
parts.push(`${Math.floor(remaining / seconds)}${unit}`);
|
|
46
|
+
remaining %= seconds;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return parts.join('');
|
|
50
|
+
}
|
|
51
|
+
exports.formatDuration = formatDuration;
|
|
52
|
+
const SIZE_UNITS = {
|
|
53
|
+
'': 1,
|
|
54
|
+
K: 1000,
|
|
55
|
+
M: 1000 ** 2,
|
|
56
|
+
G: 1000 ** 3,
|
|
57
|
+
T: 1000 ** 4,
|
|
58
|
+
Ki: 1024,
|
|
59
|
+
Mi: 1024 ** 2,
|
|
60
|
+
Gi: 1024 ** 3,
|
|
61
|
+
Ti: 1024 ** 4,
|
|
62
|
+
};
|
|
63
|
+
// Parse a size such as `500Mi`, `5Gi` or `2G` (Kubernetes-style suffixes;
|
|
64
|
+
// binary `Ki/Mi/Gi/Ti`, decimal `K/M/G/T`, none for bytes) into bytes.
|
|
65
|
+
function parseSize(value) {
|
|
66
|
+
const match = /^(\d+(?:\.\d+)?)(Ki|Mi|Gi|Ti|K|M|G|T)?$/.exec(value);
|
|
67
|
+
if (!match) {
|
|
68
|
+
throw new Error(`invalid size "${value}", expected e.g. 500Mi, 5Gi or 2G`);
|
|
69
|
+
}
|
|
70
|
+
return Math.round(parseFloat(match[1]) * SIZE_UNITS[match[2] ?? '']);
|
|
71
|
+
}
|
|
72
|
+
exports.parseSize = parseSize;
|
|
73
|
+
function formatSize(bytes) {
|
|
74
|
+
for (const [unit, factor] of [
|
|
75
|
+
['Ti', 1024 ** 4],
|
|
76
|
+
['Gi', 1024 ** 3],
|
|
77
|
+
['Mi', 1024 ** 2],
|
|
78
|
+
['Ki', 1024],
|
|
79
|
+
]) {
|
|
80
|
+
if (bytes >= factor) {
|
|
81
|
+
return `${(bytes / factor).toFixed(1).replace(/\.0$/, '')}${unit}`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return `${bytes}B`;
|
|
85
|
+
}
|
|
86
|
+
exports.formatSize = formatSize;
|
|
87
|
+
//# sourceMappingURL=units.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.
|
|
4
|
+
"version": "1.7.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"build",
|
|
7
7
|
"build-tool",
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
],
|
|
15
15
|
"homepage": "https://no0dles.gitbook.io/hammerkit/",
|
|
16
16
|
"bin": "dist/index.js",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"!dist/**/*.spec.*",
|
|
20
|
+
"!dist/testing",
|
|
21
|
+
"!dist/**/*.map",
|
|
22
|
+
"build.schema.json"
|
|
23
|
+
],
|
|
17
24
|
"bugs": {
|
|
18
25
|
"url": "https://github.com/no0dles/hammerkit/issues"
|
|
19
26
|
},
|
|
@@ -34,6 +41,7 @@
|
|
|
34
41
|
"build": "tsc -b tsconfig.json",
|
|
35
42
|
"test": "vitest run",
|
|
36
43
|
"test:integration": "vitest run --mode integration",
|
|
44
|
+
"test:mutation": "stryker run",
|
|
37
45
|
"format": "prettier -w src/** && eslint . --fix --ext .ts",
|
|
38
46
|
"lint": "eslint . --ext .ts"
|
|
39
47
|
},
|
|
@@ -51,6 +59,8 @@
|
|
|
51
59
|
"zod": "^3.22.4"
|
|
52
60
|
},
|
|
53
61
|
"devDependencies": {
|
|
62
|
+
"@stryker-mutator/core": "^10.0.0",
|
|
63
|
+
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
54
64
|
"@types/dockerode": "^3.3.23",
|
|
55
65
|
"@types/minimatch": "^5.1.2",
|
|
56
66
|
"@types/node": "^24.0.0",
|
|
@@ -60,6 +70,7 @@
|
|
|
60
70
|
"@vitest/coverage-istanbul": "^2.1.8",
|
|
61
71
|
"@yao-pkg/pkg": "^6.20.0",
|
|
62
72
|
"eslint": "^8.56.0",
|
|
73
|
+
"fast-check": "^4.10.2",
|
|
63
74
|
"prettier": "^3.1.1",
|
|
64
75
|
"typescript": "^5.3.3",
|
|
65
76
|
"vitest": "^2.1.8",
|
package/.agents/FEEDBACK.md
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# Feedback log
|
|
2
|
-
|
|
3
|
-
Durable record of corrections and preferences the user has given AI agents working
|
|
4
|
-
in this repo. The point of this file is that the user should **not have to repeat
|
|
5
|
-
himself**.
|
|
6
|
-
|
|
7
|
-
**Contract — every agent, every session:**
|
|
8
|
-
|
|
9
|
-
1. **Read this file before you start working.** Apply every rule here without being
|
|
10
|
-
reminded.
|
|
11
|
-
2. **Don't re-ask anything already answered here.** If a rule covers the situation,
|
|
12
|
-
follow it instead of asking.
|
|
13
|
-
3. **When the user corrects you or states a preference, append an entry here
|
|
14
|
-
immediately** — same session, before moving on — so it is captured for next
|
|
15
|
-
time. Keep entries short: a rule, **Why**, **How to apply**, and a date.
|
|
16
|
-
|
|
17
|
-
Related: the companion `do-the-work` skill explains the behavior; this file is the
|
|
18
|
-
running record.
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Rules
|
|
23
|
-
|
|
24
|
-
### Do the work — don't propose avoiding it _(2026-05-25)_
|
|
25
|
-
|
|
26
|
-
Skipping, disabling, excluding, `.skip` / `it.skip` / `xfail`, commenting-out,
|
|
27
|
-
deleting, loosening/weakening an assertion, or stopping early is **never** an
|
|
28
|
-
acceptable way to resolve a failing test or a red build. Fix the root cause.
|
|
29
|
-
|
|
30
|
-
- **Why:** The user asked for the thing to be *fixed*. Avoiding the work is not a
|
|
31
|
-
smaller version of the job — it's not doing the job, and it hides a regression.
|
|
32
|
-
Avoidance being the agent's default reflex is what the user finds worrying.
|
|
33
|
-
- **How to apply:** Diagnose the underlying cause (timing, environment,
|
|
34
|
-
file-watching, resource leaks, ordering, a real bug) and fix it so the check
|
|
35
|
-
passes legitimately. Only ever skip/disable something if the user *explicitly*
|
|
36
|
-
asks for it in that conversation.
|
|
37
|
-
|
|
38
|
-
### "Fix CI" means fix the failure, not remove the test _(2026-05-25)_
|
|
39
|
-
|
|
40
|
-
When the task is to get CI green, the deliverable is a green run achieved by
|
|
41
|
-
fixing what is broken — not by removing, skipping, or muting the failing check.
|
|
42
|
-
|
|
43
|
-
- **Why:** Fixing CI *is* the requested work. Removing the test makes CI green
|
|
44
|
-
while leaving the actual problem in place.
|
|
45
|
-
- **How to apply:** Use the `fix-ci` skill loop (diagnose → fix → verify →
|
|
46
|
-
commit → push → watch). If a failure is genuinely container/k8s/infra-only and
|
|
47
|
-
cannot be reproduced on this host, say so explicitly and rely on CI to confirm
|
|
48
|
-
the fix — that is not the same as skipping the test.
|
|
49
|
-
|
|
50
|
-
### Prefer configurable timeouts over skips or huge fixed values _(2026-05-25)_
|
|
51
|
-
|
|
52
|
-
When a test hangs or flakes, make the timeout configurable (e.g. via env var)
|
|
53
|
-
rather than skipping the test or hardcoding a giant timeout.
|
|
54
|
-
|
|
55
|
-
- **Why:** CI is slower than local; a configurable budget lets local fail fast
|
|
56
|
-
(surfacing hangs quickly) while CI gets the time it needs. Reducing flake is
|
|
57
|
-
always desirable; skipping never is.
|
|
58
|
-
- **How to apply:** Drive the timeout from an env var with a sensible local
|
|
59
|
-
default and a longer CI value; fix the underlying cause of the hang/flake.
|
|
60
|
-
|
|
61
|
-
### Ask implementation questions freely; never ask permission to avoid work _(2026-05-25)_
|
|
62
|
-
|
|
63
|
-
Questions about *how* to implement something — design, approach, ambiguity,
|
|
64
|
-
trade-offs — are welcome and encouraged. Questions that are really requests to
|
|
65
|
-
*not do* the assigned work are not.
|
|
66
|
-
|
|
67
|
-
- **Why:** The user repeatedly got asked variants of "can I skip/stop/disable
|
|
68
|
-
this?" instead of getting the work done. He doesn't want to keep saying "no, do
|
|
69
|
-
it."
|
|
70
|
-
- **How to apply:** If a question's effect is to avoid the work (skip a test,
|
|
71
|
-
drop a requirement, leave the failure), don't ask it — do the work. Only
|
|
72
|
-
escalate when *genuinely* blocked: missing access/credentials, a product
|
|
73
|
-
decision only the user can make, or contradictory requirements — and even then,
|
|
74
|
-
propose the fix path, not the avoidance.
|