diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 01b9084..22c1cce 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -3,16 +3,14 @@ name: CI on: push: branches: [ master ] - pull_request: branches: [ master ] - workflow_dispatch: {} - jobs: Job: name: Node.js - uses: artusjs/github-actions/.github/workflows/node-test.yml@v1 + uses: node-modules/github-actions/.github/workflows/node-test.yml@master with: - os: 'ubuntu-latest' - version: '16.13.0, 16, 18, 20' + version: '18.19.0, 18, 20, 22' + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1612587..a2bf04a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,14 +4,10 @@ on: push: branches: [ master ] - workflow_dispatch: {} - jobs: release: name: Node.js - uses: artusjs/github-actions/.github/workflows/node-release.yml@v1 + uses: eggjs/github-actions/.github/workflows/node-release.yml@master secrets: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} GIT_TOKEN: ${{ secrets.GIT_TOKEN }} - with: - checkTest: false diff --git a/.gitignore b/.gitignore index 6728359..a007a65 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ test/fixtures/**/*.yml .nyc_output/ test/fixtures/tmp/ lib/ +.tshy* +dist/ diff --git a/package.json b/package.json index d9e1927..deb7285 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,22 @@ { "name": "@eggjs/utils", "version": "3.0.1", + "engine": { + "node": ">=18.19.0" + }, + "publishConfig": { + "access": "public" + }, "description": "Utils for all egg projects", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "files": [ - "lib" - ], "scripts": { - "lint": "eslint .", - "pretest": "npm run lint && npm run tsc", - "test": "egg-bin test", - "preci": "npm run lint && npm run tsc", - "ci": "egg-bin cov", - "tsc": "tsc", - "clean": "tsc --build --clean", - "prepublishOnly": "npm run clean && npm run tsc", - "contributor": "git-contributor" + "lint": "eslint src test --ext ts", + "pretest": "npm run prepublishOnly", + "test": "npm run lint -- --fix && npm run test-local", + "test-local": "egg-bin test", + "preci": "npm run prepublishOnly", + "ci": "npm run lint && egg-bin cov && npm run prepublishOnly", + "contributor": "git-contributor", + "prepublishOnly": "tshy && tshy-after" }, "keywords": [ "egg", @@ -25,29 +25,52 @@ "author": "fengmk2 (https://github.com/fengmk2)", "repository": { "type": "git", - "url": "https://github.com/eggjs/egg-utils.git" + "url": "git://github.com/eggjs/egg-utils.git" }, "license": "MIT", "dependencies": {}, "devDependencies": { - "@eggjs/tsconfig": "^1.3.3", - "@types/mocha": "^10.0.1", - "@types/node": "^20.2.5", - "coffee": "^5.5.0", - "egg-bin": "^6.4.0", - "eslint": "^8.41.0", - "eslint-config-egg": "^12.2.1", - "git-contributor": "^2.1.5", - "mm": "^3.3.0", - "npm": "^9.6.7", - "npminstall": "^7.9.0", - "runscript": "^1.5.3", - "typescript": "^5.0.4" + "@eggjs/tsconfig": "1", + "@types/mocha": "10", + "@types/node": "20", + "coffee": "5", + "egg-bin": "6", + "eslint": "8", + "eslint-config-egg": "13", + "git-contributor": "2", + "mm": "3", + "npminstall": "7", + "runscript": "1", + "tshy": "1", + "tshy-after": "1", + "typescript": "5" }, - "engine": { - "node": ">=16.13.0" + "files": [ + "dist", + "src" + ], + "type": "module", + "tshy": { + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts" + } }, - "publishConfig": { - "access": "public" - } + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "source": "./src/index.ts", + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "source": "./src/index.ts", + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + } + }, + "main": "./dist/commonjs/index.js", + "types": "./dist/commonjs/index.d.ts" } diff --git a/src/deprecated.ts b/src/deprecated.ts index ad3d3e5..d01961d 100644 --- a/src/deprecated.ts +++ b/src/deprecated.ts @@ -1,6 +1,6 @@ import path from 'node:path'; import { existsSync, readdirSync } from 'node:fs'; -import { readJSONSync } from './utils'; +import { readJSONSync } from './utils.js'; /** * Try to get framework dir path diff --git a/src/framework.ts b/src/framework.ts index 4bbe3cd..9286b95 100644 --- a/src/framework.ts +++ b/src/framework.ts @@ -1,7 +1,7 @@ import path from 'node:path'; import assert from 'node:assert'; import { existsSync } from 'node:fs'; -import { readJSONSync } from './utils'; +import { readJSONSync } from './utils.js'; const initCwd = process.cwd(); diff --git a/src/import.ts b/src/import.ts new file mode 100644 index 0000000..393c54c --- /dev/null +++ b/src/import.ts @@ -0,0 +1,82 @@ +import { debuglog } from 'node:util'; +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const debug = debuglog('@eggjs/utils:loader'); + +let _customRequire: NodeRequire; + +export interface ImportResolveOptions { + paths?: string[]; +} + +export interface ImportModuleOptions extends ImportResolveOptions { + // only import export default object + importDefaultOnly?: boolean; +} + +export function importResolve(filepath: string, options?: ImportResolveOptions) { + if (!_customRequire) { + if (typeof require !== 'undefined') { + _customRequire = require; + } else { + _customRequire = createRequire(process.cwd()); + } + } + const moduleFilePath = _customRequire.resolve(filepath, options); + debug('[importResolve] %o, options: %o => %o', filepath, options, moduleFilePath); + return moduleFilePath; +} + +export async function importModule(filepath: string, options?: ImportModuleOptions) { + const moduleFilePath = importResolve(filepath, options); + let obj: any; + if (typeof require === 'function') { + // commonjs + obj = require(moduleFilePath); + debug('[importModule] require %o => %o', filepath, obj); + if (obj?.__esModule === true && obj?.default) { + // 兼容 cjs 模拟 esm 的导出格式 + // { + // __esModule: true, + // default: { fn: [Function: fn], foo: 'bar', one: 1 } + // } + obj = obj.default; + } + } else { + // esm + debug('[importModule] await import start: %o', filepath); + const fileUrl = pathToFileURL(moduleFilePath).toString(); + obj = await import(fileUrl); + debug('[importModule] await import end: %o => %o', filepath, obj); + // { + // default: { foo: 'bar', one: 1 }, + // foo: 'bar', + // one: 1, + // [Symbol(Symbol.toStringTag)]: 'Module' + // } + if (obj?.__esModule === true && obj?.default?.__esModule === true) { + // 兼容 cjs 模拟 esm 的导出格式 + // { + // __esModule: true, + // default: { + // __esModule: true, + // default: { + // fn: [Function: fn] { [length]: 0, [name]: 'fn' }, + // foo: 'bar', + // one: 1 + // } + // }, + // [Symbol(Symbol.toStringTag)]: 'Module' + // } + obj = obj.default; + } + if (options?.importDefaultOnly) { + if (obj.default) { + obj = obj.default; + } + } + } + debug('[importModule] return %o => %o', filepath, obj); + return obj; +} diff --git a/src/index.ts b/src/index.ts index 3a62130..06e1e9c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,12 @@ -import { getFrameworkPath } from './framework'; -import { getPlugins, getConfig, getLoadUnits } from './plugin'; -import { getFrameworkOrEggPath } from './deprecated'; +import { getFrameworkPath } from './framework.js'; +import { getPlugins, getConfig, getLoadUnits } from './plugin.js'; +import { getFrameworkOrEggPath } from './deprecated.js'; // support import { getFrameworkPath } from '@eggjs/utils' -export { getFrameworkPath } from './framework'; -export { getPlugins, getConfig, getLoadUnits } from './plugin'; -export { getFrameworkOrEggPath } from './deprecated'; +export { getFrameworkPath } from './framework.js'; +export { getPlugins, getConfig, getLoadUnits } from './plugin.js'; +export { getFrameworkOrEggPath } from './deprecated.js'; +export * from './import.js'; // support import utils from '@eggjs/utils' export default { diff --git a/src/plugin.ts b/src/plugin.ts index 8c41639..22e84de 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,13 +1,16 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - +import { debuglog } from 'node:util'; import path from 'node:path'; import assert from 'node:assert'; import os from 'node:os'; -import { existsSync, mkdirSync, writeFileSync, realpathSync } from 'node:fs'; +import { stat, mkdir, writeFile, realpath } from 'node:fs/promises'; +import { importModule } from './import.js'; + +const debug = debuglog('@eggjs/utils:plugin'); const tmpDir = os.tmpdir(); -// eslint-disable-next-line @typescript-eslint/no-empty-function + function noop() {} + const logger = { debug: noop, info: noop, @@ -15,13 +18,13 @@ const logger = { error: noop, }; -interface LoaderOptions { +export interface LoaderOptions { framework: string; baseDir: string; env?: string; } -interface Plugin { +export interface Plugin { name: string; version?: string; enable: boolean; @@ -36,9 +39,9 @@ interface Plugin { /** * @see https://github.com/eggjs/egg-core/blob/2920f6eade07959d25f5c4f96b154d3fbae877db/lib/loader/mixin/plugin.js#L203 */ -export function getPlugins(options: LoaderOptions) { - const loader = getLoader(options); - loader.loadPlugin(); +export async function getPlugins(options: LoaderOptions) { + const loader = await getLoader(options); + await loader.loadPlugin(); return loader.allPlugins; } @@ -50,79 +53,110 @@ interface Unit { /** * @see https://github.com/eggjs/egg-core/blob/2920f6eade07959d25f5c4f96b154d3fbae877db/lib/loader/egg_loader.js#L348 */ -export function getLoadUnits(options: LoaderOptions) { - const loader = getLoader(options); - loader.loadPlugin(); +export async function getLoadUnits(options: LoaderOptions) { + const loader = await getLoader(options); + await loader.loadPlugin(); return loader.getLoadUnits(); } -export function getConfig(options: LoaderOptions) { - const loader = getLoader(options); - loader.loadPlugin(); - loader.loadConfig(); +export async function getConfig(options: LoaderOptions) { + const loader = await getLoader(options); + await loader.loadPlugin(); + await loader.loadConfig(); return loader.config; } -function getLoader(options: LoaderOptions) { - let { framework, baseDir, env } = options; - assert(framework, 'framework is required'); - assert(existsSync(framework), `${framework} should exist`); - if (!(baseDir && existsSync(baseDir))) { - baseDir = path.join(tmpDir, String(Date.now()), 'tmpapp'); - mkdirSync(baseDir, { recursive: true }); - writeFileSync(path.join(baseDir, 'package.json'), JSON.stringify({ name: 'tmpapp' })); +async function exists(filepath: string) { + try { + await stat(filepath); + return true; + } catch { + return false; } - - const EggLoader = findEggCore({ baseDir, framework }); - const { Application } = require(framework); - if (env) process.env.EGG_SERVER_ENV = env; - return new EggLoader({ - baseDir, - logger, - app: Object.create(Application.prototype), - }); } -interface Loader { - // eslint-disable-next-line @typescript-eslint/no-misused-new - new(...args: any): Loader; - loadPlugin(): void; - loadConfig(): void; +interface IEggLoader { + loadPlugin(): Promise; + loadConfig(): Promise; config: Record; getLoadUnits(): Unit[]; allPlugins: Record; } -function findEggCore({ baseDir, framework }): Loader { - const baseDirRealpath = realpathSync(baseDir); - const frameworkRealpath = realpathSync(framework); - // custom framework => egg => egg/lib/loader/index.js +interface IEggLoaderOptions { + baseDir: string; + app: unknown; + logger: object; + EggCoreClass?: unknown; +} + +type EggLoaderImplClass = new(options: IEggLoaderOptions) => T; + +async function getLoader(options: LoaderOptions) { + assert(options.framework, 'framework is required'); + assert(await exists(options.framework), `${options.framework} should exist`); + if (!(options.baseDir && await exists(options.baseDir))) { + options.baseDir = path.join(tmpDir, 'egg_utils', `${Date.now()}`, 'tmp_app'); + await mkdir(options.baseDir, { recursive: true }); + await writeFile(path.join(options.baseDir, 'package.json'), JSON.stringify({ + name: 'tmp_app', + })); + debug('[getLoader] create baseDir: %o', options.baseDir); + } + + const { EggCore, EggLoader } = await findEggCore(options); + const mod = await importModule(options.framework); + const Application = mod.Application ?? mod.default?.Application; + assert(Application, `Application not export on ${options.framework}`); + if (options.env) { + process.env.EGG_SERVER_ENV = options.env; + } + return new EggLoader({ + baseDir: options.baseDir, + logger, + app: Object.create(Application.prototype), + EggCoreClass: EggCore, + }); +} + +async function findEggCore(options: LoaderOptions): Promise<{ EggCore?: object; EggLoader: EggLoaderImplClass }> { + const baseDirRealpath = await realpath(options.baseDir); + const frameworkRealpath = await realpath(options.framework); + const paths = [ frameworkRealpath, baseDirRealpath ]; + // custom framework => egg => @eggjs/core try { - return require(require.resolve('egg/lib/loader', { - paths: [ frameworkRealpath, baseDirRealpath ], - })).EggLoader; - } catch { - // ignore + const { EggCore, EggLoader } = await importModule('egg', { paths }); + if (EggLoader) { + return { EggCore, EggLoader }; + } + } catch (err: any) { + debug('[findEggCore] import "egg" from paths:%o error: %o', paths, err); } - const name = 'egg-core'; + const name = '@eggjs/core'; // egg => egg-core try { - return require(require.resolve(name, { - paths: [ frameworkRealpath, baseDirRealpath ], - })).EggLoader; - } catch { - // ignore + const { EggCore, EggLoader } = await importModule(name, { paths }); + if (EggLoader) { + return { EggCore, EggLoader }; + } + } catch (err: any) { + debug('[findEggCore] import "%s" from paths:%o error: %o', name, paths, err); } try { - return require(name).EggLoader; - } catch { - let eggCorePath = path.join(baseDir, `node_modules/${name}`); - if (!existsSync(eggCorePath)) { - eggCorePath = path.join(framework, `node_modules/${name}`); + const { EggCore, EggLoader } = await importModule(name); + if (EggLoader) { + return { EggCore, EggLoader }; } - assert(existsSync(eggCorePath), `Can't find ${name} from ${baseDir} and ${framework}`); - return require(eggCorePath).EggLoader; + } catch (err: any) { + debug('[findEggCore] import "%s" error: %o', name, err); + } + + let eggCorePath = path.join(options.baseDir, `node_modules/${name}`); + if (!(await exists(eggCorePath))) { + eggCorePath = path.join(options.framework, `node_modules/${name}`); } + assert(await exists(eggCorePath), `Can't find ${name} from ${options.baseDir} and ${options.framework}`); + return await importModule(eggCorePath); } diff --git a/test/fixtures/cjs/es-module-default.js b/test/fixtures/cjs/es-module-default.js new file mode 100644 index 0000000..ae12744 --- /dev/null +++ b/test/fixtures/cjs/es-module-default.js @@ -0,0 +1,6 @@ +exports.__esModule = true; +exports["default"] = { + fn() {}, + foo: 'bar', + one: 1, +}; diff --git a/test/fixtures/cjs/exports.cjs b/test/fixtures/cjs/exports.cjs new file mode 100644 index 0000000..82286d3 --- /dev/null +++ b/test/fixtures/cjs/exports.cjs @@ -0,0 +1,2 @@ +exports.foo = 'bar'; +exports.one = 1; diff --git a/test/fixtures/cjs/exports.js b/test/fixtures/cjs/exports.js new file mode 100644 index 0000000..82286d3 --- /dev/null +++ b/test/fixtures/cjs/exports.js @@ -0,0 +1,2 @@ +exports.foo = 'bar'; +exports.one = 1; diff --git a/test/fixtures/cjs/index.js b/test/fixtures/cjs/index.js new file mode 100644 index 0000000..2bf2ae3 --- /dev/null +++ b/test/fixtures/cjs/index.js @@ -0,0 +1,5 @@ +module.exports = { + foo: 'bar', +}; + +module.exports.one = 1; diff --git a/test/fixtures/cjs/package.json b/test/fixtures/cjs/package.json new file mode 100644 index 0000000..5bbefff --- /dev/null +++ b/test/fixtures/cjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/test/fixtures/egg-app/get_config.js b/test/fixtures/egg-app/get_config.js index 614e458..b07fe35 100644 --- a/test/fixtures/egg-app/get_config.js +++ b/test/fixtures/egg-app/get_config.js @@ -1,7 +1,7 @@ -'use strict'; +const { getConfig } = require('../../..'); -const getConfig = require('../../..').getConfig; - -const configs = getConfig(JSON.parse(process.argv[2])); -console.log(process.argv[2]); -console.log('get app configs %s', Object.keys(configs)); +(async () => { + const configs = await getConfig(JSON.parse(process.argv[2])); + console.log(process.argv[2]); + console.log('get app configs %j', Object.keys(configs)); +})(); diff --git a/test/fixtures/egg-app/get_loadunit.js b/test/fixtures/egg-app/get_loadunit.js index e843ce4..9362d7d 100644 --- a/test/fixtures/egg-app/get_loadunit.js +++ b/test/fixtures/egg-app/get_loadunit.js @@ -1,8 +1,9 @@ -'use strict'; +const { getLoadUnits } = require('../../..'); -const getLoadUnits = require('../../..').getLoadUnits; - -const units = getLoadUnits(JSON.parse(process.argv[2])); -console.log('get %s plugin', units.filter(p => p.type === 'plugin').length); -console.log('get %s framework', units.filter(p => p.type === 'framework').length); -console.log('get %s app', units.filter(p => p.type === 'app').length); +(async () => { + console.log(process.argv[2]); + const units = await getLoadUnits(JSON.parse(process.argv[2])); + console.log('get %s plugin', units.filter(p => p.type === 'plugin').length); + console.log('get %s framework', units.filter(p => p.type === 'framework').length); + console.log('get %s app', units.filter(p => p.type === 'app').length); +})(); diff --git a/test/fixtures/egg-app/get_plugin.js b/test/fixtures/egg-app/get_plugin.js index d6c156f..643424d 100644 --- a/test/fixtures/egg-app/get_plugin.js +++ b/test/fixtures/egg-app/get_plugin.js @@ -1,6 +1,6 @@ -'use strict'; +const { getPlugins } = require('../../..'); -const getPlugins = require('../../..').getPlugins; - -const plugins = getPlugins(JSON.parse(process.argv[2])); -console.log('get all plugins %s', Object.keys(plugins)); +(async () => { + const plugins = await getPlugins(JSON.parse(process.argv[2])); + console.log('get all plugins %j', Object.keys(plugins)); +})(); diff --git a/test/fixtures/egg-app/package.json b/test/fixtures/egg-app/package.json index fa834f5..d2923b3 100644 --- a/test/fixtures/egg-app/package.json +++ b/test/fixtures/egg-app/package.json @@ -1,7 +1,8 @@ { "name": "egg-app", "dependencies": { - "egg": "^3.16.0", + "egg": "*", + "@eggjs/core": "*", "framework-demo": "^1.0.1" } } diff --git a/test/fixtures/esm/exports.js b/test/fixtures/esm/exports.js new file mode 100644 index 0000000..e30ded7 --- /dev/null +++ b/test/fixtures/esm/exports.js @@ -0,0 +1,2 @@ +export const foo = 'bar'; +export const one = 1; diff --git a/test/fixtures/esm/exports.mjs b/test/fixtures/esm/exports.mjs new file mode 100644 index 0000000..e30ded7 --- /dev/null +++ b/test/fixtures/esm/exports.mjs @@ -0,0 +1,2 @@ +export const foo = 'bar'; +export const one = 1; diff --git a/test/fixtures/esm/index.js b/test/fixtures/esm/index.js new file mode 100644 index 0000000..d840cca --- /dev/null +++ b/test/fixtures/esm/index.js @@ -0,0 +1,5 @@ +export default { + foo: 'bar', +}; + +export const one = 1; diff --git a/test/fixtures/esm/package.json b/test/fixtures/esm/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/test/fixtures/esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/framework.test.ts b/test/framework.test.ts index c92f402..17580d4 100644 --- a/test/framework.test.ts +++ b/test/framework.test.ts @@ -2,14 +2,15 @@ import path from 'node:path'; import { strict as assert } from 'node:assert'; import fs from 'node:fs'; import mm from 'mm'; -import { getFrameworkPath } from '../src'; +import { getFrameworkPath } from '../src/index.js'; +import { getFilepath, testDir } from './helper.js'; describe('test/framework.test.ts', () => { afterEach(mm.restore); it('should exist when specify baseDir', () => { it('should get egg by default but not exist', () => { - const baseDir = path.join(__dirname, 'noexist'); + const baseDir = getFilepath('noexist'); assert.throws(() => { getFrameworkPath({ baseDir, @@ -22,7 +23,7 @@ describe('test/framework.test.ts', () => { }); it('should get from absolute path', () => { - const baseDir = path.join(__dirname, 'fixtures/framework-egg-default'); + const baseDir = getFilepath('framework-egg-default'); const frameworkPath = path.join(baseDir, 'node_modules/egg'); const framework = getFrameworkPath({ baseDir, @@ -32,8 +33,8 @@ describe('test/framework.test.ts', () => { }); it('should get from absolute path but not exist', () => { - const baseDir = path.join(__dirname, 'fixtures/framework-egg-default'); - const frameworkPath = path.join(__dirname, 'noexist'); + const baseDir = getFilepath('framework-egg-default'); + const frameworkPath = path.join(baseDir, 'noexist'); assert.throws(() => { getFrameworkPath({ baseDir, @@ -46,7 +47,7 @@ describe('test/framework.test.ts', () => { }); it('should get from npm package', () => { - const baseDir = path.join(__dirname, 'fixtures/framework-egg-default'); + const baseDir = getFilepath('framework-egg-default'); const frameworkPath = path.join(baseDir, 'node_modules/egg'); const framework = getFrameworkPath({ baseDir, @@ -56,7 +57,7 @@ describe('test/framework.test.ts', () => { }); it('should get from npm package but not exist', () => { - const baseDir = path.join(__dirname, 'fixtures/framework-egg-default'); + const baseDir = getFilepath('framework-egg-default'); assert.throws(() => { getFrameworkPath({ baseDir, @@ -73,7 +74,7 @@ describe('test/framework.test.ts', () => { }); it('should get from pkg.egg.framework', () => { - const baseDir = path.join(__dirname, 'fixtures/framework-pkg-egg'); + const baseDir = getFilepath('framework-pkg-egg'); const framework = getFrameworkPath({ baseDir, }); @@ -81,7 +82,7 @@ describe('test/framework.test.ts', () => { }); it('should get from pkg.egg.framework but not exist', () => { - const baseDir = path.join(__dirname, 'fixtures/framework-pkg-egg-noexist'); + const baseDir = getFilepath('framework-pkg-egg-noexist'); assert.throws(() => { getFrameworkPath({ baseDir, @@ -97,7 +98,7 @@ describe('test/framework.test.ts', () => { }); it('should get egg by default', () => { - const baseDir = path.join(__dirname, 'fixtures/framework-egg-default'); + const baseDir = getFilepath('framework-egg-default'); const framework = getFrameworkPath({ baseDir, }); @@ -105,7 +106,7 @@ describe('test/framework.test.ts', () => { }); it('should get egg by default but not exist', () => { - const baseDir = path.join(__dirname, 'fixtures/framework-egg-default-noexist'); + const baseDir = getFilepath('framework-egg-default-noexist'); assert.throws(() => { getFrameworkPath({ baseDir, @@ -121,9 +122,9 @@ describe('test/framework.test.ts', () => { }); it('should get egg from process.cwd', () => { - const cwd = path.join(__dirname, 'fixtures/test-app'); + const cwd = getFilepath('test-app'); mm(process, 'cwd', () => cwd); - const baseDir = path.join(__dirname, 'fixtures/test-app/test/fixtures/app'); + const baseDir = getFilepath('test-app/test/fixtures/app'); const framework = getFrameworkPath({ baseDir, @@ -132,10 +133,10 @@ describe('test/framework.test.ts', () => { }); it('should get egg from monorepo root dir', () => { - const cwd = path.join(__dirname, 'fixtures/monorepo-app/packages/a'); + const cwd = getFilepath('monorepo-app/packages/a'); mm(process, 'cwd', () => cwd); - const linkEgg = path.join(__dirname, '..', 'node_modules/egg'); - fs.symlinkSync(path.join(__dirname, 'fixtures/monorepo-app/node_modules/egg'), linkEgg); + const linkEgg = path.join(testDir, '..', 'node_modules/egg'); + fs.symlinkSync(getFilepath('monorepo-app/node_modules/egg'), linkEgg); const framework = getFrameworkPath({ baseDir: cwd, }); diff --git a/test/getFrameworkOrEggPath.test.ts b/test/getFrameworkOrEggPath.test.ts index 1c15701..3334c9d 100644 --- a/test/getFrameworkOrEggPath.test.ts +++ b/test/getFrameworkOrEggPath.test.ts @@ -1,42 +1,40 @@ -import path from 'node:path'; import { strict as assert } from 'node:assert'; -import utils from '../src'; - -describe('/test/getFrameworkOrEggPath.test.ts', () => { - const fixtures = path.join(__dirname, 'fixtures'); +import utils from '../src/index.js'; +import { getFilepath } from './helper.js'; +describe('test/getFrameworkOrEggPath.test.ts', () => { it('get framework dir path success', () => { - const dirpath = utils.getFrameworkOrEggPath(path.join(fixtures, 'aliyun-egg-app')); - assert.equal(dirpath, path.join(fixtures, 'aliyun-egg-app/node_modules/aliyun-egg')); + const dirpath = utils.getFrameworkOrEggPath(getFilepath('aliyun-egg-app')); + assert.equal(dirpath, getFilepath('aliyun-egg-app/node_modules/aliyun-egg')); }); it('get custom framework dir path success when app set app.framework on package.json', () => { - const dirpath = utils.getFrameworkOrEggPath(path.join(fixtures, 'yadan-app')); - assert.equal(dirpath, path.join(fixtures, 'yadan-app/node_modules/yadan')); + const dirpath = utils.getFrameworkOrEggPath(getFilepath('yadan-app')); + assert.equal(dirpath, getFilepath('yadan-app/node_modules/yadan')); }); it('get custom egg dir path success', () => { - const dirpath = utils.getFrameworkOrEggPath(path.join(fixtures, 'aliyun-egg-app'), [ 'my-old-egg', 'my-new-egg' ]); - assert.equal(dirpath, path.join(fixtures, 'aliyun-egg-app/node_modules/my-new-egg')); + const dirpath = utils.getFrameworkOrEggPath(getFilepath('aliyun-egg-app'), [ 'my-old-egg', 'my-new-egg' ]); + assert.equal(dirpath, getFilepath('aliyun-egg-app/node_modules/my-new-egg')); }); it('get default egg dir path success', () => { - const dirpath = utils.getFrameworkOrEggPath(path.join(fixtures, 'default-egg-app')); - assert.equal(dirpath, path.join(fixtures, 'default-egg-app/node_modules/egg')); + const dirpath = utils.getFrameworkOrEggPath(getFilepath('default-egg-app')); + assert.equal(dirpath, getFilepath('default-egg-app/node_modules/egg')); }); it('get "" when egg name not found', () => { - const dirpath = utils.getFrameworkOrEggPath(path.join(fixtures, 'aliyun-egg-app'), [ 'my-egg' ]); + const dirpath = utils.getFrameworkOrEggPath(getFilepath('aliyun-egg-app'), [ 'my-egg' ]); assert.equal(dirpath, ''); }); it('get "" when node_modules not found', () => { - const dirpath = utils.getFrameworkOrEggPath(path.join(fixtures, 'aliyun-egg-app-not-exists')); + const dirpath = utils.getFrameworkOrEggPath(getFilepath('aliyun-egg-app-not-exists')); assert.equal(dirpath, ''); }); it('get "" when framework package.json not exists', () => { - const dirpath = utils.getFrameworkOrEggPath(path.join(fixtures, 'demoframework-app')); + const dirpath = utils.getFrameworkOrEggPath(getFilepath('demoframework-app')); assert.equal(dirpath, ''); }); }); diff --git a/test/helper.ts b/test/helper.ts new file mode 100644 index 0000000..020fa1d --- /dev/null +++ b/test/helper.ts @@ -0,0 +1,9 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +export const testDir = path.dirname(__filename); + +export function getFilepath(name: string) { + return path.join(testDir, 'fixtures', name); +} diff --git a/test/import.test.ts b/test/import.test.ts new file mode 100644 index 0000000..085e3c5 --- /dev/null +++ b/test/import.test.ts @@ -0,0 +1,87 @@ +import { strict as assert } from 'node:assert'; +import { importResolve, importModule } from '../src/index.js'; +import { getFilepath } from './helper.js'; + +describe('test/import.test.ts', () => { + describe('importResolve()', () => { + it('should work on cjs', () => { + assert.equal(importResolve(getFilepath('cjs')), getFilepath('cjs/index.js')); + }); + + it('should work on esm', () => { + assert.equal(importResolve(getFilepath('esm')), getFilepath('esm/index.js')); + }); + }); + + describe('importModule()', () => { + it('should work on cjs', async () => { + let obj = await importModule(getFilepath('cjs')); + assert.deepEqual(Object.keys(obj), [ 'default', 'one' ]); + assert.equal(obj.one, 1); + assert.deepEqual(obj.default, { foo: 'bar', one: 1 }); + + obj = await importModule(getFilepath('cjs'), { importDefaultOnly: true }); + assert.deepEqual(obj, { foo: 'bar', one: 1 }); + + obj = await importModule(getFilepath('cjs/exports')); + assert.deepEqual(Object.keys(obj), [ 'default', 'foo', 'one' ]); + assert.equal(obj.foo, 'bar'); + assert.equal(obj.one, 1); + assert.deepEqual(obj.default, { foo: 'bar', one: 1 }); + + obj = await importModule(getFilepath('cjs/exports.js')); + assert.deepEqual(Object.keys(obj), [ 'default', 'foo', 'one' ]); + assert.equal(obj.foo, 'bar'); + assert.equal(obj.one, 1); + assert.deepEqual(obj.default, { foo: 'bar', one: 1 }); + + obj = await importModule(getFilepath('cjs/exports.cjs')); + assert.deepEqual(Object.keys(obj), [ 'default', 'foo', 'one' ]); + assert.equal(obj.foo, 'bar'); + assert.equal(obj.one, 1); + assert.deepEqual(obj.default, { foo: 'bar', one: 1 }); + + obj = await importModule(getFilepath('cjs/es-module-default.js')); + assert.deepEqual(Object.keys(obj), [ '__esModule', 'default' ]); + assert.equal(obj.default.foo, 'bar'); + assert.equal(obj.default.one, 1); + assert.equal(typeof obj.default.fn, 'function'); + + obj = await importModule(getFilepath('cjs/es-module-default.js'), { importDefaultOnly: true }); + assert.deepEqual(Object.keys(obj), [ 'fn', 'foo', 'one' ]); + assert.equal(obj.foo, 'bar'); + assert.equal(obj.one, 1); + assert.equal(typeof obj.fn, 'function'); + }); + + it('should work on esm', async () => { + let obj = await importModule(getFilepath('esm')); + assert.deepEqual(Object.keys(obj), [ 'default', 'one' ]); + assert.equal(obj.one, 1); + assert.deepEqual(obj.default, { foo: 'bar' }); + + obj = await importModule(getFilepath('esm'), { importDefaultOnly: true }); + assert.deepEqual(obj, { foo: 'bar' }); + + obj = await importModule(getFilepath('esm/exports')); + assert.deepEqual(Object.keys(obj), [ 'foo', 'one' ]); + assert.equal(obj.foo, 'bar'); + assert.equal(obj.one, 1); + + obj = await importModule(getFilepath('esm/exports'), { importDefaultOnly: true }); + assert.deepEqual(Object.keys(obj), [ 'foo', 'one' ]); + assert.equal(obj.foo, 'bar'); + assert.equal(obj.one, 1); + + obj = await importModule(getFilepath('esm/exports.js')); + assert.deepEqual(Object.keys(obj), [ 'foo', 'one' ]); + assert.equal(obj.foo, 'bar'); + assert.equal(obj.one, 1); + + obj = await importModule(getFilepath('esm/exports.mjs')); + assert.deepEqual(Object.keys(obj), [ 'foo', 'one' ]); + assert.equal(obj.foo, 'bar'); + assert.equal(obj.one, 1); + }); + }); +}); diff --git a/test/plugin.test.ts b/test/plugin.test.ts index d632c9e..d59c64a 100644 --- a/test/plugin.test.ts +++ b/test/plugin.test.ts @@ -5,11 +5,12 @@ import { existsSync } from 'node:fs'; import mm from 'mm'; import coffee from 'coffee'; import runscript from 'runscript'; -import utils from '../src'; +import utils from '../src/index.js'; +import { getFilepath } from './helper.js'; describe('test/plugin.test.ts', () => { - const cwd = path.join(__dirname, 'fixtures/egg-app'); - const tmp = path.join(__dirname, 'fixtures/tmp'); + const cwd = getFilepath('egg-app'); + const tmp = getFilepath('tmp'); beforeEach(async () => { await rm(tmp, { force: true, recursive: true }); @@ -30,8 +31,7 @@ describe('test/plugin.test.ts', () => { }); await coffee.fork(bin, [ args ], { cwd: tmp }) .debug() - // .expect('stdout', 'get all plugins onerror,session,i18n,watcher,multipart,security,development,logrotator,schedule,static,jsonp,view\n') - .expect('stdout', /get all plugins/) + .expect('stdout', /get all plugins \["onerror",/) .expect('code', 0) .end(); }); @@ -45,8 +45,7 @@ describe('test/plugin.test.ts', () => { }); await coffee.fork(bin, [ args ], { cwd: tmp }) .debug() - // .expect('stdout', 'get all plugins onerror,session,i18n,watcher,multipart,security,development,logrotator,schedule,static,jsonp,view\n') - .expect('stdout', /get all plugins/) + .expect('stdout', /get all plugins \["onerror",/) .expect('code', 0) .end(); }); @@ -61,11 +60,10 @@ describe('test/plugin.test.ts', () => { }); await coffee.fork(bin, [ args ], { cwd: tmp }) .debug() - // .expect('stdout', 'get all plugins onerror,session,i18n,watcher,multipart,security,development,logrotator,schedule,static,jsonp,view,p\n') - .expect('stdout', /get all plugins/) + .expect('stdout', /get all plugins \["onerror",/) .expect('code', 0) .end(); - const plugins = utils.getPlugins({ + const plugins = await utils.getPlugins({ baseDir: tmp, framework: path.join(tmp, 'node_modules/egg'), }); @@ -107,7 +105,7 @@ describe('test/plugin.test.ts', () => { .expect('stdout', /get 1 app/) .expect('code', 0) .end(); - const units = utils.getLoadUnits({ + const units = await utils.getLoadUnits({ baseDir: tmp, framework: path.join(tmp, 'node_modules/egg'), }); @@ -162,8 +160,7 @@ describe('test/plugin.test.ts', () => { }); await coffee.fork(bin, [ args ], { cwd: tmp }) .debug() - // .expect('stdout', /get app configs session,security,helper,jsonp,onerror,i18n,watcher,multipart,logrotator,static,view,env,name,keys,proxy,protocolHeaders,ipHeaders,hostHeaders,pkg,baseDir,HOME,rundir,dump,notfound,siteFile,bodyParser,logger,httpclient,coreMiddleware,workerStartTimeout,coreMiddlewares,appMiddlewares,appMiddleware/) - .expect('stdout', /get app configs/) + .expect('stdout', /get app configs \["session"/) .expect('code', 0) .end(); }); @@ -177,8 +174,7 @@ describe('test/plugin.test.ts', () => { }); await coffee.fork(bin, [ args ], { cwd: tmp }) .debug() - // .expect('stdout', /get app configs session,security,helper,jsonp,onerror,i18n,watcher,multipart,logrotator,static,view,env,name,keys,proxy,protocolHeaders,ipHeaders,hostHeaders,pkg,baseDir,HOME,rundir,dump,notfound,siteFile,bodyParser,logger,httpclient,coreMiddleware,workerStartTimeout,coreMiddlewares,appMiddlewares,appMiddleware/) - .expect('stdout', /get app configs/) + .expect('stdout', /get app configs \["session"/) .expect('code', 0) .end(); }); @@ -192,11 +188,10 @@ describe('test/plugin.test.ts', () => { }); await coffee.fork(bin, [ args ], { cwd: tmp }) .debug() - // .expect('stdout', /get app configs session,security,helper,jsonp,onerror,i18n,watcher,multipart,logrotator,static,view,env,name,keys,proxy,protocolHeaders,ipHeaders,hostHeaders,pkg,baseDir,HOME,rundir,dump,notfound,siteFile,bodyParser,logger,httpclient,coreMiddleware,workerStartTimeout,coreMiddlewares,appMiddlewares,appMiddleware/) - .expect('stdout', /get app configs/) + .expect('stdout', /get app configs \["session"/) .expect('code', 0) .end(); - const config = utils.getConfig({ + const config = await utils.getConfig({ baseDir: tmp, framework: path.join(tmp, 'node_modules/framework-demo'), }); diff --git a/tsconfig.json b/tsconfig.json index d7c487d..ff41b73 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,10 @@ { "extends": "@eggjs/tsconfig", "compilerOptions": { + "strict": true, + "noImplicitAny": true, "target": "ES2022", - "inlineSourceMap": false, - "outDir": "lib" - }, - "exclude": [ - "test" - ], - "include": [ - "src" - ] + "module": "NodeNext", + "moduleResolution": "NodeNext" + } }