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..85c0b305e 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,40 @@ 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) { + 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})`); + }, + }); + }, + }; + }, + }, "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..cfe29957c 100644 --- a/scripts/cli-lint-rules.test.mjs +++ b/scripts/cli-lint-rules.test.mjs @@ -204,6 +204,36 @@ 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, + }, + { + filename: source, + code: "const copy = [...(first, second)];", + output: "const copy = Array.from((first, second));", + errors: 1, + }, + ], +}); + tester.run( "no-regex-markdown-parsing", plugin.rules["no-regex-markdown-parsing"],