mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-08-28 02:44:59 +00:00
build(desktop): isolate local release staging
* build(desktop): isolate local release staging * Require Apple Silicon for local macOS smoke --------- Co-authored-by: bradgroux <brad@digitalmeld.io>
This commit is contained in:
parent
ccdd55ce0b
commit
8b0ec6836b
6 changed files with 191 additions and 14 deletions
|
|
@ -14,15 +14,15 @@
|
|||
"dev:fresh": "VERITAS_DESKTOP_PROFILE=fresh electron-vite dev",
|
||||
"build": "electron-vite build",
|
||||
"package:prepare": "node ../scripts/prepare-desktop-release.mjs",
|
||||
"package:mac:dir": "node ../scripts/prepare-desktop-release.mjs && node ./node_modules/electron-builder/cli.js --mac dir --publish never --config.mac.identity=null --config.mac.notarize=false",
|
||||
"package:mac:unsigned": "node ../scripts/prepare-desktop-release.mjs && node ./node_modules/electron-builder/cli.js --mac dmg zip --publish never --config.mac.identity=null --config.mac.notarize=false",
|
||||
"package:linux:dir": "node ../scripts/prepare-desktop-release.mjs && node ./node_modules/electron-builder/cli.js --linux dir --x64 --publish never",
|
||||
"package:linux:unsigned": "node ../scripts/prepare-desktop-release.mjs && node ./node_modules/electron-builder/cli.js --linux AppImage deb rpm --x64 --publish never",
|
||||
"package:win:dir": "node ../scripts/prepare-desktop-release.mjs && node ./node_modules/electron-builder/cli.js --win dir --x64 --publish never",
|
||||
"package:win:unsigned": "node ../scripts/prepare-desktop-release.mjs && node ./node_modules/electron-builder/cli.js --win nsis zip --x64 --publish never",
|
||||
"release:mac": "node ../scripts/prepare-desktop-release.mjs && node ./node_modules/electron-builder/cli.js --mac dmg zip --publish always",
|
||||
"release:linux": "node ../scripts/prepare-desktop-release.mjs && node ./node_modules/electron-builder/cli.js --linux AppImage deb rpm --x64 --publish always",
|
||||
"release:win": "node ../scripts/prepare-desktop-release.mjs && node ./node_modules/electron-builder/cli.js --win nsis zip --x64 --publish always",
|
||||
"package:mac:dir": "node ../scripts/prepare-desktop-release.mjs && node ../scripts/run-desktop-builder.mjs --mac dir --publish never --config.mac.identity=null --config.mac.notarize=false",
|
||||
"package:mac:unsigned": "node ../scripts/prepare-desktop-release.mjs && node ../scripts/run-desktop-builder.mjs --mac dmg zip --publish never --config.mac.identity=null --config.mac.notarize=false",
|
||||
"package:linux:dir": "node ../scripts/prepare-desktop-release.mjs && node ../scripts/run-desktop-builder.mjs --linux dir --x64 --publish never",
|
||||
"package:linux:unsigned": "node ../scripts/prepare-desktop-release.mjs && node ../scripts/run-desktop-builder.mjs --linux AppImage deb rpm --x64 --publish never",
|
||||
"package:win:dir": "node ../scripts/prepare-desktop-release.mjs && node ../scripts/run-desktop-builder.mjs --win dir --x64 --publish never",
|
||||
"package:win:unsigned": "node ../scripts/prepare-desktop-release.mjs && node ../scripts/run-desktop-builder.mjs --win nsis zip --x64 --publish never",
|
||||
"release:mac": "node ../scripts/prepare-desktop-release.mjs && node ../scripts/run-desktop-builder.mjs --mac dmg zip --publish always",
|
||||
"release:linux": "node ../scripts/prepare-desktop-release.mjs && node ../scripts/run-desktop-builder.mjs --linux AppImage deb rpm --x64 --publish always",
|
||||
"release:win": "node ../scripts/prepare-desktop-release.mjs && node ../scripts/run-desktop-builder.mjs --win nsis zip --x64 --publish always",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src electron.vite.config.ts --ext .ts",
|
||||
"test": "vitest run --config vitest.config.ts",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ pnpm desktop:package:windows:unsigned
|
|||
pnpm desktop:release:mac
|
||||
pnpm desktop:release:linux
|
||||
pnpm desktop:release:windows
|
||||
pnpm desktop:smoke:mac:local
|
||||
```
|
||||
|
||||
`desktop:package:mac:dir` creates an unpacked local app for fast inspection.
|
||||
|
|
@ -40,6 +41,13 @@ The package step builds the workspace, stages the production server runtime in
|
|||
`desktop/.desktop-release/web`, and writes artifacts to `desktop/release/`.
|
||||
Both staging and release directories are ignored by git.
|
||||
|
||||
`pnpm desktop:smoke:mac:local` runs the macOS directory packaging path and then
|
||||
asserts that root dev tooling such as Prettier, ESLint, and the desktop
|
||||
`electron-builder` dependency still exist. Use it after package-script changes
|
||||
that touch production staging or pnpm deploy behavior. The staging deploy runs
|
||||
from an isolated temporary workspace so `pnpm deploy --prod` cannot rewrite the
|
||||
live repository install into a production-only dependency state.
|
||||
|
||||
## GitHub Workflows
|
||||
|
||||
`Desktop Artifacts` runs on desktop/server/web/shared changes and on manual
|
||||
|
|
@ -161,6 +169,8 @@ policy is tracked in
|
|||
- Update `CHANGELOG.md`.
|
||||
- Run `pnpm typecheck`, `pnpm lint:budget`, `pnpm build`, and
|
||||
`pnpm test:unit`.
|
||||
- Run `pnpm desktop:smoke:mac:local` to verify local packaging does not prune
|
||||
root dev tooling.
|
||||
- Run `pnpm desktop:package:mac:unsigned` and inspect artifact names.
|
||||
- Run `pnpm desktop:package:linux:unsigned` on Linux or the
|
||||
`Desktop Artifacts` Linux job and inspect preview artifact names. This is not
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@
|
|||
"qmd:refresh": "bash scripts/qmd/refresh-veritas-qmd.sh",
|
||||
"seed": "bash scripts/seed.sh",
|
||||
"prepare": "node scripts/prepare-husky.mjs",
|
||||
"demo": "docker compose -f demo/docker-compose.demo.yml up --build"
|
||||
"demo": "docker compose -f demo/docker-compose.demo.yml up --build",
|
||||
"desktop:smoke:mac:local": "node scripts/check-desktop-local-packaging-smoke.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.38.0",
|
||||
|
|
|
|||
94
scripts/check-desktop-local-packaging-smoke.mjs
Executable file
94
scripts/check-desktop-local-packaging-smoke.mjs
Executable file
|
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env node
|
||||
import { constants } from 'node:fs';
|
||||
import { access, stat } from 'node:fs/promises';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const desktopDir = path.join(rootDir, 'desktop');
|
||||
const stagingDir = path.join(desktopDir, '.desktop-release');
|
||||
|
||||
async function assertExists(label, targetPath) {
|
||||
try {
|
||||
await access(targetPath, constants.F_OK);
|
||||
} catch {
|
||||
throw new Error(`${label} is missing: ${path.relative(rootDir, targetPath)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function snapshotPath(label, targetPath) {
|
||||
await assertExists(label, targetPath);
|
||||
const info = await stat(targetPath);
|
||||
return { label, targetPath, mtimeMs: info.mtimeMs };
|
||||
}
|
||||
|
||||
function run(command, args) {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: rootDir,
|
||||
env: {
|
||||
...process.env,
|
||||
CI: 'true',
|
||||
npm_config_confirm_modules_purge: 'false',
|
||||
npm_config_confirmModulesPurge: 'false',
|
||||
NPM_CONFIG_CONFIRM_MODULES_PURGE: 'false',
|
||||
},
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`${command} ${args.join(' ')} failed with exit code ${result.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function assertRootDevToolingStillPresent(beforeSnapshots) {
|
||||
for (const snapshot of beforeSnapshots) {
|
||||
await assertExists(snapshot.label, snapshot.targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (process.platform !== 'darwin' || process.arch !== 'arm64') {
|
||||
throw new Error(
|
||||
'desktop:smoke:mac:local must run on Apple Silicon macOS because it exercises macOS arm64 packaging.'
|
||||
);
|
||||
}
|
||||
|
||||
const beforeSnapshots = await Promise.all([
|
||||
snapshotPath('Root node_modules', path.join(rootDir, 'node_modules')),
|
||||
snapshotPath('Root prettier binary', path.join(rootDir, 'node_modules/.bin/prettier')),
|
||||
snapshotPath('Root eslint binary', path.join(rootDir, 'node_modules/.bin/eslint')),
|
||||
snapshotPath(
|
||||
'Desktop electron-builder package',
|
||||
path.join(desktopDir, 'node_modules/electron-builder/package.json')
|
||||
),
|
||||
]);
|
||||
|
||||
run('pnpm', ['build']);
|
||||
run('pnpm', ['--filter', '@veritas-kanban/desktop', 'package:mac:dir']);
|
||||
|
||||
await assertRootDevToolingStillPresent(beforeSnapshots);
|
||||
await assertExists('Desktop staging server entry', path.join(stagingDir, 'server/dist/index.js'));
|
||||
await assertExists(
|
||||
'Desktop staging server dependencies',
|
||||
path.join(stagingDir, 'server/node_modules')
|
||||
);
|
||||
await assertExists('Desktop staging web app', path.join(stagingDir, 'web/dist/index.html'));
|
||||
await assertExists(
|
||||
'Unpacked macOS app',
|
||||
path.join(desktopDir, 'release/mac-arm64/Veritas Kanban.app')
|
||||
);
|
||||
|
||||
console.log(
|
||||
'Desktop local packaging smoke passed: root dev tooling survived isolated production staging.'
|
||||
);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
#!/usr/bin/env node
|
||||
import { constants } from 'node:fs';
|
||||
import { access, cp, mkdir, rm } from 'node:fs/promises';
|
||||
import { access, cp, mkdir, mkdtemp, rm } from 'node:fs/promises';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
|
|
@ -34,9 +35,9 @@ function releaseChildEnv() {
|
|||
};
|
||||
}
|
||||
|
||||
function run(command, args) {
|
||||
function run(command, args, options = {}) {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: rootDir,
|
||||
cwd: options.cwd ?? rootDir,
|
||||
encoding: 'utf8',
|
||||
env: releaseChildEnv(),
|
||||
shell: process.platform === 'win32',
|
||||
|
|
@ -83,6 +84,41 @@ async function materializeServerSelfReference() {
|
|||
});
|
||||
}
|
||||
|
||||
async function copyWorkspaceForDeploy(tempRoot) {
|
||||
const entries = [
|
||||
'pnpm-lock.yaml',
|
||||
'pnpm-workspace.yaml',
|
||||
'package.json',
|
||||
'scripts/prepare-husky.mjs',
|
||||
'server/package.json',
|
||||
'server/dist',
|
||||
'shared/package.json',
|
||||
'shared/dist',
|
||||
];
|
||||
|
||||
for (const entry of entries) {
|
||||
const source = path.join(rootDir, entry);
|
||||
const target = path.join(tempRoot, entry);
|
||||
await mkdir(path.dirname(target), { recursive: true });
|
||||
await cp(source, target, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function deployServerRuntime() {
|
||||
const deployRoot = await mkdtemp(path.join(os.tmpdir(), 'veritas-desktop-deploy-'));
|
||||
|
||||
try {
|
||||
await copyWorkspaceForDeploy(deployRoot);
|
||||
run(
|
||||
'pnpm',
|
||||
['--filter', '@veritas-kanban/server', 'deploy', '--legacy', '--prod', serverStage],
|
||||
{ cwd: deployRoot }
|
||||
);
|
||||
} finally {
|
||||
await rm(deployRoot, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await assertExists('Server build output', path.join(rootDir, 'server/dist/index.js'));
|
||||
await assertExists('Web build output', path.join(rootDir, 'web/dist/index.html'));
|
||||
|
|
@ -90,7 +126,7 @@ async function main() {
|
|||
await rm(stagingDir, { recursive: true, force: true });
|
||||
await mkdir(stagingDir, { recursive: true });
|
||||
|
||||
run('pnpm', ['--filter', '@veritas-kanban/server', 'deploy', '--legacy', '--prod', serverStage]);
|
||||
await deployServerRuntime();
|
||||
await pruneServerDeploy();
|
||||
await materializeServerSelfReference();
|
||||
|
||||
|
|
|
|||
36
scripts/run-desktop-builder.mjs
Executable file
36
scripts/run-desktop-builder.mjs
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env node
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const desktopDir = path.join(rootDir, 'desktop');
|
||||
const requireFromDesktop = createRequire(path.join(desktopDir, 'package.json'));
|
||||
|
||||
function resolveElectronBuilderCli() {
|
||||
try {
|
||||
return requireFromDesktop.resolve('electron-builder/cli.js');
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
[
|
||||
'Unable to resolve electron-builder from the desktop package dependency graph.',
|
||||
'Run `CI=true pnpm install --frozen-lockfile` from the repository root, then retry the desktop packaging command.',
|
||||
error instanceof Error ? error.message : String(error),
|
||||
].join('\n')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const cli = resolveElectronBuilderCli();
|
||||
const result = spawnSync(process.execPath, [cli, ...process.argv.slice(2)], {
|
||||
cwd: desktopDir,
|
||||
env: process.env,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
process.exit(result.status ?? 1);
|
||||
Loading…
Add table
Reference in a new issue