1#!/usr/bin/env node
2
3import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
4import * as NodeServices from "@effect/platform-node/NodeServices";
5import * as Effect from "effect/Effect";
6import * as FileSystem from "effect/FileSystem";
7import * as Option from "effect/Option";
8import * as Path from "effect/Path";
9import * as Schema from "effect/Schema";
10import { Argument, Command, Flag } from "effect/cli";
11
12import {
13 mergeUpdateManifests,
14 parseUpdateManifest,
15 serializeUpdateManifest,
16 type UpdateManifest,
17} from "./lib/update-manifest.ts";
18
19const UpdateManifestPlatform = Schema.Literals(["mac", "win"]);
20export type UpdateManifestPlatform = typeof UpdateManifestPlatform.Type;
21
22function getPlatformLabel(platform: UpdateManifestPlatform): string {
23 return platform === "mac" ? "macOS" : "Windows";
24}
25
26export function parsePlatformUpdateManifest(
27 platform: UpdateManifestPlatform,
28 raw: string,
29 sourcePath: string,
30): UpdateManifest {
31 return parseUpdateManifest(raw, sourcePath, getPlatformLabel(platform));
32}
33
34export function mergePlatformUpdateManifests(
35 platform: UpdateManifestPlatform,
36 primary: UpdateManifest,
37 secondary: UpdateManifest,
38): UpdateManifest {
39 return mergeUpdateManifests(primary, secondary, getPlatformLabel(platform));
40}
41
42export function serializePlatformUpdateManifest(
43 platform: UpdateManifestPlatform,
44 manifest: UpdateManifest,
45): string {
46 return serializeUpdateManifest(manifest, {
47 platformLabel: getPlatformLabel(platform),
48 });
49}
50
51export const mergeUpdateManifestFiles = Effect.fn("mergeUpdateManifestFiles")(function* (
52 platform: UpdateManifestPlatform,
53 primaryPathArg: string,
54 secondaryPathArg: string,
55 outputPathArg: string | undefined,
56) {
57 const fs = yield* FileSystem.FileSystem;
58 const path = yield* Path.Path;
59
60 const primaryPath = path.resolve(primaryPathArg);
61 const secondaryPath = path.resolve(secondaryPathArg);
62 const outputPath = path.resolve(outputPathArg ?? primaryPathArg);
63
64 const primaryManifest = parsePlatformUpdateManifest(
65 platform,
66 yield* fs.readFileString(primaryPath),
67 primaryPath,
68 );
69 const secondaryManifest = parsePlatformUpdateManifest(
70 platform,
71 yield* fs.readFileString(secondaryPath),
72 secondaryPath,
73 );
74 const merged = mergePlatformUpdateManifests(platform, primaryManifest, secondaryManifest);
75
76 yield* fs.writeFileString(outputPath, serializePlatformUpdateManifest(platform, merged));
77});
78
79export const mergeUpdateManifestsCommand = Command.make(
80 "merge-update-manifests",
81 {
82 platform: Flag.Literals("platform", UpdateManifestPlatform.literals).pipe(
83 Flag.withDescription("Update manifest platform."),
84 ),
85 primaryPath: Argument.String("primary-path").pipe(
86 Argument.withDescription("Primary update manifest path. Defaults to the output path."),
87 ),
88 secondaryPath: Argument.String("secondary-path").pipe(
89 Argument.withDescription(
90 "Secondary update manifest path to merge into the primary manifest.",
91 ),
92 ),
93 outputPath: Argument.String("output-path").pipe(
94 Argument.withDescription("Optional output path for the merged manifest."),
95 Argument.optional,
96 ),
97 },
98 ({ platform, primaryPath, secondaryPath, outputPath }) =>
99 mergeUpdateManifestFiles(
100 platform,
101 primaryPath,
102 secondaryPath,
103 Option.getOrUndefined(outputPath),
104 ),
105).pipe(Command.withDescription("Merge two Electron updater manifests into a multi-arch manifest."));
106
107if (import.meta.main) {
108 Command.run(mergeUpdateManifestsCommand, { version: "0.0.0" }).pipe(
109 Effect.provide(NodeServices.layer),
110 NodeRuntime.runMain,
111 );
112}