From 1d3a93fbc2991760ba56cafe528ee79a0dc60de0 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 18 Oct 2023 22:25:16 -0700 Subject: [PATCH 1/3] doc: simplified imports unification --- README.md | 157 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 80 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index eae8df5..283ddb3 100644 --- a/README.md +++ b/README.md @@ -113,58 +113,103 @@ just be passed through as-is. } ``` -### `tshy.imports` +### Package `#imports` -You can use Node `package.json` `imports` in the `tshy` config, -referencing input files in `./src`. These will be copied into the -`package.json` files built into the `dist/{esm,commonjs}` -folders, so that they work like they would in a normal Node -program. +You can use `"imports"` in your package.json, and it will be +handled in the following ways. -The `tshy.imports` entries: +### Built Imports -- Must have a string key starting with `#`. -- Must have a string value starting with `'./src'`. +Any `"imports"` that resolve to a file built as part of your +program must be a non-conditional string value pointing to the +file in `./src/`. For example: -For example, you can do this: +```json +{ + "imports": { + "#name": "./src/path/to/name.ts", + "#utils/*": "./src/path/to/utils/*.ts" + } +} +``` + +In the ESM build, `import * from '#name'` will resolve to +`./dist/esm/path/to/name.js`, and will be built for ESM. In the +CommonJS build, `require('#name')` will resolve to +`./dist/commonjs/path/to/name.js` and will be built for CommonJS. + +
+tl;dr how this works and why it can't be conditional + +In the built `dist/{dialect}/package.json` files, the `./src` +will be stripped from the path and their file extension changed +from `ts` to `js` (`cts` to `cjs` and `mts` to `mjs`). + +It shouldn't be conditional, because the condition is already +implicit in the build. In the CommonJS build, they should be +required, and in the ESM builds, they should be imported, and +there's only one thing that it can resolve to from any given +build. + +
+ +Any `"imports"` that resolve to something _not_ built by tshy, +then tshy will set `scripts.preinstall` to set up symbolic links +to make it work at install time. This just means that you can't +use `scripts.preinstall` for anything else if you have +`"imports"` that aren't managed by tshy. For example: ```json { - "tshy": { - "imports": { - "#foo": "./src/lib/foo.ts", - "#utils/*": "./src/app/shared-components/utils/*" + "imports": { + "#dep": "@scope/dep/submodule", + "#conditional": { + "types": "./vendor/blah.d.ts", + "require": "./vendor/blah.cjs", + "import": "./vendor/blah.mjs } } } ``` -Then in your program, you can do this: +
+tl;dr explanation -```ts -// src/index.ts -import { foo } from '#foo' -import { barUtil } from '#utils/bar.js' -``` +The `"imports"` field in package.json allows you to set local +package imports, which have the same kind of conditional import +logic as `"exports"`. This is especially useful when you have a +vendored dependency with `require` and `import` variants, modules +that have to be bundled in different ways for different +environments, or different dependencies for different +environments. + +These package imports are _always_ resolved against the nearest +`package.json` file, and tshy uses generated package.json files +to set the module dialect to `"type":"module"` in `dist/esm` and +`"type":"commonjs"` in `dist/commonjs`, and it swaps the +`src/package.json` file between this during the `tsc` builds. + +Furthermore, local package imports may not be relative files +outside the package folder. They may only be local files within +the local package, or dependencies resolved in `node_modules`. -When this is compiled to `./dist/esm/index.js`, it will -automatically map `#foo` to `./dist/esm/lib/foo.js` and -`#utils/bar.js` to -`./dist/esm/app/shared-components/utils/bar.js`. +To support this, tshy copies the `imports` field from the +project's package.json into these dialect-setting generated +package.json files, and creates symlinks into the appropriate +places so that they resolve to the same files on disk. -When using this feature, `tshy` will automatically update your -`./tsconfig.json` file to set the -[`paths`](https://www.typescriptlang.org/tsconfig#paths) -appropriately so that it can find the types. +Because symlinks may not be included in npm packages (and even if +they are included, they won't be unpacked at install time), the +symlinks it places in `./dist` wouldn't do much good. In order to +work around _this_ restriction, tshy creates a node program at +`dist/.tshy-link-imports.mjs`, which generates the symlinks at +install time via the `preinstall` script. -Note that you can _not_ set conditional imports in this way, so -you can't use this to have `#import` style module identifiers -pointing to something outside of the built `dist` folder. (That -_is_ supported with `imports` in the top level `package.json`, -with some caveats. See below.) +
-None of the keys in `tshy.imports` are allowed to conflict with -the keys in the `package.json`'s top-level `imports`. +_If a `tshy.imports` is present (a previous iteration of this +behavior), it will be merged into the top-level `"imports"` and +deleted from the `tshy` section._ ### Making Noise @@ -457,48 +502,6 @@ for this purpose, and then delete it afterwards. If that file exists and _wasn't_ put there by `tshy`, then it will be destroyed. -## Package `#imports` (outside of `tshy` config) - -If you use `"imports"` in your package.json, then tshy will set -`scripts.preinstall` to set up some symbolic links to make it -work. This just means you can't use `scripts.preinstall` for -anything else if you use `"imports"`. - -
-tl;dr explanation - -The `"imports"` field in package.json allows you to set local -package imports, which have the same kind of conditional import -logic as `"exports"`. This is especially useful when you have a -vendored dependency with `require` and `import` variants, modules -that have to be bundled in different ways for different -environments, or different dependencies for different -environments. - -These package imports are _always_ resolved against the nearest -`package.json` file, and tshy uses generated package.json files -to set the module dialect to `"type":"module"` in `dist/esm` and -`"type":"commonjs"` in `dist/commonjs`, and it swaps the -`src/package.json` file between this during the `tsc` builds. - -Furthermore, local package imports may not be relative files -outside the package folder. They may only be local files within -the local package, or dependencies resolved in `node_modules`. - -To support this, tshy copies the `imports` field from the -project's package.json into these dialect-setting generated -package.json files, and creates symlinks into the appropriate -places so that they resolve to the same files on disk. - -Because symlinks may not be included in npm packages (and even if -they are included, they won't be unpacked at install time), the -symlinks it places in `./dist` wouldn't do much good. In order to -work around _this_ restriction, tshy creates a node program at -`dist/.tshy-link-imports.mjs`, which generates the symlinks at -install time via the `preinstall` script. - -
- ## Local Package `exports` In order to facilitate local package exports, tshy will create a From e6d554ad4f5b354b665b5cc395169303fa46fd74 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 18 Oct 2023 22:24:45 -0700 Subject: [PATCH 2/3] refactor: name the imports handling modules better --- src/build-fail.ts | 2 +- src/build.ts | 2 +- src/{get-imports.ts => built-imports.ts} | 0 src/set-folder-dialect.ts | 2 +- src/{imports.ts => unbuilt-imports.ts} | 16 ++++++++++++++-- test/build-fail.ts | 2 +- test/build.ts | 4 ++-- test/{get-imports.ts => built-imports.ts} | 2 +- test/{imports.ts => unbuilt-imports.ts} | 0 9 files changed, 21 insertions(+), 9 deletions(-) rename src/{get-imports.ts => built-imports.ts} (100%) rename src/{imports.ts => unbuilt-imports.ts} (88%) rename test/{get-imports.ts => built-imports.ts} (95%) rename test/{imports.ts => unbuilt-imports.ts} (100%) diff --git a/src/build-fail.ts b/src/build-fail.ts index b401541..5a99b6d 100644 --- a/src/build-fail.ts +++ b/src/build-fail.ts @@ -3,7 +3,7 @@ import * as console from './console.js' import fail from './fail.js' import setFolderDialect from './set-folder-dialect.js' import './tsconfig.js' -import { unlink as unlinkImports } from './imports.js' +import { unlink as unlinkImports } from './unbuilt-imports.js' import { unlink as unlinkSelfDep } from './self-dep.js' import pkg from './package.js' diff --git a/src/build.ts b/src/build.ts index ba0cad2..fa8f05f 100644 --- a/src/build.ts +++ b/src/build.ts @@ -10,7 +10,7 @@ import { link as linkImports, save as saveImports, unlink as unlinkImports, -} from './imports.js' +} from './unbuilt-imports.js' import pkg from './package.js' import { link as linkSelfDep, diff --git a/src/get-imports.ts b/src/built-imports.ts similarity index 100% rename from src/get-imports.ts rename to src/built-imports.ts diff --git a/src/set-folder-dialect.ts b/src/set-folder-dialect.ts index 7a34540..fae2786 100644 --- a/src/set-folder-dialect.ts +++ b/src/set-folder-dialect.ts @@ -2,7 +2,7 @@ import chalk from 'chalk' import { writeFileSync } from 'fs' import { rimrafSync } from 'rimraf' import * as console from './console.js' -import getImports from './get-imports.js' +import getImports from './built-imports.js' import pkg from './package.js' import { Dialect } from './types.js' diff --git a/src/imports.ts b/src/unbuilt-imports.ts similarity index 88% rename from src/imports.ts rename to src/unbuilt-imports.ts index f684d8f..9595006 100644 --- a/src/imports.ts +++ b/src/unbuilt-imports.ts @@ -38,8 +38,12 @@ Promise.all(links.map(([dest, src]) => symlink(src, dest).catch(e))) } let targets: undefined | string[] = undefined +// Get the targets that will have to be linked, because they're not +// a target in ./src const getTargets = async (imports: Record) => { - const conds = getAllConditionalValues(imports) + const conds = getAllConditionalValues(imports).filter( + c => !c.startsWith('./src/') + ) if (!conds.some(c => c.includes('*'))) { // fast path return (targets = conds.filter(c => c.startsWith('./'))) @@ -54,7 +58,14 @@ const getTargets = async (imports: Record) => { if (typeof url === 'string') continue const p = fileURLToPath(url) const rel = relative(process.cwd(), p) - if (!rel || rel.startsWith('..' + sep)) continue + // if it's empty, a dep in node_modules, or a built module, skip + if ( + !rel || + rel.startsWith('..' + sep) || + rel.startsWith('src' + sep) || + rel.startsWith('node_modules' + sep) + ) + continue t.add('./' + rel.replace(/\\/g, '/')) } } @@ -72,6 +83,7 @@ export const link = async ( const { imports } = pkg if (!imports) return if (!targets) targets = await getTargets(imports) + if (!targets.length) return console.debug(`link import targets in ${dir}`, targets) const rel = relative(resolve(dir), process.cwd()) const lps: Promise[] = [] diff --git a/test/build-fail.ts b/test/build-fail.ts index db02954..52c7e61 100644 --- a/test/build-fail.ts +++ b/test/build-fail.ts @@ -9,7 +9,7 @@ const { default: buildFail } = (await t.mockImport( { '../dist/esm/tsconfig.js': {}, '../dist/esm/package.js': { default: { name: 'package' } }, - '../dist/esm/imports.js': { + '../dist/esm/unbuilt-imports.js': { unlink: (...a: any[]) => calls.push(['unlinkImports', a]), }, '../dist/esm/self-dep.js': { diff --git a/test/build.ts b/test/build.ts index 7e1d382..667bbe6 100644 --- a/test/build.ts +++ b/test/build.ts @@ -16,7 +16,7 @@ const logCall = t.captureFn((_msg: string, _args: any[]) => {}) const calls = logCall.args const mocks = { - '../dist/esm/imports.js': { + '../dist/esm/unbuilt-imports.js': { link: async (...a: any[]) => logCall('imports.link', a), unlink: async (...a: any[]) => logCall('imports.unlink', a), save: (...a: any[]) => logCall('imports.save', a), @@ -105,7 +105,7 @@ t.test('imports linking', async t => { { ...mocks, '../dist/esm/package.js': { default: pkg }, - '../dist/esm/imports.js': { + '../dist/esm/unbuilt-imports.js': { link: async (...a: any[]) => logCall('imports.link', a), unlink: async (...a: any[]) => logCall('imports.unlink', a), diff --git a/test/get-imports.ts b/test/built-imports.ts similarity index 95% rename from test/get-imports.ts rename to test/built-imports.ts index cfb6e15..c732434 100644 --- a/test/get-imports.ts +++ b/test/built-imports.ts @@ -1,5 +1,5 @@ import t from 'tap' -import getImports from '../src/get-imports.js' +import getImports from '../src/built-imports.js' import { Package } from '../src/types.js' const cases: [ diff --git a/test/imports.ts b/test/unbuilt-imports.ts similarity index 100% rename from test/imports.ts rename to test/unbuilt-imports.ts From 8eca4a7c75f671de908c636cf32d79935f4842a3 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 18 Oct 2023 22:54:29 -0700 Subject: [PATCH 3/3] put all imports in top-level imports field --- src/add-paths-to-tsconfig.ts | 45 ------- src/built-imports.ts | 33 ++--- src/config.ts | 18 ++- src/tsconfig.ts | 6 +- src/types.ts | 1 - src/valid-imports.ts | 34 +++-- tap-snapshots/test/config.ts.test.cjs | 4 +- tap-snapshots/test/valid-imports.ts.test.cjs | 28 ++-- test/add-paths-to-tsconfig.ts | 133 ------------------- test/built-imports.ts | 20 +-- test/config.ts | 14 -- test/valid-imports.ts | 40 +++--- 12 files changed, 82 insertions(+), 294 deletions(-) delete mode 100644 src/add-paths-to-tsconfig.ts delete mode 100644 test/add-paths-to-tsconfig.ts diff --git a/src/add-paths-to-tsconfig.ts b/src/add-paths-to-tsconfig.ts deleted file mode 100644 index d7d59a0..0000000 --- a/src/add-paths-to-tsconfig.ts +++ /dev/null @@ -1,45 +0,0 @@ -// add/update paths section in tsconfig.json if tshy.imports present - -import { readFileSync, writeFileSync } from 'fs' -import config from './config.js' -import fail from './fail.js' -const { imports } = config - -export const tryParse = (s: string) => { - try { - return JSON.parse(s) - } catch (er) { - fail('could not parse tsconfig.json to add "paths"', er as Error) - return process.exit(1) - } -} - -export const addToObject = ( - tsconfig: Record & { - paths?: Record - } -) => { - if (!imports) return tsconfig - return { - ...tsconfig, - paths: { - ...tsconfig.paths, - ...Object.fromEntries( - Object.entries(imports).map(([k, v]) => [k, [v]]) - ), - }, - } -} - -export const addToFile = () => { - if (!imports) return - const tsconfig = tryParse(readFileSync('tsconfig.json', 'utf8')) - if (!tsconfig) { - // failed - return - } - writeFileSync( - 'tsconfig.json', - JSON.stringify(addToObject(tsconfig), null, 2) + '\n' - ) -} diff --git a/src/built-imports.ts b/src/built-imports.ts index 45fd3dd..5ffe8cf 100644 --- a/src/built-imports.ts +++ b/src/built-imports.ts @@ -1,27 +1,22 @@ // merge tshy.imports with package.json imports -import { Package, TshyConfig } from './types.js' +import { Package } from './types.js' -// strip the ./src/ and turn ts extension into js -const stripSrc = (ti: TshyConfig['imports']): Package['imports'] => { - if (!ti) return undefined +// strip the ./src/ and turn ts extension into js for built imports +// leave unbuilt imports alone, they'll be symlinked +export default (pkg: Package): Package['imports'] => { + const { imports } = pkg + if (!imports) return undefined return Object.fromEntries( - Object.entries(ti).map(([k, v]) => [ + Object.entries(imports).map(([k, v]) => [ k, - './' + - v - .substring('./src/'.length) - .replace(/\.([cm]?)ts$/, '.$1js') - .replace(/\.tsx$/, '.js'), + typeof v === 'string' && v.startsWith('./src/') + ? './' + + v + .substring('./src/'.length) + .replace(/\.([cm]?)ts$/, '.$1js') + .replace(/\.tsx$/, '.js') + : v, ]) ) } - -export default (pkg: Package): Package['imports'] => { - const { tshy } = pkg - if (!tshy?.imports && !pkg.imports) return undefined - return { - ...pkg.imports, - ...stripSrc(tshy?.imports), - } -} diff --git a/src/config.ts b/src/config.ts index 1104db8..a978496 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,7 @@ // get the config and package and stuff +import chalk from 'chalk' +import * as console from './console.js' import fail from './fail.js' import pkg from './package.js' import sources from './sources.js' @@ -23,14 +25,26 @@ const validConfig = (e: any): e is TshyConfig => (e.dialects === undefined || validDialects(e.dialects)) && validExtraDialects(e) && validBoolean(e, 'selfLink') && - validBoolean(e, 'main') && - validImports(e, pkg) + validBoolean(e, 'main') const getConfig = ( pkg: Package, sources: Set ): TshyConfig => { const tshy: TshyConfig = validConfig(pkg.tshy) ? pkg.tshy : {} + const ti = tshy as TshyConfig & { imports?: any } + if (ti.imports) { + console.debug( + chalk.cyan.dim('imports') + + ' moving from tshy config to top level' + ) + pkg.imports = { + ...pkg.imports, + ...ti.imports, + } + delete ti.imports + } + validImports(pkg) if (tshy.exports) return tshy const e: Exclude = { './package.json': './package.json', diff --git a/src/tsconfig.ts b/src/tsconfig.ts index 90bb803..7d472fb 100644 --- a/src/tsconfig.ts +++ b/src/tsconfig.ts @@ -11,7 +11,6 @@ import { join } from 'node:path/posix' import * as console from './console.js' // the commonjs build needs to exclude anything that will be polyfilled -import { addToFile, addToObject } from './add-paths-to-tsconfig.js' import config from './config.js' import polyfills from './polyfills.js' @@ -21,7 +20,7 @@ const { commonjsDialects = [], } = config -const recommended: Record = addToObject({ +const recommended: Record = { compilerOptions: { declaration: true, declarationMap: true, @@ -38,7 +37,7 @@ const recommended: Record = addToObject({ strict: true, target: 'es2022', }, -}) +} const build: Record = { extends: '../tsconfig.json', @@ -101,7 +100,6 @@ if (!existsSync('tsconfig.json')) { writeConfig('../tsconfig', recommended) } else { console.debug('using existing tsconfig.json') - addToFile() } for (const f of readdirSync('.tshy')) { unlinkSync(resolve('.tshy', f)) diff --git a/src/types.ts b/src/types.ts index 8433a1e..1dbf0cb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,7 +6,6 @@ import type { export type TshyConfig = { exports?: Record - imports?: Record dialects?: Dialect[] selfLink?: boolean main?: boolean diff --git a/src/valid-imports.ts b/src/valid-imports.ts index 1a8f794..c47ff62 100644 --- a/src/valid-imports.ts +++ b/src/valid-imports.ts @@ -1,31 +1,29 @@ import fail from './fail.js' -import { Package, TshyConfig } from './types.js' +import { Package } from './types.js' +import validExternalExport from './valid-external-export.js' -// this validates the tshy.imports field. -export default ({ imports }: TshyConfig, pkg: Package) => { +// validate the package.imports field +export default (pkg: Package) => { + const { imports } = pkg if (imports === undefined) return true - const { imports: pkgImports = {} } = pkg - if (typeof imports !== 'object' || Array.isArray(imports)) { - fail('tshy.imports must be an object if specified') + if (Array.isArray(imports) || typeof imports !== 'object') { + fail( + 'invalid imports object, must be Record, ' + + `got: ${JSON.stringify(imports)}` + ) return process.exit(1) } + for (const [i, v] of Object.entries(imports)) { - if (i in pkgImports) { - fail( - 'tshy.imports keys must not appear in top-level imports, ' + - 'found in both: ' + - JSON.stringify(i) - ) - return process.exit(1) - } if (!i.startsWith('#') || i === '#' || i.startsWith('#/')) { - fail('invalid tshy.imports module specifier: ' + i) + fail('invalid imports module specifier: ' + i) return process.exit(1) } - if (typeof v !== 'string' || !v.startsWith('./src/')) { + if (typeof v === 'string') continue + if (!validExternalExport(v)) { fail( - 'tshy.imports values must start with "./src/", ' + - 'got: ' + + `unbuilt package.imports ${i} must not be in ./src, ` + + 'and imports in ./src must be string values. got: ' + JSON.stringify(v) ) return process.exit(1) diff --git a/tap-snapshots/test/config.ts.test.cjs b/tap-snapshots/test/config.ts.test.cjs index 48dd34a..d1c9809 100644 --- a/tap-snapshots/test/config.ts.test.cjs +++ b/tap-snapshots/test/config.ts.test.cjs @@ -14,11 +14,11 @@ tshy.exports ./blah unbuilt exports must not be in ./src, and exports in src mus ` exports[`test/config.ts > TAP > {"config":{"imports":"blah"},"sources":[],"ok":false} > must match snapshot 1`] = ` -tshy.imports must be an object if specified +invalid imports module specifier: 0 ` exports[`test/config.ts > TAP > {"config":{"imports":["blah"]},"sources":[],"ok":false} > must match snapshot 1`] = ` -tshy.imports must be an object if specified +invalid imports module specifier: 0 ` exports[`test/config.ts > TAP > {"config":{"main":"blah"},"sources":[],"ok":false} > must match snapshot 1`] = ` diff --git a/tap-snapshots/test/valid-imports.ts.test.cjs b/tap-snapshots/test/valid-imports.ts.test.cjs index 91843ef..cde7920 100644 --- a/tap-snapshots/test/valid-imports.ts.test.cjs +++ b/tap-snapshots/test/valid-imports.ts.test.cjs @@ -5,30 +5,22 @@ * Make sure to inspect the output below. Do not ignore changes! */ 'use strict' -exports[`test/valid-imports.ts > TAP > {"config":{"imports":"asdf"},"pkg":{}} > failure message 1`] = ` -tshy.imports must be an object if specified +exports[`test/valid-imports.ts > TAP > {"pkg":{"imports":"asdf"}} > failure message 1`] = ` +invalid imports object, must be Record, got: "asdf" ` -exports[`test/valid-imports.ts > TAP > {"config":{"imports":[]},"pkg":{}} > failure message 1`] = ` -tshy.imports must be an object if specified +exports[`test/valid-imports.ts > TAP > {"pkg":{"imports":[]}} > failure message 1`] = ` +invalid imports object, must be Record, got: [] ` -exports[`test/valid-imports.ts > TAP > {"config":{"imports":{"#":"y"}},"pkg":{}} > failure message 1`] = ` -invalid tshy.imports module specifier: # +exports[`test/valid-imports.ts > TAP > {"pkg":{"imports":{"#":"y"}}} > failure message 1`] = ` +invalid imports module specifier: # ` -exports[`test/valid-imports.ts > TAP > {"config":{"imports":{"#x":"./src/x"}},"pkg":{"imports":{"#x":{}}}} > failure message 1`] = ` -tshy.imports keys must not appear in top-level imports, found in both: "#x" +exports[`test/valid-imports.ts > TAP > {"pkg":{"imports":{"#x":["./src/x"]}}} > failure message 1`] = ` +unbuilt package.imports #x must not be in ./src, and imports in ./src must be string values. got: ["./src/x"] ` -exports[`test/valid-imports.ts > TAP > {"config":{"imports":{"#x":"y"}},"pkg":{}} > failure message 1`] = ` -tshy.imports values must start with "./src/", got: "y" -` - -exports[`test/valid-imports.ts > TAP > {"config":{"imports":{"#x":{}}},"pkg":{}} > failure message 1`] = ` -tshy.imports values must start with "./src/", got: {} -` - -exports[`test/valid-imports.ts > TAP > {"config":{"imports":{"x":"y"}},"pkg":{}} > failure message 1`] = ` -invalid tshy.imports module specifier: x +exports[`test/valid-imports.ts > TAP > {"pkg":{"imports":{"x":"y"}}} > failure message 1`] = ` +invalid imports module specifier: x ` diff --git a/test/add-paths-to-tsconfig.ts b/test/add-paths-to-tsconfig.ts deleted file mode 100644 index 9a7cc82..0000000 --- a/test/add-paths-to-tsconfig.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { readFileSync } from 'node:fs' -import { fileURLToPath } from 'node:url' -import t from 'tap' - -let failed: string | undefined = undefined -const mockFail = (m: string) => (failed = m) - -const exits = t.capture(process, 'exit').args - -const mockConfig = { - imports: { - '#x': './src/blah.ts', - '#x/*': './src/blah/*.ts', - }, -} -const { paths: _, ...tsconfig } = JSON.parse( - readFileSync( - fileURLToPath(new URL('../tsconfig.json', import.meta.url)), - 'utf8' - ) -) - -t.test('add to object', async t => { - const { addToObject } = (await t.mockImport( - '../src/add-paths-to-tsconfig.js', - { - '../src/config.js': { default: mockConfig }, - '../src/fail.js': { default: mockFail }, - } - )) as typeof import('../src/add-paths-to-tsconfig.js') - const { paths } = addToObject(tsconfig) - t.strictSame(paths, { - '#x': ['./src/blah.ts'], - '#x/*': ['./src/blah/*.ts'], - }) - t.equal(failed, undefined) - t.strictSame(exits(), []) -}) - -t.test('add to object, nothing to add', async t => { - const { addToObject } = (await t.mockImport( - '../src/add-paths-to-tsconfig.js', - { - '../src/config.js': { default: {} }, - '../src/fail.js': { default: mockFail }, - } - )) as typeof import('../src/add-paths-to-tsconfig.js') - const added = addToObject(tsconfig) - t.equal(added, tsconfig, 'no change made') - t.equal(failed, undefined) - t.strictSame(exits(), []) -}) - -t.test('add to file', async t => { - const dir = t.testdir({ - 'tsconfig.json': JSON.stringify(tsconfig), - }) - const cwd = process.cwd() - process.chdir(dir) - t.teardown(() => process.chdir(cwd)) - - const { addToFile } = (await t.mockImport( - '../src/add-paths-to-tsconfig.js', - { - '../src/config.js': { default: mockConfig }, - '../src/fail.js': { default: mockFail }, - } - )) as typeof import('../src/add-paths-to-tsconfig.js') - addToFile() - const { paths } = JSON.parse( - readFileSync(dir + '/tsconfig.json', 'utf8') - ) - t.strictSame(paths, { - '#x': ['./src/blah.ts'], - '#x/*': ['./src/blah/*.ts'], - }) - t.equal(failed, undefined) - t.strictSame(exits(), []) -}) - -t.test('add to file, nothing to add', async t => { - const dir = t.testdir({ - 'tsconfig.json': JSON.stringify(tsconfig), - }) - const cwd = process.cwd() - process.chdir(dir) - t.teardown(() => process.chdir(cwd)) - - const { addToFile } = (await t.mockImport( - '../src/add-paths-to-tsconfig.js', - { - '../src/config.js': { default: {} }, - '../src/fail.js': { default: mockFail }, - } - )) as typeof import('../src/add-paths-to-tsconfig.js') - addToFile() - const result = JSON.parse( - readFileSync(dir + '/tsconfig.json', 'utf8') - ) - t.strictSame(result, tsconfig, 'no change made') - t.equal(failed, undefined) - t.strictSame(exits(), []) -}) - -t.test('fail to parse', async t => { - const tsconfigContent = JSON.stringify(tsconfig, null, 2).replace( - /\{/, - '{ // comments not supported' - ) - const dir = t.testdir({ - 'tsconfig.json': tsconfigContent, - }) - const cwd = process.cwd() - process.chdir(dir) - t.teardown(() => process.chdir(cwd)) - - const { addToFile } = (await t.mockImport( - '../src/add-paths-to-tsconfig.js', - { - '../src/config.js': { default: mockConfig }, - '../src/fail.js': { default: mockFail }, - } - )) as typeof import('../src/add-paths-to-tsconfig.js') - addToFile() - t.equal( - readFileSync(dir + '/tsconfig.json', 'utf8'), - tsconfigContent, - 'no change made to file' - ) - t.type(failed, 'string') - t.matchSnapshot(failed, 'failure message') - t.strictSame(exits(), [[1]]) -}) diff --git a/test/built-imports.ts b/test/built-imports.ts index c732434..735ca68 100644 --- a/test/built-imports.ts +++ b/test/built-imports.ts @@ -11,12 +11,10 @@ const cases: [ [{ imports: { '#x': 'y' } }, { '#x': 'y' }], [ { - tshy: { - imports: { - '#t': './src/t.ts', - '#x': './src/x.tsx', - '#b/*': './src/blah/*.ts', - }, + imports: { + '#t': './src/t.ts', + '#x': './src/x.tsx', + '#b/*': './src/blah/*.ts', }, }, { '#t': './t.js', '#x': './x.js', '#b/*': './blah/*.js' }, @@ -25,13 +23,9 @@ const cases: [ { imports: { '#a': './xyz/a.js', - }, - tshy: { - imports: { - '#t': './src/t.ts', - '#x': './src/x.tsx', - '#b/*': './src/blah/*.ts', - }, + '#t': './src/t.ts', + '#x': './src/x.tsx', + '#b/*': './src/blah/*.ts', }, }, { diff --git a/test/config.ts b/test/config.ts index 4c72610..f536761 100644 --- a/test/config.ts +++ b/test/config.ts @@ -60,20 +60,6 @@ const cases: [ //@ts-expect-error [{ imports: ['blah'] }, [], false, {}], - - [ - { imports: { '#blah': './src/blah.ts' } }, - [], - true, - { - imports: { - '#blah': './src/blah.ts', - }, - exports: { - './package.json': './package.json', - }, - }, - ], ] t.plan(cases.length) diff --git a/test/valid-imports.ts b/test/valid-imports.ts index bcb86f9..7d5baf6 100644 --- a/test/valid-imports.ts +++ b/test/valid-imports.ts @@ -1,5 +1,5 @@ import t from 'tap' -import { Package, TshyConfig } from '../src/types.js' +import { Package } from '../src/types.js' let failed: undefined | string = undefined @@ -14,32 +14,22 @@ const { default: validImports } = (await t.mockImport( const exits = t.capture(process, 'exit').args -const cases: [ - conf: TshyConfig, - pkg: Omit, - ok: boolean -][] = [ - [{}, {}, true], - //@ts-expect-error - [{ imports: 'asdf' }, {}, false], - //@ts-expect-error - [{ imports: [] }, {}, false], - //@ts-expect-error - [{ imports: { '#x': {} } }, {}, false], - [{ imports: { '#x': 'y' } }, {}, false], - [{ imports: { x: 'y' } }, {}, false], - [{ imports: { '#': 'y' } }, {}, false], +const cases: [pkg: Omit, ok: boolean][] = [ - { imports: { '#x': './src/x' } }, - { imports: { '#x': {} } }, - false, - ], - [{ imports: { '#x': './src/x' } }, {}, true], -] + [{}, true], + [{ imports: 'asdf' }, false], + [{ imports: [] }, false], + [{ imports: { '#x': {} } }, true], + [{ imports: { '#x': 'y' } }, true], + [{ imports: { x: 'y' } }, false], + [{ imports: { '#': 'y' } }, false], + [{ imports: { '#x': './src/x' } }, true], + [{ imports: { '#x': ['./src/x'] }}, false], + ] -for (const [config, pkg, ok] of cases) { - t.test(JSON.stringify({ config, pkg }), t => { - const actual = validImports(config, pkg as Package) +for (const [pkg, ok] of cases) { + t.test(JSON.stringify({ pkg }), t => { + const actual = validImports(pkg as Package) if (!ok) { t.notOk(actual, 'should not be ok') t.matchSnapshot(failed, 'failure message')