From a24f45166b96f10c3c96061c75c3798a774098f7 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:35:06 +0000 Subject: [PATCH 1/2] ref(cli): Prefer Array.from() over spread to copy iterables Add the sentry-cli/prefer-array-from lint rule. It flags array literals whose only element is a spread, such as [...set], and autofixes them to Array.from(set). Spreads that build arrays from several parts, call arguments, and object spreads are left alone. Apply the autofix across CLI src and tests and document the convention in packages/cli/AGENTS.md. Co-Authored-By: BYK --- .oxlintrc.json | 1 + packages/cli/AGENTS.md | 4 +++ packages/cli/lint-rules/cli-oxlint-plugin.js | 33 +++++++++++++++++++ .../cli/src/commands/alert/issues/list.ts | 4 +-- .../src/commands/alert/issues/rule-resolve.ts | 2 +- .../cli/src/commands/alert/metrics/list.ts | 8 ++--- .../commands/alert/metrics/rule-resolve.ts | 2 +- .../cli/src/commands/alert/mutation-utils.ts | 2 +- packages/cli/src/commands/api.ts | 4 ++- packages/cli/src/commands/auth/logout.ts | 2 +- packages/cli/src/commands/auth/refresh.ts | 2 +- .../cli/src/commands/dashboard/resolve.ts | 2 +- packages/cli/src/commands/debug-files/find.ts | 2 +- packages/cli/src/commands/dsn/list.ts | 2 +- packages/cli/src/commands/explore.ts | 2 +- packages/cli/src/commands/issue/list.ts | 2 +- packages/cli/src/commands/issue/utils.ts | 2 +- packages/cli/src/commands/local/server.ts | 4 +-- .../cli/src/commands/local/session-store.ts | 6 ++-- packages/cli/src/commands/log/list.ts | 4 +-- packages/cli/src/commands/project/create.ts | 4 +-- packages/cli/src/commands/project/list.ts | 6 ++-- packages/cli/src/commands/release/list.ts | 2 +- packages/cli/src/commands/replay/list.ts | 2 +- packages/cli/src/commands/schema.ts | 4 ++- packages/cli/src/commands/sourcemap/upload.ts | 2 +- packages/cli/src/commands/trial/list.ts | 2 +- packages/cli/src/lib/alias.ts | 12 ++++--- packages/cli/src/lib/api-schema.ts | 6 ++-- packages/cli/src/lib/api-scope.ts | 4 +-- packages/cli/src/lib/api/alerts.ts | 2 +- packages/cli/src/lib/api/issue-app-links.ts | 4 ++- packages/cli/src/lib/api/logs.ts | 6 ++-- packages/cli/src/lib/api/replays.ts | 2 +- packages/cli/src/lib/build/index.ts | 2 +- packages/cli/src/lib/complete.ts | 2 +- packages/cli/src/lib/completions.ts | 4 +-- packages/cli/src/lib/db/accounts.ts | 2 +- packages/cli/src/lib/db/pagination.ts | 7 ++-- packages/cli/src/lib/dif/find.ts | 4 +-- packages/cli/src/lib/docs-context.ts | 4 +-- packages/cli/src/lib/dsn/code-scanner.ts | 4 +-- packages/cli/src/lib/dsn/env-file.ts | 6 ++-- packages/cli/src/lib/dsn/parser.ts | 2 +- .../cli/src/lib/envelope/event-send-dsn.ts | 10 +++--- packages/cli/src/lib/errors.ts | 5 ++- .../cli/src/lib/formatters/conversation.ts | 6 ++-- packages/cli/src/lib/formatters/dashboard.ts | 2 +- packages/cli/src/lib/formatters/human.ts | 2 +- packages/cli/src/lib/formatters/local.ts | 4 +-- packages/cli/src/lib/formatters/text-table.ts | 6 ++-- packages/cli/src/lib/formatters/trace.ts | 8 +++-- packages/cli/src/lib/fuzzy.ts | 2 +- packages/cli/src/lib/init/interactive.ts | 2 +- .../lib/init/tools/file-changes/contract.ts | 4 ++- packages/cli/src/lib/init/ui/wizard-store.ts | 6 ++-- packages/cli/src/lib/init/wizard-runner.ts | 4 +-- packages/cli/src/lib/list-filter.ts | 6 ++-- packages/cli/src/lib/mcp.ts | 2 +- packages/cli/src/lib/metrics-transform.ts | 2 +- packages/cli/src/lib/org-list.ts | 2 +- packages/cli/src/lib/positional-parse.ts | 2 +- packages/cli/src/lib/replay-search.ts | 10 +++--- packages/cli/src/lib/resolve-target.ts | 28 ++++++++-------- packages/cli/src/lib/resolve-team.ts | 2 +- packages/cli/src/lib/scan/ignore.ts | 2 +- packages/cli/src/lib/scan/regex.ts | 4 +-- packages/cli/src/lib/sixel-image.ts | 4 +-- packages/cli/src/lib/snapshots/images.ts | 2 +- packages/cli/src/lib/sourcemap/debug-id.ts | 2 +- packages/cli/src/lib/wrangler.ts | 6 ++-- packages/cli/src/types/seer.ts | 2 +- .../cli/test/commands/cli/uninstall.test.ts | 2 +- .../cli/test/commands/cli/upgrade.test.ts | 18 +++++----- packages/cli/test/commands/dsn/list.test.ts | 6 ++-- packages/cli/test/commands/explore.test.ts | 2 +- .../test/commands/issue/list.property.test.ts | 2 +- packages/cli/test/commands/log/list.test.ts | 4 +-- .../cli/test/commands/platform/list.test.ts | 2 +- .../cli/test/commands/replay/list.test.ts | 4 +-- packages/cli/test/fixtures/bench/generate.ts | 4 +-- packages/cli/test/fixtures/bench/helpers.ts | 4 +-- packages/cli/test/lib/alias.property.test.ts | 4 +-- packages/cli/test/lib/alias.test.ts | 8 ++--- packages/cli/test/lib/api-schema.test.ts | 4 +-- .../lib/api/trace-item-attributes.test.ts | 4 +-- .../lib/build/asset-catalog-extract.test.ts | 2 +- .../lib/build/car-extract.integration.test.ts | 4 +-- packages/cli/test/lib/command.test.ts | 2 +- packages/cli/test/lib/custom-headers.test.ts | 2 +- packages/cli/test/lib/db/accounts.test.ts | 4 ++- .../lib/db/pagination.model-based.test.ts | 2 +- packages/cli/test/lib/dsn.property.test.ts | 2 +- .../test/lib/formatters/pixel-canvas.test.ts | 12 ++++--- packages/cli/test/lib/fuzzy.test.ts | 2 +- packages/cli/test/lib/global-flags.test.ts | 4 +-- packages/cli/test/lib/jvm-bundle.test.ts | 8 ++--- .../lib/security/fetch-layer-guard.test.ts | 2 +- .../cli/test/lib/snapshots/images.test.ts | 2 +- .../cli/test/lib/sourcemap/resolve.test.ts | 2 +- .../cli/test/lib/time-range.property.test.ts | 2 +- packages/cli/test/lib/upgrade.test.ts | 6 ++-- .../cli/test/script/sdk-declarations.test.ts | 2 +- scripts/cli-lint-rules.test.mjs | 24 ++++++++++++++ 104 files changed, 282 insertions(+), 192 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 695f7bab6..238309681 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -74,6 +74,7 @@ "sentry-cli/no-namespace-import": "error", "sentry-cli/no-examples-in-full-description": "error", "sentry-cli/no-regex-markdown-parsing": "error", + "sentry-cli/prefer-array-from": "error", "no-restricted-imports": [ "error", { diff --git a/packages/cli/AGENTS.md b/packages/cli/AGENTS.md index 1f8f79fb4..b24d19210 100644 --- a/packages/cli/AGENTS.md +++ b/packages/cli/AGENTS.md @@ -757,6 +757,10 @@ import type { SentryContext } from "../../context.js"; import { getAuthToken } from "../../lib/db/auth.js"; ``` +### Copying Iterables + +Use `Array.from(value)` to turn a `Set`, `Map` view, iterator, or array into a new array. Keep spread for building arrays from several parts, such as `[...a, ...b]` or `[...items, tail]`. The `sentry-cli/prefer-array-from` lint rule flags `[...value]` and fixes it with `pnpm run lint:fix`. + ### Target Resolution `parseOrgProjectArg()` owns target syntax: a value without `/` is a diff --git a/packages/cli/lint-rules/cli-oxlint-plugin.js b/packages/cli/lint-rules/cli-oxlint-plugin.js index 409dd1f97..c015ee31b 100644 --- a/packages/cli/lint-rules/cli-oxlint-plugin.js +++ b/packages/cli/lint-rules/cli-oxlint-plugin.js @@ -141,6 +141,9 @@ function regexSource(node) { return ""; } +const PREFER_ARRAY_FROM_MESSAGE = + "Use Array.from(value) instead of [...value] to copy an iterable."; + const rules = { "no-silent-catch": rule( "Handle or report caught errors instead of discarding them.", @@ -409,6 +412,36 @@ const rules = { } : {}, ), + "prefer-array-from": { + meta: { + type: "suggestion", + docs: { description: PREFER_ARRAY_FROM_MESSAGE }, + fixable: "code", + schema: [], + }, + create(context) { + const filename = context.filename.replaceAll("\\", "/"); + if (!filename.includes("/packages/cli/")) return {}; + return { + // Only a lone spread is a pure conversion; `[...a, ...b]` and + // `[...a, b]` build new arrays and stay as they are. + ArrayExpression(node) { + const [element] = node.elements; + if (node.elements.length !== 1 || element?.type !== "SpreadElement") + return; + context.report({ + node, + message: PREFER_ARRAY_FROM_MESSAGE, + fix: (fixer) => + fixer.replaceText( + node, + `Array.from(${context.sourceCode.getText(element.argument)})`, + ), + }); + }, + }; + }, + }, "no-skipped-tests": rule( "Do not leave disabled tests in the suite.", (_context, file, report) => diff --git a/packages/cli/src/commands/alert/issues/list.ts b/packages/cli/src/commands/alert/issues/list.ts index 7fc6c3d83..43e60fcdd 100644 --- a/packages/cli/src/commands/alert/issues/list.ts +++ b/packages/cli/src/commands/alert/issues/list.ts @@ -191,7 +191,7 @@ async function resolveWebUrl( throw new ContextError("Organization and project", USAGE_HINT); } - const orgs = [...new Set(targets.map((t) => t.org))]; + const orgs = Array.from(new Set(targets.map((t) => t.org))); if (orgs.length !== 1) { throw new ValidationError( "--web resolved alert rules in multiple organizations. Specify an explicit / or / target.", @@ -199,7 +199,7 @@ async function resolveWebUrl( ); } - const projects = [...new Set(targets.map((t) => t.project))]; + const projects = Array.from(new Set(targets.map((t) => t.project))); return buildIssueAlertsUrl( // oxlint-disable-next-line typescript/no-non-null-assertion -- orgs length is checked above orgs[0]!, diff --git a/packages/cli/src/commands/alert/issues/rule-resolve.ts b/packages/cli/src/commands/alert/issues/rule-resolve.ts index db5312a92..6783af9c1 100644 --- a/packages/cli/src/commands/alert/issues/rule-resolve.ts +++ b/packages/cli/src/commands/alert/issues/rule-resolve.ts @@ -155,7 +155,7 @@ export async function resolveIssueAlertRule( ); } - const uniqueNames = [...new Set(allRuleNames)]; + const uniqueNames = Array.from(new Set(allRuleNames)); const similar = fuzzyMatch(ref, uniqueNames, { maxResults: 5 }); if (similar.length > 0) { const lines = similar.map((n) => ` ${n}`).join("\n"); diff --git a/packages/cli/src/commands/alert/metrics/list.ts b/packages/cli/src/commands/alert/metrics/list.ts index a8e90a208..3d363a0f8 100644 --- a/packages/cli/src/commands/alert/metrics/list.ts +++ b/packages/cli/src/commands/alert/metrics/list.ts @@ -189,7 +189,7 @@ async function resolveOrgs( projectSearchResolution, }); return { - orgs: [...new Set(targets.map((t: ResolvedTarget) => t.org))], + orgs: Array.from(new Set(targets.map((t: ResolvedTarget) => t.org))), footer, }; } @@ -215,7 +215,7 @@ async function resolveWebUrl( } const { orgs } = await resolveOrgs(parsed, cwd, projectSearchResolution); - const uniqueOrgs = [...new Set(orgs)]; + const uniqueOrgs = Array.from(new Set(orgs)); if (uniqueOrgs.length === 0) { throw new ContextError("Organization", USAGE_HINT); } @@ -249,10 +249,10 @@ async function handleResolvedOrgs( throw new ContextError("Organization", USAGE_HINT); } - const uniqueOrgs = [...new Set(resolved)]; + const uniqueOrgs = Array.from(new Set(resolved)); const contextKey = buildMultiOrgContextKey(uniqueOrgs, flags.query); - const sortedOrgKeys = [...uniqueOrgs].sort(); + const sortedOrgKeys = Array.from(uniqueOrgs).sort(); const { cursor: rawCursor, direction } = resolveCursor( flags.cursor, diff --git a/packages/cli/src/commands/alert/metrics/rule-resolve.ts b/packages/cli/src/commands/alert/metrics/rule-resolve.ts index 9d4dd35ee..f1094cf6c 100644 --- a/packages/cli/src/commands/alert/metrics/rule-resolve.ts +++ b/packages/cli/src/commands/alert/metrics/rule-resolve.ts @@ -151,7 +151,7 @@ export async function resolveMetricAlertRule( ); } - const uniqueNames = [...new Set(allNames)]; + const uniqueNames = Array.from(new Set(allNames)); const similar = fuzzyMatch(ref, uniqueNames, { maxResults: 5 }); if (similar.length > 0) { const lines = similar.map((n) => ` ${n}`).join("\n"); diff --git a/packages/cli/src/commands/alert/mutation-utils.ts b/packages/cli/src/commands/alert/mutation-utils.ts index 97275e4c9..8c8c9f554 100644 --- a/packages/cli/src/commands/alert/mutation-utils.ts +++ b/packages/cli/src/commands/alert/mutation-utils.ts @@ -260,7 +260,7 @@ export function validateMetricTimeWindow(timeWindow: number): void { return; } throw new ValidationError( - `timeWindow must be one of: ${[...METRIC_TIME_WINDOWS].join(", ")} minutes.`, + `timeWindow must be one of: ${Array.from(METRIC_TIME_WINDOWS).join(", ")} minutes.`, "timeWindow", ); } diff --git a/packages/cli/src/commands/api.ts b/packages/cli/src/commands/api.ts index aec7b19a6..647fec1d4 100644 --- a/packages/cli/src/commands/api.ts +++ b/packages/cli/src/commands/api.ts @@ -344,7 +344,9 @@ export function parseFieldKey(key: string): string[] { // Extract bracket contents: "[name][age]" -> ["name", "age"] // Empty brackets [] result in empty string "" for array push const segments: string[] = brackets - ? [...brackets.matchAll(BRACKET_CONTENTS_REGEX)].map((m) => m[1] ?? "") + ? Array.from(brackets.matchAll(BRACKET_CONTENTS_REGEX)).map( + (m) => m[1] ?? "", + ) : []; return [baseKey, ...segments]; diff --git a/packages/cli/src/commands/auth/logout.ts b/packages/cli/src/commands/auth/logout.ts index 6d271f081..6a8e21511 100644 --- a/packages/cli/src/commands/auth/logout.ts +++ b/packages/cli/src/commands/auth/logout.ts @@ -198,7 +198,7 @@ export const logoutCommand = buildCommand({ * active one never picks an account that is about to be removed too. */ async function removeAccounts(accounts: AccountSummary[]): Promise { - const ordered = [...accounts].sort( + const ordered = Array.from(accounts).sort( (a, b) => Number(a.active) - Number(b.active), ); for (const account of ordered) { diff --git a/packages/cli/src/commands/auth/refresh.ts b/packages/cli/src/commands/auth/refresh.ts index 8eae3422c..15ea9ff51 100644 --- a/packages/cli/src/commands/auth/refresh.ts +++ b/packages/cli/src/commands/auth/refresh.ts @@ -62,7 +62,7 @@ function resolveRefreshScope(flags: RefreshFlags): string { ); } if (flags.scope?.length) { - const scopes = [...flags.scope].flatMap((v) => v.split(",")); + const scopes = Array.from(flags.scope).flatMap((v) => v.split(",")); return resolveOAuthScopeString({ scopes }); } return resolveOAuthScopeString({ readOnly: true }); diff --git a/packages/cli/src/commands/dashboard/resolve.ts b/packages/cli/src/commands/dashboard/resolve.ts index 48f6120b0..87d7d385e 100644 --- a/packages/cli/src/commands/dashboard/resolve.ts +++ b/packages/cli/src/commands/dashboard/resolve.ts @@ -573,7 +573,7 @@ function warnUnknownAggregateFields( `Aggregate field "${inner}" in "${agg}" is not a known aggregatable span field. ` + "Span attributes (custom tags) cannot be used with numeric aggregates — " + "use them in --where or --group-by instead. " + - `Known numeric fields: ${[...KNOWN_SPAN_AGGREGATE_FIELDS].join(", ")}`, + `Known numeric fields: ${Array.from(KNOWN_SPAN_AGGREGATE_FIELDS).join(", ")}`, ); } } diff --git a/packages/cli/src/commands/debug-files/find.ts b/packages/cli/src/commands/debug-files/find.ts index cfa1b7089..a354c1d18 100644 --- a/packages/cli/src/commands/debug-files/find.ts +++ b/packages/cli/src/commands/debug-files/find.ts @@ -67,7 +67,7 @@ function formatFindResult(data: FindResult): string { /** Resolve the requested types, validating each against the known set. */ function resolveTypes(flags: FindFlags): string[] { if (!(flags.type && flags.type.length > 0)) { - return [...FIND_DIF_TYPES]; + return Array.from(FIND_DIF_TYPES); } const types = flags.type.map((t) => t.trim().toLowerCase()); for (const type of types) { diff --git a/packages/cli/src/commands/dsn/list.ts b/packages/cli/src/commands/dsn/list.ts index 25d6cb847..db6d71fdc 100644 --- a/packages/cli/src/commands/dsn/list.ts +++ b/packages/cli/src/commands/dsn/list.ts @@ -240,7 +240,7 @@ async function listForResolvedProjects< }), ); const { pages, failures } = collectProjectResults(results, activeTargets); - const allItems = [...pages.values()].flatMap((page) => page.items); + const allItems = Array.from(pages.values()).flatMap((page) => page.items); const items = trimWithGroupGuarantee(allItems, flags.limit, targetKey); const trimmed = items.length < allItems.length; const cursors = sortedKeys.map((key) => { diff --git a/packages/cli/src/commands/explore.ts b/packages/cli/src/commands/explore.ts index 2efd67cb2..08c835df9 100644 --- a/packages/cli/src/commands/explore.ts +++ b/packages/cli/src/commands/explore.ts @@ -862,7 +862,7 @@ export const exploreCommand = buildListCommand("explore", { let fieldList = flags["time-series"] !== undefined ? ["count()"] - : [...defaultFieldsForDataset(dataset)]; + : Array.from(defaultFieldsForDataset(dataset)); if (userSuppliedFields) { fieldList = flags.field; } diff --git a/packages/cli/src/commands/issue/list.ts b/packages/cli/src/commands/issue/list.ts index c14871071..a4af5a204 100644 --- a/packages/cli/src/commands/issue/list.ts +++ b/packages/cli/src/commands/issue/list.ts @@ -1083,7 +1083,7 @@ function formatDetailWithSuggestions( detailLines: string[], suggestions: string[], ): string { - const lines = [...detailLines]; + const lines = Array.from(detailLines); if (lines.length > 0) { lines.push(""); } diff --git a/packages/cli/src/commands/issue/utils.ts b/packages/cli/src/commands/issue/utils.ts index 28f8ba1f3..b08493241 100644 --- a/packages/cli/src/commands/issue/utils.ts +++ b/packages/cli/src/commands/issue/utils.ts @@ -97,7 +97,7 @@ export const issueIdsPositional = { * @returns Normalized issue identifiers */ export function collectIssueArgs(args: readonly string[]): string[] { - return [...new Set(args.flatMap(splitNewlineArg))]; + return Array.from(new Set(args.flatMap(splitNewlineArg))); } /** diff --git a/packages/cli/src/commands/local/server.ts b/packages/cli/src/commands/local/server.ts index 88663ca79..36bf069b4 100644 --- a/packages/cli/src/commands/local/server.ts +++ b/packages/cli/src/commands/local/server.ts @@ -1166,7 +1166,7 @@ export const serverCommand = buildCommand({ printLocalViewerHint(url, flags.format === "json"); await openLocalUiIfRequested(flags.open, url); if (activeFilters.size > 0) { - logger.info(`Filtering: ${[...activeFilters].join(", ")}`); + logger.info(`Filtering: ${Array.from(activeFilters).join(", ")}`); } logger.info("Press Ctrl-C to stop."); @@ -1243,7 +1243,7 @@ export const serverCommand = buildCommand({ ` Or run: ${bold(`sentry local run -p ${boundPort} -- `)}`, ); if (activeFilters.size > 0) { - logger.info(` Filtering: ${[...activeFilters].join(", ")}`); + logger.info(` Filtering: ${Array.from(activeFilters).join(", ")}`); } if (useJson) { logger.info(" Output: JSON (NDJSON)"); diff --git a/packages/cli/src/commands/local/session-store.ts b/packages/cli/src/commands/local/session-store.ts index e1c393572..a859e294c 100644 --- a/packages/cli/src/commands/local/session-store.ts +++ b/packages/cli/src/commands/local/session-store.ts @@ -110,7 +110,7 @@ export function createLocalSessionStore({ if (byId) { return byId; } - const matches = [...sessions.values()].filter( + const matches = Array.from(sessions.values()).filter( (session) => session.label === reference, ); if (matches.length === 0) { @@ -219,13 +219,13 @@ export function createLocalSessionStore({ }; }, list() { - return [...sessions.values()] + return Array.from(sessions.values()) .map(toPublicSession) .sort((left, right) => right.lastActivityAt - left.lastActivityAt); }, pruneExpired() { const timestamp = now(); - const expired = [...sessions.values()] + const expired = Array.from(sessions.values()) .filter( (session) => session.state === "retained" && diff --git a/packages/cli/src/commands/log/list.ts b/packages/cli/src/commands/log/list.ts index 0c5f6a5d6..932c29320 100644 --- a/packages/cli/src/commands/log/list.ts +++ b/packages/cli/src/commands/log/list.ts @@ -391,7 +391,7 @@ async function* generateFollowLogs( // Initial fetch const initialLogs = await config.fetch("1m"); if (initialLogs.length > 0) { - yield [...initialLogs].reverse(); + yield Array.from(initialLogs).reverse(); } lastTimestamp = maxTimestamp(initialLogs) ?? lastTimestamp; config.onInitialLogs?.(initialLogs); @@ -405,7 +405,7 @@ async function* generateFollowLogs( const newLogs = await fetchPoll(config, lastTimestamp); if (newLogs && newLogs.length > 0) { - yield [...newLogs].reverse(); + yield Array.from(newLogs).reverse(); lastTimestamp = maxTimestamp(newLogs) ?? lastTimestamp; } } diff --git a/packages/cli/src/commands/project/create.ts b/packages/cli/src/commands/project/create.ts index 36bde4a9b..3ea0d1915 100644 --- a/packages/cli/src/commands/project/create.ts +++ b/packages/cli/src/commands/project/create.ts @@ -121,7 +121,7 @@ function buildPlatformError(nameArg: string, platform?: string): string { } } - const platformTable = renderPlatformGrid([...COMMON_PLATFORMS]); + const platformTable = renderPlatformGrid(Array.from(COMMON_PLATFORMS)); return ( `${heading}\n` + @@ -483,7 +483,7 @@ function parseProjectSpecs(rawSpecs: readonly string[]): { ); if (orgs.size > 1) { throw new ValidationError( - `Cannot create projects across multiple organizations (${[...orgs].join(", ")}).\n\n` + + `Cannot create projects across multiple organizations (${Array.from(orgs).join(", ")}).\n\n` + "All names must belong to the same org.", "organization", ); diff --git a/packages/cli/src/commands/project/list.ts b/packages/cli/src/commands/project/list.ts index 209b029ab..560310e5e 100644 --- a/packages/cli/src/commands/project/list.ts +++ b/packages/cli/src/commands/project/list.ts @@ -294,9 +294,9 @@ async function resolveOrgsForAutoDetect(cwd: string): Promise { if (targetsResult.ok) { const { targets, footer, skippedSelfHosted } = targetsResult.value; if (targets.length > 0) { - const uniqueOrgs = [ - ...new Set(targets.map((t: ResolvedTarget) => t.org)), - ]; + const uniqueOrgs = Array.from( + new Set(targets.map((t: ResolvedTarget) => t.org)), + ); return { orgs: uniqueOrgs, footer, skippedSelfHosted }; } return { orgs: [], skippedSelfHosted }; diff --git a/packages/cli/src/commands/release/list.ts b/packages/cli/src/commands/release/list.ts index d2aa61780..ebf1da53a 100644 --- a/packages/cli/src/commands/release/list.ts +++ b/packages/cli/src/commands/release/list.ts @@ -716,7 +716,7 @@ export const listCommand = buildListCommand("release", { const parsed = parseOrgProjectArg(target); // Flatten: -e prod,dev -e staging → ["prod", "dev", "staging"] const envFilter = flags.environment - ? [...flags.environment] + ? Array.from(flags.environment) .flatMap((v) => v.split(",")) .map((s) => s.trim()) .filter(Boolean) diff --git a/packages/cli/src/commands/replay/list.ts b/packages/cli/src/commands/replay/list.ts index 7c46dbbd0..7728044e3 100644 --- a/packages/cli/src/commands/replay/list.ts +++ b/packages/cli/src/commands/replay/list.ts @@ -395,7 +395,7 @@ export const listCommand = buildListCommand("replay", { () => listReplays(resolved.org, { environment, - fields: [...REPLAY_LIST_FIELDS], + fields: Array.from(REPLAY_LIST_FIELDS), limit: flags.limit, query, projectSlugs: resolved.project ? [resolved.project] : undefined, diff --git a/packages/cli/src/commands/schema.ts b/packages/cli/src/commands/schema.ts index 8b5a2463a..90e88426b 100644 --- a/packages/cli/src/commands/schema.ts +++ b/packages/cli/src/commands/schema.ts @@ -269,7 +269,9 @@ function pathNotFoundError( ): never { const samePath = findEndpointsByPath(query.path); if (query.method && samePath.length > 0) { - const methods = [...new Set(samePath.map((endpoint) => endpoint.method))] + const methods = Array.from( + new Set(samePath.map((endpoint) => endpoint.method)), + ) .sort() .join(", "); const example = diff --git a/packages/cli/src/commands/sourcemap/upload.ts b/packages/cli/src/commands/sourcemap/upload.ts index abf915a4a..849a96e02 100644 --- a/packages/cli/src/commands/sourcemap/upload.ts +++ b/packages/cli/src/commands/sourcemap/upload.ts @@ -77,7 +77,7 @@ function computeCommonPrefix(paths: string[]): string { if (paths.length === 0) { return ""; } - const sorted = [...paths].sort(); + const sorted = Array.from(paths).sort(); const first = sorted[0]; const last = sorted.at(-1); if (!(first && last)) { diff --git a/packages/cli/src/commands/trial/list.ts b/packages/cli/src/commands/trial/list.ts index 249b9e407..c41b6f94c 100644 --- a/packages/cli/src/commands/trial/list.ts +++ b/packages/cli/src/commands/trial/list.ts @@ -90,7 +90,7 @@ function deduplicateTrials(entries: TrialListEntry[]): TrialListEntry[] { byName.set(entry.name, entry); } } - return [...byName.values()]; + return Array.from(byName.values()); } /** diff --git a/packages/cli/src/lib/alias.ts b/packages/cli/src/lib/alias.ts index 6312af8be..d7c4cfc2f 100644 --- a/packages/cli/src/lib/alias.ts +++ b/packages/cli/src/lib/alias.ts @@ -206,7 +206,9 @@ function processUniqueSlugs( aliasMap: Map, ): void { const uniqueProjects = pairs.filter((p) => uniqueSlugs.has(p.project)); - const uniqueProjectSlugs = [...new Set(uniqueProjects.map((p) => p.project))]; + const uniqueProjectSlugs = Array.from( + new Set(uniqueProjects.map((p) => p.project)), + ); if (uniqueProjectSlugs.length === 0) { return; @@ -227,7 +229,7 @@ function processUniqueSlugs( // Handle prefix relationships (e.g., "cli" is prefix of "cli-website") applyPrefixRelationships(uniqueProjectSlugs, slugToRemainder); - const uniqueRemainders = [...slugToRemainder.values()]; + const uniqueRemainders = Array.from(slugToRemainder.values()); const uniquePrefixes = findShortestUniquePrefixes(uniqueRemainders); for (const { org, project } of uniqueProjects) { @@ -255,11 +257,13 @@ function processCollidingSlugs( } } - const orgPrefixes = findShortestUniquePrefixes([...collidingOrgs]); + const orgPrefixes = findShortestUniquePrefixes(Array.from(collidingOrgs)); // Compute unique prefixes for colliding project slugs to handle // cases like "api" and "app" both colliding across orgs - const projectPrefixes = findShortestUniquePrefixes([...collidingSlugs]); + const projectPrefixes = findShortestUniquePrefixes( + Array.from(collidingSlugs), + ); for (const slug of collidingSlugs) { const orgs = projectToOrgs.get(slug); diff --git a/packages/cli/src/lib/api-schema.ts b/packages/cli/src/lib/api-schema.ts index 8b807356d..166a8cf61 100644 --- a/packages/cli/src/lib/api-schema.ts +++ b/packages/cli/src/lib/api-schema.ts @@ -144,7 +144,7 @@ export function findEndpointsByIdentifier(identifier: string): ApiEndpoint[] { /** Get all unique resource names, sorted alphabetically */ export function getAllResources(): string[] { const resources = new Set(schema.map((e) => e.resource)); - return [...resources].sort(); + return Array.from(resources).sort(); } /** Get all endpoints for a given resource name (case-insensitive) */ @@ -331,11 +331,11 @@ export function getResourceSummaries(): ResourceSummary[] { } } - return [...map.entries()] + return Array.from(map.entries()) .sort(([a], [b]) => a.localeCompare(b)) .map(([name, info]) => ({ name, endpointCount: info.endpointCount, - methods: [...info.methods].sort(), + methods: Array.from(info.methods).sort(), })); } diff --git a/packages/cli/src/lib/api-scope.ts b/packages/cli/src/lib/api-scope.ts index 3228669c0..185b2566d 100644 --- a/packages/cli/src/lib/api-scope.ts +++ b/packages/cli/src/lib/api-scope.ts @@ -97,7 +97,7 @@ function extractFromRecord(record: Record): string[] { } const scopes = collectScopesFromArray(value); if (scopes.length > 0) { - return [...new Set(scopes)]; + return Array.from(new Set(scopes)); } } return []; @@ -142,5 +142,5 @@ function extractFromText(text: string): string[] { if (!matches) { return []; } - return [...new Set(matches.map((m) => m.toLowerCase()))]; + return Array.from(new Set(matches.map((m) => m.toLowerCase()))); } diff --git a/packages/cli/src/lib/api/alerts.ts b/packages/cli/src/lib/api/alerts.ts index 26591fc4b..ae7f124b5 100644 --- a/packages/cli/src/lib/api/alerts.ts +++ b/packages/cli/src/lib/api/alerts.ts @@ -670,7 +670,7 @@ function buildDetectorDataSource( dataset: native.dataset, query: typeof body.query === "string" ? body.query : "", queryType: native.queryType, - eventTypes: [...native.eventTypes], + eventTypes: Array.from(native.eventTypes), timeWindow: timeWindowMinutes * 60, }; if (typeof body.environment === "string" && body.environment !== "") { diff --git a/packages/cli/src/lib/api/issue-app-links.ts b/packages/cli/src/lib/api/issue-app-links.ts index 4e209ee18..e4f56e825 100644 --- a/packages/cli/src/lib/api/issue-app-links.ts +++ b/packages/cli/src/lib/api/issue-app-links.ts @@ -485,7 +485,9 @@ async function resolveFields( ); throw new ValidationError( missing.size - ? `Missing app link fields: ${[...missing].map((name) => `--field ${name}=VALUE`).join(", ")}` + ? `Missing app link fields: ${Array.from(missing) + .map((name) => `--field ${name}=VALUE`) + .join(", ")}` : "App link fields have circular dependencies", "field", ); diff --git a/packages/cli/src/lib/api/logs.ts b/packages/cli/src/lib/api/logs.ts index e14e0d631..4aa79ef0b 100644 --- a/packages/cli/src/lib/api/logs.ts +++ b/packages/cli/src/lib/api/logs.ts @@ -365,7 +365,9 @@ async function getLogsBatch( const logsById = new Map( response.data.map((log) => [log["sentry.item_id"], log]), ); - const missingIds = [...new Set(batchIds)].filter((id) => !logsById.has(id)); + const missingIds = Array.from(new Set(batchIds)).filter( + (id) => !logsById.has(id), + ); if (missingIds.length === 0) { return response.data; } @@ -376,7 +378,7 @@ async function getLogsBatch( for (const log of retry.data) { logsById.set(log["sentry.item_id"], log); } - return [...logsById.values()]; + return Array.from(logsById.values()); } /** Options for {@link getLogs}. */ diff --git a/packages/cli/src/lib/api/replays.ts b/packages/cli/src/lib/api/replays.ts index cedb8fa14..a5b9ab7c3 100644 --- a/packages/cli/src/lib/api/replays.ts +++ b/packages/cli/src/lib/api/replays.ts @@ -199,7 +199,7 @@ async function fetchReplayPage( field: options.fields && options.fields.length > 0 ? options.fields - : [...REPLAY_LIST_FIELDS], + : Array.from(REPLAY_LIST_FIELDS), per_page: perPage, project: options.project, projectSlug: options.projectSlugs, diff --git a/packages/cli/src/lib/build/index.ts b/packages/cli/src/lib/build/index.ts index 3671324bb..13b5a1c41 100644 --- a/packages/cli/src/lib/build/index.ts +++ b/packages/cli/src/lib/build/index.ts @@ -489,7 +489,7 @@ async function addParsedAssetCatalogs( files.set(`${prefix}/${file.name}`, file.sourcePath); } } - for (const [name, sourcePath] of [...files].sort(([a], [b]) => + for (const [name, sourcePath] of Array.from(files).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0, )) { await zip.addFile(name, sourcePath); diff --git a/packages/cli/src/lib/complete.ts b/packages/cli/src/lib/complete.ts index ab89246ab..36b8e37ae 100644 --- a/packages/cli/src/lib/complete.ts +++ b/packages/cli/src/lib/complete.ts @@ -531,7 +531,7 @@ function matchCachedEntities( } } const rank = new Map( - fuzzyMatch(partial, [...candidates], { mode: "search" }).map( + fuzzyMatch(partial, Array.from(candidates), { mode: "search" }).map( (candidate, index) => [candidate, index], ), ); diff --git a/packages/cli/src/lib/completions.ts b/packages/cli/src/lib/completions.ts index 13866aa21..fb6db0f0c 100644 --- a/packages/cli/src/lib/completions.ts +++ b/packages/cli/src/lib/completions.ts @@ -81,7 +81,7 @@ function extractFlagEntries( if (def.kind === "enum" && "values" in def) { const enumDef = def as FlagDef & { values?: readonly string[] }; if (enumDef.values) { - entry.enumValues = [...enumDef.values]; + entry.enumValues = Array.from(enumDef.values); } } entries.push(entry); @@ -261,7 +261,7 @@ export function generateBashCompletion(binaryName: string): string { ) .join("\n"); - const uniqueEnumFlags = [...new Set(enumFlagNames)]; + const uniqueEnumFlags = Array.from(new Set(enumFlagNames)); const enumFlagWords = uniqueEnumFlags.join(" "); // Build the enum flag test only if there are any enum flags diff --git a/packages/cli/src/lib/db/accounts.ts b/packages/cli/src/lib/db/accounts.ts index a9eb90c36..4c5cdb493 100644 --- a/packages/cli/src/lib/db/accounts.ts +++ b/packages/cli/src/lib/db/accounts.ts @@ -351,7 +351,7 @@ export function findSimilarAccounts( } } const ranked: AccountSummary[] = []; - for (const value of fuzzyMatch(needle, [...byValue.keys()])) { + for (const value of fuzzyMatch(needle, Array.from(byValue.keys()))) { for (const account of byValue.get(value) ?? []) { if (!ranked.some((a) => a.id === account.id)) { ranked.push(account); diff --git a/packages/cli/src/lib/db/pagination.ts b/packages/cli/src/lib/db/pagination.ts index b56811e5d..c39d1e706 100644 --- a/packages/cli/src/lib/db/pagination.ts +++ b/packages/cli/src/lib/db/pagination.ts @@ -273,7 +273,7 @@ export function advancePaginationState( case "prev": { const newIndex = Math.max(0, state.index - 1); // Refresh the "next" entry from this position in case data shifted - const stack = [...state.stack]; + const stack = Array.from(state.stack); if (nextCursor) { stack[newIndex + 1] = nextCursor; } @@ -525,6 +525,9 @@ export function buildMultiOrgContextKey( orgs: string[], query: string | undefined, ): string { - const sortedOrgs = [...orgs].sort().map(escapeContextKeyValue).join(","); + const sortedOrgs = Array.from(orgs) + .sort() + .map(escapeContextKeyValue) + .join(","); return buildPaginationContextKey("multi-org", sortedOrgs, { q: query }); } diff --git a/packages/cli/src/lib/dif/find.ts b/packages/cli/src/lib/dif/find.ts index 7ca4c1878..c03c50e46 100644 --- a/packages/cli/src/lib/dif/find.ts +++ b/packages/cli/src/lib/dif/find.ts @@ -181,7 +181,7 @@ async function tryProguard( return; } // Only worth hashing when a ProGuard-shaped id is still sought. - if (![...state.remaining].some(isProguardId)) { + if (!Array.from(state.remaining).some(isProguardId)) { return; } let uuid: string; @@ -333,7 +333,7 @@ export async function findDebugFiles(opts: FindOptions): Promise { // Breakpad-only matches are reported but still counted as missing. const missingIds = new Set([...state.remaining, ...state.breakpadFound]); - const missing: MissingId[] = [...missingIds].map((id) => ({ + const missing: MissingId[] = Array.from(missingIds).map((id) => ({ id, hint: idHint(id), })); diff --git a/packages/cli/src/lib/docs-context.ts b/packages/cli/src/lib/docs-context.ts index 5ce885daf..ade7659d9 100644 --- a/packages/cli/src/lib/docs-context.ts +++ b/packages/cli/src/lib/docs-context.ts @@ -129,8 +129,8 @@ export async function detectDocsContextFromReader( } return { - frameworks: [...frameworks].sort(), - languages: [...languages].sort(), + frameworks: Array.from(frameworks).sort(), + languages: Array.from(languages).sort(), sentryConfigured, }; } diff --git a/packages/cli/src/lib/dsn/code-scanner.ts b/packages/cli/src/lib/dsn/code-scanner.ts index 113315b2f..bfe863b11 100644 --- a/packages/cli/src/lib/dsn/code-scanner.ts +++ b/packages/cli/src/lib/dsn/code-scanner.ts @@ -119,7 +119,7 @@ export function extractDsnsFromContent( } } } - return [...dsns]; + return Array.from(dsns); } /** @@ -300,7 +300,7 @@ function scanDirectory(cwd: string): Promise { "dsn.dsns_found": seen.size, }); - return { dsns: [...seen.values()], sourceMtimes, dirMtimes }; + return { dsns: Array.from(seen.values()), sourceMtimes, dirMtimes }; } catch (error) { if (error instanceof ConfigError) { throw error; diff --git a/packages/cli/src/lib/dsn/env-file.ts b/packages/cli/src/lib/dsn/env-file.ts index e4409a560..7d31362bf 100644 --- a/packages/cli/src/lib/dsn/env-file.ts +++ b/packages/cli/src/lib/dsn/env-file.ts @@ -118,7 +118,7 @@ export async function detectFromEnvFiles( "detectFromEnvFiles", "dsn.detect.env", async (span) => { - const { dsns } = await scanSpecificFiles(cwd, [...ENV_FILES], { + const { dsns } = await scanSpecificFiles(cwd, Array.from(ENV_FILES), { stopOnFirst: true, processFile: (_relativePath, content) => { const dsn = extractDsnFromEnvContent(content); @@ -157,7 +157,7 @@ export async function detectFromAllEnvFiles( // 1. Check root .env files (all of them, not just first) const { dsns: rootDsns, sourceMtimes: rootMtimes } = - await scanSpecificFiles(cwd, [...ENV_FILES], { + await scanSpecificFiles(cwd, Array.from(ENV_FILES), { stopOnFirst: false, processFile: (_relativePath, content) => { const dsn = extractDsnFromEnvContent(content); @@ -260,7 +260,7 @@ async function detectDsnInPackage( ): Promise { const { dsns, sourceMtimes: rawMtimes } = await scanSpecificFiles( pkgDir, - [...ENV_FILES], + Array.from(ENV_FILES), { stopOnFirst: true, processFile: (_relativePath, content) => { diff --git a/packages/cli/src/lib/dsn/parser.ts b/packages/cli/src/lib/dsn/parser.ts index 9b92caf69..0cc35dae1 100644 --- a/packages/cli/src/lib/dsn/parser.ts +++ b/packages/cli/src/lib/dsn/parser.ts @@ -256,7 +256,7 @@ export function createDsnFingerprint(dsns: DetectedDsn[]): string { .sort(); // Deduplicate (same DSN might be detected from multiple sources) - return [...new Set(keys)].join(","); + return Array.from(new Set(keys)).join(","); } /** diff --git a/packages/cli/src/lib/envelope/event-send-dsn.ts b/packages/cli/src/lib/envelope/event-send-dsn.ts index 91b5a2b02..c8a43020c 100644 --- a/packages/cli/src/lib/envelope/event-send-dsn.ts +++ b/packages/cli/src/lib/envelope/event-send-dsn.ts @@ -44,7 +44,7 @@ export function peelEventSendTarget(files: readonly string[]): { } { const first = files[0]; if (!first) { - return { target: undefined, files: [...files] }; + return { target: undefined, files: Array.from(files) }; } if (looksLikeDsn(first)) { return { @@ -53,7 +53,7 @@ export function peelEventSendTarget(files: readonly string[]): { }; } if (looksLikeFileArgument(first)) { - return { target: undefined, files: [...files] }; + return { target: undefined, files: Array.from(files) }; } return { target: { kind: "project", target: first }, @@ -132,14 +132,14 @@ async function dsnFromProjectTarget( "event send", ); const keys = await getProjectKeys(org, project, { status: "active" }); - const activeDsns = [ - ...new Set( + const activeDsns = Array.from( + new Set( keys .filter((key) => key.isActive) .map((key) => key.dsn.public) .filter(Boolean), ), - ]; + ); if (activeDsns.length > 1) { throw new ConfigError( `Project ${org}/${project} has multiple active DSNs. Pass the desired DSN as the first argument.`, diff --git a/packages/cli/src/lib/errors.ts b/packages/cli/src/lib/errors.ts index 71fb08bd2..87e824f1e 100644 --- a/packages/cli/src/lib/errors.ts +++ b/packages/cli/src/lib/errors.ts @@ -463,9 +463,8 @@ export class ContextError extends CliError { ) { // When alternatives is omitted, auto-detection was tried and failed const isAutoDetect = alternatives === undefined; - const resolvedAlternatives = alternatives ?? [ - ...DEFAULT_CONTEXT_ALTERNATIVES, - ]; + const resolvedAlternatives = + alternatives ?? Array.from(DEFAULT_CONTEXT_ALTERNATIVES); // Include full formatted message so it's shown even when caught by external handlers super( diff --git a/packages/cli/src/lib/formatters/conversation.ts b/packages/cli/src/lib/formatters/conversation.ts index 27a1db5ee..5393cebbe 100644 --- a/packages/cli/src/lib/formatters/conversation.ts +++ b/packages/cli/src/lib/formatters/conversation.ts @@ -331,7 +331,7 @@ function extractAssistantContent(span: AgentConversationSpan): string | null { export function extractTurns( spans: AgentConversationSpan[], ): ConversationTurn[] { - const sorted = [...spans].sort( + const sorted = Array.from(spans).sort( (a, b) => a["precise.start_ts"] - b["precise.start_ts"], ); const aiClientSpans = sorted.filter( @@ -557,9 +557,9 @@ export function buildTranscriptResult( // double-counting parent agent spans. totalTokens: stats?.totalTokens ?? turns.reduce((sum, t) => sum + t.totalTokens, 0), - traceIds: [...new Set(spans.map((s) => s.trace))].sort(), + traceIds: Array.from(new Set(spans.map((s) => s.trace))).sort(), spanCount: spans.length, - projects: [...new Set(spans.map((s) => s.project))].sort(), + projects: Array.from(new Set(spans.map((s) => s.project))).sort(), startTimestamp: spans.length > 0 ? spans.reduce( diff --git a/packages/cli/src/lib/formatters/dashboard.ts b/packages/cli/src/lib/formatters/dashboard.ts index 24554d6a0..b8e502d4c 100644 --- a/packages/cli/src/lib/formatters/dashboard.ts +++ b/packages/cli/src/lib/formatters/dashboard.ts @@ -400,7 +400,7 @@ type BarItem = { label: string; total: number; unit?: string | null }; /** Sort items by value descending, "Other" pinned to end. */ function sortBarItems(items: BarItem[]): BarItem[] { - return [...items].sort((a, b) => { + return Array.from(items).sort((a, b) => { if (a.label === "Other") { return 1; } diff --git a/packages/cli/src/lib/formatters/human.ts b/packages/cli/src/lib/formatters/human.ts index 7f55c8cef..ffdffa344 100644 --- a/packages/cli/src/lib/formatters/human.ts +++ b/packages/cli/src/lib/formatters/human.ts @@ -965,7 +965,7 @@ function formatExceptionValueMarkdown(exception: ExceptionValue): string { lines.push(""); const frames = exception.stacktrace?.frames ?? []; - const reversedFrames = [...frames].reverse(); + const reversedFrames = Array.from(frames).reverse(); for (const frame of reversedFrames) { lines.push(formatStackFrameMarkdown(frame)); } diff --git a/packages/cli/src/lib/formatters/local.ts b/packages/cli/src/lib/formatters/local.ts index 3f45946fb..750f87e90 100644 --- a/packages/cli/src/lib/formatters/local.ts +++ b/packages/cli/src/lib/formatters/local.ts @@ -427,7 +427,7 @@ function formatAttrGroup( if (entries.length === 0) { return []; } - const sorted = [...entries].sort(([a], [b]) => a.localeCompare(b)); + const sorted = Array.from(entries).sort(([a], [b]) => a.localeCompare(b)); const keyWidth = Math.max(...sorted.map(([k]) => sanitize(k).length)); const lines = [`${ATTR_INDENT}${muted(title)}`]; for (const [key, value] of sorted) { @@ -832,7 +832,7 @@ function buildJsonAttributes(payload: Record): { const merged = collectAllAttributes(payload); const user: Record = {}; const sdk: Record = {}; - for (const [key, value] of [...merged].sort(([a], [b]) => + for (const [key, value] of Array.from(merged).sort(([a], [b]) => a.localeCompare(b), )) { const target = isSdkAttribute(key) ? sdk : user; diff --git a/packages/cli/src/lib/formatters/text-table.ts b/packages/cli/src/lib/formatters/text-table.ts index aa50970ad..7b690b2d9 100644 --- a/packages/cli/src/lib/formatters/text-table.ts +++ b/packages/cli/src/lib/formatters/text-table.ts @@ -289,7 +289,7 @@ function fitProportional( const shrinkable = baseWidths.map((w, i) => w - (floorWidths[i] ?? 0)); const totalShrinkable = shrinkable.reduce((s, v) => s + v, 0); if (totalShrinkable <= 0) { - return [...floorWidths]; + return Array.from(floorWidths); } return allocateShrink({ @@ -340,7 +340,7 @@ function fitBalanced( const shrinkable = baseWidths.map((w, i) => w - (floorWidths[i] ?? 0)); const totalShrinkable = shrinkable.reduce((s, v) => s + v, 0); if (totalShrinkable <= 0) { - return [...floorWidths]; + return Array.from(floorWidths); } return allocateShrink({ @@ -380,7 +380,7 @@ function allocateShrink(params: ShrinkParams): number[] { const totalWeight = weights.reduce((s, v) => s + v, 0); if (totalWeight <= 0) { - return [...floorWidths]; + return Array.from(floorWidths); } // oxlint-disable-next-line unicorn/no-new-array -- This explicitly allocates a fixed-length array. diff --git a/packages/cli/src/lib/formatters/trace.ts b/packages/cli/src/lib/formatters/trace.ts index 82e16f728..d35b39736 100644 --- a/packages/cli/src/lib/formatters/trace.ts +++ b/packages/cli/src/lib/formatters/trace.ts @@ -202,7 +202,11 @@ export function parseTraceFields( additionalAttributes.add(field); } } - return { paths, attributes, additionalAttributes: [...additionalAttributes] }; + return { + paths, + attributes, + additionalAttributes: Array.from(additionalAttributes), + }; } type ProjectedTraceSpan = Partial> & { @@ -346,7 +350,7 @@ export function computeTraceSummary( traceId, duration, spanCount: state.spanCount, - projects: [...state.projects], + projects: Array.from(state.projects), rootTransaction: state.rootTransaction, rootOp: state.rootOp, startTimestamp: state.minStart, diff --git a/packages/cli/src/lib/fuzzy.ts b/packages/cli/src/lib/fuzzy.ts index 6041fc0b6..3979a5041 100644 --- a/packages/cli/src/lib/fuzzy.ts +++ b/packages/cli/src/lib/fuzzy.ts @@ -111,7 +111,7 @@ export function fuzzyMatch( opts?: FuzzyMatchOptions, ): string[] { if (partial === "") { - const sorted = [...candidates].sort(); + const sorted = Array.from(candidates).sort(); return opts?.maxResults ? sorted.slice(0, opts.maxResults) : sorted; } diff --git a/packages/cli/src/lib/init/interactive.ts b/packages/cli/src/lib/init/interactive.ts index 6c040de37..271f861cf 100644 --- a/packages/cli/src/lib/init/interactive.ts +++ b/packages/cli/src/lib/init/interactive.ts @@ -78,7 +78,7 @@ function normalizeFeatureSelection(features: string[]): string[] { ) { normalized.add("performanceMonitoring"); } - return sortFeatureOptions([...normalized]); + return sortFeatureOptions(Array.from(normalized)); } function buildFeatureReviewDetails(features: string[]): PromptDetail[] { diff --git a/packages/cli/src/lib/init/tools/file-changes/contract.ts b/packages/cli/src/lib/init/tools/file-changes/contract.ts index 22e678bd3..76e804a87 100644 --- a/packages/cli/src/lib/init/tools/file-changes/contract.ts +++ b/packages/cli/src/lib/init/tools/file-changes/contract.ts @@ -63,7 +63,9 @@ export function parseFileChangesRequest( ): FileChangesContractResult { const result = safeParse(legacyApplyPatchsetPayloadSchema, input); if (!result.success) { - const details = [...new Set(result.issues.map((issue) => issue.message))]; + const details = Array.from( + new Set(result.issues.map((issue) => issue.message)), + ); return { error: `Invalid file changes request: ${details.join("; ")}`, ok: false, diff --git a/packages/cli/src/lib/init/ui/wizard-store.ts b/packages/cli/src/lib/init/ui/wizard-store.ts index 756206772..fac2aa49f 100644 --- a/packages/cli/src/lib/init/ui/wizard-store.ts +++ b/packages/cli/src/lib/init/ui/wizard-store.ts @@ -438,7 +438,7 @@ export class WizardStore { byPath.set(path, { path, status: "reading" }); } } - this.update({ filesRead: [...byPath.values()] }); + this.update({ filesRead: Array.from(byPath.values()) }); } /** @@ -496,7 +496,7 @@ export class WizardStore { for (const path of paths) { byPath.set(path, { path, status: "analyzed" }); } - this.update({ filesRead: [...byPath.values()] }); + this.update({ filesRead: Array.from(byPath.values()) }); } appendStatus(message: string): void { @@ -689,7 +689,7 @@ function applyStepStatus( if (status === "skipped" && current.status !== "pending") { return steps; } - const updated = [...steps]; + const updated = Array.from(steps); updated[targetIndex] = { ...current, status }; return updated; } diff --git a/packages/cli/src/lib/init/wizard-runner.ts b/packages/cli/src/lib/init/wizard-runner.ts index bc162d794..2514f1673 100644 --- a/packages/cli/src/lib/init/wizard-runner.ts +++ b/packages/cli/src/lib/init/wizard-runner.ts @@ -777,7 +777,7 @@ function httpStatus(err: unknown): number | undefined { } function runStateRecoveryBackoffMs(): number[] { - const delays = [...RUN_STATE_RECOVERY_INITIAL_BACKOFF_MS]; + const delays = Array.from(RUN_STATE_RECOVERY_INITIAL_BACKOFF_MS); let totalWaitMs = delays.reduce((total, delayMs) => total + delayMs, 0); while (totalWaitMs < RUN_STATE_RECOVERY_MAX_WAIT_MS) { const delayMs = Math.min( @@ -1460,7 +1460,7 @@ function activeStepIdsFor(result: WorkflowRunResult, stepId: string): string[] { if (ids.size === 0 && stepId !== "unknown") { ids.add(stepId); } - return [...ids]; + return Array.from(ids); } function extractSuspendPayloadFromStep( diff --git a/packages/cli/src/lib/list-filter.ts b/packages/cli/src/lib/list-filter.ts index 0a1247f14..b22138249 100644 --- a/packages/cli/src/lib/list-filter.ts +++ b/packages/cli/src/lib/list-filter.ts @@ -32,7 +32,7 @@ export function filterByName( ): T[] { const needle = filter?.trim().toLowerCase(); if (!needle) { - return [...items]; + return Array.from(items); } if (isGlobFilter(needle)) { // Names aren't paths: `bash` lets `*` match `/` too, so `*-sdk` matches @@ -42,7 +42,9 @@ export function filterByName( namesOf(item).some((name) => matches(name.toLowerCase())), ); } - const candidates = [...new Set(items.flatMap((item) => namesOf(item)))]; + const candidates = Array.from( + new Set(items.flatMap((item) => namesOf(item))), + ); const rankOf = new Map( fuzzyMatch(needle, candidates, { mode: "search" }).map((name, rank) => [ name, diff --git a/packages/cli/src/lib/mcp.ts b/packages/cli/src/lib/mcp.ts index e5b8711e8..98005e07a 100644 --- a/packages/cli/src/lib/mcp.ts +++ b/packages/cli/src/lib/mcp.ts @@ -32,7 +32,7 @@ export function prepareMcpServerArgs( sentryUrl = getConfiguredSentryUrl(), ): string[] { if (!sentryUrl || hasSentryTargetArg(args)) { - return [...args]; + return Array.from(args); } // oxlint-disable-next-line sentry-cli/no-silent-catch -- invalid URLs are passed through to the MCP parser for its normal validation error. diff --git a/packages/cli/src/lib/metrics-transform.ts b/packages/cli/src/lib/metrics-transform.ts index bb3fd712c..5dd88f86b 100644 --- a/packages/cli/src/lib/metrics-transform.ts +++ b/packages/cli/src/lib/metrics-transform.ts @@ -49,7 +49,7 @@ export function resolveMetricField( if (!VALID_AGGS.has(agg)) { throw new ResolutionError( `Aggregation '${agg}'`, - `not recognized. Valid aggregations: ${[...VALID_AGGS].join(", ")}`, + `not recognized. Valid aggregations: ${Array.from(VALID_AGGS).join(", ")}`, `sentry explore my-org/ -m ${metricName} --agg sum --dataset metrics`, ); } diff --git a/packages/cli/src/lib/org-list.ts b/packages/cli/src/lib/org-list.ts index 5a3dc984f..790f73e7d 100644 --- a/packages/cli/src/lib/org-list.ts +++ b/packages/cli/src/lib/org-list.ts @@ -1056,7 +1056,7 @@ export async function handleProjectSearch( .filter((r): r is AuthGuardSuccess => r.ok) .flatMap((r) => r.value); } else { - const uniqueOrgs = [...new Set(matches.map((m) => m.orgSlug))]; + const uniqueOrgs = Array.from(new Set(matches.map((m) => m.orgSlug))); const results = await Promise.all( uniqueOrgs.map((org) => fetchOrgSafe(config, org)), ); diff --git a/packages/cli/src/lib/positional-parse.ts b/packages/cli/src/lib/positional-parse.ts index d4908d7da..bbfee24be 100644 --- a/packages/cli/src/lib/positional-parse.ts +++ b/packages/cli/src/lib/positional-parse.ts @@ -132,7 +132,7 @@ export function applyBoundPositionals( ): unknown[] { const def = positional as PositionalDef | undefined; if (!def) { - return [...args]; + return Array.from(args); } const count = def.kind === "tuple" diff --git a/packages/cli/src/lib/replay-search.ts b/packages/cli/src/lib/replay-search.ts index 15fd9c8c8..d4ed9b751 100644 --- a/packages/cli/src/lib/replay-search.ts +++ b/packages/cli/src/lib/replay-search.ts @@ -42,7 +42,7 @@ export function parseReplayEnvironmentFilter( values: readonly string[] | undefined, ): string[] | undefined { const parsed = values - ? [...values] + ? Array.from(values) .flatMap((value) => value.split(",")) .map((value) => value.trim()) .filter(Boolean) @@ -173,9 +173,9 @@ export function isSupportedReplayField(field: string): boolean { /** List the replay fields the CLI can render in replay search outputs. */ export function listSupportedReplayFields(): string[] { - return [ - ...new Set([...REPLAY_API_FIELDS, ...Object.keys(DERIVED_REPLAY_FIELDS)]), - ].sort(); + return Array.from( + new Set([...REPLAY_API_FIELDS, ...Object.keys(DERIVED_REPLAY_FIELDS)]), + ).sort(); } /** @@ -189,7 +189,7 @@ export function getReplayRequestFields(fields: string[]): string[] { roots.add(getDerivedReplayField(field)?.root ?? replayFieldRoot(field)); } - return [...roots]; + return Array.from(roots); } /** Read a dotted path such as `user.geo.city` from a replay row. */ diff --git a/packages/cli/src/lib/resolve-target.ts b/packages/cli/src/lib/resolve-target.ts index b8abff9ed..2d5d0b552 100644 --- a/packages/cli/src/lib/resolve-target.ts +++ b/packages/cli/src/lib/resolve-target.ts @@ -1195,7 +1195,7 @@ function combineProjectNotFoundErrors( const [first, ...others] = misses.map( ({ error }) => new Set(error.suggestions), ); - const shared = [...(first ?? [])].filter((suggestion) => + const shared = Array.from(first ?? []).filter((suggestion) => others.every((suggestions) => suggestions.has(suggestion)), ); const specific = misses.flatMap(({ slug, error }) => @@ -1517,10 +1517,12 @@ export async function resolveAllTargets( ); } else { span.setAttribute("resolve.method", "inference"); - const uniqueOrgs = [...new Set(result.targets.map((t) => t.org))]; - const uniqueProjects = [ - ...new Set(result.targets.map((t) => t.project)), - ]; + const uniqueOrgs = Array.from( + new Set(result.targets.map((t) => t.org)), + ); + const uniqueProjects = Array.from( + new Set(result.targets.map((t) => t.project)), + ); setOrgProjectContext(uniqueOrgs, uniqueProjects); } return result; @@ -1557,7 +1559,7 @@ async function resolveDetectedDsns( uniqueDsnMap.set(dedupeKey, dsn); } } - const uniqueDsns = [...uniqueDsnMap.values()]; + const uniqueDsns = Array.from(uniqueDsnMap.values()); log.debug( `Resolving ${uniqueDsns.length} unique DSN targets (${detection.all.length} total detected)`, @@ -1599,8 +1601,8 @@ async function resolveDetectedDsns( targets.length > 1 ? formatMultipleProjectsFooter(targets) : undefined; // Set telemetry context for all resolved targets - const uniqueOrgs = [...new Set(targets.map((t) => t.org))]; - const uniqueProjects = [...new Set(targets.map((t) => t.project))]; + const uniqueOrgs = Array.from(new Set(targets.map((t) => t.org))); + const uniqueProjects = Array.from(new Set(targets.map((t) => t.project))); setOrgProjectContext(uniqueOrgs, uniqueProjects); return { @@ -2336,9 +2338,9 @@ export async function resolveOrgsForListing( if (targetsResult.ok) { const { targets, footer, skippedSelfHosted } = targetsResult.value; if (targets.length > 0) { - const uniqueOrgs = [ - ...new Set(targets.map((t: ResolvedTarget) => t.org)), - ]; + const uniqueOrgs = Array.from( + new Set(targets.map((t: ResolvedTarget) => t.org)), + ); setOrgProjectContext(uniqueOrgs, []); return { orgs: uniqueOrgs, footer, skippedSelfHosted }; } @@ -2728,8 +2730,8 @@ export async function resolveProjectBoundTargets( projectDisplay: m.name, })); - const uniqueOrgs = [...new Set(targets.map((t) => t.org))]; - const uniqueProjects = [...new Set(targets.map((t) => t.project))]; + const uniqueOrgs = Array.from(new Set(targets.map((t) => t.org))); + const uniqueProjects = Array.from(new Set(targets.map((t) => t.project))); setOrgProjectContext(uniqueOrgs, uniqueProjects); return { diff --git a/packages/cli/src/lib/resolve-team.ts b/packages/cli/src/lib/resolve-team.ts index ce03ed235..3f9aaae07 100644 --- a/packages/cli/src/lib/resolve-team.ts +++ b/packages/cli/src/lib/resolve-team.ts @@ -220,7 +220,7 @@ export function pickDefaultTeam( ): SentryTeam | undefined { const adminTeams = teams.filter(canCreateProjectInTeam); const pool = adminTeams.length > 0 ? adminTeams : teams; - return [...pool].sort( + return Array.from(pool).sort( (left, right) => teamCreatedAt(left) - teamCreatedAt(right) || left.slug.localeCompare(right.slug), diff --git a/packages/cli/src/lib/scan/ignore.ts b/packages/cli/src/lib/scan/ignore.ts index bf98d8d57..acc6716b7 100644 --- a/packages/cli/src/lib/scan/ignore.ts +++ b/packages/cli/src/lib/scan/ignore.ts @@ -115,7 +115,7 @@ export class IgnoreStack implements IgnoreMatcher { // These apply even when `respectGitignore: false` because they're // the walker's policy, not part of the user's gitignore. if (opts.alwaysSkipDirs.length > 0) { - root.add([...opts.alwaysSkipDirs]); + root.add(Array.from(opts.alwaysSkipDirs)); } if (respectGitignore) { await appendGitignoreFile(root, path.join(opts.cwd, ".gitignore")); diff --git a/packages/cli/src/lib/scan/regex.ts b/packages/cli/src/lib/scan/regex.ts index 77e21df4c..d3cc86115 100644 --- a/packages/cli/src/lib/scan/regex.ts +++ b/packages/cli/src/lib/scan/regex.ts @@ -156,7 +156,7 @@ function translateFlags(raw: string): string { } } // Deterministic, RegExp-accepted order. - return [...seen] + return Array.from(seen) .filter((f) => VALID_JS_FLAGS.includes(f)) .sort() .join(""); @@ -208,7 +208,7 @@ export function compilePattern( if (opts.multiline) { flags.add("m"); } - const flagString = [...flags].sort().join(""); + const flagString = Array.from(flags).sort().join(""); try { return new RegExp(cleaned, flagString); } catch (error) { diff --git a/packages/cli/src/lib/sixel-image.ts b/packages/cli/src/lib/sixel-image.ts index c4ad23f05..bb637d812 100644 --- a/packages/cli/src/lib/sixel-image.ts +++ b/packages/cli/src/lib/sixel-image.ts @@ -381,7 +381,7 @@ function selectSplitTarget( /** Split a box in two at the median of the given channel. */ function splitBox(box: ColorBox, channel: number): [ColorBox, ColorBox] { - const sorted = [...box.colors].sort( + const sorted = Array.from(box.colors).sort( (a, b) => (a[channel] as number) - (b[channel] as number), ); const mid = Math.floor(sorted.length / 2); @@ -515,7 +515,7 @@ function colorsInBand(plane: IndexedPlane, y0: number): number[] { } } } - return [...seen]; + return Array.from(seen); } /** diff --git a/packages/cli/src/lib/snapshots/images.ts b/packages/cli/src/lib/snapshots/images.ts index b22581ee9..cb5b2194d 100644 --- a/packages/cli/src/lib/snapshots/images.ts +++ b/packages/cli/src/lib/snapshots/images.ts @@ -27,7 +27,7 @@ export const IMAGE_EXTENSIONS: ReadonlySet = new Set([ /** Same extensions in the walker's `.ext` (lowercased, dotted) form. */ const WALK_EXTENSIONS: ReadonlySet = new Set( - [...IMAGE_EXTENSIONS].map((ext) => `.${ext}`), + Array.from(IMAGE_EXTENSIONS).map((ext) => `.${ext}`), ); const PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); diff --git a/packages/cli/src/lib/sourcemap/debug-id.ts b/packages/cli/src/lib/sourcemap/debug-id.ts index 54dcd5e53..1f881abe0 100644 --- a/packages/cli/src/lib/sourcemap/debug-id.ts +++ b/packages/cli/src/lib/sourcemap/debug-id.ts @@ -440,7 +440,7 @@ export async function injectInlineDebugId( // modifying the file. Return an EMPTY debug ID and no map content so the // upload path attaches nothing — otherwise a debug ID and pre-injection map // would be uploaded for a bundle that has no snippet/comment/updated map. - const matches = [...jsContent.matchAll(INLINE_DIRECTIVE_RE)]; + const matches = Array.from(jsContent.matchAll(INLINE_DIRECTIVE_RE)); const last = matches.at(-1); if (last?.index === undefined) { log.debug( diff --git a/packages/cli/src/lib/wrangler.ts b/packages/cli/src/lib/wrangler.ts index 959802df9..f7f6cb995 100644 --- a/packages/cli/src/lib/wrangler.ts +++ b/packages/cli/src/lib/wrangler.ts @@ -398,7 +398,7 @@ function injectIntoShellArgs( shellExecutable === "cmd" || shellExecutable === "cmd.exe", ); if (injected) { - const augmented = [...args]; + const augmented = Array.from(args); augmented[index] = injected; return { args: augmented, injected: true }; } @@ -413,7 +413,7 @@ function injectIntoPackageScript( invocation: PackageScriptInvocation, script: string, ): WranglerCommandAugmentation { - const unchanged = { args: [...args], injected: false }; + const unchanged = { args: Array.from(args), injected: false }; const packageWrangler = findShellWranglerCommand(script); if (!packageWrangler || SHELL_SPOTLIGHT_VAR_RE.test(script)) { return unchanged; @@ -465,7 +465,7 @@ export async function injectWranglerSpotlightBinding( spotlightUrl: string, cwd: string, ): Promise { - const unchanged = { args: [...args], injected: false }; + const unchanged = { args: Array.from(args), injected: false }; const packageInvocation = packageScriptInvocation(args); const packageScript = packageInvocation ? await readPackageScript(cwd, packageInvocation.name) diff --git a/packages/cli/src/types/seer.ts b/packages/cli/src/types/seer.ts index a706dd945..c92664085 100644 --- a/packages/cli/src/types/seer.ts +++ b/packages/cli/src/types/seer.ts @@ -643,5 +643,5 @@ export function extractExaminedFiles(causes: RootCause[]): string[] { } } } - return [...files]; + return Array.from(files); } diff --git a/packages/cli/test/commands/cli/uninstall.test.ts b/packages/cli/test/commands/cli/uninstall.test.ts index ab5abf4d9..968bedd3b 100644 --- a/packages/cli/test/commands/cli/uninstall.test.ts +++ b/packages/cli/test/commands/cli/uninstall.test.ts @@ -32,7 +32,7 @@ const getConfigDir = useTestConfigDir("uninstall-"); let savedArgv: string[]; beforeEach(() => { - savedArgv = [...process.argv]; + savedArgv = Array.from(process.argv); // Override argv[1] to avoid vitest's node_modules path triggering // detectRuntimePackageManager() in every test process.argv[1] = "/usr/local/bin/sentry"; diff --git a/packages/cli/test/commands/cli/upgrade.test.ts b/packages/cli/test/commands/cli/upgrade.test.ts index ee57b7976..28167a85b 100644 --- a/packages/cli/test/commands/cli/upgrade.test.ts +++ b/packages/cli/test/commands/cli/upgrade.test.ts @@ -933,7 +933,7 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy spawnSpy = vi .spyOn(child_process, "spawn") .mockImplementation((cmd: string, args?: readonly string[]) => { - spawnedArgs.push({ cmd, args: [...(args ?? [])] }); + spawnedArgs.push({ cmd, args: Array.from(args ?? []) }); return fakeChildProcess(0); }); }); @@ -1025,7 +1025,7 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy const downgradeSpy = vi.spyOn(binary, "isDowngrade").mockReturnValue(true); let versionAtSetup: number | undefined; spawnSpy.mockImplementation((cmd: string, args?: readonly string[]) => { - spawnedArgs.push({ cmd, args: [...(args ?? [])] }); + spawnedArgs.push({ cmd, args: Array.from(args ?? []) }); versionAtSetup = readSchemaVersion(); return fakeChildProcess(0); }); @@ -1081,7 +1081,7 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy mockBinaryDownloadWithVersion("0.47.0"); const downgradeSpy = vi.spyOn(binary, "isDowngrade").mockReturnValue(true); spawnSpy.mockImplementation((cmd: string, args?: readonly string[]) => { - spawnedArgs.push({ cmd, args: [...(args ?? [])] }); + spawnedArgs.push({ cmd, args: Array.from(args ?? []) }); // Another invocation of this CLI re-migrates the database meanwhile. getRawDatabase() .query("UPDATE schema_version SET version = ?") @@ -1115,7 +1115,7 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy const downgradeSpy = vi.spyOn(binary, "isDowngrade").mockReturnValue(true); let versionAtSetup: number | undefined; spawnSpy.mockImplementation((cmd: string, args?: readonly string[]) => { - spawnedArgs.push({ cmd, args: [...(args ?? [])] }); + spawnedArgs.push({ cmd, args: Array.from(args ?? []) }); versionAtSetup = readSchemaVersion(); return fakeChildProcess(1); }); @@ -1150,7 +1150,7 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy mockTaggedBinaryDownload("0.47.0"); const downgradeSpy = vi.spyOn(binary, "isDowngrade").mockReturnValue(true); spawnSpy.mockImplementation((cmd: string, args?: readonly string[]) => { - spawnedArgs.push({ cmd, args: [...(args ?? [])] }); + spawnedArgs.push({ cmd, args: Array.from(args ?? []) }); return fakeChildProcess(1); }); @@ -1196,7 +1196,7 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy mockBinaryDownloadWithVersion(NEXT_RELEASE); let backupsAtSetup: string[] = []; spawnSpy.mockImplementation((cmd: string, args?: readonly string[]) => { - spawnedArgs.push({ cmd, args: [...(args ?? [])] }); + spawnedArgs.push({ cmd, args: Array.from(args ?? []) }); backupsAtSetup = readdirSync(getBackupDir()); return fakeChildProcess(0); }); @@ -1221,7 +1221,7 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy mockTaggedBinaryDownload(NEXT_RELEASE); let markerAtSetup: string | undefined; spawnSpy.mockImplementation((cmd: string, args?: readonly string[]) => { - spawnedArgs.push({ cmd, args: [...(args ?? [])] }); + spawnedArgs.push({ cmd, args: Array.from(args ?? []) }); const db = new Database(getDbPath()); try { markerAtSetup = getMetadata(db, [marker]).get(marker); @@ -1254,7 +1254,7 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy setMetadata(getRawDatabase(), { [marker]: "current" }); mockTaggedBinaryDownload(NEXT_RELEASE); spawnSpy.mockImplementation((cmd: string, args?: readonly string[]) => { - spawnedArgs.push({ cmd, args: [...(args ?? [])] }); + spawnedArgs.push({ cmd, args: Array.from(args ?? []) }); return fakeChildProcess(1); }); @@ -1311,7 +1311,7 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy await backupDatabase(getRawDatabase(), NEXT_RELEASE, CLI_VERSION); mockBinaryDownloadWithVersion(LATER_RELEASE); spawnSpy.mockImplementation((cmd: string, args?: readonly string[]) => { - spawnedArgs.push({ cmd, args: [...(args ?? [])] }); + spawnedArgs.push({ cmd, args: Array.from(args ?? []) }); return fakeChildProcess(1); }); diff --git a/packages/cli/test/commands/dsn/list.test.ts b/packages/cli/test/commands/dsn/list.test.ts index 9ad1c0ad7..7e30a86a0 100644 --- a/packages/cli/test/commands/dsn/list.test.ts +++ b/packages/cli/test/commands/dsn/list.test.ts @@ -128,11 +128,11 @@ function mockProjectApi( return mockFetch(async (input, init) => { const request = new Request(input, init); const url = new URL(request.url); - const organizations = [ - ...new Map( + const organizations = Array.from( + new Map( projects.map((item) => [item.organization.slug, item.organization]), ).values(), - ].map((org) => ({ + ).map((org) => ({ ...org, name: org.slug, links: { regionUrl: "https://us.sentry.io" }, diff --git a/packages/cli/test/commands/explore.test.ts b/packages/cli/test/commands/explore.test.ts index a742a83f1..51504f780 100644 --- a/packages/cli/test/commands/explore.test.ts +++ b/packages/cli/test/commands/explore.test.ts @@ -1772,7 +1772,7 @@ describe("sentry explore", () => { { ...DEFAULT_FLAGS, dataset: "replays", - field: [...DEFAULT_REPLAY_EXPLORE_FIELDS], + field: Array.from(DEFAULT_REPLAY_EXPLORE_FIELDS), }, "test-org/", ); diff --git a/packages/cli/test/commands/issue/list.property.test.ts b/packages/cli/test/commands/issue/list.property.test.ts index 2719ede0a..4d853c34b 100644 --- a/packages/cli/test/commands/issue/list.property.test.ts +++ b/packages/cli/test/commands/issue/list.property.test.ts @@ -497,7 +497,7 @@ describe("property: buildProjectAliasMap", () => { const results = pairs.map(([org, proj]) => makeResult(org, proj)); const { aliasMap } = buildProjectAliasMap(results); - const aliases = [...aliasMap.values()]; + const aliases = Array.from(aliasMap.values()); expect(new Set(aliases).size).toBe(aliases.length); }, ), diff --git a/packages/cli/test/commands/log/list.test.ts b/packages/cli/test/commands/log/list.test.ts index edafd7370..61e546190 100644 --- a/packages/cli/test/commands/log/list.test.ts +++ b/packages/cli/test/commands/log/list.test.ts @@ -382,7 +382,7 @@ describe("listCommand.func — standard mode", () => { test("outputs JSON in newest-first order by default", async () => { // API returns newest first: item003, item002, item001 - const newestFirst = [...sampleLogs].reverse(); + const newestFirst = Array.from(sampleLogs).reverse(); listLogsSpy.mockResolvedValue(newestFirst); resolveOrgProjectSpy.mockResolvedValue({ org: ORG, project: PROJECT }); @@ -634,7 +634,7 @@ describe("listCommand.func — trace mode", () => { }); test("outputs JSON in newest-first order by default (trace mode)", async () => { - const newestFirst = [...sampleTraceLogs].reverse(); + const newestFirst = Array.from(sampleTraceLogs).reverse(); listTraceLogsSpy.mockResolvedValue(newestFirst); resolveTraceOrgSpy.mockResolvedValue({ traceId: TRACE_ID, org: ORG }); diff --git a/packages/cli/test/commands/platform/list.test.ts b/packages/cli/test/commands/platform/list.test.ts index 108e29253..a60e92b9e 100644 --- a/packages/cli/test/commands/platform/list.test.ts +++ b/packages/cli/test/commands/platform/list.test.ts @@ -53,7 +53,7 @@ describe("platform list", () => { const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); const parsed = JSON.parse(output); expect(Array.isArray(parsed)).toBe(true); - expect(parsed).toEqual([...VALID_PLATFORMS]); + expect(parsed).toEqual(Array.from(VALID_PLATFORMS)); }); test("--search filters to matching platforms only", async () => { diff --git a/packages/cli/test/commands/replay/list.test.ts b/packages/cli/test/commands/replay/list.test.ts index e4f05bad6..fbb004754 100644 --- a/packages/cli/test/commands/replay/list.test.ts +++ b/packages/cli/test/commands/replay/list.test.ts @@ -145,7 +145,7 @@ describe("listCommand.func", () => { expect(listReplaysSpy).toHaveBeenCalledWith("test-org", { environment: undefined, - fields: [...REPLAY_LIST_FIELDS], + fields: Array.from(REPLAY_LIST_FIELDS), limit: 25, projectSlugs: ["cli"], query: undefined, @@ -232,7 +232,7 @@ describe("listCommand.func", () => { expect(listReplaysSpy).toHaveBeenCalledWith("test-org", { environment: ["production", "canary", "staging"], - fields: [...REPLAY_LIST_FIELDS], + fields: Array.from(REPLAY_LIST_FIELDS), limit: 25, projectSlugs: ["cli"], query: undefined, diff --git a/packages/cli/test/fixtures/bench/generate.ts b/packages/cli/test/fixtures/bench/generate.ts index 28f009964..ab697289b 100644 --- a/packages/cli/test/fixtures/bench/generate.ts +++ b/packages/cli/test/fixtures/bench/generate.ts @@ -376,9 +376,9 @@ function specMatches(a: FixtureMeta["spec"], b: FixtureSpec): boolean { export function hashSpec(spec: FixtureMeta["spec"]): string { const canonical = JSON.stringify({ ...spec, - fileExtensions: [...spec.fileExtensions].sort(), + fileExtensions: Array.from(spec.fileExtensions).sort(), binaryExtensions: spec.binaryExtensions - ? [...spec.binaryExtensions].sort() + ? Array.from(spec.binaryExtensions).sort() : undefined, }); return createHash("sha256").update(canonical).digest("hex").slice(0, 16); diff --git a/packages/cli/test/fixtures/bench/helpers.ts b/packages/cli/test/fixtures/bench/helpers.ts index 6ddbcf9d9..708ba754e 100644 --- a/packages/cli/test/fixtures/bench/helpers.ts +++ b/packages/cli/test/fixtures/bench/helpers.ts @@ -109,7 +109,7 @@ export function summarize(samples: readonly number[]): BenchStats { if (samples.length === 0) { return { runs: 0, min: 0, max: 0, mean: 0, stddev: 0, p50: 0, p95: 0 }; } - const sorted = [...samples].sort((a, b) => a - b); + const sorted = Array.from(samples).sort((a, b) => a - b); const n = sorted.length; const min = sorted[0] as number; const max = sorted[n - 1] as number; @@ -287,7 +287,7 @@ export function compareReports( } const rows: ComparisonRow[] = []; - for (const key of [...keys].sort()) { + for (const key of Array.from(keys).sort()) { const b = index.get(`b:${key}`); const c = index.get(`c:${key}`); const [fixture, operation] = key.split("||"); diff --git a/packages/cli/test/lib/alias.property.test.ts b/packages/cli/test/lib/alias.property.test.ts index 71b008218..4d82d4ccc 100644 --- a/packages/cli/test/lib/alias.property.test.ts +++ b/packages/cli/test/lib/alias.property.test.ts @@ -86,7 +86,7 @@ describe("property: findShortestUniquePrefixes", () => { fcAssert( property(uniqueStringsArb, (strings) => { const prefixes = findShortestUniquePrefixes(strings); - const prefixValues = [...prefixes.values()]; + const prefixValues = Array.from(prefixes.values()); const uniquePrefixValues = new Set(prefixValues); // All prefixes should be unique @@ -284,7 +284,7 @@ describe("property: buildOrgAwareAliases", () => { array(orgProjectPairArb, { minLength: 1, maxLength: 10 }), (pairs) => { const { aliasMap } = buildOrgAwareAliases(pairs); - const aliases = [...aliasMap.values()]; + const aliases = Array.from(aliasMap.values()); const uniqueAliases = new Set(aliases); expect(uniqueAliases.size).toBe(aliases.length); diff --git a/packages/cli/test/lib/alias.test.ts b/packages/cli/test/lib/alias.test.ts index cecd95115..ea652a135 100644 --- a/packages/cli/test/lib/alias.test.ts +++ b/packages/cli/test/lib/alias.test.ts @@ -37,7 +37,7 @@ describe("alias generation integration", () => { expect(slugToRemainder.get("spotlight")).toBe("spotlight"); // No prefix to strip // Find unique prefixes for remainders - const remainders = [...slugToRemainder.values()]; + const remainders = Array.from(slugToRemainder.values()); const prefixes = findShortestUniquePrefixes(remainders); expect(prefixes.get("electron")).toBe("e"); @@ -123,7 +123,7 @@ describe("buildOrgAwareAliases specific outputs", () => { expect(result.aliasMap.get("org2/web")).toContain("/"); // All should be unique - const aliases = [...result.aliasMap.values()]; + const aliases = Array.from(result.aliasMap.values()); const uniqueAliases = new Set(aliases); expect(uniqueAliases.size).toBe(aliases.length); }); @@ -138,7 +138,7 @@ describe("buildOrgAwareAliases specific outputs", () => { ]); // All four should be unique - const aliases = [...result.aliasMap.values()]; + const aliases = Array.from(result.aliasMap.values()); const uniqueAliases = new Set(aliases); expect(uniqueAliases.size).toBe(4); @@ -197,7 +197,7 @@ describe("buildOrgAwareAliases specific outputs", () => { ]); // All aliases must be unique - const aliases = [...result.aliasMap.values()]; + const aliases = Array.from(result.aliasMap.values()); const uniqueAliases = new Set(aliases); expect(uniqueAliases.size).toBe(aliases.length); diff --git a/packages/cli/test/lib/api-schema.test.ts b/packages/cli/test/lib/api-schema.test.ts index 25ff58084..7ddf8a5b6 100644 --- a/packages/cli/test/lib/api-schema.test.ts +++ b/packages/cli/test/lib/api-schema.test.ts @@ -48,7 +48,7 @@ describe("getAllResources", () => { expect(resources.length).toBeGreaterThan(10); // Verify sorted - const sorted = [...resources].sort(); + const sorted = Array.from(resources).sort(); expect(resources).toEqual(sorted); // Verify unique @@ -262,7 +262,7 @@ describe("getResourceSummaries", () => { test("methods are sorted and unique", () => { for (const summary of getResourceSummaries()) { - const sorted = [...summary.methods].sort(); + const sorted = Array.from(summary.methods).sort(); expect(summary.methods).toEqual(sorted); expect(new Set(summary.methods).size).toBe(summary.methods.length); } diff --git a/packages/cli/test/lib/api/trace-item-attributes.test.ts b/packages/cli/test/lib/api/trace-item-attributes.test.ts index 1d96de763..0bfbe18e6 100644 --- a/packages/cli/test/lib/api/trace-item-attributes.test.ts +++ b/packages/cli/test/lib/api/trace-item-attributes.test.ts @@ -66,7 +66,7 @@ describe("listTraceItemAttributes", () => { expect(capturedUrl!.pathname).toBe( "/api/0/organizations/test-org/trace-items/attributes/", ); - expect([...capturedUrl!.searchParams.keys()]).toEqual(["dataset"]); + expect(Array.from(capturedUrl!.searchParams.keys())).toEqual(["dataset"]); expect(capturedUrl!.searchParams.get("dataset")).toBe("spans"); expect(data).toEqual(ATTRIBUTES); }); @@ -98,7 +98,7 @@ describe("listTraceItemAttributes", () => { query: "", }); - expect([...capturedUrl!.searchParams.keys()]).toEqual(["dataset"]); + expect(Array.from(capturedUrl!.searchParams.keys())).toEqual(["dataset"]); }); test("sends perPage as per_page", async () => { diff --git a/packages/cli/test/lib/build/asset-catalog-extract.test.ts b/packages/cli/test/lib/build/asset-catalog-extract.test.ts index 40e77650c..6cd321e4b 100644 --- a/packages/cli/test/lib/build/asset-catalog-extract.test.ts +++ b/packages/cli/test/lib/build/asset-catalog-extract.test.ts @@ -239,7 +239,7 @@ describe("asset-catalog extraction boundary", () => { ...imageEntry(id), name: i ? "Other.jpg" : "Logo.jpg", })); - const output = await writeOutput(args, [...assets].reverse()); + const output = await writeOutput(args, Array.from(assets).reverse()); for (const asset of assets) { await writeFile(join(output, `${asset.imageId}.jpg`), asset.name); } diff --git a/packages/cli/test/lib/build/car-extract.integration.test.ts b/packages/cli/test/lib/build/car-extract.integration.test.ts index 504c435d4..d8659271c 100644 --- a/packages/cli/test/lib/build/car-extract.integration.test.ts +++ b/packages/cli/test/lib/build/car-extract.integration.test.ts @@ -115,7 +115,7 @@ function assertParsedAssets( ); if (imagePath.endsWith(".png")) { const image = Buffer.from(entries[imagePath] ?? []); - expect([...image.subarray(0, 8)]).toEqual([ + expect(Array.from(image.subarray(0, 8))).toEqual([ 137, 80, 78, 71, 13, 10, 26, 10, ]); expect(image.readUInt32BE(16)).toBe(asset?.width); @@ -167,7 +167,7 @@ describe.skipIf(process.platform !== "darwin" || process.arch !== "arm64")( const bytes = await readFile(path); expect(bytes.length).toBeGreaterThan(8); if (extension === "png") { - expect([...bytes.subarray(0, 8)]).toEqual([ + expect(Array.from(bytes.subarray(0, 8))).toEqual([ 137, 80, 78, 71, 13, 10, 26, 10, ]); } diff --git a/packages/cli/test/lib/command.test.ts b/packages/cli/test/lib/command.test.ts index d85cb039d..4ccef9883 100644 --- a/packages/cli/test/lib/command.test.ts +++ b/packages/cli/test/lib/command.test.ts @@ -445,7 +445,7 @@ describe("buildCommand telemetry integration", () => { describe("LOG_LEVEL_FLAG", () => { test("is an enum flag with all log level names", () => { expect(LOG_LEVEL_FLAG.kind).toBe("enum"); - expect(LOG_LEVEL_FLAG.values).toEqual([...LOG_LEVEL_NAMES]); + expect(LOG_LEVEL_FLAG.values).toEqual(Array.from(LOG_LEVEL_NAMES)); }); test("is optional and hidden", () => { diff --git a/packages/cli/test/lib/custom-headers.test.ts b/packages/cli/test/lib/custom-headers.test.ts index 18cb7a10c..aaa4c142c 100644 --- a/packages/cli/test/lib/custom-headers.test.ts +++ b/packages/cli/test/lib/custom-headers.test.ts @@ -468,7 +468,7 @@ describe("applyCustomHeaders", () => { applyCustomHeaders(headers, "https://sentry.io/api/0/"); // Only the original header - const keys = [...headers.keys()]; + const keys = Array.from(headers.keys()); expect(keys).toEqual(["accept"]); }); diff --git a/packages/cli/test/lib/db/accounts.test.ts b/packages/cli/test/lib/db/accounts.test.ts index 60e86f01f..1e6496f2d 100644 --- a/packages/cli/test/lib/db/accounts.test.ts +++ b/packages/cli/test/lib/db/accounts.test.ts @@ -357,7 +357,9 @@ describe("serverKey", () => { expect(set.size).toBe(1); expect(set.has(undefined)).toBe(false); } - expect(new Set(keys.flatMap((set) => [...set])).size).toBe(groups.length); + expect(new Set(keys.flatMap((set) => Array.from(set))).size).toBe( + groups.length, + ); expect(isSameServer("https://us.sentry.io", "https://sentry.io")).toBe( true, ); diff --git a/packages/cli/test/lib/db/pagination.model-based.test.ts b/packages/cli/test/lib/db/pagination.model-based.test.ts index bd906c9de..7745ae877 100644 --- a/packages/cli/test/lib/db/pagination.model-based.test.ts +++ b/packages/cli/test/lib/db/pagination.model-based.test.ts @@ -147,7 +147,7 @@ class AdvancePrevCommand implements AsyncCommand { advancePaginationState(CMD_KEY, CTX_KEY, "prev", this.nextCursor); const newIndex = Math.max(0, model.index! - 1); - const stack = [...model.stack!]; + const stack = Array.from(model.stack!); if (this.nextCursor) { stack[newIndex + 1] = this.nextCursor; } diff --git a/packages/cli/test/lib/dsn.property.test.ts b/packages/cli/test/lib/dsn.property.test.ts index 9327966ec..cec2b18f1 100644 --- a/packages/cli/test/lib/dsn.property.test.ts +++ b/packages/cli/test/lib/dsn.property.test.ts @@ -324,7 +324,7 @@ describe("property: createDsnFingerprint", () => { if (detected.length < 2) return; const fp1 = createDsnFingerprint(detected); - const fp2 = createDsnFingerprint([...detected].reverse()); + const fp2 = createDsnFingerprint(Array.from(detected).reverse()); expect(fp1).toBe(fp2); }), { numRuns: DEFAULT_NUM_RUNS }, diff --git a/packages/cli/test/lib/formatters/pixel-canvas.test.ts b/packages/cli/test/lib/formatters/pixel-canvas.test.ts index ab7dd667e..0fb34b3b5 100644 --- a/packages/cli/test/lib/formatters/pixel-canvas.test.ts +++ b/packages/cli/test/lib/formatters/pixel-canvas.test.ts @@ -35,20 +35,22 @@ describe("drawPixelText", () => { }); test("keeps Spleen's native box and block glyphs for dashboard content", () => { - expect([...getSpleenGlyph("▀")]).toEqual([ + expect(Array.from(getSpleenGlyph("▀"))).toEqual([ 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, ]); - expect([...getSpleenGlyph("─")]).toEqual([ + expect(Array.from(getSpleenGlyph("─"))).toEqual([ 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, ]); }); test("uses a question mark for unsupported text glyphs", () => { - expect([...getSpleenGlyph("\u6f22")]).toEqual([...getSpleenGlyph("?")]); + expect(Array.from(getSpleenGlyph("\u6f22"))).toEqual( + Array.from(getSpleenGlyph("?")), + ); }); test("includes the dashboard axis, sparkline, and ellipsis glyphs", () => { - const questionMark = [...getSpleenGlyph("?")]; + const questionMark = Array.from(getSpleenGlyph("?")); for (const character of [ "│", "└", @@ -66,7 +68,7 @@ describe("drawPixelText", () => { "▓", "■", ]) { - expect([...getSpleenGlyph(character)]).not.toEqual(questionMark); + expect(Array.from(getSpleenGlyph(character))).not.toEqual(questionMark); } }); diff --git a/packages/cli/test/lib/fuzzy.test.ts b/packages/cli/test/lib/fuzzy.test.ts index 3513d603f..defb411ff 100644 --- a/packages/cli/test/lib/fuzzy.test.ts +++ b/packages/cli/test/lib/fuzzy.test.ts @@ -90,7 +90,7 @@ describe("fuzzyMatch: unit tests", () => { test("empty partial returns all candidates sorted", () => { const result = fuzzyMatch("", candidates); - expect(result).toEqual([...candidates].sort()); + expect(result).toEqual(Array.from(candidates).sort()); }); test("exact match ranks first", () => { diff --git a/packages/cli/test/lib/global-flags.test.ts b/packages/cli/test/lib/global-flags.test.ts index d532ade11..886df7725 100644 --- a/packages/cli/test/lib/global-flags.test.ts +++ b/packages/cli/test/lib/global-flags.test.ts @@ -59,10 +59,10 @@ describe("buildTopLevelFlags", () => { test("matches the current GLOBAL_FLAGS definition", () => { const { booleanFlags, valueFlags } = buildTopLevelFlags(); - expect([...booleanFlags].sort()).toEqual( + expect(Array.from(booleanFlags).sort()).toEqual( ["--verbose", "-v", "--no-verbose", "--json", "--no-json"].sort(), ); - expect([...valueFlags].sort()).toEqual( + expect(Array.from(valueFlags).sort()).toEqual( ["--log-level", "--fields", "--org", "--project"].sort(), ); }); diff --git a/packages/cli/test/lib/jvm-bundle.test.ts b/packages/cli/test/lib/jvm-bundle.test.ts index ea61fb51f..910c3a103 100644 --- a/packages/cli/test/lib/jvm-bundle.test.ts +++ b/packages/cli/test/lib/jvm-bundle.test.ts @@ -246,7 +246,7 @@ describe("buildJvmBundle", () => { expect(result.fileCount).toBe(1); // URL should be package-relative, not main/java/... - const urls = [...result.files.keys()]; + const urls = Array.from(result.files.keys()); expect(urls[0]).toBe("~/com/example/App.jvm"); }); @@ -288,7 +288,7 @@ describe("buildJvmBundle", () => { }); expect(result.fileCount).toBe(1); - expect([...result.files.keys()][0]).toBe("~/com/example/Linked.jvm"); + expect(Array.from(result.files.keys())[0]).toBe("~/com/example/Linked.jvm"); }); test("follows symlinked directories", async () => { @@ -309,7 +309,7 @@ describe("buildJvmBundle", () => { }); expect(result.fileCount).toBe(1); - expect([...result.files.keys()][0]).toBe("~/com/example/Shared.jvm"); + expect(Array.from(result.files.keys())[0]).toBe("~/com/example/Shared.jvm"); }); test("does not loop on symlink cycles", async () => { @@ -328,6 +328,6 @@ describe("buildJvmBundle", () => { // The single real file is collected exactly once despite the cycle. expect(result.fileCount).toBe(1); - expect([...result.files.keys()][0]).toBe("~/com/example/App.jvm"); + expect(Array.from(result.files.keys())[0]).toBe("~/com/example/App.jvm"); }); }); diff --git a/packages/cli/test/lib/security/fetch-layer-guard.test.ts b/packages/cli/test/lib/security/fetch-layer-guard.test.ts index 330d2cc23..20506b1f6 100644 --- a/packages/cli/test/lib/security/fetch-layer-guard.test.ts +++ b/packages/cli/test/lib/security/fetch-layer-guard.test.ts @@ -54,7 +54,7 @@ describe("CVE defense-in-depth: fetch layer refuses mismatched hosts", () => { expect(headers.get("X-IAP-Token")).toBeNull(); // Verify no other custom headers leaked either - expect([...headers.keys()]).toHaveLength(0); + expect(Array.from(headers.keys())).toHaveLength(0); }); test("applyCustomHeaders: IAP token attaches to trusted URL", () => { diff --git a/packages/cli/test/lib/snapshots/images.test.ts b/packages/cli/test/lib/snapshots/images.test.ts index 300753816..1924ebc16 100644 --- a/packages/cli/test/lib/snapshots/images.test.ts +++ b/packages/cli/test/lib/snapshots/images.test.ts @@ -57,7 +57,7 @@ describe("collectImages", () => { const images = await collectImages(root); const byKey = new Map(images.map((i) => [i.relativePath, i])); - expect([...byKey.keys()].sort()).toEqual(["a.png", "sub/b.png"]); + expect(Array.from(byKey.keys()).sort()).toEqual(["a.png", "sub/b.png"]); const a = byKey.get("a.png"); expect(a?.width).toBe(4); expect(a?.height).toBe(3); diff --git a/packages/cli/test/lib/sourcemap/resolve.test.ts b/packages/cli/test/lib/sourcemap/resolve.test.ts index 50ccfb379..5ecb13095 100644 --- a/packages/cli/test/lib/sourcemap/resolve.test.ts +++ b/packages/cli/test/lib/sourcemap/resolve.test.ts @@ -96,6 +96,6 @@ describe("resolveDirectorySourcemaps", () => { const results = await resolveDirectorySourcemaps(dir); const names = results.map((r) => r.jsPath); // Byte-wise sort, matching resolveDirectorySourcemaps' path ordering. - expect(names).toEqual([...names].sort()); + expect(names).toEqual(Array.from(names).sort()); }); }); diff --git a/packages/cli/test/lib/time-range.property.test.ts b/packages/cli/test/lib/time-range.property.test.ts index 14476e27e..ec1daacb8 100644 --- a/packages/cli/test/lib/time-range.property.test.ts +++ b/packages/cli/test/lib/time-range.property.test.ts @@ -41,7 +41,7 @@ const isoDateArb = date({ /** Generate a pair of sorted ISO date strings for valid ranges */ const sortedDatePairArb = array(isoDateArb, { minLength: 2, maxLength: 2 }).map( (dates) => { - const sorted = [...dates].sort(); + const sorted = Array.from(dates).sort(); // Ensure they're different (no zero-length range issues) if (sorted[0] === sorted[1]) { const d = new Date(sorted[1]!); diff --git a/packages/cli/test/lib/upgrade.test.ts b/packages/cli/test/lib/upgrade.test.ts index e9144caf0..b1087396e 100644 --- a/packages/cli/test/lib/upgrade.test.ts +++ b/packages/cli/test/lib/upgrade.test.ts @@ -1394,7 +1394,7 @@ describe("detectPackageManagerFromPath", () => { let originalArgv: string[]; beforeEach(() => { - originalArgv = [...process.argv]; + originalArgv = Array.from(process.argv); }); afterEach(() => { @@ -1480,7 +1480,7 @@ describe("detectInstallationMethod — node_modules path fallback", () => { let originalExecPath: string; beforeEach(() => { - originalArgv = [...process.argv]; + originalArgv = Array.from(process.argv); originalExecPath = process.execPath; // Typical Node.js execPath — not Homebrew, not curl Object.defineProperty(process, "execPath", { @@ -2773,7 +2773,7 @@ describe("detectInstallationMethod — legacy pm detection via isInstalledWith", beforeEach(() => { originalExecPath = process.execPath; - originalArgv = [...process.argv]; + originalArgv = Array.from(process.argv); // Non-Homebrew, non-known-curl execPath so detection falls through to pm checks Object.defineProperty(process, "execPath", { value: "/usr/bin/sentry", diff --git a/packages/cli/test/script/sdk-declarations.test.ts b/packages/cli/test/script/sdk-declarations.test.ts index 6449ce64b..9f0e60393 100644 --- a/packages/cli/test/script/sdk-declarations.test.ts +++ b/packages/cli/test/script/sdk-declarations.test.ts @@ -22,7 +22,7 @@ describe("SDK entry-point declarations", () => { const declarations = buildCoreDeclarations(source); const options = extractSentryOptions(source); - const optionNames = [...options.matchAll(/^ {2}(\w+)\??:/gm)].map( + const optionNames = Array.from(options.matchAll(/^ {2}(\w+)\??:/gm)).map( (match) => match[1], ); diff --git a/scripts/cli-lint-rules.test.mjs b/scripts/cli-lint-rules.test.mjs index 46bc0c351..208bfc940 100644 --- a/scripts/cli-lint-rules.test.mjs +++ b/scripts/cli-lint-rules.test.mjs @@ -204,6 +204,30 @@ tester.run( }, ); +tester.run("prefer-array-from", plugin.rules["prefer-array-from"], { + valid: [ + { filename: source, code: "const copy = Array.from(items);" }, + { filename: source, code: "const merged = [...first, ...second];" }, + { filename: source, code: "const withTail = [...items, tail];" }, + { filename: source, code: "call(...args);" }, + { filename: source, code: "const merged = { ...base };" }, + ], + invalid: [ + { + filename: source, + code: "const keys = [...map.keys()].sort();", + output: "const keys = Array.from(map.keys()).sort();", + errors: 1, + }, + { + filename: "/repo/packages/cli/test/example.test.ts", + code: "const copy = [...(values ?? fallback)];", + output: "const copy = Array.from(values ?? fallback);", + errors: 1, + }, + ], +}); + tester.run( "no-regex-markdown-parsing", plugin.rules["no-regex-markdown-parsing"], From d408298fac8502588f2bec376203f4342079d54a Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:41:53 +0000 Subject: [PATCH 2/2] fix(cli): Keep comma-expression parens in prefer-array-from fix --- packages/cli/lint-rules/cli-oxlint-plugin.js | 14 +++++++++----- scripts/cli-lint-rules.test.mjs | 6 ++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/cli/lint-rules/cli-oxlint-plugin.js b/packages/cli/lint-rules/cli-oxlint-plugin.js index c015ee31b..85c0b305e 100644 --- a/packages/cli/lint-rules/cli-oxlint-plugin.js +++ b/packages/cli/lint-rules/cli-oxlint-plugin.js @@ -432,11 +432,15 @@ const rules = { context.report({ node, message: PREFER_ARRAY_FROM_MESSAGE, - fix: (fixer) => - fixer.replaceText( - node, - `Array.from(${context.sourceCode.getText(element.argument)})`, - ), + fix(fixer) { + const value = context.sourceCode.getText(element.argument); + // `[...(a, b)]` must not become `Array.from(a, b)`. + const wrapped = + element.argument.type === "SequenceExpression" + ? `(${value})` + : value; + return fixer.replaceText(node, `Array.from(${wrapped})`); + }, }); }, }; diff --git a/scripts/cli-lint-rules.test.mjs b/scripts/cli-lint-rules.test.mjs index 208bfc940..cfe29957c 100644 --- a/scripts/cli-lint-rules.test.mjs +++ b/scripts/cli-lint-rules.test.mjs @@ -225,6 +225,12 @@ tester.run("prefer-array-from", plugin.rules["prefer-array-from"], { output: "const copy = Array.from(values ?? fallback);", errors: 1, }, + { + filename: source, + code: "const copy = [...(first, second)];", + output: "const copy = Array.from((first, second));", + errors: 1, + }, ], });