Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/cli/.npmignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
src/
tests/
coverage/
*.config.ts
*.config.js
.prettierrc
.prettierignore
tsconfig.json
.prettierrc
.prettierignore
.eslintrc*
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/.prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"semi": true,
"singleQuote": false,
"printWidth": 100,
"trailingComma": "all",
"tabWidth": 2
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
48 changes: 48 additions & 0 deletions packages/cli/src/build-watch.ts
Original file line number Diff line number Diff line change
@@ -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);
});
}
49 changes: 49 additions & 0 deletions packages/cli/src/changeset-helper.ts
Original file line number Diff line number Diff line change
@@ -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");
}
46 changes: 46 additions & 0 deletions packages/cli/src/npmignore-check.ts
Original file line number Diff line number Diff line change
@@ -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");
}
45 changes: 45 additions & 0 deletions packages/cli/src/prettier-config.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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]`);
}
Expand All @@ -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}`,
Expand Down
Loading