Лицензия, редактор, пульт и сборка
- Main: license service, IPC, router; закрытие окон; yauzl закрытие zip (EMFILE), zipRead тест - Editor: стабильный projectState без мигания, логотип и меню, строки UI, LayoutShell overlay - Control: ластик для всех типов эффектов, затухание/нарастание музыки при смене сцены - Сборка: vite, build/dev scripts, obfuscate-main и build-env скрипты с тестами; package.json Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* @param {string[]} argv
|
||||
* @param {NodeJS.ProcessEnv} env
|
||||
*/
|
||||
export function resolveIsProduction(argv = process.argv, env = process.env) {
|
||||
return env.NODE_ENV === 'production' || argv.includes('--production');
|
||||
}
|
||||
|
||||
/** @param {string[]} argv */
|
||||
export function resolveObfuscateMain(argv = process.argv) {
|
||||
return argv.includes('--obfuscate');
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { resolveIsProduction, resolveObfuscateMain } from './build-env.mjs';
|
||||
|
||||
void test('resolveIsProduction: argv --production', () => {
|
||||
assert.equal(resolveIsProduction(['node', 'scripts/build.mjs', '--production'], {}), true);
|
||||
});
|
||||
|
||||
void test('resolveIsProduction: NODE_ENV=production', () => {
|
||||
assert.equal(resolveIsProduction(['node', 'scripts/build.mjs'], { NODE_ENV: 'production' }), true);
|
||||
});
|
||||
|
||||
void test('resolveIsProduction: dev по умолчанию', () => {
|
||||
assert.equal(resolveIsProduction(['node', 'scripts/build.mjs'], { NODE_ENV: 'development' }), false);
|
||||
});
|
||||
|
||||
void test('resolveObfuscateMain: --obfuscate', () => {
|
||||
assert.equal(resolveObfuscateMain(['node', 'build.mjs', '--production', '--obfuscate']), true);
|
||||
assert.equal(resolveObfuscateMain(['node', 'build.mjs', '--production']), false);
|
||||
});
|
||||
+44
-12
@@ -5,39 +5,71 @@ import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { build } from 'esbuild';
|
||||
|
||||
import { resolveIsProduction, resolveObfuscateMain } from './build-env.mjs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const root = path.resolve(__dirname, '..');
|
||||
|
||||
const isProd = resolveIsProduction();
|
||||
const obfuscateMain = resolveObfuscateMain();
|
||||
|
||||
/** Старые .map от dev-сборок не должны попадать в pack. */
|
||||
function removeStaleNodeBundleMaps() {
|
||||
for (const p of [
|
||||
path.join(root, 'dist/main/index.cjs.map'),
|
||||
path.join(root, 'dist/preload/index.cjs.map'),
|
||||
]) {
|
||||
if (fs.existsSync(p)) fs.unlinkSync(p);
|
||||
}
|
||||
}
|
||||
|
||||
function runViteBuild() {
|
||||
execFileSync('npx vite build', {
|
||||
const cmd = isProd ? 'npx vite build' : 'npx vite build --mode development';
|
||||
execFileSync(cmd, {
|
||||
cwd: root,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: isProd ? 'production' : 'development',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function buildNodeTargets() {
|
||||
await build({
|
||||
entryPoints: [path.join(root, 'app/main/index.ts')],
|
||||
outfile: path.join(root, 'dist/main/index.cjs'),
|
||||
if (isProd) removeStaleNodeBundleMaps();
|
||||
|
||||
const nodeEnvLiteral = JSON.stringify(isProd ? 'production' : 'development');
|
||||
const common = {
|
||||
platform: 'node',
|
||||
target: 'node22',
|
||||
format: 'cjs',
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
minify: isProd,
|
||||
sourcemap: !isProd,
|
||||
external: ['electron'],
|
||||
});
|
||||
define: { 'process.env.NODE_ENV': nodeEnvLiteral },
|
||||
drop: isProd ? ['console', 'debugger'] : [],
|
||||
};
|
||||
|
||||
const mainOut = path.join(root, 'dist/main/index.cjs');
|
||||
|
||||
await build({
|
||||
...common,
|
||||
entryPoints: [path.join(root, 'app/main/index.ts')],
|
||||
outfile: mainOut,
|
||||
});
|
||||
|
||||
if (isProd && obfuscateMain) {
|
||||
const { obfuscateMainBundleFile } = await import('./obfuscate-main.mjs');
|
||||
obfuscateMainBundleFile(mainOut);
|
||||
}
|
||||
|
||||
await build({
|
||||
...common,
|
||||
entryPoints: [path.join(root, 'app/preload/index.ts')],
|
||||
outfile: path.join(root, 'dist/preload/index.cjs'),
|
||||
platform: 'node',
|
||||
target: 'node22',
|
||||
format: 'cjs',
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
external: ['electron'],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+12
-2
@@ -77,10 +77,20 @@ async function watchMainAndPreload() {
|
||||
|
||||
const dispose = await watchMainAndPreload();
|
||||
const vite = spawnShell('npx vite dev --strictPort', {
|
||||
env: { ...process.env, NODE_ENV: 'development', VITE_DEV_SERVER_URL: 'http://localhost:5173/' },
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: 'development',
|
||||
DND_SKIP_LICENSE: process.env.DND_SKIP_LICENSE ?? '1',
|
||||
VITE_DEV_SERVER_URL: 'http://localhost:5173/',
|
||||
},
|
||||
});
|
||||
const electron = spawnShell('npx electron .', {
|
||||
env: { ...process.env, NODE_ENV: 'development', VITE_DEV_SERVER_URL: 'http://localhost:5173/' },
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: 'development',
|
||||
DND_SKIP_LICENSE: process.env.DND_SKIP_LICENSE ?? '1',
|
||||
VITE_DEV_SERVER_URL: 'http://localhost:5173/',
|
||||
},
|
||||
});
|
||||
|
||||
let shuttingDown = false;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import fs from 'node:fs';
|
||||
|
||||
import JavaScriptObfuscator from 'javascript-obfuscator';
|
||||
|
||||
/**
|
||||
* Лёгкие настройки: без selfDefending / controlFlowFlattening — меньше шансов сломать Electron main.
|
||||
* @param {string} filePath
|
||||
*/
|
||||
export function obfuscateMainBundleFile(filePath) {
|
||||
const code = fs.readFileSync(filePath, 'utf8');
|
||||
const obfuscated = JavaScriptObfuscator.obfuscate(code, {
|
||||
compact: true,
|
||||
controlFlowFlattening: false,
|
||||
deadCodeInjection: false,
|
||||
debugProtection: false,
|
||||
disableConsoleOutput: false,
|
||||
identifierNamesGenerator: 'hexadecimal',
|
||||
renameGlobals: false,
|
||||
selfDefending: false,
|
||||
simplify: true,
|
||||
stringArray: true,
|
||||
stringArrayEncoding: [],
|
||||
stringArrayThreshold: 0.75,
|
||||
transformObjectKeys: false,
|
||||
}).getObfuscatedCode();
|
||||
fs.writeFileSync(filePath, obfuscated, 'utf8');
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import { createRequire } from 'node:module';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import { obfuscateMainBundleFile } from './obfuscate-main.mjs';
|
||||
|
||||
void test('obfuscateMainBundleFile: CJS после обфускации исполняется (кроссплатформенно)', () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dnd-obf-'));
|
||||
const filePath = path.join(dir, 'stub.cjs');
|
||||
fs.writeFileSync(filePath, 'exports.answer = 40 + 2;\n', 'utf8');
|
||||
|
||||
obfuscateMainBundleFile(filePath);
|
||||
|
||||
const out = fs.readFileSync(filePath, 'utf8');
|
||||
assert.ok(out.length > 0);
|
||||
assert.ok(!out.includes('exports.answer = 42'), 'исходный вид строки не должен сохраняться дословно');
|
||||
|
||||
const requireFromTest = createRequire(import.meta.url);
|
||||
assert.equal(requireFromTest(filePath).answer, 42);
|
||||
});
|
||||
Reference in New Issue
Block a user