@archlast/forge-cli 0.1.0 → 0.2.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/package.json +4 -4
- package/src/index.ts +22 -5
- package/src/stack.ts +10 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@archlast/forge-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The archlast-forge command line: dev, up, build, data and config commands.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"archlast-forge": "./src/index.ts"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@archlast/forge-dashboard": "^0.
|
|
29
|
-
"@archlast/forge-runtime": "^0.
|
|
30
|
-
"create-archlast-forge": "^0.
|
|
28
|
+
"@archlast/forge-dashboard": "^0.2.0",
|
|
29
|
+
"@archlast/forge-runtime": "^0.2.0",
|
|
30
|
+
"create-archlast-forge": "^0.2.0"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
33
33
|
"bun": ">=1.4.3"
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { ops } from "@archlast/forge-runtime/server";
|
|
3
3
|
import { watch } from "node:fs";
|
|
4
4
|
import { basename, join, resolve } from "node:path";
|
|
5
|
-
import { parseArgs } from "node:util";
|
|
5
|
+
import { parseArgs, type ParseArgsConfig } from "node:util";
|
|
6
6
|
import { buildFramework, loadFramework } from "./app";
|
|
7
7
|
import { devSecrets, fail, findForgeDir, generate, trustProxyOrigins, VERSION } from "./common";
|
|
8
8
|
import { ensureDeps, findUp } from "./deps";
|
|
@@ -59,7 +59,16 @@ ${c.bold("Engine")}
|
|
|
59
59
|
|
|
60
60
|
Options: --dir <forge dir> --port <n> --no-docker`;
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
/** A mistyped option is a one-line message, not a stack trace. */
|
|
63
|
+
function parseCommandLine<T extends ParseArgsConfig>(config: T) {
|
|
64
|
+
try {
|
|
65
|
+
return parseArgs(config);
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return fail(`${(e as Error).message.replace(/\.$/, "")}. Run \`archlast-forge --help\` to see the options.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const { positionals, values } = parseCommandLine({
|
|
63
72
|
args: Bun.argv.slice(2),
|
|
64
73
|
allowPositionals: true,
|
|
65
74
|
options: {
|
|
@@ -81,8 +90,13 @@ const { positionals, values } = parseArgs({
|
|
|
81
90
|
rebuild: { type: "boolean" },
|
|
82
91
|
dev: { type: "boolean" },
|
|
83
92
|
help: { type: "boolean", short: "h" },
|
|
93
|
+
version: { type: "boolean", short: "v" },
|
|
84
94
|
},
|
|
85
95
|
});
|
|
96
|
+
if (values.version) {
|
|
97
|
+
console.log(VERSION);
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
86
100
|
const [command, ...rest] = positionals;
|
|
87
101
|
const port = Number(values.port ?? process.env.PORT ?? 3000);
|
|
88
102
|
const inContainer = process.env.ARCHLAST_ENGINE === "1";
|
|
@@ -101,6 +115,7 @@ function devEnv(dir: string) {
|
|
|
101
115
|
env.DATA_DIR ??= join(dir, "data");
|
|
102
116
|
// The app's own address is trusted, and a proxy in front of it (code-server's port forwarding) is worked out from that one.
|
|
103
117
|
env.TRUSTED_ORIGINS ||= `http://localhost:${port}`;
|
|
118
|
+
env.BETTER_AUTH_URL = `http://localhost:${port}`; // a project's .env (made for the stack) must not point sessions at another address
|
|
104
119
|
trustProxyOrigins(env);
|
|
105
120
|
return env;
|
|
106
121
|
}
|
|
@@ -136,7 +151,6 @@ async function devFramework(dir: string, cfg: ProjectConfig) {
|
|
|
136
151
|
const began = performance.now();
|
|
137
152
|
const root = projectRoot(dir);
|
|
138
153
|
const env = devEnv(dir);
|
|
139
|
-
env.BETTER_AUTH_URL = `http://localhost:${port}`; // a stray .env must not point sessions at another port
|
|
140
154
|
await ensureDeps(dir);
|
|
141
155
|
ok(`codegen: ${(await generate(dir)).length} modules`);
|
|
142
156
|
|
|
@@ -153,6 +167,7 @@ async function devFramework(dir: string, cfg: ProjectConfig) {
|
|
|
153
167
|
process.on("exit", dockerEngine.stopStartedServices); // Ctrl-C ends the session, so it ends its services too
|
|
154
168
|
}
|
|
155
169
|
|
|
170
|
+
companion(); // --run "<cmd>"
|
|
156
171
|
const { createApp } = await loadFramework(root, "server");
|
|
157
172
|
const app = await createApp({ root, dev: true });
|
|
158
173
|
const server = await serve({ forgeDir: dir, dev: true, env: { ...env, PORT: String(port) }, app, watchDir: dir, onChange: forgeChanged(dir) });
|
|
@@ -231,7 +246,8 @@ function watchSource(dir: string, onChange: (files: string[]) => Promise<void>)
|
|
|
231
246
|
/** Where data and config commands run: inside the running engine container, or in this process. */
|
|
232
247
|
function engineTarget(dir: string) {
|
|
233
248
|
if (!useDocker()) return null;
|
|
234
|
-
|
|
249
|
+
// The app of a compose stack, then a single engine container.
|
|
250
|
+
return stack.appContainer(projectRoot(dir)) ?? dockerEngine.runningContainer(dir) ?? null;
|
|
235
251
|
}
|
|
236
252
|
|
|
237
253
|
/**
|
|
@@ -350,7 +366,8 @@ const commands: Record<string, () => Promise<unknown>> = {
|
|
|
350
366
|
const root = projectRoot(dir);
|
|
351
367
|
app = await (await loadFramework(root, "server")).createApp({ root, dev: false }).catch((e: Error) => fail(e.message.includes("No production build") ? `${e.message}` : e.message));
|
|
352
368
|
}
|
|
353
|
-
|
|
369
|
+
// Next to forge/, where `dev` keeps it, unless DATA_DIR says otherwise.
|
|
370
|
+
const server = await serve({ forgeDir: dir, dev: false, env: { ...process.env, DATA_DIR: process.env.DATA_DIR ?? join(dir, "data"), PORT: String(port) }, app });
|
|
354
371
|
ok(`archlast-forge on ${server.url}${app ? " (app and API)" : ""}`);
|
|
355
372
|
},
|
|
356
373
|
async stop() {
|
package/src/stack.ts
CHANGED
|
@@ -37,7 +37,16 @@ export function ensureEnv(root: string, services: StackService[]): boolean {
|
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const
|
|
40
|
+
const rawEnvValue = (root: string, key: string) => new RegExp(`^${key}=(.*)$`, "m").exec(existsSync(join(root, ".env")) ? readFileSync(join(root, ".env"), "utf8") : "")?.[1]?.trim();
|
|
41
|
+
/** A value from .env, with `${OTHER}` filled in from the same file the way compose does (BETTER_AUTH_URL=https://${DOMAIN}). */
|
|
42
|
+
const envValue = (root: string, key: string) => rawEnvValue(root, key)?.replace(/\$\{(\w+)\}/g, (_, other) => rawEnvValue(root, other) ?? "");
|
|
43
|
+
|
|
44
|
+
/** The running app container of the project's compose stack, for commands that have to run inside it. */
|
|
45
|
+
export function appContainer(root: string): string | undefined {
|
|
46
|
+
if (!existsSync(composeFile(root)) || !dockerAvailable()) return undefined;
|
|
47
|
+
const p = Bun.spawnSync([DOCKER, "compose", "--project-directory", root, "-f", composeFile(root), "ps", "-q", "app"], { stdout: "pipe", stderr: "ignore", stdin: "ignore", env: { ...process.env, ENGINE_IMAGE: process.env.ENGINE_IMAGE ?? engineImage() } });
|
|
48
|
+
return p.exitCode === 0 ? p.stdout.toString().trim().split("\n")[0] || undefined : undefined;
|
|
49
|
+
}
|
|
41
50
|
|
|
42
51
|
/** Builds and starts the stack, and waits until the app reports healthy. */
|
|
43
52
|
export async function up(root: string, services: StackService[], opts: { rebuild?: boolean } = {}) {
|