diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b58d20c..30a6ded 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,13 @@ jobs: - name: Install dependencies run: npm ci + # _license-secret.ts is generated (gitignored) — required by + # license-activation.ts before tsc/vitest can run. + - name: Inject license secret + env: + MINGLY_LICENSE_SECRET: ${{ secrets.MINGLY_LICENSE_SECRET }} + run: npx tsx scripts/inject-secret.ts + - name: Type check (main process) run: npx tsc -p tsconfig.main.json --noEmit @@ -48,6 +55,11 @@ jobs: - name: Install dependencies run: npm ci + - name: Inject license secret + env: + MINGLY_LICENSE_SECRET: ${{ secrets.MINGLY_LICENSE_SECRET }} + run: npx tsx scripts/inject-secret.ts + - name: Run tests with coverage run: npx vitest run --coverage @@ -96,7 +108,10 @@ jobs: - name: Install dependencies run: npm ci + # prebuild:main runs inject-secret automatically (npm lifecycle hook) - name: Build main process + env: + MINGLY_LICENSE_SECRET: ${{ secrets.MINGLY_LICENSE_SECRET }} run: npm run build:main - name: Build renderer @@ -176,7 +191,10 @@ jobs: - name: Install dependencies run: npm ci + # prebuild:main runs inject-secret automatically (npm lifecycle hook) - name: Build + env: + MINGLY_LICENSE_SECRET: ${{ secrets.MINGLY_LICENSE_SECRET }} run: npm run build - name: Package and publish Electron app diff --git a/package-lock.json b/package-lock.json index dbe8819..5a53373 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "promptfoo": "^0.121.2", "tailwindcss": "^3.4.1", "tailwindcss-animate": "^1.0.7", + "tsx": "^4.22.5", "typescript": "^5.3.3", "vite": "^5.0.10", "vitest": "^4.0.18", @@ -14365,21 +14366,6 @@ "url": "https://opencollective.com/mongoose" } }, - "node_modules/mongoose/node_modules/gcp-metadata": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz", - "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", - "extraneous": true, - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/mongoose/node_modules/mongodb": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.2.0.tgz", diff --git a/package.json b/package.json index 8ae369d..06db260 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "dev:vite": "vite", "dev:electron": "npm run build:preload && wait-on http://localhost:5173 && electron .", "build": "npm run build:main && npm run build:ner-worker && npm run build:preload && npm run build:renderer", + "prebuild:main": "npx tsx scripts/inject-secret.ts", "build:main": "esbuild src/main/index.ts --bundle --platform=node --outfile=dist/main/index.js --external:electron --external:electron-updater --external:sql.js --external:@huggingface/transformers --format=cjs --target=node18 --sourcemap", "build:ner-worker": "esbuild src/main/privacy/ner-worker.ts --bundle --platform=node --outfile=dist/main/ner-worker.js --external:@huggingface/transformers --format=cjs --target=node18 --sourcemap", "build:preload": "esbuild src/preload/index.ts --bundle --platform=node --outfile=dist/preload/index.js --external:electron --format=iife --target=node18", @@ -71,6 +72,7 @@ "promptfoo": "^0.121.2", "tailwindcss": "^3.4.1", "tailwindcss-animate": "^1.0.7", + "tsx": "^4.22.5", "typescript": "^5.3.3", "vite": "^5.0.10", "vitest": "^4.0.18", diff --git a/scripts/inject-secret.ts b/scripts/inject-secret.ts new file mode 100644 index 0000000..d6afbe7 --- /dev/null +++ b/scripts/inject-secret.ts @@ -0,0 +1,51 @@ +/** + * Build-time secret injection for license validation. + * + * Reads MINGLY_LICENSE_SECRET from environment (or .env file) and generates + * a TypeScript module that exports the secret. This file is gitignored so the + * secret never leaks into the public repository. + * + * Usage: npx tsx scripts/inject-secret.ts + * Called automatically via `prebuild:main` in package.json (also runs in CI + * before typecheck/tests, since license-activation.ts imports the module). + */ + +import { writeFileSync, existsSync, readFileSync } from 'fs' +import { join } from 'path' + +const OUTPUT_PATH = join(__dirname, '..', 'src', 'main', 'services', '_license-secret.ts') +const ENV_PATH = join(__dirname, '..', '.env') +const DEV_FALLBACK = 'mingly-dev-secret-not-for-production' + +function loadEnvSecret(): string | undefined { + if (process.env.MINGLY_LICENSE_SECRET) { + return process.env.MINGLY_LICENSE_SECRET + } + + if (existsSync(ENV_PATH)) { + const content = readFileSync(ENV_PATH, 'utf-8') + const match = content.match(/^MINGLY_LICENSE_SECRET=(.+)$/m) + if (match && match[1] && match[1] !== 'your-secret-here') { + return match[1].trim() + } + } + + return undefined +} + +const secret = loadEnvSecret() +const isProduction = !!secret + +if (!secret) { + console.log('[inject-secret] No MINGLY_LICENSE_SECRET found — using dev fallback') +} else { + console.log('[inject-secret] Production secret injected') +} + +const output = `// AUTO-GENERATED — do not edit. Regenerated on every build. +export const LICENSE_HMAC_SECRET = '${secret || DEV_FALLBACK}' +export const IS_PRODUCTION_SECRET = ${isProduction} +` + +writeFileSync(OUTPUT_PATH, output, 'utf-8') +console.log(`[inject-secret] Written to ${OUTPUT_PATH}`)