diff --git a/.eslintrc b/.eslintrc index c799fe5..9bcdb46 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,6 @@ { - "extends": "eslint-config-egg" + "extends": [ + "eslint-config-egg/typescript", + "eslint-config-egg/lib/rules/enforce-node-prefix" + ] } diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index c8630f5..1a2cd21 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -12,6 +12,6 @@ jobs: uses: node-modules/github-actions/.github/workflows/node-test.yml@master with: os: 'ubuntu-latest, macos-latest' - version: '14, 16, 18, 20, 22' + version: '18.19.0, 20, 22' secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 0fe7e40..c010914 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,8 @@ node_modules/ coverage/ test/fixtures/**/run .DS_Store +.tshy* +.eslintcache +dist +package-lock.json +.package-lock.json diff --git a/README.md b/README.md index aa2cb2e..8855287 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# egg-development +# @eggjs/development [![NPM version][npm-image]][npm-url] [![Node.js CI](https://github.com/eggjs/egg-development/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/egg-development/actions/workflows/nodejs.yml) @@ -6,6 +6,7 @@ [![Known Vulnerabilities][snyk-image]][snyk-url] [![npm download][download-image]][download-url] [![Node.js Version](https://img.shields.io/node/v/egg-development.svg?style=flat)](https://nodejs.org/en/download/) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com) [npm-image]: https://img.shields.io/npm/v/egg-development.svg?style=flat-square [npm-url]: https://npmjs.org/package/egg-development @@ -75,6 +76,6 @@ Please open an issue [here](https://github.com/eggjs/egg/issues). ## Contributors -[![Contributors](https://contrib.rocks/image?repo=eggjs/egg-development)](https://github.com/eggjs/egg-development/graphs/contributors) +[![Contributors](https://contrib.rocks/image?repo=eggjs/development)](https://github.com/eggjs/development/graphs/contributors) Made with [contributors-img](https://contrib.rocks). diff --git a/config/config.default.js b/config/config.default.js deleted file mode 100755 index 4a139be..0000000 --- a/config/config.default.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @member Config#development - * @property {Array} watchDirs - dirs needed watch, when files under these change, application will reload, use relative path - * @property {Array} ignoreDirs - dirs don't need watch, including subdirectories, use relative path - * @property {Boolean} fastReady - don't wait all plugins ready, default is false. - * @property {Boolean} reloadOnDebug - whether reload on debug, default is true. - * @property {Boolean} overrideDefault - whether override default watchDirs, default is false. - * @property {Boolean} overrideIgnore - whether override default ignoreDirs, default is false. - * @property {Array|String} reloadPattern - whether to reload, use https://github.com/sindresorhus/multimatch - */ -exports.development = { - watchDirs: [], - ignoreDirs: [], - fastReady: false, - reloadOnDebug: true, - overrideDefault: false, - overrideIgnore: false, - reloadPattern: undefined, -}; diff --git a/package.json b/package.json index 9cb0bc3..1b447cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,9 @@ { - "name": "egg-development", + "name": "@eggjs/development", "version": "3.0.2", + "publishConfig": { + "access": "public" + }, "description": "development tool for egg", "eggPlugin": { "name": "development", @@ -9,7 +12,12 @@ ], "dependencies": [ "watcher" - ] + ], + "exports": { + "import": "./dist/esm", + "require": "./dist/commonjs", + "typescript": "./src" + } }, "keywords": [ "egg", @@ -18,42 +26,72 @@ "eggPlugin" ], "dependencies": { + "@eggjs/core": "^6.2.11", "debounce": "^1.1.0", "multimatch": "^5.0.0", "utility": "^2.4.0" }, "devDependencies": { - "@types/node": "^22.10.2", - "egg": "3", - "egg-bin": "6", - "egg-mock": "5", + "@arethetypeswrong/cli": "^0.17.2", + "@eggjs/bin": "7", + "@eggjs/mock": "6", + "@eggjs/supertest": "8", + "@eggjs/tsconfig": "1", + "@types/mocha": "10", + "@types/node": "22", + "egg": "beta", "eslint": "8", - "eslint-config-egg": "12", - "supertest": "^3.4.2" + "eslint-config-egg": "14", + "rimraf": "^6.0.1", + "tshy": "3", + "tshy-after": "1", + "typescript": "5" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.19.0" }, "scripts": { - "test": "npm run lint -- --fix && npm run test-local", - "test-local": "egg-bin test --ts false", - "cov": "egg-bin cov --ts false", - "lint": "eslint .", - "ci": "npm run lint && npm run cov" + "lint": "eslint --cache src test --ext .ts", + "pretest": "npm run clean && npm run lint -- --fix", + "test": "egg-bin test", + "preci": "npm run clean && npm run lint", + "ci": "egg-bin cov", + "postci": "npm run prepublishOnly && npm run clean", + "clean": "rimraf dist", + "prepublishOnly": "tshy && tshy-after && attw --pack" }, "repository": { "type": "git", - "url": "git+https://github.com/eggjs/egg-development.git" + "url": "git+https://github.com/eggjs/development.git" }, - "files": [ - "app", - "config", - "lib", - "agent.js", - "app.js" - ], "bugs": "https://github.com/eggjs/egg/issues", - "homepage": "https://github.com/eggjs/egg-development#readme", + "homepage": "https://github.com/eggjs/development#readme", "author": "jtyjty99999", - "license": "MIT" + "license": "MIT", + "type": "module", + "tshy": { + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + } + }, + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "src" + ], + "types": "./dist/commonjs/index.d.ts", + "main": "./dist/commonjs/index.js" } diff --git a/agent.js b/src/agent.ts similarity index 92% rename from agent.js rename to src/agent.ts index 7fe6ef7..f5d29ef 100755 --- a/agent.js +++ b/src/agent.ts @@ -1,8 +1,8 @@ -const path = require('node:path'); -const fs = require('node:fs/promises'); -const debounce = require('debounce'); -const multimatch = require('multimatch'); -const { exists } = require('utility'); +import path from 'node:path'; +import fs from 'node:fs/promises'; +import debounce from 'debounce'; +import multimatch from 'multimatch'; +import { exists } from 'utility'; module.exports = agent => { // clean all timing json diff --git a/app.js b/src/app.ts similarity index 100% rename from app.js rename to src/app.ts diff --git a/app/middleware/egg_loader_trace.js b/src/app/middleware/egg_loader_trace.ts similarity index 69% rename from app/middleware/egg_loader_trace.js rename to src/app/middleware/egg_loader_trace.ts index b49f4e5..d479264 100644 --- a/app/middleware/egg_loader_trace.js +++ b/src/app/middleware/egg_loader_trace.ts @@ -1,20 +1,23 @@ -const path = require('node:path'); -const fs = require('node:fs/promises'); -const { readJSON } = require('utility'); +import path from 'node:path'; +import fs from 'node:fs/promises'; +import { readJSON } from 'utility'; +import type { EggCore, MiddlewareFunc } from '@eggjs/core'; -module.exports = (_, app) => { +export default (_: unknown, app: EggCore): MiddlewareFunc => { return async (ctx, next) => { - if (ctx.path !== '/__loader_trace__') return await next(); + if (ctx.path !== '/__loader_trace__') { + return await next(); + } const template = await fs.readFile(path.join(__dirname, '../../lib/loader_trace.html'), 'utf8'); const data = await loadTimingData(app); ctx.body = template.replace('{{placeholder}}', JSON.stringify(data)); }; }; -async function loadTimingData(app) { +async function loadTimingData(app: EggCore) { const rundir = app.config.rundir; const files = await fs.readdir(rundir); - const data = []; + const data: any[] = []; for (const file of files) { if (!/^(agent|application)_timing/.test(file)) continue; const json = await readJSON(path.join(rundir, file)); diff --git a/src/config/config.default.ts b/src/config/config.default.ts new file mode 100755 index 0000000..b10b9c7 --- /dev/null +++ b/src/config/config.default.ts @@ -0,0 +1,52 @@ +export interface DevelopmentConfig { + /** + * dirs needed watch, when files under these change, application will reload, use relative path + */ + watchDirs: string[]; + /** + * dirs don't need watch, including subdirectories, use relative path + */ + ignoreDirs: string[]; + /** + * don't wait all plugins ready, default is false. + */ + fastReady: boolean; + /** + * whether reload on debug, default is true. + */ + reloadOnDebug: boolean; + /** + * whether override default watchDirs, default is false. + */ + overrideDefault: boolean; + /** + * whether override default ignoreDirs, default is false. + */ + overrideIgnore: boolean; + /** + * whether to reload, use https://github.com/sindresorhus/multimatch + */ + reloadPattern?: string[] | string; +} + +/** + * @member Config#development + * @property {Array} watchDirs - dirs needed watch, when files under these change, application will reload, use relative path + * @property {Array} ignoreDirs - dirs don't need watch, including subdirectories, use relative path + * @property {Boolean} fastReady - don't wait all plugins ready, default is false. + * @property {Boolean} reloadOnDebug - whether reload on debug, default is true. + * @property {Boolean} overrideDefault - whether override default watchDirs, default is false. + * @property {Boolean} overrideIgnore - whether override default ignoreDirs, default is false. + * @property {Array|String} reloadPattern - whether to reload, use https://github.com/sindresorhus/multimatch + */ +export default { + development: { + watchDirs: [], + ignoreDirs: [], + fastReady: false, + reloadOnDebug: true, + overrideDefault: false, + overrideIgnore: false, + reloadPattern: undefined, + } as DevelopmentConfig, +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ce5fb25 --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +import './types.js'; diff --git a/lib/loader_trace.html b/src/lib/loader_trace.html similarity index 100% rename from lib/loader_trace.html rename to src/lib/loader_trace.html diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..82f8714 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,8 @@ +import type { DevelopmentConfig } from './config/config.default.js'; + +declare module '@eggjs/core' { + // add EggAppConfig overrides types + interface EggAppConfig { + development: DevelopmentConfig; + } +} diff --git a/test/development-ts.test.ts b/test/development-ts.test.ts new file mode 100755 index 0000000..cf82b29 --- /dev/null +++ b/test/development-ts.test.ts @@ -0,0 +1,58 @@ +import fs from 'node:fs/promises'; +import { strict as assert } from 'node:assert'; +import { scheduler } from 'node:timers/promises'; +import { mm, MockApplication } from '@eggjs/mock'; +import { escape, getFilepath } from './utils.js'; + +describe('test/development-ts.test.ts', () => { + let app: MockApplication; + before(() => { + mm.env('local'); + app = mm.cluster({ + baseDir: 'development-ts', + }); + return app.ready(); + }); + after(() => app.close()); + afterEach(mm.restore); + // for debounce + afterEach(() => scheduler.wait(500)); + + it('should reload when change service', async () => { + const filepath = getFilepath('development-ts/app/service/a.ts'); + await fs.writeFile(filepath, ''); + await scheduler.wait(5000); + + await fs.unlink(filepath); + app.expect('stdout', new RegExp(escape(`reload worker because ${filepath}`))); + }); + + it('should not reload when change assets', async () => { + const filepath = getFilepath('development-ts/app/assets/b.js'); + await fs.writeFile(filepath, ''); + await scheduler.wait(5000); + + await fs.unlink(filepath); + app.notExpect('stdout', new RegExp(escape(`reload worker because ${filepath}`))); + }); + + it('should reload once when 2 file change', async () => { + const filepath = getFilepath('development-ts/app/service/c.js'); + const filepath1 = getFilepath('development-ts/app/service/d.js'); + await fs.writeFile(filepath, ''); + // set a timeout for watcher's interval + await scheduler.wait(1000); + await fs.writeFile(filepath1, ''); + + await scheduler.wait(2000); + await fs.unlink(filepath); + await fs.unlink(filepath1); + + assert.equal(count(app.stdout, 'reload worker'), 2); + }); +}); + +function count(str: string, match: string) { + const m = str.match(new RegExp(match, 'g')); + return m ? m.length : 0; +} diff --git a/test/fixtures/development-ts/app/assets/.gitkeep b/test/fixtures/development-ts/app/assets/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/test/fixtures/development-ts/app/public/foo.js b/test/fixtures/development-ts/app/public/foo.js new file mode 100755 index 0000000..fb1c35f --- /dev/null +++ b/test/fixtures/development-ts/app/public/foo.js @@ -0,0 +1 @@ +alert('bar'); diff --git a/test/fixtures/development-ts/app/router.ts b/test/fixtures/development-ts/app/router.ts new file mode 100755 index 0000000..235cc82 --- /dev/null +++ b/test/fixtures/development-ts/app/router.ts @@ -0,0 +1,11 @@ +import { Application } from 'egg'; + +export default (app: Application) => { + app.get('/foo.js', async ctx => { + ctx.body = 'foo.js'; + }); + + app.get('/foo', async ctx => { + ctx.body = 'foo'; + }); +}; diff --git a/test/fixtures/development-ts/app/service/.gitkeep b/test/fixtures/development-ts/app/service/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/test/fixtures/development-ts/config/config.default.ts b/test/fixtures/development-ts/config/config.default.ts new file mode 100644 index 0000000..c791c22 --- /dev/null +++ b/test/fixtures/development-ts/config/config.default.ts @@ -0,0 +1,10 @@ +import '../../../../src/index.js'; + +import { EggAppConfig } from 'egg'; + +export default { + keys: 'foo,bar', + development: { + fastReady: false, + }, +} as EggAppConfig; diff --git a/test/fixtures/development-ts/package.json b/test/fixtures/development-ts/package.json new file mode 100755 index 0000000..35c06e7 --- /dev/null +++ b/test/fixtures/development-ts/package.json @@ -0,0 +1,4 @@ +{ + "name": "development-ts", + "type": "module" +} diff --git a/test/fixtures/development-ts/tsconfig.json b/test/fixtures/development-ts/tsconfig.json new file mode 100644 index 0000000..ff41b73 --- /dev/null +++ b/test/fixtures/development-ts/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@eggjs/tsconfig", + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext" + } +} diff --git a/test/utils.js b/test/utils.js deleted file mode 100644 index a9d4f2a..0000000 --- a/test/utils.js +++ /dev/null @@ -1,11 +0,0 @@ -exports.escape = str => { - return str - .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') - .replace(/-/g, '\\x2d'); -}; - -exports.sleep = ms => { - return new Promise(resolve => { - setTimeout(resolve, ms); - }); -}; diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 0000000..d1cfc08 --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,15 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const fixtures = path.join(__dirname, 'fixtures'); + +export function getFilepath(name: string) { + return path.join(fixtures, name); +} + +export function escape(str: string) { + return str + .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') + .replace(/-/g, '\\x2d'); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ff41b73 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@eggjs/tsconfig", + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext" + } +}