hammerkit 1.25.0 → 1.27.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 +53 -1
- package/dist/cache/backends/local-cache-backend.js +10 -2
- package/dist/cache/explain.js +13 -11
- package/dist/cli.js +3 -0
- package/dist/docker/item-network.js +471 -0
- package/dist/docker/pull.js +4 -2
- package/dist/docker/remove-orphaned-containers.js +13 -6
- package/dist/environment/replace-env-variables.js +8 -6
- package/dist/executer/await-completed-dependencies.js +2 -1
- package/dist/executer/check-readiness.js +2 -2
- package/dist/executer/container-task-state.js +8 -6
- package/dist/executer/docker-service.js +24 -13
- package/dist/executer/docker-task.js +18 -29
- package/dist/executer/event-cache.js +4 -0
- package/dist/executer/execute-command.js +18 -5
- package/dist/executer/execute-docker.js +4 -1
- package/dist/executer/execution-steps.js +33 -7
- package/dist/executer/get-docker-executor.js +14 -7
- package/dist/executer/scheduler/enqueue-next.js +17 -3
- package/dist/executer/watch-loop.js +2 -2
- package/dist/executer/watch-state-key.js +1 -1
- package/dist/file/within-root.js +42 -0
- package/dist/harness/connect.js +99 -0
- package/dist/harness/format.js +73 -0
- package/dist/harness/mcp-command.js +59 -0
- package/dist/harness/mcp.js +432 -0
- package/dist/harness/opencode.js +492 -0
- package/dist/harness/pi.js +217 -0
- package/dist/harness/sandbox-client.js +373 -0
- package/dist/index.js +33 -36
- package/dist/kubernetes/apply.js +31 -1
- package/dist/kubernetes/await-running-state.js +33 -1
- package/dist/kubernetes/ensure-kubernetes-deployment-exists.js +44 -22
- package/dist/kubernetes/ensure-kubernetes-persistent-volume-claim-exists.js +131 -4
- package/dist/kubernetes/ensure-kubernetes-service-exists.js +25 -4
- package/dist/kubernetes/ensure-persistent-data.js +48 -7
- package/dist/kubernetes/get-service-ip.js +15 -1
- package/dist/kubernetes/job-placement.js +99 -0
- package/dist/kubernetes/job-secrets.js +306 -0
- package/dist/kubernetes/kept-claims.js +191 -0
- package/dist/kubernetes/kubernetes-instance.js +10 -2
- package/dist/kubernetes/network-gate.js +71 -0
- package/dist/kubernetes/network-policy.js +526 -0
- package/dist/kubernetes/resources.js +13 -2
- package/dist/kubernetes/restore-kubernetes-data.js +19 -4
- package/dist/kubernetes/secrets.js +2 -0
- package/dist/kubernetes/store-kubernetes-data.js +1 -1
- package/dist/kubernetes/volumes.js +100 -11
- package/dist/main.js +41 -0
- package/dist/network/egress-proxy.js +368 -0
- package/dist/network/port-relay.js +35 -0
- package/dist/optimizer/get-work-cache-stats.js +16 -0
- package/dist/optimizer/work-task-cache-description.js +3 -2
- package/dist/planner/service-definition.js +8 -2
- package/dist/planner/utils/append-work-environment.js +2 -0
- package/dist/planner/utils/append-work-service.js +4 -0
- package/dist/planner/utils/append-work-task.js +17 -1
- package/dist/planner/utils/assert-item-paths.js +50 -0
- package/dist/planner/utils/hosted-volumes.js +35 -0
- package/dist/planner/utils/task-arguments.js +155 -0
- package/dist/planner/work-cache-id.js +15 -1
- package/dist/planner/work-network.js +193 -0
- package/dist/planner/work-runtime-docker.js +43 -0
- package/dist/planner/work-runtime-kubernetes.js +336 -102
- package/dist/planner/work-runtime-local.js +16 -8
- package/dist/planner/work-service.js +7 -3
- package/dist/planner/work-state-record.js +30 -0
- package/dist/program.js +80 -7
- package/dist/remote/annotations.js +103 -0
- package/dist/remote/bundle-text.js +24 -0
- package/dist/remote/bundle.js +9 -1
- package/dist/remote/remote-commands.js +4 -1
- package/dist/remote/remote-work-commands.js +182 -69
- package/dist/remote/server-client.js +18 -5
- package/dist/remote/source-manifest.js +15 -2
- package/dist/remote/token-source.js +67 -0
- package/dist/schema/build-file-base-task-schema.js +3 -0
- package/dist/schema/build-file-container-service-schema.js +6 -0
- package/dist/schema/build-file-container-task-schema.js +3 -0
- package/dist/schema/build-file-environment-schema.js +9 -0
- package/dist/schema/network-schema.js +78 -0
- package/dist/schema/reference-parser.js +1 -0
- package/dist/schema/schema-parser.js +16 -3
- package/dist/schema/work-scope-parser.js +13 -0
- package/dist/secrets/command-secret-provider.js +73 -10
- package/dist/secrets/github-app-token-command.js +23 -0
- package/dist/secrets/github-app-token.js +191 -0
- package/dist/server/accounts-api.js +9 -2
- package/dist/server/definitions-api.js +69 -47
- package/dist/server/definitions.js +76 -51
- package/dist/server/executor.js +156 -41
- package/dist/server/git-checkout.js +72 -8
- package/dist/server/kubernetes-backend.js +554 -0
- package/dist/server/kubernetes-sandbox-storage.js +996 -0
- package/dist/server/oauth.js +1 -0
- package/dist/server/plan.js +51 -1
- package/dist/server/policy.js +104 -15
- package/dist/server/proposals-api.js +24 -40
- package/dist/server/proposals.js +12 -25
- package/dist/server/repo-access.js +37 -6
- package/dist/server/run-logs.js +217 -0
- package/dist/server/run-plan.js +51 -2
- package/dist/server/run-request.js +23 -10
- package/dist/server/run-store.js +5 -0
- package/dist/server/run-worker-command.js +13 -1
- package/dist/server/run-worker.js +121 -24
- package/dist/server/runs.js +184 -10
- package/dist/server/sandbox-docker.js +62 -0
- package/dist/server/sandbox-files-api.js +269 -0
- package/dist/server/sandbox-files.js +1054 -0
- package/dist/server/sandbox-patch.js +481 -0
- package/dist/server/sandbox-pod-command.js +15 -0
- package/dist/server/sandbox-pod.js +146 -0
- package/dist/server/sandbox-storage.js +462 -0
- package/dist/server/sandboxes-api.js +210 -0
- package/dist/server/sandboxes.js +519 -0
- package/dist/server/server-config.js +412 -22
- package/dist/server/server.js +204 -39
- package/dist/utils/run-id.js +12 -0
- package/harness/opencode.ts +11 -0
- package/harness/pi.ts +7 -0
- package/package.json +8 -2
- package/dist/remote/text-diff.js +0 -89
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getHealthcheckCommand = exports.getServiceCommand = exports.getServiceWorkingDir = exports.isContainerWorkService = void 0;
|
|
4
|
+
const task_arguments_1 = require("./utils/task-arguments");
|
|
4
5
|
const isContainerWorkService = (svc) => 'image' in svc && svc.type === 'container-service';
|
|
5
6
|
exports.isContainerWorkService = isContainerWorkService;
|
|
6
7
|
// The container's working directory: the declared `workdir`, else the build
|
|
@@ -10,14 +11,17 @@ exports.getServiceWorkingDir = getServiceWorkingDir;
|
|
|
10
11
|
// The container's entrypoint and command for a service: with a `shell`, its
|
|
11
12
|
// `cmd` runs as `<shell> -c "<cmd>"` in place of the image entrypoint; without
|
|
12
13
|
// one the tokenized `cmd` becomes the command and the image entrypoint stays.
|
|
13
|
-
|
|
14
|
+
// For Kubernetes, the arguments after -- are escaped so they stay text there.
|
|
15
|
+
function getServiceCommand(service, options = {}) {
|
|
14
16
|
if (!service.cmd) {
|
|
15
17
|
return { entrypoint: null, cmd: null };
|
|
16
18
|
}
|
|
17
19
|
if (service.shell) {
|
|
18
|
-
|
|
20
|
+
const cmd = options.kubernetes ? (0, task_arguments_1.getKubernetesCommandText)(service.cmd) : service.cmd.cmd;
|
|
21
|
+
return { entrypoint: [service.shell, '-c'], cmd: [cmd] };
|
|
19
22
|
}
|
|
20
|
-
|
|
23
|
+
const args = options.kubernetes ? (0, task_arguments_1.getKubernetesCommandArgs)(service.cmd) : service.cmd.parsed.args;
|
|
24
|
+
return { entrypoint: null, cmd: [service.cmd.parsed.command, ...args] };
|
|
21
25
|
}
|
|
22
26
|
exports.getServiceCommand = getServiceCommand;
|
|
23
27
|
// The healthcheck as an exec command, through the service's `shell` if it has one.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseStateRecord = exports.formatStateRecord = exports.getStateRecordName = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
// The state record says "the outputs at this task's paths were made by this
|
|
6
|
+
// variant of the task (its id), from this state key". It is kept per task
|
|
7
|
+
// *name*, because every variant of a task (other arguments after --, another
|
|
8
|
+
// --set value) writes the same output paths: a record per id would let a run of
|
|
9
|
+
// one variant overwrite the outputs while the record of another one still
|
|
10
|
+
// claims them. The id stored with the key makes the next run of that other
|
|
11
|
+
// variant a miss.
|
|
12
|
+
function getStateRecordName(task) {
|
|
13
|
+
return (0, crypto_1.createHash)('sha1').update(`${task.name}\0${task.data.projectRoot}`).digest('hex');
|
|
14
|
+
}
|
|
15
|
+
exports.getStateRecordName = getStateRecordName;
|
|
16
|
+
function formatStateRecord(task, stateKey) {
|
|
17
|
+
return `${task.id()}:${stateKey}`;
|
|
18
|
+
}
|
|
19
|
+
exports.formatStateRecord = formatStateRecord;
|
|
20
|
+
// The state key of the record when this variant made the outputs, else null.
|
|
21
|
+
function parseStateRecord(task, content) {
|
|
22
|
+
const record = content.trim();
|
|
23
|
+
const separator = record.indexOf(':');
|
|
24
|
+
if (separator < 0 || record.slice(0, separator) !== task.id()) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return record.slice(separator + 1) || null;
|
|
28
|
+
}
|
|
29
|
+
exports.parseStateRecord = parseStateRecord;
|
|
30
|
+
//# sourceMappingURL=work-state-record.js.map
|
package/dist/program.js
CHANGED
|
@@ -28,6 +28,9 @@ const cache_inventory_1 = require("./cache/cache-inventory");
|
|
|
28
28
|
const remote_commands_1 = require("./remote/remote-commands");
|
|
29
29
|
const server_command_1 = require("./server/server-command");
|
|
30
30
|
const run_worker_command_1 = require("./server/run-worker-command");
|
|
31
|
+
const github_app_token_command_1 = require("./secrets/github-app-token-command");
|
|
32
|
+
const work_network_1 = require("./planner/work-network");
|
|
33
|
+
const task_arguments_1 = require("./planner/utils/task-arguments");
|
|
31
34
|
async function createCli(fileName, environment, workScope) {
|
|
32
35
|
const { ctx, scope } = await (0, schema_parser_1.createParseContext)(fileName, environment);
|
|
33
36
|
const referencedScope = await (0, reference_parser_1.parseReferences)(ctx, scope, environment);
|
|
@@ -80,6 +83,31 @@ function printTaskDefinition(environment, definition) {
|
|
|
80
83
|
(0, log_1.printProperty)(environment, `env ${reference.name}`, `from $${reference.from} ${colors_1.default.grey(reference.available ? '(set)' : '(not set)')}`);
|
|
81
84
|
}
|
|
82
85
|
}
|
|
86
|
+
// `ls --json`: what a caller (an agent picking a task) needs to know, with the
|
|
87
|
+
// command that arguments after `--` are appended to (null: it takes none).
|
|
88
|
+
function getListJson(tasks, services) {
|
|
89
|
+
return {
|
|
90
|
+
tasks: tasks.map(({ item }) => ({
|
|
91
|
+
name: item.name,
|
|
92
|
+
description: item.data.description,
|
|
93
|
+
type: item.data.type,
|
|
94
|
+
...(item.data.type === 'container-task' ? { image: item.data.image } : {}),
|
|
95
|
+
deps: item.deps.map((d) => d.name),
|
|
96
|
+
needs: item.needs.map((n) => n.name),
|
|
97
|
+
labels: item.data.labels,
|
|
98
|
+
command: (0, task_arguments_1.getArgumentsTarget)(item.data)?.cmd ?? null,
|
|
99
|
+
})),
|
|
100
|
+
services: services.map(({ item }) => ({
|
|
101
|
+
name: item.name,
|
|
102
|
+
description: item.data.description,
|
|
103
|
+
type: item.data.type,
|
|
104
|
+
...(item.data.type === 'container-service' ? { image: item.data.image } : {}),
|
|
105
|
+
ports: item.data.ports.map((p) => ({ host: p.hostPort, container: p.containerPort })),
|
|
106
|
+
labels: item.data.labels,
|
|
107
|
+
command: (0, task_arguments_1.getArgumentsTarget)(item.data)?.cmd ?? null,
|
|
108
|
+
})),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
83
111
|
function parseWorkLabelScope(options) {
|
|
84
112
|
const scope = {
|
|
85
113
|
environmentName: null,
|
|
@@ -103,30 +131,57 @@ function parseWorkLabelScope(options) {
|
|
|
103
131
|
}
|
|
104
132
|
return scope;
|
|
105
133
|
}
|
|
134
|
+
// A task given arguments after -- skips the cache, unless the caller asks for a
|
|
135
|
+
// cache method on the command line.
|
|
136
|
+
function keepsCache(command) {
|
|
137
|
+
return command.getOptionValueSource('cache') === 'cli';
|
|
138
|
+
}
|
|
106
139
|
async function getProgram(environment, argv) {
|
|
107
140
|
const program = new commander_1.Command();
|
|
108
141
|
(0, server_command_1.registerServerCommand)(program, environment);
|
|
109
142
|
(0, run_worker_command_1.registerRunWorkerCommand)(program, environment);
|
|
110
|
-
|
|
143
|
+
(0, run_worker_command_1.registerSandboxPodCommand)(program, environment);
|
|
144
|
+
(0, github_app_token_command_1.registerGithubAppTokenCommand)(program, environment);
|
|
145
|
+
// What follows `--` belongs to the one task or service of a run or up, not to
|
|
146
|
+
// hammerkit: a `--set` or `--file` there is an argument like any other.
|
|
147
|
+
const separator = argv.indexOf('--');
|
|
148
|
+
const passthrough = separator >= 0 ? argv.slice(separator + 1) : [];
|
|
149
|
+
const parsed = (0, parse_set_arguments_1.parseSetArguments)(separator >= 0 ? argv.slice(0, separator) : argv);
|
|
111
150
|
const args = parsed.args;
|
|
112
151
|
environment.processEnvs = { ...environment.processEnvs, ...parsed.values };
|
|
152
|
+
const remote = args.indexOf('remote');
|
|
153
|
+
if (remote >= 0 && args[remote + 1] === 'mcp') {
|
|
154
|
+
// `remote mcp` speaks its protocol on stdout: a warning there would break it
|
|
155
|
+
environment.console = (0, log_1.consoleContext)(environment.stderr);
|
|
156
|
+
}
|
|
113
157
|
const fileIndex = args.indexOf('--file');
|
|
114
158
|
const fileName = fileIndex >= 0 ? (0, path_1.join)(environment.cwd, args[fileIndex + 1]) : await (0, default_build_file_1.getBuildFilename)(environment.cwd, environment);
|
|
115
159
|
if (fileIndex >= 0) {
|
|
116
160
|
args.splice(fileIndex, 2);
|
|
117
161
|
}
|
|
118
162
|
(0, remote_commands_1.registerRemoteCommands)(program, environment, fileName);
|
|
163
|
+
program.hook('preAction', (_, actionCommand) => {
|
|
164
|
+
const takesArguments = actionCommand.parent === program && ['run', 'up'].includes(actionCommand.name());
|
|
165
|
+
if (passthrough.length > 0 && !takesArguments) {
|
|
166
|
+
program.error(`${actionCommand.name()} takes no arguments after --, only run and up do`, { exitCode: 1 });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
119
169
|
if (await environment.file.exists(fileName)) {
|
|
120
170
|
program
|
|
121
171
|
.command('ls')
|
|
122
172
|
.description('list all tasks')
|
|
123
173
|
.addOption(new commander_1.Option('-f, --filter <labels...>', 'filter task and services with labels'))
|
|
124
174
|
.addOption(new commander_1.Option('-e, --exclude <labels...>', 'exclude task and services with labels'))
|
|
175
|
+
.addOption(new commander_1.Option('--json', 'emit the tasks and services as JSON').default(false))
|
|
125
176
|
.action(async (options) => {
|
|
126
177
|
const cli = await createCli(fileName, environment, parseWorkLabelScope(options));
|
|
127
178
|
const items = cli.ls();
|
|
128
179
|
const tasks = items.filter(cli_1.isCliTask);
|
|
129
180
|
const services = items.filter(cli_1.isCliService);
|
|
181
|
+
if (options.json) {
|
|
182
|
+
environment.stdout.write(`${JSON.stringify(getListJson(tasks, services), null, 2)}\n`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
130
185
|
if (services.length > 0) {
|
|
131
186
|
(0, log_1.printTitle)(environment, 'Services');
|
|
132
187
|
for (const service of services) {
|
|
@@ -527,7 +582,8 @@ async function getProgram(environment, argv) {
|
|
|
527
582
|
});
|
|
528
583
|
program
|
|
529
584
|
.command('up')
|
|
530
|
-
.description(
|
|
585
|
+
.description("start services(s), with the arguments after -- appended to the selected service's cmd")
|
|
586
|
+
.usage('[options] [-- args...]')
|
|
531
587
|
.addOption(new commander_1.Option('-f, --filter <labels...>', 'filter task and services with labels'))
|
|
532
588
|
.addOption(new commander_1.Option('-e, --exclude <labels...>', 'exclude task and services with labels'))
|
|
533
589
|
.addOption(new commander_1.Option('-c, --concurrency <number>', 'parallel worker count (default 4, unbounded when every task and service requests cpus and memory)').argParser(parseInt))
|
|
@@ -546,9 +602,13 @@ async function getProgram(environment, argv) {
|
|
|
546
602
|
.choices(['checksum', 'modify-date', 'none']))
|
|
547
603
|
.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))
|
|
548
604
|
.addOption(new commander_1.Option('--timeout <duration>', 'fail tasks without their own timeout after this long (e.g. 10m)').argParser(units_1.parseDuration))
|
|
549
|
-
.
|
|
605
|
+
.addOption(new commander_1.Option('--network <level>', 'the most any task or service reaches besides its needs: none, open or allow:<host>,<host>').argParser(parseNetworkArgument))
|
|
606
|
+
.action(async (options, command) => {
|
|
550
607
|
try {
|
|
551
|
-
const scope =
|
|
608
|
+
const scope = {
|
|
609
|
+
...parseWorkLabelScope(options),
|
|
610
|
+
args: { values: passthrough, services: true, cache: keepsCache(command) },
|
|
611
|
+
};
|
|
552
612
|
const cli = await createCli(fileName, environment, scope);
|
|
553
613
|
const result = await cli.runUp({
|
|
554
614
|
cacheDefault: options.cache,
|
|
@@ -560,6 +620,7 @@ async function getProgram(environment, argv) {
|
|
|
560
620
|
wait: options.wait,
|
|
561
621
|
cacheReadOnly: (0, read_only_1.isCacheReadOnly)(options.cacheReadOnly, environment.processEnvs),
|
|
562
622
|
timeout: options.timeout ?? null,
|
|
623
|
+
network: options.network ?? null,
|
|
563
624
|
});
|
|
564
625
|
if (!result.success) {
|
|
565
626
|
program.error('Up was not successful', { exitCode: 1 });
|
|
@@ -601,7 +662,8 @@ async function getProgram(environment, argv) {
|
|
|
601
662
|
});
|
|
602
663
|
program
|
|
603
664
|
.command('run [task]', { isDefault: true })
|
|
604
|
-
.description('execute task')
|
|
665
|
+
.description('execute task, with the arguments after -- appended to its last command')
|
|
666
|
+
.usage('[options] [task] [-- args...]')
|
|
605
667
|
.addOption(new commander_1.Option('-f, --filter <labels...>', 'filter task and services with labels'))
|
|
606
668
|
.addOption(new commander_1.Option('-e, --exclude <labels...>', 'exclude task and services with labels'))
|
|
607
669
|
.addOption(new commander_1.Option('-c, --concurrency <number>', 'parallel worker count (default 4, unbounded when every task and service requests cpus and memory)').argParser(parseInt))
|
|
@@ -620,15 +682,17 @@ async function getProgram(environment, argv) {
|
|
|
620
682
|
.addOption(new commander_1.Option('--dry-run', 'print the execution plan with predicted cache hits/misses without running').default(false))
|
|
621
683
|
.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))
|
|
622
684
|
.addOption(new commander_1.Option('--timeout <duration>', 'fail tasks without their own timeout after this long (e.g. 10m)').argParser(units_1.parseDuration))
|
|
685
|
+
.addOption(new commander_1.Option('--network <level>', 'the most any task or service reaches besides its needs: none, open or allow:<host>,<host>').argParser(parseNetworkArgument))
|
|
623
686
|
.addOption(new commander_1.Option('--no-skip-deps', 'run dependencies even when nothing needing them has to run'))
|
|
624
|
-
.action(async (task, options) => {
|
|
687
|
+
.action(async (task, options, command) => {
|
|
625
688
|
try {
|
|
626
689
|
// For machine-readable JSON, suppress the human progress logger (which
|
|
627
690
|
// also writes to stdout) so the only thing on stdout is the JSON.
|
|
628
691
|
const runEnvironment = options.summaryJson
|
|
629
692
|
? { ...environment, stdout: (0, empty_writable_1.emptyWritable)(), console: (0, log_1.consoleContext)((0, empty_writable_1.emptyWritable)()) }
|
|
630
693
|
: environment;
|
|
631
|
-
const
|
|
694
|
+
const args = { values: passthrough, services: false, cache: keepsCache(command) };
|
|
695
|
+
const cli = await createCli(fileName, runEnvironment, task ? { taskName: task, environmentName: options.env, args } : { ...parseWorkLabelScope(options), args });
|
|
632
696
|
if (cli.tasks().length === 0) {
|
|
633
697
|
program.error('No tasks found', { exitCode: 127 });
|
|
634
698
|
return;
|
|
@@ -652,6 +716,7 @@ async function getProgram(environment, argv) {
|
|
|
652
716
|
explain: options.explain,
|
|
653
717
|
cacheReadOnly: (0, read_only_1.isCacheReadOnly)(options.cacheReadOnly, environment.processEnvs),
|
|
654
718
|
timeout: options.timeout ?? null,
|
|
719
|
+
network: options.network ?? null,
|
|
655
720
|
skipDeps: options.skipDeps !== false,
|
|
656
721
|
});
|
|
657
722
|
// Reporting only: the summary never changes the exit code or behavior.
|
|
@@ -723,4 +788,12 @@ tasks:
|
|
|
723
788
|
return { program, args };
|
|
724
789
|
}
|
|
725
790
|
exports.getProgram = getProgram;
|
|
791
|
+
function parseNetworkArgument(value) {
|
|
792
|
+
try {
|
|
793
|
+
return (0, work_network_1.parseNetworkOption)(value);
|
|
794
|
+
}
|
|
795
|
+
catch (e) {
|
|
796
|
+
throw new commander_1.InvalidArgumentError((0, log_1.getErrorMessage)(e));
|
|
797
|
+
}
|
|
798
|
+
}
|
|
726
799
|
//# sourceMappingURL=program.js.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyAnnotations = exports.collectAnnotations = exports.remoteRepo = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const os_1 = require("os");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const util_1 = require("util");
|
|
8
|
+
const repo_1 = require("../server/repo");
|
|
9
|
+
const bundle_1 = require("./bundle");
|
|
10
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
11
|
+
async function git(cwd, ...args) {
|
|
12
|
+
try {
|
|
13
|
+
const value = (await execFileAsync('git', args, { cwd, timeout: 5000 })).stdout.trim();
|
|
14
|
+
return value || undefined;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// `host/path` of a remote, or nothing: a url that is neither https, ssh nor
|
|
21
|
+
// `user@host:path` is left out, so a credential in it never leaves the machine.
|
|
22
|
+
function remoteRepo(url) {
|
|
23
|
+
const scp = /^[\w.-]+@([\w.-]+):(?!\/)(.+)$/.exec(url);
|
|
24
|
+
if (scp) {
|
|
25
|
+
return (0, repo_1.normalizeRepo)(`${scp[1]}/${scp[2]}`) ?? undefined;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const parsed = new URL(url);
|
|
29
|
+
if (!['https:', 'ssh:'].includes(parsed.protocol)) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return (0, repo_1.normalizeRepo)(`${parsed.hostname}${parsed.pathname}`) ?? undefined;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.remoteRepo = remoteRepo;
|
|
39
|
+
// Where a build file is published from: its path in its repository (or on this
|
|
40
|
+
// machine, outside of one), the repository and commit, this machine and its user.
|
|
41
|
+
// `files` are the paths of the bundle, relative to the build file's directory: when
|
|
42
|
+
// one of them differs from the commit, `dirty=true` says the version is not that
|
|
43
|
+
// commit's. What can not be found is left out.
|
|
44
|
+
async function collectAnnotations(fileName, files = [(0, path_1.basename)(fileName)]) {
|
|
45
|
+
const path = (0, path_1.resolve)(fileName);
|
|
46
|
+
const cwd = (0, path_1.dirname)(path);
|
|
47
|
+
const annotations = {};
|
|
48
|
+
// git says where the directory is in the repository: comparing paths would trip on
|
|
49
|
+
// what the file system calls the same directory differently (symlinks, 8.3 names)
|
|
50
|
+
const inside = (await git(cwd, 'rev-parse', '--is-inside-work-tree')) === 'true';
|
|
51
|
+
const prefix = inside ? (await git(cwd, 'rev-parse', '--show-prefix')) ?? '' : undefined;
|
|
52
|
+
annotations.path = prefix !== undefined ? `${prefix}${(0, path_1.basename)(path)}` : path;
|
|
53
|
+
if (inside) {
|
|
54
|
+
const remote = await git(cwd, 'remote', 'get-url', 'origin');
|
|
55
|
+
const repo = remote ? remoteRepo(remote) : undefined;
|
|
56
|
+
const commit = await git(cwd, 'rev-parse', 'HEAD');
|
|
57
|
+
if (repo) {
|
|
58
|
+
annotations.repo = repo;
|
|
59
|
+
}
|
|
60
|
+
if (commit) {
|
|
61
|
+
annotations.commit = commit;
|
|
62
|
+
const changed = await git(cwd, '--literal-pathspecs', 'status', '--porcelain', '--untracked-files=all', '--', ...files);
|
|
63
|
+
if (changed) {
|
|
64
|
+
annotations.dirty = 'true';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
annotations.machine = (0, os_1.hostname)();
|
|
69
|
+
try {
|
|
70
|
+
annotations.user = (0, os_1.userInfo)().username;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// a user without an entry in the password database
|
|
74
|
+
}
|
|
75
|
+
return Object.fromEntries(Object.entries(annotations).filter(([, value]) => value));
|
|
76
|
+
}
|
|
77
|
+
exports.collectAnnotations = collectAnnotations;
|
|
78
|
+
// `--annotation key=value` sets one, `key=` leaves it out.
|
|
79
|
+
function applyAnnotations(annotations, options) {
|
|
80
|
+
const result = { ...annotations };
|
|
81
|
+
for (const option of options) {
|
|
82
|
+
const index = option.indexOf('=');
|
|
83
|
+
if (index <= 0) {
|
|
84
|
+
throw new Error(`${option} is not an annotation, write it as key=value`);
|
|
85
|
+
}
|
|
86
|
+
const key = option.slice(0, index);
|
|
87
|
+
const value = option.slice(index + 1);
|
|
88
|
+
if (value) {
|
|
89
|
+
result[key] = value;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
delete result[key];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const parsed = bundle_1.annotationsSchema.safeParse(result);
|
|
96
|
+
if (!parsed.success) {
|
|
97
|
+
const issue = parsed.error.issues[0];
|
|
98
|
+
throw new Error(`${issue.path.length > 0 ? `annotation ${issue.path.join('.')}` : 'annotations'}: ${issue.message}`);
|
|
99
|
+
}
|
|
100
|
+
return parsed.data;
|
|
101
|
+
}
|
|
102
|
+
exports.applyAnnotations = applyAnnotations;
|
|
103
|
+
//# sourceMappingURL=annotations.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.showBundle = exports.visible = void 0;
|
|
4
|
+
// Text from a proposal is shown to somebody who decides on it, so nothing in it may
|
|
5
|
+
// rewrite the terminal or hide what it says: control and format characters (which
|
|
6
|
+
// include the ones that reorder text) are shown as `?`. Long lines are not cut,
|
|
7
|
+
// what is hidden behind a cut is what a reviewer needs to see.
|
|
8
|
+
function visible(line) {
|
|
9
|
+
return line.replace(/\t/g, ' ').replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, '?');
|
|
10
|
+
}
|
|
11
|
+
exports.visible = visible;
|
|
12
|
+
const lines = (text) => (text === '' ? [] : text.replace(/\n$/, '').split('\n'));
|
|
13
|
+
// Every file of a version, whole: a version has no other one it could be read
|
|
14
|
+
// against, whoever decides on it reads all of it.
|
|
15
|
+
function showBundle(bundle) {
|
|
16
|
+
return Object.keys(bundle.files)
|
|
17
|
+
.sort()
|
|
18
|
+
.flatMap((path) => [
|
|
19
|
+
`--- ${visible(path)}${path === bundle.entry ? ' (entry)' : ''}`,
|
|
20
|
+
...lines(bundle.files[path]).map((line) => ` ${visible(line)}`),
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
exports.showBundle = showBundle;
|
|
24
|
+
//# sourceMappingURL=bundle-text.js.map
|
package/dist/remote/bundle.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HASH_PATTERN = exports.hashBundle = exports.bundleSchema = exports.isBundlePath = exports.MAX_BUNDLE_BYTES = exports.MAX_BUNDLE_FILES = exports.DEFAULT_ENTRY = void 0;
|
|
3
|
+
exports.HASH_PATTERN = exports.hashBundle = exports.bundleSchema = exports.annotationsSchema = exports.MAX_ANNOTATIONS = exports.isBundlePath = exports.MAX_BUNDLE_BYTES = exports.MAX_BUNDLE_FILES = exports.DEFAULT_ENTRY = void 0;
|
|
4
4
|
const crypto_1 = require("crypto");
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
exports.DEFAULT_ENTRY = '.hammerkit.yaml';
|
|
@@ -16,6 +16,14 @@ function isBundlePath(path) {
|
|
|
16
16
|
!path.split('/').some((part) => part === '' || part === '.' || part === '..'));
|
|
17
17
|
}
|
|
18
18
|
exports.isBundlePath = isBundlePath;
|
|
19
|
+
exports.MAX_ANNOTATIONS = 20;
|
|
20
|
+
exports.annotationsSchema = zod_1.z
|
|
21
|
+
.record(zod_1.z
|
|
22
|
+
.string()
|
|
23
|
+
.regex(/^[a-z][a-z0-9._-]{0,63}$/, 'use lower case letters, digits, ".", "_" and "-", starting with a letter'), zod_1.z.string().min(1).max(500))
|
|
24
|
+
.refine((annotations) => Object.keys(annotations).length <= exports.MAX_ANNOTATIONS, {
|
|
25
|
+
message: `at most ${exports.MAX_ANNOTATIONS} annotations`,
|
|
26
|
+
});
|
|
19
27
|
exports.bundleSchema = zod_1.z
|
|
20
28
|
.object({
|
|
21
29
|
entry: zod_1.z.string().default(exports.DEFAULT_ENTRY),
|
|
@@ -10,8 +10,10 @@ const log_1 = require("../log");
|
|
|
10
10
|
const host_config_1 = require("./host-config");
|
|
11
11
|
const device_login_1 = require("./device-login");
|
|
12
12
|
const credentials_1 = require("./credentials");
|
|
13
|
+
const token_source_1 = require("./token-source");
|
|
13
14
|
const server_client_1 = require("./server-client");
|
|
14
15
|
const remote_work_commands_1 = require("./remote-work-commands");
|
|
16
|
+
const mcp_command_1 = require("../harness/mcp-command");
|
|
15
17
|
// Host-level commands: they only touch the machine's config file, so they work
|
|
16
18
|
// with or without a build file in the working directory.
|
|
17
19
|
function registerRemoteCommands(program, environment, buildFile) {
|
|
@@ -129,7 +131,7 @@ function registerRemoteCommands(program, environment, buildFile) {
|
|
|
129
131
|
.description('show who you are on a server and which service accounts you may use')
|
|
130
132
|
.action(guarded('Remote accounts was not successful', async (name) => {
|
|
131
133
|
const chosen = await getServer(name);
|
|
132
|
-
const token = await (0,
|
|
134
|
+
const token = await (0, token_source_1.getTokenSource)(environment, chosen.name, chosen.server).token();
|
|
133
135
|
const me = await (0, server_client_1.getMe)(chosen.server, token);
|
|
134
136
|
const { body } = await (0, server_client_1.callServer)(chosen.server, token, 'GET', '/v1/accounts');
|
|
135
137
|
environment.stdout.write(`${chosen.name}: ${(0, server_client_1.printable)(me.sub)} (${(0, server_client_1.printable)(me.iss)})\n`);
|
|
@@ -153,6 +155,7 @@ function registerRemoteCommands(program, environment, buildFile) {
|
|
|
153
155
|
}
|
|
154
156
|
}));
|
|
155
157
|
(0, remote_work_commands_1.registerWorkCommands)(remote, { environment, buildFile, getServer, guarded });
|
|
158
|
+
(0, mcp_command_1.registerMcpCommand)(remote, environment);
|
|
156
159
|
}
|
|
157
160
|
exports.registerRemoteCommands = registerRemoteCommands;
|
|
158
161
|
//# sourceMappingURL=remote-commands.js.map
|