up
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const { spawn } = require('node:child_process')
|
||||
const { mkdtemp, mkdir, readFile, writeFile, rm } = require('node:fs/promises')
|
||||
const { join, resolve } = require('node:path')
|
||||
const { tmpdir } = require('node:os')
|
||||
const { fileHash } = require('./release.cjs')
|
||||
const metadata = require('../package.json')
|
||||
const root = resolve(__dirname, '..')
|
||||
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
async function until(check, label) {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const result = await check()
|
||||
if (result) return result
|
||||
await delay(100)
|
||||
}
|
||||
throw new Error(`Timeout: ${label}`)
|
||||
}
|
||||
async function launch(binary, profile, check) {
|
||||
const child = spawn(
|
||||
binary,
|
||||
[
|
||||
`--user-data-dir=${profile}`,
|
||||
'--remote-debugging-port=0',
|
||||
'--remote-debugging-address=127.0.0.1',
|
||||
'--no-sandbox',
|
||||
'--mute-audio',
|
||||
'--disable-gpu',
|
||||
],
|
||||
{ stdio: ['ignore', 'ignore', 'pipe'] },
|
||||
)
|
||||
let stderr = ''
|
||||
let spawnError = null
|
||||
let exited = false
|
||||
child.on('error', (error) => {
|
||||
spawnError = error
|
||||
})
|
||||
const exit = new Promise((resolve) =>
|
||||
child.on('close', () => {
|
||||
exited = true
|
||||
resolve()
|
||||
}),
|
||||
)
|
||||
child.stderr.on('data', (bytes) => {
|
||||
stderr = (stderr + bytes.toString()).slice(-32768)
|
||||
})
|
||||
let socket
|
||||
try {
|
||||
const port = await until(() => {
|
||||
if (spawnError) throw spawnError
|
||||
if (exited) throw new Error('Packaged application exited before startup')
|
||||
const match = /DevTools listening on ws:\/\/127\.0\.0\.1:(\d+)\//.exec(stderr)
|
||||
return match?.[1]
|
||||
}, 'DevTools endpoint')
|
||||
const target = await until(async () => {
|
||||
const response = await fetch(`http://127.0.0.1:${port}/json`)
|
||||
const targets = await response.json()
|
||||
return targets.find((page) => page.url === 'screen-app://app/index.html')
|
||||
}, 'control target')
|
||||
socket = new WebSocket(target.webSocketDebuggerUrl)
|
||||
await new Promise((resolve, reject) => {
|
||||
socket.onopen = resolve
|
||||
socket.onerror = reject
|
||||
})
|
||||
let id = 0
|
||||
const pending = new Map()
|
||||
socket.onmessage = ({ data }) => {
|
||||
const result = JSON.parse(data)
|
||||
const entry = pending.get(result.id)
|
||||
if (entry) {
|
||||
pending.delete(result.id)
|
||||
clearTimeout(entry.timer)
|
||||
entry.resolve(result)
|
||||
}
|
||||
}
|
||||
socket.onclose = () => {
|
||||
for (const entry of pending.values()) {
|
||||
clearTimeout(entry.timer)
|
||||
entry.reject(new Error('Debugger disconnected'))
|
||||
}
|
||||
pending.clear()
|
||||
}
|
||||
const request = (method, params = {}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const callId = ++id
|
||||
const timer = setTimeout(() => {
|
||||
pending.delete(callId)
|
||||
reject(new Error('Debugger request timeout'))
|
||||
}, 5000)
|
||||
pending.set(callId, { resolve, reject, timer })
|
||||
socket.send(JSON.stringify({ id: callId, method, params }))
|
||||
})
|
||||
const evaluate = async (expression) => {
|
||||
const result = await request('Runtime.evaluate', {
|
||||
expression,
|
||||
awaitPromise: true,
|
||||
returnByValue: true,
|
||||
})
|
||||
assert.ok(!result.error && !result.result?.exceptionDetails, 'Packaged evaluation failed')
|
||||
return result.result.result.value
|
||||
}
|
||||
await until(
|
||||
() => evaluate('!!window.screenBridge && !!document.querySelector("h1")'),
|
||||
'packaged UI',
|
||||
)
|
||||
await check(evaluate)
|
||||
void request('Page.close').catch(() => {})
|
||||
await until(() => exited, 'normal application exit')
|
||||
} finally {
|
||||
socket?.close()
|
||||
if (!exited) {
|
||||
child.kill('SIGKILL')
|
||||
await exit
|
||||
}
|
||||
}
|
||||
}
|
||||
async function main() {
|
||||
const binary = resolve(process.argv[2] || 'release/linux-unpacked/evening-detective-screen')
|
||||
const previous = process.argv[3] ? resolve(process.argv[3]) : null
|
||||
const profile = await mkdtemp(join(tmpdir(), 'screen-packaged-'))
|
||||
const { PackageStore } = require('../dist-electron/electron/package-store.js')
|
||||
const { buildScreenPackage } = require('../dist-electron/electron/screen-package.js')
|
||||
try {
|
||||
const store = new PackageStore(join(profile, 'packages'), {
|
||||
fetch: async (input) => {
|
||||
const video = String(input).endsWith('.mp4')
|
||||
return new Response(
|
||||
await readFile(join(root, 'fixtures', video ? 'video.mp4' : 'audio.wav')),
|
||||
{ headers: { 'Content-Type': video ? 'video/mp4' : 'audio/wav' } },
|
||||
)
|
||||
},
|
||||
})
|
||||
await store.initialize()
|
||||
const description = buildScreenPackage('https://packaged.test', 7, {
|
||||
game: {
|
||||
game: {
|
||||
ID: 7,
|
||||
name: 'Packaged offline',
|
||||
scenario: {
|
||||
story: { introduction: { audio: 'intro.wav' }, conclusion: { video: 'video.mp4' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
plan: {},
|
||||
music: {},
|
||||
})
|
||||
const pkg = await store.download(description)
|
||||
const connection = join(profile, 'connection')
|
||||
await mkdir(connection)
|
||||
await writeFile(
|
||||
join(connection, 'selection.json'),
|
||||
JSON.stringify({ server: description.server, key: pkg.key }),
|
||||
)
|
||||
const results = {
|
||||
packageKey: pkg.key,
|
||||
capturedAt: '2026-10-17T10:00:00.000Z',
|
||||
questions: [{ code: 'A', text: 'Question' }],
|
||||
teams: [
|
||||
{
|
||||
id: 't',
|
||||
name: 'Team',
|
||||
place: 1,
|
||||
totalScore: 3,
|
||||
actionsCount: 1,
|
||||
answers: [{ questionCode: 'A', score: 3 }],
|
||||
},
|
||||
],
|
||||
}
|
||||
const resultsFile = join(connection, `results-${pkg.key}.json`)
|
||||
await writeFile(resultsFile, JSON.stringify(results))
|
||||
await writeFile(
|
||||
join(profile, 'windows.json'),
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
control: { bounds: { x: 30, y: 30, width: 900, height: 650 }, maximized: false },
|
||||
display: null,
|
||||
}),
|
||||
)
|
||||
const manifest = join(profile, 'packages', pkg.key, `v-${pkg.version}`, 'manifest.json')
|
||||
const before = await fileHash(manifest)
|
||||
const resultHash = await fileHash(resultsFile)
|
||||
if (previous)
|
||||
await launch(previous, profile, async (evaluate) => {
|
||||
assert.equal(await evaluate('typeof window.require'), 'undefined')
|
||||
})
|
||||
for (let attempt = 0; attempt < 2; attempt++)
|
||||
await launch(binary, profile, async (evaluate) => {
|
||||
assert.equal(await evaluate('typeof window.require'), 'undefined')
|
||||
const state = await evaluate('window.screenBridge.state()')
|
||||
assert.equal(state.catalog.key, pkg.key)
|
||||
assert.equal(state.results.teams[0].totalScore, 3)
|
||||
assert.equal(state.outputOpen, false)
|
||||
assert.equal(state.timer.status, 'stopped')
|
||||
assert.equal(state.channels.main.desired, 'stopped')
|
||||
assert.equal(state.channels.music.desired, 'stopped')
|
||||
assert.equal(await evaluate('document.querySelectorAll("audio,video").length'), 0)
|
||||
for (const url of [
|
||||
'screen-app://app/media/video',
|
||||
state.catalog.media.find((m) => m.kind === 'video').url,
|
||||
]) {
|
||||
const range = await evaluate(
|
||||
`fetch(${JSON.stringify(url)}, {headers:{Range:'bytes=100-199'}}).then(async r=>({status:r.status,size:(await r.arrayBuffer()).byteLength}))`,
|
||||
)
|
||||
assert.deepEqual(range, { status: 206, size: 100 })
|
||||
}
|
||||
const logs = await readFile(join(profile, 'logs', 'diagnostic.jsonl'), 'utf8')
|
||||
const starts = logs
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map((line) => JSON.parse(line))
|
||||
.filter((row) => row.event === 'app-start')
|
||||
assert.equal(starts.at(-1).version, metadata.version)
|
||||
assert.equal(await fileHash(manifest), before)
|
||||
assert.equal(await fileHash(resultsFile), resultHash)
|
||||
assert.equal((await evaluate('window.screenBridge.host()')).sleepBlocked, false)
|
||||
})
|
||||
const log = (await readFile(join(profile, 'logs', 'diagnostic.jsonl'), 'utf8'))
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map((line) => JSON.parse(line))
|
||||
assert.equal(log.at(-1).event, 'app-stop')
|
||||
const restored = new PackageStore(join(profile, 'packages'))
|
||||
await restored.initialize()
|
||||
assert.equal(restored.list()[0].version, pkg.version)
|
||||
const windows = JSON.parse(await readFile(join(profile, 'windows.json'), 'utf8'))
|
||||
assert.equal(windows.control.maximized, false)
|
||||
assert.ok(windows.control.bounds.width <= 900 && windows.control.bounds.height <= 650)
|
||||
console.log(
|
||||
'PACKAGED_SMOKE_PASS',
|
||||
JSON.stringify({
|
||||
version: metadata.version,
|
||||
previous: !!previous,
|
||||
restarts: 2,
|
||||
offlineFiles: pkg.files.length,
|
||||
preservedResults: true,
|
||||
}),
|
||||
)
|
||||
} finally {
|
||||
await rm(profile, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
main().catch((error) => {
|
||||
console.error(error.message)
|
||||
process.exitCode = 1
|
||||
})
|
||||
@@ -0,0 +1,74 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const { mkdtempSync, readdirSync, writeFileSync, rmSync } = require('node:fs')
|
||||
const { join, basename, resolve } = require('node:path')
|
||||
const { execFileSync } = require('node:child_process')
|
||||
const { fileHash, verifyDirectory } = require('./release.cjs')
|
||||
const metadata = require('../package.json')
|
||||
const output = resolve(__dirname, '..', 'release')
|
||||
async function verifyArchive(artifact) {
|
||||
const temporary = mkdtempSync(join(output, '.verify-'))
|
||||
try {
|
||||
if (artifact.endsWith('.tar.gz')) execFileSync('tar', ['-xzf', artifact, '-C', temporary])
|
||||
else {
|
||||
const sevenZip = await require('app-builder-lib/out/toolsets/7zip').getPath7za()
|
||||
execFileSync(sevenZip, ['x', artifact, `-o${temporary}`, '-y', '-bd'], { stdio: 'pipe' })
|
||||
}
|
||||
let directory = temporary
|
||||
if (!readdirSync(temporary).some((name) => name === 'resources' || name.endsWith('.app'))) {
|
||||
const dirs = readdirSync(temporary, { withFileTypes: true }).filter((d) => d.isDirectory())
|
||||
assert.equal(dirs.length, 1, 'Expected one application directory')
|
||||
directory = join(temporary, dirs[0].name)
|
||||
}
|
||||
return await verifyDirectory(directory)
|
||||
} finally {
|
||||
rmSync(temporary, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
async function finish(context) {
|
||||
const supplied = context?.artifactPaths ?? process.argv.slice(2).map((p) => resolve(p))
|
||||
const artifacts = supplied.filter((path) => /\.(?:zip|tar\.gz)$/.test(path))
|
||||
assert.ok(artifacts.length, 'No artifacts from current build')
|
||||
const sums = []
|
||||
const sidecars = []
|
||||
for (const artifact of artifacts) {
|
||||
assert.ok(
|
||||
basename(artifact).startsWith(`${metadata.name}-${metadata.version}-`),
|
||||
'Wrong release version',
|
||||
)
|
||||
const info = await verifyArchive(artifact)
|
||||
const name = basename(artifact)
|
||||
assert.ok(
|
||||
name.includes(
|
||||
`-${info.platform === 'win32' ? 'win' : info.platform === 'darwin' ? 'mac' : 'linux'}-${info.arch}.`,
|
||||
),
|
||||
'Artifact name/architecture mismatch',
|
||||
)
|
||||
const sha256 = await fileHash(artifact)
|
||||
const sidecar = `${artifact}.build-info.json`
|
||||
writeFileSync(sidecar, JSON.stringify({ ...info, artifact: name, sha256 }, null, 2) + '\n')
|
||||
sidecars.push(sidecar)
|
||||
sums.push(`${sha256} ${name}`)
|
||||
}
|
||||
// Не подмешивать старые файлы из release/: каждый build получает свой manifest.
|
||||
const path = join(
|
||||
output,
|
||||
`SHA256SUMS-${artifacts
|
||||
.map((p) =>
|
||||
basename(p)
|
||||
.split(`${metadata.version}-`)[1]
|
||||
.replace(/\.(zip|tar\.gz)$/, ''),
|
||||
)
|
||||
.sort()
|
||||
.join('_')}`,
|
||||
)
|
||||
writeFileSync(path, sums.sort().join('\n') + '\n')
|
||||
console.log('RELEASE_ARCHIVES_PASS', artifacts.map((path) => basename(path)).join(', '))
|
||||
return [...sidecars, path]
|
||||
}
|
||||
module.exports = finish
|
||||
module.exports.verifyArchive = verifyArchive
|
||||
if (require.main === module)
|
||||
finish().catch((error) => {
|
||||
console.error(error.message)
|
||||
process.exitCode = 1
|
||||
})
|
||||
@@ -0,0 +1,145 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const { createHash } = require('node:crypto')
|
||||
const {
|
||||
createReadStream,
|
||||
existsSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
} = require('node:fs')
|
||||
const { join, relative } = require('node:path')
|
||||
const { execFileSync } = require('node:child_process')
|
||||
const asar = require('@electron/asar')
|
||||
const root = join(__dirname, '..')
|
||||
const metadata = require('../package.json')
|
||||
const sha = (bytes) => createHash('sha256').update(bytes).digest('hex')
|
||||
async function fileHash(path) {
|
||||
const hash = createHash('sha256')
|
||||
for await (const bytes of createReadStream(path)) hash.update(bytes)
|
||||
return hash.digest('hex')
|
||||
}
|
||||
function resourcesIn(directory) {
|
||||
const app = readdirSync(directory).find((name) => name.endsWith('.app'))
|
||||
return app ? join(directory, app, 'Contents', 'Resources') : join(directory, 'resources')
|
||||
}
|
||||
function verifyAsar(path) {
|
||||
const files = asar.listPackage(path).map((name) => name.replace(/^\//, ''))
|
||||
for (const name of files) {
|
||||
assert.ok(
|
||||
!/(^|\/)(?:\.git|\.env(?:\.[^/]*)?|credentials\.bin|windows\.json|selection\.json|__tests__)(?:\/|$)|\.test\.(?:js|ts)$|\.(?:pem|key|p12|pfx)$/.test(
|
||||
name,
|
||||
),
|
||||
`Forbidden packaged file: ${name}`,
|
||||
)
|
||||
assert.ok(
|
||||
/^(?:node_modules(?:\/|$)|dist(?:\/|$)|dist-electron(?:\/|$)|package\.json$)/.test(name),
|
||||
`Unexpected packaged file: ${name}`,
|
||||
)
|
||||
}
|
||||
for (const name of [
|
||||
'package.json',
|
||||
'dist/index.html',
|
||||
'dist-electron/electron/main.js',
|
||||
'dist-electron/electron/preload.js',
|
||||
'dist-electron/electron/online.js',
|
||||
'dist-electron/electron/diagnostics.js',
|
||||
])
|
||||
assert.ok(files.includes(name), `Missing ${name}`)
|
||||
const pkg = JSON.parse(asar.extractFile(path, 'package.json'))
|
||||
assert.equal(pkg.version, metadata.version)
|
||||
assert.equal(pkg.name, metadata.name)
|
||||
return pkg
|
||||
}
|
||||
function binaryArchitecture(path, platform) {
|
||||
const bytes = readFileSync(path)
|
||||
if (platform === 'linux') {
|
||||
assert.equal(bytes.subarray(0, 4).toString('hex'), '7f454c46')
|
||||
return { 62: 'x64', 183: 'arm64' }[bytes.readUInt16LE(18)]
|
||||
}
|
||||
if (platform === 'win32') {
|
||||
assert.equal(bytes.subarray(0, 2).toString(), 'MZ')
|
||||
const offset = bytes.readUInt32LE(0x3c)
|
||||
assert.equal(bytes.subarray(offset, offset + 4).toString(), 'PE\0\0')
|
||||
return { 0x8664: 'x64', 0xaa64: 'arm64' }[bytes.readUInt16LE(offset + 4)]
|
||||
}
|
||||
assert.equal(bytes.subarray(0, 4).toString('hex'), 'cffaedfe')
|
||||
return { 0x1000007: 'x64', 0x100000c: 'arm64' }[bytes.readUInt32LE(4)]
|
||||
}
|
||||
function executableIn(directory, platform) {
|
||||
if (platform === 'darwin') {
|
||||
const app = readdirSync(directory).find((name) => name.endsWith('.app'))
|
||||
return join(directory, app, 'Contents', 'MacOS', metadata.build.productName)
|
||||
}
|
||||
return join(directory, platform === 'win32' ? `${metadata.build.productName}.exe` : metadata.name)
|
||||
}
|
||||
async function verifyDirectory(directory) {
|
||||
const resources = resourcesIn(directory)
|
||||
const info = JSON.parse(readFileSync(join(resources, 'build-info.json')))
|
||||
assert.equal(info.formatVersion, 1)
|
||||
assert.equal(info.version, metadata.version)
|
||||
assert.ok(['linux', 'win32', 'darwin'].includes(info.platform))
|
||||
assert.ok(['x64', 'arm64'].includes(info.arch))
|
||||
verifyAsar(join(resources, 'app.asar'))
|
||||
assert.deepEqual(Object.keys(info.files).sort(), [
|
||||
'app.asar',
|
||||
'fixtures/audio.wav',
|
||||
'fixtures/video.mp4',
|
||||
])
|
||||
for (const [name, expected] of Object.entries(info.files))
|
||||
assert.equal(await fileHash(join(resources, name)), expected, `Hash mismatch: ${name}`)
|
||||
assert.deepEqual(readdirSync(join(resources, 'fixtures')).sort(), ['audio.wav', 'video.mp4'])
|
||||
assert.ok(
|
||||
!existsSync(join(resources, 'app.asar.unpacked')),
|
||||
'Unexpected unpacked application files',
|
||||
)
|
||||
assert.equal(binaryArchitecture(executableIn(directory, info.platform), info.platform), info.arch)
|
||||
return info
|
||||
}
|
||||
async function afterPack(context) {
|
||||
const resources = resourcesIn(context.appOutDir)
|
||||
verifyAsar(join(resources, 'app.asar'))
|
||||
const files = {}
|
||||
for (const name of ['app.asar', 'fixtures/audio.wav', 'fixtures/video.mp4'])
|
||||
files[name] = await fileHash(join(resources, name))
|
||||
let revision = null
|
||||
let dirty = true
|
||||
try {
|
||||
const result = execFileSync('git', ['rev-parse', 'HEAD'], {
|
||||
cwd: root,
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
}).trim()
|
||||
if (/^[a-f0-9]{40,64}$/.test(result)) revision = result
|
||||
dirty = !!execFileSync('git', ['status', '--porcelain'], { cwd: root, encoding: 'utf8' }).trim()
|
||||
} catch {
|
||||
/* Экспорт исходников без .git тоже может быть собран. */
|
||||
}
|
||||
const ffmpeg = execFileSync('ffmpeg', ['-version'], { encoding: 'utf8' }).split('\n')[0]
|
||||
const info = {
|
||||
formatVersion: 1,
|
||||
version: metadata.version,
|
||||
platform: context.electronPlatformName,
|
||||
arch: require('builder-util').Arch[context.arch],
|
||||
revision,
|
||||
dirty,
|
||||
tools: {
|
||||
node: process.versions.node,
|
||||
electron: context.packager.info.framework.version,
|
||||
builder: require('electron-builder/package.json').version,
|
||||
ffmpeg,
|
||||
},
|
||||
lockfileSha256: sha(readFileSync(join(root, 'package-lock.json'))),
|
||||
files,
|
||||
}
|
||||
writeFileSync(join(resources, 'build-info.json'), JSON.stringify(info, null, 2) + '\n')
|
||||
await verifyDirectory(context.appOutDir)
|
||||
console.log('RELEASE_CONTENTS_PASS', relative(root, context.appOutDir), info.platform, info.arch)
|
||||
}
|
||||
module.exports = afterPack
|
||||
Object.assign(module.exports, {
|
||||
fileHash,
|
||||
verifyAsar,
|
||||
verifyDirectory,
|
||||
binaryArchitecture,
|
||||
resourcesIn,
|
||||
})
|
||||
@@ -0,0 +1,81 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const { test } = require('node:test')
|
||||
const { mkdtempSync, writeFileSync, mkdirSync, rmSync } = require('node:fs')
|
||||
const { join } = require('node:path')
|
||||
const { tmpdir } = require('node:os')
|
||||
const asar = require('@electron/asar')
|
||||
const { binaryArchitecture, verifyAsar, verifyDirectory, fileHash } = require('./release.cjs')
|
||||
test('release verifier rejects test files and secrets in app.asar', async (t) => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'screen-release-test-'))
|
||||
t.after(() => rmSync(dir, { recursive: true, force: true }))
|
||||
const source = join(dir, 'source')
|
||||
mkdirSync(source)
|
||||
writeFileSync(join(source, '.env'), 'secret')
|
||||
const archive = join(dir, 'app.asar')
|
||||
await asar.createPackage(source, archive)
|
||||
assert.throws(() => verifyAsar(archive), /Forbidden/)
|
||||
})
|
||||
test('release verifier checks native ELF and PE architecture', (t) => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'screen-release-test-'))
|
||||
t.after(() => rmSync(dir, { recursive: true, force: true }))
|
||||
const elf = Buffer.alloc(64)
|
||||
elf.write('7f454c46', 0, 'hex')
|
||||
elf.writeUInt16LE(183, 18)
|
||||
const path = join(dir, 'binary')
|
||||
writeFileSync(path, elf)
|
||||
assert.equal(binaryArchitecture(path, 'linux'), 'arm64')
|
||||
const pe = Buffer.alloc(128)
|
||||
pe.write('MZ')
|
||||
pe.writeUInt32LE(64, 0x3c)
|
||||
pe.write('PE\0\0', 64)
|
||||
pe.writeUInt16LE(0x8664, 68)
|
||||
writeFileSync(path, pe)
|
||||
assert.equal(binaryArchitecture(path, 'win32'), 'x64')
|
||||
})
|
||||
|
||||
test('release verifier detects changed media bytes after packaging', async (t) => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'screen-release-test-'))
|
||||
t.after(() => rmSync(dir, { recursive: true, force: true }))
|
||||
const source = join(dir, 'source')
|
||||
for (const file of [
|
||||
'dist/index.html',
|
||||
'dist-electron/electron/main.js',
|
||||
'dist-electron/electron/preload.js',
|
||||
'dist-electron/electron/online.js',
|
||||
'dist-electron/electron/diagnostics.js',
|
||||
]) {
|
||||
const path = join(source, file)
|
||||
mkdirSync(require('node:path').dirname(path), { recursive: true })
|
||||
writeFileSync(path, 'fixture')
|
||||
}
|
||||
const metadata = require('../package.json')
|
||||
writeFileSync(
|
||||
join(source, 'package.json'),
|
||||
JSON.stringify({ name: metadata.name, version: metadata.version }),
|
||||
)
|
||||
const resources = join(dir, 'resources')
|
||||
mkdirSync(join(resources, 'fixtures'), { recursive: true })
|
||||
await asar.createPackage(source, join(resources, 'app.asar'))
|
||||
writeFileSync(join(resources, 'fixtures/audio.wav'), 'audio')
|
||||
writeFileSync(join(resources, 'fixtures/video.mp4'), 'video')
|
||||
const elf = Buffer.alloc(64)
|
||||
elf.write('7f454c46', 0, 'hex')
|
||||
elf.writeUInt16LE(62, 18)
|
||||
writeFileSync(join(dir, metadata.name), elf)
|
||||
const files = {}
|
||||
for (const name of ['app.asar', 'fixtures/audio.wav', 'fixtures/video.mp4'])
|
||||
files[name] = await fileHash(join(resources, name))
|
||||
writeFileSync(
|
||||
join(resources, 'build-info.json'),
|
||||
JSON.stringify({
|
||||
formatVersion: 1,
|
||||
version: metadata.version,
|
||||
platform: 'linux',
|
||||
arch: 'x64',
|
||||
files,
|
||||
}),
|
||||
)
|
||||
await verifyDirectory(dir)
|
||||
writeFileSync(join(resources, 'fixtures/video.mp4'), 'altered')
|
||||
await assert.rejects(verifyDirectory(dir), /Hash mismatch/)
|
||||
})
|
||||
Reference in New Issue
Block a user