Files
orca/config/scripts/release-javascript-artifact.mjs
T
Neil 83cf7cf5e2 perf(ci): compile release JavaScript once for all packaging hosts (#25828)
* perf(ci): share release JavaScript across packaging hosts

* fix(ci): verify the projected web entry in release archives

* fix(ci): use the Windows system archive tool for release bundles

* test(ci): retain stylesheet evidence in release build comparisons

* test(ci): verify release parity across native color rounding

* test(ci): normalize manifest asset references without changing import order

* test(ci): compare portable outputs across Windows text and color formatting

* test(ci): preserve module identity across dependent asset hashes

* fix(ci): keep SVG build inputs identical across release hosts

* fix(ci): stabilize compiler inputs and projected web bindings

* fix(ci): retain vendor minification in projected web output

* test(ci): normalize platform-specific pnpm manifest source paths

* fix(packaging): exclude shared build staging from application files

* test: align thinking-state fixtures with the current source shape

* test(mobile): reuse message fixtures within the line limit
2026-10-06 13:22:04 -07:00

177 lines
6.5 KiB
JavaScript

import assert from 'node:assert/strict'
import { createHash } from 'node:crypto'
import {
chmodSync,
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
renameSync,
rmSync,
statSync,
writeFileSync
} from 'node:fs'
import { join, resolve } from 'node:path'
import { runProcessSync, describeProcessFailure } from './script-child-process.mjs'
import { isDirectInvocation } from './script-entry-detection.mjs'
import { DESKTOP_RC_TAG, DESKTOP_STABLE_TAG } from './release-tag-patterns.mjs'
import { getTarProgram } from './zip-extractor-command.mjs'
const REQUIRED_FILES = [
'cli/index.js',
'main/index.js',
'preload/index.js',
'renderer/index.html',
'renderer/.vite/manifest.json',
'web/web-index.html',
'mobile-web/manifest.json',
'package.json'
]
const NATIVE_FILE = /\.(?:node|exe|dll|dylib|so(?:\.\d+)*|a)$/i
const ARCHIVE_NAME = 'release-javascript.tar.gz'
function sha256(bytes) {
return createHash('sha256').update(bytes).digest('hex')
}
function command(program, args, cwd) {
const result = runProcessSync({ program, args, cwd, timeoutMs: 120_000 })
assert.equal(result.code, 0, describeProcessFailure(result))
return result.stdout.trim()
}
export function releaseBuildIdentity(tag) {
if (DESKTOP_RC_TAG.test(tag)) {
return 'rc'
}
if (DESKTOP_STABLE_TAG.test(tag)) {
return 'stable'
}
throw new Error(`Invalid desktop release tag: ${tag}`)
}
export function releaseJavascriptConfiguration(root, env = process.env) {
const sourceSha = command('git', ['rev-parse', 'HEAD'], root)
assert.match(sourceSha, /^[a-f0-9]{40}$/)
if (env.ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA) {
assert.equal(
sourceSha,
env.ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA,
'Release checkout differs from bundle source'
)
}
const { version, packageManager } = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
const identity = releaseBuildIdentity(`v${version}`)
assert.equal(env.ORCA_BUILD_IDENTITY, identity, 'Release build identity differs from version')
assert(env.ORCA_DIAGNOSTICS_TOKEN_URL, 'Missing release diagnostics URL')
assert(env.ORCA_POSTHOG_WRITE_KEY, 'Missing release telemetry key')
return {
sourceSha,
version,
packageManager,
identity,
diagnosticsTokenUrl: env.ORCA_DIAGNOSTICS_TOKEN_URL,
telemetryKeySha256: sha256(env.ORCA_POSTHOG_WRITE_KEY)
}
}
export function javascriptInventory(directory, prefix = '', { excludeHostOutput = false } = {}) {
return readdirSync(directory, { withFileTypes: true })
.sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0))
.flatMap((entry) => {
const name = prefix ? `${prefix}/${entry.name}` : entry.name
const hostOutput = ['relay', 'orcad-template', 'orcad-prebuilds'].includes(name.split('/')[0])
if (excludeHostOutput && hostOutput) {
return []
}
assert(!entry.isSymbolicLink(), `JavaScript artifact contains a symlink: ${name}`)
assert(!NATIVE_FILE.test(name), `JavaScript artifact contains a native binary: ${name}`)
assert(!hostOutput, `JavaScript artifact contains host output: ${name}`)
const file = join(directory, entry.name)
if (entry.isDirectory()) {
return javascriptInventory(file, name, { excludeHostOutput })
}
assert(entry.isFile(), `JavaScript artifact contains a special file: ${name}`)
const bytes = readFileSync(file)
return [{ path: name, bytes: bytes.length, sha256: sha256(bytes) }]
})
}
function verifyRequiredFiles(files) {
const present = new Set(files.map((file) => file.path))
for (const file of REQUIRED_FILES) {
assert(present.has(file), `Missing JavaScript output: ${file}`)
}
}
export function packReleaseJavascript({ root = process.cwd(), artifactDir, configuration }) {
configuration ??= releaseJavascriptConfiguration(root)
const out = join(root, 'out')
const files = javascriptInventory(out)
verifyRequiredFiles(files)
mkdirSync(artifactDir, { recursive: true })
const archive = join(artifactDir, ARCHIVE_NAME)
command(getTarProgram(), ['-czf', archive, '-C', root, 'out'], root)
const manifest = {
schema: 1,
configuration,
archiveSha256: sha256(readFileSync(archive)),
archiveBytes: statSync(archive).size,
files
}
writeFileSync(join(artifactDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`)
return manifest
}
export function restoreReleaseJavascript({ root = process.cwd(), artifactDir, configuration }) {
configuration ??= releaseJavascriptConfiguration(root)
const manifest = JSON.parse(readFileSync(join(artifactDir, 'manifest.json'), 'utf8'))
assert.equal(manifest.schema, 1, 'Unsupported JavaScript artifact schema')
assert.deepEqual(manifest.configuration, configuration, 'JavaScript build configuration differs')
const archive = join(artifactDir, ARCHIVE_NAME)
assert.equal(sha256(readFileSync(archive)), manifest.archiveSha256, 'JavaScript archive differs')
const entries = command(getTarProgram(), ['-tzf', archive], root).split(/\r?\n/)
for (const entry of entries) {
assert(entry === 'out/' || entry.startsWith('out/'), `Unexpected archive entry: ${entry}`)
assert(
!entry.includes('\\') && !entry.split('/').includes('..'),
`Unsafe archive entry: ${entry}`
)
}
mkdirSync(join(root, '.build'), { recursive: true })
const temporary = mkdtempSync(join(root, '.build', 'release-javascript-'))
try {
command(getTarProgram(), ['-xzf', archive, '-C', temporary], root)
const staged = join(temporary, 'out')
const files = javascriptInventory(staged)
verifyRequiredFiles(files)
assert.deepEqual(files, manifest.files, 'JavaScript file inventory differs')
rmSync(join(root, 'out'), { recursive: true, force: true })
renameSync(staged, join(root, 'out'))
if (process.platform !== 'win32') {
chmodSync(join(root, 'out', 'cli', 'index.js'), 0o755)
}
} finally {
rmSync(temporary, { recursive: true, force: true })
}
return manifest
}
if (isDirectInvocation(import.meta.url, process.argv[1])) {
const [mode, artifact] = process.argv.slice(2)
if (mode === 'identity') {
console.log(releaseBuildIdentity(process.env.RELEASE_TAG))
} else {
const artifactDir = resolve(artifact ?? '.build/release-javascript')
const result =
mode === 'pack'
? packReleaseJavascript({ artifactDir })
: mode === 'restore'
? restoreReleaseJavascript({ artifactDir })
: assert.fail('Expected pack, restore, or identity')
console.log(
`[release-javascript] ${mode}: ${result.files.length} files, ${result.archiveBytes} archived bytes`
)
}
}