File size: 5,532 Bytes
8fd7a1d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// 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);
    }
);