addUpdate.ts
1 /** 2 * @file adds the new version to the `updates.json` 3 * 4 * Usage: 5 * 6 * 1. add the current version: 7 * pnpm add-update --file /path/to/coub-addons-0.1.26-firefox.xpi 8 * 9 * 2. add a specific version: 10 * - using file: 11 * pnpm add-update --version 0.1.26 --file /path/to/coub-addons-0.1.26-firefox.xpi 12 * 13 * - using precomputed hash: 14 * pnpm add-update --version 0.1.26 --sha256 sha256_hash_of_xpi 15 */ 16 17 import { createHash } from 'node:crypto'; 18 import { createReadStream } from 'node:fs'; 19 import fs from 'node:fs/promises'; 20 import path from 'node:path'; 21 import { buffer } from 'node:stream/consumers'; 22 import { fileURLToPath } from 'node:url'; 23 import { parseArgs } from 'node:util'; 24 import { codeToANSI } from '@shikijs/cli'; 25 import { execa } from 'execa'; 26 27 import updates from '../docs/updates.json' with { type: 'json' }; 28 import pkg from '../package.json' with { type: 'json' }; 29 import { geckoManifest } from '../wxt.config'; 30 31 const ROOT_PATH = path.dirname(path.dirname(fileURLToPath(import.meta.url))); 32 const UPDATES_FILE = path.join(ROOT_PATH, 'docs', 'updates.json'); 33 34 const { 35 values: { version: argsVersion, file, sha256 }, 36 } = parseArgs({ 37 options: { 38 version: { 39 type: 'string', 40 short: 'v', 41 }, 42 file: { 43 type: 'string', 44 short: 'f', 45 }, 46 sha256: { 47 type: 'string', 48 }, 49 }, 50 }); 51 52 const version = argsVersion || pkg.version; 53 54 if (!version) { 55 throw new RangeError(`\`--version\` is required, got \`${JSON.stringify(version)}\``); 56 } 57 58 let newUpdateHash: string; 59 60 if (sha256) { 61 if (!argsVersion) { 62 throw new Error('`--sha256` can only be used with `--version`'); 63 } 64 65 newUpdateHash = sha256; 66 } else { 67 if (!file) { 68 throw new RangeError(`path to the XPI \`--file\` is required, got \`${JSON.stringify(file)}\``); 69 } 70 71 try { 72 newUpdateHash = (await buffer(createReadStream(file).pipe(createHash('sha256')))).toString( 73 'hex', 74 ); 75 } catch (err) { 76 console.error('Failed to hash', file, err); 77 process.exit(1); 78 } 79 } 80 81 interface Update { 82 version: string; 83 update_link: string; 84 update_info_url: string; 85 update_hash: string; 86 applications: { 87 gecko: { 88 strict_min_version: string; 89 }; 90 }; 91 } 92 93 const newUpdate: Update = { 94 version, 95 update_link: `https://github.com/hikiko4ern/coub-addons/releases/download/v${version}/coub-addons-${version}-firefox.xpi`, 96 update_info_url: `https://coub-addons.doggo.moe/release-notes/${version}.html`, 97 update_hash: `sha256:${newUpdateHash}`, 98 applications: { 99 gecko: { 100 strict_min_version: geckoManifest.strict_min_version, 101 }, 102 }, 103 }; 104 105 const oldExtUpdates = updates.addons[process.env.VITE_GECKO_ID as keyof typeof updates.addons]; 106 const sameVersionOldUpdate = oldExtUpdates.updates.findIndex(u => u.version === version); 107 108 const newUpdates: typeof updates = { 109 ...updates, 110 addons: { 111 ...updates.addons, 112 [process.env.VITE_GECKO_ID]: { 113 ...oldExtUpdates, 114 updates: oldExtUpdates.updates.toSpliced( 115 sameVersionOldUpdate === -1 ? oldExtUpdates.updates.length : sameVersionOldUpdate, 116 sameVersionOldUpdate === -1 ? 0 : 1, 117 newUpdate, 118 ), 119 }, 120 }, 121 }; 122 123 console.log('Writing to', path.relative(ROOT_PATH, UPDATES_FILE)); 124 console.log(); 125 126 if (sameVersionOldUpdate !== -1) { 127 console.warn('Replacing old update'); 128 console.warn( 129 await codeToANSI( 130 JSON.stringify(oldExtUpdates.updates[sameVersionOldUpdate], null, 2), 131 'json', 132 'ayu-dark', 133 ), 134 ); 135 console.log(); 136 } 137 138 console.log('New update:'); 139 console.log(await codeToANSI(JSON.stringify(newUpdate, null, 2), 'json', 'ayu-dark')); 140 141 await fs.writeFile(UPDATES_FILE, JSON.stringify(newUpdates, null, 2), { encoding: 'utf8' }); 142 143 await execa({ 144 stdout: 'inherit', 145 stderr: 'inherit', 146 })`pnpm dprint fmt ${UPDATES_FILE}`;