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
272 changes: 262 additions & 10 deletions packages/cli/patches/@stricli%2Fcore@1.2.8.patch

Large diffs are not rendered by default.

70 changes: 61 additions & 9 deletions packages/cli/script/check-patches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,29 @@ for (const [key, patchPath] of Object.entries(patches)) {

/**
* Content assertions: verify a patch's *effect* is present in the installed
* package, not just that the version matches. Each entry checks that a stale
* (pre-patch) marker is absent from a given installed file. If the marker is
* still present, the patch did not apply and we fail hard.
* package, not just that the version matches. Each entry checks either that a
* stale (pre-patch) `staleMarker` is absent or that a `requiredMarker` (added
* by the patch) is present in a given installed file. If the check fails, the
* patch did not apply and we fail hard.
*
* @stricli/core: the unpatched source registers `-H` as the reserved alias for
* `--help-all` via `checkForReservedAliases(aliases, ["h", "H"])`. After our
* patch that becomes `["h"]`. The presence of `"H"` in that call is a reliable
* signal that the patch did NOT apply (in either the ESM or CJS bundle).
* @stricli/core (`-H` alias): the unpatched source registers `-H` as the
* reserved alias for `--help-all` via
* `checkForReservedAliases(aliases, ["h", "H"])`. After our patch that becomes
* `["h"]`. The presence of `"H"` in that call is a reliable signal that the
* patch did NOT apply (in either the ESM or CJS bundle).
*
* @stricli/core (top-level flags): the patch teaches `buildRouteScanner` to
* recognize a host-supplied allow-list of global flags (`scanner.topLevelFlags`)
* at any route depth, so `sentry --verbose issue list` no longer fails route
* resolution. The allow-list itself is passed in from the app (derived from
* GLOBAL_FLAGS) rather than hardcoded in the patch. This is a pure insertion, so
* it's guarded by a `requiredMarker` (`matchTopLevelFlag`) that must be present
* once patched. Its absence means global flags before a subcommand will crash.
*
* @stricli/core (`-v` version alias): the patch also drops Stricli's built-in
* `-v`=version alias in `runApplication` so `-v` stays the Sentry CLI's
* `--verbose` alias at every position; `--version` remains the version flag.
* The stale marker is the original `inputs[0] === "-v"` version check.
*
* @sentry/core and @sentry/node-core: these are tree-shaking patches that strip
* unused re-exports (AI/integration modules) from the build barrels so esbuild
Expand All @@ -191,7 +206,12 @@ const CONTENT_ASSERTIONS: ReadonlyArray<{
/** Installed file to inspect, relative to the resolved node_modules dir. */
file: string;
/** Stale marker that MUST be absent once the patch is applied. */
staleMarker: string;
staleMarker?: string;
/**
* Marker that MUST be present once the patch is applied. Used for patches
* that add code (pure insertions) with no stale line to key off of.
*/
requiredMarker?: string;
/** Human-readable explanation shown on failure. */
description: string;
}> = [
Expand All @@ -207,6 +227,30 @@ const CONTENT_ASSERTIONS: ReadonlyArray<{
description:
"@stricli/core CJS: -H alias not freed (api -H/--header will crash)",
},
{
file: "@stricli/core/dist/index.js",
requiredMarker: "matchTopLevelFlag",
description:
"@stricli/core ESM: top-level-flags scanner allow-list missing (global flags before a subcommand, e.g. `sentry --verbose issue list`, will fail route resolution)",
},
{
file: "@stricli/core/dist/index.cjs",
requiredMarker: "matchTopLevelFlag",
description:
"@stricli/core CJS: top-level-flags scanner allow-list missing (global flags before a subcommand, e.g. `sentry --verbose issue list`, will fail route resolution)",
},
{
file: "@stricli/core/dist/index.js",
staleMarker: 'inputs[0] === "--version" || inputs[0] === "-v"',
description:
"@stricli/core ESM: built-in `-v`=version alias not dropped (`sentry -v <command>` prints the version instead of running the command verbosely)",
},
{
file: "@stricli/core/dist/index.cjs",
staleMarker: 'inputs[0] === "--version" || inputs[0] === "-v"',
description:
"@stricli/core CJS: built-in `-v`=version alias not dropped (`sentry -v <command>` prints the version instead of running the command verbosely)",
},
{
file: "@sentry/core/build/cjs/index.js",
staleMarker: "exports.instrumentOpenAiClient",
Expand Down Expand Up @@ -240,7 +284,15 @@ for (const assertion of CONTENT_ASSERTIONS) {
throw new Error("unresolved");
}
const contents = await readFile(assertionPath, "utf-8");
if (contents.includes(assertion.staleMarker)) {
if (assertion.staleMarker && contents.includes(assertion.staleMarker)) {
errors.push(
` ${assertion.description} — patch not applied to ${assertion.file} (regenerate the patch for the current dependency version)`
);
}
if (
assertion.requiredMarker &&
!contents.includes(assertion.requiredMarker)
) {
errors.push(
` ${assertion.description} — patch not applied to ${assertion.file} (regenerate the patch for the current dependency version)`
);
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {
WizardError,
} from "./lib/errors.js";
import { error as errorColor, warning } from "./lib/formatters/colors.js";
import { buildTopLevelFlags } from "./lib/global-flags.js";
import { isRouteMap, type RouteMap } from "./lib/introspect.js";
import { buildRouteMap } from "./lib/route-map.js";

Expand Down Expand Up @@ -399,6 +400,11 @@ export const app = buildApplication(routes, {
// `sentry monitor run <slug> -- <command>`) can pass through flags
// like `-e` or `--verbose` to the wrapped command unambiguously.
allowArgumentEscapeSequence: true,
// Recognize global flags placed before the subcommand
// (`sentry --verbose issue list`) at any route depth and forward them to
// the leaf command, via our @stricli/core route-scanner patch. Derived
// from GLOBAL_FLAGS so adding a global flag there is all that's needed.
topLevelFlags: buildTopLevelFlags(),
},
determineExitCode: getExitCode,
localization: {
Expand Down
18 changes: 10 additions & 8 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export async function runCli(cliArgs: string[]): Promise<void> {
const { isatty } = await import("node:tty");
const { ExitCode, run } = await import("@stricli/core");
const { app } = await import("./app.js");
const { preprocessArgv } = await import("./lib/argv-hoist.js");
const { preprocessArgv } = await import("./lib/argv-glue.js");
const { buildContext } = await import("./context.js");
const { AuthError, OutputError, formatError, getExitCode } = await import(
"./lib/errors.js"
Expand All @@ -185,15 +185,17 @@ export async function runCli(cliArgs: string[]): Promise<void> {
shouldSuppressNotification,
} = await import("./lib/version-check.js");

// Preprocess argv before dispatch (see preprocessArgv):
// Normalize argv before dispatch (see preprocessArgv). Global-flag hoisting is
// now handled by Stricli's patched route scanner (top-level-flags allow-list),
// so only two application-boundary transforms remain:
// - `--version` after a route group/subcommand (e.g. `sentry cli --version`)
// is normalized to a top-level `--version`; Stricli only handles it at the
// application proxy. `-v` is left alone — it's the --verbose alias.
// - global flags (--verbose, -v, --log-level, --json, --fields) are hoisted
// to the tail so `sentry --verbose issue list` works.
// - a flag-based `--help --json` request is rewritten to the `help` command
// so JSON help works for the `--help` forms agents reach for.
// The original cliArgs are kept for post-run checks (e.g., help recovery)
// that rely on the original token positions.
const hoistedArgs = preprocessArgv(cliArgs);
const normalizedArgs = preprocessArgv(cliArgs);

// ---------------------------------------------------------------------------
// Error-recovery middleware
Expand Down Expand Up @@ -616,15 +618,15 @@ export async function runCli(cliArgs: string[]): Promise<void> {

// Use hoisted args so positional checks (e.g., args[0] === "cli") work
// even when global flags precede the subcommand in the original argv.
const suppressNotification = shouldSuppressNotification(hoistedArgs);
const suppressNotification = shouldSuppressNotification(normalizedArgs);
Comment thread
jared-outpost[bot] marked this conversation as resolved.

// Start background update check (non-blocking)
if (!suppressNotification) {
maybeCheckForUpdateInBackground();
}

try {
await executor(hoistedArgs);
await executor(normalizedArgs);

// When Stricli can't match a subcommand in a route group (e.g.,
// `sentry dashboard help`), it writes "No command registered for `help`"
Expand Down Expand Up @@ -660,7 +662,7 @@ export async function runCli(cliArgs: string[]): Promise<void> {
}
process.stderr.write(`${error("Error:")} ${formatError(err)}\n`);
process.exitCode = getExitCode(err);
const notification = getErrorUpdateNotification(err, hoistedArgs);
const notification = getErrorUpdateNotification(err, normalizedArgs);
if (notification) {
process.stderr.write(notification);
}
Expand Down
Loading
Loading