api2cli 0.1.1 → 0.1.3
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/index.js +34 -16
- package/package.json +13 -2
- package/src/commands/bundle.ts +0 -68
- package/src/commands/create.ts +0 -78
- package/src/commands/doctor.ts +0 -48
- package/src/commands/install.ts +0 -29
- package/src/commands/link.ts +0 -32
- package/src/commands/list.ts +0 -52
- package/src/commands/publish.ts +0 -22
- package/src/commands/remove.ts +0 -35
- package/src/commands/tokens.ts +0 -35
- package/src/commands/unlink.ts +0 -11
- package/src/commands/update.ts +0 -26
- package/src/index.ts +0 -41
- package/src/lib/bashrc.ts +0 -55
- package/src/lib/config.ts +0 -35
- package/src/lib/shell.ts +0 -57
- package/src/lib/template.ts +0 -63
- package/tsconfig.json +0 -8
package/dist/index.js
CHANGED
|
@@ -2147,10 +2147,11 @@ import { join as join3 } from "path";
|
|
|
2147
2147
|
|
|
2148
2148
|
// src/lib/config.ts
|
|
2149
2149
|
import { homedir } from "os";
|
|
2150
|
-
import { join
|
|
2150
|
+
import { join } from "path";
|
|
2151
2151
|
var CLI_ROOT = join(homedir(), ".cli");
|
|
2152
2152
|
var TOKENS_DIR = join(homedir(), ".config", "tokens");
|
|
2153
|
-
var
|
|
2153
|
+
var TEMPLATE_REPO = "https://github.com/Melvynx/api2cli.git";
|
|
2154
|
+
var TEMPLATE_REPO_PATH = "packages/template";
|
|
2154
2155
|
function getCliDir(app) {
|
|
2155
2156
|
return join(CLI_ROOT, `${app}-cli`);
|
|
2156
2157
|
}
|
|
@@ -2162,13 +2163,27 @@ function getDistDir(app) {
|
|
|
2162
2163
|
}
|
|
2163
2164
|
|
|
2164
2165
|
// src/lib/template.ts
|
|
2165
|
-
import { existsSync, cpSync, readdirSync, statSync, readFileSync, writeFileSync } from "fs";
|
|
2166
|
+
import { existsSync, cpSync, readdirSync, statSync, readFileSync, writeFileSync, rmSync, mkdtempSync } from "fs";
|
|
2166
2167
|
import { join as join2 } from "path";
|
|
2168
|
+
import { tmpdir } from "os";
|
|
2169
|
+
import { execSync } from "child_process";
|
|
2167
2170
|
function copyTemplate(targetDir) {
|
|
2168
|
-
|
|
2169
|
-
|
|
2171
|
+
const tmp = mkdtempSync(join2(tmpdir(), "api2cli-"));
|
|
2172
|
+
try {
|
|
2173
|
+
execSync(`git clone --depth 1 --filter=blob:none --sparse ${TEMPLATE_REPO} ${tmp}/repo`, {
|
|
2174
|
+
stdio: "pipe"
|
|
2175
|
+
});
|
|
2176
|
+
execSync(`git -C ${tmp}/repo sparse-checkout set ${TEMPLATE_REPO_PATH}`, {
|
|
2177
|
+
stdio: "pipe"
|
|
2178
|
+
});
|
|
2179
|
+
const templateSrc = join2(tmp, "repo", TEMPLATE_REPO_PATH);
|
|
2180
|
+
if (!existsSync(templateSrc)) {
|
|
2181
|
+
throw new Error(`Template not found in repo at ${TEMPLATE_REPO_PATH}`);
|
|
2182
|
+
}
|
|
2183
|
+
cpSync(templateSrc, targetDir, { recursive: true });
|
|
2184
|
+
} finally {
|
|
2185
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
2170
2186
|
}
|
|
2171
|
-
cpSync(TEMPLATE_DIR, targetDir, { recursive: true });
|
|
2172
2187
|
}
|
|
2173
2188
|
function replacePlaceholders(dir, vars) {
|
|
2174
2189
|
const replacements = [
|
|
@@ -2501,7 +2516,7 @@ ${import_picocolors7.default.bold("Configured tokens:")}
|
|
|
2501
2516
|
|
|
2502
2517
|
// src/commands/remove.ts
|
|
2503
2518
|
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
2504
|
-
import { existsSync as existsSync9, rmSync } from "fs";
|
|
2519
|
+
import { existsSync as existsSync9, rmSync as rmSync2 } from "fs";
|
|
2505
2520
|
var removeCommand = new Command("remove").description("Remove a CLI entirely").argument("<app>", "CLI to remove").option("--keep-token", "Keep the auth token").addHelpText("after", `
|
|
2506
2521
|
Examples:
|
|
2507
2522
|
api2cli remove typefully
|
|
@@ -2512,12 +2527,12 @@ Examples:
|
|
|
2512
2527
|
process.exit(1);
|
|
2513
2528
|
}
|
|
2514
2529
|
removeFromPath(app, getDistDir(app));
|
|
2515
|
-
|
|
2530
|
+
rmSync2(cliDir, { recursive: true, force: true });
|
|
2516
2531
|
console.log(`${import_picocolors8.default.green("\u2713")} Removed ${import_picocolors8.default.bold(`${app}-cli`)}`);
|
|
2517
2532
|
if (!opts.keepToken) {
|
|
2518
2533
|
const tokenFile = getTokenFile(app);
|
|
2519
2534
|
if (existsSync9(tokenFile)) {
|
|
2520
|
-
|
|
2535
|
+
rmSync2(tokenFile);
|
|
2521
2536
|
console.log(`${import_picocolors8.default.green("\u2713")} Removed token`);
|
|
2522
2537
|
}
|
|
2523
2538
|
}
|
|
@@ -2541,6 +2556,14 @@ ${import_picocolors9.default.bold("api2cli doctor")}
|
|
|
2541
2556
|
console.log(` ${import_picocolors9.default.red("\u2717")} Bun not found. Install: ${import_picocolors9.default.cyan("https://bun.sh")}`);
|
|
2542
2557
|
issues++;
|
|
2543
2558
|
}
|
|
2559
|
+
try {
|
|
2560
|
+
const proc = Bun.spawn(["git", "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
2561
|
+
const version = (await new Response(proc.stdout).text()).trim();
|
|
2562
|
+
console.log(` ${import_picocolors9.default.green("\u2713")} ${version}`);
|
|
2563
|
+
} catch {
|
|
2564
|
+
console.log(` ${import_picocolors9.default.red("\u2717")} Git not found (required to fetch template)`);
|
|
2565
|
+
issues++;
|
|
2566
|
+
}
|
|
2544
2567
|
if (existsSync10(CLI_ROOT)) {
|
|
2545
2568
|
console.log(` ${import_picocolors9.default.green("\u2713")} CLI root: ${import_picocolors9.default.dim(CLI_ROOT)}`);
|
|
2546
2569
|
} else {
|
|
@@ -2551,14 +2574,9 @@ ${import_picocolors9.default.bold("api2cli doctor")}
|
|
|
2551
2574
|
} else {
|
|
2552
2575
|
console.log(` ${import_picocolors9.default.yellow("~")} Tokens dir not yet created: ${import_picocolors9.default.dim(TOKENS_DIR)}`);
|
|
2553
2576
|
}
|
|
2554
|
-
|
|
2555
|
-
console.log(` ${import_picocolors9.default.green("\u2713")} Template: ${import_picocolors9.default.dim(TEMPLATE_DIR)}`);
|
|
2556
|
-
} else {
|
|
2557
|
-
console.log(` ${import_picocolors9.default.red("\u2717")} Template not found: ${import_picocolors9.default.dim(TEMPLATE_DIR)}`);
|
|
2558
|
-
issues++;
|
|
2559
|
-
}
|
|
2577
|
+
console.log(` ${import_picocolors9.default.green("\u2713")} Template: ${import_picocolors9.default.dim(TEMPLATE_REPO)} (fetched on create)`);
|
|
2560
2578
|
console.log(issues === 0 ? `
|
|
2561
|
-
${import_picocolors9.default.green("All good!")}
|
|
2579
|
+
${import_picocolors9.default.green("All good!")}
|
|
2562
2580
|
` : `
|
|
2563
2581
|
${import_picocolors9.default.red(`${issues} issue(s) found.`)}
|
|
2564
2582
|
`);
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api2cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Turn any REST API into a standardized, agent-ready CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"api2cli": "./dist/index.js"
|
|
8
8
|
},
|
|
9
|
+
"files": ["dist", "README.md"],
|
|
9
10
|
"scripts": {
|
|
10
11
|
"build": "bun build src/index.ts --outfile dist/index.js --target bun",
|
|
11
12
|
"dev": "bun run src/index.ts"
|
|
@@ -19,5 +20,15 @@
|
|
|
19
20
|
"typescript": "^5.7.0"
|
|
20
21
|
},
|
|
21
22
|
"license": "MIT",
|
|
22
|
-
"keywords": ["cli", "api", "rest", "agent", "skill", "mcp", "agentskills"]
|
|
23
|
+
"keywords": ["cli", "api", "rest", "agent", "skill", "mcp", "agentskills"],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/Melvynx/api2cli.git",
|
|
27
|
+
"directory": "packages/cli"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/Melvynx/api2cli#readme",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/Melvynx/api2cli/issues"
|
|
32
|
+
},
|
|
33
|
+
"author": "Melvynx"
|
|
23
34
|
}
|
package/src/commands/bundle.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { existsSync, mkdirSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import pc from "picocolors";
|
|
5
|
-
import { getCliDir, getDistDir, CLI_ROOT } from "../lib/config.js";
|
|
6
|
-
import { readdirSync } from "fs";
|
|
7
|
-
|
|
8
|
-
export const bundleCommand = new Command("bundle")
|
|
9
|
-
.description("Build/rebuild a CLI from source")
|
|
10
|
-
.argument("[app]", "CLI to build (omit with --all)")
|
|
11
|
-
.option("--compile", "Create standalone binary (~50MB, no runtime needed)")
|
|
12
|
-
.option("--all", "Build all installed CLIs")
|
|
13
|
-
.addHelpText(
|
|
14
|
-
"after",
|
|
15
|
-
"\nExamples:\n api2cli bundle typefully\n api2cli bundle typefully --compile\n api2cli bundle --all",
|
|
16
|
-
)
|
|
17
|
-
.action(async (app: string | undefined, opts) => {
|
|
18
|
-
if (opts.all) {
|
|
19
|
-
if (!existsSync(CLI_ROOT)) {
|
|
20
|
-
console.log("No CLIs installed.");
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
const dirs = readdirSync(CLI_ROOT).filter((d) => d.endsWith("-cli"));
|
|
24
|
-
for (const d of dirs) {
|
|
25
|
-
await buildCli(d.replace(/-cli$/, ""), opts.compile);
|
|
26
|
-
}
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (!app) {
|
|
31
|
-
console.error("Specify an app name or use --all");
|
|
32
|
-
process.exit(2);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
await buildCli(app, opts.compile);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
async function buildCli(app: string, compile?: boolean): Promise<void> {
|
|
39
|
-
const cliDir = getCliDir(app);
|
|
40
|
-
if (!existsSync(cliDir)) {
|
|
41
|
-
console.error(`${pc.red("✗")} ${app}-cli not found. Run: ${pc.cyan(`api2cli create ${app}`)}`);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const distDir = getDistDir(app);
|
|
46
|
-
mkdirSync(distDir, { recursive: true });
|
|
47
|
-
|
|
48
|
-
console.log(`Building ${pc.bold(`${app}-cli`)}...`);
|
|
49
|
-
|
|
50
|
-
const entry = join(cliDir, "src", "index.ts");
|
|
51
|
-
const outfile = join(distDir, compile ? `${app}-cli` : `${app}-cli.js`);
|
|
52
|
-
const args = ["bun", "build", entry, "--outfile", outfile, "--target", "bun"];
|
|
53
|
-
if (compile) args.push("--compile");
|
|
54
|
-
|
|
55
|
-
const proc = Bun.spawn(args, { cwd: cliDir, stdout: "pipe", stderr: "pipe" });
|
|
56
|
-
const code = await proc.exited;
|
|
57
|
-
|
|
58
|
-
if (code === 0) {
|
|
59
|
-
const size = Bun.file(outfile).size;
|
|
60
|
-
const sizeStr = size > 1024 * 1024
|
|
61
|
-
? `${(size / 1024 / 1024).toFixed(1)}MB`
|
|
62
|
-
: `${(size / 1024).toFixed(1)}KB`;
|
|
63
|
-
console.log(`${pc.green("✓")} Built ${pc.bold(`${app}-cli`)} (${sizeStr})`);
|
|
64
|
-
} else {
|
|
65
|
-
const stderr = await new Response(proc.stderr).text();
|
|
66
|
-
console.error(`${pc.red("✗")} Build failed: ${stderr}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
package/src/commands/create.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { existsSync, mkdirSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import pc from "picocolors";
|
|
5
|
-
import { getCliDir } from "../lib/config.js";
|
|
6
|
-
import { copyTemplate, replacePlaceholders } from "../lib/template.js";
|
|
7
|
-
|
|
8
|
-
export const createCommand = new Command("create")
|
|
9
|
-
.description("Generate a new CLI from API documentation")
|
|
10
|
-
.argument("<app>", "API/app name (e.g. typefully, dub, mercury)")
|
|
11
|
-
.option("--docs <url>", "URL to API documentation")
|
|
12
|
-
.option("--openapi <url>", "URL to OpenAPI/Swagger spec")
|
|
13
|
-
.option("--base-url <url>", "API base URL", "https://api.example.com")
|
|
14
|
-
.option("--auth-type <type>", "Auth type: bearer, api-key, basic, custom", "bearer")
|
|
15
|
-
.option("--auth-header <name>", "Auth header name", "Authorization")
|
|
16
|
-
.option("--force", "Overwrite existing CLI", false)
|
|
17
|
-
.addHelpText(
|
|
18
|
-
"after",
|
|
19
|
-
`
|
|
20
|
-
Examples:
|
|
21
|
-
api2cli create typefully --base-url https://api.typefully.com --auth-type bearer
|
|
22
|
-
api2cli create dub --openapi https://api.dub.co/openapi.json
|
|
23
|
-
api2cli create my-api --docs https://docs.example.com/api`,
|
|
24
|
-
)
|
|
25
|
-
.action(async (app: string, opts) => {
|
|
26
|
-
const cliDir = getCliDir(app);
|
|
27
|
-
|
|
28
|
-
if (existsSync(cliDir) && !opts.force) {
|
|
29
|
-
console.error(`${pc.red("✗")} ${app}-cli already exists at ${cliDir}`);
|
|
30
|
-
console.error(` Use ${pc.cyan("--force")} to overwrite.`);
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
console.log(`\n${pc.bold("Creating")} ${pc.cyan(`${app}-cli`)}...\n`);
|
|
35
|
-
|
|
36
|
-
// 1. Create target directory
|
|
37
|
-
mkdirSync(cliDir, { recursive: true });
|
|
38
|
-
console.log(` ${pc.green("+")} Created ${pc.dim(cliDir)}`);
|
|
39
|
-
|
|
40
|
-
// 2. Copy template
|
|
41
|
-
copyTemplate(cliDir);
|
|
42
|
-
console.log(` ${pc.green("+")} Copied template scaffold`);
|
|
43
|
-
|
|
44
|
-
// 3. Replace placeholders
|
|
45
|
-
replacePlaceholders(cliDir, {
|
|
46
|
-
appName: app,
|
|
47
|
-
appCli: `${app}-cli`,
|
|
48
|
-
baseUrl: opts.baseUrl,
|
|
49
|
-
authType: opts.authType,
|
|
50
|
-
authHeader: opts.authHeader,
|
|
51
|
-
});
|
|
52
|
-
console.log(` ${pc.green("+")} Configured for ${pc.bold(app)}`);
|
|
53
|
-
|
|
54
|
-
// 4. Install dependencies
|
|
55
|
-
console.log(` ${pc.dim("Installing dependencies...")}`);
|
|
56
|
-
const install = Bun.spawn(["bun", "install"], {
|
|
57
|
-
cwd: cliDir,
|
|
58
|
-
stdout: "ignore",
|
|
59
|
-
stderr: "pipe",
|
|
60
|
-
});
|
|
61
|
-
await install.exited;
|
|
62
|
-
console.log(` ${pc.green("+")} Dependencies installed`);
|
|
63
|
-
|
|
64
|
-
// 5. Rename SKILL.md.template
|
|
65
|
-
const skillTemplate = join(cliDir, "SKILL.md.template");
|
|
66
|
-
const skillTarget = join(cliDir, "SKILL.md");
|
|
67
|
-
if (existsSync(skillTemplate)) {
|
|
68
|
-
const { renameSync } = require("fs");
|
|
69
|
-
renameSync(skillTemplate, skillTarget);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
console.log(`\n${pc.green("✓")} Created ${pc.bold(`${app}-cli`)} at ${pc.dim(cliDir)}`);
|
|
73
|
-
console.log(`\n${pc.bold("Next steps:")}`);
|
|
74
|
-
console.log(` 1. Edit resources in ${pc.dim(`${cliDir}/src/resources/`)}`);
|
|
75
|
-
console.log(` 2. Build: ${pc.cyan(`api2cli bundle ${app}`)}`);
|
|
76
|
-
console.log(` 3. Link: ${pc.cyan(`api2cli link ${app}`)}`);
|
|
77
|
-
console.log(` 4. Auth: ${pc.cyan(`${app}-cli auth set "your-token"`)}`);
|
|
78
|
-
});
|
package/src/commands/doctor.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import pc from "picocolors";
|
|
4
|
-
import { CLI_ROOT, TOKENS_DIR, TEMPLATE_DIR } from "../lib/config.js";
|
|
5
|
-
|
|
6
|
-
export const doctorCommand = new Command("doctor")
|
|
7
|
-
.description("Check system requirements and configuration")
|
|
8
|
-
.addHelpText("after", "\nExample:\n api2cli doctor")
|
|
9
|
-
.action(async () => {
|
|
10
|
-
console.log(`\n${pc.bold("api2cli doctor")}\n`);
|
|
11
|
-
let issues = 0;
|
|
12
|
-
|
|
13
|
-
// Bun
|
|
14
|
-
try {
|
|
15
|
-
const proc = Bun.spawn(["bun", "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
16
|
-
const version = (await new Response(proc.stdout).text()).trim();
|
|
17
|
-
console.log(` ${pc.green("✓")} Bun ${version}`);
|
|
18
|
-
} catch {
|
|
19
|
-
console.log(` ${pc.red("✗")} Bun not found. Install: ${pc.cyan("https://bun.sh")}`);
|
|
20
|
-
issues++;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// CLI root
|
|
24
|
-
if (existsSync(CLI_ROOT)) {
|
|
25
|
-
console.log(` ${pc.green("✓")} CLI root: ${pc.dim(CLI_ROOT)}`);
|
|
26
|
-
} else {
|
|
27
|
-
console.log(` ${pc.yellow("~")} CLI root not yet created: ${pc.dim(CLI_ROOT)}`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Tokens dir
|
|
31
|
-
if (existsSync(TOKENS_DIR)) {
|
|
32
|
-
console.log(` ${pc.green("✓")} Tokens dir: ${pc.dim(TOKENS_DIR)}`);
|
|
33
|
-
} else {
|
|
34
|
-
console.log(` ${pc.yellow("~")} Tokens dir not yet created: ${pc.dim(TOKENS_DIR)}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Template
|
|
38
|
-
if (existsSync(TEMPLATE_DIR)) {
|
|
39
|
-
console.log(` ${pc.green("✓")} Template: ${pc.dim(TEMPLATE_DIR)}`);
|
|
40
|
-
} else {
|
|
41
|
-
console.log(` ${pc.red("✗")} Template not found: ${pc.dim(TEMPLATE_DIR)}`);
|
|
42
|
-
issues++;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
console.log(
|
|
46
|
-
issues === 0 ? `\n${pc.green("All good!")} 🎉\n` : `\n${pc.red(`${issues} issue(s) found.`)}\n`,
|
|
47
|
-
);
|
|
48
|
-
});
|
package/src/commands/install.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import pc from "picocolors";
|
|
4
|
-
import { getCliDir } from "../lib/config.js";
|
|
5
|
-
|
|
6
|
-
export const installCommand = new Command("install")
|
|
7
|
-
.description("Install a pre-built CLI from the registry")
|
|
8
|
-
.argument("<app>", "CLI to install (e.g. typefully, dub)")
|
|
9
|
-
.option("--force", "Overwrite existing CLI", false)
|
|
10
|
-
.addHelpText(
|
|
11
|
-
"after",
|
|
12
|
-
"\nExamples:\n api2cli install typefully\n api2cli install dub --force",
|
|
13
|
-
)
|
|
14
|
-
.action(async (app: string, opts) => {
|
|
15
|
-
const cliDir = getCliDir(app);
|
|
16
|
-
|
|
17
|
-
if (existsSync(cliDir) && !opts.force) {
|
|
18
|
-
console.error(`${pc.red("✗")} ${app}-cli already installed. Use ${pc.cyan("--force")} to reinstall.`);
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
console.log(`Installing ${pc.bold(`${app}-cli`)} from registry...`);
|
|
23
|
-
|
|
24
|
-
// TODO: Fetch from npm @api2cli/<app> or api2cli.dev API
|
|
25
|
-
// Download resources + config -> merge with template -> build -> link
|
|
26
|
-
console.log(`\n${pc.yellow("🚧")} Registry not yet available.`);
|
|
27
|
-
console.log(`\nCreate it manually instead:`);
|
|
28
|
-
console.log(` ${pc.cyan(`api2cli create ${app} --docs <api-docs-url>`)}`);
|
|
29
|
-
});
|
package/src/commands/link.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { existsSync, readdirSync } from "fs";
|
|
3
|
-
import pc from "picocolors";
|
|
4
|
-
import { getCliDir, getDistDir, CLI_ROOT } from "../lib/config.js";
|
|
5
|
-
import { addToPath } from "../lib/shell.js";
|
|
6
|
-
|
|
7
|
-
export const linkCommand = new Command("link")
|
|
8
|
-
.description("Add a CLI to your PATH")
|
|
9
|
-
.argument("[app]", "CLI to link (omit with --all)")
|
|
10
|
-
.option("--all", "Link all installed CLIs")
|
|
11
|
-
.addHelpText("after", "\nExamples:\n api2cli link typefully\n api2cli link --all")
|
|
12
|
-
.action((app: string | undefined, opts) => {
|
|
13
|
-
if (opts.all || !app) {
|
|
14
|
-
if (!existsSync(CLI_ROOT)) {
|
|
15
|
-
console.log("No CLIs installed.");
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
const dirs = readdirSync(CLI_ROOT).filter((d) => d.endsWith("-cli"));
|
|
19
|
-
for (const d of dirs) {
|
|
20
|
-
const name = d.replace(/-cli$/, "");
|
|
21
|
-
addToPath(name, getDistDir(name));
|
|
22
|
-
}
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (!existsSync(getCliDir(app))) {
|
|
27
|
-
console.error(`${pc.red("✗")} ${app}-cli not found. Run: ${pc.cyan(`api2cli create ${app}`)}`);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
addToPath(app, getDistDir(app));
|
|
32
|
-
});
|
package/src/commands/list.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { existsSync, readdirSync, statSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import pc from "picocolors";
|
|
5
|
-
import { CLI_ROOT, TOKENS_DIR } from "../lib/config.js";
|
|
6
|
-
|
|
7
|
-
export const listCommand = new Command("list")
|
|
8
|
-
.description("List all installed CLIs")
|
|
9
|
-
.option("--json", "Output as JSON")
|
|
10
|
-
.addHelpText("after", "\nExamples:\n api2cli list\n api2cli list --json")
|
|
11
|
-
.action((opts) => {
|
|
12
|
-
if (!existsSync(CLI_ROOT)) {
|
|
13
|
-
console.log("No CLIs installed. Run: api2cli create <app>");
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const dirs = readdirSync(CLI_ROOT).filter((d) => {
|
|
18
|
-
return statSync(join(CLI_ROOT, d)).isDirectory() && d.endsWith("-cli");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
if (dirs.length === 0) {
|
|
22
|
-
console.log("No CLIs installed.");
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (opts.json) {
|
|
27
|
-
const data = dirs.map((d) => {
|
|
28
|
-
const name = d.replace(/-cli$/, "");
|
|
29
|
-
return {
|
|
30
|
-
name,
|
|
31
|
-
built: existsSync(join(CLI_ROOT, d, "dist")),
|
|
32
|
-
hasToken: existsSync(join(TOKENS_DIR, `${d}.txt`)),
|
|
33
|
-
path: join(CLI_ROOT, d),
|
|
34
|
-
};
|
|
35
|
-
});
|
|
36
|
-
console.log(JSON.stringify({ ok: true, data }, null, 2));
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
console.log(`\n${pc.bold("Installed CLIs:")}\n`);
|
|
41
|
-
for (const d of dirs) {
|
|
42
|
-
const name = d.replace(/-cli$/, "");
|
|
43
|
-
const built = existsSync(join(CLI_ROOT, d, "dist"));
|
|
44
|
-
const hasToken = existsSync(join(TOKENS_DIR, `${d}.txt`));
|
|
45
|
-
const status = [
|
|
46
|
-
built ? pc.green("built") : pc.yellow("not built"),
|
|
47
|
-
hasToken ? pc.green("auth") : pc.dim("no auth"),
|
|
48
|
-
].join(pc.dim(" | "));
|
|
49
|
-
console.log(` ${pc.bold(name.padEnd(20))} ${status}`);
|
|
50
|
-
}
|
|
51
|
-
console.log();
|
|
52
|
-
});
|
package/src/commands/publish.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import pc from "picocolors";
|
|
4
|
-
import { getCliDir } from "../lib/config.js";
|
|
5
|
-
|
|
6
|
-
export const publishCommand = new Command("publish")
|
|
7
|
-
.description("Publish a CLI to the api2cli registry")
|
|
8
|
-
.argument("<app>", "CLI to publish")
|
|
9
|
-
.option("--scope <scope>", "npm scope", "@api2cli")
|
|
10
|
-
.addHelpText("after", "\nExample:\n api2cli publish typefully")
|
|
11
|
-
.action(async (app: string, opts) => {
|
|
12
|
-
const cliDir = getCliDir(app);
|
|
13
|
-
|
|
14
|
-
if (!existsSync(cliDir)) {
|
|
15
|
-
console.error(`${pc.red("✗")} ${app}-cli not found.`);
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// TODO: Package resources + config, publish to npm + api2cli.dev
|
|
20
|
-
console.log(`Publishing ${pc.bold(`${app}-cli`)} as ${pc.cyan(`${opts.scope}/${app}`)}...`);
|
|
21
|
-
console.log(`\n${pc.yellow("🚧")} Publishing not yet implemented.`);
|
|
22
|
-
});
|
package/src/commands/remove.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { existsSync, rmSync } from "fs";
|
|
3
|
-
import pc from "picocolors";
|
|
4
|
-
import { getCliDir, getTokenFile, getDistDir } from "../lib/config.js";
|
|
5
|
-
import { removeFromPath } from "../lib/shell.js";
|
|
6
|
-
|
|
7
|
-
export const removeCommand = new Command("remove")
|
|
8
|
-
.description("Remove a CLI entirely")
|
|
9
|
-
.argument("<app>", "CLI to remove")
|
|
10
|
-
.option("--keep-token", "Keep the auth token")
|
|
11
|
-
.addHelpText("after", "\nExamples:\n api2cli remove typefully\n api2cli remove typefully --keep-token")
|
|
12
|
-
.action((app: string, opts) => {
|
|
13
|
-
const cliDir = getCliDir(app);
|
|
14
|
-
|
|
15
|
-
if (!existsSync(cliDir)) {
|
|
16
|
-
console.error(`${pc.red("✗")} ${app}-cli not found.`);
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Remove from PATH
|
|
21
|
-
removeFromPath(app, getDistDir(app));
|
|
22
|
-
|
|
23
|
-
// Remove directory
|
|
24
|
-
rmSync(cliDir, { recursive: true, force: true });
|
|
25
|
-
console.log(`${pc.green("✓")} Removed ${pc.bold(`${app}-cli`)}`);
|
|
26
|
-
|
|
27
|
-
// Remove token unless --keep-token
|
|
28
|
-
if (!opts.keepToken) {
|
|
29
|
-
const tokenFile = getTokenFile(app);
|
|
30
|
-
if (existsSync(tokenFile)) {
|
|
31
|
-
rmSync(tokenFile);
|
|
32
|
-
console.log(`${pc.green("✓")} Removed token`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
});
|
package/src/commands/tokens.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
3
|
-
import pc from "picocolors";
|
|
4
|
-
import { TOKENS_DIR } from "../lib/config.js";
|
|
5
|
-
import { join } from "path";
|
|
6
|
-
|
|
7
|
-
export const tokensCommand = new Command("tokens")
|
|
8
|
-
.description("List all configured API tokens")
|
|
9
|
-
.option("--show", "Show full unmasked tokens")
|
|
10
|
-
.addHelpText("after", "\nExamples:\n api2cli tokens\n api2cli tokens --show")
|
|
11
|
-
.action((opts) => {
|
|
12
|
-
if (!existsSync(TOKENS_DIR)) {
|
|
13
|
-
console.log("No tokens configured yet.");
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const files = readdirSync(TOKENS_DIR).filter((f) => f.endsWith(".txt"));
|
|
18
|
-
if (files.length === 0) {
|
|
19
|
-
console.log("No tokens configured yet.");
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
console.log(`\n${pc.bold("Configured tokens:")}\n`);
|
|
24
|
-
for (const f of files) {
|
|
25
|
-
const name = f.replace(".txt", "");
|
|
26
|
-
const token = readFileSync(join(TOKENS_DIR, f), "utf-8").trim();
|
|
27
|
-
const display = opts.show
|
|
28
|
-
? token
|
|
29
|
-
: token.length > 8
|
|
30
|
-
? `${token.slice(0, 4)}${pc.dim("...")}${token.slice(-4)}`
|
|
31
|
-
: pc.dim("****");
|
|
32
|
-
console.log(` ${pc.bold(name.padEnd(25))} ${display}`);
|
|
33
|
-
}
|
|
34
|
-
console.log();
|
|
35
|
-
});
|
package/src/commands/unlink.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { getDistDir } from "../lib/config.js";
|
|
3
|
-
import { removeFromPath } from "../lib/shell.js";
|
|
4
|
-
|
|
5
|
-
export const unlinkCommand = new Command("unlink")
|
|
6
|
-
.description("Remove a CLI from your PATH")
|
|
7
|
-
.argument("<app>", "CLI to unlink")
|
|
8
|
-
.addHelpText("after", "\nExample:\n api2cli unlink typefully")
|
|
9
|
-
.action((app: string) => {
|
|
10
|
-
removeFromPath(app, getDistDir(app));
|
|
11
|
-
});
|
package/src/commands/update.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import pc from "picocolors";
|
|
4
|
-
import { getCliDir } from "../lib/config.js";
|
|
5
|
-
|
|
6
|
-
export const updateCommand = new Command("update")
|
|
7
|
-
.description("Re-sync a CLI when the upstream API changes")
|
|
8
|
-
.argument("<app>", "CLI to update")
|
|
9
|
-
.option("--docs <url>", "Updated API documentation URL")
|
|
10
|
-
.option("--openapi <url>", "Updated OpenAPI spec URL")
|
|
11
|
-
.addHelpText("after", "\nExample:\n api2cli update typefully --docs https://docs.typefully.com")
|
|
12
|
-
.action(async (app: string) => {
|
|
13
|
-
const cliDir = getCliDir(app);
|
|
14
|
-
|
|
15
|
-
if (!existsSync(cliDir)) {
|
|
16
|
-
console.error(`${pc.red("✗")} ${app}-cli not found. Run: ${pc.cyan(`api2cli create ${app}`)}`);
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// TODO: Agent-driven update flow
|
|
21
|
-
// Re-read API docs -> diff endpoints -> add/update resources -> rebuild
|
|
22
|
-
console.log(`${pc.yellow("🚧")} Update is agent-driven.`);
|
|
23
|
-
console.log(`\nUse your AI agent to update resources in:`);
|
|
24
|
-
console.log(` ${pc.dim(`${cliDir}/src/resources/`)}`);
|
|
25
|
-
console.log(`\nThen rebuild: ${pc.cyan(`api2cli bundle ${app}`)}`);
|
|
26
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { Command } from "commander";
|
|
3
|
-
import { createCommand } from "./commands/create.js";
|
|
4
|
-
import { installCommand } from "./commands/install.js";
|
|
5
|
-
import { listCommand } from "./commands/list.js";
|
|
6
|
-
import { bundleCommand } from "./commands/bundle.js";
|
|
7
|
-
import { linkCommand } from "./commands/link.js";
|
|
8
|
-
import { unlinkCommand } from "./commands/unlink.js";
|
|
9
|
-
import { tokensCommand } from "./commands/tokens.js";
|
|
10
|
-
import { removeCommand } from "./commands/remove.js";
|
|
11
|
-
import { doctorCommand } from "./commands/doctor.js";
|
|
12
|
-
import { updateCommand } from "./commands/update.js";
|
|
13
|
-
import { publishCommand } from "./commands/publish.js";
|
|
14
|
-
|
|
15
|
-
const program = new Command();
|
|
16
|
-
|
|
17
|
-
program
|
|
18
|
-
.name("api2cli")
|
|
19
|
-
.description("Turn any REST API into a standardized, agent-ready CLI")
|
|
20
|
-
.version("0.1.0");
|
|
21
|
-
|
|
22
|
-
// Core
|
|
23
|
-
program.addCommand(createCommand);
|
|
24
|
-
program.addCommand(bundleCommand);
|
|
25
|
-
program.addCommand(linkCommand);
|
|
26
|
-
program.addCommand(unlinkCommand);
|
|
27
|
-
program.addCommand(listCommand);
|
|
28
|
-
|
|
29
|
-
// Auth
|
|
30
|
-
program.addCommand(tokensCommand);
|
|
31
|
-
|
|
32
|
-
// Lifecycle
|
|
33
|
-
program.addCommand(removeCommand);
|
|
34
|
-
program.addCommand(doctorCommand);
|
|
35
|
-
program.addCommand(updateCommand);
|
|
36
|
-
|
|
37
|
-
// Registry
|
|
38
|
-
program.addCommand(installCommand);
|
|
39
|
-
program.addCommand(publishCommand);
|
|
40
|
-
|
|
41
|
-
program.parse();
|
package/src/lib/bashrc.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, appendFileSync } from "fs";
|
|
2
|
-
import { homedir } from "os";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
|
|
5
|
-
const MARKER_START = "# >>> api2cli >>>";
|
|
6
|
-
const MARKER_END = "# <<< api2cli <<<";
|
|
7
|
-
|
|
8
|
-
function getShellRc(): string {
|
|
9
|
-
const zshrc = join(homedir(), ".zshrc");
|
|
10
|
-
const bashrc = join(homedir(), ".bashrc");
|
|
11
|
-
if (existsSync(zshrc)) return zshrc;
|
|
12
|
-
return bashrc;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function addToPath(appName: string, binDir: string): void {
|
|
16
|
-
const rcFile = getShellRc();
|
|
17
|
-
const content = existsSync(rcFile) ? readFileSync(rcFile, "utf-8") : "";
|
|
18
|
-
|
|
19
|
-
const exportLine = `export PATH="${binDir}:$PATH"`;
|
|
20
|
-
|
|
21
|
-
// Check if already linked
|
|
22
|
-
if (content.includes(exportLine)) {
|
|
23
|
-
console.log(`${appName} already in PATH`);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Find or create api2cli block
|
|
28
|
-
if (content.includes(MARKER_START)) {
|
|
29
|
-
// Insert before the end marker
|
|
30
|
-
const updated = content.replace(
|
|
31
|
-
MARKER_END,
|
|
32
|
-
`${exportLine}\n${MARKER_END}`
|
|
33
|
-
);
|
|
34
|
-
const { writeFileSync } = require("fs");
|
|
35
|
-
writeFileSync(rcFile, updated);
|
|
36
|
-
} else {
|
|
37
|
-
appendFileSync(rcFile, `\n${MARKER_START}\n${exportLine}\n${MARKER_END}\n`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
console.log(`Added ${appName} to PATH in ${rcFile}`);
|
|
41
|
-
console.log(`Run: source ${rcFile}`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function removeFromPath(appName: string, binDir: string): void {
|
|
45
|
-
const rcFile = getShellRc();
|
|
46
|
-
if (!existsSync(rcFile)) return;
|
|
47
|
-
|
|
48
|
-
const content = readFileSync(rcFile, "utf-8");
|
|
49
|
-
const exportLine = `export PATH="${binDir}:$PATH"`;
|
|
50
|
-
const updated = content.replace(`${exportLine}\n`, "");
|
|
51
|
-
|
|
52
|
-
const { writeFileSync } = require("fs");
|
|
53
|
-
writeFileSync(rcFile, updated);
|
|
54
|
-
console.log(`Removed ${appName} from PATH`);
|
|
55
|
-
}
|
package/src/lib/config.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { homedir } from "os";
|
|
2
|
-
import { join, resolve } from "path";
|
|
3
|
-
|
|
4
|
-
/** Root directory for all generated CLIs */
|
|
5
|
-
export const CLI_ROOT = join(homedir(), ".cli");
|
|
6
|
-
|
|
7
|
-
/** Centralized token storage directory */
|
|
8
|
-
export const TOKENS_DIR = join(homedir(), ".config", "tokens");
|
|
9
|
-
|
|
10
|
-
/** Template directory (relative to this package in the monorepo) */
|
|
11
|
-
export const TEMPLATE_DIR = resolve(import.meta.dir, "..", "..", "..", "template");
|
|
12
|
-
|
|
13
|
-
/** Placeholders used in the template that get replaced during create */
|
|
14
|
-
export const PLACEHOLDERS = [
|
|
15
|
-
"{{APP_NAME}}",
|
|
16
|
-
"{{APP_CLI}}",
|
|
17
|
-
"{{BASE_URL}}",
|
|
18
|
-
"{{AUTH_TYPE}}",
|
|
19
|
-
"{{AUTH_HEADER}}",
|
|
20
|
-
] as const;
|
|
21
|
-
|
|
22
|
-
/** Get the installation directory for a CLI */
|
|
23
|
-
export function getCliDir(app: string): string {
|
|
24
|
-
return join(CLI_ROOT, `${app}-cli`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Get the token file path for a CLI */
|
|
28
|
-
export function getTokenFile(app: string): string {
|
|
29
|
-
return join(TOKENS_DIR, `${app}-cli.txt`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Get the dist directory for a CLI */
|
|
33
|
-
export function getDistDir(app: string): string {
|
|
34
|
-
return join(getCliDir(app), "dist");
|
|
35
|
-
}
|
package/src/lib/shell.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, appendFileSync } from "fs";
|
|
2
|
-
import { homedir } from "os";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import pc from "picocolors";
|
|
5
|
-
|
|
6
|
-
const MARKER_START = "# >>> api2cli >>>";
|
|
7
|
-
const MARKER_END = "# <<< api2cli <<<";
|
|
8
|
-
|
|
9
|
-
/** Detect the user's shell rc file */
|
|
10
|
-
function getShellRc(): string {
|
|
11
|
-
const shell = process.env.SHELL ?? "";
|
|
12
|
-
if (shell.includes("zsh")) return join(homedir(), ".zshrc");
|
|
13
|
-
if (shell.includes("fish")) return join(homedir(), ".config", "fish", "config.fish");
|
|
14
|
-
// Check if .zshrc exists even if SHELL isn't set
|
|
15
|
-
const zshrc = join(homedir(), ".zshrc");
|
|
16
|
-
if (existsSync(zshrc)) return zshrc;
|
|
17
|
-
return join(homedir(), ".bashrc");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Add a CLI's dist directory to PATH via the shell rc file. Idempotent. */
|
|
21
|
-
export function addToPath(app: string, binDir: string): void {
|
|
22
|
-
const rcFile = getShellRc();
|
|
23
|
-
const content = existsSync(rcFile) ? readFileSync(rcFile, "utf-8") : "";
|
|
24
|
-
const exportLine = `export PATH="${binDir}:$PATH"`;
|
|
25
|
-
|
|
26
|
-
if (content.includes(exportLine)) {
|
|
27
|
-
console.log(`${pc.dim(app)} already in PATH`);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (content.includes(MARKER_START)) {
|
|
32
|
-
const updated = content.replace(MARKER_END, `${exportLine}\n${MARKER_END}`);
|
|
33
|
-
writeFileSync(rcFile, updated);
|
|
34
|
-
} else {
|
|
35
|
-
appendFileSync(rcFile, `\n${MARKER_START}\n${exportLine}\n${MARKER_END}\n`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
console.log(`${pc.green("+")} Added ${pc.bold(app)} to PATH in ${pc.dim(rcFile)}`);
|
|
39
|
-
console.log(` Run: ${pc.cyan(`source ${rcFile}`)}`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Remove a CLI from PATH in the shell rc file */
|
|
43
|
-
export function removeFromPath(app: string, binDir: string): void {
|
|
44
|
-
const rcFile = getShellRc();
|
|
45
|
-
if (!existsSync(rcFile)) return;
|
|
46
|
-
|
|
47
|
-
const content = readFileSync(rcFile, "utf-8");
|
|
48
|
-
const exportLine = `export PATH="${binDir}:$PATH"\n`;
|
|
49
|
-
|
|
50
|
-
if (!content.includes(exportLine)) {
|
|
51
|
-
console.log(`${pc.dim(app)} not in PATH`);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
writeFileSync(rcFile, content.replace(exportLine, ""));
|
|
56
|
-
console.log(`${pc.red("-")} Removed ${pc.bold(app)} from PATH`);
|
|
57
|
-
}
|
package/src/lib/template.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { existsSync, cpSync, readdirSync, statSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { TEMPLATE_DIR } from "./config.js";
|
|
4
|
-
|
|
5
|
-
interface TemplateVars {
|
|
6
|
-
appName: string;
|
|
7
|
-
appCli: string;
|
|
8
|
-
baseUrl: string;
|
|
9
|
-
authType: string;
|
|
10
|
-
authHeader: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Copy the template scaffold to a target directory */
|
|
14
|
-
export function copyTemplate(targetDir: string): void {
|
|
15
|
-
if (!existsSync(TEMPLATE_DIR)) {
|
|
16
|
-
throw new Error(`Template not found at ${TEMPLATE_DIR}. Run: api2cli doctor`);
|
|
17
|
-
}
|
|
18
|
-
cpSync(TEMPLATE_DIR, targetDir, { recursive: true });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Replace all {{PLACEHOLDER}} tokens in every file in a directory tree */
|
|
22
|
-
export function replacePlaceholders(dir: string, vars: TemplateVars): void {
|
|
23
|
-
const replacements: [string, string][] = [
|
|
24
|
-
["{{APP_NAME}}", vars.appName],
|
|
25
|
-
["{{APP_CLI}}", vars.appCli],
|
|
26
|
-
["{{BASE_URL}}", vars.baseUrl],
|
|
27
|
-
["{{AUTH_TYPE}}", vars.authType],
|
|
28
|
-
["{{AUTH_HEADER}}", vars.authHeader],
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
walkFiles(dir, (filePath) => {
|
|
32
|
-
// Skip binary files and node_modules
|
|
33
|
-
if (filePath.includes("node_modules")) return;
|
|
34
|
-
const ext = filePath.split(".").pop() ?? "";
|
|
35
|
-
if (!["ts", "js", "json", "md", "txt", "template"].includes(ext)) return;
|
|
36
|
-
|
|
37
|
-
let content = readFileSync(filePath, "utf-8");
|
|
38
|
-
let changed = false;
|
|
39
|
-
|
|
40
|
-
for (const [placeholder, value] of replacements) {
|
|
41
|
-
if (content.includes(placeholder)) {
|
|
42
|
-
content = content.replaceAll(placeholder, value);
|
|
43
|
-
changed = true;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (changed) {
|
|
48
|
-
writeFileSync(filePath, content);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Recursively walk all files in a directory */
|
|
54
|
-
function walkFiles(dir: string, callback: (path: string) => void): void {
|
|
55
|
-
for (const entry of readdirSync(dir)) {
|
|
56
|
-
const full = join(dir, entry);
|
|
57
|
-
if (statSync(full).isDirectory()) {
|
|
58
|
-
walkFiles(full, callback);
|
|
59
|
-
} else {
|
|
60
|
-
callback(full);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|