diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c465cd..3d9c238 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,12 +88,12 @@ importers: inquirer: specifier: '8' version: 8.0.0 + json5: + specifier: ^2.2.3 + version: 2.2.3 ts-node: specifier: ^10.9.1 version: 10.9.1(@types/node@18.15.2)(typescript@4.9.5) - vite-electron-plugin: - specifier: ^0.8.2 - version: 0.8.2(esbuild@0.17.18) devDependencies: '@types/debug': specifier: ^4.1.7 @@ -1003,27 +1003,6 @@ packages: - supports-color dev: true - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: false - - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - dev: false - - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.15.0 - dev: false - /@sindresorhus/is@4.6.0: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -1682,14 +1661,6 @@ packages: - supports-color dev: true - /anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - dev: false - /app-builder-bin@4.0.0: resolution: {integrity: sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==} dev: true @@ -1957,11 +1928,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /binary-extensions@2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} - engines: {node: '>=8'} - dev: false - /bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} requiresBuild: true @@ -2021,13 +1987,6 @@ packages: - supports-color dev: true - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: false - /browserslist@4.21.5: resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -2213,21 +2172,6 @@ packages: - supports-color dev: true - /chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 - dev: false - /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -3186,17 +3130,6 @@ packages: requiresBuild: true dev: true - /fast-glob@3.2.12: - resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} - engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - dev: false - /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} requiresBuild: true @@ -3206,12 +3139,6 @@ packages: resolution: {integrity: sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==} dev: true - /fastq@1.15.0: - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} - dependencies: - reusify: 1.0.4 - dev: false - /fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} dependencies: @@ -3247,13 +3174,6 @@ packages: to-regex-range: 2.1.1 dev: true - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: false - /find-up@1.1.2: resolution: {integrity: sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==} engines: {node: '>=0.10.0'} @@ -3455,13 +3375,6 @@ packages: path-dirname: 1.0.2 dev: true - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: false - /glob-stream@6.1.0: resolution: {integrity: sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==} engines: {node: '>= 0.10'} @@ -3872,13 +3785,6 @@ packages: binary-extensions: 1.13.1 dev: true - /is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - dependencies: - binary-extensions: 2.2.0 - dev: false - /is-buffer@1.1.6: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} dev: true @@ -3942,6 +3848,7 @@ packages: /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + dev: true /is-fullwidth-code-point@1.0.0: resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==} @@ -3966,6 +3873,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + dev: true /is-negated-glob@1.0.0: resolution: {integrity: sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==} @@ -3984,11 +3892,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: false - /is-plain-object@2.0.4: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} @@ -4302,11 +4205,6 @@ packages: dev: true optional: true - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - dev: false - /micromatch@3.1.10: resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==} engines: {node: '>=0.10.0'} @@ -4328,14 +4226,6 @@ packages: - supports-color dev: true - /micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 - dev: false - /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -4505,24 +4395,13 @@ packages: /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + dev: true /normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} dev: true - /notbundle@0.4.0: - resolution: {integrity: sha512-fstkCyAyt5nqHch1GESWqkW9i/D2p80SacBEAoPNoRS0U6qBT6ZSUp2BayS+dbEWTHqheSzwxQwEEK/jpPObHw==} - peerDependencies: - '@swc/core': '*' - peerDependenciesMeta: - '@swc/core': - optional: true - dependencies: - chokidar: 3.5.3 - fast-glob: 3.2.12 - dev: false - /now-and-later@2.0.1: resolution: {integrity: sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==} engines: {node: '>= 0.10'} @@ -4718,11 +4597,6 @@ packages: /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: false - /pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -4833,10 +4707,6 @@ packages: requiresBuild: true dev: true - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: false - /quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -4907,13 +4777,6 @@ packages: - supports-color dev: true - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - dev: false - /rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} @@ -5034,11 +4897,6 @@ packages: engines: {node: '>=0.12'} dev: true - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: false - /rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} hasBin: true @@ -5087,12 +4945,6 @@ packages: engines: {node: '>=0.12.0'} dev: false - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - dev: false - /rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} @@ -5563,13 +5415,6 @@ packages: repeat-string: 1.6.1 dev: true - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - dev: false - /to-regex@3.0.2: resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} engines: {node: '>=0.10.0'} @@ -5844,27 +5689,6 @@ packages: replace-ext: 1.0.1 dev: true - /vite-electron-plugin@0.8.2(esbuild@0.17.18): - resolution: {integrity: sha512-IN0sSi1MKVh2ewc6b+sSZjsBAyL4XjUvEI9pEvroGonvjk2Jj2Pd00zIabKIQPAnvnPmeRJgv+Hjn/lrvQZqQg==} - peerDependencies: - acorn: '*' - esbuild: '*' - peerDependenciesMeta: - acorn: - optional: true - dependencies: - esbuild: 0.17.18 - fast-glob: 3.2.12 - notbundle: 0.4.0 - vite-plugin-electron: 0.11.2 - transitivePeerDependencies: - - '@swc/core' - dev: false - - /vite-plugin-electron@0.11.2: - resolution: {integrity: sha512-umQRmSuA80JVxKB3PfO55o8mFTrW+sEtu7kZ5TYKAnkuYpKw7qgxl4f/65gp8x5BGHJjYh/iIRIE26x3Xqc4mQ==} - dev: false - /vite@2.9.15: resolution: {integrity: sha512-fzMt2jK4vQ3yK56te3Kqpkaeq9DkcZfBbzHwYpobasvgYmP2SoAr6Aic05CsB4CzCZbsDv4sujX3pkEGhLabVQ==} engines: {node: '>=12.2.0'} diff --git a/src/cli/src/index.ts b/src/cli/src/index.ts index 83bf382..2c85850 100644 --- a/src/cli/src/index.ts +++ b/src/cli/src/index.ts @@ -29,7 +29,7 @@ class Constants { this._gitName = cp.execSync('git config --global user.name', { encoding: 'utf8' }).trim(); return this._gitName; } catch (ex) { - return 'author'; + return ''; } } @@ -157,7 +157,7 @@ const copyTemplate = async (props: IProjectProps, projectDir: string) => { cp.execSync('git init', { cwd: projectDir, }); - } catch (e) {} + } catch {} // 安装依赖 const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent); diff --git a/src/cli/templates/default/src/main/core/app/lifecycle.ts b/src/cli/templates/default/src/main/core/app/lifecycle.ts deleted file mode 100644 index 8b4d375..0000000 --- a/src/cli/templates/default/src/main/core/app/lifecycle.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { app } from 'electron'; -import appEntry from '../../index'; -import { updateService } from '../services'; - -const beforeStartApp = async () => { - await updateService.beforeStartCheck(); -}; - -export const startApp = async () => { - await beforeStartApp(); - - app.on('window-all-closed', () => { - if (process.platform !== 'darwin') app.quit(); - }); - - await appEntry(); -}; diff --git a/src/cli/templates/default/src/main/core/bin.ts b/src/cli/templates/default/src/main/core/bin.ts index 8ba2a2c..60709ad 100644 --- a/src/cli/templates/default/src/main/core/bin.ts +++ b/src/cli/templates/default/src/main/core/bin.ts @@ -1,3 +1 @@ -import { startApp } from './app/lifecycle'; - -startApp(); +import '@/service/appService'; diff --git a/src/cli/templates/default/src/main/core/di/index.ts b/src/cli/templates/default/src/main/core/di/index.ts new file mode 100644 index 0000000..bb2cba6 --- /dev/null +++ b/src/cli/templates/default/src/main/core/di/index.ts @@ -0,0 +1,91 @@ +interface IRegisterOptions { + identifier?: string; + /** + * 立即实例化 + */ + instant?: boolean; + /** + * 实例化参数,仅在立即实例化时生效 + */ + args?: any[]; +} + +/** + * 依赖注入容器 + */ +class Container { + private static instanceMap = new Map any | any>(); + + static register(ctor: new (...args: any[]) => any, options?: IRegisterOptions) { + const { identifier, instant, args } = options || {}; + + ctor.toString = identifier ? () => identifier : () => ctor.name; + + const exactIdentifier = ctor.toString(); + + if (this.instanceMap.has(exactIdentifier)) + throw new Error(`标识为 "${exactIdentifier}" 的服务已被注册`); + + if (instant) { + this.instanceMap.set(exactIdentifier, new ctor(...(args || []))); + } else { + const that = this; + const proxy = new Proxy( + {}, + { + get(_, p) { + const instance = new ctor(...(args || [])); + + that.instanceMap.set(exactIdentifier, instance); + return instance[p]; + }, + } + ); + this.instanceMap.set(exactIdentifier, proxy as any); + } + } + + static find(ctor: new (...args: any[]) => T) { + const instance = this.instanceMap.get(ctor.toString()); + if (!instance) throw new Error('该Service未注册'); + + return instance as any; + } +} + +export interface IServiceOptions { + /** + * 标识符 + */ + identifier?: string; + /** + * 立即实例化 + */ + instant?: boolean; + /** + * 实例化参数,仅在立即实例化时生效 + */ + args?: any[]; +} +export function Service(options?: IServiceOptions) { + const { identifier, instant, args } = options || {}; + + /** + * 返回类装饰器 + * @param target 被装饰的类 + */ + return function Class(target: new (...args: any[]) => any) { + Container.register(target, { identifier, instant, args }); + }; +} + +export function Inject(injectable: new (...args: any[]) => any) { + /** + * 返回属性装饰器 + * @param target 被装饰的类 + * @param propertyKey 被装饰类的属性名 + */ + return function Property(target: Object, propertyKey: string | symbol) { + target[propertyKey as keyof typeof target] = Container.find(injectable); + }; +} diff --git a/src/cli/templates/default/src/main/core/services/consoleService/index.ts b/src/cli/templates/default/src/main/core/services/consoleService/index.ts deleted file mode 100644 index f64d1d1..0000000 --- a/src/cli/templates/default/src/main/core/services/consoleService/index.ts +++ /dev/null @@ -1,95 +0,0 @@ -type EBashColor = - | 'white' - | 'gray' - | 'black' - | 'blue' - | 'cyan' - | 'green' - | 'magenta' - | 'red' - | 'yellow'; - -interface IConsoleService { - log: (...data: any) => void; - bold: (str: string) => void; - italics: (str: string) => void; - underline: (str: string) => void; - inverse: (str: string) => void; - lineThrough: (str: string) => void; - color: (str: string, color: EBashColor) => void; - background: (str: string, background: EBashColor) => void; -} - -class ConsoleService implements IConsoleService { - log(...data: any) { - console.log(...data); - } - - bold(str: string) { - console.log('\x1B[1m%s\x1B[22m', str); - } - - italics(str: string) { - console.log('\x1B[3m%s\x1B[23m', str); - } - - underline(str: string) { - console.log('\x1B[4m%s\x1B[24m', str); - } - - inverse(str: string) { - console.log('\x1B[7m%s\x1B[27m', str); - } - - lineThrough(str: string) { - console.log('\x1B[9m%s\x1B[29m', str); - } - - color(str: string, color: EBashColor) { - switch (color) { - case 'white': - return console.log('\x1B[37m%s\x1B[39m', str); - case 'gray': - return console.log('\x1B[90m%s\x1B[39m', str); - case 'black': - return console.log('\x1B[30m%s\x1B[39m', str); - case 'blue': - return console.log('\x1B[34m%s\x1B[39m', str); - case 'cyan': - return console.log('\x1B[36m%s\x1B[39m', str); - case 'green': - return console.log('\x1B[32m%s\x1B[39m', str); - case 'magenta': - return console.log('\x1B[35m%s\x1B[39m', str); - case 'red': - return console.log('\x1B[31m%s\x1B[39m', str); - case 'yellow': - return console.log('\x1B[33m%s\x1B[39m', str); - } - } - - background(str: string, background: EBashColor) { - switch (background) { - case 'white': - return console.log('\x1B[47m%s\x1B[49m', str); - case 'gray': - return console.log('\x1B[49;5;8m%s\x1B[49m', str); - case 'black': - return console.log('\x1B[40m%s\x1B[49m', str); - case 'blue': - return console.log('\x1B[44m%s\x1B[49m', str); - case 'cyan': - return console.log('\x1B[46m%s\x1B[49m', str); - case 'green': - return console.log('\x1B[42m%s\x1B[49m', str); - case 'magenta': - return console.log('\x1B[45m%s\x1B[49m', str); - case 'red': - return console.log('\x1B[41m%s\x1B[49m', str); - case 'yellow': - return console.log('\x1B[43m%s\x1B[49m', str); - } - } -} - -export const consoleService = new ConsoleService(); diff --git a/src/cli/templates/default/src/main/core/services/index.ts b/src/cli/templates/default/src/main/core/services/index.ts deleted file mode 100644 index 492d449..0000000 --- a/src/cli/templates/default/src/main/core/services/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './consoleService'; -export * from './updateService'; diff --git a/src/cli/templates/default/src/main/core/services/updateService/interface.ts b/src/cli/templates/default/src/main/core/services/updateService/interface.ts deleted file mode 100644 index cf96904..0000000 --- a/src/cli/templates/default/src/main/core/services/updateService/interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface IUpdateService { - /** - * 应用启动前检查是否存在可用的更新包 - */ - beforeStartCheck: () => Promise; - exeUpdater: () => Promise; -} diff --git a/src/cli/templates/default/src/main/index.ts b/src/cli/templates/default/src/main/index.ts deleted file mode 100644 index 124b9d2..0000000 --- a/src/cli/templates/default/src/main/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { app, BrowserWindow, ipcMain, screen } from 'electron'; -import * as CONSTANTS from './constants'; -import MainWindow from './windows/MainWindow'; - -const { screenSize } = CONSTANTS; - -/** - * 应用主进程入口 - * 您需要在这个函数中书写您的逻辑 - */ -export default async () => { - ipcMain.handle('APP.VERSION', () => { - return app.getVersion(); - }); - - await app.whenReady(); - - const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize; - screenSize.width = screenWidth; - screenSize.height = screenHeight; - - // 窗口按钮事件 - ipcMain.on('DRAG_BAR.BUTTONS_ONCLICK', (ev, button: number) => { - const targetWindow = BrowserWindow.fromWebContents(ev.sender); - if (!targetWindow) return; - - switch (button) { - case -1: // 隐藏窗口 - targetWindow.hide(); - break; - case 0: // 窗口最小化 - targetWindow.minimize(); - break; - case 1: // 最大化 / 还原窗口 - targetWindow.isMaximized() ? targetWindow.unmaximize() : targetWindow.maximize(); - break; - default: - // no-op - } - }); - - MainWindow.createWindow(); -}; diff --git a/src/cli/templates/default/src/main/service/appService/index.ts b/src/cli/templates/default/src/main/service/appService/index.ts new file mode 100644 index 0000000..dcebe1e --- /dev/null +++ b/src/cli/templates/default/src/main/service/appService/index.ts @@ -0,0 +1,60 @@ +import { Inject, Service } from '@/core/di'; +import UpdateService from '../updateService'; +import { BrowserWindow, app, ipcMain, screen } from 'electron'; +import { screenSize } from '@/constants'; +import MainWindow from '@/windows/MainWindow'; + +@Service({ + instant: true, +}) +export default class AppService { + @Inject(UpdateService) private updateService!: UpdateService; + + private async beforeReady() { + await this.updateService.beforeStartCheck(); + + ipcMain.handle('APP.VERSION', () => { + return app.getVersion(); + }); + + app.on('window-all-closed', () => { + if (process.platform !== 'darwin') app.quit(); + }); + } + + private async onReady() { + const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize; + screenSize.width = screenWidth; + screenSize.height = screenHeight; + + // 窗口按钮事件 + ipcMain.on('COMMON.DRAG_BAR.BUTTONS_ONCLICK', (ev, button: number) => { + const targetWindow = BrowserWindow.fromWebContents(ev.sender); + if (!targetWindow) return; + + switch (button) { + case -1: // 隐藏窗口 + targetWindow.close(); + break; + case 0: // 窗口最小化 + targetWindow.minimize(); + break; + case 1: // 最大化 / 还原窗口 + targetWindow.isMaximized() ? targetWindow.unmaximize() : targetWindow.maximize(); + break; + default: + // no-op + } + }); + + MainWindow.createWindow(); + } + + constructor() { + (async () => { + await this.beforeReady(); + await app.whenReady(); + await this.onReady(); + })(); + } +} diff --git a/src/cli/templates/default/src/main/core/services/updateService/darwin.ts b/src/cli/templates/default/src/main/service/updateService/darwin.ts similarity index 69% rename from src/cli/templates/default/src/main/core/services/updateService/darwin.ts rename to src/cli/templates/default/src/main/service/updateService/darwin.ts index fbc57f3..9413465 100644 --- a/src/cli/templates/default/src/main/core/services/updateService/darwin.ts +++ b/src/cli/templates/default/src/main/service/updateService/darwin.ts @@ -1,13 +1,12 @@ import cp from 'child_process'; import fs from 'fs'; import path from 'path'; -import { EXE_DIR } from '../../../constants'; -import { IUpdateService } from './interface'; +import { EXE_DIR } from '@/constants'; const ContentsDir = path.dirname(EXE_DIR); -class Darwin implements IUpdateService { - async beforeStartCheck() { +export default class Darwin { + static async beforeStartCheck() { if (!fs.existsSync(path.join(ContentsDir, 'update.eup'))) return; // 阻塞 @@ -16,7 +15,7 @@ class Darwin implements IUpdateService { }); } - async exeUpdater() { + static async exeUpdater() { const { spawn } = cp; const child = spawn( path.join(ContentsDir, 'updater'), @@ -29,5 +28,3 @@ class Darwin implements IUpdateService { child.unref(); } } - -export const DARWIN = new Darwin(); diff --git a/src/cli/templates/default/src/main/core/services/updateService/index.ts b/src/cli/templates/default/src/main/service/updateService/index.ts similarity index 59% rename from src/cli/templates/default/src/main/core/services/updateService/index.ts rename to src/cli/templates/default/src/main/service/updateService/index.ts index 069c270..eadf26b 100644 --- a/src/cli/templates/default/src/main/core/services/updateService/index.ts +++ b/src/cli/templates/default/src/main/service/updateService/index.ts @@ -1,9 +1,10 @@ import { ipcMain } from 'electron'; -import { DARWIN } from './darwin'; -import { IUpdateService } from './interface'; -import { WIN32 } from './win32'; +import { Service } from '@/core/di'; +import Darwin from './darwin'; +import Win32 from './win32'; -class UpdateService implements IUpdateService { +@Service() +export default class UpdateService { constructor() { ipcMain.on('APP.UPDATE.EXE_UPDATER', () => { this.exeUpdater(); @@ -13,9 +14,9 @@ class UpdateService implements IUpdateService { async beforeStartCheck() { switch (process.platform) { case 'win32': - return await WIN32.beforeStartCheck(); + return await Win32.beforeStartCheck(); case 'darwin': - return await DARWIN.beforeStartCheck(); + return await Darwin.beforeStartCheck(); case 'linux': return; // TODO: default: @@ -26,9 +27,9 @@ class UpdateService implements IUpdateService { async exeUpdater() { switch (process.platform) { case 'win32': - return await WIN32.exeUpdater(); + return await Win32.exeUpdater(); case 'darwin': - return await DARWIN.exeUpdater(); + return await Darwin.exeUpdater(); case 'linux': return; // TODO: default: @@ -36,5 +37,3 @@ class UpdateService implements IUpdateService { } } } - -export const updateService = new UpdateService(); diff --git a/src/cli/templates/default/src/main/core/services/updateService/win32.ts b/src/cli/templates/default/src/main/service/updateService/win32.ts similarity index 66% rename from src/cli/templates/default/src/main/core/services/updateService/win32.ts rename to src/cli/templates/default/src/main/service/updateService/win32.ts index ffd458d..8eab49d 100644 --- a/src/cli/templates/default/src/main/core/services/updateService/win32.ts +++ b/src/cli/templates/default/src/main/service/updateService/win32.ts @@ -1,11 +1,10 @@ import cp from 'child_process'; import fs from 'fs'; import path from 'path'; -import { EXE_DIR, EXE_PATH } from '../../../constants'; -import { IUpdateService } from './interface'; +import { EXE_DIR, EXE_PATH } from '@/constants'; -class Win32 implements IUpdateService { - async beforeStartCheck() { +export default class Win32 { + static async beforeStartCheck() { if (!fs.existsSync(path.resolve(EXE_DIR, 'update.eup'))) return; // 阻塞 @@ -14,7 +13,7 @@ class Win32 implements IUpdateService { }); } - async exeUpdater() { + static async exeUpdater() { const { spawn } = cp; const child = spawn( @@ -29,5 +28,3 @@ class Win32 implements IUpdateService { child.unref(); } } - -export const WIN32 = new Win32(); diff --git a/src/cli/templates/default/src/main/tsconfig.json b/src/cli/templates/default/src/main/tsconfig.json index 7ba04ba..6d81c54 100644 --- a/src/cli/templates/default/src/main/tsconfig.json +++ b/src/cli/templates/default/src/main/tsconfig.json @@ -9,7 +9,10 @@ "skipLibCheck": true, "resolveJsonModule": true, "outDir": "../../build/main", - "removeComments": true + "removeComments": true, + "paths": { + "@/*": ["./*"] + } }, "include": ["./**/*"], "exclude": ["node_modules/**/*", "./tsconfig.json", "./package.json"] diff --git a/src/cli/templates/default/src/main/windows/MainWindow.ts b/src/cli/templates/default/src/main/windows/MainWindow.ts index 943824a..1115f21 100644 --- a/src/cli/templates/default/src/main/windows/MainWindow.ts +++ b/src/cli/templates/default/src/main/windows/MainWindow.ts @@ -39,7 +39,7 @@ export default class MainWindow { if (IS_PACKAGED) { mainWindow.loadFile(path.resolve(ASAR_ROOT_PATH, './renderer/index.html')); } else { - mainWindow.loadURL(`http://127.0.0.1:${ARGS['--port']}/`); + mainWindow.loadURL(`http://localhost:${ARGS['--port']}/`); mainWindow.once('ready-to-show', () => { mainWindow.webContents.openDevTools(); }); diff --git a/src/cli/templates/default/src/renderer/partials/TopBar/index.tsx b/src/cli/templates/default/src/renderer/partials/TopBar/index.tsx index a254477..cc82e5e 100644 --- a/src/cli/templates/default/src/renderer/partials/TopBar/index.tsx +++ b/src/cli/templates/default/src/renderer/partials/TopBar/index.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'; import styles from './index.module.less'; const { ipcRenderer } = window; -const DRAG_BAR_BUTTON_EVENT = 'DRAG_BAR.BUTTONS_ONCLICK'; +const DRAG_BAR_BUTTON_EVENT = 'COMMON.DRAG_BAR.BUTTONS_ONCLICK'; export const TopBar: React.FC = () => { const [isMaximized, setIsMaximized] = useState(false); diff --git a/src/creta/package.json b/src/creta/package.json index 5535624..b6720ee 100644 --- a/src/creta/package.json +++ b/src/creta/package.json @@ -39,8 +39,8 @@ "esbuild": "^0.17.18", "fs-extra": "^11.1.0", "inquirer": "8", - "ts-node": "^10.9.1", - "vite-electron-plugin": "^0.8.2" + "json5": "^2.2.3", + "ts-node": "^10.9.1" }, "keywords": [ "creta", diff --git a/src/creta/src/cli/constants.ts b/src/creta/src/cli/constants.ts index c6c679f..046fbe7 100644 --- a/src/creta/src/cli/constants.ts +++ b/src/creta/src/cli/constants.ts @@ -5,8 +5,12 @@ class Constants { private _gitName?: string; get gitName() { if (this._gitName !== undefined) return this._gitName; - this._gitName = cp.execSync('git config --global user.name', { encoding: 'utf8' }).trim(); - return this._gitName; + try { + this._gitName = cp.execSync('git config --global user.name', { encoding: 'utf8' }).trim(); + return this._gitName; + } catch { + return ''; + } } cretaRootDir = path.resolve(__dirname, '..'); @@ -15,6 +19,11 @@ class Constants { scriptsCwd = process.cwd(); + /** + * 主进程入口脚本 + */ + mainScriptEntryFile = path.resolve(this.scriptsCwd, 'src', 'main', 'core', 'bin.ts'); + /** * 默认 vite 配置 */ diff --git a/src/creta/src/cli/scripts/dev.ts b/src/creta/src/cli/scripts/dev.ts index 20598d0..8cf42ad 100644 --- a/src/creta/src/cli/scripts/dev.ts +++ b/src/creta/src/cli/scripts/dev.ts @@ -1,3 +1,4 @@ +import fs from 'fs'; import path from 'path'; import { createServer } from 'vite'; import constants from '../constants'; @@ -57,126 +58,138 @@ const main = async () => { await Promise.all([ // 2.2.1 tsc watch 主进程代码 - new Promise((resolve) => { + new Promise(async (resolve) => { const mainConfigPath = path.resolve(scriptsCwd, 'src', 'main', 'tsconfig.json'); - tscWatchPrograms.main = tscWatch(mainConfigPath, { - onAfterFirstCompile(program, defaultCallback?) { - defaultCallback?.(program); - resolve(); - }, - onAfterCompile(program, defaultCallback?) { - defaultCallback?.(program); - console.log( - '\x1B[1m\x1B[32m%s\x1B[39m\x1B[22m %s', - '[CRETA]', - '主进程代码编译完成,即将重新启动 electron 应用' - ); - beforeRelaunchElectron = launchElectron(); - }, - onBeforeFirstCompile( - defaultCallback, - rootNames?, - options?, - host?, - oldProgram?, - configFileParsingDiagnostics?, - projectReferences? - ) { - return defaultCallback( - rootNames, - options, - host, - oldProgram, - configFileParsingDiagnostics, - projectReferences - ); - }, - onBeforeCompile( - defaultCallback, - rootNames?, - options?, - host?, - oldProgram?, - configFileParsingDiagnostics?, - projectReferences? - ) { - beforeRelaunchElectron(); - console.log( - '\x1B[1m\x1B[35m%s\x1B[39m\x1B[22m %s', - '[CRETA]', - '检测到主进程代码发生变化,将重新进行编译' - ); - return defaultCallback( - rootNames, - options, - host, - oldProgram, - configFileParsingDiagnostics, - projectReferences - ); - }, - }); + tscWatchPrograms.main = tscWatch( + (await fs.promises.readdir(path.resolve(scriptsCwd, 'src', 'main'))) + .filter((file) => file.endsWith('.js') || file.endsWith('.ts')) + .map((file) => path.resolve(scriptsCwd, 'src', 'main', file)), + mainConfigPath, + { + onAfterFirstCompile(program, defaultCallback?) { + defaultCallback?.(program); + resolve(); + }, + onAfterCompile(program, defaultCallback?) { + defaultCallback?.(program); + console.log( + '\x1B[1m\x1B[32m%s\x1B[39m\x1B[22m %s', + '[CRETA]', + '主进程代码编译完成,即将重新启动 electron 应用' + ); + beforeRelaunchElectron = launchElectron(); + }, + onBeforeFirstCompile( + defaultCallback, + rootNames?, + options?, + host?, + oldProgram?, + configFileParsingDiagnostics?, + projectReferences? + ) { + return defaultCallback( + rootNames, + options, + host, + oldProgram, + configFileParsingDiagnostics, + projectReferences + ); + }, + onBeforeCompile( + defaultCallback, + rootNames?, + options?, + host?, + oldProgram?, + configFileParsingDiagnostics?, + projectReferences? + ) { + beforeRelaunchElectron(); + console.log( + '\x1B[1m\x1B[35m%s\x1B[39m\x1B[22m %s', + '[CRETA]', + '检测到主进程代码发生变化,将重新进行编译' + ); + return defaultCallback( + rootNames, + options, + host, + oldProgram, + configFileParsingDiagnostics, + projectReferences + ); + }, + } + ); }), // 2.2.2 tsc watch 预加载脚本代码 - new Promise((resolve) => { + new Promise(async (resolve) => { const preloadConfigPath = path.resolve(scriptsCwd, 'src', 'preload', 'tsconfig.json'); - tscWatchPrograms.preload = tscWatch(preloadConfigPath, { - onAfterFirstCompile(program, defaultCallback?) { - defaultCallback?.(program); - resolve(); - }, - onAfterCompile(program, defaultCallback?) { - defaultCallback?.(program); - console.log( - '\x1B[1m\x1B[32m%s\x1B[39m\x1B[22m %s', - '[CRETA]', - '预加载脚本代码编译完成,即将重新启动 electron 应用' - ); - beforeRelaunchElectron = launchElectron(); - }, - onBeforeFirstCompile( - defaultCallback, - rootNames?, - options?, - host?, - oldProgram?, - configFileParsingDiagnostics?, - projectReferences? - ) { - return defaultCallback( - rootNames, - options, - host, - oldProgram, - configFileParsingDiagnostics, - projectReferences - ); - }, - onBeforeCompile( - defaultCallback, - rootNames?, - options?, - host?, - oldProgram?, - configFileParsingDiagnostics?, - projectReferences? - ) { - beforeRelaunchElectron(); - console.log( - '\x1B[1m\x1B[35m%s\x1B[39m\x1B[22m %s', - '[CRETA]', - '检测到预加载脚本代码发生变化,将重新进行编译' - ); - return defaultCallback( - rootNames, - options, - host, - oldProgram, - configFileParsingDiagnostics, - projectReferences - ); - }, - }); + tscWatchPrograms.preload = tscWatch( + (await fs.promises.readdir(path.resolve(scriptsCwd, 'src', 'preload'))) + .filter((file) => file.endsWith('.js') || file.endsWith('.ts')) + .map((file) => path.resolve(scriptsCwd, 'src', 'preload', file)), + preloadConfigPath, + { + onAfterFirstCompile(program, defaultCallback?) { + defaultCallback?.(program); + resolve(); + }, + onAfterCompile(program, defaultCallback?) { + defaultCallback?.(program); + console.log( + '\x1B[1m\x1B[32m%s\x1B[39m\x1B[22m %s', + '[CRETA]', + '预加载脚本代码编译完成,即将重新启动 electron 应用' + ); + beforeRelaunchElectron = launchElectron(); + }, + onBeforeFirstCompile( + defaultCallback, + rootNames?, + options?, + host?, + oldProgram?, + configFileParsingDiagnostics?, + projectReferences? + ) { + return defaultCallback( + rootNames, + options, + host, + oldProgram, + configFileParsingDiagnostics, + projectReferences + ); + }, + onBeforeCompile( + defaultCallback, + rootNames?, + options?, + host?, + oldProgram?, + configFileParsingDiagnostics?, + projectReferences? + ) { + beforeRelaunchElectron(); + console.log( + '\x1B[1m\x1B[35m%s\x1B[39m\x1B[22m %s', + '[CRETA]', + '检测到预加载脚本代码发生变化,将重新进行编译' + ); + return defaultCallback( + rootNames, + options, + host, + oldProgram, + configFileParsingDiagnostics, + projectReferences + ); + }, + } + ); }), ]); diff --git a/src/creta/src/cli/utils/build.ts b/src/creta/src/cli/utils/build.ts index 42e1284..2d0084f 100644 --- a/src/creta/src/cli/utils/build.ts +++ b/src/creta/src/cli/utils/build.ts @@ -1,11 +1,11 @@ -import cp from 'child_process'; +import fs from 'fs'; import path from 'path'; import { build } from 'vite'; -import electron from 'vite-electron-plugin'; import constants from '../constants'; import { getCretaConfigs } from './getCretaConfigs'; +import { tscBuild } from './tsc'; -const { cretaRootDir, defaultViteConfig, scriptsCwd } = constants; +const { defaultViteConfig, scriptsCwd } = constants; export const buildRender = async () => { const { outDir = path.resolve(process.cwd(), 'build'), viteConfig = {} } = @@ -21,30 +21,17 @@ export const buildRender = async () => { }; export const buildPreload = async () => - new Promise((resolve) => { - cp.execSync('tsc', { - cwd: path.resolve(scriptsCwd, 'src', 'preload'), - stdio: 'inherit', - }); - resolve(); - }); - -export const buildMain = async () => { - const { outDir = path.resolve(scriptsCwd, 'build'), viteConfig = {} } = await getCretaConfigs(); + tscBuild( + (await fs.promises.readdir(path.resolve(scriptsCwd, 'src', 'preload'))) + .filter((file) => file.endsWith('.js') || file.endsWith('.ts')) + .map((file) => path.resolve(scriptsCwd, 'src', 'preload', file)), + path.resolve(scriptsCwd, 'src', 'preload', 'tsconfig.json') + ); - return build({ - root: cretaRootDir, - plugins: [ - electron({ - include: ['src/main'], - outDir: outDir, - logger: { - info: () => undefined, - }, - }), - ], - build: { - write: false, - }, - }); -}; +export const buildMain = async () => + tscBuild( + (await fs.promises.readdir(path.resolve(scriptsCwd, 'src', 'main'))) + .filter((file) => file.endsWith('.js') || file.endsWith('.ts')) + .map((file) => path.resolve(scriptsCwd, 'src', 'main', file)), + path.resolve(scriptsCwd, 'src', 'main', 'tsconfig.json') + ); diff --git a/src/creta/src/cli/utils/index.ts b/src/creta/src/cli/utils/index.ts index e3efd71..0e6711f 100644 --- a/src/creta/src/cli/utils/index.ts +++ b/src/creta/src/cli/utils/index.ts @@ -2,4 +2,4 @@ export * from './build'; export * from './getCretaConfigs'; export * from './getScriptsPath'; export * from './runElectron'; -export * from './tscWatch'; +export * from './tsc'; diff --git a/src/creta/src/cli/utils/tsc.ts b/src/creta/src/cli/utils/tsc.ts new file mode 100644 index 0000000..c861609 --- /dev/null +++ b/src/creta/src/cli/utils/tsc.ts @@ -0,0 +1,474 @@ +import chalk from 'chalk'; +import fs from 'fs'; +import json5 from 'json5'; +import path from 'path'; +import ts from 'typescript'; + +async function loadTsConfig(configPath: string) { + const tsconfigJson = await fs.promises.readFile(configPath, { encoding: 'utf8' }); + const tsconfig = json5.parse(tsconfigJson); + return tsconfig; +} + +async function analyzeTsCompilerOptions(configPath: string) { + const tsconfig = await loadTsConfig(configPath); + let { + /* 枚举字段 */ + importsNotUsedAsValues, + jsx, + module, + moduleResolution, + moduleDetection, + newLine, + target, + /* 路径字段 */ + tsBuildInfoFile, + rootDir, + baseUrl = './', + rootDirs, + typeRoots, + types, + outFile, + outDir, + declarationDir, + /* 路径别名 */ + paths = {}, + ...options + } = tsconfig.compilerOptions || {}; + + if (importsNotUsedAsValues) { + switch (importsNotUsedAsValues.toLowerCase()) { + case 'remove': + importsNotUsedAsValues = ts.ImportsNotUsedAsValues.Remove; + break; + case 'preserve': + importsNotUsedAsValues = ts.ImportsNotUsedAsValues.Preserve; + break; + case 'error': + importsNotUsedAsValues = ts.ImportsNotUsedAsValues.Error; + break; + } + } + if (jsx) { + switch (jsx.toLowerCase()) { + case 'react': + jsx = ts.JsxEmit.React; + break; + case 'react-jsx': + jsx = ts.JsxEmit.ReactJSX; + break; + case 'react-jsxdev': + jsx = ts.JsxEmit.ReactJSXDev; + break; + case 'preserve': + jsx = ts.JsxEmit.Preserve; + break; + case 'react-native': + jsx = ts.JsxEmit.ReactNative; + break; + } + } + if (module) { + switch (module.toLowerCase()) { + case 'commonjs': + module = ts.ModuleKind.CommonJS; + break; + case 'umd': + module = ts.ModuleKind.UMD; + break; + case 'amd': + module = ts.ModuleKind.AMD; + break; + case 'system': + module = ts.ModuleKind.System; + break; + case 'esnext': + module = ts.ModuleKind.ESNext; + break; + case 'es2015': + case 'es6': + module = ts.ModuleKind.ES2015; + break; + case 'es2020': + module = ts.ModuleKind.ES2020; + break; + case 'es2022': + module = ts.ModuleKind.ES2022; + break; + case 'node16': + module = ts.ModuleKind.Node16; + break; + case 'nodenext': + module = ts.ModuleKind.NodeNext; + break; + } + } + if (moduleResolution) { + switch (moduleResolution.toLowerCase()) { + case 'node16': + moduleResolution = ts.ModuleResolutionKind.Node16; + break; + case 'nodenext': + moduleResolution = ts.ModuleResolutionKind.NodeNext; + break; + case 'node10': + case 'node': + moduleResolution = ts.ModuleResolutionKind.NodeJs; + break; + case 'classic': + moduleResolution = ts.ModuleResolutionKind.Classic; + break; + } + } + if (moduleDetection) { + switch (moduleDetection.toLowerCase()) { + case 'auto': + moduleDetection = ts.ModuleDetectionKind.Auto; + break; + case 'force': + moduleDetection = ts.ModuleDetectionKind.Force; + break; + case 'legacy': + moduleDetection = ts.ModuleDetectionKind.Legacy; + break; + } + } + if (newLine) { + switch (newLine.toLowerCase()) { + case 'crlf': + newLine = ts.NewLineKind.CarriageReturnLineFeed; + break; + case 'lf': + newLine = ts.NewLineKind.LineFeed; + break; + } + } + if (target) { + switch (target.toLowerCase()) { + case 'es3': + target = ts.ScriptTarget.ES3; + break; + case 'es5': + target = ts.ScriptTarget.ES5; + break; + case 'es6': + case 'es2015': + target = ts.ScriptTarget.ES2015; + break; + case 'es2016': + target = ts.ScriptTarget.ES2016; + break; + case 'es2017': + target = ts.ScriptTarget.ES2017; + break; + case 'es2018': + target = ts.ScriptTarget.ES2018; + break; + case 'es2019': + target = ts.ScriptTarget.ES2019; + break; + case 'es2020': + target = ts.ScriptTarget.ES2020; + break; + case 'es2021': + target = ts.ScriptTarget.ES2021; + break; + case 'es2022': + target = ts.ScriptTarget.ES2022; + break; + case 'esnext': + target = ts.ScriptTarget.ESNext; + break; + } + } + + baseUrl = path.isAbsolute(baseUrl) ? baseUrl : path.resolve(configPath, '..', baseUrl); + + function resolvePath(originalPath: string | undefined) { + if (!originalPath) return originalPath; + + return path.isAbsolute(originalPath) ? originalPath : path.resolve(baseUrl, originalPath); + } + + tsBuildInfoFile = resolvePath(tsBuildInfoFile); + rootDir = resolvePath(rootDir); + rootDirs = rootDirs?.map((dir: string) => resolvePath(dir)).filter(Boolean) as + | string[] + | undefined; + typeRoots = typeRoots?.map((root: string) => resolvePath(root)).filter(Boolean) as + | string[] + | undefined; + types = types?.map((t: string) => resolvePath(t)).filter(Boolean) as string[] | undefined; + outFile = resolvePath(outFile); + outDir = resolvePath(outDir); + declarationDir = resolvePath(declarationDir); + + return { + importsNotUsedAsValues, + jsx, + module, + moduleResolution, + moduleDetection, + newLine, + target, + + tsBuildInfoFile, + rootDir, + baseUrl, + rootDirs, + typeRoots, + types, + outFile, + outDir, + declarationDir, + + paths, + + ...options, + } as ts.CompilerOptions; +} + +const formatHost: ts.FormatDiagnosticsHost = { + getCanonicalFileName: (path) => path, + getCurrentDirectory: ts.sys.getCurrentDirectory, + getNewLine: () => ts.sys.newLine, +}; + +const reportDiagnostic = (diagnostic: ts.Diagnostic) => { + console.log( + chalk.red( + 'Error', + diagnostic.code, + ':', + ts.flattenDiagnosticMessageText(diagnostic.messageText, formatHost.getNewLine()) + ) + ); +}; + +const reportWatchStatusChanged = (diagnostic: ts.Diagnostic) => { + let message = ts.formatDiagnostic(diagnostic, formatHost); + if (message.indexOf('TS6194') > 0) { + message = message.replace(/message\sTS[0-9]{4}:(.+)(\s+)$/, '$1').trim(); + console.log('\x1B[36m%s\x1B[39m', message); + } +}; + +const getAliasTransformers = (options: ts.CompilerOptions) => { + const aliasMap: { [key: string]: string[] } = {}; + Object.keys(options.paths || {}).forEach((key) => { + const resolvedPath = path.resolve( + options.baseUrl!, + options.paths![key][0].replace(/\/\*$/, '/') + ); + aliasMap[key.replace(/\/\*$/, '/')] = [resolvedPath]; + }); + + const aliasArr = Object.keys(aliasMap).sort((a, b) => b.split('/').length - a.split('/').length); + + const customTransfers: ts.CustomTransformers = { + before: [ + (context: ts.TransformationContext): ts.CustomTransformer => { + return { + transformBundle(node) { + return node; + }, + transformSourceFile(node) { + const visitor = (node: ts.Node) => { + if (ts.isImportDeclaration(node)) { + const importPath = node.moduleSpecifier?.getText().replace(/["']/g, '') ?? ''; + + for (const key of aliasArr) { + if ( + Object.prototype.hasOwnProperty.call(aliasMap, key) && + importPath.startsWith(key) + ) { + const alias = aliasMap[key][0]; + + const resolvedPath = path.resolve( + alias, + importPath.slice(importPath.indexOf(key) + key.length) + ); + + const relativePath = path.relative( + path.dirname( + // @ts-ignore + node.parent.resolvedPath + ), + resolvedPath + ); + + return ts.factory.updateImportDeclaration( + node, + node.modifiers, + node.importClause, + ts.factory.createStringLiteral( + relativePath.startsWith(`.${path.sep}`) || + relativePath.startsWith(`..${path.sep}`) + ? relativePath + : `./${relativePath}` + ), + node.assertClause + ); + } + } + } + + return node; + }; + return ts.visitEachChild(node, visitor, context); + }, + }; + }, + ], + }; + + return customTransfers; +}; + +type TBeforeCompileCallback = ( + defaultCallback: ts.CreateProgram, + rootNames?: readonly string[] | undefined, + options?: ts.CompilerOptions | undefined, + host?: ts.CompilerHost | undefined, + oldProgram?: ts.SemanticDiagnosticsBuilderProgram | undefined, + configFileParsingDiagnostics?: readonly ts.Diagnostic[] | undefined, + projectReferences?: readonly ts.ProjectReference[] | undefined +) => ts.SemanticDiagnosticsBuilderProgram; + +type TAfterCompileCallback = ( + program: ts.SemanticDiagnosticsBuilderProgram, + defaultCallback?: TAfterCompileCallback +) => void; + +/** + * tsc watch + * @param configPath tsconfig.json 路径 + * @param callbacks 关键节点回调 + * @returns + */ +export const tscWatch = async ( + rootFiles: string[], + configPath: string, + callbacks: { + onBeforeCompile?: TBeforeCompileCallback; + onAfterCompile?: TAfterCompileCallback; + onBeforeFirstCompile?: TBeforeCompileCallback; + onAfterFirstCompile?: TAfterCompileCallback; + } = {} +) => { + const options = await analyzeTsCompilerOptions(configPath); + + const createProgram = ts.createSemanticDiagnosticsBuilderProgram; + const host = ts.createWatchCompilerHost( + rootFiles, + options, + ts.sys, + createProgram, + reportDiagnostic, + reportWatchStatusChanged, + undefined, + undefined + ); + + const aliasTransformers = getAliasTransformers(options); + + const { onAfterCompile, onBeforeCompile } = callbacks; + const onBeforeFirstCompile = callbacks.onBeforeFirstCompile || onBeforeCompile; + const onAfterFirstCompile = callbacks.onAfterFirstCompile || onAfterCompile; + + let firstCompilation = true; + + const originCreateProgram = host.createProgram; + const defaultCreateProgram = ( + rootNames: readonly string[] | undefined, + options: ts.CompilerOptions | undefined, + host: ts.CompilerHost | undefined, + oldProgram: ts.SemanticDiagnosticsBuilderProgram | undefined + ) => { + const newProgram = originCreateProgram(rootNames, options, host, oldProgram); + const originEmit = newProgram.emit; + newProgram.emit = ( + targetSourceFile?: ts.SourceFile | undefined, + writeFile?: ts.WriteFileCallback | undefined, + cancellationToken?: ts.CancellationToken | undefined, + emitOnlyDtsFiles?: boolean | undefined, + customTransformers?: ts.CustomTransformers | undefined + ) => { + const transformers: ts.CustomTransformers = { + ...(customTransformers || {}), + }; + transformers.before = (transformers.before || []).concat(aliasTransformers.before || []); + + return originEmit( + targetSourceFile, + writeFile, + cancellationToken, + emitOnlyDtsFiles, + transformers + ); + }; + return newProgram; + }; + + host.createProgram = (rootNames, options, host, oldProgram) => { + if (onBeforeCompile) { + if (!firstCompilation) + return onBeforeCompile(defaultCreateProgram, rootNames, options, host, oldProgram); + + return onBeforeFirstCompile!(defaultCreateProgram, rootNames, options, host, oldProgram); + } + + return defaultCreateProgram(rootNames, options, host, oldProgram); + }; + const defaultAfterProgramCreate = host.afterProgramCreate; + host.afterProgramCreate = (program) => { + if (onAfterCompile) { + if (!firstCompilation) return onAfterCompile(program, defaultAfterProgramCreate); + + firstCompilation = false; + return onAfterFirstCompile!(program, defaultAfterProgramCreate); + } + + defaultAfterProgramCreate?.(program); + }; + + return ts.createWatchProgram(host); +}; + +/** + * tsc build + * @param configPath tsconfig.json 路径 + * @returns + */ +export const tscBuild = async (rootFiles: string[], configPath: string) => { + const options = await analyzeTsCompilerOptions(configPath); + + const host = ts.createCompilerHost(options); + + const program = ts.createProgram({ + rootNames: options.allowJs ? rootFiles : rootFiles.filter((file) => file.endsWith('.ts')), + options, + host, + }); + + const aliasTransformers = getAliasTransformers(options); + + const emitResult = program.emit(undefined, undefined, undefined, undefined, { + before: [...(aliasTransformers.before || [])], + }); + + const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); + + allDiagnostics.forEach((diagnostic) => { + if (diagnostic.file) { + let { line, character } = ts.getLineAndCharacterOfPosition( + diagnostic.file, + diagnostic.start! + ); + let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); + console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); + } else { + console.log(ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')); + } + }); +}; diff --git a/src/creta/src/cli/utils/tscWatch.ts b/src/creta/src/cli/utils/tscWatch.ts deleted file mode 100644 index 4d5c73c..0000000 --- a/src/creta/src/cli/utils/tscWatch.ts +++ /dev/null @@ -1,90 +0,0 @@ -import ts from 'typescript'; - -const formatHost: ts.FormatDiagnosticsHost = { - getCanonicalFileName: (path) => path, - getCurrentDirectory: ts.sys.getCurrentDirectory, - getNewLine: () => ts.sys.newLine, -}; - -const reportDiagnostic = (diagnostic: ts.Diagnostic) => { - console.error( - 'Error', - diagnostic.code, - ':', - ts.flattenDiagnosticMessageText(diagnostic.messageText, formatHost.getNewLine()) - ); -}; - -const reportWatchStatusChanged = (diagnostic: ts.Diagnostic) => { - let message = ts.formatDiagnostic(diagnostic, formatHost); - if (message.indexOf('TS6194') > 0) { - message = message.replace(/message\sTS[0-9]{4}:(.+)(\s+)$/, '$1').trim(); - console.log('\x1B[36m%s\x1B[39m', message); - } -}; - -type TBeforeCompileCallback = ( - defaultCallback: ts.CreateProgram, - rootNames?: readonly string[] | undefined, - options?: ts.CompilerOptions | undefined, - host?: ts.CompilerHost | undefined, - oldProgram?: ts.SemanticDiagnosticsBuilderProgram | undefined, - configFileParsingDiagnostics?: readonly ts.Diagnostic[] | undefined, - projectReferences?: readonly ts.ProjectReference[] | undefined -) => ts.SemanticDiagnosticsBuilderProgram; - -type TAfterCompileCallback = ( - program: ts.SemanticDiagnosticsBuilderProgram, - defaultCallback?: TAfterCompileCallback -) => void; - -export const tscWatch = ( - configPath: string, - callbacks: { - onBeforeCompile?: TBeforeCompileCallback; - onAfterCompile?: TAfterCompileCallback; - onBeforeFirstCompile?: TBeforeCompileCallback; - onAfterFirstCompile?: TAfterCompileCallback; - } = {} -) => { - const createProgram = ts.createSemanticDiagnosticsBuilderProgram; - const host = ts.createWatchCompilerHost( - configPath, - {}, - ts.sys, - createProgram, - reportDiagnostic, - reportWatchStatusChanged - ); - - const { onAfterCompile, onBeforeCompile } = callbacks; - const onBeforeFirstCompile = callbacks.onBeforeFirstCompile || onBeforeCompile; - const onAfterFirstCompile = callbacks.onAfterFirstCompile || onAfterCompile; - - let firstCompilation = true; - - const defaultCreateProgram = host.createProgram; - host.createProgram = (rootNames, options, host, oldProgram) => { - if (onBeforeCompile) { - if (!firstCompilation) - return onBeforeCompile(defaultCreateProgram, rootNames, options, host, oldProgram); - - return onBeforeFirstCompile!(defaultCreateProgram, rootNames, options, host, oldProgram); - } - - return defaultCreateProgram(rootNames, options, host, oldProgram); - }; - const defaultAfterProgramCreate = host.afterProgramCreate; - host.afterProgramCreate = (program) => { - if (onAfterCompile) { - if (!firstCompilation) return onAfterCompile(program, defaultAfterProgramCreate); - - firstCompilation = false; - return onAfterFirstCompile!(program, defaultAfterProgramCreate); - } - - defaultAfterProgramCreate?.(program); - }; - - return ts.createWatchProgram(host); -};