Skip to content

Commit 4eb4aa7

Browse files
authored
Merge pull request #96 from beNative/codex/fix-windows-automatic-update-checksum-error
Fix Windows manifest normalization to refresh checksums
2 parents ffad452 + f831d22 commit 4eb4aa7

1 file changed

Lines changed: 195 additions & 22 deletions

File tree

electron/scripts/normalize-win32-artifacts.mjs

Lines changed: 195 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#!/usr/bin/env node
2-
import { promises as fs } from 'fs';
2+
import { promises as fsPromises, createReadStream } from 'fs';
33
import path from 'path';
44
import process from 'process';
5+
import { createHash } from 'crypto';
56

67
const log = (message, data) => {
78
if (data) {
@@ -15,13 +16,167 @@ const releaseDir = path.resolve(process.cwd(), 'release');
1516

1617
const exists = async (filePath) => {
1718
try {
18-
await fs.access(filePath);
19+
await fsPromises.access(filePath);
1920
return true;
2021
} catch (_error) {
2122
return false;
2223
}
2324
};
2425

26+
const computeSha512 = async (filePath) => {
27+
const hash = createHash('sha512');
28+
await new Promise((resolve, reject) => {
29+
const stream = createReadStream(filePath);
30+
stream.on('data', chunk => hash.update(chunk));
31+
stream.on('error', reject);
32+
stream.on('end', resolve);
33+
});
34+
return hash.digest('base64');
35+
};
36+
37+
const toBasename = (value) => {
38+
if (typeof value !== 'string' || value.trim() === '') {
39+
return null;
40+
}
41+
return path.basename(value.trim());
42+
};
43+
44+
const updateManifestFile = async (manifestPath, renameMap, hashMap) => {
45+
const raw = await fsPromises.readFile(manifestPath, 'utf8');
46+
const lines = raw.split(/\r?\n/);
47+
48+
let inFilesSection = false;
49+
let inPackagesSection = false;
50+
let currentFileKey = null;
51+
let currentPackageFileKey = null;
52+
let rootFileKey = null;
53+
54+
const replaceWithRename = (value) => {
55+
const base = toBasename(value);
56+
if (!base) {
57+
return value;
58+
}
59+
const renamed = renameMap.get(base);
60+
if (!renamed || renamed === base) {
61+
return value;
62+
}
63+
return value.endsWith(base)
64+
? value.slice(0, value.length - base.length) + renamed
65+
: value.replace(base, renamed);
66+
};
67+
68+
const applyHashIfAvailable = (existingLine, key, indentPattern) => {
69+
if (!key) {
70+
return existingLine;
71+
}
72+
const hash = hashMap.get(key);
73+
if (!hash) {
74+
return existingLine;
75+
}
76+
return existingLine.replace(indentPattern, `$1${hash}`);
77+
};
78+
79+
for (let index = 0; index < lines.length; index += 1) {
80+
const line = lines[index];
81+
const trimmed = line.trim();
82+
83+
if (trimmed === 'files:') {
84+
inFilesSection = true;
85+
inPackagesSection = false;
86+
currentFileKey = null;
87+
continue;
88+
}
89+
if (trimmed === 'packages:') {
90+
inFilesSection = false;
91+
inPackagesSection = true;
92+
currentPackageFileKey = null;
93+
continue;
94+
}
95+
if (!line.startsWith(' ')) {
96+
inFilesSection = trimmed === '' ? inFilesSection : false;
97+
if (trimmed !== 'packages:') {
98+
inPackagesSection = false;
99+
}
100+
}
101+
102+
if (inFilesSection) {
103+
const urlMatch = line.match(/^\s*-\s+url:\s+(.*)$/);
104+
if (urlMatch) {
105+
const rawValue = urlMatch[1];
106+
const updatedValue = replaceWithRename(rawValue);
107+
if (updatedValue !== rawValue) {
108+
lines[index] = line.replace(rawValue, updatedValue);
109+
}
110+
currentFileKey = toBasename(updatedValue);
111+
continue;
112+
}
113+
114+
const pathMatch = line.match(/^\s+path:\s+(.*)$/);
115+
if (pathMatch) {
116+
const rawValue = pathMatch[1];
117+
const updatedValue = replaceWithRename(rawValue);
118+
if (updatedValue !== rawValue) {
119+
lines[index] = line.replace(rawValue, updatedValue);
120+
}
121+
currentFileKey = toBasename(updatedValue);
122+
continue;
123+
}
124+
125+
const shaMatch = line.match(/^(\s+sha512:\s+)(.*)$/);
126+
if (shaMatch) {
127+
lines[index] = applyHashIfAvailable(line, currentFileKey, /^(\s+sha512:\s+).*/);
128+
continue;
129+
}
130+
continue;
131+
}
132+
133+
if (inPackagesSection) {
134+
const packagePathMatch = line.match(/^\s{4}path:\s+(.*)$/);
135+
if (packagePathMatch) {
136+
const rawValue = packagePathMatch[1];
137+
const updatedValue = replaceWithRename(rawValue);
138+
if (updatedValue !== rawValue) {
139+
lines[index] = line.replace(rawValue, updatedValue);
140+
}
141+
currentPackageFileKey = toBasename(updatedValue);
142+
continue;
143+
}
144+
145+
const packageShaMatch = line.match(/^(\s{4}sha512:\s+).*/);
146+
if (packageShaMatch) {
147+
lines[index] = applyHashIfAvailable(line, currentPackageFileKey, /^(\s{4}sha512:\s+).*/);
148+
continue;
149+
}
150+
continue;
151+
}
152+
153+
const rootPathMatch = line.match(/^path:\s+(.*)$/);
154+
if (rootPathMatch) {
155+
const rawValue = rootPathMatch[1];
156+
const updatedValue = replaceWithRename(rawValue);
157+
if (updatedValue !== rawValue) {
158+
lines[index] = line.replace(rawValue, updatedValue);
159+
}
160+
rootFileKey = toBasename(updatedValue);
161+
continue;
162+
}
163+
164+
const rootShaMatch = line.match(/^(sha512:\s+).*/);
165+
if (rootShaMatch) {
166+
lines[index] = applyHashIfAvailable(line, rootFileKey, /^(sha512:\s+).*/);
167+
continue;
168+
}
169+
}
170+
171+
const updatedContent = lines.join('\n');
172+
if (updatedContent !== raw) {
173+
await fsPromises.writeFile(manifestPath, updatedContent, 'utf8');
174+
log('Updated manifest metadata.', { manifest: path.basename(manifestPath) });
175+
} else {
176+
log('Manifest already up to date.', { manifest: path.basename(manifestPath) });
177+
}
178+
};
179+
25180
const updateName = (name) => name.replace(/-ia32-/gi, '-win32-');
26181

27182
const main = async () => {
@@ -31,14 +186,15 @@ const main = async () => {
31186
return;
32187
}
33188

34-
const entries = await fs.readdir(releaseDir);
189+
const entries = await fsPromises.readdir(releaseDir);
35190
const ia32Executables = entries.filter(name => /-ia32-/i.test(name) && name.endsWith('.exe'));
36191
if (ia32Executables.length === 0) {
37-
log('No ia32 Windows executables detected; nothing to normalize.');
38-
return;
192+
log('No ia32 Windows executables detected; skipping rename but refreshing manifest checksums.');
193+
} else {
194+
log('Detected ia32 executables that require normalization.', ia32Executables);
39195
}
40196

41-
log('Detected ia32 executables that require normalization.', ia32Executables);
197+
const renameMap = new Map();
42198

43199
for (const fileName of ia32Executables) {
44200
const sourcePath = path.join(releaseDir, fileName);
@@ -47,8 +203,9 @@ const main = async () => {
47203
continue;
48204
}
49205
const targetPath = path.join(releaseDir, targetName);
50-
await fs.rename(sourcePath, targetPath);
206+
await fsPromises.rename(sourcePath, targetPath);
51207
log('Renamed executable.', { from: fileName, to: targetName });
208+
renameMap.set(fileName, targetName);
52209
}
53210

54211
const blockmaps = entries.filter(name => /-ia32-/i.test(name) && name.endsWith('.exe.blockmap'));
@@ -59,30 +216,46 @@ const main = async () => {
59216
continue;
60217
}
61218
const targetPath = path.join(releaseDir, targetName);
62-
await fs.rename(sourcePath, targetPath);
219+
await fsPromises.rename(sourcePath, targetPath);
63220
log('Renamed blockmap.', { from: blockmap, to: targetName });
221+
renameMap.set(blockmap, targetName);
64222
}
65223

66224
const manifestPath = path.join(releaseDir, 'latest.yml');
225+
const fallbackManifest = path.join(releaseDir, 'latest-win32.yml');
67226
const hasManifest = await exists(manifestPath);
68-
if (!hasManifest) {
69-
const fallbackManifest = path.join(releaseDir, 'latest-win32.yml');
70-
if (await exists(fallbackManifest)) {
71-
log('Found existing latest-win32.yml manifest; normalization already applied.');
72-
return;
227+
const hasFallback = await exists(fallbackManifest);
228+
229+
if (!hasFallback && hasManifest) {
230+
await fsPromises.copyFile(manifestPath, fallbackManifest);
231+
log('Seeded latest-win32.yml from latest.yml for normalization.');
232+
}
233+
234+
const hashes = new Map();
235+
const updatedEntries = await fsPromises.readdir(releaseDir);
236+
for (const entry of updatedEntries) {
237+
if (/\.exe(\.blockmap)?$/i.test(entry)) {
238+
const fullPath = path.join(releaseDir, entry);
239+
hashes.set(entry, await computeSha512(fullPath));
73240
}
74-
log('No latest.yml manifest found for ia32 build; cannot update metadata.');
75-
return;
76241
}
77242

78-
const manifestRaw = await fs.readFile(manifestPath, 'utf8');
79-
const normalizedManifest = manifestRaw.replace(/-ia32-/gi, '-win32-');
80-
const targetManifestPath = path.join(releaseDir, 'latest-win32.yml');
81-
await fs.writeFile(targetManifestPath, normalizedManifest, 'utf8');
82-
log('Wrote normalized manifest.', { path: path.relative(process.cwd(), targetManifestPath) });
243+
const manifestsToUpdate = [];
244+
if (await exists(manifestPath)) {
245+
manifestsToUpdate.push(manifestPath);
246+
}
247+
if (await exists(fallbackManifest)) {
248+
manifestsToUpdate.push(fallbackManifest);
249+
}
250+
251+
if (manifestsToUpdate.length === 0) {
252+
log('No manifest files found to update; please verify the release output.');
253+
return;
254+
}
83255

84-
await fs.unlink(manifestPath);
85-
log('Removed original latest.yml manifest to prevent mismatched metadata.');
256+
for (const manifest of manifestsToUpdate) {
257+
await updateManifestFile(manifest, renameMap, hashes);
258+
}
86259
};
87260

88261
main().catch(error => {

0 commit comments

Comments
 (0)