smoke-client-desktop.mjs 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. // Verification for the client-only macOS desktop build.
  2. // Structural checks always run; the GUI smoke runs when Playwright can drive
  3. // the packaged Electron (skipped automatically when launch is unsupported,
  4. // e.g. the Playwright/Electron flag incompatibility on this machine).
  5. // Run: node --test scripts/smoke-client-desktop.mjs
  6. import { test } from 'node:test'
  7. import assert from 'node:assert/strict'
  8. import { existsSync, readFileSync, statSync, readdirSync } from 'node:fs'
  9. import { join, resolve } from 'node:path'
  10. import { execFileSync } from 'node:child_process'
  11. const root = resolve(import.meta.dirname, '..')
  12. const staging = join(root, '.desktop-build-client')
  13. const appRoot = join(root, 'desktop-release-client/mac-arm64/夭夭.app')
  14. test('client staging omits the local server runtime but keeps client pieces', () => {
  15. assert.equal(existsSync(join(staging, 'client-only.marker')), true, 'marker present')
  16. for (const forbidden of ['server.mjs', 'runner.mjs', 'web-service', 'ui', 'node_modules', 'node'])
  17. assert.equal(existsSync(join(staging, forbidden)), false, `${forbidden} must not ship`)
  18. for (const needed of ['github-release.mjs', 'electron-updater.cjs', 'keychain-helper', 'computer-helper', 'release.json', 'build-info.json'])
  19. assert.equal(existsSync(join(staging, needed)), true, `${needed} must ship`)
  20. for (const page of ['boot.html', 'boot.js', 'boot.css', 'preload.cjs', 'main.mjs', 'platform.mjs', 'remote-login.mjs', 'host-manager.mjs'])
  21. assert.equal(existsSync(join(staging, 'shell', page)), true, `${page} must ship`)
  22. })
  23. test('packaged app carries the marker and the client policy code', () => {
  24. assert.equal(existsSync(appRoot), true, 'packaged app exists (run desktop:pack:client first)')
  25. const runtime = join(appRoot, 'Contents/Resources/runtime')
  26. assert.equal(existsSync(join(runtime, 'client-only.marker')), true, 'marker inside Resources/runtime')
  27. for (const forbidden of ['server.mjs', 'runner.mjs', 'web-service', 'ui', 'node'])
  28. assert.equal(existsSync(join(runtime, forbidden)), false, `${forbidden} must not be packaged`)
  29. const asar = readFileSync(join(appRoot, 'Contents/Resources/app.asar'), 'latin1')
  30. assert.ok(asar.includes('clientOnlyRuntime'), 'main.mjs inside asar knows the client policy')
  31. assert.ok(!asar.includes('DesktopServiceManager(') || asar.includes('clientOnlyRuntime'), 'policy guard present')
  32. // The runtime detection must resolve for the packaged layout.
  33. const probe = `import { clientOnlyRuntime } from ${JSON.stringify('file://' + join(root, 'desktop/platform.mjs'))}
  34. console.log(clientOnlyRuntime(${JSON.stringify(runtime)}) ? 'client' : 'full')`
  35. const result = execFileSync(process.execPath, ['--input-type=module', '-e', probe], { encoding: 'utf8' }).trim()
  36. assert.equal(result, 'client')
  37. })
  38. test('packaged app size stays close to the bare Electron runtime', () => {
  39. assert.equal(existsSync(appRoot), true, 'packaged app exists')
  40. let total = 0
  41. const walk = directory => { for (const entry of readdirSync(directory, { withFileTypes: true })) { const full = join(directory, entry.name); if (entry.isDirectory()) walk(full); else total += statSync(full).size } }
  42. walk(join(appRoot, 'Contents/Resources'))
  43. const resourcesMB = total / 1024 / 1024
  44. assert.ok(resourcesMB < 25, `Resources should stay small, got ${Math.round(resourcesMB)}MB`)
  45. })