Expand the English README into a practical guide covering installation, accounts, queue controls, automation, history, backups, updates, troubleshooting, and development. Add four sanitized workflow screenshots and enforce their exact public manifest. Restore the animated Backup submenu outside its parent menu, keep expanded account groups naturally sized in the scrolling Accounts list, and strengthen Electron UI regression exit codes.
132 lines
5.3 KiB
JavaScript
132 lines
5.3 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { spawnSync } = require('node:child_process');
|
|
|
|
const root = path.resolve(__dirname, '..');
|
|
const rootFiles = [
|
|
'.gitignore',
|
|
'README.md',
|
|
'SECURITY.md',
|
|
'eslint.config.mjs',
|
|
'main.js',
|
|
'package-lock.json',
|
|
'package.json',
|
|
'preload-drop-target.js',
|
|
'preload.js'
|
|
];
|
|
const directoryRoots = ['assets', 'docs', 'lib', 'renderer', 'services/backup-api', 'tests'];
|
|
const scriptFiles = ['scripts/afterPack.cjs', 'scripts/dev-runner.cjs', 'scripts/release-plan.mjs', 'scripts/verify-public-release.mjs'];
|
|
const screenshotFiles = [
|
|
'assets/product-overview.png',
|
|
'docs/screenshots/upload-workspace.png',
|
|
'docs/screenshots/account-management.png',
|
|
'docs/screenshots/automation-settings.png',
|
|
'docs/screenshots/history.png'
|
|
];
|
|
const currentVersion = require('../package.json').version;
|
|
|
|
function copyDirectory(source, destination) {
|
|
fs.mkdirSync(destination, { recursive: true });
|
|
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
if (/^_ui-inject\..+\.tmp\.js$/.test(entry.name)) continue;
|
|
const sourcePath = path.join(source, entry.name);
|
|
const destinationPath = path.join(destination, entry.name);
|
|
if (entry.isDirectory()) copyDirectory(sourcePath, destinationPath);
|
|
else if (entry.isFile()) fs.copyFileSync(sourcePath, destinationPath);
|
|
}
|
|
}
|
|
|
|
function copyDocumentationScreenshots(stage) {
|
|
for (const relativePath of screenshotFiles.slice(1)) {
|
|
const destination = path.join(stage, relativePath);
|
|
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
fs.copyFileSync(path.join(root, screenshotFiles[0]), destination);
|
|
}
|
|
}
|
|
|
|
function createStage() {
|
|
const stage = fs.mkdtempSync(path.join(os.tmpdir(), 'mhu-public-verifier-'));
|
|
for (const relativePath of rootFiles) {
|
|
const destination = path.join(stage, relativePath);
|
|
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
fs.copyFileSync(path.join(root, relativePath), destination);
|
|
}
|
|
for (const relativePath of directoryRoots) {
|
|
if (relativePath === 'docs') copyDocumentationScreenshots(stage);
|
|
else copyDirectory(path.join(root, relativePath), path.join(stage, relativePath));
|
|
}
|
|
for (const relativePath of scriptFiles) {
|
|
const destination = path.join(stage, relativePath);
|
|
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
fs.copyFileSync(path.join(root, relativePath), destination);
|
|
}
|
|
fs.rmSync(path.join(stage, 'assets', 'product-overview.png'), { force: true });
|
|
return stage;
|
|
}
|
|
|
|
function verify(stage, version = currentVersion, sourceOnly = true) {
|
|
const args = ['scripts/verify-public-release.mjs'];
|
|
if (sourceOnly) args.push('--source-only');
|
|
args.push('--version', version);
|
|
return spawnSync(process.execPath, args, {
|
|
cwd: stage,
|
|
encoding: 'utf8'
|
|
});
|
|
}
|
|
|
|
test('public release verifier accepts only the exact source manifest and target version', (t) => {
|
|
const stage = createStage();
|
|
t.after(() => fs.rmSync(stage, { recursive: true, force: true }));
|
|
|
|
const baseline = verify(stage);
|
|
assert.equal(baseline.status, 0, baseline.stderr);
|
|
assert.match(baseline.stdout, /layout=exact/);
|
|
|
|
fs.writeFileSync(path.join(stage, 'tests', 'unexpected.json'), '{}');
|
|
const extra = verify(stage);
|
|
assert.equal(extra.status, 1);
|
|
assert.match(extra.stderr, /tests\/unexpected\.json\tsource-layout-allowlist/);
|
|
fs.rmSync(path.join(stage, 'tests', 'unexpected.json'));
|
|
|
|
fs.copyFileSync(path.join(stage, screenshotFiles[1]), path.join(stage, 'docs', 'screenshots', 'unexpected.png'));
|
|
const extraDocumentation = verify(stage);
|
|
assert.equal(extraDocumentation.status, 1);
|
|
assert.match(extraDocumentation.stderr, /docs\/screenshots\/unexpected\.png\tsource-layout-allowlist/);
|
|
fs.rmSync(path.join(stage, 'docs', 'screenshots', 'unexpected.png'));
|
|
|
|
const wrongVersion = verify(stage, '2.0.5');
|
|
assert.equal(wrongVersion.status, 1);
|
|
assert.match(wrongVersion.stderr, /package\.json\tpackage-version-target/);
|
|
});
|
|
|
|
test('public release verifier requires and validates every approved screenshot', (t) => {
|
|
const stage = createStage();
|
|
t.after(() => fs.rmSync(stage, { recursive: true, force: true }));
|
|
fs.copyFileSync(path.join(root, screenshotFiles[0]), path.join(stage, screenshotFiles[0]));
|
|
|
|
const baseline = verify(stage, currentVersion, false);
|
|
assert.equal(baseline.status, 0, baseline.stderr);
|
|
|
|
for (const relativePath of screenshotFiles) {
|
|
const absolutePath = path.join(stage, relativePath);
|
|
const original = fs.readFileSync(absolutePath);
|
|
const invalid = Buffer.from(original);
|
|
invalid.writeUInt32BE(999, 16);
|
|
fs.writeFileSync(absolutePath, invalid);
|
|
|
|
const invalidScreenshot = verify(stage, currentVersion, false);
|
|
assert.equal(invalidScreenshot.status, 1);
|
|
assert.ok(invalidScreenshot.stderr.includes(`${relativePath}\tproduct-screenshot`), invalidScreenshot.stderr);
|
|
fs.writeFileSync(absolutePath, original);
|
|
}
|
|
|
|
const missingPath = screenshotFiles.at(-1);
|
|
fs.rmSync(path.join(stage, missingPath));
|
|
const missingScreenshot = verify(stage, currentVersion, false);
|
|
assert.equal(missingScreenshot.status, 1);
|
|
assert.ok(missingScreenshot.stderr.includes(`${missingPath}\trequired-screenshot`), missingScreenshot.stderr);
|
|
});
|