hammerkit 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/build.schema.json +59 -15
- 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 +17 -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 +18 -3
- 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-docker.js +18 -2
- package/dist/executer/execute-work-service.js +10 -1
- package/dist/executer/execute-work-task.js +111 -26
- package/dist/executer/execute-work-tree.js +26 -1
- package/dist/executer/execution-steps.js +3 -8
- package/dist/executer/get-container-binds.js +13 -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/index.js +14 -1
- package/dist/kubernetes/apply.js +6 -7
- package/dist/kubernetes/await-running-state.js +100 -60
- package/dist/kubernetes/ensure-http-route.js +7 -10
- package/dist/kubernetes/ensure-namespace.js +9 -10
- package/dist/kubernetes/ensure-persistent-data.js +6 -5
- package/dist/kubernetes/get-service-ip.js +2 -2
- package/dist/kubernetes/kubernetes-instance.js +0 -4
- package/dist/kubernetes/remove-persistent-data.js +16 -6
- package/dist/kubernetes/resolve-pod-name.js +7 -7
- 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-environment.js +1 -1
- package/dist/planner/utils/append-work-service.js +2 -1
- package/dist/planner/utils/append-work-task.js +7 -1
- package/dist/planner/utils/get-outputs-to-reset.js +20 -0
- package/dist/planner/utils/parse-work-generate.js +2 -2
- 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 +58 -15
- package/dist/planner/work-runtime-kubernetes.js +95 -45
- package/dist/planner/work-runtime-local.js +31 -8
- 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/build-file-environment-schema-http-route.js +15 -0
- package/dist/schema/build-file-environment-schema.js +8 -2
- package/dist/schema/build-file-schema.js +1 -1
- package/dist/schema/cache-schema.js +21 -2
- package/dist/schema/duration-schema.js +18 -0
- package/dist/schema/envs-schema.js +1 -1
- 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 +20 -9
- 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-ingress.js +0 -54
- 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-environment-schema-ingress.js +0 -14
- 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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.printDryRun = exports.planDryRun = void 0;
|
|
7
|
+
const colors_1 = __importDefault(require("colors"));
|
|
8
|
+
const validate_1 = require("../planner/validate");
|
|
9
|
+
const explain_1 = require("../cache/explain");
|
|
10
|
+
const log_1 = require("../log");
|
|
11
|
+
// Deterministic topological order (dependencies before dependents). Visited-set
|
|
12
|
+
// guards against runaway recursion; a real cycle is reported separately.
|
|
13
|
+
function topoOrder(workTree) {
|
|
14
|
+
const visited = new Set();
|
|
15
|
+
const order = [];
|
|
16
|
+
const visit = (task) => {
|
|
17
|
+
if (visited.has(task.id())) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
visited.add(task.id());
|
|
21
|
+
for (const dep of [...task.deps].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
22
|
+
visit(dep);
|
|
23
|
+
}
|
|
24
|
+
order.push(task);
|
|
25
|
+
};
|
|
26
|
+
for (const task of Object.values(workTree.tasks).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
27
|
+
visit(task);
|
|
28
|
+
}
|
|
29
|
+
return order;
|
|
30
|
+
}
|
|
31
|
+
// Compute the ordered execution plan with a predicted cache decision per task,
|
|
32
|
+
// reusing the cache-explain engine — no command runs, no container/service
|
|
33
|
+
// starts, no cache push/pull occurs.
|
|
34
|
+
async function planDryRun(workTree, defaultCacheMethod, environment) {
|
|
35
|
+
for (const task of Object.values(workTree.tasks)) {
|
|
36
|
+
const path = (0, validate_1.hasDependencyCycle)(task, []);
|
|
37
|
+
if (path && path.length > 0) {
|
|
38
|
+
return { entries: [], cycle: path.map((n) => n.name) };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const explanations = await (0, explain_1.explainWorkTree)(workTree, defaultCacheMethod, environment);
|
|
42
|
+
const byName = new Map(explanations.map((e) => [e.taskName, e]));
|
|
43
|
+
const entries = [];
|
|
44
|
+
for (const task of topoOrder(workTree)) {
|
|
45
|
+
const explanation = byName.get(task.name);
|
|
46
|
+
entries.push({
|
|
47
|
+
taskId: task.id(),
|
|
48
|
+
taskName: task.name,
|
|
49
|
+
status: explanation?.status ?? 'miss',
|
|
50
|
+
causes: explanation?.causes ?? [],
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return { entries, cycle: null };
|
|
54
|
+
}
|
|
55
|
+
exports.planDryRun = planDryRun;
|
|
56
|
+
function printDryRun(environment, plan) {
|
|
57
|
+
(0, log_1.printTitle)(environment, 'Dry run (no commands executed)');
|
|
58
|
+
if (plan.entries.length === 0) {
|
|
59
|
+
environment.stdout.write(' no tasks in scope\n');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
plan.entries.forEach((entry, index) => {
|
|
63
|
+
const label = entry.status === 'hit'
|
|
64
|
+
? colors_1.default.green('cache hit')
|
|
65
|
+
: entry.status === 'uncacheable'
|
|
66
|
+
? colors_1.default.grey('uncacheable')
|
|
67
|
+
: colors_1.default.yellow('cache miss');
|
|
68
|
+
const causes = entry.causes.length > 0 ? colors_1.default.grey(` (${entry.causes.map(explain_1.describeCause).join(', ')})`) : '';
|
|
69
|
+
environment.stdout.write(` ${index + 1}. ${entry.taskName}: ${label}${causes}\n`);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
exports.printDryRun = printDryRun;
|
|
73
|
+
//# sourceMappingURL=dry-run.js.map
|
|
@@ -5,9 +5,10 @@ const plan_work_tasks_1 = require("../planner/utils/plan-work-tasks");
|
|
|
5
5
|
const get_cache_directory_1 = require("../optimizer/get-cache-directory");
|
|
6
6
|
const path_1 = require("path");
|
|
7
7
|
const move_files_1 = require("../file/move-files");
|
|
8
|
+
const work_instance_id_1 = require("../planner/work-instance-id");
|
|
8
9
|
async function restoreCache(environment, path, workTree) {
|
|
9
10
|
for (const task of (0, plan_work_tasks_1.iterateWorkTasks)(workTree)) {
|
|
10
|
-
const cachePath = (0, get_cache_directory_1.getCacheDirectory)(
|
|
11
|
+
const cachePath = (0, get_cache_directory_1.getCacheDirectory)((0, work_instance_id_1.getWorkInstanceId)(task));
|
|
11
12
|
const sourceCacheDir = (0, path_1.join)(path, task.id());
|
|
12
13
|
// TODO move into local runtime
|
|
13
14
|
await (0, move_files_1.moveFiles)(task, environment, function* () {
|
|
@@ -17,7 +18,7 @@ async function restoreCache(environment, path, workTree) {
|
|
|
17
18
|
await task.runtime.restore(environment, sourceCacheDir);
|
|
18
19
|
}
|
|
19
20
|
for (const service of (0, plan_work_tasks_1.iterateWorkServices)(workTree)) {
|
|
20
|
-
const cachePath = (0, get_cache_directory_1.getCacheDirectory)(
|
|
21
|
+
const cachePath = (0, get_cache_directory_1.getCacheDirectory)((0, work_instance_id_1.getWorkInstanceId)(service));
|
|
21
22
|
const sourceCacheDir = (0, path_1.join)(path, service.id());
|
|
22
23
|
// TODO move into local runtime
|
|
23
24
|
await (0, move_files_1.moveFiles)(service, environment, function* () {
|
|
@@ -50,7 +51,7 @@ function* getArchivePaths(task, path) {
|
|
|
50
51
|
exports.getArchivePaths = getArchivePaths;
|
|
51
52
|
async function storeCache(environment, path, workTree) {
|
|
52
53
|
for (const task of (0, plan_work_tasks_1.iterateWorkTasks)(workTree)) {
|
|
53
|
-
const cachePath = (0, get_cache_directory_1.getCacheDirectory)(
|
|
54
|
+
const cachePath = (0, get_cache_directory_1.getCacheDirectory)((0, work_instance_id_1.getWorkInstanceId)(task));
|
|
54
55
|
const sourceCacheDir = (0, path_1.join)(path, task.id());
|
|
55
56
|
await (0, move_files_1.moveFiles)(task, environment, function* () {
|
|
56
57
|
yield { from: (0, path_1.join)(cachePath, 'stats.json'), to: (0, path_1.join)(sourceCacheDir, 'stats.json') };
|
|
@@ -60,7 +61,7 @@ async function storeCache(environment, path, workTree) {
|
|
|
60
61
|
await task.runtime.archive(environment, sourceCacheDir);
|
|
61
62
|
}
|
|
62
63
|
for (const service of (0, plan_work_tasks_1.iterateWorkServices)(workTree)) {
|
|
63
|
-
const cachePath = (0, get_cache_directory_1.getCacheDirectory)(
|
|
64
|
+
const cachePath = (0, get_cache_directory_1.getCacheDirectory)((0, work_instance_id_1.getWorkInstanceId)(service));
|
|
64
65
|
const sourceCacheDir = (0, path_1.join)(path, service.id());
|
|
65
66
|
await (0, move_files_1.moveFiles)(service, environment, function* () {
|
|
66
67
|
yield { from: (0, path_1.join)(cachePath, 'stats.json'), to: (0, path_1.join)(sourceCacheDir, 'stats.json') };
|
|
@@ -77,7 +78,7 @@ async function cleanCache(workTree, environment, options) {
|
|
|
77
78
|
if (options?.cache) {
|
|
78
79
|
await task.data.caching.backend.clear(task.id(), environment);
|
|
79
80
|
}
|
|
80
|
-
const cachePath = (0, get_cache_directory_1.getCacheDirectory)(
|
|
81
|
+
const cachePath = (0, get_cache_directory_1.getCacheDirectory)((0, work_instance_id_1.getWorkInstanceId)(task));
|
|
81
82
|
if (await environment.file.exists(cachePath)) {
|
|
82
83
|
task.status.write('info', `remove cache ${cachePath}`);
|
|
83
84
|
await environment.file.remove(cachePath);
|
|
@@ -7,12 +7,21 @@ const log_1 = require("../log");
|
|
|
7
7
|
const abort_1 = require("./abort");
|
|
8
8
|
const abort_event_1 = require("../utils/abort-event");
|
|
9
9
|
async function executeCommand(status, abortSignal, cwd, command, envs, environment) {
|
|
10
|
+
const windows = (0, os_1.platform)() === 'win32';
|
|
10
11
|
return new Promise((resolve, reject) => {
|
|
11
|
-
const ps = (0, child_process_1.
|
|
12
|
+
const ps = (0, child_process_1.spawn)(command, {
|
|
12
13
|
env: { ...envs, PATH: environment.processEnvs['PATH'] },
|
|
13
14
|
cwd,
|
|
14
|
-
shell:
|
|
15
|
+
shell: windows ? 'powershell.exe' : true,
|
|
16
|
+
// its own process group, so an abort reaches every process the command
|
|
17
|
+
// started — the shell may run them as children (dash always does), and
|
|
18
|
+
// they would keep running and holding the output open
|
|
19
|
+
detached: !windows,
|
|
15
20
|
});
|
|
21
|
+
// decode across chunk boundaries, so a multi-byte character split between
|
|
22
|
+
// two chunks stays intact
|
|
23
|
+
ps.stdout?.setEncoding('utf8');
|
|
24
|
+
ps.stderr?.setEncoding('utf8');
|
|
16
25
|
ps.stdout?.on('data', async (data) => {
|
|
17
26
|
for (const log of (0, log_1.getLogs)(data)) {
|
|
18
27
|
status.console('stdout', log);
|
|
@@ -35,7 +44,16 @@ async function executeCommand(status, abortSignal, cwd, command, envs, environme
|
|
|
35
44
|
resolve(code ?? 0);
|
|
36
45
|
});
|
|
37
46
|
const abortListener = (0, abort_event_1.listenOnAbort)(abortSignal, () => {
|
|
38
|
-
ps.
|
|
47
|
+
if (windows || !ps.pid) {
|
|
48
|
+
ps.kill();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
process.kill(-ps.pid, 'SIGTERM');
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// the group is already gone
|
|
56
|
+
}
|
|
39
57
|
});
|
|
40
58
|
});
|
|
41
59
|
}
|
|
@@ -3,16 +3,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.execCommand = exports.convertToPosixPath = exports.startContainer = exports.getContainerCli = void 0;
|
|
6
|
+
exports.clearContainerDirectory = exports.execCommand = exports.convertToPosixPath = exports.startContainer = exports.getContainerCli = exports.createDockerClient = void 0;
|
|
7
7
|
const stream_1 = require("../docker/stream");
|
|
8
8
|
const dockerode_1 = __importDefault(require("dockerode"));
|
|
9
9
|
const path_1 = require("path");
|
|
10
10
|
const os_1 = require("os");
|
|
11
11
|
const abort_event_1 = require("../utils/abort-event");
|
|
12
12
|
let dockerInstance = null;
|
|
13
|
+
// Without options dockerode reads DOCKER_HOST and the TLS variables; passing
|
|
14
|
+
// `{ host: undefined }` would overwrite the host it parsed from DOCKER_HOST.
|
|
15
|
+
function createDockerClient(host) {
|
|
16
|
+
return host ? new dockerode_1.default({ host }) : new dockerode_1.default();
|
|
17
|
+
}
|
|
18
|
+
exports.createDockerClient = createDockerClient;
|
|
13
19
|
function getContainerCli(workEnvironment) {
|
|
14
20
|
if (!dockerInstance) {
|
|
15
|
-
dockerInstance =
|
|
21
|
+
dockerInstance = createDockerClient(workEnvironment.host);
|
|
16
22
|
}
|
|
17
23
|
return dockerInstance;
|
|
18
24
|
}
|
|
@@ -127,4 +133,14 @@ async function execCommand(status, environment, container, cwd, cmd, user, timeo
|
|
|
127
133
|
});
|
|
128
134
|
}
|
|
129
135
|
exports.execCommand = execCommand;
|
|
136
|
+
// Empty a directory inside a running container, as root. Volumes are emptied
|
|
137
|
+
// this way rather than recreated: Docker refuses to remove a volume while any
|
|
138
|
+
// container (a dependent's paused one, say) still mounts it.
|
|
139
|
+
async function clearContainerDirectory(status, environment, container, path) {
|
|
140
|
+
const result = await execCommand(status, environment, container, '/', ['sh', '-c', 'rm -rf "$1"/* "$1"/.[!.]* "$1"/..?*', 'sh', convertToPosixPath(path)], null, undefined, environment.abortCtrl.signal);
|
|
141
|
+
if (result.type !== 'result' || result.result.ExitCode !== 0) {
|
|
142
|
+
throw new Error(`unable to empty ${path}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.clearContainerDirectory = clearContainerDirectory;
|
|
130
146
|
//# sourceMappingURL=execute-docker.js.map
|
|
@@ -14,7 +14,11 @@ exports.stopService = stopService;
|
|
|
14
14
|
async function executeWorkService(work, environment, options) {
|
|
15
15
|
try {
|
|
16
16
|
if (options.type === 'execute') {
|
|
17
|
-
await (0, await_completed_dependencies_1.awaitRequirement)(work, environment.abortCtrl.signal);
|
|
17
|
+
const required = await (0, await_completed_dependencies_1.awaitRequirement)(work, environment.abortCtrl.signal, { untilAllDone: !options.watch });
|
|
18
|
+
if (!required) {
|
|
19
|
+
work.status.write('debug', `${work.name} not started, no task needing it had to run`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
18
22
|
}
|
|
19
23
|
work.state.set({
|
|
20
24
|
type: 'starting',
|
|
@@ -54,6 +58,11 @@ async function executeWorkService(work, environment, options) {
|
|
|
54
58
|
errorMessage: (0, log_1.getErrorMessage)(e),
|
|
55
59
|
stateKey: null,
|
|
56
60
|
});
|
|
61
|
+
// fail fast like a failing task: whatever needs this service can never
|
|
62
|
+
// run, and would otherwise wait for it forever
|
|
63
|
+
if (!options.watch) {
|
|
64
|
+
environment.abortCtrl.abort();
|
|
65
|
+
}
|
|
57
66
|
}
|
|
58
67
|
}
|
|
59
68
|
}
|
|
@@ -6,20 +6,21 @@ const abort_1 = require("./abort");
|
|
|
6
6
|
const log_1 = require("../log");
|
|
7
7
|
const await_completed_dependencies_1 = require("./await-completed-dependencies");
|
|
8
8
|
const watch_loop_1 = require("./watch-loop");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
async function pushToBackend(work, environment, cacheState) {
|
|
14
|
-
const { resolved, stateKey } = cacheState;
|
|
15
|
-
if (resolved.method === 'none') {
|
|
9
|
+
const explain_1 = require("../cache/explain");
|
|
10
|
+
const archive_task_entry_1 = require("./archive-task-entry");
|
|
11
|
+
const units_1 = require("../utils/units");
|
|
12
|
+
const abort_event_1 = require("../utils/abort-event");
|
|
13
|
+
async function pushToBackend(work, environment, cacheState, options) {
|
|
14
|
+
const { resolved, stateKey, provable } = cacheState;
|
|
15
|
+
if (resolved.method === 'none' || !provable) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (options.cacheReadOnly) {
|
|
19
|
+
work.status.write('debug', `${work.name} not pushed to cache "${resolved.name}" (read-only)`);
|
|
16
20
|
return;
|
|
17
21
|
}
|
|
18
22
|
try {
|
|
19
|
-
const cacheDir = (0,
|
|
20
|
-
const stats = await (0, get_work_cache_stats_1.getWorkCacheStats)(work.data, environment);
|
|
21
|
-
await (0, cache_metadata_1.writeCacheMetadata)(environment, work.id(), stats, (0, work_task_cache_description_1.getWorkTaskCacheDescription)(work.data));
|
|
22
|
-
await work.runtime.archive(environment, cacheDir);
|
|
23
|
+
const cacheDir = await (0, archive_task_entry_1.archiveTaskEntry)(work, environment);
|
|
23
24
|
await resolved.backend.push(work.id(), stateKey, cacheDir, environment);
|
|
24
25
|
work.status.write('info', `${work.name} pushed to cache "${resolved.name}" (${resolved.backend.type})`);
|
|
25
26
|
}
|
|
@@ -27,8 +28,51 @@ async function pushToBackend(work, environment, cacheState) {
|
|
|
27
28
|
work.status.write('warn', `${work.name} failed to push to cache "${resolved.name}": ${(0, log_1.getErrorMessage)(e)}`);
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
// An abort signal that follows `parent` and additionally fires after `timeout`
|
|
32
|
+
// ms, so a task deadline reuses the runtimes' existing abort/cleanup path.
|
|
33
|
+
function withDeadline(parent, timeout) {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
let expired = false;
|
|
36
|
+
const parentListener = (0, abort_event_1.listenOnAbort)(parent, () => controller.abort());
|
|
37
|
+
const timer = timeout === null
|
|
38
|
+
? null
|
|
39
|
+
: setTimeout(() => {
|
|
40
|
+
expired = true;
|
|
41
|
+
controller.abort();
|
|
42
|
+
}, timeout);
|
|
43
|
+
return {
|
|
44
|
+
signal: controller.signal,
|
|
45
|
+
expired: () => expired,
|
|
46
|
+
clear() {
|
|
47
|
+
if (timer) {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
parentListener.close();
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// A task without cmds only groups its dependencies (`ci: { deps: [test, build] }`).
|
|
55
|
+
// Having no src of its own it is never a cache hit, so its dependencies are
|
|
56
|
+
// always checked and their outputs restored. When every one of them was a hit,
|
|
57
|
+
// nothing ran: report the group as cached too. Reporting only, the cache
|
|
58
|
+
// decision is untouched.
|
|
59
|
+
function onlyGroupsCacheHits(work) {
|
|
60
|
+
return (work.data.cmds.length === 0 &&
|
|
61
|
+
work.deps.every((dep) => dep.state.current.type === 'completed' && dep.state.current.cached));
|
|
62
|
+
}
|
|
63
|
+
async function executeWorkTask(work, environment, options,
|
|
64
|
+
// when set, the task was not requested and only runs if one of these tasks
|
|
65
|
+
// depending on it misses the cache (see executeWorkTree)
|
|
66
|
+
dependents = null) {
|
|
31
67
|
try {
|
|
68
|
+
if (dependents) {
|
|
69
|
+
const needed = await (0, await_completed_dependencies_1.awaitDependentNeed)(dependents, environment.abortCtrl.signal);
|
|
70
|
+
if (!needed) {
|
|
71
|
+
work.status.write('debug', `${work.name} skipped, every task depending on it was a cache hit`);
|
|
72
|
+
work.state.set({ type: 'completed', cached: false, skipped: true, duration: 0, stateKey: '' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
32
76
|
work.state.set({
|
|
33
77
|
type: 'starting',
|
|
34
78
|
started: new Date(),
|
|
@@ -36,11 +80,6 @@ async function executeWorkTask(work, environment, options) {
|
|
|
36
80
|
});
|
|
37
81
|
await (0, watch_loop_1.watchLoop)(work, environment, options, async (cacheState, abort) => {
|
|
38
82
|
const started = new Date();
|
|
39
|
-
work.state.set({
|
|
40
|
-
type: 'ready',
|
|
41
|
-
stateKey: cacheState.stateKey,
|
|
42
|
-
started: new Date(),
|
|
43
|
-
});
|
|
44
83
|
if (cacheState.cached) {
|
|
45
84
|
work.status.write('debug', 'completed for cached state key ' + cacheState.stateKey);
|
|
46
85
|
work.state.set({
|
|
@@ -51,6 +90,25 @@ async function executeWorkTask(work, environment, options) {
|
|
|
51
90
|
});
|
|
52
91
|
return;
|
|
53
92
|
}
|
|
93
|
+
// Only a cache miss is `ready`: needed services start on `ready`, so a
|
|
94
|
+
// cached task never starts the services it would have needed.
|
|
95
|
+
work.state.set({
|
|
96
|
+
type: 'ready',
|
|
97
|
+
stateKey: cacheState.stateKey,
|
|
98
|
+
started: new Date(),
|
|
99
|
+
});
|
|
100
|
+
// Cache miss: under the explain flag, report why this task is rebuilding,
|
|
101
|
+
// reusing the cache-explain engine. Captured BEFORE execution overwrites
|
|
102
|
+
// the last-resolved record, then carried onto the completed state so the
|
|
103
|
+
// build summary can show the cause column. Reporting only.
|
|
104
|
+
let missCauses;
|
|
105
|
+
if (options.explain) {
|
|
106
|
+
const explanation = await (0, explain_1.explainTask)(work, options.cacheDefault, environment);
|
|
107
|
+
missCauses = explanation.causes.map(explain_1.describeCause);
|
|
108
|
+
for (const cause of missCauses) {
|
|
109
|
+
work.status.write('info', `cache miss: ${cause}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
54
112
|
await (0, await_completed_dependencies_1.awaitCompletedDependencies)(work, work.deps, abort);
|
|
55
113
|
await (0, await_completed_dependencies_1.awaitRunningNeeds)(work, work.needs.map((n) => n.service), abort);
|
|
56
114
|
await options.processManager.task(work, async () => {
|
|
@@ -59,22 +117,44 @@ async function executeWorkTask(work, environment, options) {
|
|
|
59
117
|
stateKey: cacheState.stateKey,
|
|
60
118
|
started,
|
|
61
119
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
120
|
+
const timeout = work.data.timeout ?? options.timeout;
|
|
121
|
+
const deadline = withDeadline(abort, timeout);
|
|
122
|
+
try {
|
|
123
|
+
await work.runtime.execute(environment, {
|
|
124
|
+
cache: cacheState,
|
|
125
|
+
abort: deadline.signal,
|
|
126
|
+
stateKey: cacheState.stateKey,
|
|
127
|
+
state: work.state,
|
|
128
|
+
daemon: options.daemon,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
deadline.clear();
|
|
133
|
+
}
|
|
134
|
+
if (deadline.expired() && timeout !== null) {
|
|
135
|
+
// the runtime saw an abort and cleaned up; report it as a failure
|
|
136
|
+
// (never a cancellation) and stop the run like any failing task
|
|
137
|
+
work.state.set({
|
|
138
|
+
type: 'error',
|
|
139
|
+
stateKey: cacheState.stateKey,
|
|
140
|
+
errorMessage: `timed out after ${(0, units_1.formatDuration)(timeout)}`,
|
|
141
|
+
});
|
|
142
|
+
if (!options.watch) {
|
|
143
|
+
environment.abortCtrl.abort();
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
69
147
|
(0, abort_1.checkForAbort)(abort);
|
|
70
148
|
if (work.state.current.type === 'running') {
|
|
71
149
|
work.status.write('debug', 'completed for state key ' + cacheState.stateKey);
|
|
72
|
-
await pushToBackend(work, environment, cacheState);
|
|
150
|
+
await pushToBackend(work, environment, cacheState, options);
|
|
151
|
+
const cached = onlyGroupsCacheHits(work);
|
|
73
152
|
work.state.set({
|
|
74
153
|
stateKey: cacheState.stateKey,
|
|
75
154
|
type: 'completed',
|
|
76
|
-
cached
|
|
155
|
+
cached,
|
|
77
156
|
duration: (0, states_1.getDuration)(started),
|
|
157
|
+
missCauses: cached ? undefined : missCauses,
|
|
78
158
|
});
|
|
79
159
|
}
|
|
80
160
|
else {
|
|
@@ -100,6 +180,11 @@ async function executeWorkTask(work, environment, options) {
|
|
|
100
180
|
errorMessage: (0, log_1.getErrorMessage)(e),
|
|
101
181
|
stateKey: null,
|
|
102
182
|
});
|
|
183
|
+
// Stop the run like a failing command does: dependents wait for this task
|
|
184
|
+
// to complete, which it now never will.
|
|
185
|
+
if (!options.watch) {
|
|
186
|
+
environment.abortCtrl.abort();
|
|
187
|
+
}
|
|
103
188
|
}
|
|
104
189
|
}
|
|
105
190
|
}
|
|
@@ -6,9 +6,10 @@ const execute_work_service_1 = require("./execute-work-service");
|
|
|
6
6
|
const execute_work_task_1 = require("./execute-work-task");
|
|
7
7
|
async function executeWorkTree(work, environment, options) {
|
|
8
8
|
const itemPromises = [];
|
|
9
|
+
const dependents = skippableDependents(work, options);
|
|
9
10
|
for (const task of (0, plan_work_tasks_1.iterateWorkTasks)(work)) {
|
|
10
11
|
if (task.state.current.type === 'pending') {
|
|
11
|
-
itemPromises.push((0, execute_work_task_1.executeWorkTask)(task, environment, options));
|
|
12
|
+
itemPromises.push((0, execute_work_task_1.executeWorkTask)(task, environment, options, dependents.get(task.name) ?? null));
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
15
|
for (const service of (0, plan_work_tasks_1.iterateWorkServices)(work)) {
|
|
@@ -24,4 +25,28 @@ async function executeWorkTree(work, environment, options) {
|
|
|
24
25
|
await Promise.all(itemPromises);
|
|
25
26
|
}
|
|
26
27
|
exports.executeWorkTree = executeWorkTree;
|
|
28
|
+
// For every task that may be skipped when nothing needs it, the tasks in this
|
|
29
|
+
// run that depend on it. Requested tasks always run; so does anything a service
|
|
30
|
+
// depends on, and everything in watch mode or with skipping turned off.
|
|
31
|
+
function skippableDependents(work, options) {
|
|
32
|
+
const result = new Map();
|
|
33
|
+
if (options.type !== 'execute' || !options.skipDeps || options.watch || !work.requested) {
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
const neededByService = new Set();
|
|
37
|
+
for (const service of (0, plan_work_tasks_1.iterateWorkServices)(work)) {
|
|
38
|
+
for (const dep of service.deps) {
|
|
39
|
+
neededByService.add(dep.name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const task of (0, plan_work_tasks_1.iterateWorkTasks)(work)) {
|
|
43
|
+
for (const dep of task.deps) {
|
|
44
|
+
if (work.requested.includes(dep.name) || neededByService.has(dep.name)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
result.set(dep.name, [...(result.get(dep.name) ?? []), task]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
27
52
|
//# sourceMappingURL=execute-work-tree.js.map
|
|
@@ -26,18 +26,13 @@ async function prepareVolume(item, docker) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
if ((0, work_task_1.isContainerWorkTask)(item.data)) {
|
|
29
|
+
// a task's outputs are emptied inside its container (see dockerTask)
|
|
29
30
|
for (const generate of item.data.generates) {
|
|
30
31
|
if (generate.isFile) {
|
|
31
32
|
continue;
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
await (0, get_docker_executor_1.recreateVolume)(docker, item.status, generate.volumeName);
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
item.status.write('debug', 'ensure volume exists');
|
|
39
|
-
await (0, get_docker_executor_1.ensureVolumeExists)(docker, item.status, generate.volumeName);
|
|
40
|
-
}
|
|
34
|
+
item.status.write('debug', 'ensure volume exists');
|
|
35
|
+
await (0, get_docker_executor_1.ensureVolumeExists)(docker, item.status, generate.volumeName);
|
|
41
36
|
}
|
|
42
37
|
}
|
|
43
38
|
}
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getContainerBinds = void 0;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
function isInside(path, parent) {
|
|
6
|
+
const rel = (0, path_1.relative)(parent, path);
|
|
7
|
+
return rel !== '' && !rel.startsWith('..') && !(0, path_1.isAbsolute)(rel);
|
|
8
|
+
}
|
|
4
9
|
function getContainerBinds(item) {
|
|
10
|
+
const sources = item.data.src.map((s) => s.absolutePath);
|
|
5
11
|
const items = [
|
|
6
12
|
...item.data.mounts,
|
|
7
|
-
|
|
13
|
+
// A source inside another source (a dependency's `app/app.csproj` under the
|
|
14
|
+
// task's own `app`) is already visible through the outer bind. Binding it
|
|
15
|
+
// again would nest bind mounts, which Docker Desktop then refuses to let the
|
|
16
|
+
// host delete while the paused container exists.
|
|
17
|
+
...sources
|
|
18
|
+
.filter((path) => !sources.some((other) => isInside(path, other)))
|
|
19
|
+
.map((path) => ({ localPath: path, containerPath: path })),
|
|
8
20
|
...item.data.generates.filter((g) => !g.isFile).map((v) => ({ localPath: v.volumeName, containerPath: v.path })),
|
|
9
21
|
...item.data.generates.filter((g) => g.isFile).map((v) => ({ localPath: v.path, containerPath: v.path })),
|
|
10
22
|
];
|
|
@@ -12,7 +12,10 @@ function getSchedulerExecuteResult(state) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
for (const service of (0, plan_work_tasks_1.iterateWorkServices)(state)) {
|
|
15
|
-
|
|
15
|
+
const current = service.state.current;
|
|
16
|
+
if (current.type === 'canceled' ||
|
|
17
|
+
current.type === 'error' ||
|
|
18
|
+
(current.type === 'end' && current.reason === 'crash')) {
|
|
16
19
|
return {
|
|
17
20
|
state,
|
|
18
21
|
success: false,
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.lazyResolver = void 0;
|
|
4
|
-
|
|
4
|
+
// Memoizes factory(). While the factory is still running, a re-entrant call
|
|
5
|
+
// (a cycle) returns onReentry() when given, instead of recursing forever.
|
|
6
|
+
function lazyResolver(factory, onReentry) {
|
|
5
7
|
let value = null;
|
|
6
8
|
let resolved = false;
|
|
9
|
+
let resolving = false;
|
|
7
10
|
return () => {
|
|
8
11
|
if (!resolved) {
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
if (resolving && onReentry) {
|
|
13
|
+
return onReentry();
|
|
14
|
+
}
|
|
15
|
+
resolving = true;
|
|
16
|
+
try {
|
|
17
|
+
value = factory();
|
|
18
|
+
resolved = true;
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
resolving = false;
|
|
22
|
+
}
|
|
11
23
|
}
|
|
12
24
|
return value;
|
|
13
25
|
};
|