@letterapp/cli 0.1.0 → 0.3.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.
@@ -1,45 +0,0 @@
1
- import path from "node:path";
2
- import { readFile, writeFile, stat } from "node:fs/promises";
3
- /**
4
- * Upserts `key=value` in an env file, creating it if needed. Existing keys are
5
- * replaced in place; new keys are appended. Returns the file path. The value is
6
- * never logged by this module - callers print only the key name.
7
- */
8
- export async function upsertEnv(cwd, file, entries) {
9
- const filePath = path.join(cwd, file);
10
- let contents = "";
11
- try {
12
- contents = await readFile(filePath, "utf8");
13
- }
14
- catch {
15
- contents = "";
16
- }
17
- let next = contents;
18
- for (const [key, value] of Object.entries(entries)) {
19
- const line = `${key}=${value}`;
20
- const re = new RegExp(`^${escapeRegExp(key)}=.*$`, "m");
21
- if (re.test(next)) {
22
- next = next.replace(re, line);
23
- }
24
- else {
25
- if (next.length && !next.endsWith("\n"))
26
- next += "\n";
27
- next += `${line}\n`;
28
- }
29
- }
30
- await writeFile(filePath, next, "utf8");
31
- return filePath;
32
- }
33
- /** True if a file exists at `cwd/name`. */
34
- export async function fileExists(cwd, name) {
35
- try {
36
- await stat(path.join(cwd, name));
37
- return true;
38
- }
39
- catch {
40
- return false;
41
- }
42
- }
43
- function escapeRegExp(s) {
44
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45
- }
package/dist/lib/pm.js DELETED
@@ -1,72 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { fileExists } from "./env-file.js";
3
- /**
4
- * Detects the package manager from lockfiles (then the `npm_config_user_agent`
5
- * of the running process), defaulting to npm.
6
- */
7
- export async function detectPackageManager(cwd) {
8
- if (await fileExists(cwd, "pnpm-lock.yaml"))
9
- return "pnpm";
10
- if (await fileExists(cwd, "yarn.lock"))
11
- return "yarn";
12
- if (await fileExists(cwd, "bun.lockb"))
13
- return "bun";
14
- if (await fileExists(cwd, "package-lock.json"))
15
- return "npm";
16
- const ua = process.env.npm_config_user_agent ?? "";
17
- if (ua.startsWith("pnpm"))
18
- return "pnpm";
19
- if (ua.startsWith("yarn"))
20
- return "yarn";
21
- if (ua.startsWith("bun"))
22
- return "bun";
23
- return "npm";
24
- }
25
- /** Detects a likely web framework for friendlier guidance. */
26
- export async function detectFramework(cwd) {
27
- try {
28
- const pkgPath = `${cwd}/package.json`;
29
- const { readFile } = await import("node:fs/promises");
30
- const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
31
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
32
- if (deps.next)
33
- return "Next.js";
34
- if (deps.nuxt)
35
- return "Nuxt";
36
- if (deps["@remix-run/node"] || deps["@remix-run/react"])
37
- return "Remix";
38
- if (deps.express)
39
- return "Express";
40
- if (deps.fastify)
41
- return "Fastify";
42
- if (deps.hono)
43
- return "Hono";
44
- if (deps["@sveltejs/kit"])
45
- return "SvelteKit";
46
- return null;
47
- }
48
- catch {
49
- return null;
50
- }
51
- }
52
- export function installCommand(pm, pkg) {
53
- switch (pm) {
54
- case "pnpm":
55
- return `pnpm add ${pkg}`;
56
- case "yarn":
57
- return `yarn add ${pkg}`;
58
- case "bun":
59
- return `bun add ${pkg}`;
60
- default:
61
- return `npm install ${pkg}`;
62
- }
63
- }
64
- /** Runs the install command, streaming output. Resolves to the exit code. */
65
- export function runInstall(pm, pkg, cwd) {
66
- const args = pm === "npm" ? ["install", pkg] : pm === "yarn" ? ["add", pkg] : ["add", pkg];
67
- return new Promise((resolve) => {
68
- const child = spawn(pm, args, { cwd, stdio: "inherit", shell: process.platform === "win32" });
69
- child.on("error", () => resolve(1));
70
- child.on("close", (code) => resolve(code ?? 1));
71
- });
72
- }
package/dist/lib/ui.js DELETED
@@ -1,57 +0,0 @@
1
- import readline from "node:readline";
2
- const useColor = process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== "dumb";
3
- function wrap(open, close) {
4
- return (s) => useColor ? `\u001b[${open}m${s}\u001b[${close}m` : s;
5
- }
6
- export const color = {
7
- bold: wrap(1, 22),
8
- dim: wrap(2, 22),
9
- red: wrap(31, 39),
10
- green: wrap(32, 39),
11
- yellow: wrap(33, 39),
12
- blue: wrap(34, 39),
13
- cyan: wrap(36, 39),
14
- gray: wrap(90, 39),
15
- };
16
- export function log(msg = "") {
17
- process.stdout.write(`${msg}\n`);
18
- }
19
- export function info(msg) {
20
- log(`${color.cyan("›")} ${msg}`);
21
- }
22
- export function success(msg) {
23
- log(`${color.green("✓")} ${msg}`);
24
- }
25
- export function warn(msg) {
26
- log(`${color.yellow("!")} ${msg}`);
27
- }
28
- export function error(msg) {
29
- process.stderr.write(`${color.red("✗")} ${msg}\n`);
30
- }
31
- export function step(n, total, msg) {
32
- log(`${color.dim(`[${n}/${total}]`)} ${msg}`);
33
- }
34
- /** The Letter wordmark banner. */
35
- export function banner() {
36
- log();
37
- log(` ${color.bold("Letter")}${color.red(".")} ${color.dim("CLI")}`);
38
- log();
39
- }
40
- /**
41
- * Prompts the user and resolves with their input. Returns "" immediately on a
42
- * non-interactive stdin so automated/agent runs don't hang.
43
- */
44
- export function prompt(question) {
45
- if (!process.stdin.isTTY)
46
- return Promise.resolve("");
47
- const rl = readline.createInterface({
48
- input: process.stdin,
49
- output: process.stdout,
50
- });
51
- return new Promise((resolve) => {
52
- rl.question(question, (answer) => {
53
- rl.close();
54
- resolve(answer);
55
- });
56
- });
57
- }