perf(windows): support isolated packaging workspaces
This commit is contained in:
1 parent
a12c0dfe46
commit
9ee94429b9
8 files changed
+262
-6
No files matched your search
@@ -47,6 +47,11 @@ on:
|
||||
type: string
|
||||
default: ''
|
||||
required: false
|
||||
build_root:
|
||||
description: '独立构建根目录:填写绝对路径,每次任务创建独立源码副本;留空使用 runner 工作区'
|
||||
type: string
|
||||
default: ''
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -71,6 +76,7 @@ jobs:
|
||||
PACKAGE_TEST_RELEASE_ID: ${{ inputs.test_release_id }}
|
||||
PACKAGE_SIGNING: ${{ inputs.signing }}
|
||||
PACKAGE_CACHE_NAMESPACE: ${{ inputs.cache_namespace }}
|
||||
PACKAGE_BUILD_ROOT: ${{ inputs.build_root }}
|
||||
steps:
|
||||
- name: Reset packaging checkout
|
||||
working-directory: ${{ runner.temp }}
|
||||
@@ -113,6 +119,17 @@ jobs:
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
node-version: '24'
|
||||
- name: Select packaging workspace
|
||||
id: build_workspace
|
||||
env:
|
||||
PACKAGE_SOURCE_SHA: ${{ steps.checkout.outputs.commit }}
|
||||
run: |
|
||||
if (!$env:PACKAGE_BUILD_ROOT) {
|
||||
"directory=$env:GITHUB_WORKSPACE" >> $env:GITHUB_OUTPUT
|
||||
} else {
|
||||
node apps/desktop/scripts/windows-build-workspace.mjs
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
- name: Configure persistent pnpm store
|
||||
# PNPM_HOME belongs to this run; keep downloaded packages outside its temporary directory.
|
||||
env:
|
||||
@@ -129,6 +146,7 @@ jobs:
|
||||
"DSH_DESKTOP_PACKAGE_CACHE_DIR=$env:DSH_DESKTOP_PACKAGE_CACHE_DIR" >> $env:GITHUB_ENV
|
||||
"PNPM_CONFIG_STORE_DIR=$storeRoot" >> $env:GITHUB_ENV
|
||||
- name: Install locked dependencies
|
||||
working-directory: ${{ steps.build_workspace.outputs.directory }}
|
||||
run: |
|
||||
pnpm store path
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
@@ -136,10 +154,12 @@ jobs:
|
||||
exit $LASTEXITCODE
|
||||
- name: Validate preset and resolve build version
|
||||
id: settings
|
||||
working-directory: ${{ steps.build_workspace.outputs.directory }}
|
||||
env:
|
||||
PACKAGE_BUILD_VERSION: ${{ inputs.build_version }}
|
||||
run: node apps/desktop/scripts/prepare-windows-ci.mjs
|
||||
- name: Check packaging configuration and toolchain
|
||||
working-directory: ${{ steps.build_workspace.outputs.directory }}
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ steps.settings.outputs.version }}
|
||||
run: |
|
||||
@@ -148,6 +168,7 @@ jobs:
|
||||
pnpm @packageArgs
|
||||
exit $LASTEXITCODE
|
||||
- name: Build installer and verify packaged runtime
|
||||
working-directory: ${{ steps.build_workspace.outputs.directory }}
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ steps.settings.outputs.version }}
|
||||
run: |
|
||||
@@ -157,6 +178,7 @@ jobs:
|
||||
exit $LASTEXITCODE
|
||||
- name: Retain complete package outside the checkout
|
||||
id: retained
|
||||
working-directory: ${{ steps.build_workspace.outputs.directory }}
|
||||
env:
|
||||
PACKAGE_SOURCE_SHA: ${{ steps.checkout.outputs.commit }}
|
||||
PACKAGE_VERSION: ${{ steps.settings.outputs.version }}
|
||||
@@ -167,7 +189,7 @@ jobs:
|
||||
with:
|
||||
name: windows-package-logs-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
include-hidden-files: true
|
||||
path: apps/desktop/.desktop-build/packaging-runs/
|
||||
path: ${{ steps.build_workspace.outputs.directory || github.workspace }}/apps/desktop/.desktop-build/packaging-runs/
|
||||
if-no-files-found: warn
|
||||
retention-days: 14
|
||||
- name: Retain verified unsigned installer
|
||||
@@ -188,3 +210,8 @@ jobs:
|
||||
path: ${{ steps.retained.outputs.directory }}/*
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
- name: Remove isolated packaging workspace
|
||||
if: success() && inputs.build_root != ''
|
||||
env:
|
||||
PACKAGE_SOURCE_SHA: ${{ steps.checkout.outputs.commit }}
|
||||
run: node apps/desktop/scripts/windows-build-workspace.mjs --remove
|
||||
@@ -24,8 +24,8 @@
|
||||
en: 5b4f0f80a117f202
|
||||
zh: ff05be196c181087
|
||||
/desktop-packaging/windows-actions-packaging:
|
||||
en: 79f09d4deac94041
|
||||
zh: 6e0cfbda4c2f6518
|
||||
en: 68d800ed10cac4a6
|
||||
zh: f72ac7e939af7ad5
|
||||
/desktop-packaging/macos-self-hosted-packaging:
|
||||
en: a3af9523fdc60036
|
||||
zh: 5e18d4740e9b7f57
|
||||
|
||||
@@ -113,6 +113,8 @@ Select the workflow in Actions, choose the source branch, preset, signing mode a
|
||||
|
||||
Open the completed run in Actions and download its Artifacts. `windows-x64-signed-<version>-<run_id>-<run_attempt>` and `windows-x64-unsigned-<version>-<run_id>-<run_attempt>` contain the corresponding EXE; `windows-package-logs-<run_id>-<run_attempt>` contains packaging diagnostics. The upload steps are `Retain verified signed installer`, `Retain verified unsigned installer`, and `Retain packaging diagnostics`; their step durations and timestamped logs show upload time. These artifacts are not a Release or download-site publication.
|
||||
|
||||
For path-sensitive Windows build measurements, `build_root` optionally selects an absolute directory outside existing Git checkouts. Each run clones the validated source commit into a new `windows-package-<run-id>-<attempt>/source` directory; ignored files and previous build outputs are not copied. Dependency and signing caches retain their existing locations. Successful runs remove only their owned checkout after artifact uploads; failed runs retain it for inspection. Leave the input empty to build in the original Actions checkout. Include checkout creation and cleanup in the complete Job timing.
|
||||
|
||||
For cache measurements, `cache_namespace` selects a retained subdirectory for both dependency stores and upstream downloads; leave it empty for normal shared caches. Use a new name for a cold sample and repeat that name for warm samples. Names contain 1–64 lowercase letters, digits or hyphens, start with a letter or digit, and cannot be Windows device names. This does not reset the separate signature cache or OS file cache; report those states and Job start-to-completion time alongside phase timings and artifact size.
|
||||
|
||||
<a id="macos-actions"></a>
|
||||
|
||||
@@ -113,6 +113,8 @@ runner 的受监督启动和恢复见 [Windows runner 机器准备](../windows-s
|
||||
|
||||
在 Actions 打开完成的运行并下载 Artifacts。`windows-x64-signed-<version>-<run_id>-<run_attempt>` 与 `windows-x64-unsigned-<version>-<run_id>-<run_attempt>` 包含对应 EXE;`windows-package-logs-<run_id>-<run_attempt>` 包含打包诊断。上传步骤名为 `Retain verified signed installer`、`Retain verified unsigned installer` 和 `Retain packaging diagnostics`;步骤耗时与带时间戳的日志显示上传用时。这些产物不等于发布 Release 或下载站内容。
|
||||
|
||||
测量 Windows 构建路径对性能的影响时,可用 `build_root` 指定现有 Git checkout 之外的绝对目录。每轮任务将已验证的源码提交克隆到新的 `windows-package-<run-id>-<attempt>/source` 目录;不会复制忽略文件或此前的构建输出。依赖缓存和签名缓存沿用原有位置。成功任务在产物上传后仅删除本轮所属的 checkout;失败任务保留该目录供检查。输入留空时使用原 Actions checkout。完整 Job 耗时须包含 checkout 创建和清理。
|
||||
|
||||
测量缓存时,`cache_namespace` 为两个依赖 store 和上游下载选择保留的子目录;留空使用正常共享缓存。新名称用于冷缓存采样,再次使用同名用于热缓存采样。名称由 1–64 个小写字母、数字或连字符组成,以字母或数字开头,且不能为 Windows 设备名。这不会重置独立的签名缓存或操作系统文件缓存;应将其状态、Job 开始至完成的总耗时与阶段耗时和产物体积一并报告。
|
||||
|
||||
<a id="macos-actions"></a>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/** Identity of a Windows packaging run and its independent build root. */
|
||||
export interface WindowsBuildWorkspaceOptions {
|
||||
sourceDirectory: string
|
||||
buildRoot: string
|
||||
runId: string
|
||||
runAttempt: string
|
||||
sourceSha: string
|
||||
}
|
||||
/**
|
||||
* Clone the clean selected commit into a new run-owned directory.
|
||||
* @param options - Original checkout and Actions run identity.
|
||||
* @returns Checkout to use for packaging.
|
||||
*/
|
||||
export function prepareWindowsBuildWorkspace(options: WindowsBuildWorkspaceOptions): { directory: string }
|
||||
/**
|
||||
* Remove the directory only when its ownership record matches the run.
|
||||
* @param options - Original checkout and Actions run identity.
|
||||
*/
|
||||
export function removeWindowsBuildWorkspace(options: WindowsBuildWorkspaceOptions): void
|
||||
@@ -0,0 +1,94 @@
|
||||
/** Create and remove a run-owned checkout for Windows packaging. */
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { appendFileSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, isAbsolute, join, parse, resolve } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
|
||||
function assertUnlinkedAncestors(directory) {
|
||||
for (let current = directory; ; current = dirname(current)) {
|
||||
if (existsSync(current) && lstatSync(current).isSymbolicLink()) {
|
||||
throw new Error('Packaging workspace ancestors must not be links')
|
||||
}
|
||||
if (dirname(current) === current) break
|
||||
}
|
||||
}
|
||||
|
||||
function locations(options) {
|
||||
const { buildRoot, runId, runAttempt, sourceSha } = options
|
||||
if (!buildRoot || !isAbsolute(buildRoot) || /[\r\n\0]/.test(buildRoot)) {
|
||||
throw new Error('Packaging build root must be a nonempty absolute directory')
|
||||
}
|
||||
const root = resolve(buildRoot)
|
||||
if (root === parse(root).root) throw new Error('Packaging build root must not be a volume root')
|
||||
if (!/^[1-9]\d*$/.test(runId) || !/^[1-9]\d*$/.test(runAttempt) || !/^[a-f0-9]{40}$/.test(sourceSha)) {
|
||||
throw new Error('Packaging workspace requires a run identity and exact commit')
|
||||
}
|
||||
assertUnlinkedAncestors(root)
|
||||
for (let current = root; ; current = dirname(current)) {
|
||||
if (existsSync(join(current, '.git'))) throw new Error('Packaging build root must be outside existing Git checkouts')
|
||||
if (dirname(current) === current) break
|
||||
}
|
||||
const owner = join(root, `windows-package-${runId}-${runAttempt}`)
|
||||
assertUnlinkedAncestors(owner)
|
||||
return { root, owner, directory: join(owner, 'source') }
|
||||
}
|
||||
|
||||
function git(directory, args) {
|
||||
return execFileSync('git', args, { cwd: directory, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim()
|
||||
}
|
||||
|
||||
function ownership(options, paths) {
|
||||
return JSON.stringify({ version: 1, root: paths.root, source: resolve(options.sourceDirectory), runId: options.runId, runAttempt: options.runAttempt, sourceSha: options.sourceSha })
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the validated commit into a new run-owned checkout; failures retain diagnostics.
|
||||
* @param {{sourceDirectory: string, buildRoot: string, runId: string, runAttempt: string, sourceSha: string}} options - Source and run identity.
|
||||
* @returns {{directory: string}} Checkout to use for packaging.
|
||||
*/
|
||||
export function prepareWindowsBuildWorkspace(options) {
|
||||
const paths = locations(options)
|
||||
if (git(options.sourceDirectory, ['rev-parse', 'HEAD']) !== options.sourceSha || git(options.sourceDirectory, ['status', '--porcelain'])) {
|
||||
throw new Error('Packaging source must be clean and match the selected commit')
|
||||
}
|
||||
mkdirSync(paths.root, { recursive: true })
|
||||
mkdirSync(paths.owner)
|
||||
writeFileSync(join(paths.owner, 'owner.json'), ownership(options, paths), { flag: 'wx' })
|
||||
git(paths.owner, ['clone', '--no-hardlinks', '--no-checkout', resolve(options.sourceDirectory), paths.directory])
|
||||
git(paths.directory, ['checkout', '--detach', options.sourceSha])
|
||||
if (git(paths.directory, ['rev-parse', 'HEAD']) !== options.sourceSha || git(paths.directory, ['status', '--porcelain'])) {
|
||||
throw new Error('Isolated packaging checkout does not match the selected commit')
|
||||
}
|
||||
return { directory: paths.directory }
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove only the matching run-owned directory after successful artifact upload.
|
||||
* @param {{sourceDirectory: string, buildRoot: string, runId: string, runAttempt: string, sourceSha: string}} options - Original source and run identity.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function removeWindowsBuildWorkspace(options) {
|
||||
const paths = locations(options)
|
||||
const marker = join(paths.owner, 'owner.json')
|
||||
const stat = lstatSync(marker)
|
||||
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 8192 || readFileSync(marker, 'utf8') !== ownership(options, paths)) {
|
||||
throw new Error('Packaging workspace ownership does not match this run')
|
||||
}
|
||||
rmSync(paths.owner, { recursive: true })
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
||||
const options = {
|
||||
sourceDirectory: process.env.GITHUB_WORKSPACE ?? '',
|
||||
buildRoot: process.env.PACKAGE_BUILD_ROOT ?? '',
|
||||
runId: process.env.GITHUB_RUN_ID ?? '',
|
||||
runAttempt: process.env.GITHUB_RUN_ATTEMPT ?? '',
|
||||
sourceSha: process.env.PACKAGE_SOURCE_SHA ?? '',
|
||||
}
|
||||
if (process.argv[2] === '--remove') removeWindowsBuildWorkspace(options)
|
||||
else {
|
||||
if (process.argv.length !== 2 || !process.env.GITHUB_OUTPUT) throw new Error('Expected Actions output file and no arguments')
|
||||
const result = prepareWindowsBuildWorkspace(options)
|
||||
appendFileSync(process.env.GITHUB_OUTPUT, `directory=${result.directory}\n`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { expect, it } from 'vitest'
|
||||
import { prepareWindowsBuildWorkspace, removeWindowsBuildWorkspace } from '../apps/desktop/scripts/windows-build-workspace.mjs'
|
||||
|
||||
it('clones the exact clean commit and removes only its run-owned checkout', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'package-workspace-'))
|
||||
try {
|
||||
const sourceDirectory = join(root, 'original')
|
||||
mkdirSync(sourceDirectory)
|
||||
const git = (...args: string[]) => execFileSync('git', args, { cwd: sourceDirectory, encoding: 'utf8', stdio: 'pipe' }).trim()
|
||||
git('init')
|
||||
writeFileSync(join(sourceDirectory, 'source.txt'), 'selected source')
|
||||
writeFileSync(join(sourceDirectory, '.gitignore'), 'private.env\n')
|
||||
git('add', '.')
|
||||
git('-c', 'user.name=Fixture', '-c', '[email protected]', 'commit', '-m', 'fixture')
|
||||
writeFileSync(join(sourceDirectory, 'private.env'), 'ignored fixture')
|
||||
const options = { sourceDirectory, buildRoot: join(root, 'builds'), runId: '123', runAttempt: '1', sourceSha: git('rev-parse', 'HEAD') }
|
||||
expect(() => prepareWindowsBuildWorkspace({ ...options, buildRoot: sourceDirectory })).toThrow('outside existing Git')
|
||||
expect(() => prepareWindowsBuildWorkspace({ ...options, buildRoot: 'relative' })).toThrow('absolute')
|
||||
expect(() => prepareWindowsBuildWorkspace({ ...options, runId: '../other' })).toThrow('identity')
|
||||
expect(() => prepareWindowsBuildWorkspace({ ...options, sourceSha: '0'.repeat(40) })).toThrow('match')
|
||||
writeFileSync(join(sourceDirectory, 'source.txt'), 'dirty')
|
||||
expect(() => prepareWindowsBuildWorkspace(options)).toThrow('clean')
|
||||
writeFileSync(join(sourceDirectory, 'source.txt'), 'selected source')
|
||||
const output = join(root, 'actions-output')
|
||||
execFileSync(process.execPath, [resolve(import.meta.dirname, '../apps/desktop/scripts/windows-build-workspace.mjs')], {
|
||||
cwd: sourceDirectory,
|
||||
env: {
|
||||
...process.env,
|
||||
GITHUB_WORKSPACE: sourceDirectory,
|
||||
PACKAGE_BUILD_ROOT: options.buildRoot,
|
||||
GITHUB_RUN_ID: options.runId,
|
||||
GITHUB_RUN_ATTEMPT: options.runAttempt,
|
||||
PACKAGE_SOURCE_SHA: options.sourceSha,
|
||||
GITHUB_OUTPUT: output,
|
||||
},
|
||||
stdio: 'pipe',
|
||||
})
|
||||
const directory = join(options.buildRoot, 'windows-package-123-1', 'source')
|
||||
expect(readFileSync(output, 'utf8')).toBe(`directory=${directory}\n`)
|
||||
expect(readFileSync(join(directory, 'source.txt'), 'utf8')).toBe('selected source')
|
||||
expect(existsSync(join(directory, 'private.env'))).toBe(false)
|
||||
expect(() => prepareWindowsBuildWorkspace(options)).toThrow()
|
||||
expect(() => { removeWindowsBuildWorkspace({ ...options, sourceSha: '0'.repeat(40) }) }).toThrow('ownership')
|
||||
expect(existsSync(directory)).toBe(true)
|
||||
const external = join(root, 'external')
|
||||
mkdirSync(external)
|
||||
writeFileSync(join(external, 'keep.txt'), 'keep')
|
||||
symlinkSync(external, join(directory, 'node_modules'), process.platform === 'win32' ? 'junction' : 'dir')
|
||||
removeWindowsBuildWorkspace(options)
|
||||
expect(existsSync(directory)).toBe(false)
|
||||
expect(readFileSync(join(external, 'keep.txt'), 'utf8')).toBe('keep')
|
||||
expect(readFileSync(join(sourceDirectory, 'source.txt'), 'utf8')).toBe('selected source')
|
||||
const linked = join(root, 'linked')
|
||||
symlinkSync(external, linked, process.platform === 'win32' ? 'junction' : 'dir')
|
||||
expect(() => prepareWindowsBuildWorkspace({ ...options, buildRoot: join(linked, 'builds') })).toThrow('links')
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
@@ -7,7 +7,7 @@ import { expect, it } from 'vitest'
|
||||
|
||||
const root = resolve(import.meta.dirname, '..')
|
||||
const workflow = yaml.load(readFileSync(resolve(root, '.github/workflows/windows-package.yml'), 'utf8')) as {
|
||||
on: Record<string, { inputs: Record<string, { required?: boolean; default?: string; description?: string }> }>
|
||||
on: Record<string, { inputs: Record<string, { type?: string; required?: boolean; default?: string; description?: string }> }>
|
||||
permissions: Record<string, string>
|
||||
concurrency: { group: string; 'cancel-in-progress': boolean }
|
||||
jobs: Record<string, {
|
||||
@@ -19,6 +19,7 @@ const workflow = yaml.load(readFileSync(resolve(root, '.github/workflows/windows
|
||||
uses?: string
|
||||
run?: string
|
||||
if?: string
|
||||
'working-directory'?: string
|
||||
env?: Record<string, string>
|
||||
with?: Record<string, unknown>
|
||||
}>
|
||||
@@ -67,7 +68,7 @@ it('routes manual packaging to the dedicated runner with explicit preset and sig
|
||||
expect(workflow.concurrency).toEqual({ group: 'windows-desktop-package', 'cancel-in-progress': false })
|
||||
expect(workflow.permissions).toEqual({ contents: 'read' })
|
||||
expect(Object.keys(workflow.on.workflow_dispatch!.inputs).sort()).toEqual([
|
||||
'build_version', 'cache_namespace', 'preset', 'signing', 'source_ref', 'test_release_id', 'update_channel', 'version_mode',
|
||||
'build_root', 'build_version', 'cache_namespace', 'preset', 'signing', 'source_ref', 'test_release_id', 'update_channel', 'version_mode',
|
||||
])
|
||||
expect(workflow.on.workflow_dispatch!.inputs.signing!.default).toBe('signed')
|
||||
expect(workflow.on.workflow_dispatch!.inputs.build_version!.required).toBe(false)
|
||||
@@ -104,13 +105,61 @@ it('retains failure diagnostics and uploads only the selected successful install
|
||||
const uploads = job.steps.filter(step => step.uses?.startsWith('actions/upload-artifact@'))
|
||||
expect(uploads).toHaveLength(3)
|
||||
expect(uploads[0]!.if).toBe("always() && steps.checkout.outcome == 'success'")
|
||||
expect(uploads[0]!.with).toMatchObject({ path: 'apps/desktop/.desktop-build/packaging-runs/', 'include-hidden-files': true })
|
||||
expect(uploads[0]!.with).toMatchObject({
|
||||
path: '${{ steps.build_workspace.outputs.directory || github.workspace }}/apps/desktop/.desktop-build/packaging-runs/',
|
||||
'include-hidden-files': true,
|
||||
})
|
||||
for (const mode of ['unsigned', 'signed']) {
|
||||
const upload = uploads.find(step => step.if === `success() && inputs.signing == '${mode}'`)!
|
||||
expect(upload.with).toMatchObject({ path: '${{ steps.retained.outputs.directory }}/*', 'if-no-files-found': 'error' })
|
||||
}
|
||||
})
|
||||
|
||||
it('routes all packaging consumers to the selected workspace and removes it only after successful uploads', () => {
|
||||
expect(workflow.on.workflow_dispatch!.inputs.build_root).toMatchObject({ type: 'string', default: '', required: false })
|
||||
expect(job.env.PACKAGE_BUILD_ROOT).toBe('${{ inputs.build_root }}')
|
||||
const selected = job.steps.find(step => step.id === 'build_workspace')!
|
||||
expect(selected.name).toBe('Select packaging workspace')
|
||||
expect(selected.env).toEqual({ PACKAGE_SOURCE_SHA: '${{ steps.checkout.outputs.commit }}' })
|
||||
expect(selected['working-directory']).toBeUndefined()
|
||||
expect(selected.run).toContain('if (!$env:PACKAGE_BUILD_ROOT)')
|
||||
expect(selected.run).toContain('"directory=$env:GITHUB_WORKSPACE" >> $env:GITHUB_OUTPUT')
|
||||
expect(selected.run).toContain('} else {')
|
||||
expect(selected.run).toContain('node apps/desktop/scripts/windows-build-workspace.mjs')
|
||||
expect(selected.run).toContain('exit $LASTEXITCODE')
|
||||
const index = job.steps.indexOf(selected)
|
||||
expect(index).toBeGreaterThan(job.steps.findIndex(step => step.uses?.startsWith('actions/setup-node@')))
|
||||
expect(index).toBeLessThan(job.steps.findIndex(step => step.name === 'Configure persistent pnpm store'))
|
||||
const consumers = [
|
||||
'Install locked dependencies',
|
||||
'Validate preset and resolve build version',
|
||||
'Check packaging configuration and toolchain',
|
||||
'Build installer and verify packaged runtime',
|
||||
'Retain complete package outside the checkout',
|
||||
]
|
||||
for (const name of consumers) {
|
||||
const step = job.steps.find(entry => entry.name === name)!
|
||||
expect(step['working-directory']).toBe('${{ steps.build_workspace.outputs.directory }}')
|
||||
expect(job.steps.indexOf(step)).toBeGreaterThan(index)
|
||||
}
|
||||
expect(job.steps.filter(step => step['working-directory'] === '${{ steps.build_workspace.outputs.directory }}'))
|
||||
.toHaveLength(consumers.length)
|
||||
const store = job.steps.find(step => step.name === 'Configure persistent pnpm store')!
|
||||
expect(store.run).toContain('[IO.Path]::GetPathRoot($env:GITHUB_WORKSPACE)')
|
||||
expect(store['working-directory']).toBeUndefined()
|
||||
const cleanup = job.steps.at(-1)!
|
||||
expect(cleanup).toMatchObject({
|
||||
name: 'Remove isolated packaging workspace',
|
||||
if: "success() && inputs.build_root != ''",
|
||||
env: { PACKAGE_SOURCE_SHA: '${{ steps.checkout.outputs.commit }}' },
|
||||
run: 'node apps/desktop/scripts/windows-build-workspace.mjs --remove',
|
||||
})
|
||||
expect(cleanup['working-directory']).toBeUndefined()
|
||||
for (const upload of job.steps.filter(step => step.uses?.startsWith('actions/upload-artifact@'))) {
|
||||
expect(job.steps.indexOf(upload)).toBeLessThan(job.steps.indexOf(cleanup))
|
||||
}
|
||||
})
|
||||
|
||||
it.skipIf(process.platform !== 'win32')('keeps the dependency store stable across run-specific pnpm homes', () => {
|
||||
const directory = mkdtempSync(resolve(tmpdir(), 'windows-package-store-'))
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user