build: embed pac catalog in application bundles

This commit is contained in:
reaper
2026-08-14 17:59:18 -05:00
parent 2abc625f00
commit d1fa7825fb
6 changed files with 78 additions and 5 deletions
+4 -2
View File
@@ -13,6 +13,7 @@
- Package manifests are validated for identity, version, category, dependencies, conflicts, settings, Core/API compatibility, project contributions, and safe relative entry paths. - Package manifests are validated for identity, version, category, dependencies, conflicts, settings, Core/API compatibility, project contributions, and safe relative entry paths.
- Native `.pac` files are ZIP-compressed Aurora packages with `manifest.json` at archive root. - Native `.pac` files are ZIP-compressed Aurora packages with `manifest.json` at archive root.
- A `.pac` placed beside the AppImage or in its `modules` folder is discovered automatically and appears in the Modules screen. - A `.pac` placed beside the AppImage or in its `modules` folder is discovered automatically and appears in the Modules screen.
- Packaged applications also carry a generated `.pac` catalog in their application resources. The macOS DMG layout exposes the same catalog as an **Aurora Modules** folder while the copied `.app` retains its own embedded catalog after the DMG is ejected.
- Linux packages use Aurora's 512×512 application icon and a synchronized `aurora-dockside` desktop filename, executable name, icon name, and `StartupWMClass`. - Linux packages use Aurora's 512×512 application icon and a synchronized `aurora-dockside` desktop filename, executable name, icon name, and `StartupWMClass`.
- `.pac` inspection rejects encrypted entries, symbolic links, path traversal, absolute/drive paths, excessive entry counts, and expanded archives larger than 256 MiB before extraction. - `.pac` inspection rejects encrypted entries, symbolic links, path traversal, absolute/drive paths, excessive entry counts, and expanded archives larger than 256 MiB before extraction.
- Installation uses staging plus rollback-safe replacement, so a failed update preserves the currently installed module. - Installation uses staging plus rollback-safe replacement, so a failed update preserves the currently installed module.
@@ -45,7 +46,7 @@ npm run build
npx electron-builder --linux AppImage npx electron-builder --linux AppImage
``` ```
Automated result: 6 test files and 26 tests passed. Coverage includes: Automated result: 6 test files and 27 tests passed. Coverage includes:
- Empty registry - Empty registry
- Available local packages - Available local packages
@@ -78,7 +79,7 @@ Live project smoke result for project `24`:
SHA-256: SHA-256:
```text ```text
faafa9555072e8de98db1ee65cf329804c0eb37d44cd3bf4e6b50c5216f9f6e6 aurora-dockside-2.0.0-alpha.24.AppImage ad679c904204b7f8db52b209231a08eea6ba1baf34dd3f24eae90ffcdfae7e56 aurora-dockside-2.0.0-alpha.24.AppImage
d2165af4b75ab888c6d35a750a449205123866231ba5e98e1cbe9bcaa8738f46 aurora-module-wordpress-1.2.0.pac d2165af4b75ab888c6d35a750a449205123866231ba5e98e1cbe9bcaa8738f46 aurora-module-wordpress-1.2.0.pac
``` ```
@@ -86,3 +87,4 @@ d2165af4b75ab888c6d35a750a449205123866231ba5e98e1cbe9bcaa8738f46 aurora-module-
- Clean-registry install/remove behavior was exercised through the real registry implementation in automated temporary-directory tests. The live GUI smoke used the already installed local WordPress package and project `24`. - Clean-registry install/remove behavior was exercised through the real registry implementation in automated temporary-directory tests. The live GUI smoke used the already installed local WordPress package and project `24`.
- AppImage systems without working FUSE can use `--appimage-extract` and launch `squashfs-root/AppRun --no-sandbox`. - AppImage systems without working FUSE can use `--appimage-extract` and launch `squashfs-root/AppRun --no-sandbox`.
- The embedded catalog and DMG layout are configured and the identical Linux application-resource layout was verified. A final signed/notarized `.app` and DMG must be built and inspected on macOS or a macOS CI runner.
+18
View File
@@ -1,5 +1,6 @@
appId: com.aurora-dockside.app appId: com.aurora-dockside.app
productName: Aurora Dockside productName: Aurora Dockside
beforePack: scripts/package-modules.cjs
directories: directories:
buildResources: build buildResources: build
files: files:
@@ -11,6 +12,11 @@ files:
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}' - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
asarUnpack: asarUnpack:
- resources/** - resources/**
extraResources:
- from: dist/module-catalog
to: module-catalog
filter:
- '*.pac'
win: win:
executableName: aurora-dockside executableName: aurora-dockside
nsis: nsis:
@@ -23,6 +29,18 @@ mac:
notarize: false notarize: false
dmg: dmg:
artifactName: ${name}-${version}.${ext} artifactName: ${name}-${version}.${ext}
contents:
- x: 130
y: 220
- x: 410
y: 220
type: link
path: /Applications
- x: 270
y: 110
type: dir
name: Aurora Modules
path: dist/module-catalog
linux: linux:
executableName: aurora-dockside executableName: aurora-dockside
icon: resources/icon.png icon: resources/icon.png
+3 -2
View File
@@ -19,11 +19,12 @@
"start": "electron-vite preview", "start": "electron-vite preview",
"dev": "electron-vite dev", "dev": "electron-vite dev",
"build": "npm run typecheck && electron-vite build", "build": "npm run typecheck && electron-vite build",
"build:modules": "node scripts/package-modules.cjs",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir", "build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win", "build:win": "npm run build && electron-builder --win",
"build:mac": "electron-vite build && electron-builder --mac", "build:mac": "npm run build && electron-builder --mac",
"build:linux": "electron-vite build && electron-builder --linux", "build:linux": "npm run build && electron-builder --linux",
"test": "vitest", "test": "vitest",
"test:run": "vitest run", "test:run": "vitest run",
"test:coverage": "vitest run --coverage", "test:coverage": "vitest run --coverage",
+34
View File
@@ -0,0 +1,34 @@
'use strict'
const { mkdirSync, readFileSync, readdirSync, rmSync } = require('fs')
const { join, resolve } = require('path')
const { spawnSync } = require('child_process')
function packageModules() {
const projectRoot = resolve(__dirname, '..')
const packagesRoot = join(projectRoot, 'packages')
const catalogRoot = join(projectRoot, 'dist', 'module-catalog')
mkdirSync(catalogRoot, { recursive: true })
for (const entry of readdirSync(packagesRoot, { withFileTypes: true })) {
if (!entry.isDirectory()) continue
const source = join(packagesRoot, entry.name)
let manifest
try {
manifest = JSON.parse(readFileSync(join(source, 'manifest.json'), 'utf8'))
} catch {
continue
}
if (!manifest.id || !manifest.version) throw new Error(`Invalid module manifest in ${source}`)
const output = join(catalogRoot, `${manifest.id}-${manifest.version}.pac`)
rmSync(output, { force: true })
const result = spawnSync('zip', ['-qr', output, '.'], { cwd: source, stdio: 'inherit' })
if (result.error) throw result.error
if (result.status !== 0) throw new Error(`zip failed for ${manifest.id} with exit code ${result.status}`)
process.stdout.write(` • packaged module ${manifest.id}@${manifest.version}${output}\n`)
}
}
module.exports = packageModules
if (require.main === module) packageModules()
+15
View File
@@ -51,6 +51,21 @@ describe('external module registry', () => {
expect((await getAvailableModulePackages()).map((item) => item.manifest.id)).toContain('sample-app') expect((await getAvailableModulePackages()).map((item) => item.manifest.id)).toContain('sample-app')
delete process.env.APPIMAGE delete process.env.APPIMAGE
}) })
it('discovers a .pac embedded in the packaged application resources', async () => {
const resources = await temp('aurora-resources-')
const catalog = join(resources, 'module-catalog')
await mkdir(catalog)
const pac = await pacFile()
await import('fs/promises').then(({ copyFile }) => copyFile(pac, join(catalog, 'sample-app.pac')))
const previous = Object.getOwnPropertyDescriptor(process, 'resourcesPath')
Object.defineProperty(process, 'resourcesPath', { value: resources, configurable: true })
try {
expect((await getAvailableModulePackages()).map((item) => item.manifest.id)).toContain('sample-app')
} finally {
if (previous) Object.defineProperty(process, 'resourcesPath', previous)
else Reflect.deleteProperty(process, 'resourcesPath')
}
})
it('installs a valid local package and refreshes after install', async () => { it('installs a valid local package and refreshes after install', async () => {
const userData = await temp('aurora-user-'); const source = await packageDir() const userData = await temp('aurora-user-'); const source = await packageDir()
await installModulePackage(source, userData) await installModulePackage(source, userData)
+4 -1
View File
@@ -165,7 +165,10 @@ function catalogDirectories(): string[] {
const besideImage = process.env.APPIMAGE const besideImage = process.env.APPIMAGE
? [dirname(process.env.APPIMAGE), join(dirname(process.env.APPIMAGE), 'modules')] ? [dirname(process.env.APPIMAGE), join(dirname(process.env.APPIMAGE), 'modules')]
: [] : []
return [...configured, ...besideImage, join(process.cwd(), 'modules'), join(process.cwd(), 'packages'), join(app.getAppPath(), 'packages')] const embedded = typeof process.resourcesPath === 'string'
? [join(process.resourcesPath, 'module-catalog')]
: []
return [...configured, ...besideImage, ...embedded, join(process.cwd(), 'modules'), join(process.cwd(), 'packages'), join(app.getAppPath(), 'packages')]
} }
export async function getAvailableModulePackages(): Promise<AuroraAvailableModule[]> { export async function getAvailableModulePackages(): Promise<AuroraAvailableModule[]> {