| ... | @@ -18,7 +18,6 @@ import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts"; | ... | @@ -18,7 +18,6 @@ import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts"; |
| 18 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 18 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 19 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 19 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 20 | import * as Config from "effect/Config"; | 20 | import * as Config from "effect/Config"; |
| 21 | import * as Data from "effect/Data"; | | |
| 22 | import * as Effect from "effect/Effect"; | 21 | import * as Effect from "effect/Effect"; |
| 23 | import * as FileSystem from "effect/FileSystem"; | 22 | import * as FileSystem from "effect/FileSystem"; |
| 24 | import * as Layer from "effect/Layer"; | 23 | import * as Layer from "effect/Layer"; |
| ... | @@ -126,19 +125,238 @@ const getDefaultArch = Effect.fn("getDefaultArch")(function* (platform: typeof B | ... | @@ -126,19 +125,238 @@ const getDefaultArch = Effect.fn("getDefaultArch")(function* (platform: typeof B |
| 126 | return yield* getDefaultBuildArch(platform, config); | 125 | return yield* getDefaultBuildArch(platform, config); |
| 127 | }); | 126 | }); |
| 128 | | 127 | |
| 129 | export class BuildScriptError extends Data.TaggedError("BuildScriptError")<{ | 128 | export class MacPasskeySigningConfigurationResolutionError extends Schema.TaggedErrorClass<MacPasskeySigningConfigurationResolutionError>()( |
| 130 | readonly message: string; | 129 | "MacPasskeySigningConfigurationResolutionError", |
| 131 | readonly cause?: unknown; | 130 | { |
| 132 | }> { | 131 | cause: Schema.Defect(), |
| 133 | static fromMacPasskeySigningConfiguration( | 132 | }, |
| | 133 | ) { |
| | 134 | static fromCause( |
| 134 | cause: unknown, | 135 | cause: unknown, |
| 135 | ): MacPasskeySigningConfigurationError | BuildScriptError { | 136 | ): MacPasskeySigningConfigurationError | MacPasskeySigningConfigurationResolutionError { |
| 136 | return isMacPasskeySigningConfigurationError(cause) | 137 | return isMacPasskeySigningConfigurationError(cause) |
| 137 | ? cause | 138 | ? cause |
| 138 | : new BuildScriptError({ | 139 | : new MacPasskeySigningConfigurationResolutionError({ cause }); |
| 139 | message: "Failed to resolve macOS passkey signing configuration.", | 140 | } |
| 140 | cause, | 141 | |
| 141 | }); | 142 | override get message(): string { |
| | 143 | return "Failed to resolve macOS passkey signing configuration."; |
| | 144 | } |
| | 145 | } |
| | 146 | |
| | 147 | export class ClerkPasskeyNativePackageMissingError extends Schema.TaggedErrorClass<ClerkPasskeyNativePackageMissingError>()( |
| | 148 | "ClerkPasskeyNativePackageMissingError", |
| | 149 | { |
| | 150 | packageName: Schema.String, |
| | 151 | binaryFileName: Schema.String, |
| | 152 | packageEntryPath: Schema.String, |
| | 153 | platform: BuildPlatform, |
| | 154 | arch: BuildArch, |
| | 155 | cause: Schema.Defect(), |
| | 156 | }, |
| | 157 | ) { |
| | 158 | override get message(): string { |
| | 159 | return `Clerk passkey native package is missing: ${this.packageName}`; |
| | 160 | } |
| | 161 | } |
| | 162 | |
| | 163 | export class UnsupportedHostBuildPlatformError extends Schema.TaggedErrorClass<UnsupportedHostBuildPlatformError>()( |
| | 164 | "UnsupportedHostBuildPlatformError", |
| | 165 | { |
| | 166 | hostPlatform: Schema.String, |
| | 167 | }, |
| | 168 | ) { |
| | 169 | override get message(): string { |
| | 170 | return `Unsupported host platform '${this.hostPlatform}'.`; |
| | 171 | } |
| | 172 | } |
| | 173 | |
| | 174 | const InvalidMockUpdateServerPortReason = Schema.Literals([ |
| | 175 | "not-numeric", |
| | 176 | "not-integer", |
| | 177 | "out-of-range", |
| | 178 | ]); |
| | 179 | |
| | 180 | export class InvalidMockUpdateServerPortError extends Schema.TaggedErrorClass<InvalidMockUpdateServerPortError>()( |
| | 181 | "InvalidMockUpdateServerPortError", |
| | 182 | { |
| | 183 | reason: InvalidMockUpdateServerPortReason, |
| | 184 | inputLength: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)), |
| | 185 | cause: Schema.Defect(), |
| | 186 | }, |
| | 187 | ) { |
| | 188 | override get message(): string { |
| | 189 | return "Invalid mock update server port."; |
| | 190 | } |
| | 191 | |
| | 192 | static fromConfigValue(configuredPort: string, cause: unknown) { |
| | 193 | return new InvalidMockUpdateServerPortError({ |
| | 194 | reason: invalidMockUpdateServerPortReason(configuredPort), |
| | 195 | inputLength: configuredPort.length, |
| | 196 | cause, |
| | 197 | }); |
| | 198 | } |
| | 199 | } |
| | 200 | |
| | 201 | export class BuildCommandFailedError extends Schema.TaggedErrorClass<BuildCommandFailedError>()( |
| | 202 | "BuildCommandFailedError", |
| | 203 | { |
| | 204 | command: Schema.String, |
| | 205 | exitCode: Schema.Int, |
| | 206 | stdoutTail: Schema.optionalKey(Schema.String), |
| | 207 | stderrTail: Schema.optionalKey(Schema.String), |
| | 208 | }, |
| | 209 | ) { |
| | 210 | override get message(): string { |
| | 211 | const outputSections = [ |
| | 212 | `Command: ${this.command}`, |
| | 213 | formatOutputSection("stdout", this.stdoutTail ?? ""), |
| | 214 | formatOutputSection("stderr", this.stderrTail ?? ""), |
| | 215 | ].filter((section): section is string => section !== undefined); |
| | 216 | const outputSuffix = outputSections.length > 0 ? `\n\n${outputSections.join("\n\n")}` : ""; |
| | 217 | return `Command exited with non-zero exit code (${this.exitCode})${outputSuffix}`; |
| | 218 | } |
| | 219 | } |
| | 220 | |
| | 221 | const desktopIconPlatformNames = { |
| | 222 | mac: "macOS", |
| | 223 | linux: "Linux", |
| | 224 | win: "Windows", |
| | 225 | } satisfies Record<typeof BuildPlatform.Type, string>; |
| | 226 | |
| | 227 | export class DesktopIconSourceMissingError extends Schema.TaggedErrorClass<DesktopIconSourceMissingError>()( |
| | 228 | "DesktopIconSourceMissingError", |
| | 229 | { |
| | 230 | platform: BuildPlatform, |
| | 231 | sourcePath: Schema.String, |
| | 232 | }, |
| | 233 | ) { |
| | 234 | override get message(): string { |
| | 235 | return `Desktop ${desktopIconPlatformNames[this.platform]} icon source is missing at ${this.sourcePath}`; |
| | 236 | } |
| | 237 | } |
| | 238 | |
| | 239 | export class BundledClientAssetsMissingError extends Schema.TaggedErrorClass<BundledClientAssetsMissingError>()( |
| | 240 | "BundledClientAssetsMissingError", |
| | 241 | { |
| | 242 | indexPath: Schema.String, |
| | 243 | missingFiles: Schema.Array(Schema.String), |
| | 244 | }, |
| | 245 | ) { |
| | 246 | override get message(): string { |
| | 247 | const preview = this.missingFiles.slice(0, 6).join(", "); |
| | 248 | const suffix = this.missingFiles.length > 6 ? ` (+${this.missingFiles.length - 6} more)` : ""; |
| | 249 | return `Bundled client references missing files in ${this.indexPath}: ${preview}${suffix}. Rebuild web/server artifacts.`; |
| | 250 | } |
| | 251 | } |
| | 252 | |
| | 253 | export class UnsupportedDesktopBuildPlatformError extends Schema.TaggedErrorClass<UnsupportedDesktopBuildPlatformError>()( |
| | 254 | "UnsupportedDesktopBuildPlatformError", |
| | 255 | { |
| | 256 | platform: Schema.String, |
| | 257 | }, |
| | 258 | ) { |
| | 259 | override get message(): string { |
| | 260 | return `Unsupported platform '${this.platform}'.`; |
| | 261 | } |
| | 262 | } |
| | 263 | |
| | 264 | const dependencyResolutionDescriptions = { |
| | 265 | "server-production": "production dependencies", |
| | 266 | "workspace-overrides": "overrides", |
| | 267 | "desktop-runtime": "desktop runtime dependencies", |
| | 268 | } as const; |
| | 269 | const DependencyResolutionKind = Schema.Literals([ |
| | 270 | "server-production", |
| | 271 | "workspace-overrides", |
| | 272 | "desktop-runtime", |
| | 273 | ]); |
| | 274 | |
| | 275 | export class DesktopBuildDependencyResolutionError extends Schema.TaggedErrorClass<DesktopBuildDependencyResolutionError>()( |
| | 276 | "DesktopBuildDependencyResolutionError", |
| | 277 | { |
| | 278 | kind: DependencyResolutionKind, |
| | 279 | manifestPath: Schema.String, |
| | 280 | cause: Schema.Defect(), |
| | 281 | }, |
| | 282 | ) { |
| | 283 | override get message(): string { |
| | 284 | return `Could not resolve ${dependencyResolutionDescriptions[this.kind]} from ${this.manifestPath}.`; |
| | 285 | } |
| | 286 | } |
| | 287 | |
| | 288 | export class MissingServerProductionDependenciesError extends Schema.TaggedErrorClass<MissingServerProductionDependenciesError>()( |
| | 289 | "MissingServerProductionDependenciesError", |
| | 290 | { |
| | 291 | manifestPath: Schema.String, |
| | 292 | }, |
| | 293 | ) { |
| | 294 | override get message(): string { |
| | 295 | return `Could not resolve production dependencies from ${this.manifestPath}.`; |
| | 296 | } |
| | 297 | } |
| | 298 | |
| | 299 | const DesktopBuildInputArtifact = Schema.Literals([ |
| | 300 | "desktop-dist", |
| | 301 | "desktop-resources", |
| | 302 | "server-dist", |
| | 303 | "bundled-server-client", |
| | 304 | ]); |
| | 305 | type DesktopBuildInputArtifact = typeof DesktopBuildInputArtifact.Type; |
| | 306 | const desktopBuildInputArtifactNames = { |
| | 307 | "desktop-dist": "desktopDist", |
| | 308 | "desktop-resources": "desktopResources", |
| | 309 | "server-dist": "serverDist", |
| | 310 | "bundled-server-client": "bundled server client", |
| | 311 | } satisfies Record<DesktopBuildInputArtifact, string>; |
| | 312 | |
| | 313 | export class MissingDesktopBuildInputError extends Schema.TaggedErrorClass<MissingDesktopBuildInputError>()( |
| | 314 | "MissingDesktopBuildInputError", |
| | 315 | { |
| | 316 | artifact: DesktopBuildInputArtifact, |
| | 317 | artifactPath: Schema.String, |
| | 318 | buildCommand: Schema.Literal("vp run build:desktop"), |
| | 319 | }, |
| | 320 | ) { |
| | 321 | override get message(): string { |
| | 322 | return `Missing ${desktopBuildInputArtifactNames[this.artifact]} at ${this.artifactPath}. Run '${this.buildCommand}' first.`; |
| | 323 | } |
| | 324 | } |
| | 325 | |
| | 326 | export class MacProvisioningProfileNotFoundError extends Schema.TaggedErrorClass<MacProvisioningProfileNotFoundError>()( |
| | 327 | "MacProvisioningProfileNotFoundError", |
| | 328 | { |
| | 329 | provisioningProfilePath: Schema.String, |
| | 330 | }, |
| | 331 | ) { |
| | 332 | override get message(): string { |
| | 333 | return `macOS provisioning profile not found: ${this.provisioningProfilePath}`; |
| | 334 | } |
| | 335 | } |
| | 336 | |
| | 337 | export class DesktopBuildDistDirectoryMissingError extends Schema.TaggedErrorClass<DesktopBuildDistDirectoryMissingError>()( |
| | 338 | "DesktopBuildDistDirectoryMissingError", |
| | 339 | { |
| | 340 | distPath: Schema.String, |
| | 341 | platform: BuildPlatform, |
| | 342 | arch: BuildArch, |
| | 343 | }, |
| | 344 | ) { |
| | 345 | override get message(): string { |
| | 346 | return `Build completed but dist directory was not found at ${this.distPath}`; |
| | 347 | } |
| | 348 | } |
| | 349 | |
| | 350 | export class DesktopBuildNoArtifactsProducedError extends Schema.TaggedErrorClass<DesktopBuildNoArtifactsProducedError>()( |
| | 351 | "DesktopBuildNoArtifactsProducedError", |
| | 352 | { |
| | 353 | distPath: Schema.String, |
| | 354 | platform: BuildPlatform, |
| | 355 | arch: BuildArch, |
| | 356 | }, |
| | 357 | ) { |
| | 358 | override get message(): string { |
| | 359 | return `Build completed but no files were produced in ${this.distPath}`; |
| 142 | } | 360 | } |
| 143 | } | 361 | } |
| 144 | | 362 | |
| ... | @@ -616,8 +834,12 @@ const stageClerkPasskeyNativeBinaries = Effect.fn("stageClerkPasskeyNativeBinari | ... | @@ -616,8 +834,12 @@ const stageClerkPasskeyNativeBinaries = Effect.fn("stageClerkPasskeyNativeBinari |
| 616 | const sourcePath = yield* Effect.try({ | 834 | const sourcePath = yield* Effect.try({ |
| 617 | try: () => packageRequire.resolve(artifact.packageName), | 835 | try: () => packageRequire.resolve(artifact.packageName), |
| 618 | catch: (cause) => | 836 | catch: (cause) => |
| 619 | new BuildScriptError({ | 837 | new ClerkPasskeyNativePackageMissingError({ |
| 620 | message: `Clerk passkey native package is missing: ${artifact.packageName}`, | 838 | packageName: artifact.packageName, |
| | 839 | binaryFileName: artifact.binaryFileName, |
| | 840 | packageEntryPath, |
| | 841 | platform, |
| | 842 | arch, |
| 621 | cause, | 843 | cause, |
| 622 | }), | 844 | }), |
| 623 | }); | 845 | }); |
| ... | @@ -691,6 +913,18 @@ const MockUpdateServerPortSchema = Schema.NumberFromString.check( | ... | @@ -691,6 +913,18 @@ const MockUpdateServerPortSchema = Schema.NumberFromString.check( |
| 691 | ); | 913 | ); |
| 692 | const decodeMockUpdateServerPort = Schema.decodeUnknownEffect(MockUpdateServerPortSchema); | 914 | const decodeMockUpdateServerPort = Schema.decodeUnknownEffect(MockUpdateServerPortSchema); |
| 693 | | 915 | |
| | 916 | function invalidMockUpdateServerPortReason( |
| | 917 | configuredPort: string, |
| | 918 | ): typeof InvalidMockUpdateServerPortReason.Type { |
| | 919 | const parsed = Number(configuredPort); |
| | 920 | if (!Number.isFinite(parsed)) return "not-numeric"; |
| | 921 | if (!Number.isInteger(parsed)) return "not-integer"; |
| | 922 | if (parsed < 1 || parsed > 65535) return "out-of-range"; |
| | 923 | // This mapper is only called after schema decoding failed. An otherwise |
| | 924 | // valid integer therefore used a representation the decoder did not accept. |
| | 925 | return "not-numeric"; |
| | 926 | } |
| | 927 | |
| 694 | const resolveBooleanFlag = (flag: Option.Option<boolean>, envValue: boolean) => | 928 | const resolveBooleanFlag = (flag: Option.Option<boolean>, envValue: boolean) => |
| 695 | Option.getOrElse(flag, () => envValue); | 929 | Option.getOrElse(flag, () => envValue); |
| 696 | const mergeOptions = <A>(a: Option.Option<A>, b: Option.Option<A>, defaultValue: A) => | 930 | const mergeOptions = <A>(a: Option.Option<A>, b: Option.Option<A>, defaultValue: A) => |
| ... | @@ -722,9 +956,7 @@ export const resolveBuildOptions = Effect.fn("resolveBuildOptions")(function* ( | ... | @@ -722,9 +956,7 @@ export const resolveBuildOptions = Effect.fn("resolveBuildOptions")(function* ( |
| 722 | ); | 956 | ); |
| 723 | | 957 | |
| 724 | if (!platform) { | 958 | if (!platform) { |
| 725 | return yield* new BuildScriptError({ | 959 | return yield* new UnsupportedHostBuildPlatformError({ hostPlatform }); |
| 726 | message: `Unsupported host platform '${hostPlatform}'.`, | | |
| 727 | }); | | |
| 728 | } | 960 | } |
| 729 | | 961 | |
| 730 | const target = mergeOptions(input.target, env.target, PLATFORM_CONFIG[platform].defaultTarget); | 962 | const target = mergeOptions(input.target, env.target, PLATFORM_CONFIG[platform].defaultTarget); |
| ... | @@ -745,17 +977,16 @@ export const resolveBuildOptions = Effect.fn("resolveBuildOptions")(function* ( | ... | @@ -745,17 +977,16 @@ export const resolveBuildOptions = Effect.fn("resolveBuildOptions")(function* ( |
| 745 | const verbose = resolveBooleanFlag(input.verbose, env.verbose); | 977 | const verbose = resolveBooleanFlag(input.verbose, env.verbose); |
| 746 | | 978 | |
| 747 | const mockUpdates = resolveBooleanFlag(input.mockUpdates, env.mockUpdates); | 979 | const mockUpdates = resolveBooleanFlag(input.mockUpdates, env.mockUpdates); |
| | 980 | const configuredMockUpdateServerPort = Option.getOrUndefined(env.mockUpdateServerPort); |
| 748 | const mockUpdateServerPort = | 981 | const mockUpdateServerPort = |
| 749 | Option.getOrUndefined(input.mockUpdateServerPort) ?? | 982 | Option.getOrUndefined(input.mockUpdateServerPort) ?? |
| 750 | (yield* resolveMockUpdateServerPort(Option.getOrUndefined(env.mockUpdateServerPort)).pipe( | 983 | (configuredMockUpdateServerPort === undefined |
| 751 | Effect.mapError( | 984 | ? undefined |
| 752 | (cause) => | 985 | : yield* resolveMockUpdateServerPort(configuredMockUpdateServerPort).pipe( |
| 753 | new BuildScriptError({ | 986 | Effect.mapError((cause) => |
| 754 | message: "Invalid mock update server port.", | 987 | InvalidMockUpdateServerPortError.fromConfigValue(configuredMockUpdateServerPort, cause), |
| 755 | cause, | 988 | ), |
| 756 | }), | 989 | )); |
| 757 | ), | | |
| 758 | )); | | |
| 759 | | 990 | |
| 760 | return { | 991 | return { |
| 761 | platform, | 992 | platform, |
| ... | @@ -775,7 +1006,7 @@ export const resolveBuildOptions = Effect.fn("resolveBuildOptions")(function* ( | ... | @@ -775,7 +1006,7 @@ export const resolveBuildOptions = Effect.fn("resolveBuildOptions")(function* ( |
| 775 | const runCommand = Effect.fn("runCommand")(function* ( | 1006 | const runCommand = Effect.fn("runCommand")(function* ( |
| 776 | command: ChildProcess.Command, | 1007 | command: ChildProcess.Command, |
| 777 | options: { | 1008 | options: { |
| 778 | readonly label?: string; | 1009 | readonly label: string; |
| 779 | readonly verbose: boolean; | 1010 | readonly verbose: boolean; |
| 780 | }, | 1011 | }, |
| 781 | ) { | 1012 | ) { |
| ... | @@ -791,14 +1022,11 @@ const runCommand = Effect.fn("runCommand")(function* ( | ... | @@ -791,14 +1022,11 @@ const runCommand = Effect.fn("runCommand")(function* ( |
| 791 | ); | 1022 | ); |
| 792 | | 1023 | |
| 793 | if (exitCode !== 0) { | 1024 | if (exitCode !== 0) { |
| 794 | const outputSections = [ | 1025 | return yield* new BuildCommandFailedError({ |
| 795 | options.label ? `Command: ${options.label}` : undefined, | 1026 | command: options.label, |
| 796 | formatOutputSection("stdout", stdout), | 1027 | exitCode, |
| 797 | formatOutputSection("stderr", stderr), | 1028 | ...(stdout.trim() ? { stdoutTail: stdout } : {}), |
| 798 | ].filter((section): section is string => section !== undefined); | 1029 | ...(stderr.trim() ? { stderrTail: stderr } : {}), |
| 799 | const outputSuffix = outputSections.length > 0 ? `\n\n${outputSections.join("\n\n")}` : ""; | | |
| 800 | return yield* new BuildScriptError({ | | |
| 801 | message: `Command exited with non-zero exit code (${exitCode})${outputSuffix}`, | | |
| 802 | }); | 1030 | }); |
| 803 | } | 1031 | } |
| 804 | }); | 1032 | }); |
| ... | @@ -845,8 +1073,9 @@ function stageMacIcons(stageResourcesDir: string, sourcePng: string, verbose: bo | ... | @@ -845,8 +1073,9 @@ function stageMacIcons(stageResourcesDir: string, sourcePng: string, verbose: bo |
| 845 | const fs = yield* FileSystem.FileSystem; | 1073 | const fs = yield* FileSystem.FileSystem; |
| 846 | const path = yield* Path.Path; | 1074 | const path = yield* Path.Path; |
| 847 | if (!(yield* fs.exists(sourcePng))) { | 1075 | if (!(yield* fs.exists(sourcePng))) { |
| 848 | return yield* new BuildScriptError({ | 1076 | return yield* new DesktopIconSourceMissingError({ |
| 849 | message: `Desktop macOS icon source is missing at ${sourcePng}`, | 1077 | platform: "mac", |
| | 1078 | sourcePath: sourcePng, |
| 850 | }); | 1079 | }); |
| 851 | } | 1080 | } |
| 852 | | 1081 | |
| ... | @@ -871,8 +1100,9 @@ function stageLinuxIcons(stageResourcesDir: string, sourcePng: string, verbose: | ... | @@ -871,8 +1100,9 @@ function stageLinuxIcons(stageResourcesDir: string, sourcePng: string, verbose: |
| 871 | const fs = yield* FileSystem.FileSystem; | 1100 | const fs = yield* FileSystem.FileSystem; |
| 872 | const path = yield* Path.Path; | 1101 | const path = yield* Path.Path; |
| 873 | if (!(yield* fs.exists(sourcePng))) { | 1102 | if (!(yield* fs.exists(sourcePng))) { |
| 874 | return yield* new BuildScriptError({ | 1103 | return yield* new DesktopIconSourceMissingError({ |
| 875 | message: `Desktop Linux icon source is missing at ${sourcePng}`, | 1104 | platform: "linux", |
| | 1105 | sourcePath: sourcePng, |
| 876 | }); | 1106 | }); |
| 877 | } | 1107 | } |
| 878 | | 1108 | |
| ... | @@ -931,8 +1161,9 @@ function stageWindowsIcons(stageResourcesDir: string, sourceIco: string) { | ... | @@ -931,8 +1161,9 @@ function stageWindowsIcons(stageResourcesDir: string, sourceIco: string) { |
| 931 | const fs = yield* FileSystem.FileSystem; | 1161 | const fs = yield* FileSystem.FileSystem; |
| 932 | const path = yield* Path.Path; | 1162 | const path = yield* Path.Path; |
| 933 | if (!(yield* fs.exists(sourceIco))) { | 1163 | if (!(yield* fs.exists(sourceIco))) { |
| 934 | return yield* new BuildScriptError({ | 1164 | return yield* new DesktopIconSourceMissingError({ |
| 935 | message: `Desktop Windows icon source is missing at ${sourceIco}`, | 1165 | platform: "win", |
| | 1166 | sourcePath: sourceIco, |
| 936 | }); | 1167 | }); |
| 937 | } | 1168 | } |
| 938 | | 1169 | |
| ... | @@ -969,10 +1200,9 @@ function validateBundledClientAssets(clientDir: string) { | ... | @@ -969,10 +1200,9 @@ function validateBundledClientAssets(clientDir: string) { |
| 969 | } | 1200 | } |
| 970 | | 1201 | |
| 971 | if (missing.length > 0) { | 1202 | if (missing.length > 0) { |
| 972 | const preview = missing.slice(0, 6).join(", "); | 1203 | return yield* new BundledClientAssetsMissingError({ |
| 973 | const suffix = missing.length > 6 ? ` (+${missing.length - 6} more)` : ""; | 1204 | indexPath, |
| 974 | return yield* new BuildScriptError({ | 1205 | missingFiles: missing, |
| 975 | message: `Bundled client references missing files in ${indexPath}: ${preview}${suffix}. Rebuild web/server artifacts.`, | | |
| 976 | }); | 1206 | }); |
| 977 | } | 1207 | } |
| 978 | }); | 1208 | }); |
| ... | @@ -1174,8 +1404,8 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( | ... | @@ -1174,8 +1404,8 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 1174 | | 1404 | |
| 1175 | const platformConfig = PLATFORM_CONFIG[options.platform]; | 1405 | const platformConfig = PLATFORM_CONFIG[options.platform]; |
| 1176 | if (!platformConfig) { | 1406 | if (!platformConfig) { |
| 1177 | return yield* new BuildScriptError({ | 1407 | return yield* new UnsupportedDesktopBuildPlatformError({ |
| 1178 | message: `Unsupported platform '${options.platform}'.`, | 1408 | platform: options.platform, |
| 1179 | }); | 1409 | }); |
| 1180 | } | 1410 | } |
| 1181 | | 1411 | |
| ... | @@ -1183,16 +1413,17 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( | ... | @@ -1183,16 +1413,17 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 1183 | | 1413 | |
| 1184 | const serverDependencies = serverPackageJson.dependencies; | 1414 | const serverDependencies = serverPackageJson.dependencies; |
| 1185 | if (!serverDependencies || Object.keys(serverDependencies).length === 0) { | 1415 | if (!serverDependencies || Object.keys(serverDependencies).length === 0) { |
| 1186 | return yield* new BuildScriptError({ | 1416 | return yield* new MissingServerProductionDependenciesError({ |
| 1187 | message: "Could not resolve production dependencies from apps/server/package.json.", | 1417 | manifestPath: "apps/server/package.json", |
| 1188 | }); | 1418 | }); |
| 1189 | } | 1419 | } |
| 1190 | | 1420 | |
| 1191 | const resolvedOverrides = yield* Effect.try({ | 1421 | const resolvedOverrides = yield* Effect.try({ |
| 1192 | try: () => resolveCatalogDependencies(workspaceOverrides, workspaceCatalog, "apps/desktop"), | 1422 | try: () => resolveCatalogDependencies(workspaceOverrides, workspaceCatalog, "apps/desktop"), |
| 1193 | catch: (cause) => | 1423 | catch: (cause) => |
| 1194 | new BuildScriptError({ | 1424 | new DesktopBuildDependencyResolutionError({ |
| 1195 | message: "Could not resolve overrides from pnpm-workspace.yaml.", | 1425 | kind: "workspace-overrides", |
| | 1426 | manifestPath: "pnpm-workspace.yaml", |
| 1196 | cause, | 1427 | cause, |
| 1197 | }), | 1428 | }), |
| 1198 | }); | 1429 | }); |
| ... | @@ -1200,16 +1431,18 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( | ... | @@ -1200,16 +1431,18 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 1200 | const resolvedServerDependencies = yield* Effect.try({ | 1431 | const resolvedServerDependencies = yield* Effect.try({ |
| 1201 | try: () => resolveCatalogDependencies(serverDependencies, workspaceCatalog, "apps/server"), | 1432 | try: () => resolveCatalogDependencies(serverDependencies, workspaceCatalog, "apps/server"), |
| 1202 | catch: (cause) => | 1433 | catch: (cause) => |
| 1203 | new BuildScriptError({ | 1434 | new DesktopBuildDependencyResolutionError({ |
| 1204 | message: "Could not resolve production dependencies from apps/server/package.json.", | 1435 | kind: "server-production", |
| | 1436 | manifestPath: "apps/server/package.json", |
| 1205 | cause, | 1437 | cause, |
| 1206 | }), | 1438 | }), |
| 1207 | }); | 1439 | }); |
| 1208 | const resolvedDesktopRuntimeDependencies = yield* Effect.try({ | 1440 | const resolvedDesktopRuntimeDependencies = yield* Effect.try({ |
| 1209 | try: () => resolveDesktopRuntimeDependencies(desktopPackageJson.dependencies, workspaceCatalog), | 1441 | try: () => resolveDesktopRuntimeDependencies(desktopPackageJson.dependencies, workspaceCatalog), |
| 1210 | catch: (cause) => | 1442 | catch: (cause) => |
| 1211 | new BuildScriptError({ | 1443 | new DesktopBuildDependencyResolutionError({ |
| 1212 | message: "Could not resolve desktop runtime dependencies from apps/desktop/package.json.", | 1444 | kind: "desktop-runtime", |
| | 1445 | manifestPath: "apps/desktop/package.json", |
| 1213 | cause, | 1446 | cause, |
| 1214 | }), | 1447 | }), |
| 1215 | }); | 1448 | }); |
| ... | @@ -1243,17 +1476,25 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( | ... | @@ -1243,17 +1476,25 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 1243 | ); | 1476 | ); |
| 1244 | } | 1477 | } |
| 1245 | | 1478 | |
| 1246 | for (const [label, dir] of Object.entries(distDirs)) { | 1479 | const requiredBuildInputs = [ |
| 1247 | if (!(yield* fs.exists(dir))) { | 1480 | { artifact: "desktop-dist", artifactPath: distDirs.desktopDist }, |
| 1248 | return yield* new BuildScriptError({ | 1481 | { artifact: "desktop-resources", artifactPath: distDirs.desktopResources }, |
| 1249 | message: `Missing ${label} at ${dir}. Run 'vp run build:desktop' first.`, | 1482 | { artifact: "server-dist", artifactPath: distDirs.serverDist }, |
| | 1483 | ] as const; |
| | 1484 | for (const input of requiredBuildInputs) { |
| | 1485 | if (!(yield* fs.exists(input.artifactPath))) { |
| | 1486 | return yield* new MissingDesktopBuildInputError({ |
| | 1487 | ...input, |
| | 1488 | buildCommand: "vp run build:desktop", |
| 1250 | }); | 1489 | }); |
| 1251 | } | 1490 | } |
| 1252 | } | 1491 | } |
| 1253 | | 1492 | |
| 1254 | if (!(yield* fs.exists(bundledClientEntry))) { | 1493 | if (!(yield* fs.exists(bundledClientEntry))) { |
| 1255 | return yield* new BuildScriptError({ | 1494 | return yield* new MissingDesktopBuildInputError({ |
| 1256 | message: `Missing bundled server client at ${bundledClientEntry}. Run 'vp run build:desktop' first.`, | 1495 | artifact: "bundled-server-client", |
| | 1496 | artifactPath: bundledClientEntry, |
| | 1497 | buildCommand: "vp run build:desktop", |
| 1257 | }); | 1498 | }); |
| 1258 | } | 1499 | } |
| 1259 | | 1500 | |
| ... | @@ -1285,7 +1526,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( | ... | @@ -1285,7 +1526,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 1285 | options.platform === "mac" && options.signed | 1526 | options.platform === "mac" && options.signed |
| 1286 | ? yield* Effect.try({ | 1527 | ? yield* Effect.try({ |
| 1287 | try: () => resolveMacPasskeySigningConfiguration(loadRepoEnv({ repoRoot })), | 1528 | try: () => resolveMacPasskeySigningConfiguration(loadRepoEnv({ repoRoot })), |
| 1288 | catch: BuildScriptError.fromMacPasskeySigningConfiguration, | 1529 | catch: MacPasskeySigningConfigurationResolutionError.fromCause, |
| 1289 | }) | 1530 | }) |
| 1290 | : undefined; | 1531 | : undefined; |
| 1291 | const macPasskeySigning = configuredMacPasskeySigning | 1532 | const macPasskeySigning = configuredMacPasskeySigning |
| ... | @@ -1302,8 +1543,8 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( | ... | @@ -1302,8 +1543,8 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 1302 | : undefined; | 1543 | : undefined; |
| 1303 | if (macPasskeySigning && macEntitlementsPath) { | 1544 | if (macPasskeySigning && macEntitlementsPath) { |
| 1304 | if (!(yield* fs.exists(macPasskeySigning.provisioningProfilePath))) { | 1545 | if (!(yield* fs.exists(macPasskeySigning.provisioningProfilePath))) { |
| 1305 | return yield* new BuildScriptError({ | 1546 | return yield* new MacProvisioningProfileNotFoundError({ |
| 1306 | message: `macOS provisioning profile not found: ${macPasskeySigning.provisioningProfilePath}`, | 1547 | provisioningProfilePath: macPasskeySigning.provisioningProfilePath, |
| 1307 | }); | 1548 | }); |
| 1308 | } | 1549 | } |
| 1309 | yield* fs.writeFileString(macEntitlementsPath, renderMacPasskeyEntitlements(macPasskeySigning)); | 1550 | yield* fs.writeFileString(macEntitlementsPath, renderMacPasskeyEntitlements(macPasskeySigning)); |
| ... | @@ -1442,8 +1683,10 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( | ... | @@ -1442,8 +1683,10 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 1442 | | 1683 | |
| 1443 | const stageDistDir = path.join(stageAppDir, "dist"); | 1684 | const stageDistDir = path.join(stageAppDir, "dist"); |
| 1444 | if (!(yield* fs.exists(stageDistDir))) { | 1685 | if (!(yield* fs.exists(stageDistDir))) { |
| 1445 | return yield* new BuildScriptError({ | 1686 | return yield* new DesktopBuildDistDirectoryMissingError({ |
| 1446 | message: `Build completed but dist directory was not found at ${stageDistDir}`, | 1687 | distPath: stageDistDir, |
| | 1688 | platform: options.platform, |
| | 1689 | arch: options.arch, |
| 1447 | }); | 1690 | }); |
| 1448 | } | 1691 | } |
| 1449 | | 1692 | |
| ... | @@ -1462,8 +1705,10 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( | ... | @@ -1462,8 +1705,10 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 1462 | } | 1705 | } |
| 1463 | | 1706 | |
| 1464 | if (copiedArtifacts.length === 0) { | 1707 | if (copiedArtifacts.length === 0) { |
| 1465 | return yield* new BuildScriptError({ | 1708 | return yield* new DesktopBuildNoArtifactsProducedError({ |
| 1466 | message: `Build completed but no files were produced in ${stageDistDir}`, | 1709 | distPath: stageDistDir, |
| | 1710 | platform: options.platform, |
| | 1711 | arch: options.arch, |
| 1467 | }); | 1712 | }); |
| 1468 | } | 1713 | } |
| 1469 | | 1714 | |