From bae00758cd4ef2e373b76c927d0c89858d21e9e2 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 18 Oct 2023 22:54:29 -0700 Subject: [PATCH] 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')