- Introduced `compile-web-script.ts` for generating JavaScript files for userscripts. - Supports options for minification and obfuscation. - Generates base JS, minified JS, and obfuscated userscript. - Configurable via command line arguments and environment variables.
145 lines
5.4 KiB
TypeScript
145 lines
5.4 KiB
TypeScript
#!/usr/bin/env tsx
|
||
/**
|
||
* compile-web-script.ts
|
||
*
|
||
* Usage:
|
||
* yarn compile-web-script
|
||
* yarn compile-web-script --no-obf # minifyのみ(難読化なし)
|
||
* yarn compile-web-script --no-userscript # .js のみ生成(userscript不要)
|
||
*
|
||
* Options (環境変数でも上書き可):
|
||
* UI=tokyo|default (default: tokyo)
|
||
* DARK=true|false (default: false)
|
||
*/
|
||
|
||
import { execSync } from 'child_process';
|
||
import fs from 'fs';
|
||
import path from 'path';
|
||
import { injectJavascriptData } from '../lib/webViewInjectjavascript';
|
||
|
||
// ---- オプション解析 ----
|
||
const args = process.argv.slice(2);
|
||
const noObf = args.includes('--no-obf');
|
||
const noUserscript = args.includes('--no-userscript');
|
||
|
||
const options = {
|
||
mapSwitch: 'false',
|
||
iconSetting: 'true',
|
||
stationMenu: 'false',
|
||
trainMenu: 'false',
|
||
uiSetting: (process.env.UI ?? 'tokyo') as string,
|
||
useUnyohub: 'false',
|
||
useElesite: 'false',
|
||
isDark: (process.env.DARK ?? 'false') === 'true',
|
||
backendApiBaseUrl:'https://jr-shikoku-backend-api-v1.haruk.in',
|
||
mockApiConfig: null,
|
||
} as const;
|
||
|
||
// ---- 出力ディレクトリ ----
|
||
const outDir = path.resolve(__dirname, '../docs/generated');
|
||
fs.mkdirSync(outDir, { recursive: true });
|
||
|
||
// const baseJs = path.join(outDir, 'webInjectJavascript.parsed.current.js'); // 中間: ベタJS
|
||
// const minJs = path.join(outDir, 'webInjectJavascript.parsed.current.min.js'); // 中間: minify済み
|
||
// const obfJs = path.join(outDir, 'webInjectJavascript.parsed.current.obf.js'); // 中間: 難読化済みJS
|
||
// const baseUser = path.join(outDir, 'webInjectJavascript.parsed.current.user.js'); // 中間: ベタuserscript
|
||
const obfUser = path.join(outDir, 'webInjectJavascript.parsed.current.obf.user.js');
|
||
|
||
// 中間ファイルを出力したい場合は上の const を有効化して各ステップの writeFileSync コメントを外す
|
||
const baseJs = '/tmp/webInjectJavascript.base.js';
|
||
const minJs = '/tmp/webInjectJavascript.min.js';
|
||
const obfJs = '/tmp/webInjectJavascript.obf.js';
|
||
|
||
// ---- userscript ヘッダ ----
|
||
const userscriptHeader = `\
|
||
// ==UserScript==
|
||
// @name JR Shikoku WebInject
|
||
// @namespace jrshikoku
|
||
// @version 1.0.0
|
||
// @description Generated inject script for train.jr-shikoku.co.jp
|
||
// @match https://train.jr-shikoku.co.jp/*
|
||
// @run-at document-end
|
||
// @grant none
|
||
// ==/UserScript==
|
||
(function(){
|
||
`;
|
||
const userscriptFooter = `\n})();\n`;
|
||
|
||
function wrapUserscript(src: string): string {
|
||
return userscriptHeader + src + userscriptFooter;
|
||
}
|
||
|
||
// ---- Step 1: ベタJS生成 ----
|
||
console.log('[1/3] Generating base JS...');
|
||
const shim = `\
|
||
/*
|
||
Generated from lib/webViewInjectjavascript.ts
|
||
Options: ${JSON.stringify(options)}
|
||
Date: ${new Date().toISOString()}
|
||
*/
|
||
if (!window.ReactNativeWebView) {
|
||
window.ReactNativeWebView = {
|
||
postMessage: (msg) => console.log('[ReactNativeWebView.postMessage]', msg),
|
||
};
|
||
}
|
||
`;
|
||
const raw = injectJavascriptData(options);
|
||
const baseContent = `${shim}\n${raw}`;
|
||
fs.writeFileSync(baseJs, baseContent, 'utf8');
|
||
// fs.writeFileSync(baseJs, baseContent, 'utf8'); // 中間ファイルとして保存する場合
|
||
// if (!noUserscript) {
|
||
// fs.writeFileSync(baseUser, wrapUserscript(baseContent), 'utf8'); // ベタuserscriptを保存する場合
|
||
// }
|
||
console.log(` -> base JS generated (${Math.round(Buffer.byteLength(baseContent) / 1024)}KB)`);
|
||
|
||
// ---- Step 2: minify (terser) ----
|
||
console.log('[2/3] Minifying with terser...');
|
||
const terserCmd = [
|
||
'npx', 'terser', JSON.stringify(baseJs),
|
||
'--compress', 'passes=3,drop_console=false,pure_getters=true',
|
||
'--mangle', 'toplevel=true',
|
||
'--output', JSON.stringify(minJs),
|
||
].join(' ');
|
||
execSync(terserCmd, { stdio: 'inherit' });
|
||
// fs.copyFileSync(minJs, path.join(outDir, 'webInjectJavascript.parsed.current.min.js')); // 中間ファイルとして保存する場合
|
||
console.log(` -> minified (${Math.round(fs.statSync(minJs).size / 1024)}KB)`);
|
||
|
||
if (noObf) {
|
||
console.log('[3/3] Skipped obfuscation (--no-obf)');
|
||
console.log('Done.');
|
||
process.exit(0);
|
||
}
|
||
|
||
// ---- Step 3: 難読化 (javascript-obfuscator) ----
|
||
console.log('[3/3] Obfuscating with javascript-obfuscator...');
|
||
const obfCmd = [
|
||
'npx', 'javascript-obfuscator', JSON.stringify(minJs),
|
||
'--output', JSON.stringify(obfJs),
|
||
'--compact', 'true',
|
||
'--control-flow-flattening', 'false',
|
||
'--dead-code-injection', 'false',
|
||
'--string-array', 'true',
|
||
'--string-array-encoding', 'base64',
|
||
'--string-array-threshold', '0.75',
|
||
'--string-array-rotate', 'true',
|
||
'--string-array-shuffle', 'true',
|
||
'--string-array-calls-transform', 'true',
|
||
'--string-array-index-shift', 'true',
|
||
'--string-array-wrappers-count', '2',
|
||
'--string-array-wrappers-type', 'function',
|
||
'--string-array-wrappers-chained-calls', 'true',
|
||
'--split-strings', 'true',
|
||
'--split-strings-chunk-length', '8',
|
||
'--identifier-names-generator', 'hexadecimal',
|
||
'--rename-globals', 'false',
|
||
'--self-defending', 'false',
|
||
].join(' ');
|
||
execSync(obfCmd, { stdio: 'inherit' });
|
||
// fs.copyFileSync(obfJs, path.join(outDir, 'webInjectJavascript.parsed.current.obf.js')); // 難読化JSを保存する場合
|
||
|
||
const obfContent = fs.readFileSync(obfJs, 'utf8');
|
||
fs.writeFileSync(obfUser, wrapUserscript(obfContent), 'utf8');
|
||
|
||
console.log('\nDone. Generated:');
|
||
console.log(` ${path.relative(process.cwd(), obfUser)} (${Math.round(fs.statSync(obfUser).size / 1024)}KB)`);
|