diff --git a/.changeset/violet-trees-help.md b/.changeset/violet-trees-help.md new file mode 100644 index 00000000..8037df96 --- /dev/null +++ b/.changeset/violet-trees-help.md @@ -0,0 +1,7 @@ +--- +'@spotlightjs/spotlight': minor +'@spotlightjs/sidecar': minor +--- + +Create a self-contained executable for Linux, macOS, and Windows for Spotlight. +Docker images now use these binaries instead of a Node build in the image. diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5477a7ef..f2bc9a04 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,13 +30,17 @@ jobs: runs-on: ubuntu-latest # For whatever reason, yaml does not like the full "meta(changelog): Update package versions" string # So we check this in two parts - if: | - (contains(github.event.head_commit.message, 'meta(changelog)') - && contains(github.event.head_commit.message, 'Update package versions')) - || inputs.npm + if: >- + ( + contains(github.event.head_commit.message, 'meta(changelog)') + && + contains(github.event.head_commit.message, 'Update package versions') + ) || inputs.npm steps: - name: Checkout Repo uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Node uses: actions/setup-node@v4 @@ -57,6 +61,28 @@ jobs: publish: pnpm changeset:publish createGithubReleases: true + - name: Store standalone spotlight binary + uses: actions/upload-artifact@v4 + with: + name: spotlight + if-no-files-found: error + path: packages/spotlight/dist/spotlight-* + + - name: Gets latest created release info + id: latest_release_info + uses: gregziegan/fetch-latest-release@v2.0.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload binaries to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file_glob: true + file: packages/spotlight/dist/spotlight-* + tag: ${{ steps.latest_release_info.outputs.tag_name }} + make_latest: false + docker: name: Docker Image needs: npm @@ -66,9 +92,6 @@ jobs: && (inputs.docker || github.event_name == 'push') && (needs.npm.result == 'success' || needs.npm.result == 'skipped') steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - name: Login to GHCR uses: docker/login-action@v3 with: @@ -76,21 +99,13 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: docker/setup-qemu-action@v3 - - uses: docker/setup-buildx-action@v3 - - name: Build and Push Docker Image - uses: docker/build-push-action@v5 - with: - context: . - cache-from: type=gha,scope=prod - no-cache: ${{ inputs.nocache == true }} - platforms: linux/amd64,linux/arm64 - push: true - tags: | - ghcr.io/getsentry/spotlight:latest - ghcr.io/getsentry/spotlight:${{ github.sha }} + - name: Push Docker Image as latest + run: >- + docker buildx imagetools create + --tag ghcr.io/getsentry/spotlight:latest + ghcr.io/getsentry/spotlight:${{ github.sha }} - name: Summarize run: | @@ -104,14 +119,12 @@ jobs: !cancelled() && (inputs.electron || github.event_name == 'push') && (needs.npm.result == 'success' || needs.npm.result == 'skipped') - # strategy: - # fail-fast: false - # matrix: - # platform: [x64, arm64] steps: - name: Checkout Repo uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Node uses: actions/setup-node@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f2333551..37bdc072 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,6 +5,11 @@ on: branches: [main] pull_request: +defaults: + run: + shell: bash + + env: BUILD_CACHE_KEY: ${{ github.event.inputs.commit || github.sha }} CACHED_BUILD_PATHS: | @@ -29,47 +34,96 @@ jobs: node-version-file: 'package.json' cache: 'pnpm' - - name: Setup dependencies + - name: Setup NPM dependencies run: pnpm install + - name: Setup Codesign Dependencies + env: + APPLE_CERT_DATA: ${{ secrets.CSC_LINK }} + APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} + run: | + curl -L 'https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.28.0/apple-codesign-0.28.0-x86_64-unknown-linux-musl.tar.gz' | tar -xz --strip-components=1 + mv rcodesign /usr/local/bin/rcodesign + # Export certs + echo "$APPLE_CERT_DATA" | base64 --decode > /tmp/certs.p12 + echo 'APPLE_CERT_PATH=/tmp/certs.p12' >> $GITHUB_ENV + echo "$APPLE_API_KEY" | base64 -d > /tmp/apple_key.json + echo 'APPLE_API_KEY_PATH=/tmp/apple_key.json' >> $GITHUB_ENV + - name: Build packages + env: + BUILD_PLATFORMS: 'linux-x64,linux-arm64,win-x64,darwin-x64,darwin-arm64' + APPLE_CERT_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.TEAMID }} run: pnpm build + - name: Smoke test + env: + SPOTLIGHT_BINARY: ${{ github.workspace }}/packages/spotlight/dist/spotlight-${{ runner.os }}-${{ runner.arch }} + run: | + # Lowercase the binary name because `runner.os` and `runner.arch` are uppercase :facepalm: + SPOTLIGHT_BINARY=$(echo "$SPOTLIGHT_BINARY" | tr '[:upper:]' '[:lower:]') + [ -f "$SPOTLIGHT_BINARY" ] + $SPOTLIGHT_BINARY & + SPOTLIGHT_PID=$! + curl -sf --retry 3 --retry-all-errors -o /dev/null 'http://localhost:8969/' && echo "Spotlight ran successfully" + kill -2 $SPOTLIGHT_PID + + - name: Store Spotlight CJS + uses: actions/upload-artifact@v4 + with: + name: spotlight-cjs + if-no-files-found: error + path: | + packages/spotlight/dist/spotlight.cjs + packages/spotlight/dist/overlay/ + + - name: Store standalone spotlight binary + uses: actions/upload-artifact@v4 + with: + name: spotlight + if-no-files-found: error + path: packages/spotlight/dist/spotlight-* + - name: Update build cache uses: actions/cache@v4 with: path: ${{ env.CACHED_BUILD_PATHS }} key: ${{ env.BUILD_CACHE_KEY }} - test-docker: - name: Docker Test - runs-on: ubuntu-latest - needs: build - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 + - name: Rename x64 to amd64 + # This is because Node ecosystem uses x64, but Docker uses amd64 :shrug: + run: mv packages/spotlight/dist/spotlight-linux-x64 packages/spotlight/dist/spotlight-linux-amd64 - - name: Get changed files - id: changed-files-yaml - uses: tj-actions/changed-files@v41 + - name: Login to GHCR + uses: docker/login-action@v3 with: - files_yaml: | - docker: - - Dockerfile + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup QEMU for cross-compilation + uses: docker/setup-qemu-action@v3 - name: Configure Docker Context - if: ${{ inputs.force || steps.changed-files-yaml.outputs.docker_any_changed == 'true' }} uses: docker/setup-buildx-action@v3 - name: Build Docker Image - if: ${{ inputs.force || steps.changed-files-yaml.outputs.docker_any_changed == 'true' }} uses: docker/build-push-action@v5 with: context: . cache-from: type=gha,scope=prod cache-to: type=gha,mode=max,scope=prod no-cache: ${{ inputs.nocache == 'true' }} + platforms: linux/amd64,linux/arm64 + push: true + tags: ghcr.io/getsentry/spotlight:${{ github.sha }} + + - name: Test Docker Image + run: | + docker run --rm -d -p 8969:8969 ghcr.io/getsentry/spotlight:${{ github.sha }} + curl -sf --retry 3 --retry-all-errors -o /dev/null 'http://localhost:8969/' && echo "Spotlight ran successfully" + test-unit: name: Unit Tests @@ -111,7 +165,7 @@ jobs: report_paths: '**/junit.xml' - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} @@ -119,6 +173,9 @@ jobs: name: E2E Tests needs: build runs-on: ubuntu-latest + strategy: + matrix: + node_version: [18, 20, 22] steps: - uses: actions/checkout@v4 with: @@ -130,7 +187,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version-file: 'package.json' + node-version: ${{ matrix.version }} cache: 'pnpm' - name: Setup dependencies diff --git a/.vscode/settings.json b/.vscode/settings.json index 3b5afdb2..17c6d7ee 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,23 +1,31 @@ { "editor.formatOnSave": true, "cSpell.words": [ + "APPLEID", + "APPLEIDPASS", "Astro", "Astro's", "astrojs", + "codesign", "contextlines", "Endcaps", "fontsource", "getsentry", "iife", "nextjs", + "notarytool", "outro", "pageload", + "postject", "spotlightjs", "svgr", "tailwindcss", + "TEAMID", "treeshake", "ttfb", + "unsign", "uuidv", - "webvitals" + "webvitals", + "xcrun" ] } diff --git a/Dockerfile b/Dockerfile index 2ddb0f09..927cefa0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,6 @@ -FROM node:lts-bullseye-slim AS base -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable - -FROM base AS build +FROM debian:bookworm-slim +ARG TARGETARCH WORKDIR /app -COPY . /app -# https://github.com/pnpm/pnpm/issues/6295 -RUN echo "dedupe-peer-dependents=false" > .npmrc -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile -RUN pnpm run --filter="./packages/sidecar" build && \ - pnpm run --filter="./packages/overlay" build -RUN pnpm run --filter="./packages/spotlight" build -RUN pnpm deploy --filter="./packages/spotlight" --prod /deploy/spotlight +COPY --chmod=555 packages/spotlight/dist/spotlight-linux-$TARGETARCH /app/spotlight -FROM base as spotlight -# FROM gcr.io/distroless/nodejs20-debian12 as sidecar -COPY --from=build /deploy/spotlight /app -WORKDIR /app -EXPOSE 8969 -ENTRYPOINT [ "./bin/run.js" ] +ENTRYPOINT ["/app/spotlight"] diff --git a/package.json b/package.json index f103c4c3..657bf231 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ }, "packageManager": "pnpm@9.13.0", "volta": { - "node": "20.10.0", + "node": "22.11.0", "pnpm": "9.13.0" } } diff --git a/packages/sidecar/src/main.ts b/packages/sidecar/src/main.ts index fed27fd3..608d59aa 100644 --- a/packages/sidecar/src/main.ts +++ b/packages/sidecar/src/main.ts @@ -1,5 +1,5 @@ import launchEditor from 'launch-editor'; -import { createWriteStream, readFile } from 'node:fs'; +import { createWriteStream, readFileSync } from 'node:fs'; import { createServer, get, type IncomingMessage, type Server, type ServerResponse } from 'node:http'; import { extname, join, resolve } from 'node:path'; import { createGunzip, createInflate } from 'node:zlib'; @@ -32,6 +32,8 @@ type SideCarOptions = { */ basePath?: string; + filesToServe?: Record; + /** * More verbose logging. */ @@ -101,7 +103,7 @@ function streamRequestHandler(buffer: MessageBuffer, incomingPayload?: if ( req.method === 'GET' && req.headers.accept && - req.headers.accept == 'text/event-stream' && + req.headers.accept === 'text/event-stream' && pathname === '/stream' ) { res.writeHead(200, { @@ -115,12 +117,12 @@ function streamRequestHandler(buffer: MessageBuffer, incomingPayload?: res.write('\n'); const sub = buffer.subscribe(([payloadType, data]) => { - logger.debug(`🕊️ sending to Spotlight`); + logger.debug('🕊️ sending to Spotlight'); res.write(`event:${payloadType}\n`); // This is very important - SSE events are delimited by two newlines - data.split('\n').forEach(line => { + for (const line of data.split('\n')) { res.write(`data:${line}\n`); - }); + } res.write('\n'); }); @@ -189,12 +191,13 @@ function streamRequestHandler(buffer: MessageBuffer, incomingPayload?: }; } -function fileServer(basePath: string) { +function fileServer(filesToServe: Record) { return function serveFile(req: IncomingMessage, res: ServerResponse, pathname?: string): void { - let filePath = `.${pathname || req.url}`; - if (filePath === './') { - filePath = './src/index.html'; + let filePath = `${pathname || req.url}`; + if (filePath === '/') { + filePath = '/src/index.html'; } + filePath = filePath.slice(1); const extName = extname(filePath); let contentType = 'text/html'; @@ -210,14 +213,12 @@ function fileServer(basePath: string) { break; } - readFile(join(basePath, filePath), (error, content) => { - if (error) { - return error404(req, res); - } - + if (!Object.prototype.hasOwnProperty.call(filesToServe, filePath)) { + error404(req, res); + } else { res.writeHead(200, { 'Content-Type': contentType }); - res.end(content, 'utf-8'); - }); + res.end(filesToServe[filePath]); + } }; } @@ -288,15 +289,22 @@ function startServer( buffer: MessageBuffer, port: number, basePath?: string, + filesToServe?: Record, incomingPayload?: IncomingPayloadCallback, ): Server { + if (basePath && !filesToServe) { + filesToServe = { + '/src/index.html': readFileSync(join(basePath, 'src/index.html')), + '/assets/main.js': readFileSync(join(basePath, 'assets/main.js')), + }; + } const ROUTES: [RegExp, RequestHandler][] = [ [/^\/health$/, handleHealthRequest], [/^\/clear$/, enableCORS(handleClearRequest)], [/^\/stream$|^\/api\/\d+\/envelope\/?$/, enableCORS(streamRequestHandler(buffer, incomingPayload))], [/^\/open$/, enableCORS(openRequestHandler(basePath))], [RegExp(`^${CONTEXT_LINES_ENDPOINT}$`), enableCORS(contextLinesHandler)], - [/^.+$/, basePath != null ? enableCORS(fileServer(basePath)) : error404], + [/^.+$/, filesToServe != null ? enableCORS(fileServer(filesToServe)) : error404], ]; const server = createServer((req: IncomingMessage, res: ServerResponse) => { @@ -379,6 +387,7 @@ export function setupSidecar({ port, logger: customLogger, basePath, + filesToServe, debug, incomingPayload, }: SideCarOptions = {}): void { @@ -403,7 +412,7 @@ export function setupSidecar({ logger.info(`Sidecar is already running on port ${sidecarPort}`); } else { if (!serverInstance) { - serverInstance = startServer(buffer, sidecarPort, basePath, incomingPayload); + serverInstance = startServer(buffer, sidecarPort, basePath, filesToServe, incomingPayload); } } }); @@ -415,11 +424,10 @@ export function clearBuffer(): void { export function shutdown() { if (serverInstance) { - logger.info('Shutting down Server'); + logger.info('Shutting down server...'); serverInstance.close(); } } -process.on('SIGTERM', () => { - shutdown(); -}); +process.on('SIGINT', shutdown); +process.on('SIGTERM', shutdown); diff --git a/packages/spotlight/bin/build.js b/packages/spotlight/bin/build.js new file mode 100755 index 00000000..281c337d --- /dev/null +++ b/packages/spotlight/bin/build.js @@ -0,0 +1,144 @@ +#!/usr/bin/env node +import { execFile as execFileCb } from 'node:child_process'; +import { createWriteStream } from 'node:fs'; +import { copyFile, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { Readable } from 'node:stream'; +import { finished } from 'node:stream/promises'; +import { promisify } from 'node:util'; + +import { unsign } from 'macho-unsign'; +import { signatureSet } from 'portable-executable-signature'; +import { inject } from 'postject'; + +const execFile = promisify(execFileCb); + +const DIST_DIR = './dist'; +const ASSETS_DIR = join(DIST_DIR, 'overlay'); +const MANIFEST_NAME = 'manifest.json'; +const MANIFEST_PATH = join(ASSETS_DIR, MANIFEST_NAME); +const ENTRY_POINT_NAME = 'src/index.html'; +const SEA_CONFIG_PATH = join(DIST_DIR, 'sea-config.json'); +const SPOTLIGHT_BLOB_PATH = join(DIST_DIR, 'spotlight.blob'); +const NODE_VERSION = '22.11.0'; +const PLATFORMS = (process.env.BUILD_PLATFORMS || `${process.platform}-${process.arch}`).split(',').map(p => p.trim()); +const manifest = JSON.parse(await readFile(MANIFEST_PATH)); +const seaConfig = { + main: join(DIST_DIR, 'spotlight.cjs'), + output: SPOTLIGHT_BLOB_PATH, + disableExperimentalSEAWarning: true, + useSnapshot: false, + useCodeCache: false, // We do cross-compiling so disable this + assets: { + [MANIFEST_NAME]: MANIFEST_PATH, + [ENTRY_POINT_NAME]: join(ASSETS_DIR, ENTRY_POINT_NAME), + ...Object.fromEntries(Object.values(manifest).map(entry => [entry.file, join(ASSETS_DIR, entry.file)])), + }, +}; + +async function run(cmd, ...args) { + let output; + try { + output = await execFile(cmd, args, { encoding: 'utf8' }); + } catch (err) { + console.error(`Failed to \`run ${cmd} ${args.join(' ')}\``); + console.error(err.stdout); + console.error(err.stderr); + process.exit(err.code); + } + if (output.stdout.trim()) { + console.log(output.stdout); + } else { + console.log(`> ${[cmd, ...args].join(' ')}`); + } + return output.stdout; +} + +async function getNodeBinary(platform, targetPath = DIST_DIR) { + const suffix = platform.startsWith('win') ? 'zip' : 'tar.xz'; + const remoteArchiveName = `node-v${NODE_VERSION}-${platform}.${suffix}`; + const url = `https://nodejs.org/dist/v${NODE_VERSION}/${remoteArchiveName}`; + const resp = await fetch(url); + if (!resp.ok) throw new Error(`Failed to fetch ${url}`); + const tmpDir = await mkdtemp(join(tmpdir(), remoteArchiveName)); + const stream = createWriteStream(join(tmpDir, remoteArchiveName)); + await finished(Readable.fromWeb(resp.body).pipe(stream)); + let sourceFile; + let targetFile; + if (platform.startsWith('win')) { + await run('unzip', '-qq', stream.path, '-d', tmpDir); + sourceFile = join(tmpDir, `node-v${NODE_VERSION}-${platform}`, 'node.exe'); + targetFile = join(targetPath, `spotlight-${platform}.exe`); + const data = await readFile(sourceFile); + const unsigned = signatureSet(data, null); + await writeFile(targetFile, Buffer.from(unsigned)); + console.log('Signature removed from Win PE binary', targetFile); + } else { + await run('tar', '-xf', stream.path, '-C', tmpDir); + sourceFile = join(tmpDir, `node-v${NODE_VERSION}-${platform}`, 'bin', 'node'); + targetFile = join(targetPath, `spotlight-${platform}`); + } + + if (platform.startsWith('darwin')) { + const unsigned = unsign(await readFile(sourceFile)); + await writeFile(targetFile, Buffer.from(unsigned)); + console.log('Signature removed from macOS binary', targetFile); + } else { + await copyFile(sourceFile, targetFile); + } + + await rm(tmpDir, { recursive: true }); + return targetFile; +} + +await writeFile(SEA_CONFIG_PATH, JSON.stringify(seaConfig)); +await run(process.execPath, '--experimental-sea-config', SEA_CONFIG_PATH); +await Promise.all( + PLATFORMS.map(async platform => { + const nodeBinary = await getNodeBinary(platform); + console.log('Injecting spotlight blob into node executable...'); + await inject(nodeBinary, 'NODE_SEA_BLOB', await readFile(SPOTLIGHT_BLOB_PATH), { + sentinelFuse: 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2', + machoSegmentName: platform.startsWith('darwin') ? 'NODE_SEA' : undefined, + }); + console.log('Created executable', nodeBinary); + await run('chmod', '+x', nodeBinary); + if (platform.startsWith('darwin')) { + const { APPLE_TEAM_ID, APPLE_CERT_PATH, APPLE_CERT_PASSWORD, APPLE_API_KEY_PATH } = process.env; + if (!APPLE_TEAM_ID || !APPLE_CERT_PATH || !APPLE_CERT_PASSWORD) { + console.warn( + "Missing required environment variables for macOS signing, you won't be able to use this binary until you sign it yourself.", + ); + console.info({ APPLE_TEAM_ID, APPLE_CERT_PATH, APPLE_CERT_PASSWORD }); + return; + } + console.log(`Signing ${nodeBinary}...`); + await run( + 'rcodesign', + 'sign', + '--team-name', + APPLE_TEAM_ID, + '--p12-file', + APPLE_CERT_PATH, + '--p12-password', + APPLE_CERT_PASSWORD, + '--for-notarization', + '-e', + join(import.meta.dirname, 'entitlements.plist'), + nodeBinary, + ); + if (!APPLE_API_KEY_PATH) { + console.warn( + "Missing required environment variable for macOS notarization, you won't be able to notarize this binary which will annoy people trying to run it.", + ); + console.info({ APPLE_API_KEY_PATH }); + return; + } + const zipFile = `${nodeBinary}.zip`; + await run('zip', zipFile, nodeBinary); + await run('rcodesign', 'notary-submit', '--api-key-file', APPLE_API_KEY_PATH, '--wait', zipFile); + await rm(zipFile); + } + }), +); diff --git a/packages/spotlight/bin/entitlements.plist b/packages/spotlight/bin/entitlements.plist new file mode 100644 index 00000000..d27c0e20 --- /dev/null +++ b/packages/spotlight/bin/entitlements.plist @@ -0,0 +1,19 @@ + + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + com.apple.security.get-task-allow + + + diff --git a/packages/spotlight/bin/run.js b/packages/spotlight/bin/run.js index d96e3c9b..c61cdd33 100755 --- a/packages/spotlight/bin/run.js +++ b/packages/spotlight/bin/run.js @@ -1,6 +1,30 @@ #!/usr/bin/env node -const { setupSidecar } = await import('../dist/sidecar.js'); -const port = process.argv.length >= 3 ? Number(process.argv[2]) : undefined; +import { setupSidecar } from '@spotlightjs/sidecar'; +import { readFileSync } from 'node:fs'; import { join } from 'node:path'; +import * as sea from 'node:sea'; import { fileURLToPath } from 'node:url'; -setupSidecar({ port, basePath: join(fileURLToPath(import.meta.url), '../../dist/overlay/') }); +const port = process.argv.length >= 3 ? Number(process.argv[2]) : undefined; + +const MANIFEST_NAME = 'manifest.json'; +const ENTRY_POINT_NAME = 'src/index.html'; + +const basePath = process.cwd(); +const filesToServe = Object.create(null); + +const readAsset = sea.isSea() + ? name => Buffer.from(sea.getRawAsset(name)) + : (() => { + const ASSET_DIR = join(fileURLToPath(import.meta.url), '../../dist/overlay/'); + + return name => readFileSync(join(ASSET_DIR, name)); + })(); + +// Following the guide here: https://vite.dev/guide/backend-integration.html +const manifest = JSON.parse(readAsset(MANIFEST_NAME)); +filesToServe[ENTRY_POINT_NAME] = readAsset(ENTRY_POINT_NAME); +const entries = Object.values(manifest); +for (const entry of entries) { + filesToServe[entry.file] = readAsset(entry.file); +} +setupSidecar({ port, basePath, filesToServe }); diff --git a/packages/spotlight/package.json b/packages/spotlight/package.json index e0c456f0..ec0b763c 100644 --- a/packages/spotlight/package.json +++ b/packages/spotlight/package.json @@ -7,7 +7,7 @@ "scripts": { "start": "./bin/run.js", "dev": "vite build --watch", - "build": "vite build && vite build --config vite.overlay.config.ts && tsc", + "build": "vite build && vite build --config vite.overlay.config.ts && vite build --config vite.binary.config.ts && ./bin/build.js && tsc", "build:watch": "vite build --watch", "yalc:publish": "yalc publish --push --sig --private", "clean": "rimraf dist" @@ -42,7 +42,10 @@ "dependencies": { "@spotlightjs/overlay": "workspace:*", "@spotlightjs/sidecar": "workspace:*", - "import-meta-resolve": "^4.1.0" + "import-meta-resolve": "^4.1.0", + "macho-unsign": "^2.0.6", + "portable-executable-signature": "^2.0.6", + "postject": "1.0.0-alpha.6" }, "devDependencies": { "@spotlightjs/tsconfig": "workspace:*", diff --git a/packages/spotlight/vite.binary.config.ts b/packages/spotlight/vite.binary.config.ts new file mode 100644 index 00000000..b660fbdf --- /dev/null +++ b/packages/spotlight/vite.binary.config.ts @@ -0,0 +1,19 @@ +import { builtinModules } from 'node:module'; +import { resolve } from 'node:path'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + build: { + emptyOutDir: false, + reportCompressedSize: false, + lib: { + entry: { + spotlight: resolve(__dirname, 'bin/run.js'), + }, + formats: ['cjs'], + }, + rollupOptions: { + external: ['node:sea', ...builtinModules, ...builtinModules.map(x => `node:${x}`)], + }, + }, +}); diff --git a/packages/spotlight/vite.overlay.config.ts b/packages/spotlight/vite.overlay.config.ts index 7e6bc850..dd4e3647 100644 --- a/packages/spotlight/vite.overlay.config.ts +++ b/packages/spotlight/vite.overlay.config.ts @@ -4,6 +4,7 @@ import { defineConfig } from 'vite'; export default defineConfig({ build: { outDir: resolve(__dirname, 'dist', 'overlay'), + manifest: true, rollupOptions: { input: { main: resolve(__dirname, 'src', 'index.html'), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65629273..96bbaf61 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -343,7 +343,7 @@ importers: version: 31.6.0 electron-builder: specifier: ^24.13.3 - version: 24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)) + version: 24.13.3(electron-builder-squirrel-windows@24.13.3) electron-vite: specifier: ^2.3.0 version: 2.3.0(vite@5.4.8(@types/node@22.7.5)(terser@5.34.1)) @@ -495,6 +495,15 @@ importers: import-meta-resolve: specifier: ^4.1.0 version: 4.1.0 + macho-unsign: + specifier: ^2.0.6 + version: 2.0.6 + portable-executable-signature: + specifier: ^2.0.6 + version: 2.0.6 + postject: + specifier: 1.0.0-alpha.6 + version: 1.0.0-alpha.6 devDependencies: '@spotlightjs/tsconfig': specifier: workspace:* @@ -4925,6 +4934,10 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + macho-unsign@2.0.6: + resolution: {integrity: sha512-YkIVGFnpVHJMMwfy4bHo79Vy05ddVk/PZGSCmmiCT4zepx+FMP/JAt9hOoXuc31s2bbcOtnzznOGca5fRhgZOg==} + engines: {node: '>=18.12.0'} + magic-string@0.27.0: resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} engines: {node: '>=12'} @@ -5627,6 +5640,10 @@ packages: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} + portable-executable-signature@2.0.6: + resolution: {integrity: sha512-VV+1GuJca0cJ0PFwnCW/xK8Ro9DDX38e4iUDh6ngPjd9vj7VLiemh9rSlqquvcVGtClkVzYaV/UseMVnUrxS/Q==} + engines: {node: '>=18.12.0'} + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -5716,6 +5733,11 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} + postject@1.0.0-alpha.6: + resolution: {integrity: sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==} + engines: {node: '>=14.0.0'} + hasBin: true + preferred-pm@4.0.0: resolution: {integrity: sha512-gYBeFTZLu055D8Vv3cSPox/0iTPtkzxpLroSYYA7WXgRi31WCJ51Uyl8ZiPeUUjyvs2MBzK+S8v9JVUgHU/Sqw==} engines: {node: '>=18.12'} @@ -10740,7 +10762,7 @@ snapshots: app-builder-bin@4.0.0: {} - app-builder-lib@24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)): + app-builder-lib@24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3): dependencies: '@develar/schema-utils': 2.6.5 '@electron/notarize': 2.2.1 @@ -10909,7 +10931,7 @@ snapshots: unist-util-visit: 5.0.0 vfile: 6.0.3 vite: 5.4.8(@types/node@18.19.55)(terser@5.34.1) - vitefu: 1.0.2(vite@5.4.8(@types/node@18.19.55)(terser@5.34.1)) + vitefu: 1.0.2(vite@5.4.8(@types/node@22.7.5)(terser@5.34.1)) which-pm: 3.0.0 xxhash-wasm: 1.0.2 yargs-parser: 21.1.1 @@ -11366,8 +11388,7 @@ snapshots: commander@5.1.0: {} - commander@9.5.0: - optional: true + commander@9.5.0: {} common-ancestor-path@1.0.1: {} @@ -11610,7 +11631,7 @@ snapshots: dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3): dependencies: - app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)) + app-builder-lib: 24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3) builder-util: 24.13.1 builder-util-runtime: 9.2.4 fs-extra: 10.1.0 @@ -11672,7 +11693,7 @@ snapshots: electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3): dependencies: - app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)) + app-builder-lib: 24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3) archiver: 5.3.2 builder-util: 24.13.1 fs-extra: 10.1.0 @@ -11680,9 +11701,9 @@ snapshots: - dmg-builder - supports-color - electron-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)): + electron-builder@24.13.3(electron-builder-squirrel-windows@24.13.3): dependencies: - app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)) + app-builder-lib: 24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3) builder-util: 24.13.1 builder-util-runtime: 9.2.4 chalk: 4.1.2 @@ -13102,6 +13123,8 @@ snapshots: dependencies: yallist: 4.0.0 + macho-unsign@2.0.6: {} + magic-string@0.27.0: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -14072,6 +14095,8 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + portable-executable-signature@2.0.6: {} + postcss-import@15.1.0(postcss@8.4.47): dependencies: postcss: 8.4.47 @@ -14145,6 +14170,10 @@ snapshots: dependencies: xtend: 4.0.2 + postject@1.0.0-alpha.6: + dependencies: + commander: 9.5.0 + preferred-pm@4.0.0: dependencies: find-up-simple: 1.0.0 @@ -15557,10 +15586,6 @@ snapshots: optionalDependencies: vite: 5.4.8(@types/node@22.7.5)(terser@5.34.1) - vitefu@1.0.2(vite@5.4.8(@types/node@18.19.55)(terser@5.34.1)): - optionalDependencies: - vite: 5.4.8(@types/node@18.19.55)(terser@5.34.1) - vitefu@1.0.2(vite@5.4.8(@types/node@22.7.5)(terser@5.34.1)): optionalDependencies: vite: 5.4.8(@types/node@22.7.5)(terser@5.34.1)