diff --git a/packages/cli/.npmignore b/packages/cli/.npmignore index 6337d94..bddc101 100644 --- a/packages/cli/.npmignore +++ b/packages/cli/.npmignore @@ -1,6 +1,11 @@ src/ tests/ coverage/ +*.config.ts +*.config.js +.prettierrc +.prettierignore +tsconfig.json .prettierrc .prettierignore .eslintrc* diff --git a/packages/cli/.prettierrc b/packages/cli/.prettierrc index 1d9d0f6..2942f4b 100644 --- a/packages/cli/.prettierrc +++ b/packages/cli/.prettierrc @@ -1,5 +1,9 @@ { "semi": true, + "singleQuote": false, + "printWidth": 100, + "trailingComma": "all", + "tabWidth": 2 "singleQuote": true, "trailingComma": "all", "printWidth": 100, diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index ea9d18b..38239fb 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,3 +1,10 @@ +# @stellar-explain/cli + +## 0.1.0 + +### Initial release + +- CLI for querying and explaining Stellar transactions via the Stellar Explain API # @stellar-explain/cli Changelog All notable changes to this package will be documented here. diff --git a/packages/cli/src/build-watch.ts b/packages/cli/src/build-watch.ts new file mode 100644 index 0000000..42e78c3 --- /dev/null +++ b/packages/cli/src/build-watch.ts @@ -0,0 +1,48 @@ +/** + * Spawns the TypeScript compiler in watch mode for CLI development. + * Issue #333 — Add a build:watch script for CLI development. + */ + +import { spawn, ChildProcess } from "child_process"; +import path from "path"; + +interface WatchOptions { + cwd?: string; + silent?: boolean; +} + +export function startWatcher(opts: WatchOptions = {}): ChildProcess { + const cwd = opts.cwd ?? path.resolve(__dirname, ".."); + const tsc = process.platform === "win32" ? "tsc.cmd" : "tsc"; + + const child = spawn(tsc, ["--watch", "--preserveWatchOutput"], { + cwd, + stdio: opts.silent ? "ignore" : "inherit", + shell: false, + }); + + child.on("error", (err) => { + console.error("Failed to start tsc watcher:", err.message); + process.exit(1); + }); + + child.on("exit", (code) => { + if (code !== null && code !== 0) { + console.error(`tsc exited with code ${code}`); + } + }); + + return child; +} + +export function stopWatcher(child: ChildProcess): void { + if (!child.killed) child.kill("SIGTERM"); +} + +if (require.main === module) { + const watcher = startWatcher(); + process.on("SIGINT", () => { + stopWatcher(watcher); + process.exit(0); + }); +} diff --git a/packages/cli/src/changeset-helper.ts b/packages/cli/src/changeset-helper.ts new file mode 100644 index 0000000..3ed0f07 --- /dev/null +++ b/packages/cli/src/changeset-helper.ts @@ -0,0 +1,49 @@ +/** + * Reads and validates the Changesets config for the CLI package. + * Issue #335 — Add Changesets for CLI version management. + */ + +import fs from "fs"; +import path from "path"; + +interface ChangesetConfig { + $schema: string; + changelog: string; + commit: boolean; + access: "public" | "restricted"; + baseBranch: string; + updateInternalDependencies: "patch" | "minor"; + ignore: string[]; +} + +export function loadChangesetConfig(root: string): ChangesetConfig | null { + const configPath = path.join(root, ".changeset", "config.json"); + if (!fs.existsSync(configPath)) return null; + try { + return JSON.parse(fs.readFileSync(configPath, "utf-8")) as ChangesetConfig; + } catch { + return null; + } +} + +export function validateChangesetConfig(config: ChangesetConfig): string[] { + const errors: string[] = []; + if (config.access !== "public") errors.push(`access should be "public"`); + if (config.baseBranch !== "main") errors.push(`baseBranch should be "main"`); + if (config.commit !== false) errors.push(`commit should be false`); + return errors; +} + +export function checkChangesetSetup(root: string): void { + const config = loadChangesetConfig(root); + if (!config) { + console.error("Missing .changeset/config.json"); + process.exit(1); + } + const errors = validateChangesetConfig(config); + if (errors.length > 0) { + console.error("Changeset config issues:\n" + errors.join("\n")); + process.exit(1); + } + console.log("Changeset config OK"); +} diff --git a/packages/cli/src/npmignore-check.ts b/packages/cli/src/npmignore-check.ts new file mode 100644 index 0000000..16b3f9a --- /dev/null +++ b/packages/cli/src/npmignore-check.ts @@ -0,0 +1,46 @@ +/** + * Verifies .npmignore excludes dev-only paths from the published package. + * Issue #334 — Add .npmignore to keep published package lean. + */ + +import fs from "fs"; +import path from "path"; + +const REQUIRED_EXCLUSIONS = [ + "src/", + "tests/", + "coverage/", + "*.config.ts", + "*.config.js", + ".prettierrc", + ".prettierignore", + "tsconfig.json", +]; + +export function loadNpmIgnore(root: string): string[] { + const filePath = path.join(root, ".npmignore"); + if (!fs.existsSync(filePath)) return []; + return fs + .readFileSync(filePath, "utf-8") + .split("\n") + .map((l) => l.trim()) + .filter(Boolean); +} + +export function findMissingExclusions(entries: string[]): string[] { + return REQUIRED_EXCLUSIONS.filter((req) => !entries.includes(req)); +} + +export function checkNpmIgnore(root: string): void { + const entries = loadNpmIgnore(root); + if (entries.length === 0) { + console.error(".npmignore is missing or empty"); + process.exit(1); + } + const missing = findMissingExclusions(entries); + if (missing.length > 0) { + console.error("Missing exclusions in .npmignore:\n" + missing.join("\n")); + process.exit(1); + } + console.log(".npmignore OK — all required exclusions present"); +} diff --git a/packages/cli/src/prettier-config.ts b/packages/cli/src/prettier-config.ts index 5f024ce..afe7738 100644 --- a/packages/cli/src/prettier-config.ts +++ b/packages/cli/src/prettier-config.ts @@ -1,9 +1,37 @@ +/** + * Validates that the Prettier config exists and matches expected rules. + * Issue #332 — Add Prettier config to the CLI package. + */ + +import fs from "fs"; +import path from "path"; import * as fs from 'fs'; import * as path from 'path'; interface PrettierConfig { semi: boolean; singleQuote: boolean; + printWidth: number; + trailingComma: string; + tabWidth: number; +} + +const EXPECTED: PrettierConfig = { + semi: true, + singleQuote: false, + printWidth: 100, + trailingComma: "all", + tabWidth: 2, +}; + +export function loadPrettierConfig(root: string): PrettierConfig | null { + const configPath = path.join(root, ".prettierrc"); + if (!fs.existsSync(configPath)) return null; + try { + return JSON.parse(fs.readFileSync(configPath, "utf-8")) as PrettierConfig; + } catch { + return null; + } trailingComma: string; printWidth: number; tabWidth: number; @@ -30,6 +58,11 @@ export function loadPrettierConfig(packageDir: string): PrettierConfig { export function validatePrettierConfig(config: PrettierConfig): string[] { const errors: string[] = []; + for (const [key, expected] of Object.entries(EXPECTED)) { + const actual = config[key as keyof PrettierConfig]; + if (actual !== expected) { + errors.push(`"${key}": expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); + } if (config.printWidth < 40 || config.printWidth > 200) { errors.push(`printWidth ${config.printWidth} is out of range [40, 200]`); } @@ -42,6 +75,18 @@ export function validatePrettierConfig(config: PrettierConfig): string[] { return errors; } +export function checkPrettierSetup(root: string): void { + const config = loadPrettierConfig(root); + if (!config) { + console.error("Missing .prettierrc in", root); + process.exit(1); + } + const errors = validatePrettierConfig(config); + if (errors.length > 0) { + console.error("Prettier config mismatch:\n" + errors.join("\n")); + process.exit(1); + } + console.log("Prettier config OK"); export function describePrettierConfig(config: PrettierConfig): string { const parts = [ `printWidth=${config.printWidth}`,