hammerkit 1.27.0-next.9 → 2.0.0-next.1
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/build.schema.json +176 -6
- package/dist/cache/backends/local-cache-backend.js +6 -0
- package/dist/cache/backends/registry/registry-client.js +1 -0
- package/dist/cache/cache-sync.js +1 -1
- package/dist/cache/explain.js +17 -0
- package/dist/cli.js +2 -0
- package/dist/dashboard/assets/index-CjnKm5-3.css +2 -0
- package/dist/dashboard/assets/index-zPXa4klZ.js +13 -0
- package/dist/dashboard/favicon.svg +1 -0
- package/dist/dashboard/index.html +15 -0
- package/dist/docker/item-network.js +13 -5
- package/dist/executer/archive-task-entry.js +4 -2
- package/dist/executer/deadline.js +17 -3
- package/dist/executer/deferred-restore.js +36 -0
- package/dist/executer/docker-service.js +30 -8
- package/dist/executer/docker-task.js +21 -1
- package/dist/executer/execute-work-service.js +2 -0
- package/dist/executer/execute-work-task.js +11 -4
- package/dist/executer/execution-steps.js +25 -4
- package/dist/executer/reset-work-tree.js +4 -0
- package/dist/executer/scheduler/enqueue-next.js +57 -6
- package/dist/executer/scheduler/state-key.js +42 -5
- package/dist/executer/watch-loop.js +6 -3
- package/dist/harness/connect.js +20 -6
- package/dist/harness/mcp-command.js +22 -6
- package/dist/harness/mcp.js +63 -114
- package/dist/harness/opencode.js +158 -50
- package/dist/harness/pi.js +29 -0
- package/dist/harness/sandbox-client.js +194 -23
- package/dist/harness/service-tools.js +141 -0
- package/dist/harness/task-tools.js +221 -0
- package/dist/kubernetes/apply.js +49 -1
- package/dist/kubernetes/await-running-state.js +172 -13
- package/dist/kubernetes/cluster-state.js +245 -0
- package/dist/kubernetes/ensure-kubernetes-deployment-exists.js +133 -9
- package/dist/kubernetes/ensure-kubernetes-service-exists.js +2 -0
- package/dist/kubernetes/ensure-persistent-data.js +211 -6
- package/dist/kubernetes/follow-pod-logs.js +182 -0
- package/dist/kubernetes/job-placement.js +36 -12
- package/dist/kubernetes/kubernetes-instance.js +1 -0
- package/dist/kubernetes/network-policy.js +2 -1
- package/dist/kubernetes/remove-stale-items.js +46 -0
- package/dist/kubernetes/restore-kubernetes-data.js +9 -5
- package/dist/kubernetes/sandbox-runner.js +628 -0
- package/dist/kubernetes/service-rollouts.js +74 -0
- package/dist/kubernetes/store-kubernetes-data.js +5 -2
- package/dist/kubernetes/volumes.js +94 -9
- package/dist/main.js +2 -0
- package/dist/optimizer/calculate-checksum.js +3 -2
- package/dist/optimizer/check-key.js +94 -0
- package/dist/optimizer/check-keys.js +64 -0
- package/dist/optimizer/get-work-cache-stats.js +17 -3
- package/dist/optimizer/work-task-cache-description.js +49 -1
- package/dist/planner/service-definition.js +9 -4
- package/dist/planner/utils/append-work-dependencies.js +5 -3
- package/dist/planner/utils/append-work-service.js +18 -1
- package/dist/planner/utils/append-work-task.js +13 -3
- package/dist/planner/utils/get-work-runtime.js +6 -1
- package/dist/planner/utils/normalize-path.js +19 -1
- package/dist/planner/utils/parse-work-mount.js +7 -1
- package/dist/planner/utils/parse-work-port.js +4 -1
- package/dist/planner/validate.js +22 -0
- package/dist/planner/work-network.js +3 -1
- package/dist/planner/work-runtime-aggregate.js +32 -0
- package/dist/planner/work-runtime-kubernetes.js +136 -13
- package/dist/planner/work-task.js +6 -1
- package/dist/program.js +27 -9
- package/dist/remote/device-login.js +7 -2
- package/dist/remote/host-config.js +69 -3
- package/dist/remote/remote-commands.js +33 -7
- package/dist/remote/remote-status.js +112 -0
- package/dist/remote/remote-work-commands.js +562 -45
- package/dist/remote/server-client.js +15 -2
- package/dist/remote/token-source.js +64 -26
- package/dist/run-program.js +9 -0
- package/dist/schema/build-file-base-task-schema.js +3 -3
- package/dist/schema/build-file-container-service-schema.js +10 -0
- package/dist/schema/build-file-container-task-schema.js +16 -1
- package/dist/schema/build-file-include-schema.js +18 -4
- package/dist/schema/build-file-kubernetes-service-schema.js +2 -0
- package/dist/schema/build-file-task-schema.js +5 -1
- package/dist/schema/capabilities-schema.js +56 -0
- package/dist/schema/reference-parser.js +119 -11
- package/dist/schema/removed-properties.js +20 -0
- package/dist/schema/schema-parser.js +94 -41
- package/dist/schema/work-scope-parser.js +33 -10
- package/dist/secrets/command-secret-provider.js +3 -1
- package/dist/secrets/github-app-token-command.js +3 -1
- package/dist/secrets/secret-catalog.js +43 -19
- package/dist/server/accounts-api.js +4 -1
- package/dist/server/approvals-api.js +1 -1
- package/dist/server/capacity-format.js +36 -0
- package/dist/server/capacity.js +300 -0
- package/dist/server/checks-api.js +83 -0
- package/dist/server/checks.js +116 -0
- package/dist/server/dashboard.js +196 -0
- package/dist/server/definitions-api.js +66 -3
- package/dist/server/endpoints.js +794 -0
- package/dist/server/executor.js +164 -9
- package/dist/server/kubernetes-backend.js +415 -48
- package/dist/server/kubernetes-sandbox-storage.js +46 -2
- package/dist/server/overview.js +271 -0
- package/dist/server/plan.js +170 -121
- package/dist/server/policy.js +34 -2
- package/dist/server/proposals-api.js +17 -8
- package/dist/server/repo-access.js +5 -0
- package/dist/server/run-logs.js +4 -0
- package/dist/server/run-plan.js +38 -4
- package/dist/server/run-request.js +58 -3
- package/dist/server/run-worker.js +262 -13
- package/dist/server/runs.js +962 -36
- package/dist/server/sandbox-pod.js +28 -0
- package/dist/server/sandbox-storage.js +23 -0
- package/dist/server/sandboxes.js +8 -0
- package/dist/server/server-config.js +246 -10
- package/dist/server/server.js +429 -32
- package/dist/server/service-runs.js +82 -0
- package/dist/server/timings.js +132 -0
- package/dist/server/wait-reason.js +21 -0
- package/dist/utils/tar.js +28 -1
- package/dist/utils/terminal-prompt.js +53 -0
- package/package.json +8 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.callServer = exports.getMe = exports.getDiscovery = exports.printableWhole = exports.printable = exports.ServerError = void 0;
|
|
3
|
+
exports.callServer = exports.getMe = exports.getDiscovery = exports.printableWhole = exports.printableLines = exports.printable = exports.ServerError = void 0;
|
|
4
4
|
const discovery_1 = require("../server/discovery");
|
|
5
5
|
const TIMEOUT_MS = 10000;
|
|
6
6
|
const WRITE_TIMEOUT_MS = 30000;
|
|
@@ -21,6 +21,17 @@ function printable(value) {
|
|
|
21
21
|
return text.length > 300 ? `${text.slice(0, 300)}… (+${text.length - 300} chars)` : text;
|
|
22
22
|
}
|
|
23
23
|
exports.printable = printable;
|
|
24
|
+
// Text of several lines, such as every problem of a refused publish: each line made
|
|
25
|
+
// printable and cut like `printable`, one per line, and at most `MAX_LINES` of them.
|
|
26
|
+
const MAX_LINES = 100;
|
|
27
|
+
function printableLines(value) {
|
|
28
|
+
const lines = String(value)
|
|
29
|
+
.split(/\r?\n/)
|
|
30
|
+
.filter((line) => line.trim().length > 0);
|
|
31
|
+
const shown = lines.slice(0, MAX_LINES).map(printable);
|
|
32
|
+
return lines.length > MAX_LINES ? [...shown, `… (+${lines.length - MAX_LINES} lines)`] : shown;
|
|
33
|
+
}
|
|
34
|
+
exports.printableLines = printableLines;
|
|
24
35
|
// The same, never cut: for what somebody decides on, such as a command.
|
|
25
36
|
function printableWhole(value) {
|
|
26
37
|
return String(value)
|
|
@@ -92,7 +103,9 @@ async function callServer(server, token, method, path, body) {
|
|
|
92
103
|
throw new ServerError(`${server.url} did not accept the token`);
|
|
93
104
|
}
|
|
94
105
|
if (!response.ok) {
|
|
95
|
-
const
|
|
106
|
+
const lines = answer && typeof answer.error === 'string' ? printableLines(answer.error) : [];
|
|
107
|
+
// a refused publish says every problem, each on a line of its own
|
|
108
|
+
const said = lines.length > 1 ? `:\n${lines.map((line) => ` ${line}`).join('\n')}` : lines.length === 1 ? `: ${lines[0]}` : '';
|
|
96
109
|
throw new ServerError(`${server.url} answered ${response.status}${said}`, response.status);
|
|
97
110
|
}
|
|
98
111
|
return { status: response.status, body: answer };
|
|
@@ -9,6 +9,54 @@ function isToken(value) {
|
|
|
9
9
|
return /^[\x21-\x7e]+$/.test(value);
|
|
10
10
|
}
|
|
11
11
|
exports.isToken = isToken;
|
|
12
|
+
// What a failed token command said on stderr, for the user's own terminal: never its stdout,
|
|
13
|
+
// and never in an error message, which a harness hands on to an agent.
|
|
14
|
+
const MAX_STDERR_LINES = 20;
|
|
15
|
+
const MAX_STDERR_LINE = 300;
|
|
16
|
+
function writeStderr(environment, what, stderr) {
|
|
17
|
+
const lines = stderr
|
|
18
|
+
.split(/\r?\n/)
|
|
19
|
+
.map((line) => line.replace(/[^\x20-\x7e\t]/g, '?').slice(0, MAX_STDERR_LINE))
|
|
20
|
+
.filter((line) => line.trim())
|
|
21
|
+
.slice(-MAX_STDERR_LINES);
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
environment.stderr.write(`${what}: ${line}\n`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function commandSource(environment, command, what) {
|
|
27
|
+
let current;
|
|
28
|
+
const run = () => new Promise((resolve, reject) => {
|
|
29
|
+
(0, child_process_1.exec)(command, { env: environment.processEnvs, timeout: 60000 }, (error, stdout, stderr) => {
|
|
30
|
+
const token = String(stdout).trim();
|
|
31
|
+
if (error || !token) {
|
|
32
|
+
writeStderr(environment, what, String(stderr));
|
|
33
|
+
const status = error?.killed
|
|
34
|
+
? ', it did not end within a minute'
|
|
35
|
+
: typeof error?.code === 'number'
|
|
36
|
+
? `, it exited with ${error.code}`
|
|
37
|
+
: '';
|
|
38
|
+
reject(new Error(`${what} printed no token${status}`));
|
|
39
|
+
}
|
|
40
|
+
else if (!isToken(token)) {
|
|
41
|
+
// never the output itself: it can hold more secrets than the token
|
|
42
|
+
reject(new Error(`${what} printed more than a token, it has to print the token alone`));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
resolve(token);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
token: () => {
|
|
51
|
+
current = current ?? run();
|
|
52
|
+
current.catch(() => (current = undefined));
|
|
53
|
+
return current;
|
|
54
|
+
},
|
|
55
|
+
renew: async () => {
|
|
56
|
+
current = undefined;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
12
60
|
/**
|
|
13
61
|
* The token a client of a server sends: a harness, and the `remote` commands. In this order:
|
|
14
62
|
*
|
|
@@ -16,42 +64,29 @@ exports.isToken = isToken;
|
|
|
16
64
|
* identity, such as a workload identity or a service account of the identity provider. It
|
|
17
65
|
* runs again when the server no longer accepts the token.
|
|
18
66
|
* - `HAMMERKIT_TOKEN`: such a token, as the agent's platform hands it over.
|
|
67
|
+
* - the token command of the server in the host config (`remote add --token-command`): the
|
|
68
|
+
* same as `HAMMERKIT_TOKEN_COMMAND`, for one server, so each server can have the service
|
|
69
|
+
* account and audience it accepts. It only ever comes from the host config, never from a
|
|
70
|
+
* build file: whoever writes it decides what runs on this machine.
|
|
19
71
|
* - the sign-in of `hammerkit remote login <server>`.
|
|
20
72
|
*
|
|
21
73
|
* The token only goes to the URL the server is registered with in the host config.
|
|
22
74
|
*/
|
|
23
75
|
function getTokenSource(environment, name, server) {
|
|
76
|
+
// a variable holds for every server: say so when it takes the place of the one registered
|
|
77
|
+
const overrides = (variable) => {
|
|
78
|
+
if (server.tokenCommand) {
|
|
79
|
+
environment.stderr.write(`${variable} is set, so the token command of ${name} is not used\n`);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
24
82
|
const command = environment.processEnvs.HAMMERKIT_TOKEN_COMMAND;
|
|
25
83
|
if (command) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
(0, child_process_1.exec)(command, { env: environment.processEnvs, timeout: 60000 }, (error, stdout) => {
|
|
29
|
-
const token = String(stdout).trim();
|
|
30
|
-
if (error || !token) {
|
|
31
|
-
reject(new Error('HAMMERKIT_TOKEN_COMMAND printed no token'));
|
|
32
|
-
}
|
|
33
|
-
else if (!isToken(token)) {
|
|
34
|
-
// never the output itself: it can hold more secrets than the token
|
|
35
|
-
reject(new Error('HAMMERKIT_TOKEN_COMMAND printed more than a token, it has to print the token alone'));
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
resolve(token);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
return {
|
|
43
|
-
token: () => {
|
|
44
|
-
current = current ?? run();
|
|
45
|
-
current.catch(() => (current = undefined));
|
|
46
|
-
return current;
|
|
47
|
-
},
|
|
48
|
-
renew: async () => {
|
|
49
|
-
current = undefined;
|
|
50
|
-
},
|
|
51
|
-
};
|
|
84
|
+
overrides('HAMMERKIT_TOKEN_COMMAND');
|
|
85
|
+
return commandSource(environment, command, 'HAMMERKIT_TOKEN_COMMAND');
|
|
52
86
|
}
|
|
53
87
|
const fixed = environment.processEnvs.HAMMERKIT_TOKEN?.trim();
|
|
54
88
|
if (fixed) {
|
|
89
|
+
overrides('HAMMERKIT_TOKEN');
|
|
55
90
|
return {
|
|
56
91
|
token: async () => {
|
|
57
92
|
if (!isToken(fixed)) {
|
|
@@ -61,6 +96,9 @@ function getTokenSource(environment, name, server) {
|
|
|
61
96
|
},
|
|
62
97
|
};
|
|
63
98
|
}
|
|
99
|
+
if (server.tokenCommand) {
|
|
100
|
+
return commandSource(environment, server.tokenCommand, `the token command of ${name}`);
|
|
101
|
+
}
|
|
64
102
|
return { token: () => (0, credentials_1.getAccessToken)(environment, name, server) };
|
|
65
103
|
}
|
|
66
104
|
exports.getTokenSource = getTokenSource;
|
package/dist/run-program.js
CHANGED
|
@@ -7,6 +7,8 @@ exports.runProgram = void 0;
|
|
|
7
7
|
const program_1 = require("./program");
|
|
8
8
|
const parse_error_1 = require("./schema/parse-error");
|
|
9
9
|
const colors_1 = __importDefault(require("colors"));
|
|
10
|
+
const commander_1 = require("commander");
|
|
11
|
+
const log_1 = require("./log");
|
|
10
12
|
function runProgram(env, argv, exitOverride) {
|
|
11
13
|
return (0, program_1.getProgram)(env, argv)
|
|
12
14
|
.then(({ program, args }) => {
|
|
@@ -18,8 +20,15 @@ function runProgram(env, argv, exitOverride) {
|
|
|
18
20
|
for (const error of err.zod.errors) {
|
|
19
21
|
env.stderr.write(` ${colors_1.default.underline(colors_1.default.blue(`error`))} at ${colors_1.default.gray(error.path.join('.'))} ${error.message} ${colors_1.default.gray(error.code)}\n`);
|
|
20
22
|
}
|
|
23
|
+
// printed, but still a failure: CI must not pass on a build file that doesn't parse
|
|
24
|
+
throw err;
|
|
21
25
|
}
|
|
22
26
|
else {
|
|
27
|
+
// what commander reports (program.error, a wrong option) is printed already; anything
|
|
28
|
+
// else, such as a build file that is no YAML, would otherwise end the process silently
|
|
29
|
+
if (!(err instanceof commander_1.CommanderError)) {
|
|
30
|
+
env.stderr.write(`${colors_1.default.red('error')}: ${(0, log_1.getErrorMessage)(err)}\n`);
|
|
31
|
+
}
|
|
23
32
|
throw err;
|
|
24
33
|
}
|
|
25
34
|
});
|
|
@@ -29,9 +29,6 @@ exports.buildFileBaseTaskSchema = (0, zod_1.object)({
|
|
|
29
29
|
src: (0, zod_1.array)((0, zod_1.string)()).optional(),
|
|
30
30
|
generates: (0, zod_1.array)(build_file_volume_schema_1.buildFileVolumeSchema).optional(),
|
|
31
31
|
shell: (0, zod_1.string)().optional(),
|
|
32
|
-
// A task that watches its own sources and restarts itself (e.g. `ng serve`,
|
|
33
|
-
// `tsc -w`). In watch mode hammerkit then does not watch/restart it itself.
|
|
34
|
-
continuous: (0, zod_1.boolean)().optional(),
|
|
35
32
|
// Maximum execution time, e.g. `30s`, `10m`, `1h30m`. The task fails when it
|
|
36
33
|
// runs longer. Does not affect the cache key (ADR-0002).
|
|
37
34
|
timeout: duration_schema_1.durationSchema.optional(),
|
|
@@ -49,5 +46,8 @@ exports.buildFileBaseTaskSchema = (0, zod_1.object)({
|
|
|
49
46
|
// Name of the server (see `hammerkit remote`) the task has to run on; defaults
|
|
50
47
|
// to the build file's `runner`. Never a local fallback when it is unknown.
|
|
51
48
|
runner: runner_schema_1.runnerSchema.optional(),
|
|
49
|
+
// false: a server leaves it out of a published version, for a task only this machine
|
|
50
|
+
// runs (a dev watcher, a task that mounts the docker socket)
|
|
51
|
+
publish: (0, zod_1.boolean)().optional(),
|
|
52
52
|
});
|
|
53
53
|
//# sourceMappingURL=build-file-base-task-schema.js.map
|
|
@@ -41,12 +41,22 @@ exports.buildFileContainerServiceSchema = (0, zod_1.object)({
|
|
|
41
41
|
// the service account its provider secrets are read as, unless a secret names one
|
|
42
42
|
account: (0, zod_1.string)().optional(),
|
|
43
43
|
resources: resources_schema_1.resourcesSchema.optional(),
|
|
44
|
+
// on Kubernetes, how often its container may crash and be restarted before the
|
|
45
|
+
// service fails (2 by default): it fails when it crashes once more
|
|
46
|
+
restarts: (0, zod_1.number)()
|
|
47
|
+
.int()
|
|
48
|
+
.min(0)
|
|
49
|
+
.max(10)
|
|
50
|
+
.describe('crashes restarted before it fails on Kubernetes, 0 to 10, 2 by default')
|
|
51
|
+
.optional(),
|
|
44
52
|
// on Kubernetes, the size of the claim that holds its sources, mounts and volumes
|
|
45
53
|
volumeSize: size_schema_1.sizeSchema.describe('size of its claim on Kubernetes, e.g. 10Gi').optional(),
|
|
46
54
|
// what the service reaches besides its needs; tasks needing it always reach it
|
|
47
55
|
network: network_schema_1.networkSchema.optional(),
|
|
48
56
|
// whether its `cmd` takes the arguments after `--` of `up`: false refuses them
|
|
49
57
|
args: (0, zod_1.boolean)().optional(),
|
|
58
|
+
// false: a server leaves it out of a published version
|
|
59
|
+
publish: (0, zod_1.boolean)().optional(),
|
|
50
60
|
})
|
|
51
61
|
.strict()
|
|
52
62
|
.describe('container service');
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildFileContainerTaskSchema = void 0;
|
|
3
|
+
exports.CONTAINER_TASK_PROPERTIES = exports.buildFileExtendingContainerTaskSchema = exports.buildFileContainerTaskSchema = void 0;
|
|
4
4
|
const build_file_base_task_schema_1 = require("./build-file-base-task-schema");
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
const size_schema_1 = require("./size-schema");
|
|
7
|
+
const capabilities_schema_1 = require("./capabilities-schema");
|
|
7
8
|
exports.buildFileContainerTaskSchema = build_file_base_task_schema_1.buildFileBaseTaskSchema
|
|
8
9
|
.extend({
|
|
9
10
|
image: (0, zod_1.string)(),
|
|
10
11
|
mounts: (0, zod_1.array)((0, zod_1.string)()).optional(),
|
|
11
12
|
// on Kubernetes, the size of the claim that holds its sources, mounts and outputs
|
|
12
13
|
volumeSize: size_schema_1.sizeSchema.describe('size of its claim on Kubernetes, e.g. 10Gi').optional(),
|
|
14
|
+
// Linux capabilities added to the container, such as SYS_ADMIN for a daemonless image
|
|
15
|
+
// build. A server allows them only in a sandboxed runtime (`backend.capabilities`).
|
|
16
|
+
capAdd: capabilities_schema_1.capabilitiesSchema.describe('Linux capabilities added to the container, e.g. [SYS_ADMIN]').optional(),
|
|
13
17
|
})
|
|
14
18
|
.strict()
|
|
15
19
|
.describe('container task');
|
|
20
|
+
// A task that extends another takes the image of its base, so it may set the
|
|
21
|
+
// container properties without an image of its own; once extended, a task whose
|
|
22
|
+
// base has no image either is refused them (applySchemaExtension).
|
|
23
|
+
exports.buildFileExtendingContainerTaskSchema = exports.buildFileContainerTaskSchema
|
|
24
|
+
.extend({
|
|
25
|
+
image: (0, zod_1.string)().optional(),
|
|
26
|
+
extend: (0, zod_1.string)(),
|
|
27
|
+
})
|
|
28
|
+
.strict()
|
|
29
|
+
.describe('container task taking its image from the task it extends');
|
|
30
|
+
exports.CONTAINER_TASK_PROPERTIES = ['mounts', 'volumeSize', 'capAdd'];
|
|
16
31
|
//# sourceMappingURL=build-file-container-task-schema.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isGitSource = exports.buildFileIncludeSchema = exports.buildFileGitSourceSchema = void 0;
|
|
3
|
+
exports.isPublished = exports.isGitSource = exports.buildFileIncludeSchema = exports.buildFileLocalSourceSchema = exports.buildFileGitSourceSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const envs_schema_1 = require("./envs-schema");
|
|
6
6
|
// A repo URL or ref starting with `-` would be read by git as an option
|
|
@@ -21,11 +21,25 @@ exports.buildFileGitSourceSchema = (0, zod_1.object)({
|
|
|
21
21
|
path: (0, zod_1.string)().optional().describe('Build file or directory within the repository'),
|
|
22
22
|
// overrides for the top-level `envs` the included file declares as its inputs
|
|
23
23
|
with: envs_schema_1.envsSchema.optional(),
|
|
24
|
+
publish: (0, zod_1.boolean)().optional().describe('false: a server leaves out what this file declares'),
|
|
24
25
|
}).strict();
|
|
25
|
-
// A
|
|
26
|
-
|
|
26
|
+
// A local path, with values for the inputs of the file it names like `with` of a git
|
|
27
|
+
// source (a value may pass on an `envs` value of the including file as `${NAME}`), and
|
|
28
|
+
// whether a server publishes what it declares.
|
|
29
|
+
exports.buildFileLocalSourceSchema = (0, zod_1.object)({
|
|
30
|
+
path: (0, zod_1.string)().min(1).describe('Build file or directory, relative to the including file'),
|
|
31
|
+
with: envs_schema_1.envsSchema.optional(),
|
|
32
|
+
publish: (0, zod_1.boolean)().optional().describe('false: a server leaves out what this file declares'),
|
|
33
|
+
}).strict();
|
|
34
|
+
// A string is a local path; an object is a local path with options, or a git source (ADR-0004).
|
|
35
|
+
exports.buildFileIncludeSchema = (0, zod_1.union)([(0, zod_1.string)(), exports.buildFileLocalSourceSchema, exports.buildFileGitSourceSchema]);
|
|
27
36
|
function isGitSource(include) {
|
|
28
|
-
return typeof include !== 'string';
|
|
37
|
+
return typeof include !== 'string' && 'git' in include;
|
|
29
38
|
}
|
|
30
39
|
exports.isGitSource = isGitSource;
|
|
40
|
+
// `publish: false`: what the file declares is left out of a version a server publishes
|
|
41
|
+
function isPublished(include) {
|
|
42
|
+
return typeof include === 'string' || include.publish !== false;
|
|
43
|
+
}
|
|
44
|
+
exports.isPublished = isPublished;
|
|
31
45
|
//# sourceMappingURL=build-file-include-schema.js.map
|
|
@@ -13,5 +13,7 @@ exports.buildFileKubernetesServiceSchema = (0, zod_1.object)({
|
|
|
13
13
|
selector: build_file_kubernetes_service_selector_schema_1.buildFileKubernetesServiceSelectorSchema,
|
|
14
14
|
labels: labels_schema_1.labelsSchema.optional(),
|
|
15
15
|
ports: (0, zod_1.array)((0, zod_1.union)([(0, zod_1.string)(), (0, zod_1.number)()])),
|
|
16
|
+
// false: a server leaves it out of a published version
|
|
17
|
+
publish: (0, zod_1.boolean)().optional(),
|
|
16
18
|
}).strict();
|
|
17
19
|
//# sourceMappingURL=build-file-kubernetes-service-schema.js.map
|
|
@@ -4,7 +4,11 @@ exports.isBuildFileLocalTaskSchema = exports.isBuildFileContainerTaskSchema = ex
|
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const build_file_container_task_schema_1 = require("./build-file-container-task-schema");
|
|
6
6
|
const build_file_local_task_schema_1 = require("./build-file-local-task-schema");
|
|
7
|
-
exports.buildFileTaskSchema = (0, zod_1.union)([
|
|
7
|
+
exports.buildFileTaskSchema = (0, zod_1.union)([
|
|
8
|
+
build_file_container_task_schema_1.buildFileContainerTaskSchema,
|
|
9
|
+
build_file_local_task_schema_1.buildFileLocalTaskSchema,
|
|
10
|
+
build_file_container_task_schema_1.buildFileExtendingContainerTaskSchema,
|
|
11
|
+
]);
|
|
8
12
|
const isBuildFileContainerTaskSchema = (val) => 'image' in val;
|
|
9
13
|
exports.isBuildFileContainerTaskSchema = isBuildFileContainerTaskSchema;
|
|
10
14
|
const isBuildFileLocalTaskSchema = (val) => !('image' in val);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.capabilitiesSchema = exports.LINUX_CAPABILITIES = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
// The Linux capabilities, without the CAP_ prefix (capabilities(7))
|
|
6
|
+
exports.LINUX_CAPABILITIES = [
|
|
7
|
+
'AUDIT_CONTROL',
|
|
8
|
+
'AUDIT_READ',
|
|
9
|
+
'AUDIT_WRITE',
|
|
10
|
+
'BLOCK_SUSPEND',
|
|
11
|
+
'BPF',
|
|
12
|
+
'CHECKPOINT_RESTORE',
|
|
13
|
+
'CHOWN',
|
|
14
|
+
'DAC_OVERRIDE',
|
|
15
|
+
'DAC_READ_SEARCH',
|
|
16
|
+
'FOWNER',
|
|
17
|
+
'FSETID',
|
|
18
|
+
'IPC_LOCK',
|
|
19
|
+
'IPC_OWNER',
|
|
20
|
+
'KILL',
|
|
21
|
+
'LEASE',
|
|
22
|
+
'LINUX_IMMUTABLE',
|
|
23
|
+
'MAC_ADMIN',
|
|
24
|
+
'MAC_OVERRIDE',
|
|
25
|
+
'MKNOD',
|
|
26
|
+
'NET_ADMIN',
|
|
27
|
+
'NET_BIND_SERVICE',
|
|
28
|
+
'NET_BROADCAST',
|
|
29
|
+
'NET_RAW',
|
|
30
|
+
'PERFMON',
|
|
31
|
+
'SETFCAP',
|
|
32
|
+
'SETGID',
|
|
33
|
+
'SETPCAP',
|
|
34
|
+
'SETUID',
|
|
35
|
+
'SYS_ADMIN',
|
|
36
|
+
'SYS_BOOT',
|
|
37
|
+
'SYS_CHROOT',
|
|
38
|
+
'SYS_MODULE',
|
|
39
|
+
'SYS_NICE',
|
|
40
|
+
'SYS_PACCT',
|
|
41
|
+
'SYS_PTRACE',
|
|
42
|
+
'SYS_RAWIO',
|
|
43
|
+
'SYS_RESOURCE',
|
|
44
|
+
'SYS_TIME',
|
|
45
|
+
'SYS_TTY_CONFIG',
|
|
46
|
+
'SYSLOG',
|
|
47
|
+
'WAKE_ALARM',
|
|
48
|
+
];
|
|
49
|
+
// Capabilities a container gets on top of its runtime's default set, by name such as
|
|
50
|
+
// SYS_ADMIN. `ALL` is not one: a task names what it needs.
|
|
51
|
+
exports.capabilitiesSchema = (0, zod_1.array)((0, zod_1.enum)(exports.LINUX_CAPABILITIES, {
|
|
52
|
+
errorMap: (issue, ctx) => issue.code === 'invalid_enum_value'
|
|
53
|
+
? { message: `unknown capability ${JSON.stringify(issue.received)}, expected a name such as SYS_ADMIN` }
|
|
54
|
+
: { message: ctx.defaultError },
|
|
55
|
+
})).refine((names) => new Set(names).size === names.length, 'a capability is listed twice');
|
|
56
|
+
//# sourceMappingURL=capabilities-schema.js.map
|
|
@@ -1,20 +1,69 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.findService = exports.findTask = exports.parseReferences = void 0;
|
|
3
|
+
exports.findService = exports.findTask = exports.parseReferences = exports.getPublishedFiles = void 0;
|
|
4
4
|
const build_file_task_schema_1 = require("./build-file-task-schema");
|
|
5
5
|
const build_file_service_schema_1 = require("./build-file-service-schema");
|
|
6
6
|
const split_name_1 = require("../planner/utils/split-name");
|
|
7
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
|
+
const build_file_container_task_schema_1 = require("./build-file-container-task-schema");
|
|
10
11
|
const resolve_cache_1 = require("../cache/resolve-cache");
|
|
11
12
|
const project_root_1 = require("../planner/project-root");
|
|
12
13
|
const secret_catalog_1 = require("../secrets/secret-catalog");
|
|
14
|
+
const host_config_1 = require("../remote/host-config");
|
|
15
|
+
// The providers and accounts of the host config, see HostSecretDeclarations
|
|
16
|
+
async function readHostSecrets(environment) {
|
|
17
|
+
const config = await (0, host_config_1.readHostConfig)(environment);
|
|
18
|
+
return {
|
|
19
|
+
fileName: (0, host_config_1.getHostConfigPath)(environment),
|
|
20
|
+
secretProviders: config.secretProviders,
|
|
21
|
+
secretAccounts: config.secretAccounts,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// What a task or service names that a server left out (`publish: false`)
|
|
25
|
+
class UnpublishedError extends Error {
|
|
26
|
+
constructor(name, reason) {
|
|
27
|
+
super(`${name} is not published (${reason})`);
|
|
28
|
+
this.name = name;
|
|
29
|
+
this.reason = reason;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// The task or service `user` names another one by, which a server left out: the error says
|
|
33
|
+
// which published one still needs it, as an error elsewhere is left as it is
|
|
34
|
+
function findPublished(user, verb, find) {
|
|
35
|
+
try {
|
|
36
|
+
return find();
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
if (e instanceof UnpublishedError) {
|
|
40
|
+
throw new Error(`${user} ${verb} ${e.name}, which is not published (${e.reason})`);
|
|
41
|
+
}
|
|
42
|
+
throw e;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// The build files a server publishes: the entry file and what it includes or references,
|
|
46
|
+
// except through an include with `publish: false`. The key of each is in ctx.files.
|
|
47
|
+
function getPublishedFiles(mainScope) {
|
|
48
|
+
const published = new Set([mainScope]);
|
|
49
|
+
const queue = [mainScope];
|
|
50
|
+
for (let scope = queue.shift(); scope; scope = queue.shift()) {
|
|
51
|
+
for (const reference of Object.values(scope.references)) {
|
|
52
|
+
if (reference.publish && !published.has(reference.scope)) {
|
|
53
|
+
published.add(reference.scope);
|
|
54
|
+
queue.push(reference.scope);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return published;
|
|
59
|
+
}
|
|
60
|
+
exports.getPublishedFiles = getPublishedFiles;
|
|
13
61
|
function combinePath(namePrefix, name) {
|
|
14
62
|
return namePrefix.length > 0 ? `${namePrefix}:${name}` : name;
|
|
15
63
|
}
|
|
16
64
|
async function parseReferences(ctx, mainScope, environment) {
|
|
17
65
|
const declaredCaches = {};
|
|
66
|
+
const unpublished = {};
|
|
18
67
|
const reference = {
|
|
19
68
|
files: {},
|
|
20
69
|
tasks: {},
|
|
@@ -24,9 +73,28 @@ async function parseReferences(ctx, mainScope, environment) {
|
|
|
24
73
|
caches: {},
|
|
25
74
|
secrets: (0, secret_catalog_1.emptySecretCatalog)(),
|
|
26
75
|
projectRoot: await (0, project_root_1.findProjectRoot)(mainScope.cwd, environment),
|
|
76
|
+
unpublished,
|
|
77
|
+
};
|
|
78
|
+
// a server leaves out what is not published: it doesn't run, and isn't checked against
|
|
79
|
+
// its policy. Locally everything is there.
|
|
80
|
+
const publishedFiles = environment.hosted ? getPublishedFiles(mainScope) : null;
|
|
81
|
+
const isLeftOut = (file, name, schema) => {
|
|
82
|
+
if (!publishedFiles) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (!publishedFiles.has(file)) {
|
|
86
|
+
unpublished[name] = 'its build file is included with publish: false';
|
|
87
|
+
}
|
|
88
|
+
else if (schema.publish === false) {
|
|
89
|
+
unpublished[name] = 'publish: false';
|
|
90
|
+
}
|
|
91
|
+
return name in unpublished;
|
|
27
92
|
};
|
|
28
93
|
for (const file of Object.values(ctx.files)) {
|
|
29
|
-
|
|
94
|
+
// what a server leaves out of a file it doesn't publish: its tasks and services (see
|
|
95
|
+
// isLeftOut), and where it would run or store things, which only a local run reads
|
|
96
|
+
const published = !publishedFiles || publishedFiles.has(file);
|
|
97
|
+
if (published && environment.hosted && (file.schema.secretProviders || file.schema.secretAccounts)) {
|
|
30
98
|
throw new Error(`${file.fileName} declares secret providers or accounts, which a server defines in its own config`);
|
|
31
99
|
}
|
|
32
100
|
if (!reference.envFiles[file.cwd]) {
|
|
@@ -38,6 +106,9 @@ async function parseReferences(ctx, mainScope, environment) {
|
|
|
38
106
|
for (const [taskName, task] of Object.entries(file.schema.tasks)) {
|
|
39
107
|
const taskEnvs = (0, merge_environment_variables_1.mergeEnvironmentVariables)(task.envs, buildFileEnvs);
|
|
40
108
|
const relativeName = combinePath(file.namePrefix, taskName);
|
|
109
|
+
if (isLeftOut(file, relativeName, task)) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
41
112
|
reference.tasks[relativeName] = {
|
|
42
113
|
type: 'task',
|
|
43
114
|
schema: task,
|
|
@@ -51,7 +122,7 @@ async function parseReferences(ctx, mainScope, environment) {
|
|
|
51
122
|
};
|
|
52
123
|
}
|
|
53
124
|
}
|
|
54
|
-
if (file.schema.environments) {
|
|
125
|
+
if (file.schema.environments && published) {
|
|
55
126
|
for (const [envName, env] of Object.entries(file.schema.environments)) {
|
|
56
127
|
reference.environments[envName] = {
|
|
57
128
|
type: 'environment',
|
|
@@ -61,7 +132,7 @@ async function parseReferences(ctx, mainScope, environment) {
|
|
|
61
132
|
};
|
|
62
133
|
}
|
|
63
134
|
}
|
|
64
|
-
if (file.schema.caches) {
|
|
135
|
+
if (file.schema.caches && published) {
|
|
65
136
|
for (const [cacheName, cache] of Object.entries(file.schema.caches)) {
|
|
66
137
|
declaredCaches[cacheName] = cache;
|
|
67
138
|
}
|
|
@@ -69,6 +140,9 @@ async function parseReferences(ctx, mainScope, environment) {
|
|
|
69
140
|
if (file.schema.services) {
|
|
70
141
|
for (const [serviceName, service] of Object.entries(file.schema.services)) {
|
|
71
142
|
const relativeName = combinePath(file.namePrefix, serviceName);
|
|
143
|
+
if (isLeftOut(file, relativeName, service)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
72
146
|
const serviceEnvs = (0, merge_environment_variables_1.mergeEnvironmentVariables)((0, build_file_service_schema_1.isBuildFileKubernetesServiceSchema)(service) ? {} : service.envs, buildFileEnvs);
|
|
73
147
|
reference.services[relativeName] = {
|
|
74
148
|
type: 'service',
|
|
@@ -98,7 +172,7 @@ async function parseReferences(ctx, mainScope, environment) {
|
|
|
98
172
|
reference.caches = (0, resolve_cache_1.withBuiltinCaches)(declaredCaches);
|
|
99
173
|
reference.secrets = environment.hosted
|
|
100
174
|
? { ...environment.hosted.catalog, explicitAccounts: true }
|
|
101
|
-
: (0, secret_catalog_1.collectSecretCatalog)(Object.values(ctx.files), environment, reference);
|
|
175
|
+
: (0, secret_catalog_1.collectSecretCatalog)(Object.values(ctx.files), environment, reference, await readHostSecrets(environment));
|
|
102
176
|
return reference;
|
|
103
177
|
}
|
|
104
178
|
exports.parseReferences = parseReferences;
|
|
@@ -122,7 +196,8 @@ function applySchemaExtension(reference, task) {
|
|
|
122
196
|
task.extension = 'applied';
|
|
123
197
|
return;
|
|
124
198
|
}
|
|
125
|
-
const
|
|
199
|
+
const name = task.schema.extend;
|
|
200
|
+
const extend = findPublished(task.relativeName, 'extends', () => findTask(reference, task.scope, name, null)).task;
|
|
126
201
|
applySchemaExtension(reference, extend);
|
|
127
202
|
task.schema.shell = task.schema.shell ?? extend.schema.shell;
|
|
128
203
|
task.schema.cache = task.schema.cache ?? extend.schema.cache;
|
|
@@ -148,6 +223,7 @@ function applySchemaExtension(reference, task) {
|
|
|
148
223
|
if (!reset.has('mounts'))
|
|
149
224
|
taskSchema.mounts = extendArray(taskSchema.mounts, extend.schema.mounts);
|
|
150
225
|
}
|
|
226
|
+
rejectContainerPropertiesWithoutImage(task);
|
|
151
227
|
// deps and needs resolve in the file that declares them, so every base up the
|
|
152
228
|
// chain contributes its own, not only the nearest; what a task resets is not
|
|
153
229
|
// passed on to the tasks extending it either
|
|
@@ -162,6 +238,16 @@ function applySchemaExtension(reference, task) {
|
|
|
162
238
|
}
|
|
163
239
|
task.extension = 'applied';
|
|
164
240
|
}
|
|
241
|
+
function rejectContainerPropertiesWithoutImage(task) {
|
|
242
|
+
if ((0, build_file_task_schema_1.isBuildFileContainerTaskSchema)(task.schema)) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const schema = task.schema;
|
|
246
|
+
const property = build_file_container_task_schema_1.CONTAINER_TASK_PROPERTIES.find((key) => schema[key] !== undefined);
|
|
247
|
+
if (property) {
|
|
248
|
+
throw new Error(`task ${task.relativeName} sets ${property}, but neither it nor its base has an image`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
165
251
|
function extendArray(base, extend) {
|
|
166
252
|
if (!extend) {
|
|
167
253
|
return base;
|
|
@@ -182,7 +268,7 @@ function extendObject(base, extend) {
|
|
|
182
268
|
}
|
|
183
269
|
function resolveDeps(reference, taskOrService, scope, schema) {
|
|
184
270
|
for (const depName of schema.deps || []) {
|
|
185
|
-
const depTask = findTask(reference, scope, depName, null);
|
|
271
|
+
const depTask = findPublished(taskOrService.relativeName, 'depends on', () => findTask(reference, scope, depName, null));
|
|
186
272
|
taskOrService.deps.push({
|
|
187
273
|
task: depTask.task,
|
|
188
274
|
type: depTask.type,
|
|
@@ -195,7 +281,8 @@ function resolveInit(reference, service) {
|
|
|
195
281
|
if ((0, build_file_service_schema_1.isBuildFileKubernetesServiceSchema)(service.schema) || !service.schema.init) {
|
|
196
282
|
return;
|
|
197
283
|
}
|
|
198
|
-
const
|
|
284
|
+
const init = service.schema.init;
|
|
285
|
+
const initTask = findPublished(service.relativeName, 'runs as init', () => findTask(reference, service.scope, init, null));
|
|
199
286
|
service.init = {
|
|
200
287
|
task: initTask.task,
|
|
201
288
|
type: initTask.type,
|
|
@@ -210,19 +297,27 @@ function resolveNeeds(reference, taskOrService, scope, schema) {
|
|
|
210
297
|
for (const needName of schema.needs || []) {
|
|
211
298
|
if (typeof needName === 'string') {
|
|
212
299
|
taskOrService.needs.push({
|
|
213
|
-
|
|
214
|
-
|
|
300
|
+
// the host name it is reached by: a service of an include, `services:postgres`, as
|
|
301
|
+
// `services-postgres`, since Docker and DNS take no colon
|
|
302
|
+
relativeName: needName.replace(/:/g, '-'),
|
|
303
|
+
service: findPublished(taskOrService.relativeName, 'needs', () => findService(reference, scope, needName)),
|
|
215
304
|
});
|
|
216
305
|
}
|
|
217
306
|
else {
|
|
218
307
|
taskOrService.needs.push({
|
|
219
308
|
relativeName: needName.name,
|
|
220
|
-
service: findService(reference, scope, needName.service),
|
|
309
|
+
service: findPublished(taskOrService.relativeName, 'needs', () => findService(reference, scope, needName.service)),
|
|
221
310
|
});
|
|
222
311
|
}
|
|
223
312
|
}
|
|
313
|
+
const names = taskOrService.needs.map((need) => need.relativeName);
|
|
314
|
+
const twice = names.find((name, index) => names.indexOf(name) !== index);
|
|
315
|
+
if (twice) {
|
|
316
|
+
throw new Error(`${taskOrService.relativeName} needs two services as ${twice}, give one another name with name:`);
|
|
317
|
+
}
|
|
224
318
|
}
|
|
225
319
|
function findTask(ctx, scope, name, type) {
|
|
320
|
+
refuseNotFetched(scope, name);
|
|
226
321
|
const ref = (0, split_name_1.splitName)(name);
|
|
227
322
|
if (ref.prefix) {
|
|
228
323
|
if (scope.references[ref.prefix]) {
|
|
@@ -238,15 +333,25 @@ function findTask(ctx, scope, name, type) {
|
|
|
238
333
|
throw new Error('unable to find ' + name); // TODO improve
|
|
239
334
|
}
|
|
240
335
|
exports.findTask = findTask;
|
|
336
|
+
// A name in a git source a server did not fetch, since it is left out
|
|
337
|
+
function refuseNotFetched(scope, name) {
|
|
338
|
+
if (scope.notFetched) {
|
|
339
|
+
throw new UnpublishedError(combinePath(scope.namePrefix, name), 'its build file is included with publish: false');
|
|
340
|
+
}
|
|
341
|
+
}
|
|
241
342
|
function resolveTask(ctx, scope, name, type) {
|
|
242
343
|
const fullName = combinePath(scope.namePrefix, name);
|
|
243
344
|
const buildFileTask = ctx.tasks[fullName];
|
|
345
|
+
if (!buildFileTask && ctx.unpublished?.[fullName]) {
|
|
346
|
+
throw new UnpublishedError(fullName, ctx.unpublished[fullName]);
|
|
347
|
+
}
|
|
244
348
|
if (!buildFileTask) {
|
|
245
349
|
throw new Error('unable to find ' + fullName);
|
|
246
350
|
}
|
|
247
351
|
return { task: buildFileTask, type };
|
|
248
352
|
}
|
|
249
353
|
function findService(ctx, scope, name) {
|
|
354
|
+
refuseNotFetched(scope, name);
|
|
250
355
|
const ref = (0, split_name_1.splitName)(name);
|
|
251
356
|
if (ref.prefix) {
|
|
252
357
|
if (scope.references[ref.prefix]) {
|
|
@@ -265,6 +370,9 @@ exports.findService = findService;
|
|
|
265
370
|
function resolveService(ctx, scope, name) {
|
|
266
371
|
const fullName = combinePath(scope.namePrefix, name);
|
|
267
372
|
const service = ctx.services[fullName];
|
|
373
|
+
if (!service && ctx.unpublished?.[fullName]) {
|
|
374
|
+
throw new UnpublishedError(fullName, ctx.unpublished[fullName]);
|
|
375
|
+
}
|
|
268
376
|
if (!service) {
|
|
269
377
|
throw new Error('unable to find ' + fullName);
|
|
270
378
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertNoRemovedProperties = void 0;
|
|
4
|
+
// Properties a build file may no longer have, with what takes their place: a file that
|
|
5
|
+
// still has one fails to parse with that, rather than with an unknown key.
|
|
6
|
+
function assertNoRemovedProperties(input, fileName) {
|
|
7
|
+
const tasks = isObject(input) && isObject(input.tasks) ? input.tasks : {};
|
|
8
|
+
for (const [name, task] of Object.entries(tasks)) {
|
|
9
|
+
if (isObject(task) && 'continuous' in task) {
|
|
10
|
+
throw new Error(`task ${name} in ${fileName}: continuous is for services only since hammerkit 2. A dev server that ` +
|
|
11
|
+
`watches its own files (ng serve, vite, nodemon) is a service with continuous: true, see ` +
|
|
12
|
+
`https://hammerkit.dev/docs/service/container#continuous`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.assertNoRemovedProperties = assertNoRemovedProperties;
|
|
17
|
+
function isObject(value) {
|
|
18
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=removed-properties.js.map
|