5e6ff42fe6
- 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)`);
|