diff --git a/src/core/release/statusCommand.test.ts b/src/core/release/statusCommand.test.ts index 360a18b..120b8f9 100644 --- a/src/core/release/statusCommand.test.ts +++ b/src/core/release/statusCommand.test.ts @@ -4,6 +4,7 @@ import { NodeHttpClient } from '@effect/platform-node'; import { createLogger, makeLaunchLoggerTest } from '../services/logger.js'; import type { AppDescriptor } from '../types/app.js'; import { classifyVerdict, type ReleaseStatus } from './appStoreRelease.js'; +import type { MutableDeep } from '../types/mutable.js'; import { formatStatusLine, selectIosApps, @@ -32,7 +33,7 @@ const releaseStatus = (overrides: Partial = {}): ReleaseStatus => }; const discoveredApp = (appName: string, bundleId?: string): AppDescriptor => { - const appDescriptor: AppDescriptor = { + const appDescriptor: MutableDeep = { name: appName, dir: `/repo/${appName}`, configPath: `/repo/${appName}/app.json`, diff --git a/src/core/release/testflightFeedback.ts b/src/core/release/testflightFeedback.ts index a2d5fa2..25b9d52 100644 --- a/src/core/release/testflightFeedback.ts +++ b/src/core/release/testflightFeedback.ts @@ -143,7 +143,7 @@ const safeFeedbackIdentifier = (feedbackId: string): string => { /** Download screenshot attachments serially into one directory. */ export const downloadFeedbackAttachments = ( appleStore: AscFeedbackApi, - feedbackEntries: BetaFeedback[], + feedbackEntries: readonly BetaFeedback[], outputDirectory: string, ): Effect.Effect => Effect.gen(function* () { diff --git a/src/core/release/version.ts b/src/core/release/version.ts index 6b917bb..b79dc99 100644 --- a/src/core/release/version.ts +++ b/src/core/release/version.ts @@ -72,7 +72,7 @@ export const compareVersions = (a: string, b: string): number => { * Used to fold App Store + TestFlight versions into a single "latest on record" without trusting the * store's own (lexical) sort, which would order `1.10.0` below `1.9.0`. */ -export const highestVersion = (versions: string[]): string | null => { +export const highestVersion = (versions: readonly string[]): string | null => { const parseable = versions.filter((version) => parseVersion(version) !== null); if (parseable.length === 0) return null; return parseable.reduce((highest, version) => { diff --git a/src/core/releaseTrain/orchestrator.ts b/src/core/releaseTrain/orchestrator.ts index 0b41aae..fcaf3b4 100644 --- a/src/core/releaseTrain/orchestrator.ts +++ b/src/core/releaseTrain/orchestrator.ts @@ -10,6 +10,7 @@ import type { TrainState, } from '../types/releaseTrain.js'; import { isCarTerminal, isNativeCar, isOtaCar } from './guards.js'; +import type { MutableDeep } from '../types/mutable.js'; /** Store and OTA operations driven by the release-train state machine. */ export type TrainEngine = Readonly<{ @@ -100,7 +101,7 @@ export const startTrain = ( Effect.gen(function* () { const trainCars: Car[] = []; for (const platform of trainInput.platforms) { - const nativeCar: NativeCar = { + const nativeCar: MutableDeep = { kind: platform, state: 'building', updatedAt: trainInput.now, @@ -148,7 +149,7 @@ export const advanceTrain = ( if (trainRecord.state === 'done') return trainRecord; if (trainRecord.state === 'aborted') return trainRecord; const forced = advanceOptions.force === true; - const trainCars = trainRecord.cars.map((trainCar): Car => ({ ...trainCar })); + const trainCars = trainRecord.cars.map((trainCar): MutableDeep => ({ ...trainCar })); for (const trainCar of trainCars) { if (!isNativeCar(trainCar)) continue; @@ -160,7 +161,11 @@ export const advanceTrain = ( if (!isNativeFailure(trainCar)) delete trainCar.error; } - const nativeCars = trainCars.filter(isNativeCar); + const nativeCars: MutableDeep[] = []; + for (const trainCar of trainCars) { + if (!isNativeCar(trainCar)) continue; + nativeCars.push(trainCar); + } const hasNativeFailure = nativeCars.some(isNativeFailure); const allApproved = nativeCars.every(isNativeApprovedOrReleased); const gateOpen = isReleaseGateOpen(trainRecord.hold, forced, hasNativeFailure, allApproved); diff --git a/src/core/releaseTrain/record.ts b/src/core/releaseTrain/record.ts index 4cd099a..80d2f41 100644 --- a/src/core/releaseTrain/record.ts +++ b/src/core/releaseTrain/record.ts @@ -8,47 +8,41 @@ import { } from '../services/paths.js'; import type { TrainRecord } from '../types/releaseTrain.js'; -const NativeCarSchema = Schema.mutable( - Schema.Struct({ - kind: Schema.Literal('ios', 'android'), - state: Schema.Literal( - 'building', - 'submitted', - 'in-review', - 'approved', - 'released', - 'rejected', - 'failed', - ), - buildId: Schema.optionalWith(Schema.String, { exact: true }), - error: Schema.optionalWith(Schema.String, { exact: true }), - updatedAt: Schema.String, - }), -); - -const OtaCarSchema = Schema.mutable( - Schema.Struct({ - kind: Schema.Literal('ota'), - platform: Schema.Literal('ios', 'android'), - channel: Schema.String, - runtimeVersion: Schema.String, - state: Schema.Literal('pending', 'published'), - manifestId: Schema.optionalWith(Schema.String, { exact: true }), - updatedAt: Schema.String, - }), -); - -const TrainRecordSchema: Schema.Schema = Schema.mutable( - Schema.Struct({ - id: Schema.String, - app: Schema.String, - hold: Schema.Boolean, - state: Schema.Literal('running', 'blocked', 'done', 'aborted'), - createdAt: Schema.String, - updatedAt: Schema.String, - cars: Schema.mutable(Schema.Array(Schema.Union(NativeCarSchema, OtaCarSchema))), - }), -); +const NativeCarSchema = Schema.Struct({ + kind: Schema.Literal('ios', 'android'), + state: Schema.Literal( + 'building', + 'submitted', + 'in-review', + 'approved', + 'released', + 'rejected', + 'failed', + ), + buildId: Schema.optionalWith(Schema.String, { exact: true }), + error: Schema.optionalWith(Schema.String, { exact: true }), + updatedAt: Schema.String, +}); + +const OtaCarSchema = Schema.Struct({ + kind: Schema.Literal('ota'), + platform: Schema.Literal('ios', 'android'), + channel: Schema.String, + runtimeVersion: Schema.String, + state: Schema.Literal('pending', 'published'), + manifestId: Schema.optionalWith(Schema.String, { exact: true }), + updatedAt: Schema.String, +}); + +const TrainRecordSchema: Schema.Schema = Schema.Struct({ + id: Schema.String, + app: Schema.String, + hold: Schema.Boolean, + state: Schema.Literal('running', 'blocked', 'done', 'aborted'), + createdAt: Schema.String, + updatedAt: Schema.String, + cars: Schema.Array(Schema.Union(NativeCarSchema, OtaCarSchema)), +}); /** A persisted release-train record could not be read or written. */ export type TrainRecordFailure = Readonly<{