Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Adds a new @neaps/cli workspace package providing a neaps command-line interface for tide station search, extremes, timelines (ASCII chart), plus an embedded API server command, along with test coverage and release/binary distribution tooling.
Changes:
- Introduces
neapsCLI commands:stations,extremes,timeline, andservewith text/JSON output. - Adds Vitest-based CLI tests and supporting helpers.
- Adds build + distribution assets: SEA binary build script, install script, Homebrew formula template, and GitHub Actions workflow to build/upload binaries.
Reviewed changes
Copilot reviewed 30 out of 31 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/cli/vitest.config.ts | Adds Vitest project config for the new CLI package. |
| packages/cli/tsdown.config.ts | Adds tsdown build config for ESM output + d.ts. |
| packages/cli/tsconfig.json | Adds CLI package TS config extending repo defaults. |
| packages/cli/test/lib/station.test.ts | Adds tests around station resolution (--station, --near, --ip) and related errors. |
| packages/cli/test/lib/chart.test.ts | Adds tests for ASCII chart rendering behavior. |
| packages/cli/test/helpers.ts | Adds helper to run commander program and capture output for tests. |
| packages/cli/test/commands/timeline.test.ts | Adds end-to-end tests for neaps timeline in text/JSON modes. |
| packages/cli/test/commands/stations.test.ts | Adds end-to-end tests for neaps stations output + flags. |
| packages/cli/test/commands/serve.test.ts | Adds integration tests for neaps serve HTTP responses. |
| packages/cli/test/commands/extremes.test.ts | Adds end-to-end tests for neaps extremes and top-level --help. |
| packages/cli/src/program.ts | Defines the commander program and registers CLI subcommands. |
| packages/cli/src/lib/station.ts | Adds station/coordinate resolution helpers including --ip geolocation. |
| packages/cli/src/lib/chart.ts | Implements ASCII chart renderer for timeline output. |
| packages/cli/src/index.ts | CLI entrypoint to parse argv and handle errors. |
| packages/cli/src/formatters/text.ts | Text formatter for station lists, extremes, and timeline chart output. |
| packages/cli/src/formatters/json.ts | JSON formatter for machine-readable output. |
| packages/cli/src/formatters/index.ts | Formatter registry/types shared across commands. |
| packages/cli/src/commands/timeline.ts | Implements neaps timeline command and options. |
| packages/cli/src/commands/stations.ts | Implements neaps stations command and options. |
| packages/cli/src/commands/serve.ts | Implements neaps serve command to start the API server. |
| packages/cli/src/commands/extremes.ts | Implements neaps extremes command and options. |
| packages/cli/scripts/build-sea.ts | Adds Node SEA build script to generate a single executable. |
| packages/cli/package.json | Adds publishable CLI package metadata, bin entry, deps, and scripts. |
| packages/cli/homebrew/neaps.rb | Adds a Homebrew formula template for release automation. |
| packages/cli/bin/neaps.js | Adds executable shim that imports the built CLI entrypoint. |
| packages/cli/README.md | Adds CLI installation and usage documentation. |
| package.json | Adds packages/cli to workspaces. |
| install.sh | Adds shell installer for downloading prebuilt CLI binaries. |
| .github/workflows/release.yml | Updates release workflow Node version. |
| .github/workflows/build-binaries.yml | Adds workflow to build/package/upload CLI binaries and update Homebrew tap. |
| .changeset/config.json | Fixes Changesets GitHub repo reference. |
Comments suppressed due to low confidence (7)
packages/cli/src/commands/stations.ts:18
limitis computed withparseInt(opts.limit, 10)but never validated. If the user passes a non-numeric value (or0/negative),limitcan becomeNaNor an invalid number, which then causesslice(0, limit)to return[]and triggers a misleading "No stations found" error. Validatelimitas a positive integer (or handleInfinity) and return a specific error message for invalid--limit.
.action(async (query: string | undefined, opts) => {
const limit = opts.all ? Infinity : parseInt(opts.limit, 10);
let results: StationResult[];
packages/cli/src/program.ts:10
- The CLI version is hard-coded to
0.1.0, which can easily drift frompackages/cli/package.jsonas releases happen. Consider reading the version from package metadata at runtime/build time soneaps --versionalways matches the published package/binary version.
program.name("neaps").description("Tide prediction command line interface").version("0.1.0");
packages/cli/package.json:46
@neaps/tide-databaseis declared as"*". This makes installs non-reproducible and can pull in breaking changes unexpectedly for CLI consumers. Pin it to a specific version/range (or use a workspace protocol likeworkspace:*if it should always resolve to the monorepo package).
"dependencies": {
"@clack/prompts": "^0.10.0",
"@neaps/api": "^0.4.0",
"@neaps/tide-database": "*",
"commander": "^13.0.0",
"neaps": "^0.5.1"
packages/cli/README.md:28
- README claims binaries are available for "macOS (Intel & Apple Silicon)", but the build workflow only produces
darwin-arm64(and nodarwin-x64). Either add adarwin-x64build target or update the README to match the actual published artifacts.
### Download binary
Pre-built binaries for macOS (Intel & Apple Silicon), Linux, and Windows are available on the [GitHub Releases](https://github.com/openwatersio/neaps/releases) page.
packages/cli/src/commands/timeline.ts:29
new Date(opts.start/end)andparseInt(opts.interval, 10)are used without validation. Invalid date strings produceInvalid Dateand invalid/empty--intervalyieldsNaN, which then gets passed intogetTimelinePredictionasstart/end/timeFidelityand can cause confusing downstream failures. Validate these inputs (e.g.,isNaN(date.getTime()),Number.isFinite(intervalMinutes)and> 0) and throw a clear CLI error early.
const start = opts.start ? new Date(opts.start) : new Date();
const end = opts.end ? new Date(opts.end) : new Date(start.getTime() + 24 * 60 * 60 * 1000);
const timeFidelity = parseInt(opts.interval, 10) * 60;
packages/cli/src/commands/serve.ts:28
- The server startup promise never rejects on listen errors (e.g.,
EADDRINUSE, invalid--port), so failures can surface as unhandled events/hanging behavior. Add validation forport(finite integer in range) and attach aserver.on('error', ...)handler to reject/throw a clear message when the port can't be bound.
.action(async (opts) => {
const port = parseInt(opts.port, 10);
const app = createApp({ prefix: "/" });
await new Promise<void>((resolve) => {
const server = app.listen(port, () => {
console.log(`Neaps API listening on http://localhost:${port}`);
resolve();
});
abortController = new AbortController();
abortController.signal.addEventListener("abort", () => {
server.close(() => {
console.log("Neaps API server stopped");
});
});
packages/cli/test/helpers.ts:27
- The test helper monkey-patches
process.stdout.write/process.stderr.writewith functions that don't match Node's fullwrite(chunk, encoding?, cb?)signature. This can break libraries that pass an encoding/callback and lead to flaky tests. Prefer spying/mocking withvi.spyOn(...).mockImplementation((chunk, ...rest) => ...)or implement a wrapper that accepts...argsand forwards/handles them correctly.
const originalWrite = process.stdout.write;
process.stdout.write = ((chunk: unknown) => {
chunks.push(String(chunk));
return true;
}) as typeof process.stdout.write;
// Suppress clack's stderr output in tests
const originalStderrWrite = process.stderr.write;
process.stderr.write = (() => true) as typeof process.stderr.write;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Command line interface for tide predictions. Search for stations, view high/low tides, and generate water level timelines from your terminal. It includes pre-built binaries for macOS, Linux, and Windows.
This supercedes #182