From d0c226d15552b92a31e780efbe4bf86773088871 Mon Sep 17 00:00:00 2001 From: YosefHayim Date: Fri, 7 Aug 2026 14:05:23 +0300 Subject: [PATCH] refactor(cli-agents): readonly-domain-types consumer fixes --- src/cli/commands/completion.ts | 2 +- src/cli/commands/testflight.ts | 4 ++-- src/cli/options.ts | 2 +- src/core/dashboard/render.ts | 8 ++++---- src/core/docs/commandDocs/commandReference.ts | 2 +- src/core/docs/commandDocs/common.ts | 2 +- src/core/insights/command.ts | 5 +++-- src/core/mcp/gate.test.ts | 2 +- src/core/mcp/tools.test.ts | 4 ++-- src/core/terminal/completion.ts | 2 +- src/core/terminal/halfblock.ts | 2 +- src/core/terminal/wizardCommand.test.ts | 2 +- src/core/terminal/wizardCommand.ts | 4 ++-- 13 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/cli/commands/completion.ts b/src/cli/commands/completion.ts index a0d98006..c1c08100 100644 --- a/src/cli/commands/completion.ts +++ b/src/cli/commands/completion.ts @@ -27,7 +27,7 @@ export const registerCompletionCommand = (program: Command): void => { completion .command(`${COMPLETE_SUBCOMMAND} [words...]`, { hidden: true }) .description('internal: emit completion candidates for the words typed so far') - .action((words: string[]) => { + .action((words: readonly string[]) => { return runCliProgram( completionCommandProgram({ operation: 'complete', words, commandTree: program }), ); diff --git a/src/cli/commands/testflight.ts b/src/cli/commands/testflight.ts index eb7325a5..aa55ae83 100644 --- a/src/cli/commands/testflight.ts +++ b/src/cli/commands/testflight.ts @@ -94,7 +94,7 @@ export const registerTestflightCommand = (program: Command): void => { .option('--csv ', 'import testers from a CSV (email,firstName,lastName per line)') .option('--dry-run', 'report what would change without inviting anyone', false) .option('-y, --yes', 'skip the confirmation prompt', false) - .action((emails: string[], commandOptions: AddTesterOptions) => + .action((emails: readonly string[], commandOptions: AddTesterOptions) => runCliProgram( testflightCommandProgram({ operation: 'add', @@ -118,7 +118,7 @@ export const registerTestflightCommand = (program: Command): void => { .option('-g, --group ', "beta group to remove from (auto-selected if there's only one)") .option('--dry-run', 'report what would change without removing anyone', false) .option('-y, --yes', 'skip the confirmation prompt', false) - .action((emails: string[], commandOptions: TesterMutationOptions) => + .action((emails: readonly string[], commandOptions: TesterMutationOptions) => runCliProgram( testflightCommandProgram({ operation: 'remove', diff --git a/src/cli/options.ts b/src/cli/options.ts index ff91499e..d3f47b68 100644 --- a/src/cli/options.ts +++ b/src/cli/options.ts @@ -10,7 +10,7 @@ export type EnvFlags = { printEnv: boolean; }; /** Commander reducer: collect a repeatable string option into an array. */ -const collectEnv = (environmentFlag: string, previousFlags: string[]): string[] => { +const collectEnv = (environmentFlag: string, previousFlags: readonly string[]): string[] => { return [...previousFlags, environmentFlag]; }; /** diff --git a/src/core/dashboard/render.ts b/src/core/dashboard/render.ts index cd562b23..1a759e9f 100644 --- a/src/core/dashboard/render.ts +++ b/src/core/dashboard/render.ts @@ -46,7 +46,7 @@ const renderSection = (sectionTitle: string, sectionHtml: string): string => const renderProviderChip = (providerLabel: string, providerName: string): string => `${escapeHtml(providerLabel)} ${escapeHtml(providerName)}`; -const renderAppsTable = (apps: DashboardApp[]): string => { +const renderAppsTable = (apps: readonly DashboardApp[]): string => { const appTableRows = apps.map((app) => [ renderTableCell(app.name), renderTableCell(app.version), @@ -65,7 +65,7 @@ const renderAccountStatus = (account: DashboardAccount): string => { return renderTableCell(null); }; -const renderAccountsTable = (accounts: DashboardAccount[]): string => { +const renderAccountsTable = (accounts: readonly DashboardAccount[]): string => { const accountTableRows = accounts.map((account) => [ renderTableCell(account.label), renderTableCell(account.keyId), @@ -90,7 +90,7 @@ const renderArtifactStatus = (buildArtifact: DashboardArtifact): string => { return 'on disk'; }; -const renderArtifactsTable = (buildArtifacts: DashboardArtifact[]): string => { +const renderArtifactsTable = (buildArtifacts: readonly DashboardArtifact[]): string => { const artifactTableRows = buildArtifacts.map((buildArtifact) => [ renderTableCell(buildArtifact.app), renderTableCell(buildArtifact.platform), @@ -112,7 +112,7 @@ const renderSecretScope = (buildSecret: DashboardSecret): string => { return renderTableCell(buildSecret.profile); }; -const renderSecretsTable = (buildSecrets: DashboardSecret[]): string => { +const renderSecretsTable = (buildSecrets: readonly DashboardSecret[]): string => { const secretTableRows = buildSecrets.map((buildSecret) => [ renderTableCell(buildSecret.app), renderSecretScope(buildSecret), diff --git a/src/core/docs/commandDocs/commandReference.ts b/src/core/docs/commandDocs/commandReference.ts index 1a35172d..af49a707 100644 --- a/src/core/docs/commandDocs/commandReference.ts +++ b/src/core/docs/commandDocs/commandReference.ts @@ -1,7 +1,7 @@ import { escapeCell } from './common.js'; import type { CommandSpec, DocStats, OptionSpec } from '@core/types/commandDocs.js'; /** Render a command's flag table, or `""` when it has no options. */ -const renderOptionsTable = (options: OptionSpec[]): string => { +const renderOptionsTable = (options: readonly OptionSpec[]): string => { if (options.length === 0) return ''; const rows = options.map((o) => `| \`${escapeCell(o.flags)}\` | ${escapeCell(o.description)} |`); return ['', '| Flag | Description |', '| --- | --- |', ...rows].join('\n'); diff --git a/src/core/docs/commandDocs/common.ts b/src/core/docs/commandDocs/common.ts index 0f8f5c8d..6ca91c5f 100644 --- a/src/core/docs/commandDocs/common.ts +++ b/src/core/docs/commandDocs/common.ts @@ -9,7 +9,7 @@ export const countAsyncMethods = (source: string): number => { return methodMatches.length; }; /** Count test cases (`it(` / `test(` calls, including `.each` / `.skip`) across the given test sources. */ -export const countTestCases = (sources: string[]): number => { +export const countTestCases = (sources: readonly string[]): number => { let testCount = 0; for (const source of sources) { const testMatches = source.match(/^[ \t]*(?:it|test)(?:\.[a-z]+)?\(/gm); diff --git a/src/core/insights/command.ts b/src/core/insights/command.ts index 089dc832..f1c640f6 100644 --- a/src/core/insights/command.ts +++ b/src/core/insights/command.ts @@ -18,6 +18,7 @@ import type { StarRating, } from '../types/insights.js'; import { buildInsightsReport, STARS } from './aggregate.js'; +import type { MutableDeep } from '../types/mutable.js'; /** Options accepted by the cross-store insights command. */ export type InsightsCommandOptions = Readonly<{ @@ -66,7 +67,7 @@ const normalizeAscReviews = (customerReviews: readonly CustomerReviewResource[]) for (const customerReview of customerReviews) { const starRating = toStarRating(customerReview.rating); if (starRating === null) continue; - const normalizedReview: ReviewDatum = { + const normalizedReview: MutableDeep = { store: 'appstore', rating: starRating, answered: customerReview.answered, @@ -85,7 +86,7 @@ const normalizePlayReviews = (playReviews: readonly PlayReview[]): ReviewDatum[] for (const playReview of playReviews) { const starRating = toStarRating(playReview.rating); if (starRating === null) continue; - const normalizedReview: ReviewDatum = { + const normalizedReview: MutableDeep = { store: 'play', rating: starRating, answered: playReview.answered, diff --git a/src/core/mcp/gate.test.ts b/src/core/mcp/gate.test.ts index d5337820..52b99fa0 100644 --- a/src/core/mcp/gate.test.ts +++ b/src/core/mcp/gate.test.ts @@ -5,7 +5,7 @@ import type { McpTool } from '../types/mcp.js'; import type { McpCapability } from '../types/storeSurface.js'; import { enabledCapabilities, gateTools } from './gate.js'; /** A bare config with an optional `mcp` block - only the fields the gate reads matter here. */ -const config = (capabilities?: McpCapability[]): LaunchConfig => { +const config = (capabilities?: readonly McpCapability[]): LaunchConfig => { const launchConfig: LaunchConfig = { profiles: {}, credentials: 'local', diff --git a/src/core/mcp/tools.test.ts b/src/core/mcp/tools.test.ts index b559134b..3bbc0f51 100644 --- a/src/core/mcp/tools.test.ts +++ b/src/core/mcp/tools.test.ts @@ -28,7 +28,7 @@ import { makeLaunchPathsTest } from '../services/paths.js'; import { makeLaunchSecretStoreTest } from '../services/secretStore.js'; /** A bare config exposing the given MCP capability tiers - only the fields the gate reads matter here. */ -const config = (capabilities: McpCapability[]): LaunchConfig => { +const config = (capabilities: readonly McpCapability[]): LaunchConfig => { return { profiles: {}, credentials: 'local', @@ -60,7 +60,7 @@ const provideToolServices = (program: Effect.Effect) => /** Parse the JSON a successful read tool emits as its single text block. */ const parseToolOutput = ( - toolOutput: { content: { text: string }[] }, + toolOutput: { content: readonly { readonly text: string }[] }, outputSchema: Schema.Schema, ): DecodedOutput => Schema.decodeUnknownSync(outputSchema)( diff --git a/src/core/terminal/completion.ts b/src/core/terminal/completion.ts index 0c84db72..54d52e8f 100644 --- a/src/core/terminal/completion.ts +++ b/src/core/terminal/completion.ts @@ -178,7 +178,7 @@ const optionFlags = (command: Command): string[] => { */ const descendCommandTree = ( program: Command, - words: string[], + words: readonly string[], ): { command: Command; commandPath: string[]; diff --git a/src/core/terminal/halfblock.ts b/src/core/terminal/halfblock.ts index dbf14708..803e81a5 100644 --- a/src/core/terminal/halfblock.ts +++ b/src/core/terminal/halfblock.ts @@ -46,7 +46,7 @@ const paint = ( return `\x1b[${params.join(';')}m${text}\x1b[0m`; }; /** Render a row to a string, coalescing runs of same-color cells into one ANSI span (or plain text). */ -const renderRow = (cellRow: Cell[], depth: ColorDepth): string => { +const renderRow = (cellRow: readonly Cell[], depth: ColorDepth): string => { if (depth === 'none') return cellRow.map((cell) => cell.ch).join(''); let out = ''; let i = 0; diff --git a/src/core/terminal/wizardCommand.test.ts b/src/core/terminal/wizardCommand.test.ts index 2ffa4ee8..3a906a80 100644 --- a/src/core/terminal/wizardCommand.test.ts +++ b/src/core/terminal/wizardCommand.test.ts @@ -10,7 +10,7 @@ import { validateCustomBudget, } from './wizardCommand.js'; -const configWith = (profileNames: string[]): LaunchConfig => ({ +const configWith = (profileNames: readonly string[]): LaunchConfig => ({ profiles: Object.fromEntries( profileNames.map((profileName) => [profileName, { name: profileName }]), ), diff --git a/src/core/terminal/wizardCommand.ts b/src/core/terminal/wizardCommand.ts index 0e21c313..36569a6e 100644 --- a/src/core/terminal/wizardCommand.ts +++ b/src/core/terminal/wizardCommand.ts @@ -67,7 +67,7 @@ const teach = (topic: GlossaryTopic, title: string) => }); /** Select the app platform while showing whether each store is configured. */ -const selectPlatform = (configuredApps: AppDescriptor[]) => +const selectPlatform = (configuredApps: readonly AppDescriptor[]) => Effect.gen(function* () { const hasIosApp = configuredApps.some((configuredApp) => configuredApp.bundleId !== undefined); const hasAndroidApp = configuredApps.some( @@ -358,7 +358,7 @@ const isPromptSelectionFailure = (cause: unknown): cause is PromptSelectionFailu export const flowInvalidReason = ( rememberedFlow: LastFlow, launchConfig: LaunchConfig, - configuredApps: AppDescriptor[], + configuredApps: readonly AppDescriptor[], accountKeyIds: Set, ): string | null => { let platformConfigured = configuredApps.some(