Initial commit of 001code-html Scratch frontend project.
Includes scratch-gui, scratch-vm, scratch-blocks, scratch-render, scratch-l10n, and deployment config. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
8
scratch-gui/scripts/.eslintrc.js
Normal file
8
scratch-gui/scripts/.eslintrc.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const path = require('path');
|
||||
module.exports = {
|
||||
extends: [path.resolve(__dirname, '..', '.eslintrc.js')],
|
||||
rules: {
|
||||
// NPM scripts are allowed to use console.log & friends
|
||||
'no-console': 'off'
|
||||
}
|
||||
};
|
||||
57
scratch-gui/scripts/patch-config.js
Normal file
57
scratch-gui/scripts/patch-config.js
Normal file
@@ -0,0 +1,57 @@
|
||||
// patch-config.js: replace DataServerBaseUrl and disabledev before dev server or build
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const targetFile = path.join(__dirname, '..', 'src', 'playground', 'config.js');
|
||||
|
||||
function decideEnv() {
|
||||
const ev = (process.env.npm_lifecycle_event || '').toLowerCase();
|
||||
if (ev.includes('build:prod')) return 'prod';
|
||||
if (ev.includes('build:test')) return 'test';
|
||||
if (ev.includes('start')) return 'start';
|
||||
if (ev.includes('dev')) return 'dev';
|
||||
// default
|
||||
return 'start';
|
||||
}
|
||||
|
||||
function getConfigForEnv(env) {
|
||||
switch (env) {
|
||||
case 'dev':
|
||||
case 'test':
|
||||
return { url: 'https://test.server.001code.com', disabledev: false };
|
||||
case 'prod':
|
||||
return { url: 'https://server.001code.com', disabledev: true };
|
||||
case 'start':
|
||||
default:
|
||||
return { url: 'http://127.0.0.1:8000', disabledev: false };
|
||||
}
|
||||
}
|
||||
|
||||
function replaceContent(src, url, disabledev) {
|
||||
let out = src;
|
||||
out = out.replace(/DataServerBaseUrl\s*:\s*[^,]+/g, `DataServerBaseUrl: "${url}"`);
|
||||
out = out.replace(/disabledev\s*:\s*[^,]+/g, `disabledev: ${disabledev}`);
|
||||
return out;
|
||||
}
|
||||
|
||||
(function run() {
|
||||
const env = decideEnv();
|
||||
const cfg = getConfigForEnv(env);
|
||||
console.log(`[patch-config] Env=${env} -> DataServerBaseUrl="${cfg.url}", disabledev=${cfg.disabledev}`);
|
||||
|
||||
try {
|
||||
const original = fs.readFileSync(targetFile, 'utf8');
|
||||
const patched = replaceContent(original, cfg.url, cfg.disabledev);
|
||||
|
||||
if (patched !== original) {
|
||||
fs.writeFileSync(targetFile, patched, 'utf8');
|
||||
console.log('[patch-config] Applied replacements to', targetFile);
|
||||
} else {
|
||||
console.warn('[patch-config] No changes applied. Patterns not found or already set.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[patch-config] Failed to patch', targetFile, err);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
132
scratch-gui/scripts/prepublish.mjs
Normal file
132
scratch-gui/scripts/prepublish.mjs
Normal file
@@ -0,0 +1,132 @@
|
||||
// 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);
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user