diff --git a/AGENTS.md b/AGENTS.md index c62bcae8f..5761af840 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -269,6 +269,40 @@ export const myCommand = buildCommand({ - The wrapper auto-injects `--json` and `--fields` flags. Do NOT add your own `json` flag. - Do NOT use `stdout.write()` or `if (flags.json)` branching — the wrapper handles it. +### Route Maps (Stricli) + +Route groups use Stricli's `buildRouteMap` wrapped by `src/lib/route-map.ts`. + +**CRITICAL**: Import `buildRouteMap` from `../../lib/route-map.js`, **NEVER** from `@stricli/core` directly — the wrapper auto-injects standard subcommand aliases based on which route keys exist: + +| Route | Auto-aliases | +|----------|----------------| +| `list` | `ls` | +| `view` | `show` | +| `delete` | `remove`, `rm` | +| `create` | `new` | + +Manually specified aliases in `aliases` are merged with (and take precedence over) auto-generated ones. Do NOT manually add aliases that are already in the standard set above. + +```typescript +import { buildRouteMap } from "../../lib/route-map.js"; + +export const myRoute = buildRouteMap({ + routes: { + list: listCommand, + view: viewCommand, + create: createCommand, + }, + defaultCommand: "view", + // No need for aliases — ls, show, and new are auto-injected. + // Only add aliases for non-standard mappings: + // aliases: { custom: "list" }, + docs: { + brief: "Manage my resources", + }, +}); +``` + ### Positional Arguments Use `parseSlashSeparatedArg` from `src/lib/arg-parsing.ts` for the standard `[//]` pattern. Required identifiers (trace IDs, span IDs) should be **positional args**, not flags. diff --git a/biome.jsonc b/biome.jsonc index 6f67b506b..f80b99417 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -22,8 +22,8 @@ "options": { "paths": { "@stricli/core": { - "importNames": ["buildCommand"], - "message": "Import buildCommand from '../lib/command.js' instead. The wrapper injects telemetry, --log-level, and --verbose." + "importNames": ["buildCommand", "buildRouteMap"], + "message": "Import buildCommand from '../lib/command.js' and buildRouteMap from '../lib/route-map.js' instead. The wrappers inject telemetry and standard subcommand aliases." } } } @@ -89,8 +89,8 @@ } }, { - // command.ts is the canonical wrapper — it must import buildCommand from @stricli/core - "includes": ["src/lib/command.ts"], + // command.ts and route-map.ts are the canonical wrappers — they must import from @stricli/core + "includes": ["src/lib/command.ts", "src/lib/route-map.ts"], "linter": { "rules": { "style": { @@ -111,8 +111,8 @@ "options": { "paths": { "@stricli/core": { - "importNames": ["buildCommand"], - "message": "Import buildCommand from '../lib/command.js' instead. The wrapper injects telemetry, --log-level, and --verbose." + "importNames": ["buildCommand", "buildRouteMap"], + "message": "Import buildCommand from '../lib/command.js' and buildRouteMap from '../lib/route-map.js' instead. The wrappers inject telemetry and standard subcommand aliases." }, "chalk": { "message": "Use colorTag() from formatters/markdown.js for colored output. Raw chalk bypasses the plain-output pipeline (NO_COLOR, SENTRY_PLAIN_OUTPUT)." diff --git a/docs/src/content/docs/commands/dashboard.md b/docs/src/content/docs/commands/dashboard.md index 51ce630e0..d40c7e971 100644 --- a/docs/src/content/docs/commands/dashboard.md +++ b/docs/src/content/docs/commands/dashboard.md @@ -43,7 +43,7 @@ View a dashboard | `-w, --web` | Open in browser | | `-f, --fresh` | Bypass cache, re-detect projects, and fetch fresh data | | `-r, --refresh ` | Auto-refresh interval in seconds (default: 60, min: 10) | -| `-t, --period ` | Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" | +| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" | ### `sentry dashboard create ` diff --git a/docs/src/content/docs/commands/event.md b/docs/src/content/docs/commands/event.md index 34d1e0465..87ca42361 100644 --- a/docs/src/content/docs/commands/event.md +++ b/docs/src/content/docs/commands/event.md @@ -42,7 +42,7 @@ List events for an issue | `-n, --limit ` | Number of events (1-1000) (default: "25") | | `-q, --query ` | Search query (Sentry search syntax) | | `--full` | Include full event body (stacktraces) | -| `-t, --period ` | Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" (default: "7d") | +| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" (default: "7d") | | `-f, --fresh` | Bypass cache, re-detect projects, and fetch fresh data | | `-c, --cursor ` | Navigate pages: "next", "prev", "first" (or raw cursor string) | diff --git a/docs/src/content/docs/commands/issue.md b/docs/src/content/docs/commands/issue.md index 7b5fb9d32..1eb4503c9 100644 --- a/docs/src/content/docs/commands/issue.md +++ b/docs/src/content/docs/commands/issue.md @@ -24,7 +24,7 @@ List issues in a project | `-q, --query ` | Search query (Sentry search syntax) | | `-n, --limit ` | Maximum number of issues to list (default: "25") | | `-s, --sort ` | Sort by: date, new, freq, user (default: "date") | -| `-t, --period ` | Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" (default: "90d") | +| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" (default: "90d") | | `-c, --cursor ` | Pagination cursor (use "next" for next page, "prev" for previous) | | `--compact` | Single-line rows for compact output (auto-detects if omitted) | | `-f, --fresh` | Bypass cache, re-detect projects, and fetch fresh data | @@ -46,7 +46,7 @@ List events for a specific issue | `-n, --limit ` | Number of events (1-1000) (default: "25") | | `-q, --query ` | Search query (Sentry search syntax) | | `--full` | Include full event body (stacktraces) | -| `-t, --period ` | Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" (default: "7d") | +| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" (default: "7d") | | `-f, --fresh` | Bypass cache, re-detect projects, and fetch fresh data | | `-c, --cursor ` | Navigate pages: "next", "prev", "first" (or raw cursor string) | diff --git a/docs/src/content/docs/commands/log.md b/docs/src/content/docs/commands/log.md index dcbd6b470..bb6c82343 100644 --- a/docs/src/content/docs/commands/log.md +++ b/docs/src/content/docs/commands/log.md @@ -24,7 +24,7 @@ List logs from a project | `-n, --limit ` | Number of log entries (1-1000) (default: "100") | | `-q, --query ` | Filter query (Sentry search syntax) | | `-f, --follow ` | Stream logs (optionally specify poll interval in seconds) | -| `-t, --period ` | Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" | +| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" | | `-s, --sort ` | Sort order: "newest" (default) or "oldest" (default: "newest") | | `--fresh` | Bypass cache, re-detect projects, and fetch fresh data | diff --git a/docs/src/content/docs/commands/span.md b/docs/src/content/docs/commands/span.md index 053367ccb..85bfef5da 100644 --- a/docs/src/content/docs/commands/span.md +++ b/docs/src/content/docs/commands/span.md @@ -24,7 +24,7 @@ List spans in a project or trace | `-n, --limit ` | Number of spans (<=1000) (default: "25") | | `-q, --query ` | Filter spans (e.g., "op:db", "duration:>100ms", "project:backend") | | `-s, --sort ` | Sort order: date, duration (default: "date") | -| `-t, --period ` | Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" (default: "7d") | +| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" (default: "7d") | | `-f, --fresh` | Bypass cache, re-detect projects, and fetch fresh data | | `-c, --cursor ` | Navigate pages: "next", "prev", "first" (or raw cursor string) | diff --git a/docs/src/content/docs/commands/trace.md b/docs/src/content/docs/commands/trace.md index b5d8c1e07..162168b85 100644 --- a/docs/src/content/docs/commands/trace.md +++ b/docs/src/content/docs/commands/trace.md @@ -24,7 +24,7 @@ List recent traces in a project | `-n, --limit ` | Number of traces (1-1000) (default: "25") | | `-q, --query ` | Search query (Sentry search syntax) | | `-s, --sort ` | Sort by: date, duration (default: "date") | -| `-t, --period ` | Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" (default: "7d") | +| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" (default: "7d") | | `-f, --fresh` | Bypass cache, re-detect projects, and fetch fresh data | | `-c, --cursor ` | Navigate pages: "next", "prev", "first" (or raw cursor string) | @@ -61,7 +61,7 @@ View logs associated with a trace | Option | Description | |--------|-------------| | `-w, --web` | Open trace in browser | -| `-t, --period ` | Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" (default: "14d") | +| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" (default: "14d") | | `-n, --limit ` | Number of log entries (<=1000) (default: "100") | | `-q, --query ` | Additional filter query (Sentry search syntax) | | `-s, --sort ` | Sort order: "newest" (default) or "oldest" (default: "newest") | diff --git a/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md b/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md index e1f837354..0211a409f 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md @@ -42,7 +42,7 @@ View a dashboard - `-w, --web - Open in browser` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-r, --refresh - Auto-refresh interval in seconds (default: 60, min: 10)` -- `-t, --period - Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07"` +- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08"` **Examples:** diff --git a/plugins/sentry-cli/skills/sentry-cli/references/event.md b/plugins/sentry-cli/skills/sentry-cli/references/event.md index 573eb2e1d..2a1fe67b5 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/event.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/event.md @@ -28,7 +28,7 @@ List events for an issue - `-n, --limit - Number of events (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry search syntax)` - `--full - Include full event body (stacktraces)` -- `-t, --period - Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/issue.md b/plugins/sentry-cli/skills/sentry-cli/references/issue.md index 197f9da95..d1d699399 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/issue.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/issue.md @@ -19,7 +19,7 @@ List issues in a project - `-q, --query - Search query (Sentry search syntax)` - `-n, --limit - Maximum number of issues to list - (default: "25")` - `-s, --sort - Sort by: date, new, freq, user - (default: "date")` -- `-t, --period - Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" - (default: "90d")` +- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" - (default: "90d")` - `-c, --cursor - Pagination cursor (use "next" for next page, "prev" for previous)` - `--compact - Single-line rows for compact output (auto-detects if omitted)` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` @@ -78,7 +78,7 @@ List events for a specific issue - `-n, --limit - Number of events (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry search syntax)` - `--full - Include full event body (stacktraces)` -- `-t, --period - Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/log.md b/plugins/sentry-cli/skills/sentry-cli/references/log.md index a75fd06e0..6701287c1 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/log.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/log.md @@ -19,7 +19,7 @@ List logs from a project - `-n, --limit - Number of log entries (1-1000) - (default: "100")` - `-q, --query - Filter query (Sentry search syntax)` - `-f, --follow - Stream logs (optionally specify poll interval in seconds)` -- `-t, --period - Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07"` +- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08"` - `-s, --sort - Sort order: "newest" (default) or "oldest" - (default: "newest")` - `--fresh - Bypass cache, re-detect projects, and fetch fresh data` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/span.md b/plugins/sentry-cli/skills/sentry-cli/references/span.md index 0bba2e955..98e3125f0 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/span.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/span.md @@ -19,7 +19,7 @@ List spans in a project or trace - `-n, --limit - Number of spans (<=1000) - (default: "25")` - `-q, --query - Filter spans (e.g., "op:db", "duration:>100ms", "project:backend")` - `-s, --sort - Sort order: date, duration - (default: "date")` -- `-t, --period - Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/trace.md b/plugins/sentry-cli/skills/sentry-cli/references/trace.md index dbbd6e8aa..6718fa6ad 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/trace.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/trace.md @@ -19,7 +19,7 @@ List recent traces in a project - `-n, --limit - Number of traces (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry search syntax)` - `-s, --sort - Sort by: date, duration - (default: "date")` -- `-t, --period - Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` @@ -78,7 +78,7 @@ View logs associated with a trace **Flags:** - `-w, --web - Open trace in browser` -- `-t, --period - Time range: "7d", "2026-03-07..2026-04-07", ">=2026-03-07" - (default: "14d")` +- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" - (default: "14d")` - `-n, --limit - Number of log entries (<=1000) - (default: "100")` - `-q, --query - Additional filter query (Sentry search syntax)` - `-s, --sort - Sort order: "newest" (default) or "oldest" - (default: "newest")` diff --git a/src/app.ts b/src/app.ts index e12e5401b..c17326d03 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,7 +3,6 @@ import * as Sentry from "@sentry/node-core/light"; import { type ApplicationText, buildApplication, - buildRouteMap, text_en, UnexpectedPositionalError, UnsatisfiedPositionalError, @@ -55,6 +54,7 @@ import { } from "./lib/errors.js"; import { error as errorColor, warning } from "./lib/formatters/colors.js"; import { isRouteMap, type RouteMap } from "./lib/introspect.js"; +import { buildRouteMap } from "./lib/route-map.js"; /** * Plural alias → singular route name mapping. diff --git a/src/commands/auth/index.ts b/src/commands/auth/index.ts index cdb2541a8..efd2c1ea9 100644 --- a/src/commands/auth/index.ts +++ b/src/commands/auth/index.ts @@ -1,4 +1,4 @@ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { loginCommand } from "./login.js"; import { logoutCommand } from "./logout.js"; import { refreshCommand } from "./refresh.js"; diff --git a/src/commands/cli/index.ts b/src/commands/cli/index.ts index 7d72f63dd..1273bd681 100644 --- a/src/commands/cli/index.ts +++ b/src/commands/cli/index.ts @@ -1,4 +1,4 @@ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { feedbackCommand } from "./feedback.js"; import { fixCommand } from "./fix.js"; import { setupCommand } from "./setup.js"; diff --git a/src/commands/dashboard/index.ts b/src/commands/dashboard/index.ts index 43f17aabd..27cffd8e8 100644 --- a/src/commands/dashboard/index.ts +++ b/src/commands/dashboard/index.ts @@ -1,4 +1,4 @@ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { createCommand } from "./create.js"; import { listCommand } from "./list.js"; import { viewCommand } from "./view.js"; @@ -12,7 +12,6 @@ export const dashboardRoute = buildRouteMap({ widget: widgetRoute, }, defaultCommand: "view", - aliases: { show: "view" }, docs: { brief: "Manage Sentry dashboards", fullDescription: diff --git a/src/commands/dashboard/widget/index.ts b/src/commands/dashboard/widget/index.ts index a0b9cbb81..131d76baa 100644 --- a/src/commands/dashboard/widget/index.ts +++ b/src/commands/dashboard/widget/index.ts @@ -1,4 +1,4 @@ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../../lib/route-map.js"; import { addCommand } from "./add.js"; import { deleteCommand } from "./delete.js"; import { editCommand } from "./edit.js"; @@ -9,7 +9,6 @@ export const widgetRoute = buildRouteMap({ edit: editCommand, delete: deleteCommand, }, - aliases: { remove: "delete" }, docs: { brief: "Manage dashboard widgets", fullDescription: diff --git a/src/commands/event/index.ts b/src/commands/event/index.ts index 947520e8f..b1909fadc 100644 --- a/src/commands/event/index.ts +++ b/src/commands/event/index.ts @@ -1,4 +1,4 @@ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { listCommand } from "./list.js"; import { viewCommand } from "./view.js"; @@ -8,7 +8,6 @@ export const eventRoute = buildRouteMap({ list: listCommand, }, defaultCommand: "view", - aliases: { show: "view" }, docs: { brief: "View and list Sentry events", fullDescription: diff --git a/src/commands/issue/index.ts b/src/commands/issue/index.ts index fd8134daf..9fdb513d8 100644 --- a/src/commands/issue/index.ts +++ b/src/commands/issue/index.ts @@ -1,4 +1,4 @@ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { eventsCommand } from "./events.js"; import { explainCommand } from "./explain.js"; import { listCommand } from "./list.js"; @@ -14,7 +14,6 @@ export const issueRoute = buildRouteMap({ view: viewCommand, }, defaultCommand: "view", - aliases: { show: "view" }, docs: { brief: "Manage Sentry issues", fullDescription: diff --git a/src/commands/log/index.ts b/src/commands/log/index.ts index f05c06829..79e75bd41 100644 --- a/src/commands/log/index.ts +++ b/src/commands/log/index.ts @@ -4,7 +4,7 @@ * View and stream logs from Sentry projects. */ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { listCommand } from "./list.js"; import { viewCommand } from "./view.js"; @@ -14,7 +14,6 @@ export const logRoute = buildRouteMap({ view: viewCommand, }, defaultCommand: "view", - aliases: { show: "view" }, docs: { brief: "View Sentry logs", fullDescription: diff --git a/src/commands/org/index.ts b/src/commands/org/index.ts index 8010efbc0..d83886f8f 100644 --- a/src/commands/org/index.ts +++ b/src/commands/org/index.ts @@ -1,4 +1,4 @@ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { listCommand } from "./list.js"; import { viewCommand } from "./view.js"; @@ -8,7 +8,6 @@ export const orgRoute = buildRouteMap({ view: viewCommand, }, defaultCommand: "view", - aliases: { show: "view" }, docs: { brief: "Work with Sentry organizations", fullDescription: diff --git a/src/commands/project/index.ts b/src/commands/project/index.ts index 7c57caff8..77709bf73 100644 --- a/src/commands/project/index.ts +++ b/src/commands/project/index.ts @@ -1,4 +1,4 @@ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { createCommand } from "./create.js"; import { deleteCommand } from "./delete.js"; import { listCommand } from "./list.js"; @@ -12,7 +12,6 @@ export const projectRoute = buildRouteMap({ view: viewCommand, }, defaultCommand: "view", - aliases: { show: "view", remove: "delete" }, docs: { brief: "Work with Sentry projects", fullDescription: diff --git a/src/commands/release/index.ts b/src/commands/release/index.ts index 0139d05e2..11b2195b2 100644 --- a/src/commands/release/index.ts +++ b/src/commands/release/index.ts @@ -4,7 +4,7 @@ * Route map for release management commands. */ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { createCommand } from "./create.js"; import { deleteCommand } from "./delete.js"; import { deployCommand } from "./deploy.js"; diff --git a/src/commands/repo/index.ts b/src/commands/repo/index.ts index 468df2a30..a284693b5 100644 --- a/src/commands/repo/index.ts +++ b/src/commands/repo/index.ts @@ -1,4 +1,4 @@ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { listCommand } from "./list.js"; export const repoRoute = buildRouteMap({ diff --git a/src/commands/sourcemap/index.ts b/src/commands/sourcemap/index.ts index 5c8664fcd..2adae2ae2 100644 --- a/src/commands/sourcemap/index.ts +++ b/src/commands/sourcemap/index.ts @@ -1,4 +1,4 @@ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { injectCommand } from "./inject.js"; import { uploadCommand } from "./upload.js"; diff --git a/src/commands/span/index.ts b/src/commands/span/index.ts index 3b6dfffda..13c504400 100644 --- a/src/commands/span/index.ts +++ b/src/commands/span/index.ts @@ -4,7 +4,7 @@ * List and explore individual spans within distributed traces or across projects. */ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { listCommand } from "./list.js"; import { viewCommand } from "./view.js"; @@ -14,7 +14,6 @@ export const spanRoute = buildRouteMap({ view: viewCommand, }, defaultCommand: "view", - aliases: { show: "view" }, docs: { brief: "List and view spans in projects or traces", fullDescription: diff --git a/src/commands/team/index.ts b/src/commands/team/index.ts index e0a75f51e..c22523cc0 100644 --- a/src/commands/team/index.ts +++ b/src/commands/team/index.ts @@ -1,4 +1,4 @@ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { listCommand } from "./list.js"; export const teamRoute = buildRouteMap({ diff --git a/src/commands/trace/index.ts b/src/commands/trace/index.ts index 99c9be710..258eb1306 100644 --- a/src/commands/trace/index.ts +++ b/src/commands/trace/index.ts @@ -4,7 +4,7 @@ * View and explore distributed traces from Sentry projects. */ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { listCommand } from "./list.js"; import { logsCommand } from "./logs.js"; import { viewCommand } from "./view.js"; @@ -16,7 +16,6 @@ export const traceRoute = buildRouteMap({ logs: logsCommand, }, defaultCommand: "view", - aliases: { show: "view" }, docs: { brief: "View distributed traces", fullDescription: diff --git a/src/commands/trial/index.ts b/src/commands/trial/index.ts index 5128e86c0..525c20a5e 100644 --- a/src/commands/trial/index.ts +++ b/src/commands/trial/index.ts @@ -1,4 +1,4 @@ -import { buildRouteMap } from "@stricli/core"; +import { buildRouteMap } from "../../lib/route-map.js"; import { listCommand } from "./list.js"; import { startCommand } from "./start.js"; diff --git a/src/lib/list-command.ts b/src/lib/list-command.ts index a620e9708..123e354a7 100644 --- a/src/lib/list-command.ts +++ b/src/lib/list-command.ts @@ -335,6 +335,31 @@ export const LIST_BASE_ALIASES: Aliases = { n: "limit", c: "cursor" }; let _subcommandsByRoute: Map> | undefined; +/** + * Entry shape returned by Stricli's getAllEntries(). + * `aliases` is always present at runtime (empty array when none), + * but typed as optional for defensive safety. + */ +type RouteEntry = { + name: { original: string }; + aliases?: readonly string[]; + target: unknown; +}; + +/** Collect all subcommand names and aliases from a route group's children. */ +function collectChildNames(parent: { + getAllEntries: () => readonly RouteEntry[]; +}): Set { + const names = new Set(); + for (const child of parent.getAllEntries()) { + names.add(child.name.original); + for (const alias of child.aliases ?? []) { + names.add(alias); + } + } + return names; +} + /** * Get the subcommand names for a given singular route (e.g. "project" → {"list", "view"}). * @@ -346,27 +371,18 @@ function getSubcommandsForRoute(routeName: string): Set { _subcommandsByRoute = new Map(); const { routes } = require("../app.js") as { - routes: { - getAllEntries: () => readonly { - name: { original: string }; - target: unknown; - }[]; - }; + routes: { getAllEntries: () => readonly RouteEntry[] }; }; for (const entry of routes.getAllEntries()) { const target = entry.target as unknown as Record; if (typeof target?.getAllEntries === "function") { - const children = ( - target.getAllEntries as () => readonly { - name: { original: string }; - }[] - )(); - const names = new Set(); - for (const child of children) { - names.add(child.name.original); - } - _subcommandsByRoute.set(entry.name.original, names); + _subcommandsByRoute.set( + entry.name.original, + collectChildNames( + target as { getAllEntries: () => readonly RouteEntry[] } + ) + ); } } } diff --git a/src/lib/route-map.ts b/src/lib/route-map.ts new file mode 100644 index 000000000..53e98e908 --- /dev/null +++ b/src/lib/route-map.ts @@ -0,0 +1,66 @@ +/** + * Drop-in replacement for `@stricli/core`'s `buildRouteMap`. + * + * Analogous to how `buildCommand` in `./command.ts` wraps Stricli's version, + * this is the **only** place that should import `buildRouteMap` from + * `@stricli/core`. All other files import from here. + */ + +import { + type CommandContext, + type RouteMap, + type RouteMapBuilderArguments, + buildRouteMap as stricliRouteMap, +} from "@stricli/core"; + +/** + * Standard subcommand aliases, auto-injected when the matching route key + * exists. Each entry maps a canonical route name to the set of short-form + * aliases that should resolve to it. + */ +const STANDARD_ALIASES = new Map>([ + ["list", new Set(["ls"])], + ["view", new Set(["show"])], + ["delete", new Set(["remove", "rm"])], + ["create", new Set(["new"])], +]); + +/** + * Build a route map with standard subcommand aliases auto-injected. + * + * | Route | Auto-aliases | + * |----------|----------------| + * | `list` | `ls` | + * | `view` | `show` | + * | `delete` | `remove`, `rm` | + * | `create` | `new` | + * + * Manually specified aliases in `args.aliases` take precedence over + * auto-generated ones. Aliases that would collide with actual route + * names are silently skipped. + */ +export function buildRouteMap< + R extends string, + CONTEXT extends CommandContext = CommandContext, +>(args: RouteMapBuilderArguments): RouteMap { + const routeKeys = new Set(Object.keys(args.routes)); + const autoAliases: Record = {}; + + for (const [routeName, aliases] of STANDARD_ALIASES) { + if (routeKeys.has(routeName)) { + for (const alias of aliases) { + if (!routeKeys.has(alias)) { + autoAliases[alias] = routeName; + } + } + } + } + + return stricliRouteMap({ + ...args, + aliases: { + ...autoAliases, + ...args.aliases, + } as RouteMapBuilderArguments["aliases"], + }); +} diff --git a/test/lib/command.test.ts b/test/lib/command.test.ts index bbecdf846..75833a800 100644 --- a/test/lib/command.test.ts +++ b/test/lib/command.test.ts @@ -11,7 +11,6 @@ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test"; import * as Sentry from "@sentry/node-core/light"; import { buildApplication, - buildRouteMap, type CommandContext, run, text_en, @@ -28,6 +27,7 @@ import { import { OutputError } from "../../src/lib/errors.js"; import { CommandOutput } from "../../src/lib/formatters/output.js"; import { LOG_LEVEL_NAMES, logger, setLogLevel } from "../../src/lib/logger.js"; +import { buildRouteMap } from "../../src/lib/route-map.js"; /** Minimal context for test commands */ type TestContext = CommandContext & {