@mifune/agro 0.15.1 → 0.16.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 +14 -2
- package/dist/agro.js +607 -324
- package/package.json +1 -1
package/dist/agro.js
CHANGED
|
@@ -7,8 +7,8 @@ var __export = (target, all) => {
|
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { dirname as dirname10, join as
|
|
11
|
-
import { existsSync as
|
|
10
|
+
import { dirname as dirname10, join as join19, resolve as resolve11 } from "node:path";
|
|
11
|
+
import { existsSync as existsSync23, mkdtempSync as mkdtempSync5, readFileSync as readFileSync13, rmSync as rmSync6 } from "node:fs";
|
|
12
12
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
13
13
|
|
|
14
14
|
// src/commands/update.ts
|
|
@@ -2222,7 +2222,7 @@ function resolveExecutionTarget(opts) {
|
|
|
2222
2222
|
}
|
|
2223
2223
|
|
|
2224
2224
|
// src/lib/version.ts
|
|
2225
|
-
var AGRO_VERSION = true ? "0.
|
|
2225
|
+
var AGRO_VERSION = true ? "0.16.0" : "0.0.0-dev";
|
|
2226
2226
|
var OFFICIAL_IMAGE = "ghcr.io/mifunedev/agro";
|
|
2227
2227
|
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z][a-z0-9]*\.[0-9]+)?$/;
|
|
2228
2228
|
function parseReleaseVersion(value) {
|
|
@@ -3129,7 +3129,7 @@ var HARNESS_CATALOG = [
|
|
|
3129
3129
|
installArgv: [
|
|
3130
3130
|
"bash",
|
|
3131
3131
|
"-lc",
|
|
3132
|
-
`curl -fsSL https://hermes-agent.nousresearch.com/install.sh | HERMES_INSTALL_DIR="${HARNESS_PREFIX_TOKEN}/lib/hermes-agent" bash -s -- --skip-setup --skip-browser &&
|
|
3132
|
+
`curl -fsSL https://hermes-agent.nousresearch.com/install.sh | HERMES_INSTALL_DIR="${HARNESS_PREFIX_TOKEN}/lib/hermes-agent" bash -s -- --skip-setup --skip-browser && "${HARNESS_PREFIX_TOKEN}/bin/hermes" pm install --extra slack --extra teams`
|
|
3133
3133
|
],
|
|
3134
3134
|
installUser: "sandbox",
|
|
3135
3135
|
verifyArgv: ["hermes", "--version"],
|
|
@@ -3784,6 +3784,55 @@ function maybePrintStarPrompt(io, env = process.env, isTTY = process.stdout.isTT
|
|
|
3784
3784
|
}
|
|
3785
3785
|
}
|
|
3786
3786
|
|
|
3787
|
+
// src/commands/sandbox-list.ts
|
|
3788
|
+
async function entryStatus(root, name, run) {
|
|
3789
|
+
try {
|
|
3790
|
+
const target = resolveExecutionTarget({ projectRoot: root, container: name, run });
|
|
3791
|
+
return await target.status();
|
|
3792
|
+
} catch {
|
|
3793
|
+
return "unknown";
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
async function runSandboxList(opts, io) {
|
|
3797
|
+
const run = opts.run ?? spawnRunner;
|
|
3798
|
+
const rows = [];
|
|
3799
|
+
for (const name of listEntries()) {
|
|
3800
|
+
const root = entryRoot(name);
|
|
3801
|
+
const config = readAgroConfig(agroConfigPath(root));
|
|
3802
|
+
const checkout = configCheckout(config) ?? "-";
|
|
3803
|
+
rows.push({
|
|
3804
|
+
name,
|
|
3805
|
+
runtime: config.runtime ?? "docker",
|
|
3806
|
+
checkout,
|
|
3807
|
+
repo: checkout,
|
|
3808
|
+
status: await entryStatus(root, name, run)
|
|
3809
|
+
});
|
|
3810
|
+
}
|
|
3811
|
+
if (opts.json === true) {
|
|
3812
|
+
io.stdout(`${JSON.stringify(rows, null, 2)}
|
|
3813
|
+
`);
|
|
3814
|
+
return 0;
|
|
3815
|
+
}
|
|
3816
|
+
if (rows.length === 0) {
|
|
3817
|
+
io.stdout(
|
|
3818
|
+
`no sandbox is registered in ${registryRoot()} \u2014 create one with \`${opts.bin} sandbox install docker\`
|
|
3819
|
+
`
|
|
3820
|
+
);
|
|
3821
|
+
return 0;
|
|
3822
|
+
}
|
|
3823
|
+
const width = (pick) => Math.max(...rows.map((row) => pick(row).length));
|
|
3824
|
+
const nameWidth = width((row) => row.name);
|
|
3825
|
+
const runtimeWidth = width((row) => row.runtime);
|
|
3826
|
+
const statusWidth = width((row) => row.status);
|
|
3827
|
+
for (const row of rows) {
|
|
3828
|
+
io.stdout(
|
|
3829
|
+
`${row.name.padEnd(nameWidth)} ${row.runtime.padEnd(runtimeWidth)} ${row.status.padEnd(statusWidth)} ${row.checkout}
|
|
3830
|
+
`
|
|
3831
|
+
);
|
|
3832
|
+
}
|
|
3833
|
+
return 0;
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3787
3836
|
// src/commands/sandbox.ts
|
|
3788
3837
|
function hostTimezone() {
|
|
3789
3838
|
const tz = process.env.TZ;
|
|
@@ -4001,58 +4050,384 @@ async function runSandboxInstall(opts, io) {
|
|
|
4001
4050
|
}
|
|
4002
4051
|
return code;
|
|
4003
4052
|
}
|
|
4004
|
-
|
|
4053
|
+
|
|
4054
|
+
// src/services/sandbox-upgrade.ts
|
|
4055
|
+
import { closeSync as closeSync2, existsSync as existsSync17, openSync as openSync2, readFileSync as readFileSync11, unlinkSync as unlinkSync5, writeSync } from "node:fs";
|
|
4056
|
+
import { join as join13 } from "node:path";
|
|
4057
|
+
function errorMessage(error) {
|
|
4058
|
+
return error instanceof Error ? error.message : String(error);
|
|
4059
|
+
}
|
|
4060
|
+
function conflictingDotenvImage(root, imageRef) {
|
|
4061
|
+
const rootEnv = join13(root, ".env");
|
|
4062
|
+
const fallbackEnv = join13(root, ".devcontainer", ".env");
|
|
4063
|
+
const file = existsSync17(rootEnv) ? rootEnv : fallbackEnv;
|
|
4064
|
+
if (!existsSync17(file)) return void 0;
|
|
4065
|
+
for (const line of readFileSync11(file, "utf8").split(/\r?\n/)) {
|
|
4066
|
+
const match = /^AGRO_SANDBOX_IMAGE=(.*)$/.exec(line);
|
|
4067
|
+
if (!match) continue;
|
|
4068
|
+
const value = match[1].trim().replace(/^(?:"(.*)"|'(.*)')$/, (_whole, double, single) => double ?? single ?? "");
|
|
4069
|
+
if (value !== imageRef) return file;
|
|
4070
|
+
}
|
|
4071
|
+
return void 0;
|
|
4072
|
+
}
|
|
4073
|
+
async function runSandboxUpgrade(opts, io) {
|
|
4074
|
+
const prefix = `${opts.bin} sandbox upgrade:`;
|
|
4075
|
+
const version = parseReleaseVersion(opts.version);
|
|
4076
|
+
if (version === void 0) {
|
|
4077
|
+
io.stderr(`${prefix} invalid release version "${opts.version}"
|
|
4078
|
+
`);
|
|
4079
|
+
return 1;
|
|
4080
|
+
}
|
|
4081
|
+
if (runningInsideSandbox()) {
|
|
4082
|
+
io.stderr(`${prefix} host-only \u2014 run this command on the host
|
|
4083
|
+
`);
|
|
4084
|
+
return 1;
|
|
4085
|
+
}
|
|
4086
|
+
let root;
|
|
4005
4087
|
try {
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4088
|
+
root = entryRoot(opts.name);
|
|
4089
|
+
} catch (error) {
|
|
4090
|
+
io.stderr(`${prefix} ${errorMessage(error)}
|
|
4091
|
+
`);
|
|
4092
|
+
return 1;
|
|
4093
|
+
}
|
|
4094
|
+
const configFile = agroConfigPath(root);
|
|
4095
|
+
if (!existsSync17(configFile)) {
|
|
4096
|
+
io.stderr(`${prefix} no sandbox entry named "${opts.name}"
|
|
4097
|
+
`);
|
|
4098
|
+
return 1;
|
|
4099
|
+
}
|
|
4100
|
+
const lock = join13(root, ".sandbox-upgrade.lock");
|
|
4101
|
+
let fd;
|
|
4102
|
+
try {
|
|
4103
|
+
fd = openSync2(lock, "wx", 384);
|
|
4104
|
+
} catch (error) {
|
|
4105
|
+
const message = error instanceof Error && "code" in error && error.code === "EEXIST" ? `upgrade already in progress for ${opts.name}; lock: ${lock}. Confirm no upgrade process owns this entry before removing that lock and retrying` : errorMessage(error);
|
|
4106
|
+
io.stderr(`${prefix} ${message}
|
|
4107
|
+
`);
|
|
4108
|
+
return 1;
|
|
4109
|
+
}
|
|
4110
|
+
try {
|
|
4111
|
+
writeSync(fd, `${process.pid}
|
|
4112
|
+
`);
|
|
4113
|
+
const config = readAgroConfig(configFile);
|
|
4114
|
+
if (config.image?.mode !== "image") {
|
|
4115
|
+
io.stderr(`${prefix} ${opts.name} requires image.mode "image"
|
|
4116
|
+
`);
|
|
4117
|
+
return 1;
|
|
4118
|
+
}
|
|
4119
|
+
const imageRef = officialImageRef(version);
|
|
4120
|
+
if (process.env.AGRO_SANDBOX_IMAGE !== void 0 && process.env.AGRO_SANDBOX_IMAGE !== "" && process.env.AGRO_SANDBOX_IMAGE !== imageRef) {
|
|
4121
|
+
io.stderr(`${prefix} conflicting AGRO_SANDBOX_IMAGE in the host environment
|
|
4122
|
+
`);
|
|
4123
|
+
return 1;
|
|
4124
|
+
}
|
|
4125
|
+
const dotenv = conflictingDotenvImage(root, imageRef);
|
|
4126
|
+
if (dotenv !== void 0) {
|
|
4127
|
+
io.stderr(`${prefix} conflicting AGRO_SANDBOX_IMAGE in ${dotenv}
|
|
4128
|
+
`);
|
|
4129
|
+
return 1;
|
|
4130
|
+
}
|
|
4131
|
+
const run = opts.run ?? spawnRunner;
|
|
4132
|
+
let priorRef = config.image.ref;
|
|
4133
|
+
if (!priorRef) {
|
|
4134
|
+
const name = configuredContainerName(root) ?? DEFAULT_CONTAINER_NAME;
|
|
4135
|
+
const inspected = run("docker", ["inspect", "-f", "{{.Config.Image}}", name], { stdio: "capture" });
|
|
4136
|
+
priorRef = inspected.status === 0 && !inspected.error ? inspected.stdout?.trim() : void 0;
|
|
4137
|
+
if (!priorRef) {
|
|
4138
|
+
io.stderr(`${prefix} cannot determine previous image for ${name}; no changes made
|
|
4139
|
+
`);
|
|
4140
|
+
return 1;
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
const apply = (ref) => runSandbox({ bin: opts.bin, cwd: root, imageRef: ref, run }, io);
|
|
4144
|
+
let failure;
|
|
4145
|
+
try {
|
|
4146
|
+
const code = await apply(imageRef);
|
|
4147
|
+
if (code !== 0) failure = `exit ${code}`;
|
|
4148
|
+
} catch (error) {
|
|
4149
|
+
failure = errorMessage(error);
|
|
4150
|
+
}
|
|
4151
|
+
if (failure === void 0) {
|
|
4152
|
+
try {
|
|
4153
|
+
(opts.writeConfig ?? writeAgroConfig)(root, { ...config, image: { ...config.image, ref: imageRef } });
|
|
4154
|
+
return 0;
|
|
4155
|
+
} catch (error) {
|
|
4156
|
+
failure = `config persistence failed (${errorMessage(error)})`;
|
|
4157
|
+
}
|
|
4158
|
+
} else {
|
|
4159
|
+
failure = `provisioning failed (${failure})`;
|
|
4160
|
+
}
|
|
4161
|
+
io.stderr(`${prefix} ${failure}; restoring previous image
|
|
4162
|
+
`);
|
|
4163
|
+
try {
|
|
4164
|
+
const code = await apply(priorRef);
|
|
4165
|
+
if (code !== 0) io.stderr(`${prefix} restoration failed (exit ${code})
|
|
4166
|
+
`);
|
|
4167
|
+
} catch (error) {
|
|
4168
|
+
io.stderr(`${prefix} restoration failed (${errorMessage(error)})
|
|
4169
|
+
`);
|
|
4170
|
+
}
|
|
4171
|
+
return 1;
|
|
4172
|
+
} catch (error) {
|
|
4173
|
+
io.stderr(`${prefix} ${errorMessage(error)}
|
|
4174
|
+
`);
|
|
4175
|
+
return 1;
|
|
4176
|
+
} finally {
|
|
4177
|
+
closeSync2(fd);
|
|
4178
|
+
unlinkSync5(lock);
|
|
4010
4179
|
}
|
|
4011
4180
|
}
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4181
|
+
|
|
4182
|
+
// src/controllers/sandbox.ts
|
|
4183
|
+
function isHelpFlag(arg) {
|
|
4184
|
+
return arg === "--help" || arg === "-h" || arg === "help";
|
|
4185
|
+
}
|
|
4186
|
+
function runtimeLines() {
|
|
4187
|
+
const width = Math.max(...RUNTIME_CATALOG.map((r) => r.id.length));
|
|
4188
|
+
return RUNTIME_CATALOG.map(
|
|
4189
|
+
(r) => ` ${r.id.padEnd(width)} ${r.provisionable ? "provisionable" : "planned"}`
|
|
4190
|
+
).join("\n");
|
|
4191
|
+
}
|
|
4192
|
+
function printSandboxHelp(bin = AGRO_PRODUCT.bin) {
|
|
4193
|
+
process.stdout.write(`${bin} sandbox \u2014 Create and list sandboxes
|
|
4194
|
+
|
|
4195
|
+
Usage:
|
|
4196
|
+
${bin} sandbox install <runtime> [--name <name>] [--checkout <dir>]
|
|
4197
|
+
[--home-mount <dir>] [--yes]
|
|
4198
|
+
[--version <X.Y.Z> | --image[=<ref>]]
|
|
4199
|
+
[--no-build] [--print-argv]
|
|
4200
|
+
${bin} sandbox list [--json]
|
|
4201
|
+
${bin} sandbox upgrade <name> --version <X.Y.Z>
|
|
4202
|
+
|
|
4203
|
+
\`install\` writes a sandbox entry under \${${stateNames(bin).envPrefix}HOME:-~/${stateNames(bin).userStateDir}}/sandboxes/<name>/,
|
|
4204
|
+
materialises the compose files and the compose wrapper into it, then starts the
|
|
4205
|
+
container. It needs no project checkout and runs from any directory. Without
|
|
4206
|
+
--checkout the sandbox runs the prebuilt image; with --checkout <dir> that
|
|
4207
|
+
checkout is bound at /home/sandbox/harness; the sandbox builds locally only
|
|
4208
|
+
when that directory holds .devcontainer/Dockerfile.
|
|
4209
|
+
|
|
4210
|
+
On a terminal without --yes it asks for the sandbox name, the timezone, the git
|
|
4211
|
+
identity, SSH (with its host port), the host Docker socket, and the host path
|
|
4212
|
+
for /home/sandbox. With --checkout it seeds those answers from that
|
|
4213
|
+
checkout's ${stateNames(bin).configFile}.
|
|
4214
|
+
|
|
4215
|
+
Flags:
|
|
4216
|
+
--name <name> Registry entry name (default: the lowest free ${DEFAULT_NAME_PREFIX}<n>)
|
|
4217
|
+
--checkout <dir> Bind this checkout into the sandbox and seed the defaults
|
|
4218
|
+
from its ${stateNames(bin).configFile}. The sandbox builds
|
|
4219
|
+
locally only when that directory holds
|
|
4220
|
+
.devcontainer/Dockerfile
|
|
4221
|
+
--repo <dir> Deprecated alias for --checkout
|
|
4222
|
+
--home-mount <dir>
|
|
4223
|
+
Bind this host directory at /home/sandbox instead of the
|
|
4224
|
+
Docker-managed volume. Relative paths resolve against the
|
|
4225
|
+
working directory. The directory is created when absent
|
|
4226
|
+
--yes Non-interactive: keep every default and ask nothing
|
|
4227
|
+
--version <X.Y.Z>
|
|
4228
|
+
Run the official release image ghcr.io/mifunedev/agro:<X.Y.Z>
|
|
4229
|
+
(implies --image). A leading v and a -<channel>.<n>
|
|
4230
|
+
pre-release suffix are accepted
|
|
4231
|
+
--image Run the prebuilt image instead of building (implies
|
|
4232
|
+
--no-build). The ref resolves first-match: --version or
|
|
4233
|
+
--image=<ref> > ${stateNames(bin).envPrefix}SANDBOX_IMAGE >
|
|
4234
|
+
${stateNames(bin).configFile} image.ref > ${officialImageRef(AGRO_VERSION)}
|
|
4235
|
+
--image=<ref> Run a custom image (implies --image); conflicts with --version
|
|
4236
|
+
--no-build Suppress the local build and reuse an existing image
|
|
4237
|
+
--print-argv Print the docker compose argv that would run, then exit
|
|
4238
|
+
without writing an entry
|
|
4239
|
+
--json Machine-readable output (list)
|
|
4240
|
+
|
|
4241
|
+
Runtimes:
|
|
4242
|
+
${runtimeLines()}
|
|
4243
|
+
|
|
4244
|
+
Next: ${bin} shell <name>
|
|
4245
|
+
`);
|
|
4246
|
+
}
|
|
4247
|
+
var SANDBOX_VALUE_FLAGS = {
|
|
4248
|
+
"--name": "name",
|
|
4249
|
+
"--checkout": "checkout",
|
|
4250
|
+
"--repo": "checkout",
|
|
4251
|
+
"--home-mount": "homeMount"
|
|
4252
|
+
};
|
|
4253
|
+
function parseSandboxArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
4254
|
+
const args = {
|
|
4255
|
+
help: false,
|
|
4256
|
+
yes: false,
|
|
4257
|
+
image: false,
|
|
4258
|
+
noBuild: false,
|
|
4259
|
+
printArgv: false,
|
|
4260
|
+
json: false
|
|
4261
|
+
};
|
|
4262
|
+
if (rest.length === 0 || isHelpFlag(rest[0])) {
|
|
4263
|
+
return { ok: true, args: { ...args, help: true } };
|
|
4026
4264
|
}
|
|
4027
|
-
|
|
4028
|
-
|
|
4265
|
+
const [head, ...tail] = rest;
|
|
4266
|
+
if (head === "upgrade") {
|
|
4267
|
+
if (tail.length === 1 && isHelpFlag(tail[0])) {
|
|
4268
|
+
return { ok: true, args: { ...args, subcommand: "upgrade", help: true } };
|
|
4269
|
+
}
|
|
4270
|
+
const [name, flag, value, ...extra] = tail;
|
|
4271
|
+
if (name === void 0 || name === "" || name.startsWith("-")) {
|
|
4272
|
+
return { ok: false, error: `${bin} sandbox upgrade: a name is required` };
|
|
4273
|
+
}
|
|
4274
|
+
if (flag === void 0) {
|
|
4275
|
+
return { ok: false, error: `${bin} sandbox upgrade: --version is required` };
|
|
4276
|
+
}
|
|
4277
|
+
if (flag !== "--version") {
|
|
4278
|
+
return { ok: false, error: `${bin} sandbox upgrade: ${flag.startsWith("-") ? "unknown flag" : "unexpected argument"} "${flag}"` };
|
|
4279
|
+
}
|
|
4280
|
+
if (value === void 0 || value === "") {
|
|
4281
|
+
return { ok: false, error: `${bin} sandbox upgrade: --version requires a value` };
|
|
4282
|
+
}
|
|
4283
|
+
if (value.startsWith("-")) {
|
|
4284
|
+
return { ok: false, error: `${bin} sandbox upgrade: unknown flag "${value}"` };
|
|
4285
|
+
}
|
|
4286
|
+
const version2 = parseReleaseVersion(value);
|
|
4287
|
+
if (version2 === void 0) {
|
|
4288
|
+
return { ok: false, error: `${bin} sandbox upgrade: --version "${value}" is not a release version \u2014 expected X.Y.Z` };
|
|
4289
|
+
}
|
|
4290
|
+
if (extra.length > 0) {
|
|
4291
|
+
const token = extra[0];
|
|
4292
|
+
return { ok: false, error: `${bin} sandbox upgrade: ${token.startsWith("-") && token !== "--version" ? "unknown flag" : "unexpected argument"} "${token}"` };
|
|
4293
|
+
}
|
|
4294
|
+
return { ok: true, args: { ...args, subcommand: "upgrade", name, version: version2 } };
|
|
4295
|
+
}
|
|
4296
|
+
if (head !== "install" && head !== "list") {
|
|
4297
|
+
return {
|
|
4298
|
+
ok: false,
|
|
4299
|
+
error: `${bin} sandbox: unknown subcommand "${head}" \u2014 expected install or list`,
|
|
4300
|
+
showHelp: true
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
args.subcommand = head;
|
|
4304
|
+
if (isHelpFlag(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
4305
|
+
const positionals = [];
|
|
4306
|
+
const checkoutSpellings = /* @__PURE__ */ new Set();
|
|
4307
|
+
let version;
|
|
4308
|
+
for (let i = 0; i < tail.length; i++) {
|
|
4309
|
+
const token = tail[i];
|
|
4310
|
+
const valueFlag = SANDBOX_VALUE_FLAGS[token];
|
|
4311
|
+
if (valueFlag !== void 0) {
|
|
4312
|
+
const value = tail[i + 1];
|
|
4313
|
+
if (value === void 0) {
|
|
4314
|
+
return { ok: false, error: `${bin} sandbox ${head}: ${token} requires a value` };
|
|
4315
|
+
}
|
|
4316
|
+
if (valueFlag === "checkout") checkoutSpellings.add(token);
|
|
4317
|
+
args[valueFlag] = value;
|
|
4318
|
+
i++;
|
|
4319
|
+
} else if (token === "--yes") {
|
|
4320
|
+
args.yes = true;
|
|
4321
|
+
} else if (token === "--no-build") {
|
|
4322
|
+
args.noBuild = true;
|
|
4323
|
+
} else if (token === "--print-argv") {
|
|
4324
|
+
args.printArgv = true;
|
|
4325
|
+
} else if (token === "--image") {
|
|
4326
|
+
args.image = true;
|
|
4327
|
+
} else if (token.startsWith("--image=")) {
|
|
4328
|
+
const ref = token.slice("--image=".length);
|
|
4329
|
+
if (ref === "") {
|
|
4330
|
+
return { ok: false, error: `${bin} sandbox: --image=<ref> requires a non-empty image ref` };
|
|
4331
|
+
}
|
|
4332
|
+
args.image = true;
|
|
4333
|
+
args.imageRef = ref;
|
|
4334
|
+
} else if (token === "--version" || token.startsWith("--version=")) {
|
|
4335
|
+
const value = token === "--version" ? tail[++i] : token.slice("--version=".length);
|
|
4336
|
+
if (value === void 0 || value === "") {
|
|
4337
|
+
return { ok: false, error: `${bin} sandbox ${head}: --version requires a value` };
|
|
4338
|
+
}
|
|
4339
|
+
version = parseReleaseVersion(value);
|
|
4340
|
+
if (version === void 0) {
|
|
4341
|
+
return {
|
|
4342
|
+
ok: false,
|
|
4343
|
+
error: `${bin} sandbox ${head}: --version "${value}" is not a release version \u2014 expected X.Y.Z`
|
|
4344
|
+
};
|
|
4345
|
+
}
|
|
4346
|
+
} else if (token === "--json") {
|
|
4347
|
+
args.json = true;
|
|
4348
|
+
} else if (token.startsWith("-")) {
|
|
4349
|
+
return { ok: false, error: `${bin} sandbox ${head}: unknown flag "${token}"` };
|
|
4350
|
+
} else {
|
|
4351
|
+
positionals.push(token);
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
if (checkoutSpellings.has("--checkout") && checkoutSpellings.has("--repo")) {
|
|
4355
|
+
return {
|
|
4356
|
+
ok: false,
|
|
4357
|
+
error: `${bin} sandbox ${head}: --checkout conflicts with --repo \u2014 pass exactly one, and prefer --checkout`
|
|
4358
|
+
};
|
|
4359
|
+
}
|
|
4360
|
+
if (version !== void 0) {
|
|
4361
|
+
if (args.imageRef !== void 0) {
|
|
4362
|
+
return {
|
|
4363
|
+
ok: false,
|
|
4364
|
+
error: `${bin} sandbox ${head}: --version conflicts with --image=<ref> \u2014 pass --version for an official release or --image=<ref> for a custom image`
|
|
4365
|
+
};
|
|
4366
|
+
}
|
|
4367
|
+
args.image = true;
|
|
4368
|
+
args.imageRef = officialImageRef(version);
|
|
4369
|
+
}
|
|
4370
|
+
if (head === "list") {
|
|
4371
|
+
if (positionals.length > 0) {
|
|
4372
|
+
return { ok: false, error: `${bin} sandbox list: unexpected argument "${positionals[0]}"` };
|
|
4373
|
+
}
|
|
4374
|
+
return { ok: true, args };
|
|
4375
|
+
}
|
|
4376
|
+
if (positionals.length === 0) {
|
|
4377
|
+
return {
|
|
4378
|
+
ok: false,
|
|
4379
|
+
error: `${bin} sandbox install: a runtime is required, e.g. \`${bin} sandbox install docker\``,
|
|
4380
|
+
showHelp: true
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
if (positionals.length > 1) {
|
|
4384
|
+
return { ok: false, error: `${bin} sandbox install: unexpected argument "${positionals[1]}"` };
|
|
4385
|
+
}
|
|
4386
|
+
args.runtime = positionals[0];
|
|
4387
|
+
return { ok: true, args };
|
|
4388
|
+
}
|
|
4389
|
+
async function runSandboxCommand(rest, bin) {
|
|
4390
|
+
const parsed = parseSandboxArgs(rest, bin);
|
|
4391
|
+
if (!parsed.ok) {
|
|
4392
|
+
process.stderr.write(`${parsed.error}
|
|
4029
4393
|
`);
|
|
4030
|
-
|
|
4394
|
+
if (parsed.showHelp) printSandboxHelp(bin);
|
|
4395
|
+
return 1;
|
|
4031
4396
|
}
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
);
|
|
4037
|
-
return 0;
|
|
4397
|
+
const a = parsed.args;
|
|
4398
|
+
if (a.help) {
|
|
4399
|
+
printSandboxHelp(bin);
|
|
4400
|
+
return a.subcommand === void 0 ? 1 : 0;
|
|
4038
4401
|
}
|
|
4039
|
-
const
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
`
|
|
4047
|
-
);
|
|
4402
|
+
const io = {
|
|
4403
|
+
stdout: (s) => process.stdout.write(s),
|
|
4404
|
+
stderr: (s) => process.stderr.write(s)
|
|
4405
|
+
};
|
|
4406
|
+
if (a.subcommand === "list") return await runSandboxList({ bin, json: a.json }, io);
|
|
4407
|
+
if (a.subcommand === "upgrade") {
|
|
4408
|
+
return await runSandboxUpgrade({ bin, name: a.name, version: a.version }, io);
|
|
4048
4409
|
}
|
|
4049
|
-
return
|
|
4410
|
+
return await runSandboxInstall(
|
|
4411
|
+
{
|
|
4412
|
+
bin,
|
|
4413
|
+
runtime: a.runtime,
|
|
4414
|
+
...a.name !== void 0 ? { name: a.name } : {},
|
|
4415
|
+
...a.checkout !== void 0 ? { checkout: a.checkout } : {},
|
|
4416
|
+
...a.homeMount !== void 0 ? { homeMount: a.homeMount } : {},
|
|
4417
|
+
yes: a.yes,
|
|
4418
|
+
image: a.image,
|
|
4419
|
+
...a.imageRef !== void 0 ? { imageRef: a.imageRef } : {},
|
|
4420
|
+
noBuild: a.noBuild,
|
|
4421
|
+
printArgv: a.printArgv
|
|
4422
|
+
},
|
|
4423
|
+
io
|
|
4424
|
+
);
|
|
4050
4425
|
}
|
|
4051
4426
|
|
|
4052
4427
|
// src/commands/harness.ts
|
|
4053
|
-
import { existsSync as
|
|
4428
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
4054
4429
|
import { homedir as homedir3 } from "node:os";
|
|
4055
|
-
import { delimiter as delimiter2, join as
|
|
4430
|
+
import { delimiter as delimiter2, join as join16 } from "node:path";
|
|
4056
4431
|
|
|
4057
4432
|
// src/lib/docs.ts
|
|
4058
4433
|
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
@@ -4065,17 +4440,20 @@ function sourceDocsUrl(docsPath) {
|
|
|
4065
4440
|
}
|
|
4066
4441
|
|
|
4067
4442
|
// src/lib/host-config.ts
|
|
4068
|
-
import { existsSync as
|
|
4069
|
-
import { basename as basename5, dirname as dirname8, isAbsolute, join as
|
|
4443
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync12, renameSync as renameSync4, unlinkSync as unlinkSync6, writeFileSync as writeFileSync9 } from "node:fs";
|
|
4444
|
+
import { basename as basename5, dirname as dirname8, isAbsolute, join as join14, resolve as resolve8 } from "node:path";
|
|
4070
4445
|
var HOST_CONFIG_MODE = 420;
|
|
4071
4446
|
var HOST_CONFIG_FILE = "config.json";
|
|
4072
4447
|
var WORKSPACES_SUBDIR = "workspaces";
|
|
4073
4448
|
var DEFAULT_WORKSPACE_NAME = "harness";
|
|
4449
|
+
function isRootToolReceipt(receipt) {
|
|
4450
|
+
return receipt.scope === "root";
|
|
4451
|
+
}
|
|
4074
4452
|
function hostStateHome(env, home) {
|
|
4075
4453
|
return resolveUserStateHome(env, home);
|
|
4076
4454
|
}
|
|
4077
4455
|
function hostConfigPath(env, home) {
|
|
4078
|
-
return
|
|
4456
|
+
return join14(hostStateHome(env, home), HOST_CONFIG_FILE);
|
|
4079
4457
|
}
|
|
4080
4458
|
function assertWorkspaceName(name) {
|
|
4081
4459
|
if (!SANDBOX_NAME_PATTERN.test(name)) {
|
|
@@ -4085,14 +4463,14 @@ function assertWorkspaceName(name) {
|
|
|
4085
4463
|
}
|
|
4086
4464
|
}
|
|
4087
4465
|
function workspacesRoot(env, home) {
|
|
4088
|
-
return
|
|
4466
|
+
return join14(hostStateHome(env, home), WORKSPACES_SUBDIR);
|
|
4089
4467
|
}
|
|
4090
4468
|
function workspaceRoot(name, env, home) {
|
|
4091
4469
|
assertWorkspaceName(name);
|
|
4092
|
-
return
|
|
4470
|
+
return join14(workspacesRoot(env, home), name);
|
|
4093
4471
|
}
|
|
4094
4472
|
function defaultHarnessRoot(env, home) {
|
|
4095
|
-
return
|
|
4473
|
+
return join14(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
|
|
4096
4474
|
}
|
|
4097
4475
|
function validateHostConfig(value) {
|
|
4098
4476
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -4114,7 +4492,7 @@ function validateHostConfig(value) {
|
|
|
4114
4492
|
const hostHarnesses = record.hostHarnesses;
|
|
4115
4493
|
if (hostHarnesses !== void 0) validateReceipts(hostHarnesses, "hostHarnesses", "harness");
|
|
4116
4494
|
const hostTools = record.hostTools;
|
|
4117
|
-
if (hostTools !== void 0) validateReceipts(hostTools, "hostTools", "tool");
|
|
4495
|
+
if (hostTools !== void 0) validateReceipts(hostTools, "hostTools", "tool", true);
|
|
4118
4496
|
return { ...record, version: 1 };
|
|
4119
4497
|
}
|
|
4120
4498
|
function absoluteField(value, path4) {
|
|
@@ -4124,7 +4502,7 @@ function absoluteField(value, path4) {
|
|
|
4124
4502
|
if (!isAbsolute(value)) throw fieldError2(path4, "must be an absolute path");
|
|
4125
4503
|
return value;
|
|
4126
4504
|
}
|
|
4127
|
-
function validateReceipts(value, field, noun) {
|
|
4505
|
+
function validateReceipts(value, field, noun, allowRootScope = false) {
|
|
4128
4506
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4129
4507
|
throw fieldError2(field, `must be a JSON object keyed by ${noun} id`);
|
|
4130
4508
|
}
|
|
@@ -4134,9 +4512,16 @@ function validateReceipts(value, field, noun) {
|
|
|
4134
4512
|
if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) {
|
|
4135
4513
|
throw fieldError2(at, "must be a JSON object");
|
|
4136
4514
|
}
|
|
4137
|
-
const entry = receipt;
|
|
4138
|
-
|
|
4139
|
-
|
|
4515
|
+
const entry = receipt;
|
|
4516
|
+
if (allowRootScope && entry.scope !== void 0) {
|
|
4517
|
+
if (entry.scope !== "root") throw fieldError2(`${at}.scope`, 'must be "root" when present');
|
|
4518
|
+
for (const key of ["prefix", "binPath"]) {
|
|
4519
|
+
if (entry[key] !== void 0) throw fieldError2(`${at}.${key}`, 'must be absent when scope is "root"');
|
|
4520
|
+
}
|
|
4521
|
+
} else {
|
|
4522
|
+
absoluteField(entry.prefix, `${at}.prefix`);
|
|
4523
|
+
absoluteField(entry.binPath, `${at}.binPath`);
|
|
4524
|
+
}
|
|
4140
4525
|
if (typeof entry.binary !== "string" || entry.binary === "") {
|
|
4141
4526
|
throw fieldError2(`${at}.binary`, "must be a non-empty string");
|
|
4142
4527
|
}
|
|
@@ -4150,10 +4535,10 @@ function validateReceipts(value, field, noun) {
|
|
|
4150
4535
|
}
|
|
4151
4536
|
function readHostConfig(env, home) {
|
|
4152
4537
|
const path4 = hostConfigPath(env, home);
|
|
4153
|
-
if (!
|
|
4538
|
+
if (!existsSync18(path4)) return { version: 1 };
|
|
4154
4539
|
let raw;
|
|
4155
4540
|
try {
|
|
4156
|
-
raw =
|
|
4541
|
+
raw = readFileSync12(path4, "utf8");
|
|
4157
4542
|
} catch (error) {
|
|
4158
4543
|
const detail = error instanceof Error ? error.message : String(error);
|
|
4159
4544
|
throw new Error(`could not read ${path4}: ${detail}`);
|
|
@@ -4178,7 +4563,7 @@ function writeHostConfig(config, env, home) {
|
|
|
4178
4563
|
renameSync4(tmp, path4);
|
|
4179
4564
|
} catch (error) {
|
|
4180
4565
|
try {
|
|
4181
|
-
|
|
4566
|
+
unlinkSync6(tmp);
|
|
4182
4567
|
} catch {
|
|
4183
4568
|
}
|
|
4184
4569
|
throw error;
|
|
@@ -4201,8 +4586,8 @@ function fieldError2(path4, requirement) {
|
|
|
4201
4586
|
}
|
|
4202
4587
|
|
|
4203
4588
|
// src/lib/host-workspace.ts
|
|
4204
|
-
import { existsSync as
|
|
4205
|
-
import { dirname as dirname9, join as
|
|
4589
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync4, readdirSync as readdirSync3, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync5 } from "node:fs";
|
|
4590
|
+
import { dirname as dirname9, join as join15, resolve as resolve9 } from "node:path";
|
|
4206
4591
|
var AGRO_REPO_URL = "https://github.com/mifunedev/agro.git";
|
|
4207
4592
|
var IGNORABLE_ENTRIES = [
|
|
4208
4593
|
REGISTRY_SUBDIR,
|
|
@@ -4214,7 +4599,7 @@ function isDirectory3(path4) {
|
|
|
4214
4599
|
return statSync5(path4, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
4215
4600
|
}
|
|
4216
4601
|
function stateHomes(home) {
|
|
4217
|
-
return STATE_HOME_DIRS.map((name) =>
|
|
4602
|
+
return STATE_HOME_DIRS.map((name) => join15(home, name));
|
|
4218
4603
|
}
|
|
4219
4604
|
function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
4220
4605
|
const target = resolve9(root);
|
|
@@ -4222,7 +4607,7 @@ function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
|
4222
4607
|
if (enclosing === void 0) return void 0;
|
|
4223
4608
|
return `${bin} ${verb}: the ${verb} root ${target} is the state home ${enclosing} itself.
|
|
4224
4609
|
A workspace there collides with AGRO state and blocks every command run from ${home}.
|
|
4225
|
-
Move it out \u2014 for example \`mv ${enclosing} ${
|
|
4610
|
+
Move it out \u2014 for example \`mv ${enclosing} ${join15(home, "agro")}\` \u2014 then re-run this command.
|
|
4226
4611
|
`;
|
|
4227
4612
|
}
|
|
4228
4613
|
function isIgnorableEntry(name) {
|
|
@@ -4239,16 +4624,16 @@ function cloneInto(target, run, root, ref) {
|
|
|
4239
4624
|
assertCloned(run("git", ["clone", ...branch, AGRO_REPO_URL, target], { stdio: "inherit" }), root);
|
|
4240
4625
|
}
|
|
4241
4626
|
function cloneThroughStaging(root, run, ref) {
|
|
4242
|
-
const staging = mkdtempSync4(
|
|
4627
|
+
const staging = mkdtempSync4(join15(dirname9(root), "agro-clone-"));
|
|
4243
4628
|
try {
|
|
4244
|
-
const checkout =
|
|
4629
|
+
const checkout = join15(staging, "agro");
|
|
4245
4630
|
cloneInto(checkout, run, root, ref);
|
|
4246
4631
|
for (const name of readdirSync3(checkout)) {
|
|
4247
|
-
const destination =
|
|
4248
|
-
if (
|
|
4632
|
+
const destination = join15(root, name);
|
|
4633
|
+
if (existsSync19(destination)) {
|
|
4249
4634
|
throw new Error(`could not move ${name} into ${root} \u2014 the path already exists`);
|
|
4250
4635
|
}
|
|
4251
|
-
renameSync5(
|
|
4636
|
+
renameSync5(join15(checkout, name), destination);
|
|
4252
4637
|
}
|
|
4253
4638
|
} finally {
|
|
4254
4639
|
rmSync4(staging, { recursive: true, force: true });
|
|
@@ -4256,7 +4641,7 @@ function cloneThroughStaging(root, run, ref) {
|
|
|
4256
4641
|
}
|
|
4257
4642
|
function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
4258
4643
|
const root = resolve9(path4);
|
|
4259
|
-
if (
|
|
4644
|
+
if (existsSync19(join15(root, ".git"))) return { root, action: "reused" };
|
|
4260
4645
|
const stats = statSync5(root, { throwIfNoEntry: false });
|
|
4261
4646
|
if (stats === void 0) {
|
|
4262
4647
|
mkdirSync10(dirname9(root), { recursive: true });
|
|
@@ -4278,11 +4663,11 @@ function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
|
4278
4663
|
function listHostWorkspaces(env, home) {
|
|
4279
4664
|
const root = workspacesRoot(env, home);
|
|
4280
4665
|
if (!isDirectory3(root)) return [];
|
|
4281
|
-
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) =>
|
|
4666
|
+
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) => existsSync19(join15(root, name, ".git"))).sort();
|
|
4282
4667
|
}
|
|
4283
4668
|
function resolveExistingWorkspace(bin, verb, path4, env, home) {
|
|
4284
4669
|
const root = resolve9(path4);
|
|
4285
|
-
if (
|
|
4670
|
+
if (existsSync19(join15(root, ".git"))) return { ok: true, root };
|
|
4286
4671
|
const existing = listHostWorkspaces(env, home);
|
|
4287
4672
|
const inventory = existing.length === 0 ? "No host workspace exists yet.\n" : `Workspaces that exist: ${existing.join(", ")}
|
|
4288
4673
|
`;
|
|
@@ -4312,7 +4697,7 @@ function targetFor(root, run, env) {
|
|
|
4312
4697
|
});
|
|
4313
4698
|
}
|
|
4314
4699
|
function hostPrefix(home) {
|
|
4315
|
-
return
|
|
4700
|
+
return join16(home, ".local");
|
|
4316
4701
|
}
|
|
4317
4702
|
function pathEntries(env) {
|
|
4318
4703
|
return (env.PATH ?? "").split(delimiter2).filter((part) => part !== "");
|
|
@@ -4346,6 +4731,21 @@ async function probeInstalled(target, entry, prefix, user, env) {
|
|
|
4346
4731
|
throw err;
|
|
4347
4732
|
}
|
|
4348
4733
|
}
|
|
4734
|
+
function sandboxMarkerPath(entry) {
|
|
4735
|
+
return `${SANDBOX_HARNESS_PREFIX}/share/agro/harnesses/${entry.id}.installed`;
|
|
4736
|
+
}
|
|
4737
|
+
async function sandboxMarkerExists(target, entry) {
|
|
4738
|
+
const r = await target.exec({
|
|
4739
|
+
argv: ["test", "-f", sandboxMarkerPath(entry)],
|
|
4740
|
+
user: "sandbox",
|
|
4741
|
+
stdio: "capture",
|
|
4742
|
+
timeoutMs: PROBE_TIMEOUT_MS
|
|
4743
|
+
});
|
|
4744
|
+
return r.exitCode === 0;
|
|
4745
|
+
}
|
|
4746
|
+
function hostBinaryUnderPrefix(entry, prefix) {
|
|
4747
|
+
return existsSync20(join16(harnessBinPath(prefix), entry.binary));
|
|
4748
|
+
}
|
|
4349
4749
|
function stateOf(entry, installed, location) {
|
|
4350
4750
|
return {
|
|
4351
4751
|
id: entry.id,
|
|
@@ -4365,7 +4765,7 @@ function probeableHost(env, home) {
|
|
|
4365
4765
|
return void 0;
|
|
4366
4766
|
}
|
|
4367
4767
|
const prefix = hostPrefix(home);
|
|
4368
|
-
if (!
|
|
4768
|
+
if (!existsSync20(join16(root, ".git")) && !existsSync20(prefix)) return void 0;
|
|
4369
4769
|
return { root, prefix };
|
|
4370
4770
|
}
|
|
4371
4771
|
async function collectStates(root, run, home, env, only) {
|
|
@@ -4571,7 +4971,15 @@ Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
|
4571
4971
|
const code = await reconcileHermes(target, io, bin, void 0, root);
|
|
4572
4972
|
if (code !== 0) return code;
|
|
4573
4973
|
}
|
|
4574
|
-
|
|
4974
|
+
let hasReceipt;
|
|
4975
|
+
try {
|
|
4976
|
+
hasReceipt = readHostConfig(env, home).hostHarnesses?.[entry.id] !== void 0;
|
|
4977
|
+
} catch (err) {
|
|
4978
|
+
io.stderr(`${bin} harness: ${messageOf(err)}
|
|
4979
|
+
`);
|
|
4980
|
+
return 1;
|
|
4981
|
+
}
|
|
4982
|
+
if (await probeInstalled(target, entry, prefix, void 0, installEnv) === true && (hasReceipt || !hostBinaryUnderPrefix(entry, prefix))) {
|
|
4575
4983
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
4576
4984
|
`);
|
|
4577
4985
|
try {
|
|
@@ -4735,7 +5143,20 @@ async function runHarnessUninstall(name, opts, io) {
|
|
|
4735
5143
|
if (!isReachable(await resolveTargetStatus(target))) {
|
|
4736
5144
|
return await uninstallOnHost(entry, opts, io, run);
|
|
4737
5145
|
}
|
|
4738
|
-
|
|
5146
|
+
const outcome = await removeHarness(entry, target, SANDBOX_HARNESS_PREFIX, "sandbox", opts, io);
|
|
5147
|
+
if (outcome.dropReceipt) {
|
|
5148
|
+
const cleared = await target.exec({
|
|
5149
|
+
argv: ["rm", "-f", sandboxMarkerPath(entry)],
|
|
5150
|
+
user: "sandbox",
|
|
5151
|
+
stdio: "capture"
|
|
5152
|
+
});
|
|
5153
|
+
if (cleared.exitCode !== 0) {
|
|
5154
|
+
io.stderr(`${opts.bin} harness: could not clear the install record (exit ${cleared.exitCode}).
|
|
5155
|
+
`);
|
|
5156
|
+
return 1;
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
return outcome.code;
|
|
4739
5160
|
}
|
|
4740
5161
|
async function runHarnessInstall(name, opts, io) {
|
|
4741
5162
|
const run = opts.run ?? spawnRunner;
|
|
@@ -4761,7 +5182,7 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4761
5182
|
if (code !== 0) return code;
|
|
4762
5183
|
}
|
|
4763
5184
|
const already = await probeInstalled(target, entry, SANDBOX_HARNESS_PREFIX, "sandbox", installEnv);
|
|
4764
|
-
if (already === true) {
|
|
5185
|
+
if (already === true && await sandboxMarkerExists(target, entry)) {
|
|
4765
5186
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
4766
5187
|
`);
|
|
4767
5188
|
return 0;
|
|
@@ -4792,15 +5213,26 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4792
5213
|
const code = await reconcileHermes(target, io, opts.bin, "sandbox");
|
|
4793
5214
|
if (code !== 0) return code;
|
|
4794
5215
|
}
|
|
5216
|
+
const marker = sandboxMarkerPath(entry);
|
|
5217
|
+
const marked = await target.exec({
|
|
5218
|
+
argv: ["sh", "-c", `mkdir -p "$(dirname "$1")" && : > "$1"`, "sh", marker],
|
|
5219
|
+
user: "sandbox",
|
|
5220
|
+
stdio: "capture"
|
|
5221
|
+
});
|
|
5222
|
+
if (marked.exitCode !== 0) {
|
|
5223
|
+
io.stderr(`${opts.bin} harness: could not record the install at ${marker} (exit ${marked.exitCode}).
|
|
5224
|
+
`);
|
|
5225
|
+
return 1;
|
|
5226
|
+
}
|
|
4795
5227
|
io.stdout(`${entry.id}: installed \u2014 see ${sourceDocsUrl(entry.docsPath)} for authentication
|
|
4796
5228
|
`);
|
|
4797
5229
|
return 0;
|
|
4798
5230
|
}
|
|
4799
5231
|
|
|
4800
5232
|
// src/commands/workspace.ts
|
|
4801
|
-
import { existsSync as
|
|
5233
|
+
import { existsSync as existsSync21, rmSync as rmSync5 } from "node:fs";
|
|
4802
5234
|
import { homedir as homedir4 } from "node:os";
|
|
4803
|
-
import { join as
|
|
5235
|
+
import { join as join17, resolve as resolve10 } from "node:path";
|
|
4804
5236
|
function homeOf2(opts) {
|
|
4805
5237
|
return (opts.homedir ?? homedir4)();
|
|
4806
5238
|
}
|
|
@@ -4846,9 +5278,9 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
4846
5278
|
return 1;
|
|
4847
5279
|
}
|
|
4848
5280
|
let action;
|
|
4849
|
-
const preexisting =
|
|
5281
|
+
const preexisting = existsSync21(root);
|
|
4850
5282
|
try {
|
|
4851
|
-
if (!opts.json && !
|
|
5283
|
+
if (!opts.json && !existsSync21(join17(root, ".git"))) {
|
|
4852
5284
|
const at = opts.ref !== void 0 ? ` at ${opts.ref}` : "";
|
|
4853
5285
|
io.stdout(`cloning ${AGRO_REPO_URL}${at} into ${root}\u2026
|
|
4854
5286
|
`);
|
|
@@ -4893,7 +5325,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
4893
5325
|
const recorded = recordedRoot(env, home);
|
|
4894
5326
|
const registry = workspacesRoot(env, home);
|
|
4895
5327
|
rows = listHostWorkspaces(env, home).map((name) => {
|
|
4896
|
-
const root =
|
|
5328
|
+
const root = join17(registry, name);
|
|
4897
5329
|
return { name, root, default: recorded === root };
|
|
4898
5330
|
});
|
|
4899
5331
|
} catch (err) {
|
|
@@ -4918,9 +5350,9 @@ async function runWorkspaceList(opts, io) {
|
|
|
4918
5350
|
}
|
|
4919
5351
|
|
|
4920
5352
|
// src/commands/tool.ts
|
|
4921
|
-
import { existsSync as
|
|
5353
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
4922
5354
|
import { homedir as homedir5, userInfo as userInfo2 } from "node:os";
|
|
4923
|
-
import { delimiter as delimiter3, join as
|
|
5355
|
+
import { delimiter as delimiter3, join as join18 } from "node:path";
|
|
4924
5356
|
|
|
4925
5357
|
// src/lib/tools/catalog.ts
|
|
4926
5358
|
var TOOLS_DOC = "docs/installation.md";
|
|
@@ -5337,6 +5769,7 @@ function resolveToolInstallArgv(entry, host) {
|
|
|
5337
5769
|
}
|
|
5338
5770
|
|
|
5339
5771
|
// src/commands/tool.ts
|
|
5772
|
+
var ROOT_TOOL_REMOVAL_DOCS = `${sourceDocsUrl("docs/installation.md")}#remove-a-root-level-tool`;
|
|
5340
5773
|
function isReachable2(status) {
|
|
5341
5774
|
return status === "ready" || status === "starting";
|
|
5342
5775
|
}
|
|
@@ -5350,7 +5783,21 @@ function targetFor2(root, run, env) {
|
|
|
5350
5783
|
});
|
|
5351
5784
|
}
|
|
5352
5785
|
function hostPrefix2(home) {
|
|
5353
|
-
return
|
|
5786
|
+
return join18(home, ".local");
|
|
5787
|
+
}
|
|
5788
|
+
function sandboxMarkerPath2(entry) {
|
|
5789
|
+
return `${SANDBOX_HARNESS_PREFIX}/share/agro/tools/${entry.id}.installed`;
|
|
5790
|
+
}
|
|
5791
|
+
async function sandboxMarkerExists2(target, entry) {
|
|
5792
|
+
const r = await target.exec({
|
|
5793
|
+
argv: ["test", "-f", sandboxMarkerPath2(entry)],
|
|
5794
|
+
user: "sandbox",
|
|
5795
|
+
stdio: "capture"
|
|
5796
|
+
});
|
|
5797
|
+
return r.exitCode === 0;
|
|
5798
|
+
}
|
|
5799
|
+
function hostBinaryUnderPrefix2(entry, prefix) {
|
|
5800
|
+
return existsSync22(join18(harnessBinPath(prefix), entry.binary));
|
|
5354
5801
|
}
|
|
5355
5802
|
function pathEntries2(env) {
|
|
5356
5803
|
return (env.PATH ?? "").split(delimiter3).filter((part) => part !== "");
|
|
@@ -5430,7 +5877,7 @@ function probeableHost2(env, home) {
|
|
|
5430
5877
|
return void 0;
|
|
5431
5878
|
}
|
|
5432
5879
|
const prefix = hostPrefix2(home);
|
|
5433
|
-
if (!
|
|
5880
|
+
if (!existsSync22(join18(root, ".git")) && !existsSync22(prefix)) return void 0;
|
|
5434
5881
|
return { root, prefix };
|
|
5435
5882
|
}
|
|
5436
5883
|
async function collectRows(root, run, home, env, only) {
|
|
@@ -5646,7 +6093,15 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5646
6093
|
const prefix = hostPrefix2(home);
|
|
5647
6094
|
const installEnv = { NPM_USER_PREFIX: prefix };
|
|
5648
6095
|
const target = hostTargetFor2(root, prefix, run, env);
|
|
5649
|
-
|
|
6096
|
+
let hasReceipt;
|
|
6097
|
+
try {
|
|
6098
|
+
hasReceipt = readHostConfig(env, home).hostTools?.[entry.id] !== void 0;
|
|
6099
|
+
} catch (err) {
|
|
6100
|
+
io.stderr(`${bin} tool: ${messageOf3(err)}
|
|
6101
|
+
`);
|
|
6102
|
+
return 1;
|
|
6103
|
+
}
|
|
6104
|
+
if (await probeInstalled2(target, entry, void 0, installEnv) === true && (hasReceipt || entry.hostInstallUser === "root" || !hostBinaryUnderPrefix2(entry, prefix))) {
|
|
5650
6105
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
5651
6106
|
`);
|
|
5652
6107
|
try {
|
|
@@ -5681,13 +6136,8 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5681
6136
|
`);
|
|
5682
6137
|
return r.exitCode;
|
|
5683
6138
|
}
|
|
5684
|
-
const
|
|
5685
|
-
|
|
5686
|
-
binary: entry.binary,
|
|
5687
|
-
binPath: harnessBinPath(prefix),
|
|
5688
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5689
|
-
workspaceRoot: root
|
|
5690
|
-
};
|
|
6139
|
+
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6140
|
+
const receipt = entry.hostInstallUser === "root" ? { scope: "root", binary: entry.binary, installedAt, workspaceRoot: root } : { prefix, binary: entry.binary, binPath: harnessBinPath(prefix), installedAt, workspaceRoot: root };
|
|
5691
6141
|
try {
|
|
5692
6142
|
const config = readHostConfig(env, home);
|
|
5693
6143
|
writeHostConfig(
|
|
@@ -5747,7 +6197,7 @@ ${installableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
|
5747
6197
|
}
|
|
5748
6198
|
if (entry.installArgv === void 0) return refuseContainer();
|
|
5749
6199
|
const already = await probeInstalled2(target, entry, "sandbox");
|
|
5750
|
-
if (already === true) {
|
|
6200
|
+
if (already === true && await sandboxMarkerExists2(target, entry)) {
|
|
5751
6201
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
5752
6202
|
`);
|
|
5753
6203
|
return 0;
|
|
@@ -5769,6 +6219,17 @@ ${installableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
|
5769
6219
|
`);
|
|
5770
6220
|
return r.exitCode;
|
|
5771
6221
|
}
|
|
6222
|
+
const marker = sandboxMarkerPath2(entry);
|
|
6223
|
+
const marked = await target.exec({
|
|
6224
|
+
argv: ["sh", "-c", `mkdir -p "$(dirname "$1")" && : > "$1"`, "sh", marker],
|
|
6225
|
+
user: "sandbox",
|
|
6226
|
+
stdio: "capture"
|
|
6227
|
+
});
|
|
6228
|
+
if (marked.exitCode !== 0) {
|
|
6229
|
+
io.stderr(`${opts.bin} tool: could not record the install at ${marker} (exit ${marked.exitCode}).
|
|
6230
|
+
`);
|
|
6231
|
+
return 1;
|
|
6232
|
+
}
|
|
5772
6233
|
io.stdout(`${entry.id}: installed \u2014 see ${sourceDocsUrl(entry.docsPath)}
|
|
5773
6234
|
`);
|
|
5774
6235
|
return 0;
|
|
@@ -5829,7 +6290,7 @@ Removing it could delete a tool you installed yourself. Re-run with \`--force\`
|
|
|
5829
6290
|
);
|
|
5830
6291
|
return 1;
|
|
5831
6292
|
}
|
|
5832
|
-
const prefix = receipt
|
|
6293
|
+
const prefix = receipt === void 0 || isRootToolReceipt(receipt) ? computed : receipt.prefix;
|
|
5833
6294
|
const target = hostTargetFor2(receipt?.workspaceRoot ?? workspace, prefix, run, env);
|
|
5834
6295
|
const outcome = await removeTool(entry, target, prefix, void 0, opts, io, true);
|
|
5835
6296
|
if (outcome.dropReceipt && receipt !== void 0) {
|
|
@@ -5852,6 +6313,15 @@ async function runToolUninstall(name, opts, io) {
|
|
|
5852
6313
|
const root = resolveProjectRoot(opts.cwd);
|
|
5853
6314
|
const entry = findTool(name);
|
|
5854
6315
|
if (!entry) return unknownTool(name, io, opts.bin);
|
|
6316
|
+
if (entry.hostInstallUser === "root") {
|
|
6317
|
+
io.stderr(
|
|
6318
|
+
`${opts.bin} tool: ${entry.id} cannot be removed by this command.
|
|
6319
|
+
${opts.bin} does not remove system packages that it installed as root.
|
|
6320
|
+
Remove ${entry.id} by hand: ${ROOT_TOOL_REMOVAL_DOCS}
|
|
6321
|
+
`
|
|
6322
|
+
);
|
|
6323
|
+
return 1;
|
|
6324
|
+
}
|
|
5855
6325
|
if (entry.uninstallArgv === null) {
|
|
5856
6326
|
io.stderr(`${opts.bin} tool: ${entry.id} cannot be removed by this command.
|
|
5857
6327
|
|
|
@@ -5864,7 +6334,20 @@ async function runToolUninstall(name, opts, io) {
|
|
|
5864
6334
|
if (!isReachable2(await resolveTargetStatus(target))) {
|
|
5865
6335
|
return await uninstallOnHost2(entry, opts, io, run);
|
|
5866
6336
|
}
|
|
5867
|
-
|
|
6337
|
+
const outcome = await removeTool(entry, target, SANDBOX_HARNESS_PREFIX, "sandbox", opts, io);
|
|
6338
|
+
if (outcome.dropReceipt) {
|
|
6339
|
+
const cleared = await target.exec({
|
|
6340
|
+
argv: ["rm", "-f", sandboxMarkerPath2(entry)],
|
|
6341
|
+
user: "sandbox",
|
|
6342
|
+
stdio: "capture"
|
|
6343
|
+
});
|
|
6344
|
+
if (cleared.exitCode !== 0) {
|
|
6345
|
+
io.stderr(`${opts.bin} tool: could not clear the install record (exit ${cleared.exitCode}).
|
|
6346
|
+
`);
|
|
6347
|
+
return 1;
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6350
|
+
return outcome.code;
|
|
5868
6351
|
}
|
|
5869
6352
|
|
|
5870
6353
|
// src/lib/remote.ts
|
|
@@ -5944,7 +6427,7 @@ var INTEGRATIONS = {
|
|
|
5944
6427
|
)
|
|
5945
6428
|
}
|
|
5946
6429
|
};
|
|
5947
|
-
function
|
|
6430
|
+
function isHelpFlag2(arg) {
|
|
5948
6431
|
return arg === "--help" || arg === "-h" || arg === "help";
|
|
5949
6432
|
}
|
|
5950
6433
|
function isVersionFlag(arg) {
|
|
@@ -5961,7 +6444,7 @@ function printAgroHelp(product = AGRO_PRODUCT) {
|
|
|
5961
6444
|
process.stdout.write(`${bin} \u2014 ${title} (v${AGRO_VERSION})
|
|
5962
6445
|
|
|
5963
6446
|
Usage:
|
|
5964
|
-
${bin} sandbox <args...> Create and
|
|
6447
|
+
${bin} sandbox <args...> Create, list, and upgrade sandboxes (install|list|upgrade)
|
|
5965
6448
|
${bin} shell [name] Open a zsh shell in the running sandbox container
|
|
5966
6449
|
${bin} config <args...> Read and write ${stateNames(bin).configFile} (show|set), or run a wizard
|
|
5967
6450
|
${bin} secret <args...> Read and write the gitignored root .env (set|list)
|
|
@@ -6111,66 +6594,6 @@ Flags:
|
|
|
6111
6594
|
--force Override the up-to-date / downgrade gate.
|
|
6112
6595
|
`);
|
|
6113
6596
|
}
|
|
6114
|
-
function runtimeLines() {
|
|
6115
|
-
const width = Math.max(...RUNTIME_CATALOG.map((r) => r.id.length));
|
|
6116
|
-
return RUNTIME_CATALOG.map(
|
|
6117
|
-
(r) => ` ${r.id.padEnd(width)} ${r.provisionable ? "provisionable" : "planned"}`
|
|
6118
|
-
).join("\n");
|
|
6119
|
-
}
|
|
6120
|
-
function printSandboxHelp(bin = AGRO_PRODUCT.bin) {
|
|
6121
|
-
process.stdout.write(`${bin} sandbox \u2014 Create and list sandboxes
|
|
6122
|
-
|
|
6123
|
-
Usage:
|
|
6124
|
-
${bin} sandbox install <runtime> [--name <name>] [--checkout <dir>]
|
|
6125
|
-
[--home-mount <dir>] [--yes]
|
|
6126
|
-
[--version <X.Y.Z> | --image[=<ref>]]
|
|
6127
|
-
[--no-build] [--print-argv]
|
|
6128
|
-
${bin} sandbox list [--json]
|
|
6129
|
-
|
|
6130
|
-
\`install\` writes a sandbox entry under \${${stateNames(bin).envPrefix}HOME:-~/${stateNames(bin).userStateDir}}/sandboxes/<name>/,
|
|
6131
|
-
materialises the compose files and the compose wrapper into it, then starts the
|
|
6132
|
-
container. It needs no project checkout and runs from any directory. Without
|
|
6133
|
-
--checkout the sandbox runs the prebuilt image; with --checkout <dir> that
|
|
6134
|
-
checkout is bound at /home/sandbox/harness; the sandbox builds locally only
|
|
6135
|
-
when that directory holds .devcontainer/Dockerfile.
|
|
6136
|
-
|
|
6137
|
-
On a terminal without --yes it asks for the sandbox name, the timezone, the git
|
|
6138
|
-
identity, SSH (with its host port), the host Docker socket, and the host path
|
|
6139
|
-
for /home/sandbox. With --checkout it seeds those answers from that
|
|
6140
|
-
checkout's ${stateNames(bin).configFile}.
|
|
6141
|
-
|
|
6142
|
-
Flags:
|
|
6143
|
-
--name <name> Registry entry name (default: the lowest free ${DEFAULT_NAME_PREFIX}<n>)
|
|
6144
|
-
--checkout <dir> Bind this checkout into the sandbox and seed the defaults
|
|
6145
|
-
from its ${stateNames(bin).configFile}. The sandbox builds
|
|
6146
|
-
locally only when that directory holds
|
|
6147
|
-
.devcontainer/Dockerfile
|
|
6148
|
-
--repo <dir> Deprecated alias for --checkout
|
|
6149
|
-
--home-mount <dir>
|
|
6150
|
-
Bind this host directory at /home/sandbox instead of the
|
|
6151
|
-
Docker-managed volume. Relative paths resolve against the
|
|
6152
|
-
working directory. The directory is created when absent
|
|
6153
|
-
--yes Non-interactive: keep every default and ask nothing
|
|
6154
|
-
--version <X.Y.Z>
|
|
6155
|
-
Run the official release image ghcr.io/mifunedev/agro:<X.Y.Z>
|
|
6156
|
-
(implies --image). A leading v and a -<channel>.<n>
|
|
6157
|
-
pre-release suffix are accepted
|
|
6158
|
-
--image Run the prebuilt image instead of building (implies
|
|
6159
|
-
--no-build). The ref resolves first-match: --version or
|
|
6160
|
-
--image=<ref> > ${stateNames(bin).envPrefix}SANDBOX_IMAGE >
|
|
6161
|
-
${stateNames(bin).configFile} image.ref > ${officialImageRef(AGRO_VERSION)}
|
|
6162
|
-
--image=<ref> Run a custom image (implies --image); conflicts with --version
|
|
6163
|
-
--no-build Suppress the local build and reuse an existing image
|
|
6164
|
-
--print-argv Print the docker compose argv that would run, then exit
|
|
6165
|
-
without writing an entry
|
|
6166
|
-
--json Machine-readable output (list)
|
|
6167
|
-
|
|
6168
|
-
Runtimes:
|
|
6169
|
-
${runtimeLines()}
|
|
6170
|
-
|
|
6171
|
-
Next: ${bin} shell <name>
|
|
6172
|
-
`);
|
|
6173
|
-
}
|
|
6174
6597
|
function printShellHelp(bin = AGRO_PRODUCT.bin) {
|
|
6175
6598
|
process.stdout.write(`${bin} shell \u2014 Open a shell in the running sandbox container
|
|
6176
6599
|
|
|
@@ -6295,6 +6718,8 @@ no workspace: run \`${bin} workspace create\` first. It installs into
|
|
|
6295
6718
|
\`~/.local\` and records the install as \`hostTools\` in that same file. On the host,
|
|
6296
6719
|
\`uninstall\` removes only what \`install\` recorded, from the prefix in that record.
|
|
6297
6720
|
Without a record it refuses, and \`--force\` overrides.
|
|
6721
|
+
\`uninstall\` refuses each root-level tool, marked \`(root)\` in \`${bin} tool list\`,
|
|
6722
|
+
with and without \`--force\`. The refusal links the manual removal steps.
|
|
6298
6723
|
|
|
6299
6724
|
Flags:
|
|
6300
6725
|
--yes Accept a large download without prompting
|
|
@@ -6384,7 +6809,7 @@ function extractSandboxFlag(command, rest) {
|
|
|
6384
6809
|
}
|
|
6385
6810
|
var LANGFUSE_VERBS = ["apply", "status", "disable"];
|
|
6386
6811
|
function parseLangfuseArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6387
|
-
if (rest.length === 0 ||
|
|
6812
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) return { ok: true, args: { help: true } };
|
|
6388
6813
|
const [head, ...tail] = rest;
|
|
6389
6814
|
if (!LANGFUSE_VERBS.includes(head)) {
|
|
6390
6815
|
return {
|
|
@@ -6393,7 +6818,7 @@ function parseLangfuseArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6393
6818
|
showHelp: true
|
|
6394
6819
|
};
|
|
6395
6820
|
}
|
|
6396
|
-
if (
|
|
6821
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { help: true } };
|
|
6397
6822
|
if (tail.length > 0) {
|
|
6398
6823
|
return { ok: false, error: `${bin} langfuse ${head}: unexpected argument "${tail[0]}"` };
|
|
6399
6824
|
}
|
|
@@ -6411,12 +6836,12 @@ function parseConfigArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6411
6836
|
...scoped.args.sandbox === void 0 ? {} : { sandbox: scoped.args.sandbox },
|
|
6412
6837
|
...forced ? { force: true } : {}
|
|
6413
6838
|
};
|
|
6414
|
-
if (rest.length === 0 ||
|
|
6839
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6415
6840
|
return { ok: true, args: { ...args, help: true } };
|
|
6416
6841
|
}
|
|
6417
6842
|
const [head, ...tail] = rest;
|
|
6418
6843
|
if (!CONFIG_VERBS.includes(head)) {
|
|
6419
|
-
if (tail.length > 0 &&
|
|
6844
|
+
if (tail.length > 0 && isHelpFlag2(tail[0])) {
|
|
6420
6845
|
return { ok: true, args: { ...args, integration: head, integrationHelp: true } };
|
|
6421
6846
|
}
|
|
6422
6847
|
if (tail.length > 0) {
|
|
@@ -6428,7 +6853,7 @@ function parseConfigArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6428
6853
|
return { ok: true, args: { ...args, integration: head } };
|
|
6429
6854
|
}
|
|
6430
6855
|
const verb = head;
|
|
6431
|
-
if (
|
|
6856
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
6432
6857
|
if (verb === "show" || verb === "repo") {
|
|
6433
6858
|
if (tail.length > 0) {
|
|
6434
6859
|
return { ok: false, error: `${bin} config ${verb}: unexpected argument "${tail[0]}"` };
|
|
@@ -6460,7 +6885,7 @@ function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6460
6885
|
help: false,
|
|
6461
6886
|
...scoped.args.sandbox === void 0 ? {} : { sandbox: scoped.args.sandbox }
|
|
6462
6887
|
};
|
|
6463
|
-
if (rest.length === 0 ||
|
|
6888
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6464
6889
|
return { ok: true, args: { ...args, help: true } };
|
|
6465
6890
|
}
|
|
6466
6891
|
const [head, ...tail] = rest;
|
|
@@ -6472,7 +6897,7 @@ function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6472
6897
|
};
|
|
6473
6898
|
}
|
|
6474
6899
|
const verb = head;
|
|
6475
|
-
if (
|
|
6900
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
6476
6901
|
if (verb === "list") {
|
|
6477
6902
|
if (tail.length > 0) {
|
|
6478
6903
|
return { ok: false, error: `${bin} secret list: unexpected argument "${tail[0]}"` };
|
|
@@ -6493,7 +6918,7 @@ function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6493
6918
|
}
|
|
6494
6919
|
function parseDestroyArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6495
6920
|
const args = { help: false, yes: false };
|
|
6496
|
-
if (
|
|
6921
|
+
if (isHelpFlag2(rest[0])) return { ok: true, args: { ...args, help: true } };
|
|
6497
6922
|
for (const token of rest) {
|
|
6498
6923
|
if (token === "--yes") {
|
|
6499
6924
|
args.yes = true;
|
|
@@ -6512,7 +6937,7 @@ function parseDestroyArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6512
6937
|
}
|
|
6513
6938
|
function parseComposeArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6514
6939
|
const args = { help: false, passthrough: [] };
|
|
6515
|
-
if (rest.length === 0 ||
|
|
6940
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6516
6941
|
return { ok: true, args: { ...args, help: true } };
|
|
6517
6942
|
}
|
|
6518
6943
|
if (rest[0] !== "config") {
|
|
@@ -6524,7 +6949,7 @@ function parseComposeArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6524
6949
|
}
|
|
6525
6950
|
args.subcommand = "config";
|
|
6526
6951
|
const tail = rest.slice(1);
|
|
6527
|
-
if (
|
|
6952
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
6528
6953
|
const sep4 = tail.indexOf("--");
|
|
6529
6954
|
if (sep4 === -1 && tail.length > 0) {
|
|
6530
6955
|
return {
|
|
@@ -6550,7 +6975,7 @@ function parseSelfUpgradeArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6550
6975
|
args.dryRun = true;
|
|
6551
6976
|
continue;
|
|
6552
6977
|
}
|
|
6553
|
-
if (
|
|
6978
|
+
if (isHelpFlag2(arg)) {
|
|
6554
6979
|
args.help = true;
|
|
6555
6980
|
return { ok: true, args };
|
|
6556
6981
|
}
|
|
@@ -6592,7 +7017,7 @@ function parseVendorArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6592
7017
|
args.force = true;
|
|
6593
7018
|
continue;
|
|
6594
7019
|
}
|
|
6595
|
-
if (
|
|
7020
|
+
if (isHelpFlag2(arg)) {
|
|
6596
7021
|
args.help = true;
|
|
6597
7022
|
return { ok: true, args };
|
|
6598
7023
|
}
|
|
@@ -6609,121 +7034,9 @@ function parseVendorArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6609
7034
|
}
|
|
6610
7035
|
return { ok: true, args };
|
|
6611
7036
|
}
|
|
6612
|
-
var SANDBOX_VALUE_FLAGS = {
|
|
6613
|
-
"--name": "name",
|
|
6614
|
-
"--checkout": "checkout",
|
|
6615
|
-
"--repo": "checkout",
|
|
6616
|
-
"--home-mount": "homeMount"
|
|
6617
|
-
};
|
|
6618
|
-
function parseSandboxArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6619
|
-
const args = {
|
|
6620
|
-
help: false,
|
|
6621
|
-
yes: false,
|
|
6622
|
-
image: false,
|
|
6623
|
-
noBuild: false,
|
|
6624
|
-
printArgv: false,
|
|
6625
|
-
json: false
|
|
6626
|
-
};
|
|
6627
|
-
if (rest.length === 0 || isHelpFlag(rest[0])) {
|
|
6628
|
-
return { ok: true, args: { ...args, help: true } };
|
|
6629
|
-
}
|
|
6630
|
-
const [head, ...tail] = rest;
|
|
6631
|
-
if (head !== "install" && head !== "list") {
|
|
6632
|
-
return {
|
|
6633
|
-
ok: false,
|
|
6634
|
-
error: `${bin} sandbox: unknown subcommand "${head}" \u2014 expected install or list`,
|
|
6635
|
-
showHelp: true
|
|
6636
|
-
};
|
|
6637
|
-
}
|
|
6638
|
-
args.subcommand = head;
|
|
6639
|
-
if (isHelpFlag(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
6640
|
-
const positionals = [];
|
|
6641
|
-
const checkoutSpellings = /* @__PURE__ */ new Set();
|
|
6642
|
-
let version;
|
|
6643
|
-
for (let i = 0; i < tail.length; i++) {
|
|
6644
|
-
const token = tail[i];
|
|
6645
|
-
const valueFlag = SANDBOX_VALUE_FLAGS[token];
|
|
6646
|
-
if (valueFlag !== void 0) {
|
|
6647
|
-
const value = tail[i + 1];
|
|
6648
|
-
if (value === void 0) {
|
|
6649
|
-
return { ok: false, error: `${bin} sandbox ${head}: ${token} requires a value` };
|
|
6650
|
-
}
|
|
6651
|
-
if (valueFlag === "checkout") checkoutSpellings.add(token);
|
|
6652
|
-
args[valueFlag] = value;
|
|
6653
|
-
i++;
|
|
6654
|
-
} else if (token === "--yes") {
|
|
6655
|
-
args.yes = true;
|
|
6656
|
-
} else if (token === "--no-build") {
|
|
6657
|
-
args.noBuild = true;
|
|
6658
|
-
} else if (token === "--print-argv") {
|
|
6659
|
-
args.printArgv = true;
|
|
6660
|
-
} else if (token === "--image") {
|
|
6661
|
-
args.image = true;
|
|
6662
|
-
} else if (token.startsWith("--image=")) {
|
|
6663
|
-
const ref = token.slice("--image=".length);
|
|
6664
|
-
if (ref === "") {
|
|
6665
|
-
return { ok: false, error: `${bin} sandbox: --image=<ref> requires a non-empty image ref` };
|
|
6666
|
-
}
|
|
6667
|
-
args.image = true;
|
|
6668
|
-
args.imageRef = ref;
|
|
6669
|
-
} else if (token === "--version" || token.startsWith("--version=")) {
|
|
6670
|
-
const value = token === "--version" ? tail[++i] : token.slice("--version=".length);
|
|
6671
|
-
if (value === void 0 || value === "") {
|
|
6672
|
-
return { ok: false, error: `${bin} sandbox ${head}: --version requires a value` };
|
|
6673
|
-
}
|
|
6674
|
-
version = parseReleaseVersion(value);
|
|
6675
|
-
if (version === void 0) {
|
|
6676
|
-
return {
|
|
6677
|
-
ok: false,
|
|
6678
|
-
error: `${bin} sandbox ${head}: --version "${value}" is not a release version \u2014 expected X.Y.Z`
|
|
6679
|
-
};
|
|
6680
|
-
}
|
|
6681
|
-
} else if (token === "--json") {
|
|
6682
|
-
args.json = true;
|
|
6683
|
-
} else if (token.startsWith("-")) {
|
|
6684
|
-
return { ok: false, error: `${bin} sandbox ${head}: unknown flag "${token}"` };
|
|
6685
|
-
} else {
|
|
6686
|
-
positionals.push(token);
|
|
6687
|
-
}
|
|
6688
|
-
}
|
|
6689
|
-
if (checkoutSpellings.has("--checkout") && checkoutSpellings.has("--repo")) {
|
|
6690
|
-
return {
|
|
6691
|
-
ok: false,
|
|
6692
|
-
error: `${bin} sandbox ${head}: --checkout conflicts with --repo \u2014 pass exactly one, and prefer --checkout`
|
|
6693
|
-
};
|
|
6694
|
-
}
|
|
6695
|
-
if (version !== void 0) {
|
|
6696
|
-
if (args.imageRef !== void 0) {
|
|
6697
|
-
return {
|
|
6698
|
-
ok: false,
|
|
6699
|
-
error: `${bin} sandbox ${head}: --version conflicts with --image=<ref> \u2014 pass --version for an official release or --image=<ref> for a custom image`
|
|
6700
|
-
};
|
|
6701
|
-
}
|
|
6702
|
-
args.image = true;
|
|
6703
|
-
args.imageRef = officialImageRef(version);
|
|
6704
|
-
}
|
|
6705
|
-
if (head === "list") {
|
|
6706
|
-
if (positionals.length > 0) {
|
|
6707
|
-
return { ok: false, error: `${bin} sandbox list: unexpected argument "${positionals[0]}"` };
|
|
6708
|
-
}
|
|
6709
|
-
return { ok: true, args };
|
|
6710
|
-
}
|
|
6711
|
-
if (positionals.length === 0) {
|
|
6712
|
-
return {
|
|
6713
|
-
ok: false,
|
|
6714
|
-
error: `${bin} sandbox install: a runtime is required, e.g. \`${bin} sandbox install docker\``,
|
|
6715
|
-
showHelp: true
|
|
6716
|
-
};
|
|
6717
|
-
}
|
|
6718
|
-
if (positionals.length > 1) {
|
|
6719
|
-
return { ok: false, error: `${bin} sandbox install: unexpected argument "${positionals[1]}"` };
|
|
6720
|
-
}
|
|
6721
|
-
args.runtime = positionals[0];
|
|
6722
|
-
return { ok: true, args };
|
|
6723
|
-
}
|
|
6724
7037
|
function parseShellArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6725
7038
|
const args = { help: false };
|
|
6726
|
-
if (
|
|
7039
|
+
if (isHelpFlag2(rest[0])) return { ok: true, args: { help: true } };
|
|
6727
7040
|
for (const token of rest) {
|
|
6728
7041
|
if (token.startsWith("-")) {
|
|
6729
7042
|
return { ok: false, error: `${bin} shell: unknown flag "${token}"` };
|
|
@@ -6737,7 +7050,7 @@ function parseShellArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6737
7050
|
}
|
|
6738
7051
|
function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6739
7052
|
const args = { help: false, json: false };
|
|
6740
|
-
if (rest.length === 0 ||
|
|
7053
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6741
7054
|
return { ok: true, args: { ...args, help: true } };
|
|
6742
7055
|
}
|
|
6743
7056
|
const positionals = [];
|
|
@@ -6809,7 +7122,7 @@ function parseHarnessArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6809
7122
|
host: false,
|
|
6810
7123
|
force: false
|
|
6811
7124
|
};
|
|
6812
|
-
if (rest.length === 0 ||
|
|
7125
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6813
7126
|
return { ok: true, args: { ...args, help: true } };
|
|
6814
7127
|
}
|
|
6815
7128
|
const positionals = [];
|
|
@@ -6890,7 +7203,7 @@ function parseHarnessArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6890
7203
|
}
|
|
6891
7204
|
function parseToolArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6892
7205
|
const args = { help: false, yes: false, json: false, host: false, force: false };
|
|
6893
|
-
if (rest.length === 0 ||
|
|
7206
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6894
7207
|
return { ok: true, args: { ...args, help: true } };
|
|
6895
7208
|
}
|
|
6896
7209
|
const positionals = [];
|
|
@@ -6948,11 +7261,11 @@ function parseGatewayArgs(rest) {
|
|
|
6948
7261
|
}
|
|
6949
7262
|
return { ok: true, args: { help: false, passthrough: [...rest] } };
|
|
6950
7263
|
}
|
|
6951
|
-
function bundledPayloadExists(bundled, exists =
|
|
6952
|
-
return exists(
|
|
7264
|
+
function bundledPayloadExists(bundled, exists = existsSync23) {
|
|
7265
|
+
return exists(join19(bundled.sourceControlDir, "manifest.json"));
|
|
6953
7266
|
}
|
|
6954
7267
|
function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
6955
|
-
const exists = bundled.exists ??
|
|
7268
|
+
const exists = bundled.exists ?? existsSync23;
|
|
6956
7269
|
if (args.fromDir !== void 0) {
|
|
6957
7270
|
return { kind: "local", fromDir: resolve11(args.fromDir) };
|
|
6958
7271
|
}
|
|
@@ -6972,7 +7285,7 @@ function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
|
6972
7285
|
function readPayloadVersion(checkoutDir) {
|
|
6973
7286
|
try {
|
|
6974
7287
|
const parsed = JSON.parse(
|
|
6975
|
-
|
|
7288
|
+
readFileSync13(join19(resolveControlDir(checkoutDir), "cli", "package.json"), "utf8")
|
|
6976
7289
|
);
|
|
6977
7290
|
if (parsed && typeof parsed.version === "string") return parsed.version;
|
|
6978
7291
|
} catch {
|
|
@@ -6981,7 +7294,7 @@ function readPayloadVersion(checkoutDir) {
|
|
|
6981
7294
|
}
|
|
6982
7295
|
async function runWithRemoteSource(hooks, fn) {
|
|
6983
7296
|
const fetch2 = hooks.fetch ?? fetchRemoteSource;
|
|
6984
|
-
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(
|
|
7297
|
+
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(join19(tmpdir4(), "oh-remote-")));
|
|
6985
7298
|
const rm = hooks.rm ?? ((dir) => rmSync6(dir, { recursive: true, force: true }));
|
|
6986
7299
|
const out = hooks.stdout ?? ((s) => process.stdout.write(s));
|
|
6987
7300
|
const checkoutDir = mkdtemp();
|
|
@@ -6998,7 +7311,7 @@ async function main(argv) {
|
|
|
6998
7311
|
const product = resolveProduct(process.argv[1]);
|
|
6999
7312
|
const bin = product.bin;
|
|
7000
7313
|
const [first, second] = argv;
|
|
7001
|
-
if (!first ||
|
|
7314
|
+
if (!first || isHelpFlag2(first)) {
|
|
7002
7315
|
printAgroHelp(product);
|
|
7003
7316
|
return 0;
|
|
7004
7317
|
}
|
|
@@ -7141,37 +7454,7 @@ async function main(argv) {
|
|
|
7141
7454
|
(checkoutDir) => runUpdate({ bin, targetDir, fromDir: checkoutDir, force, dryRun }, io)
|
|
7142
7455
|
);
|
|
7143
7456
|
}
|
|
7144
|
-
if (first === "sandbox")
|
|
7145
|
-
const parsed = parseSandboxArgs(argv.slice(1), bin);
|
|
7146
|
-
if (!parsed.ok) {
|
|
7147
|
-
process.stderr.write(`${parsed.error}
|
|
7148
|
-
`);
|
|
7149
|
-
if (parsed.showHelp) printSandboxHelp(bin);
|
|
7150
|
-
return 1;
|
|
7151
|
-
}
|
|
7152
|
-
const a = parsed.args;
|
|
7153
|
-
if (a.help) {
|
|
7154
|
-
printSandboxHelp(bin);
|
|
7155
|
-
return a.subcommand === void 0 ? 1 : 0;
|
|
7156
|
-
}
|
|
7157
|
-
const io = lifecycleIo();
|
|
7158
|
-
if (a.subcommand === "list") return await runSandboxList({ bin, json: a.json }, io);
|
|
7159
|
-
return await runSandboxInstall(
|
|
7160
|
-
{
|
|
7161
|
-
bin,
|
|
7162
|
-
runtime: a.runtime,
|
|
7163
|
-
...a.name !== void 0 ? { name: a.name } : {},
|
|
7164
|
-
...a.checkout !== void 0 ? { checkout: a.checkout } : {},
|
|
7165
|
-
...a.homeMount !== void 0 ? { homeMount: a.homeMount } : {},
|
|
7166
|
-
yes: a.yes,
|
|
7167
|
-
image: a.image,
|
|
7168
|
-
...a.imageRef !== void 0 ? { imageRef: a.imageRef } : {},
|
|
7169
|
-
noBuild: a.noBuild,
|
|
7170
|
-
printArgv: a.printArgv
|
|
7171
|
-
},
|
|
7172
|
-
io
|
|
7173
|
-
);
|
|
7174
|
-
}
|
|
7457
|
+
if (first === "sandbox") return runSandboxCommand(argv.slice(1), bin);
|
|
7175
7458
|
if (first === "shell") {
|
|
7176
7459
|
const parsed = parseShellArgs(argv.slice(1), bin);
|
|
7177
7460
|
if (!parsed.ok) {
|
|
@@ -7256,7 +7539,7 @@ async function main(argv) {
|
|
|
7256
7539
|
if (composeVerbs().includes(first)) {
|
|
7257
7540
|
const verb = first;
|
|
7258
7541
|
const rest = argv.slice(1);
|
|
7259
|
-
if (
|
|
7542
|
+
if (isHelpFlag2(rest[0])) {
|
|
7260
7543
|
printComposeVerbHelp(verb, bin);
|
|
7261
7544
|
return 0;
|
|
7262
7545
|
}
|
|
@@ -7386,7 +7669,7 @@ export {
|
|
|
7386
7669
|
SECRET_VERBS,
|
|
7387
7670
|
bundledPayloadExists,
|
|
7388
7671
|
extractSandboxFlag,
|
|
7389
|
-
isHelpFlag,
|
|
7672
|
+
isHelpFlag2 as isHelpFlag,
|
|
7390
7673
|
isVersionFlag,
|
|
7391
7674
|
parseComposeArgs,
|
|
7392
7675
|
parseConfigArgs,
|