#!/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)`);