diff --git a/package.json b/package.json index 30d4ea4..4904293 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "test": "yarn lint-prettier && yarn build && yarn lint" }, "dependencies": { - "@magicspace/core": "^0.3.5", + "@magicspace/core": "^0.3.6", "@magicspace/utils": "^0.3.4", "lodash": "^4.17.20", "npm-which": "^3.0.1", diff --git a/typescript/src/composables/jest.config.js.ts b/typescript/src/composables/jest.config.js.ts new file mode 100644 index 0000000..63e7517 --- /dev/null +++ b/typescript/src/composables/jest.config.js.ts @@ -0,0 +1,44 @@ +import {join, posix} from 'path'; + +import {composable, objectModule} from '@magicspace/core'; + +import type {ResolvedOptions} from '../library/index.js'; + +const COMMENT = `/** @type {import('jest').Config} */`; + +export default composable(({type, mono, resolvedProjects}) => { + const testProjects = resolvedProjects.filter(project => project.test); + + return [ + mono && + objectModule( + 'jest.config.js', + { + projects: testProjects.map(project => project.package.resolvedDir), + }, + { + type, + comment: COMMENT, + }, + ), + ...testProjects.map(({bldDir, package: {type, resolvedDir}}) => { + return objectModule( + join(resolvedDir, 'jest.config.js'), + (config: {testMatch?: string[]}) => { + return { + ...config, + transform: {}, + testMatch: [ + ...(config?.testMatch ?? []), + `/${posix.relative(resolvedDir, bldDir)}/test/*.test.js`, + ], + }; + }, + { + type, + comment: COMMENT, + }, + ); + }), + ]; +}); diff --git a/typescript/src/composables/package.json.ts b/typescript/src/composables/package.json.ts index b4425ef..21dfb86 100644 --- a/typescript/src/composables/package.json.ts +++ b/typescript/src/composables/package.json.ts @@ -15,11 +15,18 @@ import type { ResolvedTypeScriptProjectOptions, } from '../library/index.js'; -const ROOT_DEV_DEPENDENCY_DICT = { - rimraf: '5', - typescript: '5', - 'run-in-every': '0.2', -}; +function ROOT_DEV_DEPENDENCY_DICT(test: boolean): Record { + return { + ...(test && { + '@types/jest': '29', + 'cross-env': '7', + jest: '29', + }), + rimraf: '5', + 'run-in-every': '0.2', + typescript: '5', + }; +} const PROJECT_DEPENDENCY_DICT = { tslib: '2', @@ -57,13 +64,15 @@ export default composable( packageOptions => packageOptions.packageJSONPath, ); + const anyTestProject = projects.some(project => project.test); + const [ rootDevDependencies, projectDependencies, esmProjectEntrancesDependencies, cjsProjectEntrancesDependencies, ] = await Promise.all([ - fetchPackageVersions(ROOT_DEV_DEPENDENCY_DICT), + fetchPackageVersions(ROOT_DEV_DEPENDENCY_DICT(anyTestProject)), fetchPackageVersions(PROJECT_DEPENDENCY_DICT), anyESMProjectWithEntrances ? fetchPackageVersions(PROJECT_ENTRANCES_DEPENDENCY_DICT(true)) @@ -111,9 +120,15 @@ export default composable( scripts = extendObjectProperties( scripts, { - test: extendPackageScript(scripts.test, `${packageManager} build`, { - after: '*lint-prettier*', - }), + 'bare-test': anyTestProject + ? 'cross-env NODE_OPTIONS=--experimental-vm-modules jest' + : undefined, + test: extendPackageScript( + extendPackageScript(scripts.test, `${packageManager} build`, { + after: '*lint-prettier*', + }), + `${packageManager} bare-test`, + ), }, { after: '*lint*', diff --git a/typescript/src/composables/tsconfig.json.ts b/typescript/src/composables/tsconfig.json.ts index 54460f3..f02ce90 100644 --- a/typescript/src/composables/tsconfig.json.ts +++ b/typescript/src/composables/tsconfig.json.ts @@ -37,13 +37,14 @@ export default composable(({resolvedProjects: projects}) => { JSON_OPTIONS, ), ...projects.map( - ({inDir, entrances, outDir, references, package: packageOptions}) => + ({inDir, entrances, outDir, references, test, package: packageOptions}) => json( Path.posix.join(inDir, 'tsconfig.json'), { extends: Path.posix.relative(inDir, 'tsconfig.base.json'), compilerOptions: { composite: true, + types: test ? ['jest'] : undefined, // fallback to undefined if no condition matched. experimentalDecorators: (packageOptions.type !== 'module' && entrances.length > 0) || diff --git a/typescript/src/library/options.ts b/typescript/src/library/options.ts index b477644..5ee3922 100644 --- a/typescript/src/library/options.ts +++ b/typescript/src/library/options.ts @@ -64,7 +64,7 @@ export const TypeScriptProjectOptions = x.object({ .union([x.literal('library'), x.literal('program'), x.literal('script')]) .nominal({ description: - "Defaults to 'library' if project name is 'library', otherwise 'program'.", + "Defaults to 'library' if project name includes 'library', otherwise 'program'.", }) .optional(), exports: x @@ -80,7 +80,7 @@ export const TypeScriptProjectOptions = x.object({ .nominal({ description: `\ Whether this TypeScript project is a development-time project, \ -defaults to true if the project name is 'test' or project type is 'script', \ +defaults to true if the project name includes 'test' or project type is 'script', \ otherwise false.`, }) .optional(), @@ -109,6 +109,12 @@ Whether this project does not emit build artifact, \ defaults to true if \`src\` is false, otherwise false.`, }) .optional(), + test: x.boolean + .nominal({ + description: + "Whether this project is a test project, defaults to true if `name` includes 'test', otherwise false.", + }) + .optional(), entrances: x .union([x.array(x.string), x.boolean]) .nominal({ @@ -159,6 +165,7 @@ export type ResolvedTypeScriptProjectOptions = { exportSourceAs: string | undefined; dev: boolean; noEmit: boolean; + test: boolean; entrances: string[]; package: ResolvedPackageOptions; references: ResolvedTypeScriptProjectReference[] | undefined; @@ -260,6 +267,7 @@ export function buildResolvedTypeScriptProjectOptions( src = type === 'script' ? false : 'src', dir = name, noEmit = src === false ? true : false, + test = name.includes('test') ? true : false, entrances = false, ...rest }: TypeScriptProjectOptions, @@ -317,6 +325,7 @@ export function buildResolvedTypeScriptProjectOptions( exportSourceAs, dev, noEmit, + test, entrances: typeof entrances === 'boolean' ? entrances diff --git a/yarn.lock b/yarn.lock index 0328328..5b74f98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -70,10 +70,10 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" -"@magicspace/core@^0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@magicspace/core/-/core-0.3.5.tgz#d3632f6f4ef912e538994fb350f0d78c464e6c76" - integrity sha512-40X5tHREEHtX7PyHolSILtxG8mjC6BNJvchJ+WC9PO8hl1WePrDgihanoeI/VCW/Q0OmtESHfadkbLVufqaQgQ== +"@magicspace/core@^0.3.6": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@magicspace/core/-/core-0.3.6.tgz#179835f8c5d9fb5a21883b5da0407650d03b48d3" + integrity sha512-QNoBU8tmvlS+xwr6RDTqkIOd835g9xurcWGbtV9rTxVN4iIgTb5ntnnLXMlr1szM6Eb/BHvo0ZP25XA5LsXkwg== dependencies: "@magicspace/utils" "^0.3.4" enhanced-resolve "^5.12.0"