@alexkroman1/aai-cli 0.10.3 → 0.10.4
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/_api-client-Br1a_Dsp.mjs +36 -0
- package/dist/_bundler-BM-vnJUp.mjs +117 -0
- package/dist/{_discover-CbHCotwB.mjs → _discover-DiRl7b_K.mjs} +46 -27
- package/dist/_init-CVkKf8t_.mjs +56 -0
- package/dist/_templates-s5MYfWS9.mjs +63 -0
- package/dist/_ui-Cu-v_Bzz.mjs +13 -0
- package/dist/cli.mjs +49 -191
- package/dist/delete-CSPF9vdh.mjs +34 -0
- package/dist/deploy-C2s4C_ff.mjs +92 -0
- package/dist/dev-CBKWhwaQ.mjs +20 -0
- package/dist/{generate-Di_Shw-X.mjs → generate-DQt7C4zz.mjs} +33 -38
- package/dist/init-BAYELcXE.mjs +112 -0
- package/dist/secret-CRLbxN5P.mjs +44 -0
- package/dist/{test-9fFn9DCf.mjs → test-CuMTKw38.mjs} +8 -10
- package/package.json +8 -13
- package/dist/_build-CElA0PJB.mjs +0 -176
- package/dist/_init-CQ6Cw4k-.mjs +0 -108
- package/dist/_link-D6mmDQ0B.mjs +0 -49
- package/dist/_server-common-DekUfYGG.mjs +0 -105
- package/dist/_ui-C9IvR7Fh.mjs +0 -50
- package/dist/delete-DYUD8j8f.mjs +0 -40
- package/dist/deploy-BWF0H8UK.mjs +0 -90
- package/dist/dev-nCt6xqmm.mjs +0 -60
- package/dist/doctor-Dvz0rdWo.mjs +0 -215
- package/dist/init-Pa-YbNnJ.mjs +0 -61
- package/dist/rag-qtN4p79Q.mjs +0 -178
- package/dist/secret-BkN22eT1.mjs +0 -50
- package/dist/start-BDAtcDr3.mjs +0 -23
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
//#region _api-client.ts
|
|
3
|
+
/**
|
|
4
|
+
* Shared HTTP helpers for platform API calls (deploy, delete, secrets).
|
|
5
|
+
*/
|
|
6
|
+
const HINT_INVALID_API_KEY = "Your API key may be invalid. Check ~/.config/aai/config.json or set ASSEMBLYAI_API_KEY.";
|
|
7
|
+
/**
|
|
8
|
+
* Send an authenticated request to the platform API.
|
|
9
|
+
*
|
|
10
|
+
* Adds the `Authorization` header and, on network failure, throws with a
|
|
11
|
+
* contextual hint (localhost → "is the dev server running?", remote →
|
|
12
|
+
* "check your network connection").
|
|
13
|
+
*/
|
|
14
|
+
async function apiRequest(url, init, fetchFn = globalThis.fetch.bind(globalThis)) {
|
|
15
|
+
const { apiKey, action, ...rest } = init;
|
|
16
|
+
const headers = {
|
|
17
|
+
Authorization: `Bearer ${apiKey}`,
|
|
18
|
+
...rest.body ? { "Content-Type": "application/json" } : {},
|
|
19
|
+
...rest.headers
|
|
20
|
+
};
|
|
21
|
+
try {
|
|
22
|
+
return await fetchFn(url, {
|
|
23
|
+
...rest,
|
|
24
|
+
headers
|
|
25
|
+
});
|
|
26
|
+
} catch (err) {
|
|
27
|
+
const hint = url.startsWith("http://localhost") ? "Is the local dev server running? Start it with `aai dev`." : "Check your network connection and verify the server URL is correct.";
|
|
28
|
+
throw new Error(`${action} failed: could not reach ${url}\n ${hint}`, { cause: err });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Format a non-ok API response into a descriptive error. */
|
|
32
|
+
function apiError(action, status, body, hint) {
|
|
33
|
+
return /* @__PURE__ */ new Error(`${action} failed (HTTP ${status}): ${body}${hint ? `\n ${hint}` : ""}`);
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { apiError as n, apiRequest as r, HINT_INVALID_API_KEY as t };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import { errorMessage } from "@alexkroman1/aai/utils";
|
|
5
|
+
import { build } from "vite";
|
|
6
|
+
//#region _bundler.ts
|
|
7
|
+
var BundleError = class extends Error {
|
|
8
|
+
constructor(message, options) {
|
|
9
|
+
super(message, options);
|
|
10
|
+
this.name = "BundleError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const TEXT_EXTENSIONS = new Set([
|
|
14
|
+
".html",
|
|
15
|
+
".htm",
|
|
16
|
+
".css",
|
|
17
|
+
".js",
|
|
18
|
+
".mjs",
|
|
19
|
+
".cjs",
|
|
20
|
+
".ts",
|
|
21
|
+
".mts",
|
|
22
|
+
".json",
|
|
23
|
+
".map",
|
|
24
|
+
".svg",
|
|
25
|
+
".xml",
|
|
26
|
+
".txt",
|
|
27
|
+
".md"
|
|
28
|
+
]);
|
|
29
|
+
async function readDirFiles(dir) {
|
|
30
|
+
let entries;
|
|
31
|
+
try {
|
|
32
|
+
entries = await fs.readdir(dir, {
|
|
33
|
+
recursive: true,
|
|
34
|
+
withFileTypes: true
|
|
35
|
+
});
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") return {};
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
const files = {};
|
|
41
|
+
await Promise.all(entries.filter((e) => e.isFile()).map(async (e) => {
|
|
42
|
+
const full = path.join(e.parentPath, e.name);
|
|
43
|
+
const ext = path.extname(e.name).toLowerCase();
|
|
44
|
+
const rel = path.relative(dir, full);
|
|
45
|
+
if (TEXT_EXTENSIONS.has(ext)) files[rel] = await fs.readFile(full, "utf-8");
|
|
46
|
+
else files[rel] = `base64:${(await fs.readFile(full)).toString("base64")}`;
|
|
47
|
+
}));
|
|
48
|
+
return files;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Bundle an agent project using Vite.
|
|
52
|
+
*
|
|
53
|
+
* - Worker: `vite build --ssr agent.ts` (uses project's vite.config.ts)
|
|
54
|
+
* - Client: `vite build` (uses project's vite.config.ts)
|
|
55
|
+
*/
|
|
56
|
+
async function bundleAgent(agent, opts) {
|
|
57
|
+
const aaiDir = path.join(agent.dir, ".aai");
|
|
58
|
+
const buildDir = path.join(aaiDir, "build");
|
|
59
|
+
const clientDir = path.join(aaiDir, "client");
|
|
60
|
+
try {
|
|
61
|
+
await build({
|
|
62
|
+
root: agent.dir,
|
|
63
|
+
logLevel: "warn",
|
|
64
|
+
build: {
|
|
65
|
+
ssr: path.join(agent.dir, "agent.ts"),
|
|
66
|
+
outDir: buildDir,
|
|
67
|
+
emptyOutDir: true,
|
|
68
|
+
rollupOptions: { output: { entryFileNames: "worker.js" } }
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
} catch (err) {
|
|
72
|
+
throw new BundleError(errorMessage(err), { cause: err });
|
|
73
|
+
}
|
|
74
|
+
if (!(opts?.skipClient ?? !agent.clientEntry)) try {
|
|
75
|
+
await build({
|
|
76
|
+
root: agent.dir,
|
|
77
|
+
base: "./",
|
|
78
|
+
logLevel: "warn",
|
|
79
|
+
build: {
|
|
80
|
+
outDir: clientDir,
|
|
81
|
+
emptyOutDir: true
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
} catch (err) {
|
|
85
|
+
throw new BundleError(errorMessage(err), { cause: err });
|
|
86
|
+
}
|
|
87
|
+
const worker = await fs.readFile(path.join(buildDir, "worker.js"), "utf-8");
|
|
88
|
+
return {
|
|
89
|
+
worker,
|
|
90
|
+
clientFiles: await readDirFiles(clientDir),
|
|
91
|
+
clientDir,
|
|
92
|
+
workerBytes: Buffer.byteLength(worker)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function buildAgentBundle(cwd) {
|
|
96
|
+
const { loadAgent } = await import("./_discover-DiRl7b_K.mjs").then((n) => n.t);
|
|
97
|
+
const { consola } = await import("./_ui-Cu-v_Bzz.mjs");
|
|
98
|
+
const agent = await loadAgent(cwd);
|
|
99
|
+
if (!agent) throw new Error("No agent found — run `aai init` first");
|
|
100
|
+
consola.start(`Bundle ${agent.slug}`);
|
|
101
|
+
let bundle;
|
|
102
|
+
try {
|
|
103
|
+
bundle = await bundleAgent(agent);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (err instanceof BundleError) throw new Error(`Bundle failed: ${err.message}`, { cause: err });
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
consola.log(`worker: ${(bundle.workerBytes / 1024).toFixed(1)} KB, client: ${Object.keys(bundle.clientFiles).length} file(s)`);
|
|
109
|
+
return bundle;
|
|
110
|
+
}
|
|
111
|
+
async function runBuildCommand(cwd) {
|
|
112
|
+
const { consola } = await import("./_ui-Cu-v_Bzz.mjs");
|
|
113
|
+
await buildAgentBundle(cwd);
|
|
114
|
+
consola.success("Build ok");
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { buildAgentBundle, runBuildCommand };
|
|
@@ -2,13 +2,24 @@
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import fs
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
6
|
import { consola } from "consola";
|
|
7
7
|
import { humanId } from "human-id";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { createInterface } from "node:readline";
|
|
10
|
+
//#region \0rolldown/runtime.js
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __exportAll = (all, no_symbols) => {
|
|
13
|
+
let target = {};
|
|
14
|
+
for (var name in all) __defProp(target, name, {
|
|
15
|
+
get: all[name],
|
|
16
|
+
enumerable: true
|
|
17
|
+
});
|
|
18
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
19
|
+
return target;
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
10
22
|
//#region _prompts.ts
|
|
11
|
-
const consola$1 = consola.create({ formatOptions: { date: false } });
|
|
12
23
|
/**
|
|
13
24
|
* Prompt the user for a password (masked input).
|
|
14
25
|
* Returns the entered string.
|
|
@@ -47,20 +58,23 @@ function readMasked(stdin) {
|
|
|
47
58
|
stdin.on("data", onData);
|
|
48
59
|
});
|
|
49
60
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Prompt the user for text input with a default value.
|
|
52
|
-
* Returns the entered string, or the default if empty.
|
|
53
|
-
*/
|
|
54
|
-
async function askText(message, defaultValue) {
|
|
55
|
-
return await consola$1.prompt(message, {
|
|
56
|
-
type: "text",
|
|
57
|
-
placeholder: defaultValue,
|
|
58
|
-
initial: defaultValue,
|
|
59
|
-
cancel: "reject"
|
|
60
|
-
}) || defaultValue;
|
|
61
|
-
}
|
|
62
61
|
//#endregion
|
|
63
62
|
//#region _discover.ts
|
|
63
|
+
var _discover_exports = /* @__PURE__ */ __exportAll({
|
|
64
|
+
DEFAULT_DEV_SERVER: () => DEFAULT_DEV_SERVER,
|
|
65
|
+
DEFAULT_SERVER: () => DEFAULT_SERVER,
|
|
66
|
+
ensureApiKeyInEnv: () => ensureApiKeyInEnv,
|
|
67
|
+
fileExists: () => fileExists,
|
|
68
|
+
generateSlug: () => generateSlug,
|
|
69
|
+
getApiKey: () => getApiKey,
|
|
70
|
+
getServerInfo: () => getServerInfo,
|
|
71
|
+
isDevMode: () => isDevMode,
|
|
72
|
+
loadAgent: () => loadAgent,
|
|
73
|
+
readProjectConfig: () => readProjectConfig,
|
|
74
|
+
resolveCwd: () => resolveCwd,
|
|
75
|
+
resolveServerUrl: () => resolveServerUrl,
|
|
76
|
+
writeProjectConfig: () => writeProjectConfig
|
|
77
|
+
});
|
|
64
78
|
const AuthConfigSchema = z.object({ assemblyai_api_key: z.string().optional() });
|
|
65
79
|
const ProjectConfigSchema = z.object({
|
|
66
80
|
slug: z.string(),
|
|
@@ -84,15 +98,16 @@ const CONFIG_DIR = path.join(process.env.HOME ?? process.env.USERPROFILE ?? ".",
|
|
|
84
98
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
85
99
|
async function readAuthConfig() {
|
|
86
100
|
try {
|
|
87
|
-
return AuthConfigSchema.parse(JSON.parse(await fs
|
|
88
|
-
} catch {
|
|
101
|
+
return AuthConfigSchema.parse(JSON.parse(await fs.readFile(CONFIG_FILE, "utf-8")));
|
|
102
|
+
} catch (error) {
|
|
103
|
+
consola.debug(`Failed to read auth config from ${CONFIG_FILE}:`, error);
|
|
89
104
|
return {};
|
|
90
105
|
}
|
|
91
106
|
}
|
|
92
107
|
async function writeAuthConfig(config) {
|
|
93
|
-
await fs
|
|
94
|
-
await fs
|
|
95
|
-
if (process.platform !== "win32") await fs
|
|
108
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
109
|
+
await fs.writeFile(CONFIG_FILE, `${JSON.stringify(config, null, 2)}\n`);
|
|
110
|
+
if (process.platform !== "win32") await fs.chmod(CONFIG_FILE, 384);
|
|
96
111
|
}
|
|
97
112
|
/**
|
|
98
113
|
* Retrieves the AssemblyAI API key from `process.env`, `~/.config/aai/config.json`,
|
|
@@ -128,8 +143,9 @@ async function ensureApiKeyInEnv() {
|
|
|
128
143
|
*/
|
|
129
144
|
async function readProjectConfig(agentDir) {
|
|
130
145
|
try {
|
|
131
|
-
return ProjectConfigSchema.parse(JSON.parse(await fs
|
|
132
|
-
} catch {
|
|
146
|
+
return ProjectConfigSchema.parse(JSON.parse(await fs.readFile(path.join(agentDir, ".aai", "project.json"), "utf-8")));
|
|
147
|
+
} catch (error) {
|
|
148
|
+
consola.debug(`Failed to read project config from ${path.join(agentDir, ".aai", "project.json")}:`, error);
|
|
133
149
|
return null;
|
|
134
150
|
}
|
|
135
151
|
}
|
|
@@ -138,12 +154,12 @@ async function readProjectConfig(agentDir) {
|
|
|
138
154
|
*/
|
|
139
155
|
async function writeProjectConfig(agentDir, data) {
|
|
140
156
|
const aaiDir = path.join(agentDir, ".aai");
|
|
141
|
-
await fs
|
|
142
|
-
await fs
|
|
157
|
+
await fs.mkdir(aaiDir, { recursive: true });
|
|
158
|
+
await fs.writeFile(path.join(aaiDir, "project.json"), `${JSON.stringify(data, null, 2)}\n`);
|
|
143
159
|
}
|
|
144
160
|
/**
|
|
145
161
|
* Read project config (throws if missing), resolve API key and server URL.
|
|
146
|
-
* Shared by secret
|
|
162
|
+
* Shared by secret commands.
|
|
147
163
|
*/
|
|
148
164
|
async function getServerInfo(cwd, explicitServer, explicitApiKey) {
|
|
149
165
|
const config = await readProjectConfig(cwd);
|
|
@@ -155,6 +171,8 @@ async function getServerInfo(cwd, explicitServer, explicitApiKey) {
|
|
|
155
171
|
apiKey
|
|
156
172
|
};
|
|
157
173
|
}
|
|
174
|
+
/** Default production server URL for agent deployments. */
|
|
175
|
+
const DEFAULT_SERVER = "https://aai-agent.fly.dev";
|
|
158
176
|
/** Default local dev server URL. */
|
|
159
177
|
const DEFAULT_DEV_SERVER = "http://localhost:8787";
|
|
160
178
|
/** Check if the CLI is running from the monorepo (dev mode). */
|
|
@@ -172,9 +190,10 @@ function resolveServerUrl(explicit, configUrl) {
|
|
|
172
190
|
}
|
|
173
191
|
async function fileExists(p) {
|
|
174
192
|
try {
|
|
175
|
-
await fs
|
|
193
|
+
await fs.access(p);
|
|
176
194
|
return true;
|
|
177
|
-
} catch {
|
|
195
|
+
} catch (error) {
|
|
196
|
+
consola.debug(`File access check failed for ${p}:`, error);
|
|
178
197
|
return false;
|
|
179
198
|
}
|
|
180
199
|
}
|
|
@@ -197,4 +216,4 @@ async function loadAgent(dir) {
|
|
|
197
216
|
};
|
|
198
217
|
}
|
|
199
218
|
//#endregion
|
|
200
|
-
export {
|
|
219
|
+
export { getApiKey as a, readProjectConfig as c, writeProjectConfig as d, askPassword as f, generateSlug as i, resolveCwd as l, ensureApiKeyInEnv as n, getServerInfo as o, fileExists as r, isDevMode as s, _discover_exports as t, resolveServerUrl as u };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as downloadAndMergeTemplate } from "./_templates-s5MYfWS9.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
//#region _init.ts
|
|
6
|
+
async function runInit(opts) {
|
|
7
|
+
const { targetDir, template } = opts;
|
|
8
|
+
await downloadAndMergeTemplate(template, targetDir);
|
|
9
|
+
try {
|
|
10
|
+
await fs.copyFile(path.join(targetDir, ".env.example"), path.join(targetDir, ".env"));
|
|
11
|
+
} catch {}
|
|
12
|
+
const readmePath = path.join(targetDir, "README.md");
|
|
13
|
+
const readme = `# ${path.basename(path.resolve(targetDir))}
|
|
14
|
+
|
|
15
|
+
A voice agent built with [aai](https://github.com/anthropics/aai).
|
|
16
|
+
|
|
17
|
+
## Getting started
|
|
18
|
+
|
|
19
|
+
\`\`\`sh
|
|
20
|
+
npm install # Install dependencies
|
|
21
|
+
npm run dev # Run locally (opens browser)
|
|
22
|
+
npm run deploy # Deploy to production
|
|
23
|
+
\`\`\`
|
|
24
|
+
|
|
25
|
+
## Secrets
|
|
26
|
+
|
|
27
|
+
Access secrets in your agent via \`ctx.env.MY_KEY\`.
|
|
28
|
+
|
|
29
|
+
**Local development** — add secrets to \`.env\` (auto-loaded by \`aai dev\`):
|
|
30
|
+
|
|
31
|
+
\`\`\`sh
|
|
32
|
+
ALPHA_VANTAGE_KEY=sk-abc123
|
|
33
|
+
MY_API_KEY=secret-value
|
|
34
|
+
\`\`\`
|
|
35
|
+
|
|
36
|
+
**Production** — set secrets on the server:
|
|
37
|
+
|
|
38
|
+
\`\`\`sh
|
|
39
|
+
aai secret put MY_KEY # Set a secret (prompts for value)
|
|
40
|
+
aai secret list # List secret names
|
|
41
|
+
aai secret delete MY_KEY # Remove a secret
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
## Learn more
|
|
45
|
+
|
|
46
|
+
See \`CLAUDE.md\` for the full agent API reference.
|
|
47
|
+
`;
|
|
48
|
+
try {
|
|
49
|
+
await fs.writeFile(readmePath, readme, { flag: "wx" });
|
|
50
|
+
} catch (err) {
|
|
51
|
+
if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
|
|
52
|
+
}
|
|
53
|
+
return targetDir;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { runInit };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { s as isDevMode } from "./_discover-DiRl7b_K.mjs";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import fs from "node:fs/promises";
|
|
7
|
+
import { downloadTemplate } from "giget";
|
|
8
|
+
//#region _templates.ts
|
|
9
|
+
const GIGET_SOURCE = "github:alexkroman/agent/packages/aai-templates";
|
|
10
|
+
const GIGET_REF = process.env.AAI_TEMPLATES_REF ?? "main";
|
|
11
|
+
/** Resolve the local aai-templates package directory (dev mode only). */
|
|
12
|
+
function resolveLocalTemplatesDir() {
|
|
13
|
+
const cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const fromSrc = path.resolve(cliDir, "../aai-templates");
|
|
15
|
+
const fromDist = path.resolve(cliDir, "../../aai-templates");
|
|
16
|
+
if (existsSync(fromSrc)) return fromSrc;
|
|
17
|
+
if (existsSync(fromDist)) return fromDist;
|
|
18
|
+
throw new Error("Cannot find local aai-templates package");
|
|
19
|
+
}
|
|
20
|
+
/** Resolve the templates directory — local in dev, giget download in prod. */
|
|
21
|
+
async function resolveTemplatesDir() {
|
|
22
|
+
if (isDevMode()) return resolveLocalTemplatesDir();
|
|
23
|
+
const { dir } = await downloadTemplate(`${GIGET_SOURCE}#${GIGET_REF}`, { force: true });
|
|
24
|
+
return dir;
|
|
25
|
+
}
|
|
26
|
+
/** List available template names. */
|
|
27
|
+
async function listTemplates() {
|
|
28
|
+
const dir = path.join(await resolveTemplatesDir(), "templates");
|
|
29
|
+
return (await fs.readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort((a, b) => a.localeCompare(b));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Download a template into targetDir, merging scaffold files underneath.
|
|
33
|
+
*/
|
|
34
|
+
async function downloadAndMergeTemplate(template, targetDir) {
|
|
35
|
+
const root = await resolveTemplatesDir();
|
|
36
|
+
const templatesDir = path.join(root, "templates");
|
|
37
|
+
const names = (await fs.readdir(templatesDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
38
|
+
if (!names.includes(template)) throw new Error(`unknown template '${template}' -- available: ${names.join(", ")}`);
|
|
39
|
+
await fs.cp(path.join(templatesDir, template), targetDir, {
|
|
40
|
+
recursive: true,
|
|
41
|
+
force: true
|
|
42
|
+
});
|
|
43
|
+
const scaffoldDir = path.join(root, "scaffold");
|
|
44
|
+
if (existsSync(scaffoldDir)) {
|
|
45
|
+
const entries = await fs.readdir(scaffoldDir, {
|
|
46
|
+
recursive: true,
|
|
47
|
+
withFileTypes: true
|
|
48
|
+
});
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (!entry.isFile()) continue;
|
|
51
|
+
const rel = path.relative(scaffoldDir, path.join(entry.parentPath, entry.name));
|
|
52
|
+
const destPath = path.join(targetDir, rel);
|
|
53
|
+
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
54
|
+
try {
|
|
55
|
+
await fs.copyFile(path.join(scaffoldDir, rel), destPath, fs.constants.COPYFILE_EXCL);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { listTemplates as n, downloadAndMergeTemplate as t };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { consola as consola$1 } from "consola";
|
|
3
|
+
//#region _ui.ts
|
|
4
|
+
/** Shared consola instance with date display disabled. */
|
|
5
|
+
const consola = consola$1.create({ formatOptions: { date: false } });
|
|
6
|
+
/** Parse and validate a port string. Returns the numeric port or throws. */
|
|
7
|
+
function parsePort(raw) {
|
|
8
|
+
const port = Number.parseInt(raw, 10);
|
|
9
|
+
if (Number.isNaN(port) || port < 0 || port > 65535) throw new Error(`Invalid port: ${raw}. Must be a number between 0 and 65535.`);
|
|
10
|
+
return port;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { consola, parsePort };
|