Spaces:
Runtime error
Runtime error
// From the NPM docs: | |
// "If you need to perform operations on your package before it is used, in a way that is not dependent on the | |
// operating system or architecture of the target system, use a prepublish script." | |
// Once this step is complete, a developer should be able to work without an Internet connection. | |
// See also: https://docs.npmjs.com/cli/using-npm/scripts | |
import fs from 'fs'; | |
import path from 'path'; | |
import nodeCrypto from 'crypto'; | |
import crossFetch from 'cross-fetch'; | |
import yauzl from 'yauzl'; | |
import {fileURLToPath} from 'url'; | |
/** @typedef {import('yauzl').Entry} ZipEntry */ | |
/** @typedef {import('yauzl').ZipFile} ZipFile */ | |
// these aren't set in ESM mode | |
const __filename = fileURLToPath(import.meta.url); | |
const __dirname = path.dirname(__filename); | |
// base/root path for the project | |
const basePath = path.join(__dirname, '..'); | |
/** | |
* Extract the first matching file from a zip buffer. | |
* The path within the zip file is ignored: the destination path is `${destinationDirectory}/${basename(entry.name)}`. | |
* Prints warnings if more than one matching file is found. | |
* @param {function(ZipEntry): boolean} filter Returns true if the entry should be extracted. | |
* @param {string} relativeDestDir The directory to extract to, relative to `basePath`. | |
* @param {Buffer} zipBuffer A buffer containing the zip file. | |
* @returns {Promise<string>} A Promise for the base name of the written file (without directory). | |
*/ | |
const extractFirstMatchingFile = (filter, relativeDestDir, zipBuffer) => new Promise((resolve, reject) => { | |
try { | |
let extractedFileName; | |
yauzl.fromBuffer(zipBuffer, {lazyEntries: true}, (zipError, zipfile) => { | |
if (zipError) { | |
throw zipError; | |
} | |
zipfile.readEntry(); | |
zipfile.on('end', () => { | |
resolve(extractedFileName); | |
}); | |
zipfile.on('entry', entry => { | |
if (!filter(entry)) { | |
// ignore non-matching file | |
return zipfile.readEntry(); | |
} | |
if (extractedFileName) { | |
console.warn(`Multiple matching files found. Ignoring: ${entry.fileName}`); | |
return zipfile.readEntry(); | |
} | |
extractedFileName = entry.fileName; | |
console.info(`Found matching file: ${entry.fileName}`); | |
zipfile.openReadStream(entry, (fileError, readStream) => { | |
if (fileError) { | |
throw fileError; | |
} | |
const baseName = path.basename(entry.fileName); | |
const relativeDestFile = path.join(relativeDestDir, baseName); | |
console.info(`Extracting ${relativeDestFile}`); | |
const absoluteDestDir = path.join(basePath, relativeDestDir); | |
fs.mkdirSync(absoluteDestDir, {recursive: true}); | |
const absoluteDestFile = path.join(basePath, relativeDestFile); | |
const outStream = fs.createWriteStream(absoluteDestFile); | |
readStream.on('end', () => { | |
outStream.close(); | |
zipfile.readEntry(); | |
}); | |
readStream.pipe(outStream); | |
}); | |
}); | |
}); | |
} catch (error) { | |
reject(error); | |
} | |
}); | |
const downloadMicrobitHex = async () => { | |
const url = 'https://packagerdata.turbowarp.org/scratch-microbit-1.2.0.hex.zip'; | |
const expectedSHA256 = 'dfd574b709307fe76c44dbb6b0ac8942e7908f4d5c18359fae25fbda3c9f4399'; | |
console.info(`Downloading ${url}`); | |
const response = await crossFetch(url); | |
const zipBuffer = Buffer.from(await response.arrayBuffer()); | |
const sha256 = nodeCrypto.createHash('sha-256').update(zipBuffer).digest('hex'); | |
if (sha256 !== expectedSHA256) { | |
throw new Error(`microbit hex has SHA-256 ${sha256} but expected ${expectedSHA256}`); | |
} | |
const relativeHexDir = path.join('static', 'microbit'); | |
const hexFileName = await extractFirstMatchingFile( | |
entry => /\.hex$/.test(entry.fileName), | |
path.join('static', 'microbit'), | |
zipBuffer | |
); | |
const relativeHexFile = path.join(relativeHexDir, hexFileName); | |
const relativeGeneratedDir = path.join('src', 'generated'); | |
const relativeGeneratedFile = path.join(relativeGeneratedDir, 'microbit-hex-url.cjs'); | |
const absoluteGeneratedDir = path.join(basePath, relativeGeneratedDir); | |
fs.mkdirSync(absoluteGeneratedDir, {recursive: true}); | |
const absoluteGeneratedFile = path.join(basePath, relativeGeneratedFile); | |
const requirePath = `./${path | |
.relative(relativeGeneratedDir, relativeHexFile) | |
.split(path.win32.sep) | |
.join(path.posix.sep)}`; | |
fs.writeFileSync( | |
absoluteGeneratedFile, | |
[ | |
'// This file is generated by scripts/prepublish.mjs', | |
'// Do not edit this file directly', | |
'// This file relies on a loader to turn this `require` into a URL', | |
`module.exports = require('${requirePath}');`, | |
'' // final newline | |
].join('\n') | |
); | |
console.info(`Wrote ${relativeGeneratedFile}`); | |
}; | |
const prepublish = async () => { | |
await downloadMicrobitHex(); | |
}; | |
prepublish().then( | |
() => { | |
console.info('Prepublish script complete'); | |
process.exit(0); | |
}, | |
e => { | |
console.error(e); | |
process.exit(1); | |
} | |
); | |