@pablozaiden/installer 0.0.0-development
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +330 -0
- package/index.ts +3 -0
- package/install.sh +396 -0
- package/package.json +37 -0
- package/src/contract.ts +170 -0
- package/src/manifest.ts +214 -0
- package/src/update.ts +502 -0
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_CHECKSUM_EXTENSION,
|
|
3
|
+
DEFAULT_INSTALL_DIR,
|
|
4
|
+
DEFAULT_INSTALLER_MANIFEST_PATHS,
|
|
5
|
+
SUPPORTED_RELEASE_TARGETS,
|
|
6
|
+
assertGitHubRepository,
|
|
7
|
+
type GitHubRepository,
|
|
8
|
+
type ReleaseTarget,
|
|
9
|
+
} from "./contract";
|
|
10
|
+
|
|
11
|
+
export type InstallerManifestBinary = {
|
|
12
|
+
name: string;
|
|
13
|
+
assetPrefix?: string;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
postInstallMessage?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type InstallerChecksumPolicy = {
|
|
19
|
+
required: boolean;
|
|
20
|
+
extension?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type InstallerManifest = {
|
|
24
|
+
schemaVersion: 1;
|
|
25
|
+
repo?: GitHubRepository;
|
|
26
|
+
installDir?: string;
|
|
27
|
+
binaries: InstallerManifestBinary[];
|
|
28
|
+
checksums?: InstallerChecksumPolicy;
|
|
29
|
+
platforms?: Partial<Record<"linux" | "darwin", Array<"x64" | "arm64">>>;
|
|
30
|
+
postInstallMessage?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type NormalizedInstallerBinary = {
|
|
34
|
+
name: string;
|
|
35
|
+
assetPrefix: string;
|
|
36
|
+
required: boolean;
|
|
37
|
+
postInstallMessage?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type NormalizedInstallerManifest = {
|
|
41
|
+
schemaVersion: 1;
|
|
42
|
+
repo: GitHubRepository;
|
|
43
|
+
installDir: string;
|
|
44
|
+
binaries: NormalizedInstallerBinary[];
|
|
45
|
+
checksums: Required<InstallerChecksumPolicy>;
|
|
46
|
+
targets: ReleaseTarget[];
|
|
47
|
+
postInstallMessage?: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const DEFAULT_MANIFEST_PATHS = DEFAULT_INSTALLER_MANIFEST_PATHS;
|
|
51
|
+
|
|
52
|
+
function expectObject(value: unknown, label: string): Record<string, unknown> {
|
|
53
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
54
|
+
throw new Error(`${label} must be an object.`);
|
|
55
|
+
}
|
|
56
|
+
return value as Record<string, unknown>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function expectString(value: unknown, label: string): string {
|
|
60
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
61
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function optionalString(value: unknown, label: string): string | undefined {
|
|
67
|
+
if (value === undefined) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
return expectString(value, label);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function expectBoolean(value: unknown, label: string): boolean {
|
|
74
|
+
if (typeof value !== "boolean") {
|
|
75
|
+
throw new Error(`${label} must be a boolean.`);
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function optionalBoolean(value: unknown, label: string): boolean | undefined {
|
|
81
|
+
if (value === undefined) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return expectBoolean(value, label);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseBinary(value: unknown, index: number): InstallerManifestBinary {
|
|
88
|
+
const raw = expectObject(value, `binaries[${index}]`);
|
|
89
|
+
return {
|
|
90
|
+
name: expectString(raw["name"], `binaries[${index}].name`),
|
|
91
|
+
assetPrefix: optionalString(raw["assetPrefix"], `binaries[${index}].assetPrefix`),
|
|
92
|
+
required: optionalBoolean(raw["required"], `binaries[${index}].required`),
|
|
93
|
+
postInstallMessage: optionalString(raw["postInstallMessage"], `binaries[${index}].postInstallMessage`),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseBinaries(value: unknown): InstallerManifestBinary[] {
|
|
98
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
99
|
+
throw new Error("binaries must be a non-empty array.");
|
|
100
|
+
}
|
|
101
|
+
return value.map(parseBinary);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseChecksums(value: unknown): InstallerChecksumPolicy | undefined {
|
|
105
|
+
if (value === undefined) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const raw = expectObject(value, "checksums");
|
|
109
|
+
return {
|
|
110
|
+
required: expectBoolean(raw["required"], "checksums.required"),
|
|
111
|
+
extension: optionalString(raw["extension"], "checksums.extension"),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parsePlatforms(value: unknown): InstallerManifest["platforms"] {
|
|
116
|
+
if (value === undefined) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
const raw = expectObject(value, "platforms");
|
|
120
|
+
const platforms: InstallerManifest["platforms"] = {};
|
|
121
|
+
for (const os of ["linux", "darwin"] as const) {
|
|
122
|
+
const arches = raw[os];
|
|
123
|
+
if (arches === undefined) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (!Array.isArray(arches)) {
|
|
127
|
+
throw new Error(`platforms.${os} must be an array.`);
|
|
128
|
+
}
|
|
129
|
+
platforms[os] = arches.map((arch, index) => {
|
|
130
|
+
if (arch !== "x64" && arch !== "arm64") {
|
|
131
|
+
throw new Error(`platforms.${os}[${index}] must be "x64" or "arm64".`);
|
|
132
|
+
}
|
|
133
|
+
return arch;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return platforms;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function parseInstallerManifest(value: unknown): InstallerManifest {
|
|
140
|
+
const raw = expectObject(value, "manifest");
|
|
141
|
+
if (raw["schemaVersion"] !== 1) {
|
|
142
|
+
throw new Error("manifest.schemaVersion must be 1.");
|
|
143
|
+
}
|
|
144
|
+
const repo = raw["repo"] === undefined ? undefined : assertGitHubRepository(expectString(raw["repo"], "repo"));
|
|
145
|
+
return {
|
|
146
|
+
schemaVersion: 1,
|
|
147
|
+
repo,
|
|
148
|
+
installDir: optionalString(raw["installDir"], "installDir"),
|
|
149
|
+
binaries: parseBinaries(raw["binaries"]),
|
|
150
|
+
checksums: parseChecksums(raw["checksums"]),
|
|
151
|
+
platforms: parsePlatforms(raw["platforms"]),
|
|
152
|
+
postInstallMessage: optionalString(raw["postInstallMessage"], "postInstallMessage"),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function parseInstallerManifestJson(json: string): InstallerManifest {
|
|
157
|
+
let parsed: unknown;
|
|
158
|
+
try {
|
|
159
|
+
parsed = JSON.parse(json) as unknown;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
throw new Error(`Invalid installer manifest JSON: ${String(error)}`);
|
|
162
|
+
}
|
|
163
|
+
return parseInstallerManifest(parsed);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizeTargets(platforms: InstallerManifest["platforms"]): ReleaseTarget[] {
|
|
167
|
+
if (!platforms) {
|
|
168
|
+
return [...SUPPORTED_RELEASE_TARGETS];
|
|
169
|
+
}
|
|
170
|
+
const targets: ReleaseTarget[] = [];
|
|
171
|
+
for (const os of ["linux", "darwin"] as const) {
|
|
172
|
+
for (const arch of platforms[os] ?? []) {
|
|
173
|
+
targets.push(`${os}-${arch}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (targets.length === 0) {
|
|
177
|
+
throw new Error("platforms must include at least one target.");
|
|
178
|
+
}
|
|
179
|
+
return targets;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function normalizeInstallerManifest(
|
|
183
|
+
manifest: InstallerManifest,
|
|
184
|
+
fallbackRepository: GitHubRepository,
|
|
185
|
+
): NormalizedInstallerManifest {
|
|
186
|
+
const repo = manifest.repo ?? fallbackRepository;
|
|
187
|
+
return {
|
|
188
|
+
schemaVersion: 1,
|
|
189
|
+
repo,
|
|
190
|
+
installDir: manifest.installDir ?? DEFAULT_INSTALL_DIR,
|
|
191
|
+
binaries: manifest.binaries.map(binary => ({
|
|
192
|
+
name: binary.name,
|
|
193
|
+
assetPrefix: binary.assetPrefix ?? binary.name,
|
|
194
|
+
required: binary.required ?? true,
|
|
195
|
+
postInstallMessage: binary.postInstallMessage,
|
|
196
|
+
})),
|
|
197
|
+
checksums: {
|
|
198
|
+
required: manifest.checksums?.required ?? true,
|
|
199
|
+
extension: manifest.checksums?.extension ?? DEFAULT_CHECKSUM_EXTENSION,
|
|
200
|
+
},
|
|
201
|
+
targets: normalizeTargets(manifest.platforms),
|
|
202
|
+
postInstallMessage: manifest.postInstallMessage,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function createSingleBinaryManifest(repository: string, binaryName: string): NormalizedInstallerManifest {
|
|
207
|
+
const repo = assertGitHubRepository(repository);
|
|
208
|
+
const name = expectString(binaryName, "binaryName");
|
|
209
|
+
return normalizeInstallerManifest({
|
|
210
|
+
schemaVersion: 1,
|
|
211
|
+
repo,
|
|
212
|
+
binaries: [{ name }],
|
|
213
|
+
}, repo);
|
|
214
|
+
}
|