@eide/foir-cli 0.26.0 → 0.28.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/dist/cli.js +379 -16
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1215,13 +1215,14 @@ function createAppsMethods(client) {
|
|
|
1215
1215
|
create2(UpdateAppRequestSchema, { tenantId, projectId, name })
|
|
1216
1216
|
);
|
|
1217
1217
|
},
|
|
1218
|
-
async confirmUpdateApp(tenantId, projectId, name, newManifestHash) {
|
|
1218
|
+
async confirmUpdateApp(tenantId, projectId, name, newManifestHash, force = false) {
|
|
1219
1219
|
const resp = await client.confirmUpdateApp(
|
|
1220
1220
|
create2(ConfirmUpdateAppRequestSchema, {
|
|
1221
1221
|
tenantId,
|
|
1222
1222
|
projectId,
|
|
1223
1223
|
name,
|
|
1224
|
-
newManifestHash
|
|
1224
|
+
newManifestHash,
|
|
1225
|
+
force
|
|
1225
1226
|
})
|
|
1226
1227
|
);
|
|
1227
1228
|
return resp.app;
|
|
@@ -9360,10 +9361,167 @@ function registerConfigsCommands(program2, globalOpts) {
|
|
|
9360
9361
|
}
|
|
9361
9362
|
|
|
9362
9363
|
// src/commands/apps.ts
|
|
9364
|
+
import chalk17 from "chalk";
|
|
9365
|
+
import { spawn as spawn2 } from "child_process";
|
|
9366
|
+
import { existsSync as existsSync7, watch as fsWatch } from "fs";
|
|
9367
|
+
import { resolve as resolvePath } from "path";
|
|
9363
9368
|
import {
|
|
9364
9369
|
AppSchema,
|
|
9365
9370
|
ValidateManifestResponseSchema
|
|
9366
9371
|
} from "@eide/foir-proto-ts/apps/v1/apps_service_pb";
|
|
9372
|
+
|
|
9373
|
+
// src/lib/tunnel.ts
|
|
9374
|
+
import { spawn } from "child_process";
|
|
9375
|
+
import { once } from "events";
|
|
9376
|
+
import chalk16 from "chalk";
|
|
9377
|
+
async function startTunnel(opts) {
|
|
9378
|
+
const host = opts.host ?? "localhost";
|
|
9379
|
+
if (opts.kind === "cloudflared") return startCloudflared(host, opts.port, opts.logs ?? true);
|
|
9380
|
+
if (opts.kind === "ngrok") return startNgrok(opts.port, opts.logs ?? true);
|
|
9381
|
+
throw new Error(`Unknown tunnel kind: ${opts.kind}`);
|
|
9382
|
+
}
|
|
9383
|
+
var CLOUDFLARED_URL_RE = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
|
|
9384
|
+
function startCloudflared(host, port, logs) {
|
|
9385
|
+
return new Promise((resolve8, reject) => {
|
|
9386
|
+
let child;
|
|
9387
|
+
try {
|
|
9388
|
+
child = spawn(
|
|
9389
|
+
"cloudflared",
|
|
9390
|
+
[
|
|
9391
|
+
"tunnel",
|
|
9392
|
+
"--no-autoupdate",
|
|
9393
|
+
// Ignore ~/.cloudflared/config.yaml if present. Without this, a
|
|
9394
|
+
// user's existing named-tunnel ingress takes precedence over --url
|
|
9395
|
+
// and every quick-tunnel request falls through to the catch-all
|
|
9396
|
+
// 404. (Spent half a day diagnosing this once — don't relive it.)
|
|
9397
|
+
"--config",
|
|
9398
|
+
"/dev/null",
|
|
9399
|
+
"--url",
|
|
9400
|
+
`http://${host}:${port}`
|
|
9401
|
+
],
|
|
9402
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
9403
|
+
);
|
|
9404
|
+
} catch (err) {
|
|
9405
|
+
reject(new Error(`Failed to spawn cloudflared: ${err.message}`));
|
|
9406
|
+
return;
|
|
9407
|
+
}
|
|
9408
|
+
let resolved = false;
|
|
9409
|
+
let buffered = "";
|
|
9410
|
+
const onData = (chunk) => {
|
|
9411
|
+
const text = chunk.toString("utf-8");
|
|
9412
|
+
if (logs) process.stderr.write(chalk16.dim(text));
|
|
9413
|
+
buffered += text;
|
|
9414
|
+
const match = buffered.match(CLOUDFLARED_URL_RE);
|
|
9415
|
+
if (match && !resolved) {
|
|
9416
|
+
resolved = true;
|
|
9417
|
+
const url = match[0];
|
|
9418
|
+
const exited = waitForExit(child);
|
|
9419
|
+
resolve8({
|
|
9420
|
+
url,
|
|
9421
|
+
exited,
|
|
9422
|
+
stop: () => stopChild(child)
|
|
9423
|
+
});
|
|
9424
|
+
}
|
|
9425
|
+
};
|
|
9426
|
+
child.stdout.on("data", onData);
|
|
9427
|
+
child.stderr.on("data", onData);
|
|
9428
|
+
child.once("error", (err) => {
|
|
9429
|
+
if (resolved) return;
|
|
9430
|
+
const msg = err.code === "ENOENT" ? "cloudflared not found on PATH. Install: `brew install cloudflared` or https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/" : `cloudflared failed to start: ${err.message}`;
|
|
9431
|
+
reject(new Error(msg));
|
|
9432
|
+
});
|
|
9433
|
+
child.once("exit", (code) => {
|
|
9434
|
+
if (!resolved) {
|
|
9435
|
+
reject(new Error(`cloudflared exited (code ${code ?? "null"}) before a tunnel URL appeared. Output:
|
|
9436
|
+
${buffered}`));
|
|
9437
|
+
}
|
|
9438
|
+
});
|
|
9439
|
+
setTimeout(() => {
|
|
9440
|
+
if (!resolved) {
|
|
9441
|
+
resolved = true;
|
|
9442
|
+
void stopChild(child).catch(() => void 0);
|
|
9443
|
+
reject(new Error("Timed out waiting for cloudflared to print a tunnel URL."));
|
|
9444
|
+
}
|
|
9445
|
+
}, 3e4).unref();
|
|
9446
|
+
});
|
|
9447
|
+
}
|
|
9448
|
+
function startNgrok(port, logs) {
|
|
9449
|
+
return new Promise((resolve8, reject) => {
|
|
9450
|
+
let child;
|
|
9451
|
+
try {
|
|
9452
|
+
child = spawn("ngrok", ["http", String(port), "--log=stdout"], {
|
|
9453
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
9454
|
+
});
|
|
9455
|
+
} catch (err) {
|
|
9456
|
+
reject(new Error(`Failed to spawn ngrok: ${err.message}`));
|
|
9457
|
+
return;
|
|
9458
|
+
}
|
|
9459
|
+
if (logs) {
|
|
9460
|
+
child.stdout.on("data", (c) => process.stderr.write(chalk16.dim(c.toString("utf-8"))));
|
|
9461
|
+
child.stderr.on("data", (c) => process.stderr.write(chalk16.dim(c.toString("utf-8"))));
|
|
9462
|
+
}
|
|
9463
|
+
let resolved = false;
|
|
9464
|
+
child.once("error", (err) => {
|
|
9465
|
+
if (resolved) return;
|
|
9466
|
+
resolved = true;
|
|
9467
|
+
const msg = err.code === "ENOENT" ? "ngrok not found on PATH. Install: `brew install ngrok` or https://ngrok.com/download" : `ngrok failed to start: ${err.message}`;
|
|
9468
|
+
reject(new Error(msg));
|
|
9469
|
+
});
|
|
9470
|
+
child.once("exit", (code) => {
|
|
9471
|
+
if (resolved) return;
|
|
9472
|
+
resolved = true;
|
|
9473
|
+
reject(new Error(`ngrok exited (code ${code ?? "null"}) before a tunnel URL was available.`));
|
|
9474
|
+
});
|
|
9475
|
+
const deadline = Date.now() + 3e4;
|
|
9476
|
+
const poll = async () => {
|
|
9477
|
+
while (!resolved && Date.now() < deadline) {
|
|
9478
|
+
try {
|
|
9479
|
+
const resp = await fetch("http://127.0.0.1:4040/api/tunnels");
|
|
9480
|
+
if (resp.ok) {
|
|
9481
|
+
const data = await resp.json();
|
|
9482
|
+
const https = data.tunnels.find((t) => t.public_url.startsWith("https://"));
|
|
9483
|
+
if (https) {
|
|
9484
|
+
resolved = true;
|
|
9485
|
+
resolve8({
|
|
9486
|
+
url: https.public_url,
|
|
9487
|
+
exited: waitForExit(child),
|
|
9488
|
+
stop: () => stopChild(child)
|
|
9489
|
+
});
|
|
9490
|
+
return;
|
|
9491
|
+
}
|
|
9492
|
+
}
|
|
9493
|
+
} catch {
|
|
9494
|
+
}
|
|
9495
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
9496
|
+
}
|
|
9497
|
+
if (!resolved) {
|
|
9498
|
+
resolved = true;
|
|
9499
|
+
void stopChild(child).catch(() => void 0);
|
|
9500
|
+
reject(new Error("Timed out waiting for ngrok local API at http://127.0.0.1:4040."));
|
|
9501
|
+
}
|
|
9502
|
+
};
|
|
9503
|
+
void poll();
|
|
9504
|
+
});
|
|
9505
|
+
}
|
|
9506
|
+
function waitForExit(child) {
|
|
9507
|
+
return once(child, "exit").then(([code]) => code ?? null);
|
|
9508
|
+
}
|
|
9509
|
+
async function stopChild(child) {
|
|
9510
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
9511
|
+
child.kill("SIGTERM");
|
|
9512
|
+
const killed = await Promise.race([
|
|
9513
|
+
once(child, "exit").then(() => true),
|
|
9514
|
+
new Promise((r) => setTimeout(() => r(false), 3e3))
|
|
9515
|
+
]);
|
|
9516
|
+
if (!killed && child.exitCode === null) {
|
|
9517
|
+
child.kill("SIGKILL");
|
|
9518
|
+
await once(child, "exit").catch(() => void 0);
|
|
9519
|
+
}
|
|
9520
|
+
}
|
|
9521
|
+
|
|
9522
|
+
// src/commands/apps.ts
|
|
9523
|
+
var FOIR_APPS_HOST_ENV = "FOIR_APPS_HOST";
|
|
9524
|
+
var WATCH_FILES = ["foir.config.ts", "foir.config.js", "foir.config.mjs", "foir.config.json"];
|
|
9367
9525
|
function registerAppsCommands(program2, globalOpts) {
|
|
9368
9526
|
const apps = program2.command("apps").description("Install and manage apps");
|
|
9369
9527
|
apps.command("list").description("List installed apps").action(
|
|
@@ -9479,7 +9637,10 @@ function registerAppsCommands(program2, globalOpts) {
|
|
|
9479
9637
|
}
|
|
9480
9638
|
)
|
|
9481
9639
|
);
|
|
9482
|
-
apps.command("update <name>").description("Check for updates and apply if no rejected changes").option("--dry-run", "Show the diff without applying").
|
|
9640
|
+
apps.command("update <name>").description("Check for updates and apply if no rejected changes").option("--dry-run", "Show the diff without applying").option(
|
|
9641
|
+
"--force",
|
|
9642
|
+
"Apply even when the diff contains REJECTED changes (admin override; printed warning lists what got bypassed)"
|
|
9643
|
+
).action(
|
|
9483
9644
|
withErrorHandler(
|
|
9484
9645
|
globalOpts,
|
|
9485
9646
|
async (name, cmdOpts) => {
|
|
@@ -9501,18 +9662,23 @@ function registerAppsCommands(program2, globalOpts) {
|
|
|
9501
9662
|
console.error(`[${classLabel}] ${change.path}: ${change.description}`);
|
|
9502
9663
|
}
|
|
9503
9664
|
}
|
|
9665
|
+
const force = !!cmdOpts.force;
|
|
9504
9666
|
const hasRejected = updateResp.changes.some((c) => c.class === 3);
|
|
9505
|
-
if (hasRejected) {
|
|
9667
|
+
if (hasRejected && !force) {
|
|
9506
9668
|
throw new Error(
|
|
9507
|
-
"update rejected: one or more changes require admin resolution;
|
|
9669
|
+
"update rejected: one or more changes require admin resolution; rerun with --force to override"
|
|
9508
9670
|
);
|
|
9509
9671
|
}
|
|
9672
|
+
if (hasRejected && !opts.quiet) {
|
|
9673
|
+
warn("forcing through REJECTED changes \u2014 admin override");
|
|
9674
|
+
}
|
|
9510
9675
|
if (cmdOpts.dryRun) return;
|
|
9511
9676
|
const app = await client.apps.confirmUpdateApp(
|
|
9512
9677
|
resolved.project.tenantId,
|
|
9513
9678
|
resolved.project.id,
|
|
9514
9679
|
name,
|
|
9515
|
-
updateResp.newManifestHash
|
|
9680
|
+
updateResp.newManifestHash,
|
|
9681
|
+
force
|
|
9516
9682
|
);
|
|
9517
9683
|
if (opts.json) {
|
|
9518
9684
|
formatOutputProto(AppSchema, app, opts);
|
|
@@ -9587,6 +9753,203 @@ function registerAppsCommands(program2, globalOpts) {
|
|
|
9587
9753
|
throw new Error(`manifest has ${resp.issues.length} issue(s)`);
|
|
9588
9754
|
})
|
|
9589
9755
|
);
|
|
9756
|
+
apps.command("dev").description(
|
|
9757
|
+
"Spin up a public tunnel to a local app dev server so foir admin can iframe it."
|
|
9758
|
+
).option("-p, --port <port>", "Local port serving the app (default 8787 \u2014 wrangler default)", "8787").option(
|
|
9759
|
+
"--host <host>",
|
|
9760
|
+
"Local host to forward to. Use 127.0.0.1 or [::1] when the dev server binds to only one IP family.",
|
|
9761
|
+
"localhost"
|
|
9762
|
+
).option(
|
|
9763
|
+
"-t, --tunnel <kind>",
|
|
9764
|
+
"Tunnel provider: cloudflared (default), ngrok, or none (use --url)",
|
|
9765
|
+
"cloudflared"
|
|
9766
|
+
).option("--url <url>", "BYO tunnel URL; pairs with --tunnel none").option("--push", "Run `foir push` once after the tunnel comes up, with FOIR_APPS_HOST set").option("--watch", "Re-run `foir push` on foir.config.* changes (implies --push)").action(
|
|
9767
|
+
withErrorHandler(
|
|
9768
|
+
globalOpts,
|
|
9769
|
+
async (cmdOpts) => {
|
|
9770
|
+
const opts = globalOpts();
|
|
9771
|
+
const port = Number(cmdOpts.port);
|
|
9772
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
9773
|
+
throw new Error(`--port must be an integer between 1 and 65535 (got "${cmdOpts.port}")`);
|
|
9774
|
+
}
|
|
9775
|
+
const kind = cmdOpts.tunnel;
|
|
9776
|
+
if (kind !== "cloudflared" && kind !== "ngrok" && kind !== "none") {
|
|
9777
|
+
throw new Error(`--tunnel must be one of: cloudflared, ngrok, none (got "${kind}")`);
|
|
9778
|
+
}
|
|
9779
|
+
const doPush = !!cmdOpts.push || !!cmdOpts.watch;
|
|
9780
|
+
const doWatch = !!cmdOpts.watch;
|
|
9781
|
+
if (kind === "none") {
|
|
9782
|
+
if (!cmdOpts.url) {
|
|
9783
|
+
throw new Error("--tunnel none requires --url <public-https-url>");
|
|
9784
|
+
}
|
|
9785
|
+
if (!/^https:\/\//.test(cmdOpts.url)) {
|
|
9786
|
+
throw new Error("--url must be an https:// URL");
|
|
9787
|
+
}
|
|
9788
|
+
await runDevSession({
|
|
9789
|
+
publicUrl: cmdOpts.url,
|
|
9790
|
+
port,
|
|
9791
|
+
kind: "byo",
|
|
9792
|
+
json: !!opts.json
|
|
9793
|
+
});
|
|
9794
|
+
if (doPush) {
|
|
9795
|
+
await runPush(hostOf(cmdOpts.url), !!opts.json);
|
|
9796
|
+
}
|
|
9797
|
+
if (doWatch) {
|
|
9798
|
+
await runWatchLoop(hostOf(cmdOpts.url), !!opts.json);
|
|
9799
|
+
}
|
|
9800
|
+
return;
|
|
9801
|
+
}
|
|
9802
|
+
if (!opts.json) {
|
|
9803
|
+
console.error(chalk17.dim(`Starting ${kind} tunnel \u2192 http://${cmdOpts.host}:${port}\u2026`));
|
|
9804
|
+
}
|
|
9805
|
+
const handle = await startTunnel({
|
|
9806
|
+
kind,
|
|
9807
|
+
port,
|
|
9808
|
+
host: cmdOpts.host,
|
|
9809
|
+
logs: !opts.json
|
|
9810
|
+
});
|
|
9811
|
+
let stopped = false;
|
|
9812
|
+
let watcher = null;
|
|
9813
|
+
const stop = async () => {
|
|
9814
|
+
if (stopped) return;
|
|
9815
|
+
stopped = true;
|
|
9816
|
+
watcher?.close();
|
|
9817
|
+
await handle.stop();
|
|
9818
|
+
};
|
|
9819
|
+
process.once("SIGINT", () => {
|
|
9820
|
+
void stop().finally(() => process.exit(0));
|
|
9821
|
+
});
|
|
9822
|
+
process.once("SIGTERM", () => {
|
|
9823
|
+
void stop().finally(() => process.exit(0));
|
|
9824
|
+
});
|
|
9825
|
+
await runDevSession({
|
|
9826
|
+
publicUrl: handle.url,
|
|
9827
|
+
port,
|
|
9828
|
+
localHost: cmdOpts.host,
|
|
9829
|
+
kind,
|
|
9830
|
+
json: !!opts.json
|
|
9831
|
+
});
|
|
9832
|
+
const host = hostOf(handle.url);
|
|
9833
|
+
if (doPush) {
|
|
9834
|
+
await runPush(host, !!opts.json);
|
|
9835
|
+
}
|
|
9836
|
+
if (doWatch) {
|
|
9837
|
+
watcher = startWatchLoop(host, !!opts.json);
|
|
9838
|
+
}
|
|
9839
|
+
await handle.exited;
|
|
9840
|
+
watcher?.close();
|
|
9841
|
+
}
|
|
9842
|
+
)
|
|
9843
|
+
);
|
|
9844
|
+
}
|
|
9845
|
+
function runDevSession(info) {
|
|
9846
|
+
if (info.json) {
|
|
9847
|
+
formatOutput(
|
|
9848
|
+
{
|
|
9849
|
+
tunnel: info.kind,
|
|
9850
|
+
port: info.port,
|
|
9851
|
+
publicUrl: info.publicUrl
|
|
9852
|
+
},
|
|
9853
|
+
{ json: true }
|
|
9854
|
+
);
|
|
9855
|
+
return Promise.resolve();
|
|
9856
|
+
}
|
|
9857
|
+
const host = hostOf(info.publicUrl);
|
|
9858
|
+
console.log();
|
|
9859
|
+
console.log(chalk17.bold.green(" Tunnel ready"));
|
|
9860
|
+
console.log(` ${chalk17.bold(info.publicUrl)} ${chalk17.dim("\u2192")} http://${info.localHost ?? "localhost"}:${info.port}`);
|
|
9861
|
+
console.log();
|
|
9862
|
+
console.log(chalk17.dim(" Point `foir push` at the tunnel so app manifests resolve here."));
|
|
9863
|
+
console.log(chalk17.dim(" If your foir.config.ts uses an env-gated source (recommended):"));
|
|
9864
|
+
console.log(chalk17.dim(` FOIR_APPS_HOST=${host} foir push`));
|
|
9865
|
+
console.log(chalk17.dim(" Otherwise: update apps.<name>.source in foir.config.ts to a URL on"));
|
|
9866
|
+
console.log(chalk17.dim(` ${host} and run foir push.`));
|
|
9867
|
+
console.log();
|
|
9868
|
+
if (info.kind === "byo") {
|
|
9869
|
+
console.log(chalk17.dim(" (BYO tunnel \u2014 leaving lifecycle to you.)"));
|
|
9870
|
+
return Promise.resolve();
|
|
9871
|
+
}
|
|
9872
|
+
console.log(chalk17.dim(" Press Ctrl+C to stop the tunnel."));
|
|
9873
|
+
return Promise.resolve();
|
|
9874
|
+
}
|
|
9875
|
+
function hostOf(url) {
|
|
9876
|
+
try {
|
|
9877
|
+
return new URL(url).host;
|
|
9878
|
+
} catch {
|
|
9879
|
+
return url;
|
|
9880
|
+
}
|
|
9881
|
+
}
|
|
9882
|
+
function runPush(host, jsonMode) {
|
|
9883
|
+
return new Promise((resolve8, reject) => {
|
|
9884
|
+
if (!jsonMode) {
|
|
9885
|
+
console.log();
|
|
9886
|
+
console.log(chalk17.dim(`\u2192 foir push (${FOIR_APPS_HOST_ENV}=${host})`));
|
|
9887
|
+
}
|
|
9888
|
+
const entry = process.argv[1];
|
|
9889
|
+
if (!entry) {
|
|
9890
|
+
reject(new Error("Cannot locate foir CLI entry from process.argv; --push/--watch unavailable."));
|
|
9891
|
+
return;
|
|
9892
|
+
}
|
|
9893
|
+
const child = spawn2(process.execPath, [entry, "push"], {
|
|
9894
|
+
stdio: "inherit",
|
|
9895
|
+
env: { ...process.env, [FOIR_APPS_HOST_ENV]: host }
|
|
9896
|
+
});
|
|
9897
|
+
child.once("error", (err) => reject(err));
|
|
9898
|
+
child.once("exit", (code) => {
|
|
9899
|
+
if (code === 0) resolve8();
|
|
9900
|
+
else reject(new Error(`foir push exited with code ${code ?? "null"}`));
|
|
9901
|
+
});
|
|
9902
|
+
});
|
|
9903
|
+
}
|
|
9904
|
+
function startWatchLoop(host, jsonMode) {
|
|
9905
|
+
const cwd = process.cwd();
|
|
9906
|
+
const target = WATCH_FILES.map((n) => resolvePath(cwd, n)).find((p) => existsSync7(p));
|
|
9907
|
+
if (!target) {
|
|
9908
|
+
if (!jsonMode) {
|
|
9909
|
+
console.error(chalk17.yellow(`! --watch: no foir.config.* in ${cwd}; skipping watch loop.`));
|
|
9910
|
+
}
|
|
9911
|
+
return null;
|
|
9912
|
+
}
|
|
9913
|
+
if (!jsonMode) {
|
|
9914
|
+
console.log(chalk17.dim(`Watching ${target} \u2014 saving triggers foir push.`));
|
|
9915
|
+
}
|
|
9916
|
+
let pending = false;
|
|
9917
|
+
let running = false;
|
|
9918
|
+
let timer = null;
|
|
9919
|
+
const tick = () => {
|
|
9920
|
+
if (running) {
|
|
9921
|
+
pending = true;
|
|
9922
|
+
return;
|
|
9923
|
+
}
|
|
9924
|
+
running = true;
|
|
9925
|
+
pending = false;
|
|
9926
|
+
runPush(host, jsonMode).catch((err) => {
|
|
9927
|
+
if (!jsonMode) console.error(chalk17.red(`push failed: ${err.message}`));
|
|
9928
|
+
}).finally(() => {
|
|
9929
|
+
running = false;
|
|
9930
|
+
if (pending) tick();
|
|
9931
|
+
});
|
|
9932
|
+
};
|
|
9933
|
+
const watcher = fsWatch(target, { persistent: true }, () => {
|
|
9934
|
+
if (timer) clearTimeout(timer);
|
|
9935
|
+
timer = setTimeout(tick, 200);
|
|
9936
|
+
});
|
|
9937
|
+
return watcher;
|
|
9938
|
+
}
|
|
9939
|
+
function runWatchLoop(host, jsonMode) {
|
|
9940
|
+
return new Promise((resolve8) => {
|
|
9941
|
+
const watcher = startWatchLoop(host, jsonMode);
|
|
9942
|
+
if (!watcher) {
|
|
9943
|
+
resolve8();
|
|
9944
|
+
return;
|
|
9945
|
+
}
|
|
9946
|
+
const close = () => {
|
|
9947
|
+
watcher.close();
|
|
9948
|
+
resolve8();
|
|
9949
|
+
};
|
|
9950
|
+
process.once("SIGINT", close);
|
|
9951
|
+
process.once("SIGTERM", close);
|
|
9952
|
+
});
|
|
9590
9953
|
}
|
|
9591
9954
|
async function requireProject(opts) {
|
|
9592
9955
|
const resolved = await resolveProjectContext(opts);
|
|
@@ -9619,9 +9982,9 @@ function classToLabel(n) {
|
|
|
9619
9982
|
}
|
|
9620
9983
|
|
|
9621
9984
|
// src/commands/secrets.ts
|
|
9622
|
-
import { existsSync as
|
|
9985
|
+
import { existsSync as existsSync8 } from "fs";
|
|
9623
9986
|
import { promises as fs5 } from "fs";
|
|
9624
|
-
import { resolve as
|
|
9987
|
+
import { resolve as resolvePath2 } from "path";
|
|
9625
9988
|
function registerSecretsCommands(program2, globalOpts) {
|
|
9626
9989
|
const secrets = program2.command("secrets").description("Manage vault secrets");
|
|
9627
9990
|
secrets.command("put").description("Store a new secret and print its ref").option("--label <label>", "Optional human-readable label").option("--app <name>", "Owner: app name (defaults to project-owned)").option("--file <path>", "Read plaintext from file (binary-safe)").option("--value <plaintext>", "Plaintext value (string only; prefer --file for binary)").action(
|
|
@@ -9919,14 +10282,14 @@ var PLAINTEXT_CONFIG_NAMES = [
|
|
|
9919
10282
|
];
|
|
9920
10283
|
async function resolveSecretsConfigPath(explicit) {
|
|
9921
10284
|
if (explicit) {
|
|
9922
|
-
if (!
|
|
10285
|
+
if (!existsSync8(explicit)) {
|
|
9923
10286
|
throw new Error(`Secrets config not found: ${explicit}`);
|
|
9924
10287
|
}
|
|
9925
|
-
return
|
|
10288
|
+
return resolvePath2(explicit);
|
|
9926
10289
|
}
|
|
9927
10290
|
for (const name of SECRETS_CONFIG_NAMES) {
|
|
9928
|
-
const path3 =
|
|
9929
|
-
if (
|
|
10291
|
+
const path3 = resolvePath2(process.cwd(), name);
|
|
10292
|
+
if (existsSync8(path3)) return path3;
|
|
9930
10293
|
}
|
|
9931
10294
|
throw new Error(
|
|
9932
10295
|
`No secrets config found. Looked for: ${SECRETS_CONFIG_NAMES.join(", ")}.`
|
|
@@ -9934,14 +10297,14 @@ async function resolveSecretsConfigPath(explicit) {
|
|
|
9934
10297
|
}
|
|
9935
10298
|
async function resolvePlaintextPath(explicit) {
|
|
9936
10299
|
if (explicit) {
|
|
9937
|
-
if (!
|
|
10300
|
+
if (!existsSync8(explicit)) {
|
|
9938
10301
|
throw new Error(`Plaintext file not found: ${explicit}`);
|
|
9939
10302
|
}
|
|
9940
|
-
return
|
|
10303
|
+
return resolvePath2(explicit);
|
|
9941
10304
|
}
|
|
9942
10305
|
for (const name of PLAINTEXT_CONFIG_NAMES) {
|
|
9943
|
-
const path3 =
|
|
9944
|
-
if (
|
|
10306
|
+
const path3 = resolvePath2(process.cwd(), name);
|
|
10307
|
+
if (existsSync8(path3)) return path3;
|
|
9945
10308
|
}
|
|
9946
10309
|
return null;
|
|
9947
10310
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eide/foir-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Universal platform CLI for Foir platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@bufbuild/protovalidate": "^1.1.1",
|
|
51
51
|
"@connectrpc/connect": "^2.0.0",
|
|
52
52
|
"@connectrpc/connect-node": "^2.0.0",
|
|
53
|
-
"@eide/foir-proto-ts": "^0.
|
|
53
|
+
"@eide/foir-proto-ts": "^0.71.0",
|
|
54
54
|
"chalk": "^5.3.0",
|
|
55
55
|
"commander": "^12.1.0",
|
|
56
56
|
"dotenv": "^16.4.5",
|