Skip to content

Add neaps Command Line Interface#218

Merged
bkeepers merged 17 commits intomainfrom
cli-v2
Feb 25, 2026
Merged

Add neaps Command Line Interface#218
bkeepers merged 17 commits intomainfrom
cli-v2

Conversation

@bkeepers
Copy link
Contributor

@bkeepers bkeepers commented Feb 18, 2026

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

@codecov
Copy link

codecov bot commented Feb 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@bkeepers bkeepers changed the title CLI (take 2) Add neaps Command Line Interface Feb 18, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 neaps CLI commands: stations, extremes, timeline, and serve with 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

  • limit is computed with parseInt(opts.limit, 10) but never validated. If the user passes a non-numeric value (or 0/negative), limit can become NaN or an invalid number, which then causes slice(0, limit) to return [] and triggers a misleading "No stations found" error. Validate limit as a positive integer (or handle Infinity) 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 from packages/cli/package.json as releases happen. Consider reading the version from package metadata at runtime/build time so neaps --version always 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-database is 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 like workspace:* 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 no darwin-x64). Either add a darwin-x64 build 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) and parseInt(opts.interval, 10) are used without validation. Invalid date strings produce Invalid Date and invalid/empty --interval yields NaN, which then gets passed into getTimelinePrediction as start/end/timeFidelity and 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 for port (finite integer in range) and attach a server.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.write with functions that don't match Node's full write(chunk, encoding?, cb?) signature. This can break libraries that pass an encoding/callback and lead to flaky tests. Prefer spying/mocking with vi.spyOn(...).mockImplementation((chunk, ...rest) => ...) or implement a wrapper that accepts ...args and 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.

@bkeepers bkeepers merged commit fe7ddbf into main Feb 25, 2026
8 checks passed
@bkeepers bkeepers deleted the cli-v2 branch February 25, 2026 04:40
@github-actions github-actions bot mentioned this pull request Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants