├── .github └── workflows │ ├── ci.yml │ └── typedoc.yml ├── .gitignore ├── .prettierignore ├── .tshy ├── build.json └── esm.json ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── map.js ├── package-lock.json ├── package.json ├── scripts └── fixup.sh ├── src ├── add-dot.ts ├── bins.ts ├── build-commonjs.ts ├── build-esm.ts ├── build-fail.ts ├── build-live-commonjs.ts ├── build-live-esm.ts ├── build.ts ├── built-imports.ts ├── clean-build-tmp.ts ├── config.ts ├── console.ts ├── dialects.ts ├── exports.ts ├── fail.ts ├── if-exist.ts ├── index.ts ├── package.ts ├── polyfills.ts ├── prevent-verbatim-module-syntax.ts ├── read-typescript-config.ts ├── resolve-export.ts ├── self-link.ts ├── set-folder-dialect.ts ├── sources.ts ├── tsconfig.ts ├── types.ts ├── unbuilt-imports.ts ├── usage.ts ├── valid-dialects.ts ├── valid-exclude.ts ├── valid-exports.ts ├── valid-external-export.ts ├── valid-extra-dialects.ts ├── valid-imports.ts ├── valid-project.ts ├── watch.ts ├── which-tsc.ts └── write-package.ts ├── tap-snapshots └── test │ ├── add-paths-to-tsconfig.ts.test.cjs │ ├── build-commonjs.ts.test.cjs │ ├── build-esm.ts.test.cjs │ ├── build-fail.ts.test.cjs │ ├── build-live-commonjs.ts.test.cjs │ ├── build-live-esm.ts.test.cjs │ ├── build.ts.test.cjs │ ├── config.ts.test.cjs │ ├── exports.ts.test.cjs │ ├── fail.ts.test.cjs │ ├── polyfills.ts.test.cjs │ ├── prevent-verbatim-module-syntax.ts.test.cjs │ ├── self-dep.ts.test.cjs │ ├── tsconfig.ts.test.cjs │ ├── usage.ts.test.cjs │ ├── valid-exports.ts.test.cjs │ ├── valid-extra-dialects.ts.test.cjs │ ├── valid-imports.ts.test.cjs │ └── watch.ts.test.cjs ├── test ├── add-dot.ts ├── bins.ts ├── build-commonjs.ts ├── build-esm.ts ├── build-fail.ts ├── build-live-commonjs.ts ├── build-live-esm.ts ├── build.ts ├── built-imports.ts ├── clean-build-tmp.ts ├── config.ts ├── console.ts ├── dialects.ts ├── exports.ts ├── fail.ts ├── fixtures │ ├── basic-custom-project │ │ ├── .tshy │ │ │ ├── build.json │ │ │ ├── commonjs.json │ │ │ └── esm.json │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ └── tsconfig.custom.json │ ├── basic-imports-only-deps │ │ ├── .tshy │ │ │ ├── build.json │ │ │ ├── commonjs.json │ │ │ └── esm.json │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── basic │ │ ├── .tshy │ │ │ ├── build.json │ │ │ ├── commonjs.json │ │ │ └── esm.json │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── imports-with-star │ │ ├── .tshy │ │ │ ├── build.json │ │ │ ├── commonjs.json │ │ │ └── esm.json │ │ ├── lib │ │ │ ├── foo-browser.js │ │ │ ├── foo-global.d.ts │ │ │ ├── foo-import.mjs │ │ │ ├── foo-node.cjs │ │ │ ├── foo-node.mjs │ │ │ ├── foo-require.cjs │ │ │ ├── foo.d.cts │ │ │ ├── foo.d.mts │ │ │ └── foo.d.ts │ │ ├── package.json │ │ ├── root.cjs │ │ ├── src │ │ │ ├── g.ts │ │ │ └── index.ts │ │ └── tsconfig.json │ └── imports │ │ ├── .tshy │ │ ├── build.json │ │ ├── commonjs.json │ │ └── esm.json │ │ ├── lib │ │ ├── foo-browser.js │ │ ├── foo-global.d.ts │ │ ├── foo-import.mjs │ │ ├── foo-node.cjs │ │ ├── foo-node.mjs │ │ ├── foo-require.cjs │ │ ├── foo.d.cts │ │ ├── foo.d.mts │ │ └── foo.d.ts │ │ ├── package.json │ │ ├── root.cjs │ │ ├── src │ │ ├── g.ts │ │ └── index.ts │ │ └── tsconfig.json ├── if-exist.ts ├── index.ts ├── package.ts ├── polyfills.ts ├── prevent-verbatim-module-syntax.ts ├── read-typescript-config.ts ├── resolve-export.ts ├── self-dep.ts ├── set-folder-dialect.ts ├── sources.ts ├── tsconfig.ts ├── types.ts ├── unbuilt-imports.ts ├── usage.ts ├── valid-dialects.ts ├── valid-exclude.ts ├── valid-exports.ts ├── valid-external-export.ts ├── valid-extra-dialects.ts ├── valid-imports.ts ├── watch.ts ├── which-tsc.ts └── write-package.ts ├── tsconfig.json └── typedoc.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | node-version: [18.x, 20.x] 10 | platform: 11 | - os: ubuntu-latest 12 | shell: bash 13 | - os: macos-latest 14 | shell: bash 15 | fail-fast: false 16 | 17 | runs-on: ${{ matrix.platform.os }} 18 | defaults: 19 | run: 20 | shell: ${{ matrix.platform.shell }} 21 | 22 | steps: 23 | - name: Checkout Repository 24 | uses: actions/checkout@v4 25 | 26 | - name: Use Nodejs ${{ matrix.node-version }} 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | cache: npm 31 | 32 | - name: Install dependencies 33 | run: npm install 34 | 35 | - name: Run Tests 36 | run: npm test -- -c -t0 37 | -------------------------------------------------------------------------------- /.github/workflows/typedoc.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Single deploy job since we're just deploying 25 | deploy: 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | - name: Use Nodejs ${{ matrix.node-version }} 34 | uses: actions/setup-node@v3 35 | with: 36 | node-version: 18.x 37 | - name: Install dependencies 38 | run: npm install 39 | - name: Generate typedocs 40 | run: npm run typedoc 41 | 42 | - name: Setup Pages 43 | uses: actions/configure-pages@v3 44 | - name: Upload artifact 45 | uses: actions/upload-pages-artifact@v1 46 | with: 47 | path: './docs' 48 | - name: Deploy to GitHub Pages 49 | id: deployment 50 | uses: actions/deploy-pages@v1 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.tshy-build 2 | /node_modules 3 | /dist 4 | /docs 5 | /.tap 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /.* 2 | /package*.json 3 | /node_modules 4 | /example 5 | /.github 6 | /.tshy 7 | /.tsh-build 8 | /dist 9 | /tap-snapshots 10 | /.nyc_output 11 | /coverage 12 | /benchmark 13 | /.tap 14 | /docs 15 | /LICENSE.md 16 | /test/fixtures 17 | -------------------------------------------------------------------------------- /.tshy/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "../src", 5 | "module": "nodenext", 6 | "moduleResolution": "nodenext" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.tshy/esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.mts", 6 | "../src/**/*.tsx", 7 | "../src/**/*.json" 8 | ], 9 | "exclude": [ 10 | "../src/package.json" 11 | ], 12 | "compilerOptions": { 13 | "outDir": "../.tshy-build/esm" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 3.0 2 | 3 | - Drop support for nodes before 20 4 | 5 | # 2.0 6 | 7 | - No longer create a `source` export automatically 8 | - Limit custom `sourceDialects` exports to only those that match 9 | the package type. For example, do not export source under a 10 | `require` condition if the package.json is `"type": "module"`. 11 | - Treat `composite` and `incremental` the same, since `composite` 12 | implies `incremental`. 13 | 14 | # 1.18 15 | 16 | - Add `sourceDialects` 17 | 18 | # 1.17 19 | 20 | - Add `module` field if a top-level `esm` export exists for 21 | subpath `"."`, and `tshy.module` is not set to false. 22 | 23 | # 1.16 24 | 25 | - Upgrade to TypeScript 5.5 26 | 27 | # 1.15 28 | 29 | - Add `liveDev` option 30 | - Add `"source"` export condition 31 | 32 | # 1.14 33 | 34 | - Do not fail if sourcemaps are not generated 35 | - Support glob patterns in `tshy.exports` 36 | - Add support for TypeScript 5.4 37 | 38 | # 1.13 39 | 40 | - Take `target` from `tsconfig.json` if present, rather than 41 | hard-coding in the `build.json` config. 42 | - Find `tsc` where pnpm hides it. 43 | 44 | # 1.12 45 | 46 | - Respect `package.json` type field if set to `"commonjs"` 47 | - Ignore `package.json` changes in `tshy --watch` if the data 48 | itself did not change. 49 | 50 | # 1.11 51 | 52 | - Add support for TypeScript 5.3 53 | - Preserve indentation/newlines in `package.json` files 54 | 55 | # 1.10 56 | 57 | - Exclude sources from all builds via the `exclude` config 58 | setting. 59 | 60 | # 1.9 61 | 62 | - Set a custom tsconfig file via the `project` config setting. 63 | 64 | # 1.8 65 | 66 | - Support `"incremental": true` tsconfig option, making the build 67 | directory persistent if there are `*.tsbuildinfo` files 68 | present. 69 | - Rename build directory from '.tshy-build-tmp' to '.tshy-build', 70 | since it's not temporary if incremental builds are used. 71 | - Make the `selfLink` best-effort if not explicitly true or 72 | false. 73 | 74 | # 1.7 75 | 76 | - Prevent `verbatimModuleSyntax` ts config if building for both 77 | ESM and CommonJS, as it's fundamentally incompatible 78 | - Add `--watch` option 79 | - Add `--help` option 80 | 81 | # 1.6 82 | 83 | - put all imports in top-level imports field (2023-10-19) 84 | 85 | # 1.5 86 | 87 | - Add `tshy.imports` config 88 | 89 | # 1.4 90 | 91 | - Add `tshy.esmDialects` and `tshy.commonjsDialects` configs 92 | - Use more complete package/import/export types defined by the 93 | `resolve-import` package 94 | 95 | # 1.3 96 | 97 | - Default `tshy.main = true` if a `'.'` CommonJS export is 98 | present 99 | 100 | # 1.2 101 | 102 | - Initial experimental support for `tshy.main` 103 | - Add `tshy.selfLink` config to suppress the internal symlink 104 | 105 | # 1.1 106 | 107 | - Add support for local package imports/exports 108 | 109 | # 1.0 110 | 111 | - Initial version 112 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All packages under `src/` are licensed according to the terms in 2 | their respective `LICENSE` or `LICENSE.md` files. 3 | 4 | The remainder of this project is licensed under the Blue Oak 5 | Model License, as follows: 6 | 7 | ----- 8 | 9 | # Blue Oak Model License 10 | 11 | Version 1.0.0 12 | 13 | ## Purpose 14 | 15 | This license gives everyone as much permission to work with 16 | this software as possible, while protecting contributors 17 | from liability. 18 | 19 | ## Acceptance 20 | 21 | In order to receive this license, you must agree to its 22 | rules. The rules of this license are both obligations 23 | under that agreement and conditions to your license. 24 | You must not do anything with this software that triggers 25 | a rule that you cannot or will not follow. 26 | 27 | ## Copyright 28 | 29 | Each contributor licenses you to do everything with this 30 | software that would otherwise infringe that contributor's 31 | copyright in it. 32 | 33 | ## Notices 34 | 35 | You must ensure that everyone who gets a copy of 36 | any part of this software from you, with or without 37 | changes, also gets the text of this license or a link to 38 | . 39 | 40 | ## Excuse 41 | 42 | If anyone notifies you in writing that you have not 43 | complied with [Notices](#notices), you can keep your 44 | license by taking all practical steps to comply within 30 45 | days after the notice. If you do not do so, your license 46 | ends immediately. 47 | 48 | ## Patent 49 | 50 | Each contributor licenses you to do everything with this 51 | software that would otherwise infringe any patent claims 52 | they can license or become able to license. 53 | 54 | ## Reliability 55 | 56 | No contributor can revoke this license. 57 | 58 | ## No Liability 59 | 60 | ***As far as the law allows, this software comes as is, 61 | without any warranty or condition, and no contributor 62 | will be liable to anyone for any damages related to this 63 | software or this license, under any kind of legal claim.*** 64 | -------------------------------------------------------------------------------- /map.js: -------------------------------------------------------------------------------- 1 | export default s => s.replace(/test/, 'src') 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tshy", 3 | "version": "3.0.2", 4 | "description": "TypeScript HYbridizer - Hybrid (CommonJS/ESM) TypeScript node package builder", 5 | "author": "Isaac Z. Schlueter (https://izs.me)", 6 | "license": "BlueOak-1.0.0", 7 | "type": "module", 8 | "bin": "./dist/esm/index.js", 9 | "files": [ 10 | "dist" 11 | ], 12 | "dependencies": { 13 | "chalk": "^5.3.0", 14 | "chokidar": "^3.6.0", 15 | "foreground-child": "^3.1.1", 16 | "minimatch": "^10.0.0", 17 | "mkdirp": "^3.0.1", 18 | "polite-json": "^5.0.0", 19 | "resolve-import": "^2.0.0", 20 | "rimraf": "^6.0.0", 21 | "sync-content": "^2.0.1", 22 | "typescript": "^5.5.3", 23 | "walk-up-path": "^4.0.0" 24 | }, 25 | "scripts": { 26 | "preversion": "npm test", 27 | "postversion": "npm publish", 28 | "prepublishOnly": "git push origin --follow-tags", 29 | "prepare": "tsc -p .tshy/esm.json && bash scripts/fixup.sh", 30 | "pretest": "npm run prepare", 31 | "presnap": "npm run prepare", 32 | "format": "prettier --write . --ignore-path ./.prettierignore --cache", 33 | "typedoc": "typedoc", 34 | "test": "tap", 35 | "snap": "tap" 36 | }, 37 | "tap": { 38 | "coverage-map": "map.js" 39 | }, 40 | "engines": { 41 | "node": "20 || >=22" 42 | }, 43 | "repository": "https://github.com/isaacs/tshy", 44 | "keywords": [ 45 | "typescript", 46 | "tsc", 47 | "hybrid", 48 | "esm", 49 | "commonjs", 50 | "build" 51 | ], 52 | "devDependencies": { 53 | "@types/node": "^20.14.10", 54 | "prettier": "^3.3.2", 55 | "tap": "^20.0.3", 56 | "typedoc": "^0.26.3" 57 | }, 58 | "prettier": { 59 | "experimentalTernaries": true, 60 | "semi": false, 61 | "printWidth": 70, 62 | "tabWidth": 2, 63 | "useTabs": false, 64 | "singleQuote": true, 65 | "jsxSingleQuote": false, 66 | "bracketSameLine": true, 67 | "arrowParens": "avoid", 68 | "endOfLine": "lf" 69 | }, 70 | "tshy": { 71 | "dialects": [ 72 | "esm" 73 | ], 74 | "exports": { 75 | "./package.json": "./package.json", 76 | ".": "./src/index.ts" 77 | } 78 | }, 79 | "exports": { 80 | "./package.json": "./package.json", 81 | ".": { 82 | "import": { 83 | "types": "./dist/esm/index.d.ts", 84 | "default": "./dist/esm/index.js" 85 | } 86 | } 87 | }, 88 | "module": "./dist/esm/index.js" 89 | } 90 | -------------------------------------------------------------------------------- /scripts/fixup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | chmod 0755 .tshy-build/esm/index.js 4 | sync-content .tshy-build dist 5 | rm -rf .tshy-build 6 | -------------------------------------------------------------------------------- /src/add-dot.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path/posix' 2 | export default (s: string) => `./${join(s)}` 3 | -------------------------------------------------------------------------------- /src/bins.ts: -------------------------------------------------------------------------------- 1 | // chmod bins after build 2 | import { chmodSync } from 'fs' 3 | import { resolve } from 'path' 4 | import pkg from './package.js' 5 | export default () => { 6 | const { bin } = pkg 7 | if (!bin) return 8 | if (typeof bin === 'string') { 9 | chmodSync(resolve(bin), 0o755) 10 | } else { 11 | for (const v of Object.values(bin)) { 12 | chmodSync(resolve(v), 0o755) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/build-commonjs.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { spawnSync } from 'node:child_process' 3 | import { relative, resolve } from 'node:path/posix' 4 | import buildFail from './build-fail.js' 5 | import config from './config.js' 6 | import * as console from './console.js' 7 | import ifExist from './if-exist.js' 8 | import polyfills from './polyfills.js' 9 | import setFolderDialect from './set-folder-dialect.js' 10 | import './tsconfig.js' 11 | import tsc from './which-tsc.js' 12 | 13 | const node = process.execPath 14 | const { commonjsDialects = [] } = config 15 | 16 | export const buildCommonJS = () => { 17 | setFolderDialect('src', 'commonjs') 18 | for (const d of ['commonjs', ...commonjsDialects]) { 19 | const pf = polyfills.get(d === 'commonjs' ? 'cjs' : d) 20 | console.debug(chalk.cyan.dim('building ' + d)) 21 | const res = spawnSync(node, [tsc, '-p', `.tshy/${d}.json`], { 22 | stdio: 'inherit', 23 | }) 24 | if (res.status || res.signal) { 25 | setFolderDialect('src') 26 | return buildFail(res) 27 | } 28 | setFolderDialect('.tshy-build/' + d, 'commonjs') 29 | for (const [override, orig] of pf?.map.entries() ?? []) { 30 | const stemFrom = resolve( 31 | `.tshy-build/${d}`, 32 | relative(resolve('src'), resolve(override)), 33 | ).replace(/\.cts$/, '') 34 | const stemTo = resolve( 35 | `.tshy-build/${d}`, 36 | relative(resolve('src'), resolve(orig)), 37 | ).replace(/\.tsx?$/, '') 38 | const stemToPath = `${stemTo}.js.map` 39 | const stemToDtsPath = `${stemTo}.d.ts.map` 40 | ifExist.unlink(stemToPath) 41 | ifExist.unlink(stemToDtsPath) 42 | ifExist.rename(`${stemFrom}.cjs`, `${stemTo}.js`) 43 | ifExist.rename(`${stemFrom}.d.cts`, `${stemTo}.d.ts`) 44 | } 45 | console.error(chalk.cyan.bold('built commonjs')) 46 | } 47 | setFolderDialect('src') 48 | } 49 | -------------------------------------------------------------------------------- /src/build-esm.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { spawnSync } from 'node:child_process' 3 | import { relative, resolve } from 'node:path' 4 | import buildFail from './build-fail.js' 5 | import config from './config.js' 6 | import * as console from './console.js' 7 | import ifExist from './if-exist.js' 8 | import polyfills from './polyfills.js' 9 | import setFolderDialect from './set-folder-dialect.js' 10 | import './tsconfig.js' 11 | import tsc from './which-tsc.js' 12 | 13 | const node = process.execPath 14 | const { esmDialects = [] } = config 15 | 16 | export const buildESM = () => { 17 | setFolderDialect('src', 'esm') 18 | for (const d of ['esm', ...esmDialects]) { 19 | const pf = polyfills.get(d) 20 | console.debug(chalk.cyan.dim('building ' + d)) 21 | const res = spawnSync(node, [tsc, '-p', `.tshy/${d}.json`], { 22 | stdio: 'inherit', 23 | }) 24 | if (res.status || res.signal) { 25 | setFolderDialect('src') 26 | return buildFail(res) 27 | } 28 | setFolderDialect('.tshy-build/' + d, 'esm') 29 | for (const [override, orig] of pf?.map.entries() ?? []) { 30 | const stemFrom = resolve( 31 | `.tshy-build/${d}`, 32 | relative(resolve('src'), resolve(override)), 33 | ).replace(/\.mts$/, '') 34 | const stemTo = resolve( 35 | `.tshy-build/${d}`, 36 | relative(resolve('src'), resolve(orig)), 37 | ).replace(/\.tsx?$/, '') 38 | ifExist.unlink(`${stemTo}.js.map`) 39 | ifExist.unlink(`${stemTo}.d.ts.map`) 40 | ifExist.rename(`${stemFrom}.mjs`, `${stemTo}.js`) 41 | ifExist.rename(`${stemFrom}.d.mts`, `${stemTo}.d.ts`) 42 | } 43 | console.error(chalk.cyan.bold('built ' + d)) 44 | } 45 | setFolderDialect('src') 46 | } 47 | -------------------------------------------------------------------------------- /src/build-fail.ts: -------------------------------------------------------------------------------- 1 | import { SpawnSyncReturns } from 'node:child_process' 2 | import * as console from './console.js' 3 | import fail from './fail.js' 4 | import setFolderDialect from './set-folder-dialect.js' 5 | import './tsconfig.js' 6 | import { unlink as unlinkImports } from './unbuilt-imports.js' 7 | import { unlink as unlinkSelfDep } from './self-link.js' 8 | import pkg from './package.js' 9 | 10 | export default (res: SpawnSyncReturns) => { 11 | setFolderDialect('src') 12 | unlinkImports(pkg, 'src') 13 | unlinkSelfDep(pkg, 'src') 14 | fail('build failed') 15 | console.error(res) 16 | process.exit(1) 17 | } 18 | -------------------------------------------------------------------------------- /src/build-live-commonjs.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { linkSync, mkdirSync } from 'node:fs' 3 | import { dirname } from 'node:path' 4 | import { relative, resolve } from 'node:path/posix' 5 | import config from './config.js' 6 | import * as console from './console.js' 7 | import ifExist from './if-exist.js' 8 | import polyfills from './polyfills.js' 9 | import setFolderDialect from './set-folder-dialect.js' 10 | import sources from './sources.js' 11 | import './tsconfig.js' 12 | 13 | const { commonjsDialects = [] } = config 14 | 15 | // don't actually do a build, just link files into places. 16 | export const buildLiveCommonJS = () => { 17 | for (const d of ['commonjs', ...commonjsDialects]) { 18 | const pf = polyfills.get(d === 'commonjs' ? 'cjs' : d) 19 | console.debug(chalk.cyan.dim('linking ' + d)) 20 | for (const s of sources) { 21 | const source = s.substring('./src/'.length) 22 | const target = resolve(`.tshy-build/${d}/${source}`) 23 | mkdirSync(dirname(target), { recursive: true }) 24 | linkSync(s, target) 25 | } 26 | setFolderDialect('.tshy-build/' + d, 'commonjs') 27 | for (const [override, orig] of pf?.map.entries() ?? []) { 28 | const stemFrom = resolve( 29 | `.tshy-build/${d}`, 30 | relative(resolve('src'), resolve(override)), 31 | ).replace(/\.cts$/, '') 32 | const stemTo = resolve( 33 | `.tshy-build/${d}`, 34 | relative(resolve('src'), resolve(orig)), 35 | ).replace(/\.tsx?$/, '') 36 | ifExist.unlink(`${stemTo}.js.map`) 37 | ifExist.unlink(`${stemTo}.d.ts.map`) 38 | ifExist.rename(`${stemFrom}.cjs`, `${stemTo}.js`) 39 | ifExist.rename(`${stemFrom}.d.cts`, `${stemTo}.d.ts`) 40 | } 41 | console.error(chalk.cyan.bold('linked commonjs')) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/build-live-esm.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { linkSync, mkdirSync } from 'node:fs' 3 | import { dirname, relative, resolve } from 'node:path' 4 | import config from './config.js' 5 | import * as console from './console.js' 6 | import ifExist from './if-exist.js' 7 | import polyfills from './polyfills.js' 8 | import setFolderDialect from './set-folder-dialect.js' 9 | import sources from './sources.js' 10 | import './tsconfig.js' 11 | 12 | const { esmDialects = [] } = config 13 | 14 | export const buildLiveESM = () => { 15 | for (const d of ['esm', ...esmDialects]) { 16 | const pf = polyfills.get(d) 17 | console.debug(chalk.cyan.dim('linking ' + d)) 18 | for (const s of sources) { 19 | const source = s.substring('./src/'.length) 20 | const target = resolve(`.tshy-build/${d}/${source}`) 21 | mkdirSync(dirname(target), { recursive: true }) 22 | linkSync(s, target) 23 | } 24 | setFolderDialect('.tshy-build/' + d, 'esm') 25 | for (const [override, orig] of pf?.map.entries() ?? []) { 26 | const stemFrom = resolve( 27 | `.tshy-build/${d}`, 28 | relative(resolve('src'), resolve(override)), 29 | ).replace(/\.mts$/, '') 30 | const stemTo = resolve( 31 | `.tshy-build/${d}`, 32 | relative(resolve('src'), resolve(orig)), 33 | ).replace(/\.tsx?$/, '') 34 | ifExist.unlink(`${stemTo}.js.map`) 35 | ifExist.unlink(`${stemTo}.d.ts.map`) 36 | ifExist.rename(`${stemFrom}.mjs`, `${stemTo}.js`) 37 | ifExist.rename(`${stemFrom}.d.mts`, `${stemTo}.d.ts`) 38 | } 39 | console.error(chalk.cyan.bold('linked ' + d)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/build.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import config from './config.js' 3 | import { syncContentSync } from 'sync-content' 4 | import bins from './bins.js' 5 | import { buildCommonJS } from './build-commonjs.js' 6 | import { buildESM } from './build-esm.js' 7 | import cleanBuildTmp from './clean-build-tmp.js' 8 | import * as console from './console.js' 9 | import dialects from './dialects.js' 10 | import pkg from './package.js' 11 | import { 12 | link as linkSelfDep, 13 | unlink as unlinkSelfDep, 14 | } from './self-link.js' 15 | import './tsconfig.js' 16 | import { 17 | link as linkImports, 18 | save as saveImports, 19 | unlink as unlinkImports, 20 | } from './unbuilt-imports.js' 21 | import writePackage from './write-package.js' 22 | import { buildLiveESM } from './build-live-esm.js' 23 | import { buildLiveCommonJS } from './build-live-commonjs.js' 24 | 25 | export default async () => { 26 | cleanBuildTmp() 27 | 28 | linkSelfDep(pkg, 'src') 29 | await linkImports(pkg, 'src') 30 | const liveDev = 31 | config.liveDev && 32 | process.env.npm_command !== 'publish' && 33 | process.env.npm_command !== 'pack' 34 | const esm = liveDev ? buildLiveESM : buildESM 35 | const commonjs = liveDev ? buildLiveCommonJS : buildCommonJS 36 | if (dialects.includes('esm')) esm() 37 | if (dialects.includes('commonjs')) commonjs() 38 | await unlinkImports(pkg, 'src') 39 | unlinkSelfDep(pkg, 'src') 40 | 41 | console.debug(chalk.cyan.dim('moving to ./dist')) 42 | syncContentSync('.tshy-build', 'dist') 43 | console.debug(chalk.cyan.dim('cleaning build temp dir')) 44 | 45 | cleanBuildTmp() 46 | 47 | linkSelfDep(pkg, 'dist') 48 | 49 | if (pkg.imports) { 50 | console.debug('linking package imports', pkg.imports) 51 | if (dialects.includes('commonjs')) 52 | await linkImports(pkg, 'dist/commonjs', true) 53 | if (dialects.includes('esm')) 54 | await linkImports(pkg, 'dist/esm', true) 55 | if (saveImports('dist/.tshy-link-imports.mjs')) { 56 | pkg.scripts = pkg.scripts || {} 57 | pkg.scripts.preinstall = 58 | 'node -e "import(process.argv[1]).catch(()=>{})" ' + 59 | 'dist/.tshy-link-imports.mjs' 60 | } 61 | } 62 | 63 | console.debug(chalk.cyan.dim('chmod bins')) 64 | bins() 65 | console.debug(chalk.cyan.dim('write package.json')) 66 | writePackage() 67 | } 68 | -------------------------------------------------------------------------------- /src/built-imports.ts: -------------------------------------------------------------------------------- 1 | // merge tshy.imports with package.json imports 2 | 3 | import { Package } from './types.js' 4 | 5 | // strip the ./src/ and turn ts extension into js for built imports 6 | // leave unbuilt imports alone, they'll be symlinked 7 | export default (pkg: Package): Package['imports'] => { 8 | const { imports } = pkg 9 | if (!imports) return undefined 10 | return Object.fromEntries( 11 | Object.entries(imports).map(([k, v]) => [ 12 | k, 13 | typeof v === 'string' && v.startsWith('./src/') ? 14 | './' + 15 | v 16 | .substring('./src/'.length) 17 | .replace(/\.([cm]?)ts$/, '.$1js') 18 | .replace(/\.tsx$/, '.js') 19 | : v, 20 | ]), 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/clean-build-tmp.ts: -------------------------------------------------------------------------------- 1 | // Remove the .tshy-build folder, but ONLY if 2 | // the "incremental" config value is not set, or if 3 | // it does not contain any tsbuildinfo files. 4 | // If we are in incremental mode, and have tsbuildinfo files, 5 | // then find and remove any files here that do not have a matching 6 | // source file in ./src 7 | 8 | import { readdirSync } from 'fs' 9 | import { parse } from 'path' 10 | import { rimrafSync } from 'rimraf' 11 | import * as console from './console.js' 12 | import readTypescriptConfig from './read-typescript-config.js' 13 | 14 | const cleanRemovedOutputs = (path: string, root: string) => { 15 | const entries = readdirSync(`${root}/${path}`, { 16 | withFileTypes: true, 17 | }) 18 | let sources: Set | undefined = undefined 19 | try { 20 | sources = new Set(readdirSync(`src/${path}`)) 21 | } catch {} 22 | // directory was removed 23 | if (!sources) { 24 | return rimrafSync(`${root}/${path}`) 25 | } 26 | for (const e of entries) { 27 | const outputFile = `${path}/${e.name}` 28 | if (e.isDirectory()) { 29 | cleanRemovedOutputs(outputFile, root) 30 | continue 31 | } 32 | let { ext, name } = parse(outputFile) 33 | if (ext === '.map') { 34 | continue 35 | } 36 | if (name.endsWith('.d') && ext.endsWith('ts')) { 37 | ext = '.d' + ext 38 | name = name.substring(0, name.length - '.d'.length) 39 | } 40 | 41 | const inputSearch = 42 | ext === '.js' || ext === '.d.ts' ? ['.tsx', '.ts'] 43 | : ext === '.mjs' || ext === '.d.mts' ? ['.mts'] 44 | : ext === '.cjs' || ext === '.d.cts' ? ['.cts'] 45 | : [] 46 | inputSearch.push(ext) 47 | let del = true 48 | for (const ext of inputSearch) { 49 | if (sources.has(`${name}${ext}`)) { 50 | del = false 51 | break 52 | } 53 | } 54 | if (del) { 55 | console.debug('removing output file', outputFile) 56 | rimrafSync([ 57 | `${root}/${outputFile}`, 58 | `${root}/${outputFile}.map`, 59 | ]) 60 | } 61 | } 62 | } 63 | 64 | export default () => { 65 | const config = readTypescriptConfig() 66 | if ( 67 | config.options.incremental !== true && 68 | config.options.composite !== true 69 | ) { 70 | return rimrafSync('.tshy-build') 71 | } 72 | 73 | let buildInfos: string[] | undefined = undefined 74 | try { 75 | buildInfos = readdirSync('.tshy-build/.tshy') 76 | } catch {} 77 | if (!buildInfos?.length) { 78 | return rimrafSync('.tshy-build') 79 | } 80 | 81 | // delete anything that has been removed from src. 82 | for (const dialect of readdirSync('.tshy-build')) { 83 | if (dialect === '.tshy') continue 84 | cleanRemovedOutputs('.', `.tshy-build/${dialect}`) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | // get the config and package and stuff 2 | 3 | import chalk from 'chalk' 4 | import { Minimatch } from 'minimatch' 5 | import * as console from './console.js' 6 | import fail from './fail.js' 7 | import pkg from './package.js' 8 | import sources from './sources.js' 9 | import { 10 | Package, 11 | TshyConfig, 12 | TshyConfigMaybeGlobExports, 13 | } from './types.js' 14 | import validDialects from './valid-dialects.js' 15 | import validExclude from './valid-exclude.js' 16 | import validExports from './valid-exports.js' 17 | import validExtraDialects from './valid-extra-dialects.js' 18 | import validImports from './valid-imports.js' 19 | import validProject from './valid-project.js' 20 | 21 | const validBoolean = (e: Record, name: string) => { 22 | const v = e[name] 23 | if (v === undefined || typeof v === 'boolean') return true 24 | fail(`tshy.${name} must be a boolean value if specified, got: ` + v) 25 | return process.exit(1) 26 | } 27 | 28 | const isStringArray = (e: any): e is string[] => 29 | !!e && Array.isArray(e) && !e.some(e => typeof e !== 'string') 30 | 31 | const validConfig = (e: any): e is TshyConfigMaybeGlobExports => 32 | !!e && 33 | typeof e === 'object' && 34 | (e.exports === undefined || 35 | typeof e.exports === 'string' || 36 | isStringArray(e.exports) || 37 | validExports(e.exports)) && 38 | (e.dialects === undefined || validDialects(e.dialects)) && 39 | (e.project === undefined || validProject(e.project)) && 40 | (e.exclude === undefined || validExclude(e.exclude)) && 41 | validExtraDialects(e) && 42 | validBoolean(e, 'selfLink') && 43 | validBoolean(e, 'main') && 44 | validBoolean(e, 'liveDev') 45 | 46 | const match = (e: string, pattern: Minimatch[]): boolean => 47 | pattern.some(m => m.match(e)) 48 | 49 | const parsePattern = (p: string | string[]): Minimatch[] => 50 | Array.isArray(p) ? 51 | p.map(p => new Minimatch(p.replace(/^\.\//, ''))) 52 | : parsePattern([p]) 53 | 54 | const getConfig = ( 55 | pkg: Package, 56 | sources: Set, 57 | ): TshyConfig => { 58 | const tshy: TshyConfigMaybeGlobExports = 59 | validConfig(pkg.tshy) ? pkg.tshy : {} 60 | let exportsConfig = tshy.exports 61 | if ( 62 | typeof exportsConfig === 'string' || 63 | Array.isArray(exportsConfig) 64 | ) { 65 | // Strip off the `./src` prefix and the extension 66 | // exports: "src/**/*.ts" => exports: {"./foo": "./src/foo.ts"} 67 | const exp: Exclude = {} 68 | const pattern: string | string[] = exportsConfig 69 | const m = parsePattern(pattern) 70 | for (const e of sources) { 71 | if (!match(e.replace(/^\.\//, ''), m)) continue 72 | // index is main, anything else is a subpath 73 | const sp = 74 | /^\.\/src\/index.([mc]?[jt]s|[jt]sx)$/.test(e) ? '.' : ( 75 | './' + 76 | e 77 | .replace(/^\.\/src\//, '') 78 | .replace(/\.([mc]?[tj]s|[jt]sx)$/, '') 79 | ) 80 | exp[sp] = `./${e}` 81 | } 82 | /* c8 ignore start - should be impossible */ 83 | if (!validExports(exp)) { 84 | console.error('invalid exports pattern, using default exports') 85 | delete tshy.exports 86 | exportsConfig = undefined 87 | } else { 88 | /* c8 ignore stop */ 89 | exp['./package.json'] = './package.json' 90 | tshy.exports = exp 91 | } 92 | } 93 | const config = { ...tshy } as TshyConfig 94 | const ti = config as TshyConfig & { imports?: any } 95 | if (ti.imports) { 96 | console.debug( 97 | chalk.cyan.dim('imports') + 98 | ' moving from tshy config to top level', 99 | ) 100 | pkg.imports = { 101 | ...pkg.imports, 102 | ...ti.imports, 103 | } 104 | delete ti.imports 105 | } 106 | validImports(pkg) 107 | if (!exportsConfig) { 108 | const e: Exclude = { 109 | './package.json': './package.json', 110 | } 111 | for (const i of sources) { 112 | if (/^\.\/src\/index\.[^\.]+$/.test(i)) { 113 | e['.'] = i 114 | break 115 | } 116 | } 117 | config.exports = e 118 | tshy.exports = e 119 | exportsConfig = e 120 | } 121 | // return the filled out config, but leave the package.json 122 | // exports as they were, as long as they turned out to be valid. 123 | pkg.tshy = { ...tshy, exports: exportsConfig } 124 | return config 125 | } 126 | 127 | const config: TshyConfig = getConfig(pkg, sources) 128 | export default config 129 | -------------------------------------------------------------------------------- /src/console.ts: -------------------------------------------------------------------------------- 1 | // only print the logs if it fails, or if TSHY_VERBOSE is set 2 | 3 | let verbose = parseInt(process.env.TSHY_VERBOSE || '0') 4 | 5 | const errors: any[][] = [] 6 | export const error = (...a: any[]) => { 7 | if (verbose >= 1) console.error(...a) 8 | else errors.push(a) 9 | } 10 | export const debug = (...a: any[]) => { 11 | if (verbose >= 2) console.error(...a) 12 | else errors.push(a) 13 | } 14 | 15 | // we only print stdout on success anyway 16 | export const log = (...a: any[]) => { 17 | if (verbose >= 1) console.log(...a) 18 | } 19 | 20 | export const print = () => { 21 | for (const a of errors) { 22 | console.error(...a) 23 | } 24 | errors.length = 0 25 | } 26 | -------------------------------------------------------------------------------- /src/dialects.ts: -------------------------------------------------------------------------------- 1 | import config from './config.js' 2 | export default config.dialects || ['esm', 'commonjs'] 3 | -------------------------------------------------------------------------------- /src/exports.ts: -------------------------------------------------------------------------------- 1 | import { relative, resolve } from 'node:path/posix' 2 | import { 3 | ConditionalValue, 4 | ConditionalValueObject, 5 | ExportsSubpaths, 6 | } from 'resolve-import' 7 | import config from './config.js' 8 | import dialects from './dialects.js' 9 | import fail from './fail.js' 10 | import pkg from './package.js' 11 | import type { PolyfillSet } from './polyfills.js' 12 | import polyfills from './polyfills.js' 13 | import { resolveExport } from './resolve-export.js' 14 | import { Package, TshyConfig, TshyExport } from './types.js' 15 | const { esmDialects = [], commonjsDialects = [] } = config 16 | 17 | const liveDev = 18 | config.liveDev && 19 | process.env.npm_command !== 'publish' && 20 | process.env.npm_command !== 'pack' 21 | 22 | const getTargetForDialectCondition = ( 23 | s: string | TshyExport | undefined | null, 24 | dialect: T, 25 | condition: T extends 'esm' ? 'import' 26 | : T extends 'commonjs' ? 'require' 27 | : T, 28 | type: T extends 'esm' ? 'esm' 29 | : T extends 'commonjs' ? 'commonjs' 30 | : 'esm' | 'commonjs', 31 | polyfills: Map = new Map(), 32 | ): string | undefined | null => { 33 | if (s === undefined) return undefined 34 | if (typeof s === 'string') { 35 | // the excluded filename pattern 36 | const xts = type === 'commonjs' ? '.mts' : '.cts' 37 | if (s.endsWith(xts)) return undefined 38 | const pf = dialect === 'commonjs' ? 'cjs' : dialect 39 | const rel = relative( 40 | resolve('./src'), 41 | resolve(polyfills.get(pf)?.map.get(s) ?? s), 42 | ) 43 | const target = 44 | liveDev ? rel : rel.replace(/\.([mc]?)tsx?$/, '.$1js') 45 | return ( 46 | !s || !s.startsWith('./src/') ? s 47 | : dialects.includes(type) ? `./dist/${dialect}/${target}` 48 | : undefined 49 | ) 50 | } 51 | return resolveExport(s, [condition]) 52 | } 53 | 54 | export const getImpTarget = ( 55 | s: string | TshyExport | undefined | null, 56 | polyfills: Map = new Map(), 57 | ) => 58 | getTargetForDialectCondition(s, 'esm', 'import', 'esm', polyfills) 59 | 60 | export const getReqTarget = ( 61 | s: string | TshyExport | undefined | null, 62 | polyfills: Map = new Map(), 63 | ) => 64 | getTargetForDialectCondition( 65 | s, 66 | 'commonjs', 67 | 'require', 68 | 'commonjs', 69 | polyfills, 70 | ) 71 | 72 | const getExports = ( 73 | c: TshyConfig, 74 | pkgType: 'commonjs' | 'module', 75 | ): Record => { 76 | // by this time it always exports, will get the default if missing 77 | /* c8 ignore start */ 78 | if (!c.exports) { 79 | fail('no exports on tshy config (is there code in ./src?)') 80 | return process.exit(1) 81 | } 82 | /* c8 ignore stop */ 83 | const e: Record = {} 84 | for (const [sub, s] of Object.entries(c.exports)) { 85 | // external export, not built by us 86 | if ( 87 | s !== null && 88 | (typeof s !== 'string' || !s.startsWith('./src/')) 89 | ) { 90 | // already been validated, just accept as-is 91 | e[sub] = s as ConditionalValue 92 | continue 93 | } 94 | 95 | /* c8 ignore next - already guarded */ 96 | if (s === null) continue 97 | 98 | const impTarget = getImpTarget(s, polyfills) 99 | const reqTarget = getReqTarget(s, polyfills) 100 | 101 | // should be impossible 102 | /* c8 ignore start */ 103 | if (!impTarget && !reqTarget) continue 104 | /* c8 ignore stop */ 105 | 106 | const exp: ConditionalValueObject = (e[sub] = {}) 107 | if (impTarget) { 108 | for (const d of esmDialects) { 109 | const source = s && (polyfills.get(d)?.map.get(s) ?? s) 110 | 111 | const target = getTargetForDialectCondition( 112 | s, 113 | d, 114 | d, 115 | 'esm', 116 | polyfills, 117 | ) 118 | if (target) { 119 | exp[d] = 120 | liveDev ? 121 | { 122 | ...(pkgType === 'commonjs' ? 123 | getSourceDialects(source, c) 124 | : {}), 125 | default: target, 126 | } 127 | : { 128 | ...(pkgType === 'commonjs' ? 129 | getSourceDialects(source, c) 130 | : {}), 131 | types: target.replace(/\.js$/, '.d.ts'), 132 | default: target, 133 | } 134 | } 135 | } 136 | } 137 | 138 | if (reqTarget) { 139 | for (const d of commonjsDialects) { 140 | const source = s && (polyfills.get(d)?.map.get(s) ?? s) 141 | const target = getTargetForDialectCondition( 142 | s, 143 | d, 144 | d, 145 | 'commonjs', 146 | polyfills, 147 | ) 148 | if (target) { 149 | exp[d] = 150 | liveDev ? 151 | { 152 | ...(pkgType === 'module' ? 153 | getSourceDialects(source, c) 154 | : {}), 155 | default: target, 156 | } 157 | : { 158 | ...(pkgType === 'module' ? 159 | getSourceDialects(source, c) 160 | : {}), 161 | types: target.replace(/\.js$/, '.d.ts'), 162 | default: target, 163 | } 164 | } 165 | } 166 | } 167 | 168 | // put the default import/require after all the other special ones. 169 | if (impTarget) { 170 | exp.import = 171 | liveDev ? 172 | { 173 | ...(pkgType === 'module' ? getSourceDialects(s, c) : {}), 174 | default: impTarget, 175 | } 176 | : { 177 | ...(pkgType === 'module' ? getSourceDialects(s, c) : {}), 178 | types: impTarget.replace(/\.(m?)js$/, '.d.$1ts'), 179 | default: impTarget, 180 | } 181 | } 182 | if (reqTarget) { 183 | exp.require = 184 | liveDev ? 185 | { 186 | ...(pkgType === 'commonjs' ? getSourceDialects(s, c) : {}), 187 | default: reqTarget, 188 | } 189 | : { 190 | ...(pkgType === 'commonjs' ? getSourceDialects(s, c) : {}), 191 | types: reqTarget.replace(/\.(c?)js$/, '.d.$1ts'), 192 | default: reqTarget, 193 | } 194 | } 195 | } 196 | return e 197 | } 198 | 199 | const getSourceDialects = (source: string, c: TshyConfig) => { 200 | const { sourceDialects } = c 201 | if (!sourceDialects) return {} 202 | return Object.fromEntries(sourceDialects.map(s => [s, source])) 203 | } 204 | 205 | export const setMain = ( 206 | c: TshyConfig | undefined, 207 | pkg: Package & { exports: ExportsSubpaths }, 208 | ) => { 209 | pkg.type = pkg.type === 'commonjs' ? 'commonjs' : 'module' 210 | const mod = resolveExport(pkg.exports['.'], ['require']) 211 | const main = c?.main ?? !!mod 212 | if (main) { 213 | if (!mod) { 214 | fail(`could not resolve exports['.'] for tshy.main 'require'`) 215 | return process.exit(1) 216 | } 217 | const types = resolveExport(pkg.exports['.'], [ 218 | 'require', 219 | 'types', 220 | ]) 221 | pkg.main = mod 222 | if (types && types !== mod) pkg.types = types 223 | else delete pkg.types 224 | } else { 225 | if (c && c.main !== false) delete c.main 226 | delete pkg.main 227 | delete pkg.types 228 | } 229 | 230 | // Set the package module to exports["."] 231 | const importMod = resolveExport(pkg.exports['.'], ['import']) 232 | const module = c?.module ?? !!importMod 233 | if (module) { 234 | if (!importMod) { 235 | fail(`could not resolve exports['.'] for tshy.module 'import'`) 236 | return process.exit(1) 237 | } 238 | 239 | pkg.module = importMod 240 | } else { 241 | if (c && c.module !== false) delete c.module 242 | delete pkg.module 243 | } 244 | } 245 | 246 | pkg.exports = getExports(config, pkg.type) 247 | 248 | setMain(config, pkg as Package & { exports: ExportsSubpaths }) 249 | export default pkg.exports 250 | -------------------------------------------------------------------------------- /src/fail.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import * as console from './console.js' 3 | export default (message: string, er?: Error) => { 4 | console.error(chalk.red.bold(message)) 5 | if (er) console.error(er.message) 6 | console.print() 7 | } 8 | -------------------------------------------------------------------------------- /src/if-exist.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, renameSync, unlinkSync } from 'fs' 2 | 3 | const unlink = (f: string) => existsSync(f) && unlinkSync(f) 4 | const rename = (f: string, to: string) => 5 | existsSync(f) && renameSync(f, to) 6 | 7 | export default { 8 | unlink, 9 | rename, 10 | } 11 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import chalk from 'chalk' 4 | import build from './build.js' 5 | import * as debugConsole from './console.js' 6 | import './exports.js' 7 | import pkg from './package.js' 8 | import usage from './usage.js' 9 | import watch from './watch.js' 10 | 11 | const { exports: exp, tshy } = pkg 12 | 13 | const main = async () => { 14 | for (const arg of process.argv.slice(2)) { 15 | switch (arg) { 16 | case '--help': 17 | case '-h': 18 | return usage() 19 | case '--watch': 20 | case '-w': 21 | return watch() 22 | default: 23 | return usage(`Unknown argument: ${arg}`) 24 | } 25 | } 26 | 27 | debugConsole.debug(chalk.yellow.bold('building'), process.cwd()) 28 | debugConsole.debug(chalk.cyan.dim('tshy config'), tshy) 29 | debugConsole.debug(chalk.cyan.dim('exports'), exp) 30 | 31 | await build() 32 | 33 | debugConsole.log(chalk.bold.green('success!')) 34 | } 35 | await main() 36 | -------------------------------------------------------------------------------- /src/package.ts: -------------------------------------------------------------------------------- 1 | // get the package.json data for the cwd 2 | 3 | import { readFileSync } from 'fs' 4 | import { JSONResult, parse, stringify } from 'polite-json' 5 | import fail from './fail.js' 6 | import { Package } from './types.js' 7 | 8 | const isPackage = (pkg: JSONResult): pkg is Package => 9 | !!pkg && typeof pkg === 'object' && !Array.isArray(pkg) 10 | 11 | const readPkg = (): Package & { type: 'commonjs' | 'module' } => { 12 | try { 13 | const res = parse(readFileSync('package.json', 'utf8')) 14 | if (isPackage(res)) { 15 | return Object.assign(res, { 16 | type: res.type === 'commonjs' ? 'commonjs' : 'module' 17 | }) 18 | } 19 | throw new Error( 20 | 'Invalid package.json contents: ' + stringify(res), 21 | ) 22 | } catch (er) { 23 | fail('failed to read package.json', er as Error) 24 | process.exit(1) 25 | } 26 | } 27 | 28 | export default readPkg() 29 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // the modules like -cjs.cts that override a module at .ts 2 | import chalk from 'chalk' 3 | import config from './config.js' 4 | import * as console from './console.js' 5 | import sources from './sources.js' 6 | 7 | const { esmDialects = [], commonjsDialects = [] } = config 8 | 9 | export class PolyfillSet { 10 | type: 'esm' | 'commonjs' 11 | name: string 12 | map = new Map() 13 | constructor(type: 'esm' | 'commonjs', name: string) { 14 | this.type = type 15 | this.name = name 16 | } 17 | addFile(f: string, sources: Set) { 18 | const dotts = this.type === 'commonjs' ? 'cts' : 'mts' 19 | const ending = `-${this.name}.${dotts}` 20 | if (!f.endsWith(ending)) return 21 | const ts = f.substring(0, f.length - ending.length) + '.ts' 22 | const tsx = ts + 'x' 23 | if (sources.has(ts)) this.map.set(f, ts) 24 | else if (sources.has(tsx)) this.map.set(f, tsx) 25 | } 26 | [Symbol.for('nodejs.util.inspect.custom')]() { 27 | return [this.name, this.map] 28 | } 29 | } 30 | 31 | const polyfills = new Map([ 32 | ['cjs', new PolyfillSet('commonjs', 'cjs')], 33 | ]) 34 | for (const d of commonjsDialects) 35 | polyfills.set(d, new PolyfillSet('commonjs', d)) 36 | for (const d of esmDialects) 37 | polyfills.set(d, new PolyfillSet('esm', d)) 38 | 39 | for (const f of sources) { 40 | for (const pf of polyfills.values()) { 41 | pf.addFile(f, sources) 42 | } 43 | } 44 | 45 | // delete any polyfill types that have no entries 46 | for (const [name, pf] of polyfills.entries()) { 47 | if (pf.map.size === 0) polyfills.delete(name) 48 | } 49 | 50 | if (polyfills.size) { 51 | console.debug(chalk.cyan.dim('polyfills detected'), polyfills) 52 | } 53 | 54 | export default polyfills 55 | -------------------------------------------------------------------------------- /src/prevent-verbatim-module-syntax.ts: -------------------------------------------------------------------------------- 1 | // prevent the use of verbatimModuleSyntax: true when 2 | // more than one dialect is in use, since this cannot ever 3 | // be made to work in a hybrid context. 4 | // Note: cannot just use JSON.parse, because ts config files 5 | // are jsonc. 6 | import * as console from './console.js' 7 | import fail from './fail.js' 8 | import readTypescriptConfig from './read-typescript-config.js' 9 | 10 | export default () => { 11 | const config = readTypescriptConfig() 12 | if (config.options.verbatimModuleSyntax) { 13 | fail('verbatimModuleSyntax detected') 14 | console.error( 15 | `verbatimModuleSyntax is incompatible with multi-dialect builds. Either remove 16 | this field from tsconfig.json, or set a single dialect in the "dialects" 17 | field in package.json, for example: 18 | 19 | { 20 | "tshy": { 21 | "dialects": ["esm"] 22 | } 23 | } 24 | 25 | or 26 | 27 | { 28 | "tshy": { 29 | "dialects": ["commonjs"] 30 | } 31 | } 32 | `, 33 | ) 34 | console.print() 35 | process.exit(1) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/read-typescript-config.ts: -------------------------------------------------------------------------------- 1 | // read the actual configuration that tsc is using 2 | // Note: cannot just use JSON.parse, because ts config files 3 | // are jsonc. 4 | import { resolve } from 'path' 5 | import ts from 'typescript' 6 | import config from './config.js' 7 | const { readFile } = ts.sys 8 | 9 | let parsedTsConfig: ts.ParsedCommandLine | undefined = undefined 10 | export default () => { 11 | if (parsedTsConfig) return parsedTsConfig 12 | const configPath = config.project ?? resolve('tsconfig.json') 13 | const readResult = ts.readConfigFile(configPath, readFile) 14 | return (parsedTsConfig = ts.parseJsonConfigFileContent( 15 | readResult.config, 16 | ts.sys, 17 | process.cwd(), 18 | )) 19 | } 20 | -------------------------------------------------------------------------------- /src/resolve-export.ts: -------------------------------------------------------------------------------- 1 | export const resolveExport = ( 2 | exp: any, 3 | conditions: string[], 4 | ): string | undefined | null => { 5 | if (typeof exp === 'string') return exp 6 | if (typeof exp !== 'object') return undefined 7 | if (exp === null) return exp 8 | if (Array.isArray(exp)) { 9 | for (const e of exp) { 10 | const u = resolveExport(e, conditions) 11 | if (u || u === null) return u 12 | } 13 | return undefined 14 | } 15 | const conds = [...conditions, 'node', 'default'] 16 | for (const [c, e] of Object.entries(exp)) { 17 | if (conds.includes(c)) { 18 | const u = resolveExport(e, conditions) 19 | if (u || u === null) return u 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/self-link.ts: -------------------------------------------------------------------------------- 1 | // link the package folder into ./target/node_modules/ 2 | import { readlinkSync, symlinkSync } from 'fs' 3 | import { mkdirpSync } from 'mkdirp' 4 | import { dirname, relative, resolve, sep } from 'path' 5 | import { rimrafSync } from 'rimraf' 6 | import { walkUp } from 'walk-up-path' 7 | import { Package } from './types.js' 8 | 9 | const dirsMade = new Map() 10 | 11 | // if the cwd is in already linked to or living within node_modules, 12 | // then skip the linking, because it's already done. 13 | // This is typically the case in a workspaces setup, and 14 | // creating yet *another* symlink to ourselves in src/node_modules 15 | // will break nx's change detection logic with an ELOOP error. 16 | let inNM: boolean | undefined = undefined 17 | 18 | const linkedAlready = (pkg: Package) => { 19 | if (inNM !== undefined) { 20 | return inNM 21 | } 22 | 23 | const cwd = process.cwd() 24 | const p = `${sep}node_modules${sep}${pkg.name}`.toLowerCase() 25 | if (cwd.toLowerCase().endsWith(p)) { 26 | return (inNM = true) 27 | } 28 | 29 | for (const p of walkUp(cwd)) { 30 | const link = resolve(p, 'node_modules', pkg.name) 31 | try { 32 | const target = resolve(dirname(link), readlinkSync(link)) 33 | if (relative(target, cwd) === '') { 34 | return (inNM = true) 35 | } 36 | } catch {} 37 | } 38 | 39 | return (inNM = false) 40 | } 41 | 42 | export const link = (pkg: Package, where: string) => { 43 | const selfLink = pkg?.tshy?.selfLink 44 | if (!pkg.name || selfLink === false || linkedAlready(pkg)) { 45 | return 46 | } 47 | const dest = resolve(where, 'node_modules', pkg.name) 48 | const dir = dirname(dest) 49 | const src = relative(dir, process.cwd()) 50 | const made = mkdirpSync(dir) 51 | if (made) dirsMade.set(dest, made) 52 | try { 53 | symlinkSync(src, dest) 54 | } catch { 55 | rimrafSync(dest) 56 | let threw = true 57 | try { 58 | symlinkSync(src, dest) 59 | threw = false 60 | } finally { 61 | // best effort if not set explicitly. suppress error with return. 62 | if (threw && selfLink === undefined) return 63 | } 64 | } 65 | } 66 | 67 | export const unlink = (pkg: Package, where: string) => { 68 | if ( 69 | !pkg.name || 70 | pkg?.tshy?.selfLink === false || 71 | linkedAlready(pkg) 72 | ) { 73 | return 74 | } 75 | const dest = resolve(where, 'node_modules', pkg.name) 76 | rimrafSync(dest) 77 | const made = dirsMade.get(dest) 78 | if (made) rimrafSync(made) 79 | } 80 | -------------------------------------------------------------------------------- /src/set-folder-dialect.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { writeFileSync } from 'fs' 3 | import { rimrafSync } from 'rimraf' 4 | import * as console from './console.js' 5 | import getImports from './built-imports.js' 6 | import pkg from './package.js' 7 | import { Dialect } from './types.js' 8 | 9 | const writeDialectPJ = (d: string, mode?: Dialect) => { 10 | if (!mode) { 11 | return rimrafSync(`${d}/package.json`) 12 | } 13 | const v: { type: string; imports?: Record } = { 14 | type: mode === 'commonjs' ? 'commonjs' : 'module', 15 | imports: getImports(pkg), 16 | } 17 | writeFileSync( 18 | `${d}/package.json`, 19 | JSON.stringify(v, null, 2) + '\n', 20 | ) 21 | } 22 | 23 | export default (where: string, mode?: Dialect) => { 24 | if (mode) 25 | console.debug(chalk.cyan.dim('set dialect'), { where, mode }) 26 | writeDialectPJ(where, mode) 27 | } 28 | -------------------------------------------------------------------------------- /src/sources.ts: -------------------------------------------------------------------------------- 1 | // get the list of sources in ./src 2 | 3 | import { readdirSync } from 'fs' 4 | import { join } from 'path/posix' 5 | 6 | const getSources = (dir = 'src'): string[] => { 7 | const sources: string[] = [] 8 | const entries = readdirSync(dir, { withFileTypes: true }) 9 | for (const e of entries) { 10 | const j = `./${join(dir, e.name)}` 11 | if (e.isFile()) sources.push(j) 12 | else if (e.isDirectory()) { 13 | sources.push(...getSources(j)) 14 | } 15 | } 16 | return sources 17 | } 18 | 19 | const sources = new Set(getSources()) 20 | export default sources 21 | -------------------------------------------------------------------------------- /src/tsconfig.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { mkdirpSync } from 'mkdirp' 3 | import { 4 | existsSync, 5 | readdirSync, 6 | unlinkSync, 7 | writeFileSync, 8 | } from 'node:fs' 9 | import { resolve } from 'node:path' 10 | import { join } from 'node:path/posix' 11 | import * as console from './console.js' 12 | 13 | // the commonjs build needs to exclude anything that will be polyfilled 14 | import config from './config.js' 15 | import polyfills from './polyfills.js' 16 | import preventVerbatimModuleSyntax from './prevent-verbatim-module-syntax.js' 17 | import readTypescriptConfig from './read-typescript-config.js' 18 | 19 | const { 20 | dialects = ['esm', 'commonjs'], 21 | esmDialects = [], 22 | commonjsDialects = [], 23 | exclude = [], 24 | } = config 25 | 26 | const relativeExclude = exclude.map( 27 | e => `../${e.replace(/^\.\//, '')}`, 28 | ) 29 | 30 | const recommended: Record = { 31 | compilerOptions: { 32 | declaration: true, 33 | declarationMap: true, 34 | esModuleInterop: true, 35 | forceConsistentCasingInFileNames: true, 36 | inlineSources: true, 37 | jsx: 'react', 38 | module: 'nodenext', 39 | moduleResolution: 'nodenext', 40 | noUncheckedIndexedAccess: true, 41 | resolveJsonModule: true, 42 | skipLibCheck: true, 43 | sourceMap: true, 44 | strict: true, 45 | target: 'es2022', 46 | }, 47 | } 48 | 49 | const build = (): Record => ({ 50 | extends: 51 | config.project === undefined ? 52 | '../tsconfig.json' 53 | : join('..', config.project), 54 | compilerOptions: { 55 | target: 56 | readTypescriptConfig().options.target === undefined ? 57 | 'es2022' 58 | : undefined, 59 | rootDir: '../src', 60 | module: 'nodenext', 61 | moduleResolution: 'nodenext', 62 | }, 63 | }) 64 | 65 | const commonjs = (dialect: string): Record => { 66 | const exclude = [ 67 | ...relativeExclude, 68 | '../src/**/*.mts', 69 | '../src/package.json', 70 | ] 71 | for (const [d, pf] of polyfills) { 72 | if (d === dialect) continue 73 | for (const f of pf.map.keys()) { 74 | exclude.push(`../${join(f)}`) 75 | } 76 | } 77 | return { 78 | extends: './build.json', 79 | include: [ 80 | '../src/**/*.ts', 81 | '../src/**/*.cts', 82 | '../src/**/*.tsx', 83 | '../src/**/*.json', 84 | ], 85 | exclude, 86 | compilerOptions: { 87 | outDir: 88 | '../.tshy-build/' + 89 | (dialect === 'cjs' ? 'commonjs' : dialect), 90 | }, 91 | } 92 | } 93 | 94 | const esm = (dialect: string): Record => { 95 | const exclude: string[] = [ 96 | ...relativeExclude, 97 | '../src/package.json', 98 | ] 99 | for (const [d, pf] of polyfills) { 100 | if (d === dialect) continue 101 | for (const f of pf.map.keys()) { 102 | exclude.push(`../${f.replace(/^\.\//, '')}`) 103 | } 104 | } 105 | return { 106 | extends: './build.json', 107 | include: [ 108 | '../src/**/*.ts', 109 | '../src/**/*.mts', 110 | '../src/**/*.tsx', 111 | '../src/**/*.json', 112 | ], 113 | exclude, 114 | compilerOptions: { 115 | outDir: '../.tshy-build/' + dialect, 116 | }, 117 | } 118 | } 119 | 120 | mkdirpSync('.tshy') 121 | const writeConfig = (name: string, data: Record) => 122 | writeFileSync( 123 | `.tshy/${name}.json`, 124 | JSON.stringify(data, null, 2) + '\n', 125 | ) 126 | 127 | console.debug(chalk.cyan.dim('writing tsconfig files...')) 128 | if (config.project === undefined && !existsSync('tsconfig.json')) { 129 | console.debug('using recommended tsconfig.json') 130 | writeConfig('../tsconfig', recommended) 131 | } else { 132 | if (dialects.length > 1) preventVerbatimModuleSyntax() 133 | console.debug('using existing tsconfig.json') 134 | } 135 | for (const f of readdirSync('.tshy')) { 136 | unlinkSync(resolve('.tshy', f)) 137 | } 138 | writeConfig('build', build()) 139 | if (dialects.includes('commonjs')) { 140 | writeConfig('commonjs', commonjs('cjs')) 141 | for (const d of commonjsDialects) { 142 | writeConfig(d, commonjs(d)) 143 | } 144 | } 145 | if (dialects.includes('esm')) { 146 | writeConfig('esm', esm('esm')) 147 | for (const d of esmDialects) { 148 | writeConfig(d, esm(d)) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ConditionalValue, 3 | ExportsSubpaths, 4 | Imports, 5 | } from 'resolve-import' 6 | 7 | export type TshyConfigMaybeGlobExports = { 8 | exports?: string | string[] | Record 9 | dialects?: Dialect[] 10 | selfLink?: boolean 11 | main?: boolean 12 | module?: boolean 13 | commonjsDialects?: string[] 14 | esmDialects?: string[] 15 | sourceDialects?: string[] 16 | project?: string 17 | exclude?: string[] 18 | liveDev?: boolean 19 | } 20 | 21 | export type TshyConfig = TshyConfigMaybeGlobExports & { 22 | exports?: Record 23 | } 24 | 25 | export type Dialect = 'commonjs' | 'esm' 26 | 27 | export type ExportDetail = { 28 | default: string 29 | [k: string]: string 30 | } 31 | 32 | export type TshyExport = ConditionalValue 33 | 34 | export type Package = { 35 | name: string 36 | version: string 37 | main?: string 38 | types?: string 39 | type?: 'module' | 'commonjs' 40 | bin?: string | Record 41 | exports?: ExportsSubpaths 42 | tshy?: TshyConfigMaybeGlobExports 43 | imports?: Imports 44 | [k: string]: any 45 | } 46 | -------------------------------------------------------------------------------- /src/unbuilt-imports.ts: -------------------------------------------------------------------------------- 1 | // this is the thing that supports top-level package.json imports 2 | // via symlinks, not the tshy.imports which are just config. 3 | import { writeFileSync } from 'fs' 4 | import { symlink } from 'fs/promises' 5 | import { mkdirp } from 'mkdirp' 6 | import { dirname, relative, resolve, sep } from 'path' 7 | import { 8 | getAllConditionalValues, 9 | getUniqueConditionSets, 10 | resolveAllLocalImports, 11 | } from 'resolve-import' 12 | import { rimraf } from 'rimraf' 13 | import { fileURLToPath } from 'url' 14 | import * as console from './console.js' 15 | import { Package } from './types.js' 16 | 17 | const dirsMade = new Set() 18 | 19 | // write out the steps to the save file script 20 | export const save = (f: string): boolean => { 21 | const links = [...saveSet.entries()] 22 | if (!links.length) return false 23 | const dirs = new Set(links.map(([dest]) => dirname(dest))) 24 | console.debug('save import linker', f) 25 | writeFileSync( 26 | f, 27 | `import { mkdirSync } from 'node:fs' 28 | import { symlink } from 'node:fs/promises' 29 | const dirs = ${JSON.stringify([...dirs])} 30 | const links = [ 31 | ${links.map(l => ` ${JSON.stringify(l)},\n`).join('')}] 32 | const e = (er) => { if (er.code !== 'EEXIST') throw er } 33 | for (const d of dirs) mkdirSync(d, { recursive: true }) 34 | Promise.all(links.map(([dest, src]) => symlink(src, dest).catch(e))) 35 | `, 36 | ) 37 | return true 38 | } 39 | 40 | let targets: undefined | string[] = undefined 41 | // Get the targets that will have to be linked, because they're not 42 | // a target in ./src 43 | const getTargets = async (imports: Record) => { 44 | const conds = getAllConditionalValues(imports).filter( 45 | c => !c.startsWith('./src/'), 46 | ) 47 | if (!conds.some(c => c.includes('*'))) { 48 | // fast path 49 | return (targets = conds.filter(c => c.startsWith('./'))) 50 | } 51 | const sets = getUniqueConditionSets(imports) 52 | const t = new Set() 53 | const pj = resolve('package.json') 54 | for (const conditions of sets) { 55 | const imps = await resolveAllLocalImports(pj, { conditions }) 56 | for (const url of Object.values(imps)) { 57 | // node builtin 58 | if (typeof url === 'string') continue 59 | const p = fileURLToPath(url) 60 | const rel = relative(process.cwd(), p) 61 | // if it's empty, a dep in node_modules, or a built module, skip 62 | if ( 63 | !rel || 64 | rel.startsWith('..' + sep) || 65 | rel.startsWith('src' + sep) || 66 | rel.startsWith('node_modules' + sep) 67 | ) 68 | continue 69 | t.add('./' + rel.replace(/\\/g, '/')) 70 | } 71 | } 72 | return (targets = [...t]) 73 | } 74 | 75 | const saveSet = new Map() 76 | 77 | // create symlinks for the package imports in the target dir 78 | export const link = async ( 79 | pkg: Package, 80 | dir: string, 81 | save = false, 82 | ) => { 83 | const { imports } = pkg 84 | if (!imports) return 85 | if (!targets) targets = await getTargets(imports) 86 | if (!targets.length) return 87 | console.debug(`link import targets in ${dir}`, targets) 88 | const rel = relative(resolve(dir), process.cwd()) 89 | const lps: Promise[] = [] 90 | for (const t of targets) { 91 | const l = t.replace(/^\.\//, '') 92 | const df = dirname(l) 93 | const dfrel = 94 | df === '.' ? '' : ( 95 | df 96 | .split('/') 97 | .map(() => '../') 98 | .join('') 99 | ) 100 | const dest = dir + '/' + l 101 | const src = rel + '/' + dfrel + l 102 | if (save) saveSet.set(dest, src) 103 | lps.push( 104 | mkdirp(dirname(dest)) 105 | .then(d => { 106 | // if we aren't saving, then this is a transient link 107 | // save the dirs created so that we can clean them up 108 | if (!save && d) dirsMade.add(d) 109 | return rimraf(dest) 110 | }) 111 | .then(() => symlink(src, dest)), 112 | ) 113 | } 114 | await Promise.all(lps) 115 | } 116 | 117 | // remove symlinks created for package imports in the target dir 118 | export const unlink = async (pkg: Package, dir: string) => { 119 | const { imports } = pkg 120 | if (!imports) return 121 | // will always have targets by this point 122 | /* c8 ignore start */ 123 | if (!targets) targets = await getTargets(imports) 124 | /* c8 ignore stop */ 125 | console.debug(`unlink import targets in ${dir}`, targets) 126 | const lps: Promise[] = [] 127 | for (const t of targets) { 128 | const dest = resolve(dir, t) 129 | lps.push(rimraf(dest)) 130 | } 131 | for (const d of dirsMade) lps.push(rimraf(d)) 132 | await Promise.all(lps) 133 | } 134 | -------------------------------------------------------------------------------- /src/usage.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import fail from './fail.js' 3 | 4 | export default (err?: string) => { 5 | const url = 'https://github.com/isaacs/tshy' 6 | const link = 7 | chalk.level > 0 ? `\x1b]8;;${url}\x1b\\${url}\x1b]8;;\x1b\\` : url 8 | console[err ? 'error' : 'log'](`Usage: tshy [--help] 9 | --help -h Print this message and exit. 10 | --watch -w Watch files in ./src and build when they change. 11 | 12 | Default behavior: build project according to tshy config in package.json 13 | 14 | See the docs for more information: ${link}`) 15 | if (err) fail(err) 16 | process.exit(err ? 1 : 0) 17 | } 18 | -------------------------------------------------------------------------------- /src/valid-dialects.ts: -------------------------------------------------------------------------------- 1 | import fail from './fail.js' 2 | import { Dialect, TshyConfig } from './types.js' 3 | 4 | export const isDialect = (d: any): d is Dialect => 5 | d === 'commonjs' || d === 'esm' 6 | 7 | export default ( 8 | d: any, 9 | ): d is Exclude => { 10 | if ( 11 | !!d && 12 | Array.isArray(d) && 13 | d.length && 14 | !d.some(d => !isDialect(d)) 15 | ) { 16 | return true 17 | } 18 | 19 | fail( 20 | `tshy.dialects must be an array including "esm" and/or "commonjs", ` + 21 | `got: ${JSON.stringify(d)}`, 22 | ) 23 | return process.exit(1) 24 | } 25 | -------------------------------------------------------------------------------- /src/valid-exclude.ts: -------------------------------------------------------------------------------- 1 | import fail from './fail.js' 2 | import { TshyConfig } from './types.js' 3 | export default ( 4 | d: any, 5 | ): d is Exclude => { 6 | if ( 7 | !!d && 8 | Array.isArray(d) && 9 | d.length && 10 | !d.some(d => typeof d !== 'string') 11 | ) { 12 | return true 13 | } 14 | fail( 15 | `tshy.exclude must be an array of string glob patterns if defined, ` + 16 | `got: ${JSON.stringify(d)}`, 17 | ) 18 | return process.exit(1) 19 | } 20 | -------------------------------------------------------------------------------- /src/valid-exports.ts: -------------------------------------------------------------------------------- 1 | import addDot from './add-dot.js' 2 | import fail from './fail.js' 3 | import { TshyConfig } from './types.js' 4 | import validExternalExport from './valid-external-export.js' 5 | 6 | export default ( 7 | e: any, 8 | ): e is Exclude => { 9 | if (!e || typeof e !== 'object' || Array.isArray(e)) return false 10 | for (const [sub, exp] of Object.entries(e)) { 11 | if (sub !== '.' && !sub.startsWith('./')) { 12 | fail( 13 | `tshy.exports key must be "." or start with "./", got: ${sub}`, 14 | ) 15 | return process.exit(1) 16 | } 17 | 18 | // just a module. either a built export, or a simple unbuilt export 19 | if (typeof exp === 'string') { 20 | e[sub] = addDot(exp) 21 | continue 22 | } 23 | 24 | if (typeof exp !== 'object') { 25 | fail( 26 | `tshy.exports ${sub} value must be valid package.json exports ` + 27 | `value, got: ${JSON.stringify(exp)}`, 28 | ) 29 | return process.exit(1) 30 | } 31 | 32 | // can be any valid external export, but the resolution of 33 | // import and require must NOT be in ./src 34 | if (!validExternalExport(exp)) { 35 | fail( 36 | `tshy.exports ${sub} unbuilt exports must not be in ./src, ` + 37 | `and exports in src must be string values. ` + 38 | `got: ${JSON.stringify(exp)}`, 39 | ) 40 | return process.exit(1) 41 | } 42 | 43 | e[sub] = exp 44 | } 45 | return true 46 | } 47 | -------------------------------------------------------------------------------- /src/valid-external-export.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path/posix' 2 | import type { ConditionalValueObject } from 'resolve-import' 3 | import { resolveExport } from './resolve-export.js' 4 | 5 | export default (exp: any): exp is ConditionalValueObject => { 6 | const i = resolveExport(exp, ['import']) 7 | const r = resolveExport(exp, ['require']) 8 | if (i && join(i).startsWith('src/')) return false 9 | if (r && join(r).startsWith('src/')) return false 10 | return true 11 | } 12 | -------------------------------------------------------------------------------- /src/valid-extra-dialects.ts: -------------------------------------------------------------------------------- 1 | // validate esmDialects and commonjsDialects 2 | import fail from './fail.js' 3 | import { TshyConfig } from './types.js' 4 | 5 | const arrayOverlap = ( 6 | a: string[] | undefined, 7 | b: string[] | undefined, 8 | ): string | false => { 9 | if (!a || !b) return false 10 | for (const av of a) { 11 | if (b.includes(av)) return av 12 | } 13 | return false 14 | } 15 | 16 | const validExtraDialectSet = ( 17 | e: string[], 18 | which: 'commonjs' | 'esm' | 'source', 19 | ) => { 20 | for (const d of e) { 21 | if (typeof d !== 'string') { 22 | fail(`${which} must be an array of strings, got: ${d}`) 23 | return process.exit(1) 24 | } 25 | if ( 26 | d === 'commonjs' || 27 | d === 'cjs' || 28 | d === 'esm' || 29 | d === 'require' || 30 | d === 'import' || 31 | d === 'node' || 32 | d === 'source' || 33 | d === 'default' 34 | ) { 35 | fail( 36 | `tshy.${which}Dialects must not contain ${JSON.stringify(d)}`, 37 | ) 38 | return process.exit(1) 39 | } 40 | } 41 | return true 42 | } 43 | 44 | export default ({ 45 | commonjsDialects, 46 | esmDialects, 47 | sourceDialects, 48 | }: TshyConfig) => { 49 | if ( 50 | commonjsDialects === undefined && 51 | esmDialects === undefined && 52 | sourceDialects === undefined 53 | ) { 54 | return true 55 | } 56 | if ( 57 | commonjsDialects && 58 | !validExtraDialectSet(commonjsDialects, 'commonjs') 59 | ) { 60 | return false 61 | } 62 | if (esmDialects && !validExtraDialectSet(esmDialects, 'esm')) { 63 | return false 64 | } 65 | if ( 66 | sourceDialects && 67 | !validExtraDialectSet(sourceDialects, 'source') 68 | ) { 69 | return false 70 | } 71 | for (const [aname, bname, a, b] of [ 72 | [ 73 | 'commonjsDialects', 74 | 'esmDialects', 75 | commonjsDialects, 76 | esmDialects, 77 | ], 78 | [ 79 | 'commonjsDialects', 80 | 'sourceDialects', 81 | commonjsDialects, 82 | sourceDialects, 83 | ], 84 | ['esmDialects', 'sourceDialects', esmDialects, sourceDialects], 85 | ] as const) { 86 | const overlap = arrayOverlap(a, b) 87 | if (!overlap) continue 88 | fail( 89 | `${aname} and ${bname} must be unique, found ${overlap} in both lists`, 90 | ) 91 | return process.exit(1) 92 | } 93 | return true 94 | } 95 | -------------------------------------------------------------------------------- /src/valid-imports.ts: -------------------------------------------------------------------------------- 1 | import fail from './fail.js' 2 | import { Package } from './types.js' 3 | import validExternalExport from './valid-external-export.js' 4 | 5 | // validate the package.imports field 6 | export default (pkg: Package) => { 7 | const { imports } = pkg 8 | if (imports === undefined) return true 9 | if (Array.isArray(imports) || typeof imports !== 'object') { 10 | fail( 11 | 'invalid imports object, must be Record, ' + 12 | `got: ${JSON.stringify(imports)}`, 13 | ) 14 | return process.exit(1) 15 | } 16 | 17 | for (const [i, v] of Object.entries(imports)) { 18 | if (!i.startsWith('#') || i === '#' || i.startsWith('#/')) { 19 | fail('invalid imports module specifier: ' + i) 20 | return process.exit(1) 21 | } 22 | if (typeof v === 'string') continue 23 | if (!validExternalExport(v)) { 24 | fail( 25 | `unbuilt package.imports ${i} must not be in ./src, ` + 26 | 'and imports in ./src must be string values. got: ' + 27 | JSON.stringify(v), 28 | ) 29 | return process.exit(1) 30 | } 31 | } 32 | return true 33 | } 34 | -------------------------------------------------------------------------------- /src/valid-project.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { readFileSync } from 'node:fs' 3 | import fail from './fail.js' 4 | import { TshyConfig } from './types.js' 5 | 6 | export default (p: any): p is TshyConfig['project'] => { 7 | if (typeof p === 'string') { 8 | try { 9 | readFileSync(resolve(p), 'utf8') 10 | return true 11 | } catch (_) {} 12 | } 13 | 14 | fail( 15 | `tshy.project must point to a tsconfig file on disk, ` + 16 | `got: ${JSON.stringify(p)}`, 17 | ) 18 | return process.exit(1) 19 | } 20 | -------------------------------------------------------------------------------- /src/watch.ts: -------------------------------------------------------------------------------- 1 | // any time the root package.json or any typescript files in src 2 | // are changed/added/removed, run the build 3 | import chalk from 'chalk' 4 | import { spawn } from 'child_process' 5 | import { watch, WatchOptions } from 'chokidar' 6 | import { readFileSync } from 'fs' 7 | import { resolve, sep } from 'path' 8 | import { fileURLToPath } from 'url' 9 | import * as tshyConsole from './console.js' 10 | 11 | const pjData = (): string => { 12 | try { 13 | return JSON.stringify( 14 | JSON.parse(readFileSync('./package.json', 'utf8')), 15 | ) 16 | /* c8 ignore start */ 17 | } catch { 18 | return 'null' 19 | } 20 | /* c8 ignore stop */ 21 | } 22 | let lastPJData: string = 'null' 23 | 24 | export const options: WatchOptions = { 25 | persistent: true, 26 | ignoreInitial: true, 27 | ignorePermissionErrors: true, 28 | ignored: path => { 29 | const r = resolve(path) 30 | if (r === srcPJ) return true 31 | if (r === srcNM) return true 32 | if (r.startsWith(srcNM + sep)) return true 33 | return false 34 | }, 35 | } 36 | 37 | export const srcPJ = resolve('./src/package.json') 38 | export const srcNM = resolve('./src/node_modules') 39 | export const src = resolve('./src') 40 | export const rootPJ = resolve('./package.json') 41 | export const targets = [src, rootPJ] 42 | export const bin = fileURLToPath( 43 | new URL('./index.js', import.meta.url), 44 | ) 45 | 46 | export default () => { 47 | let building = false 48 | let needRebuild = false 49 | const watcher = watch(targets, options) 50 | const build = () => { 51 | building = true 52 | needRebuild = false 53 | const child = spawn(process.execPath, [bin], { stdio: 'inherit' }) 54 | child.on('close', (code, signal) => { 55 | if (code || signal) tshyConsole.error({ code, signal }) 56 | else console.log(chalk.green('build success'), { code, signal }) 57 | if (needRebuild) build() 58 | else building = false 59 | }) 60 | } 61 | watcher.on('all', (ev, path) => { 62 | const r = resolve(path) 63 | if (r === srcPJ) return 64 | if (r === rootPJ) { 65 | // check if the data actually changed 66 | const newData = pjData() 67 | /* c8 ignore next */ 68 | if (newData === lastPJData) return 69 | lastPJData = newData 70 | } 71 | if (building) { 72 | if (r !== rootPJ) needRebuild = true 73 | return 74 | } 75 | tshyConsole.debug(chalk.cyan.dim(ev), path) 76 | build() 77 | }) 78 | build() 79 | } 80 | -------------------------------------------------------------------------------- /src/which-tsc.ts: -------------------------------------------------------------------------------- 1 | // find the location of the tsc binary 2 | // This is necessary because pnpm install trees don't expose binaries 3 | // of meta-deps, and it's nicer to not require that the tshy user has 4 | // a dep on typescript directly if they don't need it otherwise. 5 | import { resolveImport } from 'resolve-import' 6 | import { fileURLToPath } from 'url' 7 | 8 | export default fileURLToPath( 9 | new URL( 10 | '../bin/tsc', 11 | await resolveImport('typescript', import.meta.url), 12 | ), 13 | ) 14 | -------------------------------------------------------------------------------- /src/write-package.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'fs' 2 | import { stringify } from 'polite-json' 3 | import pkg from './package.js' 4 | 5 | export default () => writeFileSync('package.json', stringify(pkg)) 6 | -------------------------------------------------------------------------------- /tap-snapshots/test/add-paths-to-tsconfig.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/add-paths-to-tsconfig.ts > TAP > fail to parse > failure message 1`] = ` 9 | could not parse tsconfig.json to add "paths" 10 | ` 11 | -------------------------------------------------------------------------------- /tap-snapshots/test/build-commonjs.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/build-commonjs.ts > TAP > basic commonjs build > must match snapshot 1`] = ` 9 | Array [ 10 | "blah-cjs.cjs.map", 11 | "blah-cjs.d.cts.map", 12 | "blah.d.ts", 13 | "blah.js", 14 | "index.d.ts", 15 | "index.d.ts.map", 16 | "index.js", 17 | "index.js.map", 18 | "package.json", 19 | ] 20 | ` 21 | 22 | exports[`test/build-commonjs.ts > TAP > basic commonjs build > must match snapshot 2`] = ` 23 | Array [ 24 | Array [ 25 | "{NODE}", 26 | Array [ 27 | "{CWD}/node_modules/typescript/bin/tsc", 28 | "-p", 29 | ".tshy/commonjs.json", 30 | ], 31 | Object { 32 | "stdio": "inherit", 33 | }, 34 | ], 35 | Array [ 36 | "{NODE}", 37 | Array [ 38 | "{CWD}/node_modules/typescript/bin/tsc", 39 | "-p", 40 | ".tshy/blah.json", 41 | ], 42 | Object { 43 | "stdio": "inherit", 44 | }, 45 | ], 46 | Array [ 47 | "{NODE}", 48 | Array [ 49 | "{CWD}/node_modules/typescript/bin/tsc", 50 | "-p", 51 | ".tshy/no-overrides.json", 52 | ], 53 | Object { 54 | "stdio": "inherit", 55 | }, 56 | ], 57 | ] 58 | ` 59 | 60 | exports[`test/build-commonjs.ts > TAP > build failure > must match snapshot 1`] = ` 61 | Array [ 62 | "blah-cjs.cjs", 63 | "blah-cjs.cjs.map", 64 | "blah-cjs.d.cts", 65 | "blah-cjs.d.cts.map", 66 | "blah.d.ts", 67 | "blah.d.ts.map", 68 | "blah.js", 69 | "blah.js.map", 70 | "index.d.ts", 71 | "index.d.ts.map", 72 | "index.js", 73 | "index.js.map", 74 | ] 75 | ` 76 | 77 | exports[`test/build-commonjs.ts > TAP > build failure > must match snapshot 2`] = ` 78 | Array [ 79 | Array [ 80 | "{NODE}", 81 | Array [ 82 | "{CWD}/node_modules/typescript/bin/tsc", 83 | "-p", 84 | ".tshy/commonjs.json", 85 | ], 86 | Object { 87 | "stdio": "inherit", 88 | }, 89 | ], 90 | Array [ 91 | "{NODE}", 92 | Array [ 93 | "{CWD}/node_modules/typescript/bin/tsc", 94 | "-p", 95 | ".tshy/blah.json", 96 | ], 97 | Object { 98 | "stdio": "inherit", 99 | }, 100 | ], 101 | Array [ 102 | "{NODE}", 103 | Array [ 104 | "{CWD}/node_modules/typescript/bin/tsc", 105 | "-p", 106 | ".tshy/no-overrides.json", 107 | ], 108 | Object { 109 | "stdio": "inherit", 110 | }, 111 | ], 112 | Array [ 113 | "{NODE}", 114 | Array [ 115 | "{CWD}/node_modules/typescript/bin/tsc", 116 | "-p", 117 | ".tshy/commonjs.json", 118 | ], 119 | Object { 120 | "stdio": "inherit", 121 | }, 122 | ], 123 | ] 124 | ` 125 | -------------------------------------------------------------------------------- /tap-snapshots/test/build-esm.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/build-esm.ts > TAP > basic esm build > must match snapshot 1`] = ` 9 | Array [ 10 | "blah.d.ts", 11 | "blah.d.ts.map", 12 | "blah.js", 13 | "blah.js.map", 14 | "index.d.ts", 15 | "index.d.ts.map", 16 | "index.js", 17 | "index.js.map", 18 | "package.json", 19 | ] 20 | ` 21 | 22 | exports[`test/build-esm.ts > TAP > basic esm build > must match snapshot 2`] = ` 23 | Array [ 24 | Array [ 25 | "{NODE}", 26 | Array [ 27 | "{CWD}/node_modules/typescript/bin/tsc", 28 | "-p", 29 | ".tshy/esm.json", 30 | ], 31 | Object { 32 | "stdio": "inherit", 33 | }, 34 | ], 35 | Array [ 36 | "{NODE}", 37 | Array [ 38 | "{CWD}/node_modules/typescript/bin/tsc", 39 | "-p", 40 | ".tshy/blah.json", 41 | ], 42 | Object { 43 | "stdio": "inherit", 44 | }, 45 | ], 46 | Array [ 47 | "{NODE}", 48 | Array [ 49 | "{CWD}/node_modules/typescript/bin/tsc", 50 | "-p", 51 | ".tshy/no-overrides.json", 52 | ], 53 | Object { 54 | "stdio": "inherit", 55 | }, 56 | ], 57 | ] 58 | ` 59 | 60 | exports[`test/build-esm.ts > TAP > build failure > must match snapshot 1`] = ` 61 | Array [ 62 | "blah.d.ts", 63 | "blah.d.ts.map", 64 | "blah.js", 65 | "blah.js.map", 66 | "index.d.ts", 67 | "index.d.ts.map", 68 | "index.js", 69 | "index.js.map", 70 | ] 71 | ` 72 | 73 | exports[`test/build-esm.ts > TAP > build failure > must match snapshot 2`] = ` 74 | Array [ 75 | Array [ 76 | "{NODE}", 77 | Array [ 78 | "{CWD}/node_modules/typescript/bin/tsc", 79 | "-p", 80 | ".tshy/esm.json", 81 | ], 82 | Object { 83 | "stdio": "inherit", 84 | }, 85 | ], 86 | Array [ 87 | "{NODE}", 88 | Array [ 89 | "{CWD}/node_modules/typescript/bin/tsc", 90 | "-p", 91 | ".tshy/blah.json", 92 | ], 93 | Object { 94 | "stdio": "inherit", 95 | }, 96 | ], 97 | Array [ 98 | "{NODE}", 99 | Array [ 100 | "{CWD}/node_modules/typescript/bin/tsc", 101 | "-p", 102 | ".tshy/no-overrides.json", 103 | ], 104 | Object { 105 | "stdio": "inherit", 106 | }, 107 | ], 108 | Array [ 109 | "{NODE}", 110 | Array [ 111 | "{CWD}/node_modules/typescript/bin/tsc", 112 | "-p", 113 | ".tshy/esm.json", 114 | ], 115 | Object { 116 | "stdio": "inherit", 117 | }, 118 | ], 119 | ] 120 | ` 121 | -------------------------------------------------------------------------------- /tap-snapshots/test/build-fail.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/build-fail.ts > TAP > must match snapshot 1`] = ` 9 | Array [ 10 | Array [ 11 | "setFolderDialect", 12 | Array [ 13 | "src", 14 | ], 15 | ], 16 | Array [ 17 | "unlinkImports", 18 | Array [ 19 | Object { 20 | "name": "package", 21 | }, 22 | "src", 23 | ], 24 | ], 25 | Array [ 26 | "unlinkSelfDep", 27 | Array [ 28 | Object { 29 | "name": "package", 30 | }, 31 | "src", 32 | ], 33 | ], 34 | Array [ 35 | "fail", 36 | Array [ 37 | "build failed", 38 | ], 39 | ], 40 | Array [ 41 | "console.error", 42 | Array [ 43 | Object { 44 | "code": 0, 45 | "signal": "testing", 46 | }, 47 | ], 48 | ], 49 | Array [ 50 | "process.exit", 51 | Array [ 52 | 1, 53 | ], 54 | ], 55 | ] 56 | ` 57 | -------------------------------------------------------------------------------- /tap-snapshots/test/build-live-commonjs.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/build-live-commonjs.ts > TAP > commonjs live dev build > must match snapshot 1`] = ` 9 | Array [ 10 | "blah-blah.cts", 11 | "blah-cjs.cts", 12 | "blah.ts", 13 | "index.ts", 14 | "package.json", 15 | ] 16 | ` 17 | -------------------------------------------------------------------------------- /tap-snapshots/test/build-live-esm.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/build-live-esm.ts > TAP > esm live dev build > must match snapshot 1`] = ` 9 | Array [ 10 | "blah-blah.mts", 11 | "blah-cjs.cts", 12 | "blah.ts", 13 | "index.ts", 14 | "package.json", 15 | ] 16 | ` 17 | -------------------------------------------------------------------------------- /tap-snapshots/test/config.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/config.ts > TAP > basic parsing cases > {"config":{"dialects":"yolo"},"sources":[],"ok":false} > must match snapshot 1`] = ` 9 | tshy.dialects must be an array including "esm" and/or "commonjs", got: "yolo" 10 | ` 11 | 12 | exports[`test/config.ts > TAP > basic parsing cases > {"config":{"exports":{"./blah":{"require":"./src/notallowed"}}},"sources":[],"ok":false} > must match snapshot 1`] = ` 13 | tshy.exports ./blah unbuilt exports must not be in ./src, and exports in src must be string values. got: {"require":"./src/notallowed"} 14 | ` 15 | 16 | exports[`test/config.ts > TAP > basic parsing cases > {"config":{"imports":"blah"},"sources":[],"ok":false} > must match snapshot 1`] = ` 17 | invalid imports module specifier: 0 18 | ` 19 | 20 | exports[`test/config.ts > TAP > basic parsing cases > {"config":{"imports":["blah"]},"sources":[],"ok":false} > must match snapshot 1`] = ` 21 | invalid imports module specifier: 0 22 | ` 23 | 24 | exports[`test/config.ts > TAP > basic parsing cases > {"config":{"main":"blah"},"sources":[],"ok":false} > must match snapshot 1`] = ` 25 | tshy.main must be a boolean value if specified, got: blah 26 | ` 27 | 28 | exports[`test/config.ts > TAP > basic parsing cases > {"config":{"project":"thisFileDoesNotExist.json"},"sources":[],"ok":false} > must match snapshot 1`] = ` 29 | tshy.project must point to a tsconfig file on disk, got: "thisFileDoesNotExist.json" 30 | ` 31 | -------------------------------------------------------------------------------- /tap-snapshots/test/fail.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/fail.ts > TAP > must match snapshot 1`] = ` 9 | Array [ 10 | Array [ 11 | "console.error", 12 | Array [ 13 | "\\u001b[31m\\u001b[1mno error\\u001b[22m\\u001b[39m", 14 | ], 15 | ], 16 | Array [ 17 | "console.print", 18 | Array [], 19 | ], 20 | Array [ 21 | "console.error", 22 | Array [ 23 | "\\u001b[31m\\u001b[1mwith error\\u001b[22m\\u001b[39m", 24 | ], 25 | ], 26 | Array [ 27 | "console.error", 28 | Array [ 29 | "error message", 30 | ], 31 | ], 32 | Array [ 33 | "console.print", 34 | Array [], 35 | ], 36 | ] 37 | ` 38 | -------------------------------------------------------------------------------- /tap-snapshots/test/polyfills.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/polyfills.ts > TAP > must match snapshot 1`] = ` 9 | Map(3) { 10 | 'cjs' => [ 11 | 'cjs', 12 | Map(2) { 13 | './src/foo-cjs.cts' => './src/foo.ts', 14 | './src/jsx-cjs.cts' => './src/jsx.tsx' 15 | } 16 | ], 17 | 'blah' => [ 'blah', Map(1) { './src/foo-blah.cts' => './src/foo.ts' } ], 18 | 'deno' => [ 'deno', Map(1) { './src/foo-deno.mts' => './src/foo.ts' } ] 19 | } 20 | ` 21 | -------------------------------------------------------------------------------- /tap-snapshots/test/prevent-verbatim-module-syntax.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/prevent-verbatim-module-syntax.ts > TAP > is set, many worries > must match snapshot 1`] = ` 9 | verbatimModuleSyntax detected 10 | verbatimModuleSyntax is incompatible with multi-dialect builds. Either remove 11 | this field from tsconfig.json, or set a single dialect in the "dialects" 12 | field in package.json, for example: 13 | 14 | { 15 | "tshy": { 16 | "dialects": ["esm"] 17 | } 18 | } 19 | 20 | or 21 | 22 | { 23 | "tshy": { 24 | "dialects": ["commonjs"] 25 | } 26 | } 27 | 28 | ` 29 | -------------------------------------------------------------------------------- /tap-snapshots/test/self-dep.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/self-dep.ts > TAP > link, but no dirs made > mkdirps 1`] = ` 9 | Array [ 10 | Array [ 11 | "{CWD}/some/path/node_modules", 12 | ], 13 | ] 14 | ` 15 | 16 | exports[`test/self-dep.ts > TAP > link, but no dirs made > rimrafs 1`] = ` 17 | Array [ 18 | Array [ 19 | "{CWD}/some/path/node_modules/name", 20 | ], 21 | ] 22 | ` 23 | 24 | exports[`test/self-dep.ts > TAP > link, but no dirs made > symlinks 1`] = ` 25 | Array [ 26 | Array [ 27 | "../../..", 28 | "{CWD}/some/path/node_modules/name", 29 | ], 30 | ] 31 | ` 32 | 33 | exports[`test/self-dep.ts > TAP > made dir, clean up > mkdirps 1`] = ` 34 | Array [ 35 | Array [ 36 | "{CWD}/some/path/node_modules", 37 | ], 38 | ] 39 | ` 40 | 41 | exports[`test/self-dep.ts > TAP > made dir, clean up > rimrafs 1`] = ` 42 | Array [ 43 | Array [ 44 | "{CWD}/some/path/node_modules/name", 45 | ], 46 | Array [ 47 | "some", 48 | ], 49 | ] 50 | ` 51 | 52 | exports[`test/self-dep.ts > TAP > made dir, clean up > symlinks 1`] = ` 53 | Array [ 54 | Array [ 55 | "../../..", 56 | "{CWD}/some/path/node_modules/name", 57 | ], 58 | ] 59 | ` 60 | 61 | exports[`test/self-dep.ts > TAP > throw both times, but accept if best-effort > mkdirps 1`] = ` 62 | Array [ 63 | Array [ 64 | "{CWD}/some/path/node_modules", 65 | ], 66 | ] 67 | ` 68 | 69 | exports[`test/self-dep.ts > TAP > throw both times, but accept if best-effort > rimrafs 1`] = ` 70 | Array [ 71 | Array [ 72 | "{CWD}/some/path/node_modules/name", 73 | ], 74 | ] 75 | ` 76 | 77 | exports[`test/self-dep.ts > TAP > throw both times, but accept if best-effort > symlinks 1`] = ` 78 | Array [ 79 | Array [ 80 | "../../..", 81 | "{CWD}/some/path/node_modules/name", 82 | ], 83 | Array [ 84 | "../../..", 85 | "{CWD}/some/path/node_modules/name", 86 | ], 87 | ] 88 | ` 89 | 90 | exports[`test/self-dep.ts > TAP > throw both times, but self-link is required > mkdirps 1`] = ` 91 | Array [ 92 | Array [ 93 | "{CWD}/some/path/node_modules", 94 | ], 95 | ] 96 | ` 97 | 98 | exports[`test/self-dep.ts > TAP > throw both times, but self-link is required > rimrafs 1`] = ` 99 | Array [ 100 | Array [ 101 | "{CWD}/some/path/node_modules/name", 102 | ], 103 | ] 104 | ` 105 | 106 | exports[`test/self-dep.ts > TAP > throw both times, but self-link is required > symlinks 1`] = ` 107 | Array [ 108 | Array [ 109 | "../../..", 110 | "{CWD}/some/path/node_modules/name", 111 | ], 112 | Array [ 113 | "../../..", 114 | "{CWD}/some/path/node_modules/name", 115 | ], 116 | ] 117 | ` 118 | 119 | exports[`test/self-dep.ts > TAP > try one more time if it fails > mkdirps 1`] = ` 120 | Array [ 121 | Array [ 122 | "{CWD}/some/path/node_modules", 123 | ], 124 | ] 125 | ` 126 | 127 | exports[`test/self-dep.ts > TAP > try one more time if it fails > rimrafs 1`] = ` 128 | Array [ 129 | Array [ 130 | "{CWD}/some/path/node_modules/name", 131 | ], 132 | ] 133 | ` 134 | 135 | exports[`test/self-dep.ts > TAP > try one more time if it fails > symlinks 1`] = ` 136 | Array [ 137 | Array [ 138 | "../../..", 139 | "{CWD}/some/path/node_modules/name", 140 | ], 141 | Array [ 142 | "../../..", 143 | "{CWD}/some/path/node_modules/name", 144 | ], 145 | ] 146 | ` 147 | -------------------------------------------------------------------------------- /tap-snapshots/test/tsconfig.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/tsconfig.ts > TAP > with custom project tsconfig name > .tshy/build.json 1`] = ` 9 | Object { 10 | "compilerOptions": Object { 11 | "module": "nodenext", 12 | "moduleResolution": "nodenext", 13 | "rootDir": "../src", 14 | "target": "es2022", 15 | }, 16 | "extends": "../custom.json", 17 | } 18 | ` 19 | 20 | exports[`test/tsconfig.ts > TAP > with custom project tsconfig name > .tshy/commonjs.json 1`] = ` 21 | Object { 22 | "compilerOptions": Object { 23 | "outDir": "../.tshy-build/commonjs", 24 | }, 25 | "exclude": Array [ 26 | "../src/**/*.mts", 27 | "../src/package.json", 28 | "../src/index-deno.mts", 29 | ], 30 | "extends": "./build.json", 31 | "include": Array [ 32 | "../src/**/*.ts", 33 | "../src/**/*.cts", 34 | "../src/**/*.tsx", 35 | "../src/**/*.json", 36 | ], 37 | } 38 | ` 39 | 40 | exports[`test/tsconfig.ts > TAP > with custom project tsconfig name > .tshy/deno.json 1`] = ` 41 | Object { 42 | "compilerOptions": Object { 43 | "outDir": "../.tshy-build/deno", 44 | }, 45 | "exclude": Array [ 46 | "../src/package.json", 47 | "../src/index-cjs.cts", 48 | ], 49 | "extends": "./build.json", 50 | "include": Array [ 51 | "../src/**/*.ts", 52 | "../src/**/*.mts", 53 | "../src/**/*.tsx", 54 | "../src/**/*.json", 55 | ], 56 | } 57 | ` 58 | 59 | exports[`test/tsconfig.ts > TAP > with custom project tsconfig name > .tshy/esm.json 1`] = ` 60 | Object { 61 | "compilerOptions": Object { 62 | "outDir": "../.tshy-build/esm", 63 | }, 64 | "exclude": Array [ 65 | "../src/package.json", 66 | "../src/index-cjs.cts", 67 | "../src/index-deno.mts", 68 | ], 69 | "extends": "./build.json", 70 | "include": Array [ 71 | "../src/**/*.ts", 72 | "../src/**/*.mts", 73 | "../src/**/*.tsx", 74 | "../src/**/*.json", 75 | ], 76 | } 77 | ` 78 | 79 | exports[`test/tsconfig.ts > TAP > with custom project tsconfig name > .tshy/webpack.json 1`] = ` 80 | Object { 81 | "compilerOptions": Object { 82 | "outDir": "../.tshy-build/webpack", 83 | }, 84 | "exclude": Array [ 85 | "../src/**/*.mts", 86 | "../src/package.json", 87 | "../src/index-cjs.cts", 88 | "../src/index-deno.mts", 89 | ], 90 | "extends": "./build.json", 91 | "include": Array [ 92 | "../src/**/*.ts", 93 | "../src/**/*.cts", 94 | "../src/**/*.tsx", 95 | "../src/**/*.json", 96 | ], 97 | } 98 | ` 99 | 100 | exports[`test/tsconfig.ts > TAP > with custom project tsconfig name > custom.json 1`] = ` 101 | Object { 102 | "compilerOptions": Object { 103 | "this_data": "is preserved", 104 | "yolo": "🍑", 105 | }, 106 | } 107 | ` 108 | 109 | exports[`test/tsconfig.ts > TAP > with tsconfig.json file > .tshy/build.json 1`] = ` 110 | Object { 111 | "compilerOptions": Object { 112 | "module": "nodenext", 113 | "moduleResolution": "nodenext", 114 | "rootDir": "../src", 115 | "target": "es2022", 116 | }, 117 | "extends": "../tsconfig.json", 118 | } 119 | ` 120 | 121 | exports[`test/tsconfig.ts > TAP > with tsconfig.json file > .tshy/build.json generate everything 1`] = ` 122 | Object { 123 | "compilerOptions": Object { 124 | "module": "nodenext", 125 | "moduleResolution": "nodenext", 126 | "rootDir": "../src", 127 | }, 128 | "extends": "../tsconfig.json", 129 | } 130 | ` 131 | 132 | exports[`test/tsconfig.ts > TAP > with tsconfig.json file > .tshy/commonjs.json 1`] = ` 133 | Object { 134 | "compilerOptions": Object { 135 | "outDir": "../.tshy-build/commonjs", 136 | }, 137 | "exclude": Array [ 138 | "../src/**/*.test.ts", 139 | "../src/**/*.mts", 140 | "../src/package.json", 141 | "../src/index-deno.mts", 142 | ], 143 | "extends": "./build.json", 144 | "include": Array [ 145 | "../src/**/*.ts", 146 | "../src/**/*.cts", 147 | "../src/**/*.tsx", 148 | "../src/**/*.json", 149 | ], 150 | } 151 | ` 152 | 153 | exports[`test/tsconfig.ts > TAP > with tsconfig.json file > .tshy/commonjs.json generate everything 1`] = ` 154 | Object { 155 | "compilerOptions": Object { 156 | "outDir": "../.tshy-build/commonjs", 157 | }, 158 | "exclude": Array [ 159 | "../src/**/*.test.ts", 160 | "../src/**/*.mts", 161 | "../src/package.json", 162 | "../src/index-deno.mts", 163 | ], 164 | "extends": "./build.json", 165 | "include": Array [ 166 | "../src/**/*.ts", 167 | "../src/**/*.cts", 168 | "../src/**/*.tsx", 169 | "../src/**/*.json", 170 | ], 171 | } 172 | ` 173 | 174 | exports[`test/tsconfig.ts > TAP > with tsconfig.json file > .tshy/deno.json 1`] = ` 175 | Object { 176 | "compilerOptions": Object { 177 | "outDir": "../.tshy-build/deno", 178 | }, 179 | "exclude": Array [ 180 | "../src/**/*.test.ts", 181 | "../src/package.json", 182 | "../src/index-cjs.cts", 183 | ], 184 | "extends": "./build.json", 185 | "include": Array [ 186 | "../src/**/*.ts", 187 | "../src/**/*.mts", 188 | "../src/**/*.tsx", 189 | "../src/**/*.json", 190 | ], 191 | } 192 | ` 193 | 194 | exports[`test/tsconfig.ts > TAP > with tsconfig.json file > .tshy/esm.json 1`] = ` 195 | Object { 196 | "compilerOptions": Object { 197 | "outDir": "../.tshy-build/esm", 198 | }, 199 | "exclude": Array [ 200 | "../src/**/*.test.ts", 201 | "../src/package.json", 202 | "../src/index-cjs.cts", 203 | "../src/index-deno.mts", 204 | ], 205 | "extends": "./build.json", 206 | "include": Array [ 207 | "../src/**/*.ts", 208 | "../src/**/*.mts", 209 | "../src/**/*.tsx", 210 | "../src/**/*.json", 211 | ], 212 | } 213 | ` 214 | 215 | exports[`test/tsconfig.ts > TAP > with tsconfig.json file > .tshy/esm.json generate everything 1`] = ` 216 | Object { 217 | "compilerOptions": Object { 218 | "outDir": "../.tshy-build/esm", 219 | }, 220 | "exclude": Array [ 221 | "../src/**/*.test.ts", 222 | "../src/package.json", 223 | "../src/index-cjs.cts", 224 | "../src/index-deno.mts", 225 | ], 226 | "extends": "./build.json", 227 | "include": Array [ 228 | "../src/**/*.ts", 229 | "../src/**/*.mts", 230 | "../src/**/*.tsx", 231 | "../src/**/*.json", 232 | ], 233 | } 234 | ` 235 | 236 | exports[`test/tsconfig.ts > TAP > with tsconfig.json file > .tshy/webpack.json 1`] = ` 237 | Object { 238 | "compilerOptions": Object { 239 | "outDir": "../.tshy-build/webpack", 240 | }, 241 | "exclude": Array [ 242 | "../src/**/*.test.ts", 243 | "../src/**/*.mts", 244 | "../src/package.json", 245 | "../src/index-cjs.cts", 246 | "../src/index-deno.mts", 247 | ], 248 | "extends": "./build.json", 249 | "include": Array [ 250 | "../src/**/*.ts", 251 | "../src/**/*.cts", 252 | "../src/**/*.tsx", 253 | "../src/**/*.json", 254 | ], 255 | } 256 | ` 257 | 258 | exports[`test/tsconfig.ts > TAP > with tsconfig.json file > tsconfig.json 1`] = ` 259 | Object { 260 | "compilerOptions": Object { 261 | "this_data": "is preserved", 262 | "yolo": "🍑", 263 | }, 264 | } 265 | ` 266 | 267 | exports[`test/tsconfig.ts > TAP > with tsconfig.json file > tsconfig.json generate everything 1`] = ` 268 | Object { 269 | "compilerOptions": Object { 270 | "declaration": true, 271 | "declarationMap": true, 272 | "esModuleInterop": true, 273 | "forceConsistentCasingInFileNames": true, 274 | "inlineSources": true, 275 | "jsx": "react", 276 | "module": "nodenext", 277 | "moduleResolution": "nodenext", 278 | "noUncheckedIndexedAccess": true, 279 | "resolveJsonModule": true, 280 | "skipLibCheck": true, 281 | "sourceMap": true, 282 | "strict": true, 283 | "target": "es2022", 284 | }, 285 | } 286 | ` 287 | -------------------------------------------------------------------------------- /tap-snapshots/test/usage.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/usage.ts > TAP > no error > no link > must match snapshot 1`] = ` 9 | Usage: tshy [--help] 10 | --help -h Print this message and exit. 11 | --watch -w Watch files in ./src and build when they change. 12 | 13 | Default behavior: build project according to tshy config in package.json 14 | 15 | See the docs for more information: https://github.com/isaacs/tshy 16 | ` 17 | 18 | exports[`test/usage.ts > TAP > no error > with link > must match snapshot 1`] = ` 19 | Usage: tshy [--help] 20 | --help -h Print this message and exit. 21 | --watch -w Watch files in ./src and build when they change. 22 | 23 | Default behavior: build project according to tshy config in package.json 24 | 25 | See the docs for more information: ]8;;https://github.com/isaacs/tshy\\https://github.com/isaacs/tshy]8;;\\ 26 | ` 27 | 28 | exports[`test/usage.ts > TAP > with error > no link > must match snapshot 1`] = ` 29 | Usage: tshy [--help] 30 | --help -h Print this message and exit. 31 | --watch -w Watch files in ./src and build when they change. 32 | 33 | Default behavior: build project according to tshy config in package.json 34 | 35 | See the docs for more information: https://github.com/isaacs/tshy 36 | error string 37 | ` 38 | 39 | exports[`test/usage.ts > TAP > with error > with link > must match snapshot 1`] = ` 40 | Usage: tshy [--help] 41 | --help -h Print this message and exit. 42 | --watch -w Watch files in ./src and build when they change. 43 | 44 | Default behavior: build project according to tshy config in package.json 45 | 46 | See the docs for more information: ]8;;https://github.com/isaacs/tshy\\https://github.com/isaacs/tshy]8;;\\ 47 | error string 48 | ` 49 | -------------------------------------------------------------------------------- /tap-snapshots/test/valid-exports.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/valid-exports.ts > TAP > [] false > exits 1`] = ` 9 | [] 10 | ` 11 | 12 | exports[`test/valid-exports.ts > TAP > [] false > message 1`] = ` 13 | undefined 14 | ` 15 | 16 | exports[`test/valid-exports.ts > TAP > {"./b":"./src/blah.ts"} true > export result 1`] = ` 17 | Object { 18 | "./b": "./src/blah.ts", 19 | } 20 | ` 21 | 22 | exports[`test/valid-exports.ts > TAP > {"./b":"src/b.ts"} true > export result 1`] = ` 23 | Object { 24 | "./b": "./src/b.ts", 25 | } 26 | ` 27 | 28 | exports[`test/valid-exports.ts > TAP > {"./b":{"require":"./blah.js"}} true > export result 1`] = ` 29 | Object { 30 | "./b": Object { 31 | "require": "./blah.js", 32 | }, 33 | } 34 | ` 35 | 36 | exports[`test/valid-exports.ts > TAP > {"./b":{"require":"./src/blah.ts"}} false > exits 1`] = ` 37 | [[1]] 38 | ` 39 | 40 | exports[`test/valid-exports.ts > TAP > {"./b":{"require":"./src/blah.ts"}} false > message 1`] = ` 41 | tshy.exports ./b unbuilt exports must not be in ./src, and exports in src must be string values. got: {"require":"./src/blah.ts"} 42 | ` 43 | 44 | exports[`test/valid-exports.ts > TAP > {"./B":8} false > exits 1`] = ` 45 | [[1]] 46 | ` 47 | 48 | exports[`test/valid-exports.ts > TAP > {"./B":8} false > message 1`] = ` 49 | tshy.exports ./B value must be valid package.json exports value, got: 8 50 | ` 51 | 52 | exports[`test/valid-exports.ts > TAP > {"x":"./src/x.ts"} false > exits 1`] = ` 53 | [[1]] 54 | ` 55 | 56 | exports[`test/valid-exports.ts > TAP > {"x":"./src/x.ts"} false > message 1`] = ` 57 | tshy.exports key must be "." or start with "./", got: x 58 | ` 59 | 60 | exports[`test/valid-exports.ts > TAP > false false > exits 1`] = ` 61 | [] 62 | ` 63 | 64 | exports[`test/valid-exports.ts > TAP > false false > message 1`] = ` 65 | undefined 66 | ` 67 | 68 | exports[`test/valid-exports.ts > TAP > null false > exits 1`] = ` 69 | [] 70 | ` 71 | 72 | exports[`test/valid-exports.ts > TAP > null false > message 1`] = ` 73 | undefined 74 | ` 75 | -------------------------------------------------------------------------------- /tap-snapshots/test/valid-extra-dialects.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/valid-extra-dialects.ts > TAP > {"commonjsDialects":[123]} > failure message 1`] = ` 9 | Array [ 10 | Array [ 11 | "commonjs must be an array of strings, got: 123", 12 | ], 13 | ] 14 | ` 15 | 16 | exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["blah"],"commonjsDialects":["blah"]} > failure message 1`] = ` 17 | Array [ 18 | Array [ 19 | "commonjsDialects and esmDialects must be unique, found blah in both lists", 20 | ], 21 | ] 22 | ` 23 | 24 | exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["blah"],"sourceDialects":["blah"]} > failure message 1`] = ` 25 | Array [ 26 | Array [ 27 | "esmDialects and sourceDialects must be unique, found blah in both lists", 28 | ], 29 | ] 30 | ` 31 | 32 | exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["cjs"]} > failure message 1`] = ` 33 | Array [ 34 | Array [ 35 | "tshy.esmDialects must not contain \\"cjs\\"", 36 | ], 37 | ] 38 | ` 39 | 40 | exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["commonjs"]} > failure message 1`] = ` 41 | Array [ 42 | Array [ 43 | "tshy.esmDialects must not contain \\"commonjs\\"", 44 | ], 45 | ] 46 | ` 47 | 48 | exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["default"]} > failure message 1`] = ` 49 | Array [ 50 | Array [ 51 | "tshy.esmDialects must not contain \\"default\\"", 52 | ], 53 | ] 54 | ` 55 | 56 | exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["import"]} > failure message 1`] = ` 57 | Array [ 58 | Array [ 59 | "tshy.esmDialects must not contain \\"import\\"", 60 | ], 61 | ] 62 | ` 63 | 64 | exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["node"]} > failure message 1`] = ` 65 | Array [ 66 | Array [ 67 | "tshy.esmDialects must not contain \\"node\\"", 68 | ], 69 | ] 70 | ` 71 | 72 | exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["require"]} > failure message 1`] = ` 73 | Array [ 74 | Array [ 75 | "tshy.esmDialects must not contain \\"require\\"", 76 | ], 77 | ] 78 | ` 79 | 80 | exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["source"]} > failure message 1`] = ` 81 | Array [ 82 | Array [ 83 | "tshy.esmDialects must not contain \\"source\\"", 84 | ], 85 | ] 86 | ` 87 | 88 | exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":[123]} > failure message 1`] = ` 89 | Array [ 90 | Array [ 91 | "esm must be an array of strings, got: 123", 92 | ], 93 | ] 94 | ` 95 | 96 | exports[`test/valid-extra-dialects.ts > TAP > {"sourceDialects":["blah"],"commonjsDialects":["blah"]} > failure message 1`] = ` 97 | Array [ 98 | Array [ 99 | "commonjsDialects and sourceDialects must be unique, found blah in both lists", 100 | ], 101 | ] 102 | ` 103 | 104 | exports[`test/valid-extra-dialects.ts > TAP > {"sourceDialects":["source"]} > failure message 1`] = ` 105 | Array [ 106 | Array [ 107 | "tshy.sourceDialects must not contain \\"source\\"", 108 | ], 109 | ] 110 | ` 111 | -------------------------------------------------------------------------------- /tap-snapshots/test/valid-imports.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/valid-imports.ts > TAP > {"pkg":{"imports":"asdf"}} > failure message 1`] = ` 9 | invalid imports object, must be Record, got: "asdf" 10 | ` 11 | 12 | exports[`test/valid-imports.ts > TAP > {"pkg":{"imports":[]}} > failure message 1`] = ` 13 | invalid imports object, must be Record, got: [] 14 | ` 15 | 16 | exports[`test/valid-imports.ts > TAP > {"pkg":{"imports":{"#":"y"}}} > failure message 1`] = ` 17 | invalid imports module specifier: # 18 | ` 19 | 20 | exports[`test/valid-imports.ts > TAP > {"pkg":{"imports":{"#x":["./src/x"]}}} > failure message 1`] = ` 21 | unbuilt package.imports #x must not be in ./src, and imports in ./src must be string values. got: ["./src/x"] 22 | ` 23 | 24 | exports[`test/valid-imports.ts > TAP > {"pkg":{"imports":{"x":"y"}}} > failure message 1`] = ` 25 | invalid imports module specifier: x 26 | ` 27 | -------------------------------------------------------------------------------- /tap-snapshots/test/watch.ts.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/watch.ts > TAP > build failure > must match snapshot 1`] = ` 9 | Array [] 10 | ` 11 | 12 | exports[`test/watch.ts > TAP > build failure > must match snapshot 2`] = ` 13 | Array [] 14 | ` 15 | 16 | exports[`test/watch.ts > TAP > build failure > must match snapshot 3`] = ` 17 | Array [ 18 | Array [ 19 | "/u001b[32mbuild success/u001b[39m", 20 | Object { 21 | "code": 0, 22 | "signal": null, 23 | }, 24 | ], 25 | Array [ 26 | "/u001b[32mbuild success/u001b[39m", 27 | Object { 28 | "code": 0, 29 | "signal": null, 30 | }, 31 | ], 32 | ] 33 | ` 34 | 35 | exports[`test/watch.ts > TAP > build failure > must match snapshot 4`] = ` 36 | Array [] 37 | ` 38 | 39 | exports[`test/watch.ts > TAP > build whenever changes happen > must match snapshot 1`] = ` 40 | Array [ 41 | Array [ 42 | "/u001b[32mbuild success/u001b[39m", 43 | Object { 44 | "code": 0, 45 | "signal": null, 46 | }, 47 | ], 48 | Array [ 49 | "/u001b[32mbuild success/u001b[39m", 50 | Object { 51 | "code": 0, 52 | "signal": null, 53 | }, 54 | ], 55 | ] 56 | ` 57 | 58 | exports[`test/watch.ts > TAP > build whenever changes happen > must match snapshot 2`] = ` 59 | Array [] 60 | ` 61 | 62 | exports[`test/watch.ts > TAP > must match snapshot 1`] = ` 63 | Object { 64 | "bin": "{CWD}/dist/esm/index.js", 65 | "options": Object { 66 | "ignored": Function ignored(path), 67 | "ignoreInitial": true, 68 | "ignorePermissionErrors": true, 69 | "persistent": true, 70 | }, 71 | "rootPJ": "{CWD}/package.json", 72 | "src": "{CWD}/src", 73 | "srcNM": "{CWD}/src/node_modules", 74 | "srcPJ": "{CWD}/src/package.json", 75 | "targets": Array [ 76 | "{CWD}/src", 77 | "{CWD}/package.json", 78 | ], 79 | } 80 | ` 81 | -------------------------------------------------------------------------------- /test/add-dot.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | import addDot from '../src/add-dot.js' 3 | 4 | t.equal(addDot('./foo'), './foo') 5 | t.equal(addDot('foo'), './foo') 6 | -------------------------------------------------------------------------------- /test/bins.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import t from 'tap' 3 | 4 | t.test('no bin, no chmod', async t => { 5 | const chmodSync = t.captureFn((_: string, __: number) => {}) 6 | const { default: bins } = (await t.mockImport( 7 | '../dist/esm/bins.js', 8 | { 9 | '../dist/esm/package.js': { default: {} }, 10 | 'node:fs': { chmodSync }, 11 | }, 12 | )) as typeof import('../dist/esm/bins.js') 13 | bins() 14 | t.strictSame(chmodSync.args(), []) 15 | }) 16 | 17 | t.test('chmod bin string', async t => { 18 | const chmodSync = t.captureFn((_: string, __: number) => {}) 19 | const { default: bins } = (await t.mockImport( 20 | '../dist/esm/bins.js', 21 | { 22 | '../dist/esm/package.js': { default: { bin: './bin.js' } }, 23 | 'node:fs': { chmodSync }, 24 | }, 25 | )) as typeof import('../dist/esm/bins.js') 26 | bins() 27 | t.strictSame(chmodSync.args(), [[resolve('./bin.js'), 0o755]]) 28 | }) 29 | 30 | t.test('chmod bin object', async t => { 31 | const chmodSync = t.captureFn((_: string, __: number) => {}) 32 | const { default: bins } = await t.mockImport( 33 | '../dist/esm/bins.js', 34 | { 35 | '../dist/esm/package.js': { 36 | default: { bin: { a: './a.js', b: './b.js' } }, 37 | }, 38 | 'node:fs': { chmodSync }, 39 | }, 40 | ) 41 | bins() 42 | t.strictSame(chmodSync.args(), [ 43 | [resolve('./a.js'), 0o755], 44 | [resolve('./b.js'), 0o755], 45 | ]) 46 | }) 47 | -------------------------------------------------------------------------------- /test/build-commonjs.ts: -------------------------------------------------------------------------------- 1 | import { SpawnSyncReturns } from 'child_process' 2 | import { readdirSync } from 'fs' 3 | import t from 'tap' 4 | 5 | const node = process.execPath 6 | 7 | t.cleanSnapshot = s => 8 | s.split(node).join('{NODE}').replace(/\\/g, '/') 9 | 10 | const spawnSuccess: SpawnSyncReturns = { 11 | status: 0, 12 | signal: null, 13 | pid: 123, 14 | output: [], 15 | stdout: Buffer.alloc(0), 16 | stderr: Buffer.alloc(0), 17 | } 18 | 19 | const spawnFail: SpawnSyncReturns = { 20 | status: 1, 21 | signal: null, 22 | pid: 123, 23 | output: [], 24 | stdout: Buffer.alloc(0), 25 | stderr: Buffer.alloc(0), 26 | } 27 | 28 | import { spawnSync as ogSpawnSync } from 'node:child_process' 29 | let spawnResult = spawnSuccess 30 | const spawnSync = t.captureFn((...a: any[]) => { 31 | //@ts-ignore 32 | ogSpawnSync(...a) 33 | return spawnResult 34 | }) 35 | 36 | const output = () => 37 | readdirSync('.tshy-build/commonjs').sort((a, b) => 38 | a.localeCompare(b, 'en'), 39 | ) 40 | 41 | t.test('basic commonjs build', async t => { 42 | spawnResult = spawnSuccess 43 | t.chdir( 44 | t.testdir({ 45 | 'package.json': JSON.stringify({ 46 | tshy: { 47 | commonjsDialects: ['blah', 'no-overrides'], 48 | exports: { 49 | '.': './src/index.ts', 50 | './blah': './src/blah.ts', 51 | }, 52 | }, 53 | }), 54 | src: { 55 | 'index.ts': 'console.log("hello")', 56 | 'blah.ts': ` 57 | //@ts-ignore 58 | export const u = import.meta.url 59 | `, 60 | 'blah-blah.cts': ` 61 | //@ts-ignore 62 | export const u = 'file:///blah/blah.blah' 63 | `, 64 | 'blah-cjs.cts': ` 65 | import { pathToFileURL } from 'url' 66 | export const u = String(pathToFileURL(__filename)) 67 | `, 68 | }, 69 | }), 70 | ) 71 | let buildFailed = false 72 | const { buildCommonJS } = (await t.mockImport( 73 | '../dist/esm/build-commonjs.js', 74 | { 75 | child_process: { spawnSync }, 76 | '../dist/esm/build-fail.js': { 77 | default: () => { 78 | buildFailed = true 79 | }, 80 | }, 81 | }, 82 | )) as typeof import('../dist/esm/build-commonjs.js') 83 | buildCommonJS() 84 | t.equal(buildFailed, false) 85 | t.matchSnapshot(output()) 86 | t.matchSnapshot(spawnSync.args()) 87 | }) 88 | 89 | t.test('build failure', async t => { 90 | spawnResult = spawnFail 91 | t.chdir( 92 | t.testdir({ 93 | 'package.json': JSON.stringify({ 94 | tshy: { 95 | exports: { 96 | '.': './src/index.ts', 97 | './blah': './src/blah.ts', 98 | }, 99 | }, 100 | }), 101 | src: { 102 | 'index.ts': 'console.log("hello")', 103 | 'blah.ts': ` 104 | //@ts-ignore 105 | export const u = import.meta.url 106 | `, 107 | 'blah-cjs.cts': ` 108 | import { pathToFileURL } from 'url' 109 | export const u = pathToFileURL(__filename) 110 | `, 111 | }, 112 | }), 113 | ) 114 | let buildFailed = false 115 | const { buildCommonJS } = (await t.mockImport( 116 | '../dist/esm/build-commonjs.js', 117 | { 118 | child_process: { spawnSync }, 119 | '../dist/esm/build-fail.js': { 120 | default: () => { 121 | buildFailed = true 122 | }, 123 | }, 124 | }, 125 | )) as typeof import('../dist/esm/build-commonjs.js') 126 | buildCommonJS() 127 | t.equal(buildFailed, true) 128 | t.matchSnapshot(output()) 129 | t.matchSnapshot(spawnSync.args()) 130 | }) 131 | -------------------------------------------------------------------------------- /test/build-esm.ts: -------------------------------------------------------------------------------- 1 | import { SpawnSyncReturns } from 'child_process' 2 | import { readdirSync } from 'fs' 3 | import t from 'tap' 4 | 5 | t.cleanSnapshot = s => s.split(process.execPath).join('{NODE}') 6 | 7 | const spawnSuccess: SpawnSyncReturns = { 8 | status: 0, 9 | signal: null, 10 | pid: 123, 11 | output: [], 12 | stdout: Buffer.alloc(0), 13 | stderr: Buffer.alloc(0), 14 | } 15 | 16 | const spawnFail: SpawnSyncReturns = { 17 | status: 1, 18 | signal: null, 19 | pid: 123, 20 | output: [], 21 | stdout: Buffer.alloc(0), 22 | stderr: Buffer.alloc(0), 23 | } 24 | 25 | import { spawnSync as ogSpawnSync } from 'node:child_process' 26 | let spawnResult = spawnSuccess 27 | const spawnSync = t.captureFn((...a: any[]) => { 28 | //@ts-ignore 29 | ogSpawnSync(...a) 30 | return spawnResult 31 | }) 32 | 33 | const output = () => 34 | readdirSync('.tshy-build/esm').sort((a, b) => 35 | a.localeCompare(b, 'en'), 36 | ) 37 | 38 | t.test('basic esm build', async t => { 39 | spawnResult = spawnSuccess 40 | t.chdir( 41 | t.testdir({ 42 | 'package.json': JSON.stringify({ 43 | tshy: { 44 | esmDialects: ['blah', 'no-overrides'], 45 | exports: { 46 | '.': './src/index.ts', 47 | './blah': './src/blah.ts', 48 | }, 49 | }, 50 | }), 51 | src: { 52 | 'index.ts': 'console.log("hello")', 53 | 'blah.ts': ` 54 | //@ts-ignore 55 | export const u = import.meta.url 56 | `, 57 | 'blah-blah.mts': ` 58 | //@ts-ignore 59 | export const u = 'file://blah/blah.blah' 60 | `, 61 | 'blah-cjs.cts': ` 62 | import { pathToFileURL } from 'url' 63 | export const u = pathToFileURL(__filename) 64 | `, 65 | }, 66 | }), 67 | ) 68 | let buildFailed = false 69 | const { buildESM } = (await t.mockImport( 70 | '../dist/esm/build-esm.js', 71 | { 72 | child_process: { spawnSync }, 73 | '../dist/esm/build-fail.js': { 74 | default: () => { 75 | buildFailed = true 76 | }, 77 | }, 78 | }, 79 | )) as typeof import('../dist/esm/build-esm.js') 80 | buildESM() 81 | t.equal(buildFailed, false) 82 | t.matchSnapshot(output()) 83 | t.matchSnapshot(spawnSync.args()) 84 | }) 85 | 86 | t.test('build failure', async t => { 87 | spawnResult = spawnFail 88 | t.chdir( 89 | t.testdir({ 90 | 'package.json': JSON.stringify({ 91 | tshy: { 92 | exports: { 93 | '.': './src/index.ts', 94 | './blah': './src/blah.ts', 95 | }, 96 | }, 97 | }), 98 | src: { 99 | 'index.ts': 'console.log("hello")', 100 | 'blah.ts': ` 101 | //@ts-ignore 102 | export const u = import.meta.url 103 | `, 104 | 'blah-cjs.cts': ` 105 | import { pathToFileURL } from 'url' 106 | export const u = pathToFileURL(__filename) 107 | `, 108 | }, 109 | }), 110 | ) 111 | let buildFailed = false 112 | const { buildESM } = (await t.mockImport( 113 | '../dist/esm/build-esm.js', 114 | { 115 | child_process: { spawnSync }, 116 | '../dist/esm/build-fail.js': { 117 | default: () => { 118 | buildFailed = true 119 | }, 120 | }, 121 | }, 122 | )) as typeof import('../dist/esm/build-esm.js') 123 | buildESM() 124 | t.equal(buildFailed, true) 125 | t.matchSnapshot(output()) 126 | t.matchSnapshot(spawnSync.args()) 127 | }) 128 | -------------------------------------------------------------------------------- /test/build-fail.ts: -------------------------------------------------------------------------------- 1 | import { SpawnSyncReturns } from 'node:child_process' 2 | import t from 'tap' 3 | t.capture(process, 'exit', (...a: any[]) => 4 | calls.push(['process.exit', a]), 5 | ) 6 | const calls: [string, any[]][] = [] 7 | const { default: buildFail } = (await t.mockImport( 8 | '../dist/esm/build-fail.js', 9 | { 10 | '../dist/esm/tsconfig.js': {}, 11 | '../dist/esm/package.js': { default: { name: 'package' } }, 12 | '../dist/esm/unbuilt-imports.js': { 13 | unlink: (...a: any[]) => calls.push(['unlinkImports', a]), 14 | }, 15 | '../dist/esm/self-link.js': { 16 | unlink: (...a: any[]) => calls.push(['unlinkSelfDep', a]), 17 | }, 18 | '../dist/esm/set-folder-dialect.js': { 19 | default: (...a: any[]) => calls.push(['setFolderDialect', a]), 20 | }, 21 | '../dist/esm/fail.js': { 22 | default: (...a: any[]) => calls.push(['fail', a]), 23 | }, 24 | '../dist/esm/console.js': { 25 | error: (...a: any[]) => calls.push(['console.error', a]), 26 | }, 27 | }, 28 | )) as typeof import('../src/build-fail.js') 29 | 30 | buildFail({ 31 | code: 0, 32 | signal: 'testing', 33 | } as unknown as SpawnSyncReturns) 34 | 35 | t.matchSnapshot(calls) 36 | -------------------------------------------------------------------------------- /test/build-live-commonjs.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'fs' 2 | import t from 'tap' 3 | 4 | const output = () => 5 | readdirSync('.tshy-build/commonjs').sort((a, b) => 6 | a.localeCompare(b, 'en'), 7 | ) 8 | 9 | t.test('commonjs live dev build', async t => { 10 | t.chdir( 11 | t.testdir({ 12 | 'package.json': JSON.stringify({ 13 | tshy: { 14 | commonjsDialects: ['blah', 'no-overrides'], 15 | exports: { 16 | '.': './src/index.ts', 17 | './blah': './src/blah.ts', 18 | }, 19 | }, 20 | }), 21 | src: { 22 | 'index.ts': 'console.log("hello")', 23 | 'blah.ts': '', 24 | 'blah-blah.cts': '', 25 | 'blah-cjs.cts': '', 26 | }, 27 | }), 28 | ) 29 | const { buildLiveCommonJS } = await t.mockImport< 30 | typeof import('../src/build-live-commonjs.js') 31 | >('../src/build-live-commonjs.js') 32 | buildLiveCommonJS() 33 | t.matchSnapshot(output()) 34 | }) 35 | -------------------------------------------------------------------------------- /test/build-live-esm.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'fs' 2 | import t from 'tap' 3 | 4 | const output = () => 5 | readdirSync('.tshy-build/esm').sort((a, b) => 6 | a.localeCompare(b, 'en'), 7 | ) 8 | 9 | t.test('esm live dev build', async t => { 10 | t.chdir( 11 | t.testdir({ 12 | 'package.json': JSON.stringify({ 13 | tshy: { 14 | esmDialects: ['blah', 'no-overrides'], 15 | exports: { 16 | '.': './src/index.ts', 17 | './blah': './src/blah.ts', 18 | }, 19 | }, 20 | }), 21 | src: { 22 | 'index.ts': '', 23 | 'blah.ts': '', 24 | 'blah-blah.mts': '', 25 | 'blah-cjs.cts': '', 26 | }, 27 | }), 28 | ) 29 | const { buildLiveESM } = await t.mockImport< 30 | typeof import('../src/build-live-esm.js') 31 | >('../src/build-live-esm.js') 32 | buildLiveESM() 33 | t.matchSnapshot(output()) 34 | }) 35 | -------------------------------------------------------------------------------- /test/build.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | import t from 'tap' 3 | import { fileURLToPath } from 'url' 4 | import { Package } from '../src/types.js' 5 | 6 | const pkg = {} as unknown as Package 7 | 8 | let builtCommonJS = false 9 | let builtESM = false 10 | let builtLiveCommonJS = false 11 | let builtLiveESM = false 12 | t.beforeEach(() => { 13 | builtCommonJS = false 14 | builtESM = false 15 | builtLiveCommonJS = false 16 | builtLiveESM = false 17 | }) 18 | 19 | const logCall = t.captureFn((_msg: string, _args: any[]) => {}) 20 | const calls = logCall.args 21 | 22 | const mocks = { 23 | '../dist/esm/unbuilt-imports.js': { 24 | link: async (...a: any[]) => logCall('imports.link', a), 25 | unlink: async (...a: any[]) => logCall('imports.unlink', a), 26 | save: (...a: any[]) => logCall('imports.save', a), 27 | }, 28 | '../dist/esm/self-link.js': { 29 | link: (...a: any[]) => logCall('self-link.link', a), 30 | unlink: (...a: any[]) => logCall('self-link.unlink', a), 31 | }, 32 | '../dist/esm/package.js': { default: pkg }, 33 | '../dist/esm/bins.js': { default: () => {} }, 34 | '../dist/esm/build-commonjs.js': { 35 | buildCommonJS: () => (builtCommonJS = true), 36 | }, 37 | '../dist/esm/build-live-commonjs.js': { 38 | buildLiveCommonJS: () => (builtLiveCommonJS = true), 39 | }, 40 | '../dist/esm/build-esm.js': { buildESM: () => (builtESM = true) }, 41 | '../dist/esm/build-live-esm.js': { 42 | buildLiveESM: () => (builtLiveESM = true), 43 | }, 44 | rimraf: { rimrafSync: () => {} }, 45 | '../dist/esm/tsconfig.js': {}, 46 | '../dist/esm/write-package.js': { default: () => {} }, 47 | 'sync-content': { syncContentSync: () => {} }, 48 | '../dist/esm/console.js': { 49 | log: () => {}, 50 | debug: () => {}, 51 | print: () => {}, 52 | }, 53 | } 54 | 55 | t.test('default settings', async t => { 56 | const { default: build } = await t.mockImport( 57 | '../dist/esm/build.js', 58 | mocks, 59 | ) 60 | await build() 61 | t.equal(builtESM, true) 62 | t.equal(builtCommonJS, true) 63 | t.equal(builtLiveESM, false) 64 | t.equal(builtLiveCommonJS, false) 65 | t.matchSnapshot(calls()) 66 | }) 67 | 68 | t.test('liveDev', async t => { 69 | pkg.tshy = { liveDev: true } 70 | t.test('no envs', async t => { 71 | const { default: build } = await t.mockImport( 72 | '../dist/esm/build.js', 73 | mocks, 74 | ) 75 | await build() 76 | t.equal(builtESM, false) 77 | t.equal(builtCommonJS, false) 78 | t.equal(builtLiveESM, true) 79 | t.equal(builtLiveCommonJS, true) 80 | t.matchSnapshot(calls()) 81 | }) 82 | for (const s of ['pack', 'publish']) { 83 | t.test(s, async t => { 84 | t.intercept(process, 'env', { 85 | value: { 86 | ...process.env, 87 | npm_command: s, 88 | }, 89 | }) 90 | const { default: build } = await t.mockImport( 91 | '../dist/esm/build.js', 92 | mocks, 93 | ) 94 | await build() 95 | t.equal(builtESM, true) 96 | t.equal(builtCommonJS, true) 97 | t.equal(builtLiveESM, false) 98 | t.equal(builtLiveCommonJS, false) 99 | t.matchSnapshot(calls()) 100 | }) 101 | } 102 | }) 103 | 104 | t.test('build commonjs only', async t => { 105 | pkg.tshy = { dialects: ['commonjs'] } 106 | const { default: build } = await t.mockImport( 107 | '../dist/esm/build.js', 108 | mocks, 109 | ) 110 | await build() 111 | t.equal(builtESM, false) 112 | t.equal(builtCommonJS, true) 113 | t.matchSnapshot(calls()) 114 | }) 115 | 116 | t.test('build esm only', async t => { 117 | pkg.tshy = { dialects: ['esm'] } 118 | const { default: build } = await t.mockImport( 119 | '../dist/esm/build.js', 120 | mocks, 121 | ) 122 | await build() 123 | t.equal(builtESM, true) 124 | t.equal(builtCommonJS, false) 125 | t.matchSnapshot(calls()) 126 | }) 127 | 128 | t.test('build both', async t => { 129 | pkg.tshy = { dialects: ['esm', 'commonjs'] } 130 | const { default: build } = await t.mockImport( 131 | '../dist/esm/build.js', 132 | mocks, 133 | ) 134 | await build() 135 | t.equal(builtESM, true) 136 | t.equal(builtCommonJS, true) 137 | t.matchSnapshot(calls()) 138 | }) 139 | 140 | t.test('imports linking', async t => { 141 | // make sure one of them doesn't already have a scripts block 142 | for (const i of ['imports', 'imports-with-star', 'basic']) { 143 | t.test(i, async t => { 144 | t.chdir( 145 | fileURLToPath(new URL('./fixtures/' + i, import.meta.url)), 146 | ) 147 | const pkg = JSON.parse(readFileSync('package.json', 'utf8')) 148 | const { default: build } = await t.mockImport( 149 | '../dist/esm/build.js', 150 | { 151 | ...mocks, 152 | '../dist/esm/package.js': { default: pkg }, 153 | '../dist/esm/unbuilt-imports.js': { 154 | link: async (...a: any[]) => logCall('imports.link', a), 155 | unlink: async (...a: any[]) => 156 | logCall('imports.unlink', a), 157 | save: (...a: any[]) => { 158 | logCall('imports.save', a) 159 | return i.startsWith('imports') 160 | }, 161 | }, 162 | }, 163 | ) 164 | await build() 165 | t.matchSnapshot(calls()) 166 | }) 167 | } 168 | }) 169 | -------------------------------------------------------------------------------- /test/built-imports.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | import getImports from '../src/built-imports.js' 3 | import { Package } from '../src/types.js' 4 | 5 | const cases: [ 6 | pkg: Omit, 7 | expect: Package['imports'], 8 | ][] = [ 9 | [{}, undefined], 10 | [{ tshy: {} }, undefined], 11 | [{ imports: { '#x': 'y' } }, { '#x': 'y' }], 12 | [ 13 | { 14 | imports: { 15 | '#t': './src/t.ts', 16 | '#x': './src/x.tsx', 17 | '#b/*': './src/blah/*.ts', 18 | }, 19 | }, 20 | { '#t': './t.js', '#x': './x.js', '#b/*': './blah/*.js' }, 21 | ], 22 | [ 23 | { 24 | imports: { 25 | '#a': './xyz/a.js', 26 | '#t': './src/t.ts', 27 | '#x': './src/x.tsx', 28 | '#b/*': './src/blah/*.ts', 29 | }, 30 | }, 31 | { 32 | '#a': './xyz/a.js', 33 | '#t': './t.js', 34 | '#x': './x.js', 35 | '#b/*': './blah/*.js', 36 | }, 37 | ], 38 | ] 39 | 40 | let i = 0 41 | for (const [pkg, expect] of cases) { 42 | t.test(String(i++), t => { 43 | t.strictSame(getImports(pkg as Package), expect) 44 | t.end() 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /test/clean-build-tmp.ts: -------------------------------------------------------------------------------- 1 | import { statSync } from 'fs' 2 | import t from 'tap' 3 | import cleanBuildTmp from '../src/clean-build-tmp.js' 4 | import readTypescriptConfig from '../src/read-typescript-config.js' 5 | 6 | t.test('no incremental build, just delete it', t => { 7 | readTypescriptConfig().options.incremental = false 8 | readTypescriptConfig().options.composite = false 9 | t.chdir( 10 | t.testdir({ 11 | '.tshy-build': {}, 12 | }), 13 | ) 14 | cleanBuildTmp() 15 | t.throws(() => statSync('.tshy-build')) 16 | t.end() 17 | }) 18 | 19 | t.test('no tsbuildinfo, just delete it', t => { 20 | readTypescriptConfig().options.incremental = true 21 | readTypescriptConfig().options.composite = true 22 | t.chdir( 23 | t.testdir({ 24 | '.tshy-build': {}, 25 | }), 26 | ) 27 | cleanBuildTmp() 28 | t.throws(() => statSync('.tshy-build')) 29 | t.end() 30 | }) 31 | 32 | t.test('remove files not found in src', t => { 33 | readTypescriptConfig().options.composite = true 34 | t.chdir( 35 | t.testdir({ 36 | '.tshy-build': { 37 | '.tshy': { 'esm.tsbuildinfo': '{}' }, 38 | esm: { 39 | 'a.d.ts': '', 40 | 'a.js': '', 41 | 'a.js.map': '', 42 | 'a.d.ts.map': '', 43 | 'a.jsx': '', 44 | 'a.jsx.map': '', 45 | dir: { 46 | 'b.d.ts': '', 47 | 'b.js': '', 48 | 'b.js.map': '', 49 | 'b.d.ts.map': '', 50 | }, 51 | 'x.d.ts': '', 52 | 'x.js': '', 53 | 'x.js.map': '', 54 | 'x.d.ts.map': '', 55 | 'm.mjs': '', 56 | 'm.d.mts': '', 57 | 'c.cjs': '', 58 | 'c.d.cts': '', 59 | xdir: { 60 | 'x.d.ts': '', 61 | 'x.js': '', 62 | 'x.js.map': '', 63 | 'x.d.ts.map': '', 64 | }, 65 | }, 66 | }, 67 | src: { 68 | 'a.tsx': '', 69 | dir: { 70 | 'b.ts': '', 71 | }, 72 | }, 73 | }), 74 | ) 75 | cleanBuildTmp() 76 | t.throws(() => statSync('.tshy-build/dist/esm/x.js.map')) 77 | t.throws(() => statSync('.tshy-build/dist/esm/x.d.ts')) 78 | t.end() 79 | }) 80 | -------------------------------------------------------------------------------- /test/config.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | import t from 'tap' 3 | import { 4 | Package, 5 | TshyConfig, 6 | TshyConfigMaybeGlobExports, 7 | } from '../src/types.js' 8 | 9 | t.test('basic parsing cases', t => { 10 | const cases: [ 11 | config: undefined | TshyConfigMaybeGlobExports, 12 | sources: string[], 13 | ok: boolean, 14 | expect: TshyConfig, 15 | ][] = [ 16 | [ 17 | undefined, 18 | [], 19 | true, 20 | { exports: { './package.json': './package.json' } }, 21 | ], 22 | 23 | [ 24 | { exports: './src/*' }, 25 | ['./src/index.ts', './src/foo.ts', './src/utils/bar.ts'], 26 | true, 27 | { 28 | exports: { 29 | './package.json': './package.json', 30 | '.': './src/index.ts', 31 | './foo': './src/foo.ts', 32 | }, 33 | }, 34 | ], 35 | [ 36 | { exports: 'src/*' }, 37 | ['./src/index.ts', './src/foo.ts', './src/utils/bar.ts'], 38 | true, 39 | { 40 | exports: { 41 | './package.json': './package.json', 42 | '.': './src/index.ts', 43 | './foo': './src/foo.ts', 44 | }, 45 | }, 46 | ], 47 | [ 48 | { exports: ['src/utils/*.ts', './src/index.*'] }, 49 | [ 50 | './src/index.ts', 51 | './src/utils/baz.js', 52 | './src/foo.ts', 53 | './src/utils/bar.ts', 54 | ], 55 | true, 56 | { 57 | exports: { 58 | './package.json': './package.json', 59 | '.': './src/index.ts', 60 | './utils/bar': './src/utils/bar.ts', 61 | }, 62 | }, 63 | ], 64 | 65 | [ 66 | undefined, 67 | ['./src/index.ts'], 68 | true, 69 | { 70 | exports: { 71 | './package.json': './package.json', 72 | '.': './src/index.ts', 73 | }, 74 | }, 75 | ], 76 | 77 | [ 78 | { selfLink: false }, 79 | ['./src/index.ts'], 80 | true, 81 | { 82 | exports: { 83 | './package.json': './package.json', 84 | '.': './src/index.ts', 85 | }, 86 | selfLink: false, 87 | }, 88 | ], 89 | 90 | //@ts-expect-error 91 | [{ dialects: 'yolo' }, [], false, {}], 92 | 93 | [ 94 | { exports: { './blah': { require: './src/notallowed' } } }, 95 | [], 96 | false, 97 | {}, 98 | ], 99 | 100 | [ 101 | { 102 | exclude: ['./src/*.test.ts'], 103 | exports: { '.': './src/index.ts' }, 104 | }, 105 | ['./src/index.ts'], 106 | true, 107 | { 108 | exclude: ['./src/*.test.ts'], 109 | exports: { '.': './src/index.ts' }, 110 | }, 111 | ], 112 | 113 | //@ts-expect-error 114 | [{ main: 'blah' }, [], false, {}], 115 | 116 | //@ts-expect-error 117 | [{ imports: 'blah' }, [], false, {}], 118 | 119 | //@ts-expect-error 120 | [{ imports: ['blah'] }, [], false, {}], 121 | 122 | [{ project: 'thisFileDoesNotExist.json' }, [], false, {}], 123 | ] 124 | 125 | t.plan(cases.length) 126 | 127 | for (const [config, sources, ok, expect] of cases) { 128 | t.test(JSON.stringify({ config, sources, ok }), async t => { 129 | const exits = t.capture(process, 'exit', () => { 130 | throw 'exit' 131 | }).args 132 | 133 | const pkg: Package = { 134 | name: 'x', 135 | version: '1.2.3', 136 | tshy: config, 137 | } 138 | let failMsg: undefined | string = undefined 139 | const result = (await t 140 | .mockImport('../dist/esm/config.js', { 141 | '../dist/esm/package.js': { default: pkg }, 142 | '../dist/esm/fail.js': { 143 | default: (m: string) => (failMsg = m), 144 | }, 145 | '../dist/esm/sources.js': { default: sources }, 146 | }) 147 | .catch(er => { 148 | if (ok) t.equal(er, undefined, 'did not expect exit') 149 | })) as typeof import('../dist/esm/config.js') 150 | if (!ok) { 151 | t.matchSnapshot(failMsg) 152 | t.strictSame(exits(), [[1]]) 153 | } else { 154 | t.strictSame(result.default, expect) 155 | t.equal(failMsg, undefined) 156 | t.strictSame(exits(), []) 157 | } 158 | }) 159 | } 160 | }) 161 | 162 | t.test('do not clobber glob exports', async t => { 163 | const pkg: Package = { 164 | name: 'x', 165 | version: '1.2.3', 166 | tshy: { 167 | selfLink: false, 168 | dialects: ['esm'], 169 | exports: 'src/*', 170 | }, 171 | } 172 | 173 | const dir = t.testdir({ 174 | 'package.json': JSON.stringify(pkg), 175 | src: { 176 | 'foo.ts': 'console.log("hello from foo")', 177 | 'bar.ts': 'console.log("hello from bar")', 178 | }, 179 | }) 180 | t.chdir(dir) 181 | await t.mockImport('../src/index.js') 182 | const result = JSON.parse(readFileSync('./package.json', 'utf8')) 183 | t.strictSame(result.tshy, pkg.tshy) 184 | }) 185 | -------------------------------------------------------------------------------- /test/console.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | const logs = t.capture(console, 'log').args 3 | const errors = t.capture(console, 'error').args 4 | 5 | const env = process.env 6 | t.beforeEach(t => t.intercept(process, 'env', { value: { ...env } })) 7 | 8 | t.test('no verbosity setting', async t => { 9 | delete process.env.TSHY_VERBOSE 10 | const console = (await t.mockImport( 11 | '../dist/esm/console.js', 12 | )) as typeof import('../dist/esm/console.js') 13 | console.log('hello') 14 | console.debug('debug') 15 | console.error('error') 16 | t.strictSame(logs(), []) 17 | t.strictSame(errors(), []) 18 | console.print() 19 | t.strictSame(logs(), []) 20 | t.strictSame(errors(), [['debug'], ['error']]) 21 | }) 22 | 23 | t.test('verbose=1', async t => { 24 | process.env.TSHY_VERBOSE = '1' 25 | const console = (await t.mockImport( 26 | '../dist/esm/console.js', 27 | )) as typeof import('../dist/esm/console.js') 28 | console.log('hello') 29 | console.debug('debug') 30 | console.error('error') 31 | t.strictSame(logs(), [['hello']]) 32 | t.strictSame(errors(), [['error']]) 33 | console.print() 34 | t.strictSame(logs(), []) 35 | t.strictSame(errors(), [['debug']]) 36 | }) 37 | 38 | t.test('verbose=2', async t => { 39 | process.env.TSHY_VERBOSE = '2' 40 | const console = (await t.mockImport( 41 | '../dist/esm/console.js', 42 | )) as typeof import('../dist/esm/console.js') 43 | console.log('hello') 44 | console.debug('debug') 45 | console.error('error') 46 | t.strictSame(logs(), [['hello']]) 47 | t.strictSame(errors(), [['debug'], ['error']]) 48 | console.print() 49 | t.strictSame(logs(), []) 50 | t.strictSame(errors(), []) 51 | }) 52 | -------------------------------------------------------------------------------- /test/dialects.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | 3 | const { default: dialects } = (await t.mockImport( 4 | '../dist/esm/dialects.js', 5 | { 6 | '../dist/esm/config.js': { default: { dialects: ['esm'] } }, 7 | }, 8 | )) as typeof import('../dist/esm/dialects.js') 9 | const { default: dialectsEmpty } = (await t.mockImport( 10 | '../dist/esm/dialects.js', 11 | { 12 | '../dist/esm/config.js': { default: {} }, 13 | }, 14 | )) as typeof import('../dist/esm/dialects.js') 15 | 16 | t.strictSame(dialects, ['esm']) 17 | t.strictSame(dialectsEmpty, ['esm', 'commonjs']) 18 | -------------------------------------------------------------------------------- /test/exports.ts: -------------------------------------------------------------------------------- 1 | import { ConditionalValue, ExportsSubpaths } from 'resolve-import' 2 | import t, { Test } from 'tap' 3 | import { PolyfillSet } from '../src/polyfills.js' 4 | import { Package, TshyConfig } from '../src/types.js' 5 | 6 | // order is relevant in the exports objects we're snapshotting here 7 | t.compareOptions = { sort: false } 8 | 9 | const { getImpTarget, getReqTarget } = await t.mockImport< 10 | typeof import('../src/exports.js') 11 | >('../src/exports.js', { 12 | '../src/dialects.js': { default: ['esm', 'commonjs'] }, 13 | }) 14 | 15 | const cjs = await t.mockImport( 16 | '../src/exports.js', 17 | { 18 | '../src/dialects.js': { default: ['commonjs'] }, 19 | }, 20 | ) 21 | const esm = await t.mockImport( 22 | '../src/exports.js', 23 | { 24 | '../src/dialects.js': { default: ['esm'] }, 25 | }, 26 | ) 27 | 28 | t.equal(getImpTarget(undefined), undefined) 29 | t.equal(getImpTarget('foo.cts'), undefined) 30 | t.equal(getImpTarget({ require: './foo.cts' }), undefined) 31 | t.equal(getImpTarget('./src/foo.cts'), undefined) 32 | t.equal(getImpTarget({ import: './foo.mts' }), './foo.mts') 33 | t.equal(getImpTarget('./src/foo.mts'), './dist/esm/foo.mjs') 34 | t.equal(cjs.getImpTarget(undefined), undefined) 35 | t.equal(cjs.getImpTarget('foo.cts'), undefined) 36 | t.equal(cjs.getImpTarget({ require: './foo.cts' }), undefined) 37 | t.equal(cjs.getImpTarget('./src/foo.cts'), undefined) 38 | t.equal(cjs.getImpTarget({ import: './foo.mts' }), './foo.mts') 39 | t.equal(cjs.getImpTarget('./src/foo.mts'), undefined) 40 | t.equal(esm.getImpTarget(undefined), undefined) 41 | t.equal(esm.getImpTarget('foo.cts'), undefined) 42 | t.equal(esm.getImpTarget({ require: './foo.cts' }), undefined) 43 | t.equal(esm.getImpTarget('./src/foo.cts'), undefined) 44 | t.equal(esm.getImpTarget({ import: './foo.mts' }), './foo.mts') 45 | t.equal(esm.getImpTarget('./src/foo.mts'), './dist/esm/foo.mjs') 46 | 47 | const p = new Map([ 48 | [ 49 | 'cjs', 50 | Object.assign(new PolyfillSet('commonjs', 'cjs'), { 51 | map: new Map([['./src/fill-cjs.cts', './src/fill.ts']]), 52 | }), 53 | ], 54 | ]) 55 | t.equal(getReqTarget(undefined, p), undefined) 56 | t.equal(getReqTarget('foo.cts', p), 'foo.cts') 57 | t.equal(getReqTarget('foo.mts', p), undefined) 58 | t.equal(getReqTarget({ require: './foo.cts' }, p), './foo.cts') 59 | t.equal(getReqTarget('./src/foo.cts', p), './dist/commonjs/foo.cjs') 60 | t.equal(getReqTarget({ import: './foo.mts' }, p), undefined) 61 | t.equal(getReqTarget('./src/foo.mts', p), undefined) 62 | t.equal(cjs.getReqTarget(undefined, p), undefined) 63 | t.equal(cjs.getReqTarget('foo.cts', p), 'foo.cts') 64 | t.equal(cjs.getReqTarget('foo.mts', p), undefined) 65 | t.equal(cjs.getReqTarget({ require: './foo.cts' }, p), './foo.cts') 66 | t.equal( 67 | cjs.getReqTarget('./src/foo.cts', p), 68 | './dist/commonjs/foo.cjs', 69 | ) 70 | t.equal(cjs.getReqTarget({ import: './foo.mts' }, p), undefined) 71 | t.equal(cjs.getReqTarget('./src/foo.mts', p), undefined) 72 | t.equal(esm.getReqTarget(undefined, p), undefined) 73 | t.equal(esm.getReqTarget('foo.cts', p), 'foo.cts') 74 | t.equal(esm.getReqTarget('foo.mts', p), undefined) 75 | t.equal(esm.getReqTarget({ require: './foo.cts' }, p), './foo.cts') 76 | t.equal(esm.getReqTarget({ require: './foo.mts' }, p), './foo.mts') 77 | t.equal(esm.getReqTarget('./src/foo.cts', p), undefined) 78 | t.equal(esm.getReqTarget({ import: './foo.mts' }, p), undefined) 79 | t.equal(esm.getReqTarget('./src/foo.mts', p), undefined) 80 | t.equal( 81 | cjs.getReqTarget('./src/fill-cjs.cts', p), 82 | './dist/commonjs/fill.js', 83 | ) 84 | 85 | t.test('setting top level main', async t => { 86 | // name, pkg, expect, ok 87 | const cases: [ 88 | string, 89 | { 90 | tshy?: TshyConfig 91 | exports: Record 92 | main?: string 93 | module?: string 94 | types?: string 95 | type?: string 96 | }, 97 | { 98 | main?: string 99 | module?: string 100 | types?: string 101 | }, 102 | boolean, 103 | ][] = [ 104 | [ 105 | 'main defaults true', 106 | { 107 | exports: { 108 | '.': { 109 | require: { types: './r.d.ts', default: './r.js' }, 110 | import: { types: './i.d.ts', default: './i.js' }, 111 | }, 112 | }, 113 | }, 114 | { main: './r.js', types: './r.d.ts' }, 115 | true, 116 | ], 117 | [ 118 | 'main explicit false, removes', 119 | { 120 | tshy: { main: false }, 121 | main: './r.js', 122 | types: './r.d.ts', 123 | exports: { 124 | '.': { 125 | require: { types: './r.d.ts', default: './r.js' }, 126 | import: { types: './i.d.ts', default: './i.js' }, 127 | }, 128 | }, 129 | }, 130 | {}, 131 | true, 132 | ], 133 | [ 134 | 'main explicit true', 135 | { 136 | tshy: { main: true }, 137 | exports: { 138 | '.': { 139 | require: { types: './r.d.ts', default: './r.js' }, 140 | import: { types: './i.d.ts', default: './i.js' }, 141 | }, 142 | }, 143 | }, 144 | { main: './r.js', types: './r.d.ts' }, 145 | true, 146 | ], 147 | [ 148 | 'main commonjs, no types', 149 | { 150 | tshy: { main: true }, 151 | exports: { 152 | '.': { 153 | require: './r.js', 154 | import: { types: './i.d.ts', default: './i.js' }, 155 | }, 156 | }, 157 | }, 158 | { main: './r.js' }, 159 | true, 160 | ], 161 | [ 162 | 'main=false, not set in pj already', 163 | { 164 | tshy: { main: false }, 165 | exports: { 166 | '.': { 167 | require: { types: './r.d.ts', default: './r.js' }, 168 | import: { types: './i.d.ts', default: './i.js' }, 169 | }, 170 | }, 171 | }, 172 | {}, 173 | true, 174 | ], 175 | [ 176 | 'main not set, no commonjs main export', 177 | { 178 | tshy: {}, 179 | exports: { 180 | '.': { 181 | import: { types: './i.d.ts', default: './i.js' }, 182 | }, 183 | }, 184 | }, 185 | {}, 186 | true, 187 | ], 188 | [ 189 | 'main explicit true, no commonjs module', 190 | { 191 | tshy: { main: true }, 192 | exports: { 193 | '.': { 194 | import: { types: './i.d.ts', default: './i.js' }, 195 | }, 196 | }, 197 | }, 198 | {}, 199 | false, 200 | ], 201 | [ 202 | 'invalid main commonjs, no exports', 203 | { 204 | tshy: { main: true }, 205 | exports: {}, 206 | }, 207 | {}, 208 | false, 209 | ], 210 | [ 211 | 'module defaults true', 212 | { 213 | exports: { 214 | '.': { 215 | require: { types: './r.d.ts', default: './r.js' }, 216 | import: { types: './i.d.ts', default: './i.js' }, 217 | }, 218 | }, 219 | }, 220 | { main: './r.js', types: './r.d.ts', module: './i.js' }, 221 | true, 222 | ], 223 | [ 224 | 'module explicit true', 225 | { 226 | tshy: { module: true }, 227 | exports: { 228 | '.': { 229 | require: { types: './r.d.ts', default: './r.js' }, 230 | import: { types: './i.d.ts', default: './i.js' }, 231 | }, 232 | }, 233 | }, 234 | { main: './r.js', types: './r.d.ts', module: './i.js' }, 235 | true, 236 | ], 237 | [ 238 | 'module explicit false, removes', 239 | { 240 | tshy: { module: false }, 241 | main: './r.js', 242 | types: './r.d.ts', 243 | exports: { 244 | '.': { 245 | require: { types: './r.d.ts', default: './r.js' }, 246 | import: { types: './i.d.ts', default: './i.js' }, 247 | }, 248 | }, 249 | }, 250 | { main: './r.js', types: './r.d.ts' }, 251 | true, 252 | ], 253 | [ 254 | 'invalid module esm, no exports', 255 | { 256 | tshy: { module: true }, 257 | exports: {}, 258 | }, 259 | {}, 260 | false, 261 | ], 262 | [ 263 | 'type defaults module', 264 | { 265 | exports: {}, 266 | }, 267 | {}, 268 | true, 269 | ], 270 | [ 271 | 'type=commonjs', 272 | { 273 | type: 'commonjs', 274 | exports: {}, 275 | }, 276 | {}, 277 | true, 278 | ], 279 | [ 280 | 'type=module', 281 | { 282 | type: 'module', 283 | exports: {}, 284 | }, 285 | {}, 286 | true, 287 | ], 288 | [ 289 | 'invalid type', 290 | { 291 | type: 'invalid type', 292 | exports: {}, 293 | }, 294 | {}, 295 | true, 296 | ], 297 | ] 298 | 299 | t.plan(cases.length) 300 | 301 | const exits = t.capture(process, 'exit', () => false).args 302 | const fails: any[][] = [] 303 | const { setMain } = await t.mockImport< 304 | typeof import('../src/exports.js') 305 | >('../src/exports.js', { 306 | '../src/fail.js': { 307 | default: (...a: any[]) => fails.push(a), 308 | }, 309 | }) 310 | for (const [name, pkg, expect, ok] of cases) { 311 | t.test(name, t => { 312 | const { tshy = {}, type } = pkg 313 | const { main } = tshy 314 | setMain(pkg.tshy, pkg as Package & { exports: ExportsSubpaths }) 315 | if (ok) { 316 | t.equal(pkg.main, expect.main) 317 | t.equal(pkg.types, expect.types) 318 | t.equal(pkg.type, type === 'commonjs' ? 'commonjs' : 'module') 319 | if (main === false) t.equal(pkg.tshy?.main, main) 320 | } else { 321 | t.strictSame(exits(), [[1]]) 322 | t.matchSnapshot(fails) 323 | fails.length = 0 324 | } 325 | t.end() 326 | }) 327 | } 328 | }) 329 | 330 | t.test('extra dialects', async t => { 331 | const dialectOptions = [undefined, ['commonjs'], ['esm']] 332 | for (const dialects of dialectOptions) { 333 | t.test(String(dialects), async t => { 334 | const esmDialects = ['deno', 'no-overrides'] 335 | const commonjsDialects = ['blah'] 336 | for (const extras of [true, false]) { 337 | for (const pkgType of ['commonjs', 'module']) { 338 | t.test(`extras=${extras} type=${pkgType}`, async t => { 339 | const { default: extraDialects } = (await t.mockImport( 340 | '../dist/esm/exports.js', 341 | { 342 | '../dist/esm/package.js': { 343 | default: { 344 | type: pkgType, 345 | tshy: { 346 | ...(extras && { 347 | esmDialects, 348 | commonjsDialects, 349 | }), 350 | sourceDialects: ['my-source'], 351 | dialects, 352 | exports: { 353 | '.': './src/index.ts', 354 | './foo': './src/foo.ts', 355 | }, 356 | }, 357 | }, 358 | }, 359 | 360 | '../dist/esm/sources.js': { 361 | default: new Set([ 362 | './src/index.ts', 363 | './src/index-blah.cts', 364 | './src/index-cjs.cts', 365 | './src/index-deno.mts', 366 | './src/foo.ts', 367 | './src/foo-blah.cts', 368 | ]), 369 | }, 370 | }, 371 | )) as typeof import('../dist/esm/exports.js') 372 | t.matchSnapshot(extraDialects) 373 | }) 374 | } 375 | } 376 | }) 377 | } 378 | 379 | t.end() 380 | }) 381 | 382 | t.test('liveDev', async t => { 383 | for (const pkgType of ['commonjs', 'module'] as const) { 384 | t.test(pkgType, async t => { 385 | const pkg: Package = { 386 | name: 'x', 387 | version: '1.2.3', 388 | type: pkgType, 389 | tshy: { 390 | liveDev: true, 391 | dialects: ['commonjs', 'esm'], 392 | esmDialects: ['deno'], 393 | commonjsDialects: ['blah'], 394 | exports: { 395 | '.': './src/index.ts', 396 | './package.json': './package.json', 397 | './foo': './src/foo.mts', 398 | './foo-cjs': './src/foo.cts', 399 | './fill': './src/fill.ts', 400 | }, 401 | }, 402 | } 403 | const getLiveDev = async (t: Test) => { 404 | t.chdir( 405 | t.testdir({ 406 | 'package.json': JSON.stringify(pkg), 407 | src: { 408 | 'index.ts': '', 409 | 'foo.mts': '', 410 | 'foo-deno.mts': '', 411 | 'foo.cts': '', 412 | 'fill.ts': '', 413 | 'fill-cjs.cts': '', 414 | }, 415 | }), 416 | ) 417 | return await t.mockImport( 418 | '../src/exports.js', 419 | { 420 | '../src/config.js': { default: pkg.tshy }, 421 | '../src/package.js': { default: pkg }, 422 | '../src/dialects.js': { default: ['commonjs', 'esm'] }, 423 | '../src/sources.js': { 424 | default: new Set([ 425 | './src/index.ts', 426 | './src/foo.mts', 427 | './src/foo.cts', 428 | './src/fill.ts', 429 | './src/fill-cjs.cts', 430 | ]), 431 | }, 432 | }, 433 | ) 434 | } 435 | 436 | t.test('no envs', async t => { 437 | const ld = await getLiveDev(t) 438 | t.equal(ld.getImpTarget('foo.cts'), undefined) 439 | t.equal(ld.getImpTarget({ require: './foo.cts' }), undefined) 440 | t.equal(ld.getImpTarget('./src/foo.cts'), undefined) 441 | t.equal(ld.getImpTarget({ import: './foo.mts' }), './foo.mts') 442 | t.equal( 443 | ld.getImpTarget('./src/foo.mts'), 444 | './dist/esm/foo.mts', 445 | ) 446 | t.equal( 447 | ld.getImpTarget('./src/index.ts'), 448 | './dist/esm/index.ts', 449 | ) 450 | t.equal(ld.getReqTarget(undefined, p), undefined) 451 | t.equal(ld.getReqTarget('foo.cts', p), 'foo.cts') 452 | t.equal(ld.getReqTarget('foo.mts', p), undefined) 453 | t.equal( 454 | ld.getReqTarget({ require: './foo.cts' }, p), 455 | './foo.cts', 456 | ) 457 | t.equal( 458 | ld.getReqTarget('./src/foo.cts'), 459 | './dist/commonjs/foo.cts', 460 | ) 461 | t.equal( 462 | ld.getReqTarget({ import: './foo.mts' }, p), 463 | undefined, 464 | ) 465 | t.equal(ld.getReqTarget('./src/foo.mts', p), undefined) 466 | t.equal( 467 | ld.getReqTarget('./src/fill-cjs.cts', p), 468 | './dist/commonjs/fill.ts', 469 | ) 470 | t.matchSnapshot(pkg.exports) 471 | delete pkg.exports 472 | t.end() 473 | }) 474 | 475 | for (const c of ['publish', 'pack']) { 476 | t.test(c, async t => { 477 | t.intercept(process, 'env', { 478 | value: { 479 | ...process.env, 480 | npm_command: c, 481 | }, 482 | }) 483 | const ld = await getLiveDev(t) 484 | // should be the same as not having liveDev: true 485 | t.equal(ld.getImpTarget('foo.cts'), undefined) 486 | t.equal( 487 | ld.getImpTarget({ require: './foo.cts' }), 488 | undefined, 489 | ) 490 | t.equal(ld.getImpTarget('./src/foo.cts'), undefined) 491 | t.equal( 492 | ld.getImpTarget({ import: './foo.mts' }), 493 | './foo.mts', 494 | ) 495 | t.equal( 496 | ld.getImpTarget('./src/foo.mts'), 497 | './dist/esm/foo.mjs', 498 | ) 499 | t.equal( 500 | ld.getImpTarget('./src/index.ts'), 501 | './dist/esm/index.js', 502 | ) 503 | t.equal(ld.getReqTarget(undefined, p), undefined) 504 | t.equal(ld.getReqTarget('foo.cts', p), 'foo.cts') 505 | t.equal(ld.getReqTarget('foo.mts', p), undefined) 506 | t.equal( 507 | ld.getReqTarget({ require: './foo.cts' }, p), 508 | './foo.cts', 509 | ) 510 | t.equal( 511 | ld.getReqTarget('./src/foo.cts'), 512 | './dist/commonjs/foo.cjs', 513 | ) 514 | t.equal( 515 | ld.getReqTarget({ import: './foo.mts' }, p), 516 | undefined, 517 | ) 518 | t.equal(ld.getReqTarget('./src/foo.mts', p), undefined) 519 | t.equal( 520 | ld.getReqTarget('./src/fill-cjs.cts', p), 521 | './dist/commonjs/fill.js', 522 | ) 523 | t.matchSnapshot(pkg.exports) 524 | delete pkg.exports 525 | }) 526 | } 527 | t.end() 528 | }) 529 | } 530 | }) 531 | -------------------------------------------------------------------------------- /test/fail.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | 3 | t.capture(process, 'exit', (...a: any[]) => 4 | calls.push(['process.exit', a]), 5 | ) 6 | const calls: [string, any[]][] = [] 7 | 8 | const { default: fail } = (await t.mockImport('../dist/esm/fail.js', { 9 | '../dist/esm/console.js': { 10 | error: (...a: any[]) => calls.push(['console.error', a]), 11 | print: (...a: any[]) => calls.push(['console.print', a]), 12 | }, 13 | })) as typeof import('../dist/esm/fail.js') 14 | 15 | fail('no error') 16 | fail('with error', { message: 'error message' } as unknown as Error) 17 | t.matchSnapshot(calls) 18 | -------------------------------------------------------------------------------- /test/fixtures/basic-custom-project/.tshy/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.custom.json", 3 | "compilerOptions": { 4 | "rootDir": "../src", 5 | "module": "nodenext", 6 | "moduleResolution": "nodenext" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/basic-custom-project/.tshy/commonjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.cts", 6 | "../src/**/*.tsx", 7 | "../src/**/*.json" 8 | ], 9 | "exclude": [ 10 | "../src/**/*.mts", 11 | "../src/package.json" 12 | ], 13 | "compilerOptions": { 14 | "outDir": "../.tshy-build/commonjs" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/basic-custom-project/.tshy/esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.mts", 6 | "../src/**/*.tsx", 7 | "../src/**/*.json" 8 | ], 9 | "exclude": [ 10 | "../src/package.json" 11 | ], 12 | "compilerOptions": { 13 | "outDir": "../.tshy-build/esm" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/basic-custom-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@my/package", 3 | "type": "module", 4 | "tshy": { 5 | "exports": { 6 | "./package.json": "./package.json", 7 | ".": "./src/index.ts" 8 | }, 9 | "project": "./tsconfig.custom.json" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/basic-custom-project/src/index.ts: -------------------------------------------------------------------------------- 1 | export const test = () => { 2 | console.log('hello') 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/basic-custom-project/tsconfig.custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "inlineSources": true, 8 | "jsx": "react", 9 | "module": "nodenext", 10 | "moduleResolution": "nodenext", 11 | "noUncheckedIndexedAccess": true, 12 | "resolveJsonModule": true, 13 | "skipLibCheck": true, 14 | "sourceMap": true, 15 | "strict": true, 16 | "target": "es2022" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/basic-imports-only-deps/.tshy/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "../src", 5 | "module": "nodenext", 6 | "moduleResolution": "nodenext" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/basic-imports-only-deps/.tshy/commonjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.cts", 6 | "../src/**/*.tsx", 7 | "../src/**/*.json" 8 | ], 9 | "exclude": [ 10 | "../src/**/*.mts", 11 | "../src/package.json" 12 | ], 13 | "compilerOptions": { 14 | "outDir": "../.tshy-build/commonjs" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/basic-imports-only-deps/.tshy/esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.mts", 6 | "../src/**/*.tsx", 7 | "../src/**/*.json" 8 | ], 9 | "exclude": [ 10 | "../src/package.json" 11 | ], 12 | "compilerOptions": { 13 | "outDir": "../.tshy-build/esm" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/basic-imports-only-deps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@my/package", 3 | "imports": { 4 | "#ri": "resolve-import" 5 | }, 6 | "tshy": { 7 | "exports": { 8 | "./package.json": "./package.json", 9 | ".": "./src/index.ts" 10 | } 11 | }, 12 | "exports": { 13 | "./package.json": "./package.json", 14 | ".": { 15 | "import": { 16 | "types": "./dist/esm/index.d.ts", 17 | "default": "./dist/esm/index.js" 18 | }, 19 | "require": { 20 | "types": "./dist/commonjs/index.d.ts", 21 | "default": "./dist/commonjs/index.js" 22 | } 23 | } 24 | }, 25 | "main": "./dist/commonjs/index.js", 26 | "types": "./dist/commonjs/index.d.ts", 27 | "type": "module" 28 | } 29 | -------------------------------------------------------------------------------- /test/fixtures/basic-imports-only-deps/src/index.ts: -------------------------------------------------------------------------------- 1 | export const test = () => { 2 | console.log('hello') 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/basic-imports-only-deps/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "inlineSources": true, 8 | "jsx": "react", 9 | "module": "nodenext", 10 | "moduleResolution": "nodenext", 11 | "noUncheckedIndexedAccess": true, 12 | "resolveJsonModule": true, 13 | "skipLibCheck": true, 14 | "sourceMap": true, 15 | "strict": true, 16 | "target": "es2022" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/basic/.tshy/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "../src", 5 | "module": "nodenext", 6 | "moduleResolution": "nodenext" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/basic/.tshy/commonjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.cts", 6 | "../src/**/*.tsx", 7 | "../src/**/*.json" 8 | ], 9 | "exclude": [ 10 | "../src/**/*.mts", 11 | "../src/package.json" 12 | ], 13 | "compilerOptions": { 14 | "outDir": "../.tshy-build/commonjs" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/basic/.tshy/esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.mts", 6 | "../src/**/*.tsx", 7 | "../src/**/*.json" 8 | ], 9 | "exclude": [ 10 | "../src/package.json" 11 | ], 12 | "compilerOptions": { 13 | "outDir": "../.tshy-build/esm" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@my/package", 3 | "type": "module", 4 | "tshy": { 5 | "exports": { 6 | "./package.json": "./package.json", 7 | ".": "./src/index.ts" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/basic/src/index.ts: -------------------------------------------------------------------------------- 1 | export const test = () => { 2 | console.log('hello') 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/basic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "inlineSources": true, 8 | "jsx": "react", 9 | "module": "nodenext", 10 | "moduleResolution": "nodenext", 11 | "noUncheckedIndexedAccess": true, 12 | "resolveJsonModule": true, 13 | "skipLibCheck": true, 14 | "sourceMap": true, 15 | "strict": true, 16 | "target": "es2022" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/.tshy/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "../src", 5 | "module": "nodenext", 6 | "moduleResolution": "nodenext" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/.tshy/commonjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.cts", 6 | "../src/**/*.tsx", 7 | "../src/**/*.json" 8 | ], 9 | "exclude": [ 10 | "../src/**/*.mts", 11 | "../src/package.json" 12 | ], 13 | "compilerOptions": { 14 | "outDir": "../.tshy-build/commonjs" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/.tshy/esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.mts", 6 | "../src/**/*.tsx", 7 | "../src/**/*.json" 8 | ], 9 | "exclude": [ 10 | "../src/package.json" 11 | ], 12 | "compilerOptions": { 13 | "outDir": "../.tshy-build/esm" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/lib/foo-browser.js: -------------------------------------------------------------------------------- 1 | globalThis.foo = 'as a global' 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/lib/foo-global.d.ts: -------------------------------------------------------------------------------- 1 | declare const foo: string 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/lib/foo-import.mjs: -------------------------------------------------------------------------------- 1 | export const foo = 'import, not node' 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/lib/foo-node.cjs: -------------------------------------------------------------------------------- 1 | exports.foo = 'node cjs' 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/lib/foo-node.mjs: -------------------------------------------------------------------------------- 1 | export const foo = 'node esm' 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/lib/foo-require.cjs: -------------------------------------------------------------------------------- 1 | exports.foo = 'require, not node' 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/lib/foo.d.cts: -------------------------------------------------------------------------------- 1 | export declare const foo: string 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/lib/foo.d.mts: -------------------------------------------------------------------------------- 1 | export declare const foo: string 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/lib/foo.d.ts: -------------------------------------------------------------------------------- 1 | export declare const foo: string 2 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@my/package", 3 | "type": "module", 4 | "imports": { 5 | "#xp": "xyz/package.json", 6 | "#ri": "resolve-import", 7 | "#ri/*": "resolve-import/*", 8 | "#m/*": "./lib/*.mjs", 9 | "#c/*": "./lib/*.cjs", 10 | "#fs": "node:fs", 11 | "#root": "./root.mjs", 12 | "#foo": { 13 | "node": { 14 | "import": [ 15 | { 16 | "types": "./lib/foo.d.mts" 17 | }, 18 | "./lib/foo-node.mjs" 19 | ], 20 | "require": [ 21 | { 22 | "types": "./lib/foo.d.cts" 23 | }, 24 | "./lib/foo-node.cjs" 25 | ] 26 | }, 27 | "require": [ 28 | { 29 | "types": "./lib/foo.d.cts" 30 | }, 31 | "./lib/foo-require.cjs" 32 | ], 33 | "import": [ 34 | { 35 | "types": "./lib/foo.d.mts" 36 | }, 37 | "./lib/foo-import.mjs" 38 | ], 39 | "types": "./lib/foo-global.d.ts", 40 | "default": "./lib/foo-browser.js" 41 | } 42 | }, 43 | "tshy": { 44 | "exports": { 45 | "./foo": { 46 | "node": { 47 | "import": [ 48 | { 49 | "types": "./lib/foo.d.mts" 50 | }, 51 | "./lib/foo-node.mjs" 52 | ], 53 | "require": [ 54 | { 55 | "types": "./lib/foo.d.cts" 56 | }, 57 | "./lib/foo-node.cjs" 58 | ] 59 | }, 60 | "require": [ 61 | { 62 | "types": "./lib/foo.d.cts" 63 | }, 64 | "./lib/foo-require.cjs" 65 | ], 66 | "import": [ 67 | { 68 | "types": "./lib/foo.d.mts" 69 | }, 70 | "./lib/foo-import.mjs" 71 | ], 72 | "types": "./lib/foo-global.d.ts", 73 | "default": "./lib/foo-browser.js" 74 | }, 75 | "./package.json": "./package.json", 76 | ".": "./src/index.ts" 77 | } 78 | }, 79 | "exports": { 80 | "./foo": { 81 | "node": { 82 | "import": [ 83 | { 84 | "types": "./lib/foo.d.mts" 85 | }, 86 | "./lib/foo-node.mjs" 87 | ], 88 | "require": [ 89 | { 90 | "types": "./lib/foo.d.cts" 91 | }, 92 | "./lib/foo-node.cjs" 93 | ] 94 | }, 95 | "require": [ 96 | { 97 | "types": "./lib/foo.d.cts" 98 | }, 99 | "./lib/foo-require.cjs" 100 | ], 101 | "import": [ 102 | { 103 | "types": "./lib/foo.d.mts" 104 | }, 105 | "./lib/foo-import.mjs" 106 | ], 107 | "types": "./lib/foo-global.d.ts", 108 | "default": "./lib/foo-browser.js" 109 | }, 110 | "./package.json": "./package.json", 111 | ".": { 112 | "import": { 113 | "types": "./dist/esm/index.d.ts", 114 | "default": "./dist/esm/index.js" 115 | }, 116 | "require": { 117 | "types": "./dist/commonjs/index.d.ts", 118 | "default": "./dist/commonjs/index.js" 119 | } 120 | } 121 | }, 122 | "scripts": { 123 | "preinstall": "node -e \"import(process.argv[1]).catch(()=>{})\" dist/.tshy-link-imports.mjs" 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/root.cjs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isaacs/tshy/fcb9003f58eac648574c660cb701624008d3c348/test/fixtures/imports-with-star/root.cjs -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/src/g.ts: -------------------------------------------------------------------------------- 1 | declare var global: { foo: string } 2 | global.foo = 'global foo' 3 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/src/index.ts: -------------------------------------------------------------------------------- 1 | import { foo } from '#foo' 2 | export const test = async () => { 3 | await import('@my/package/foo').then(({ foo }) => 4 | console.log('pkg exports', foo) 5 | ) 6 | console.log(foo) 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/imports-with-star/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@my/package/*": [ 5 | "./lib/*.js" 6 | ] 7 | }, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "inlineSources": true, 13 | "jsx": "react", 14 | "module": "nodenext", 15 | "moduleResolution": "nodenext", 16 | "noUncheckedIndexedAccess": true, 17 | "resolveJsonModule": true, 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strict": true, 21 | "target": "es2022" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/fixtures/imports/.tshy/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "../src", 5 | "module": "nodenext", 6 | "moduleResolution": "nodenext" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/imports/.tshy/commonjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.cts", 6 | "../src/**/*.tsx", 7 | "../src/**/*.json" 8 | ], 9 | "exclude": [ 10 | "../src/**/*.mts", 11 | "../src/package.json" 12 | ], 13 | "compilerOptions": { 14 | "outDir": "../.tshy-build/commonjs" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/imports/.tshy/esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.mts", 6 | "../src/**/*.tsx", 7 | "../src/**/*.json" 8 | ], 9 | "exclude": [ 10 | "../src/package.json" 11 | ], 12 | "compilerOptions": { 13 | "outDir": "../.tshy-build/esm" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/imports/lib/foo-browser.js: -------------------------------------------------------------------------------- 1 | globalThis.foo = 'as a global' 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/lib/foo-global.d.ts: -------------------------------------------------------------------------------- 1 | declare const foo: string 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/lib/foo-import.mjs: -------------------------------------------------------------------------------- 1 | export const foo = 'import, not node' 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/lib/foo-node.cjs: -------------------------------------------------------------------------------- 1 | exports.foo = 'node cjs' 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/lib/foo-node.mjs: -------------------------------------------------------------------------------- 1 | export const foo = 'node esm' 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/lib/foo-require.cjs: -------------------------------------------------------------------------------- 1 | exports.foo = 'require, not node' 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/lib/foo.d.cts: -------------------------------------------------------------------------------- 1 | export declare const foo: string 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/lib/foo.d.mts: -------------------------------------------------------------------------------- 1 | export declare const foo: string 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/lib/foo.d.ts: -------------------------------------------------------------------------------- 1 | export declare const foo: string 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@my/package", 3 | "type": "module", 4 | "imports": { 5 | "#xp": "xyz/package.json", 6 | "#ri": "resolve-import", 7 | "#fs": "node:fs", 8 | "#root": "./root.mjs", 9 | "#foo": { 10 | "node": { 11 | "import": [ 12 | { 13 | "types": "./lib/foo.d.mts" 14 | }, 15 | "./lib/foo-node.mjs" 16 | ], 17 | "require": [ 18 | { 19 | "types": "./lib/foo.d.cts" 20 | }, 21 | "./lib/foo-node.cjs" 22 | ] 23 | }, 24 | "require": [ 25 | { 26 | "types": "./lib/foo.d.cts" 27 | }, 28 | "./lib/foo-require.cjs" 29 | ], 30 | "import": [ 31 | { 32 | "types": "./lib/foo.d.mts" 33 | }, 34 | "./lib/foo-import.mjs" 35 | ], 36 | "types": "./lib/foo-global.d.ts", 37 | "default": "./lib/foo-browser.js" 38 | } 39 | }, 40 | "tshy": { 41 | "exports": { 42 | "./foo": { 43 | "node": { 44 | "import": [ 45 | { 46 | "types": "./lib/foo.d.mts" 47 | }, 48 | "./lib/foo-node.mjs" 49 | ], 50 | "require": [ 51 | { 52 | "types": "./lib/foo.d.cts" 53 | }, 54 | "./lib/foo-node.cjs" 55 | ] 56 | }, 57 | "require": [ 58 | { 59 | "types": "./lib/foo.d.cts" 60 | }, 61 | "./lib/foo-require.cjs" 62 | ], 63 | "import": [ 64 | { 65 | "types": "./lib/foo.d.mts" 66 | }, 67 | "./lib/foo-import.mjs" 68 | ], 69 | "types": "./lib/foo-global.d.ts", 70 | "default": "./lib/foo-browser.js" 71 | }, 72 | "./package.json": "./package.json", 73 | ".": "./src/index.ts" 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/fixtures/imports/root.cjs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isaacs/tshy/fcb9003f58eac648574c660cb701624008d3c348/test/fixtures/imports/root.cjs -------------------------------------------------------------------------------- /test/fixtures/imports/src/g.ts: -------------------------------------------------------------------------------- 1 | declare var global: { foo: string } 2 | global.foo = 'global foo' 3 | -------------------------------------------------------------------------------- /test/fixtures/imports/src/index.ts: -------------------------------------------------------------------------------- 1 | import { foo } from '#foo' 2 | export const test = async () => { 3 | await import('@my/package/foo').then(({ foo }) => 4 | console.log('pkg exports', foo) 5 | ) 6 | console.log(foo) 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/imports/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@my/package/*": [ 5 | "./lib/*.js" 6 | ] 7 | }, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "inlineSources": true, 13 | "jsx": "react", 14 | "module": "nodenext", 15 | "moduleResolution": "nodenext", 16 | "noUncheckedIndexedAccess": true, 17 | "resolveJsonModule": true, 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strict": true, 21 | "target": "es2022" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/if-exist.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'fs' 2 | import t from 'tap' 3 | import ifExist from '../src/if-exist.js' 4 | 5 | const dir = t.testdir({ 6 | a: 'a', 7 | b: 'b', 8 | }) 9 | 10 | ifExist.unlink(dir + '/x') 11 | ifExist.rename(dir + '/x', dir + '/z') 12 | 13 | t.strictSame(new Set(readdirSync(dir)), new Set(['a', 'b'])) 14 | 15 | ifExist.unlink(dir + '/a') 16 | ifExist.rename(dir + '/b', dir + '/c') 17 | 18 | t.strictSame(readdirSync(dir), ['c']) 19 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | process.env.FORCE_COLOR = '0' 3 | const mockConsole = { 4 | log: () => {}, 5 | debug: () => {}, 6 | } 7 | const logs = t.capture(mockConsole, 'log').args 8 | const debug = t.capture(mockConsole, 'debug').args 9 | 10 | let buildCalled = false 11 | await t.mockImport('../dist/esm/index.js', { 12 | '../dist/esm/console.js': mockConsole, 13 | '../dist/esm/build.js': () => (buildCalled = true), 14 | }) 15 | t.equal(buildCalled, true) 16 | t.match(debug(), [ 17 | ['building', process.cwd()], 18 | ['tshy config', Object], 19 | ['exports', Object], 20 | ]) 21 | t.strictSame(logs(), [['success!']]) 22 | 23 | t.test('print help message', async t => { 24 | t.intercept(process, 'argv', { 25 | value: [process.execPath, 'index.js', '--help'], 26 | }) 27 | let usageCalled: string | undefined = undefined 28 | await t.mockImport('../dist/esm/index.js', { 29 | '../dist/esm/usage.js': { 30 | default: (n?: string) => (usageCalled = n), 31 | }, 32 | }) 33 | t.equal(usageCalled, undefined) 34 | }) 35 | 36 | t.test('print usage and error for unknown arg', async t => { 37 | t.intercept(process, 'argv', { 38 | value: [process.execPath, 'index.js', 'xyz'], 39 | }) 40 | let usageCalled: string | undefined = undefined 41 | await t.mockImport('../dist/esm/index.js', { 42 | '../dist/esm/usage.js': { 43 | default: (n?: string) => (usageCalled = n), 44 | }, 45 | }) 46 | t.equal(usageCalled, `Unknown argument: xyz`) 47 | }) 48 | 49 | t.test('watch if --watch specified', async t => { 50 | t.intercept(process, 'argv', { 51 | value: [process.execPath, 'index.js', '--watch'], 52 | }) 53 | let watchCalled = false 54 | await t.mockImport('../dist/esm/index.js', { 55 | '../dist/esm/watch.js': { 56 | default: () => (watchCalled = true), 57 | }, 58 | }) 59 | t.equal(watchCalled, true) 60 | }) 61 | -------------------------------------------------------------------------------- /test/package.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | 3 | t.test('load package successfully', async t => { 4 | const { default: pkg } = await t.mockImport( 5 | '../dist/esm/package.js', 6 | ) 7 | t.equal(pkg.name, 'tshy') 8 | t.equal(pkg.type, 'module') 9 | }) 10 | 11 | t.test('respect package.type', async t => { 12 | t.chdir(t.testdir({ 13 | 'package.json': JSON.stringify({ type: 'commonjs' }), 14 | })) 15 | const { default: pkg } = await t.mockImport( 16 | '../dist/esm/package.js', 17 | ) 18 | t.equal(pkg.type, 'commonjs') 19 | }) 20 | 21 | t.test('unsuccessfully fails build', async t => { 22 | const exits = t.capture(process, 'exit').args 23 | t.chdir(t.testdir()) 24 | let failed = false 25 | await t.mockImport('../dist/esm/package.js', { 26 | '../dist/esm/fail.js': () => (failed = true), 27 | }) 28 | t.strictSame(exits(), [[1]]) 29 | t.equal(failed, true) 30 | }) 31 | 32 | t.test('fail if the package.json is not an object', async t => { 33 | const exits = t.capture(process, 'exit').args 34 | t.chdir( 35 | t.testdir({ 36 | 'package.json': '[null, 1, "asdf"]', 37 | }), 38 | ) 39 | let failed = false 40 | await t.mockImport('../dist/esm/package.js', { 41 | '../dist/esm/fail.js': () => (failed = true), 42 | }) 43 | t.strictSame(exits(), [[1]]) 44 | t.equal(failed, true) 45 | }) 46 | -------------------------------------------------------------------------------- /test/polyfills.ts: -------------------------------------------------------------------------------- 1 | import { inspect } from 'node:util' 2 | import t from 'tap' 3 | 4 | const polyfills = await t.mockImport('../dist/esm/polyfills.js', { 5 | '../dist/esm/package.js': { 6 | default: { 7 | tshy: { 8 | esmDialects: ['deno', 'none-found'], 9 | commonjsDialects: ['blah'], 10 | }, 11 | }, 12 | }, 13 | '../dist/esm/sources.js': new Set([ 14 | './src/foo.ts', 15 | './src/foo-cjs.cts', 16 | './src/foo-blah.cts', 17 | './src/foo-deno.mts', 18 | './src/no-poly.ts', 19 | './src/no-poly.cts', 20 | './src/no-poly.mts', 21 | './src/jsx.tsx', 22 | './src/jsx-cjs.cts', 23 | './src/poly-without-target-cjs.cts', 24 | ]), 25 | }) 26 | 27 | t.match( 28 | polyfills.default, 29 | new Map([ 30 | [ 31 | 'cjs', 32 | { 33 | map: new Map([ 34 | ['./src/foo-cjs.cts', './src/foo.ts'], 35 | ['./src/jsx-cjs.cts', './src/jsx.tsx'], 36 | ]), 37 | }, 38 | ], 39 | [ 40 | 'deno', 41 | { 42 | map: new Map([['./src/foo-deno.mts', './src/foo.ts']]), 43 | }, 44 | ], 45 | [ 46 | 'blah', 47 | { 48 | map: new Map([['./src/foo-blah.cts', './src/foo.ts']]), 49 | }, 50 | ], 51 | ]), 52 | ) 53 | 54 | t.matchSnapshot(inspect(polyfills.default)) 55 | -------------------------------------------------------------------------------- /test/prevent-verbatim-module-syntax.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import t from 'tap' 3 | import readTypescriptConfig from '../src/read-typescript-config.js' 4 | import preventVerbatimModuleSyntax from '../src/prevent-verbatim-module-syntax.js' 5 | 6 | chalk.level = 3 7 | 8 | const exits = t.capture(process, 'exit').args 9 | const errs = t.capture(console, 'error').args 10 | const logs = t.capture(console, 'log').args 11 | 12 | t.test('not set, no worries', t => { 13 | preventVerbatimModuleSyntax() 14 | t.strictSame(exits(), []) 15 | t.strictSame(errs(), []) 16 | t.strictSame(logs(), []) 17 | t.end() 18 | }) 19 | 20 | t.test('is set, many worries', t => { 21 | readTypescriptConfig().options.verbatimModuleSyntax = true 22 | preventVerbatimModuleSyntax() 23 | t.strictSame(exits(), [[1]]) 24 | t.matchSnapshot( 25 | errs() 26 | .map(s => s.join('')) 27 | .join('\n'), 28 | ) 29 | t.strictSame(logs(), []) 30 | t.end() 31 | }) 32 | -------------------------------------------------------------------------------- /test/read-typescript-config.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | import readTypescriptConfig from '../src/read-typescript-config.js' 3 | 4 | const config = readTypescriptConfig() 5 | t.equal(config, readTypescriptConfig(), 'cached') 6 | -------------------------------------------------------------------------------- /test/resolve-export.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | import { resolveExport } from '../src/resolve-export.js' 3 | 4 | type C = 'import' | 'require' | 'types' 5 | const cases: [ 6 | exp: any, 7 | m: C | C[], 8 | expect: string | null | undefined, 9 | ][] = [ 10 | [null, 'import', null], 11 | [null, 'require', null], 12 | [1, 'import', undefined], 13 | [1, 'require', undefined], 14 | ['foo.xyz', 'import', 'foo.xyz'], 15 | ['foo.xyz', 'require', 'foo.xyz'], 16 | [{ require: 'x.js' }, 'require', 'x.js'], 17 | [{ require: 'x.js' }, 'import', undefined], 18 | [[{ require: 'x.js' }], 'require', 'x.js'], 19 | [[{ require: 'x.js' }], 'import', undefined], 20 | [{ import: 'x.js' }, 'import', 'x.js'], 21 | [{ import: 'x.js' }, 'require', undefined], 22 | [[{ import: 'x.js' }], 'import', 'x.js'], 23 | [[{ import: 'x.js' }], 'require', undefined], 24 | [{ node: 'x.js' }, 'import', 'x.js'], 25 | [{ node: 'x.js' }, 'require', 'x.js'], 26 | [{ default: 'x.js' }, 'import', 'x.js'], 27 | [{ default: 'x.js' }, 'require', 'x.js'], 28 | [[{ require: 'r.js' }, 'd.js'], 'require', 'r.js'], 29 | [[{ require: 'r.js' }, 'd.js'], 'import', 'd.js'], 30 | [ 31 | [{ require: [{ types: 'r.d.ts' }, 'r.js'] }, 'd.js'], 32 | 'import', 33 | 'd.js', 34 | ], 35 | [ 36 | [{ require: { types: 'r.d.ts', default: 'r.js' } }, 'd.js'], 37 | ['require', 'types'], 38 | 'r.d.ts', 39 | ], 40 | [ 41 | [{ require: { default: 'r.js', types: 'r.d.ts' } }, 'd.js'], 42 | ['require', 'types'], 43 | 'r.js', 44 | ], 45 | [ 46 | [{ require: { default: null, types: 'r.d.ts' } }, 'd.js'], 47 | ['require', 'types'], 48 | null, 49 | ], 50 | ] 51 | 52 | t.plan(cases.length) 53 | for (const [exp, m, expect] of cases) { 54 | const cond = Array.isArray(m) ? m : [m] 55 | t.equal( 56 | resolveExport(exp, cond), 57 | expect, 58 | `${m} ${JSON.stringify(exp)}`, 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /test/self-dep.ts: -------------------------------------------------------------------------------- 1 | import { posix as path, resolve } from 'node:path' 2 | import t from 'tap' 3 | import { Package } from '../src/types.js' 4 | 5 | let mkdirpMade: undefined | string = undefined 6 | const mkdirp = { 7 | mkdirpSync: () => mkdirpMade, 8 | } 9 | const mkdirpCalls = t.capture( 10 | mkdirp, 11 | 'mkdirpSync', 12 | mkdirp.mkdirpSync, 13 | ).args 14 | 15 | import * as FS from 'node:fs' 16 | let symlinkThrow: Error | undefined = undefined 17 | let symlinkThrowAgain: Error | undefined = undefined 18 | const fs = { 19 | symlinkSync: () => { 20 | if (symlinkThrow) { 21 | try { 22 | throw symlinkThrow 23 | } finally { 24 | symlinkThrow = symlinkThrowAgain 25 | symlinkThrowAgain = undefined 26 | } 27 | } 28 | }, 29 | readlinkSync: FS.readlinkSync, 30 | } 31 | const symlinkCalls = t.capture(fs, 'symlinkSync', fs.symlinkSync).args 32 | 33 | const rimraf = { 34 | rimrafSync: () => {}, 35 | } 36 | const rimrafCalls = t.capture( 37 | rimraf, 38 | 'rimrafSync', 39 | rimraf.rimrafSync, 40 | ).args 41 | 42 | const { link, unlink } = (await t.mockImport( 43 | '../dist/esm/self-link.js', 44 | { mkdirp, fs, rimraf, path }, 45 | )) as typeof import('../dist/esm/self-link.js') 46 | 47 | t.test('no pkg name, nothing to do', t => { 48 | link({} as Package, 'some/path') 49 | unlink({} as Package, 'some/path') 50 | t.strictSame(symlinkCalls(), [], 'no symlinks') 51 | t.strictSame(rimrafCalls(), [], 'no rimrafs') 52 | t.strictSame(mkdirpCalls(), [], 'no mkdirps') 53 | t.end() 54 | }) 55 | 56 | t.test('no selfLink, nothing to do', t => { 57 | link( 58 | { name: 'name', tshy: { selfLink: false } } as Package, 59 | 'some/path', 60 | ) 61 | unlink( 62 | { name: 'name', tshy: { selfLink: false } } as Package, 63 | 'some/path', 64 | ) 65 | t.strictSame(symlinkCalls(), [], 'no symlinks') 66 | t.strictSame(rimrafCalls(), [], 'no rimrafs') 67 | t.strictSame(mkdirpCalls(), [], 'no mkdirps') 68 | t.end() 69 | }) 70 | 71 | t.test('try one more time if it fails', t => { 72 | symlinkThrow = new Error('eexist') 73 | link({ name: 'name', version: '1.2.3' }, 'some/path') 74 | t.matchSnapshot(symlinkCalls(), 'symlinks') 75 | t.matchSnapshot(rimrafCalls(), 'rimrafs') 76 | t.matchSnapshot(mkdirpCalls(), 'mkdirps') 77 | t.end() 78 | }) 79 | 80 | t.test('throw both times, but accept if best-effort', t => { 81 | symlinkThrow = new Error('EPERM') 82 | symlinkThrowAgain = new Error('EPERM') 83 | link({ name: 'name', version: '1.2.3' }, 'some/path') 84 | t.matchSnapshot(symlinkCalls(), 'symlinks') 85 | t.matchSnapshot(rimrafCalls(), 'rimrafs') 86 | t.matchSnapshot(mkdirpCalls(), 'mkdirps') 87 | t.end() 88 | }) 89 | 90 | t.test('throw both times, but self-link is required', t => { 91 | symlinkThrow = new Error('EPERM') 92 | symlinkThrowAgain = new Error('EPERM') 93 | t.throws(() => 94 | link( 95 | { name: 'name', version: '1.2.3', tshy: { selfLink: true } }, 96 | 'some/path', 97 | ), 98 | ) 99 | t.matchSnapshot(symlinkCalls(), 'symlinks') 100 | t.matchSnapshot(rimrafCalls(), 'rimrafs') 101 | t.matchSnapshot(mkdirpCalls(), 'mkdirps') 102 | t.end() 103 | }) 104 | 105 | t.test('link, but no dirs made', t => { 106 | link({ name: 'name', version: '1.2.3' }, 'some/path') 107 | unlink({ name: 'name', version: '1.2.3' }, 'some/path') 108 | t.matchSnapshot(symlinkCalls(), 'symlinks') 109 | t.matchSnapshot(rimrafCalls(), 'rimrafs') 110 | t.matchSnapshot(mkdirpCalls(), 'mkdirps') 111 | t.end() 112 | }) 113 | 114 | t.test('made dir, clean up', t => { 115 | mkdirpMade = 'some' 116 | link({ name: 'name', version: '1.2.3' }, 'some/path') 117 | unlink({ name: 'name', version: '1.2.3' }, 'some/path') 118 | t.matchSnapshot(symlinkCalls(), 'symlinks') 119 | t.matchSnapshot(rimrafCalls(), 'rimrafs') 120 | t.matchSnapshot(mkdirpCalls(), 'mkdirps') 121 | t.end() 122 | }) 123 | 124 | t.test('already in node_modules, do not create link', t => { 125 | const readlinkCalls = t.capture( 126 | fs, 127 | 'readlinkSync', 128 | fs.readlinkSync, 129 | ).args 130 | 131 | const dir = t.testdir({ 132 | node_modules: { 133 | installed: { src: {} }, 134 | linked: t.fixture('symlink', '../packages/linked'), 135 | '@scope': { 136 | linked: t.fixture('symlink', '../../packages/scopelinked'), 137 | installed: { src: {} }, 138 | }, 139 | }, 140 | packages: { 141 | linked: { src: {} }, 142 | scopelinked: { src: {} }, 143 | }, 144 | }) 145 | 146 | const cases: [string, string][] = [ 147 | ['installed', 'node_modules/installed'], 148 | ['@scope/installed', 'node_modules/@scope/installed'], 149 | ['linked', 'node_modules/linked'], 150 | ['@scope/linked', 'node_modules/@scope/linked'], 151 | ['linked', 'packages/linked'], 152 | ['@scope/linked', 'packages/scopelinked'], 153 | ] 154 | 155 | t.plan(cases.length) 156 | 157 | for (const [name, d] of cases) { 158 | t.test(d, async t => { 159 | // need a separate import for each test, because this gets cached 160 | // to save extra readlink and walkUp calls. 161 | const { link, unlink } = (await t.mockImport( 162 | '../dist/esm/self-link.js', 163 | { mkdirp, fs, rimraf }, 164 | )) as typeof import('../dist/esm/self-link.js') 165 | t.chdir(resolve(dir, d)) 166 | link({ name, version: '1.2.3' }, 'src') 167 | unlink({ name, version: '1.2.3' }, 'src') 168 | const rl = readlinkCalls() 169 | if (name.endsWith('linked')) { 170 | t.strictSame( 171 | rl.pop(), 172 | [resolve(dir, 'node_modules', name)], 173 | 'found link', 174 | ) 175 | } else { 176 | t.strictSame(rl, [], 'did not need to check for links') 177 | } 178 | t.strictSame(symlinkCalls(), []) 179 | t.strictSame(mkdirpCalls(), []) 180 | t.strictSame(rimrafCalls(), []) 181 | t.end() 182 | }) 183 | } 184 | }) 185 | -------------------------------------------------------------------------------- /test/set-folder-dialect.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | import { resolve } from 'path' 3 | import t from 'tap' 4 | import setFolderDialect from '../src/set-folder-dialect.js' 5 | 6 | const dir = t.testdir() 7 | 8 | const expect = (n?: string) => { 9 | if (!n) { 10 | t.throws(() => readFileSync(resolve(dir, 'package.json'), 'utf8')) 11 | } else { 12 | const expect = JSON.stringify({ type: n }, null, 2) + '\n' 13 | const actual = readFileSync(resolve(dir, 'package.json'), 'utf8') 14 | t.equal(actual, expect) 15 | } 16 | } 17 | 18 | setFolderDialect(dir, 'esm') 19 | expect('module') 20 | setFolderDialect(dir) 21 | expect() 22 | setFolderDialect(dir, 'commonjs') 23 | expect('commonjs') 24 | -------------------------------------------------------------------------------- /test/sources.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | 3 | t.chdir( 4 | t.testdir({ 5 | src: { 6 | dir: { 7 | 'file.ts': '', 8 | link: t.fixture('symlink', './file.ts'), 9 | }, 10 | 'file.ts': '', 11 | }, 12 | }), 13 | ) 14 | 15 | const { default: sources } = await import('../dist/esm/sources.js') 16 | t.strictSame(sources, new Set(['./src/dir/file.ts', './src/file.ts'])) 17 | -------------------------------------------------------------------------------- /test/tsconfig.ts: -------------------------------------------------------------------------------- 1 | import { 2 | readFileSync, 3 | renameSync, 4 | statSync, 5 | unlinkSync, 6 | writeFileSync, 7 | } from 'fs' 8 | import { resolve } from 'path' 9 | import t from 'tap' 10 | 11 | const dir = t.testdir({ 12 | 'package.json': JSON.stringify({ 13 | tshy: { 14 | exclude: ['./src/**/*.test.ts'], 15 | esmDialects: ['deno'], 16 | commonjsDialects: ['webpack'], 17 | }, 18 | }), 19 | src: { 20 | 'index.ts': '', 21 | 'index-cjs.cts': '', 22 | 'index-deno.mts': '', 23 | 'index-webpack.mts': '', 24 | }, 25 | }) 26 | t.chdir(dir) 27 | 28 | t.test('with tsconfig.json file', async t => { 29 | await import('../dist/esm/tsconfig.js') 30 | 31 | for (const f of [ 32 | 'tsconfig.json', 33 | '.tshy/build.json', 34 | '.tshy/commonjs.json', 35 | '.tshy/esm.json', 36 | ]) { 37 | t.matchSnapshot( 38 | JSON.parse(readFileSync(resolve(dir, f), 'utf8')), 39 | f + ' generate everything', 40 | ) 41 | } 42 | 43 | writeFileSync( 44 | resolve(dir, 'tsconfig.json'), 45 | JSON.stringify({ 46 | compilerOptions: { 47 | yolo: '🍑', 48 | this_data: 'is preserved', 49 | }, 50 | }), 51 | ) 52 | unlinkSync(resolve(dir, '.tshy/build.json')) 53 | writeFileSync( 54 | resolve(dir, '.tshy/esm.json'), 55 | 'not even json, this gets clobbered', 56 | ) 57 | 58 | await t.mockImport('../dist/esm/tsconfig.js') 59 | 60 | for (const f of [ 61 | 'tsconfig.json', 62 | '.tshy/build.json', 63 | '.tshy/commonjs.json', 64 | '.tshy/esm.json', 65 | '.tshy/deno.json', 66 | '.tshy/webpack.json', 67 | ]) { 68 | t.matchSnapshot( 69 | JSON.parse(readFileSync(resolve(dir, f), 'utf8')), 70 | f, 71 | ) 72 | } 73 | }) 74 | 75 | t.test('with custom project tsconfig name', async t => { 76 | renameSync( 77 | resolve(dir, 'tsconfig.json'), 78 | resolve(dir, 'custom.json'), 79 | ) 80 | 81 | writeFileSync( 82 | resolve(dir, 'package.json'), 83 | JSON.stringify({ 84 | tshy: { 85 | project: 'custom.json', 86 | esmDialects: ['deno'], 87 | commonjsDialects: ['webpack'], 88 | }, 89 | }), 90 | ) 91 | 92 | await t.mockImport('../dist/esm/tsconfig.js') 93 | 94 | t.throws(() => statSync(resolve(dir, 'tsconfig.json')), { 95 | code: 'ENOENT', 96 | }) 97 | 98 | for (const f of [ 99 | 'custom.json', 100 | '.tshy/build.json', 101 | '.tshy/commonjs.json', 102 | '.tshy/esm.json', 103 | '.tshy/deno.json', 104 | '.tshy/webpack.json', 105 | ]) { 106 | t.matchSnapshot( 107 | JSON.parse(readFileSync(resolve(dir, f), 'utf8')), 108 | f, 109 | ) 110 | } 111 | }) 112 | -------------------------------------------------------------------------------- /test/types.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | // just here for the coverage, to verify that nothing is there 3 | await t.mockImport('../dist/esm/types.js') 4 | t.pass('this is fine') 5 | -------------------------------------------------------------------------------- /test/unbuilt-imports.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from 'node:fs' 2 | import { createRequire } from 'node:module' 3 | import { fileURLToPath } from 'node:url' 4 | import { rimrafSync } from 'rimraf' 5 | import t from 'tap' 6 | 7 | t.test('imports linking', async t => { 8 | const require = createRequire(import.meta.url) 9 | 10 | // make sure one of them doesn't already have a scripts block 11 | 12 | for (const i of [ 13 | 'imports', 14 | 'imports-with-star', 15 | 'basic', 16 | 'basic-imports-only-deps', 17 | 'basic-custom-project', 18 | ]) { 19 | t.test(i, async t => { 20 | // keep the pjs unmodified 21 | const f = fileURLToPath( 22 | new URL(`./fixtures/${i}/package.json`, import.meta.url), 23 | ) 24 | const dist = fileURLToPath( 25 | new URL(`./fixtures/${i}/dist`, import.meta.url), 26 | ) 27 | const pj = JSON.parse(readFileSync(f, 'utf8')) 28 | t.teardown(() => { 29 | writeFileSync(f, JSON.stringify(pj, null, 2) + '\n') 30 | rimrafSync(dist) 31 | }) 32 | 33 | t.chdir( 34 | fileURLToPath(new URL('./fixtures/' + i, import.meta.url)), 35 | ) 36 | await t.mockImport('../dist/esm/index.js') 37 | const logs = t.capture(console, 'log').args 38 | const { test: testESM } = await import( 39 | `./fixtures/${i}/dist/esm/index.js` 40 | ) 41 | const { test: testCJS } = require( 42 | `./fixtures/${i}/dist/commonjs/index.js`, 43 | ) 44 | await testESM() 45 | t.strictSame( 46 | logs(), 47 | i.startsWith('basic') ? 48 | [['hello']] 49 | : [['pkg exports', 'node esm'], ['node esm']], 50 | ) 51 | await testCJS() 52 | t.strictSame( 53 | logs(), 54 | i.startsWith('basic') ? 55 | [['hello']] 56 | : [['pkg exports', 'node esm'], ['node cjs']], 57 | ) 58 | }) 59 | } 60 | }) 61 | -------------------------------------------------------------------------------- /test/usage.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | 3 | import fail from '../dist/esm/fail.js' 4 | 5 | const { default: link } = (await t.mockImport( 6 | '../dist/esm/usage.js', 7 | { 8 | chalk: { default: { level: 3, red: { bold: (s: string) => s } } }, 9 | '../dist/esm/fail.js': fail, 10 | }, 11 | )) as typeof import('../dist/esm/usage.js') 12 | const { default: noLink } = (await t.mockImport( 13 | '../dist/esm/usage.js', 14 | { 15 | chalk: { default: { level: 0, red: { bold: (s: string) => s } } }, 16 | '../dist/esm/fail.js': fail, 17 | }, 18 | )) as typeof import('../dist/esm/usage.js') 19 | 20 | const exits = t.capture(process, 'exit').args 21 | const errs = t.capture(console, 'error').args 22 | const logs = t.capture(console, 'log').args 23 | 24 | t.test('no error', async t => { 25 | t.test('with link', async t => { 26 | link() 27 | t.strictSame(errs(), []) 28 | t.strictSame(exits(), [[0]]) 29 | t.matchSnapshot( 30 | logs() 31 | .map(s => s.join('')) 32 | .join('\n'), 33 | ) 34 | }) 35 | t.test('no link', async t => { 36 | noLink() 37 | t.strictSame(errs(), []) 38 | t.strictSame(exits(), [[0]]) 39 | t.matchSnapshot( 40 | logs() 41 | .map(s => s.join('')) 42 | .join('\n'), 43 | ) 44 | }) 45 | }) 46 | 47 | t.test('with error', async t => { 48 | t.test('with link', async t => { 49 | link('error string') 50 | t.strictSame(logs(), []) 51 | t.strictSame(exits(), [[1]]) 52 | t.matchSnapshot( 53 | errs() 54 | .map(s => s.join('')) 55 | .join('\n'), 56 | ) 57 | }) 58 | t.test('no link', async t => { 59 | noLink('error string') 60 | t.strictSame(logs(), []) 61 | t.strictSame(exits(), [[1]]) 62 | t.matchSnapshot( 63 | errs() 64 | .map(s => s.join('')) 65 | .join('\n'), 66 | ) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/valid-dialects.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | 3 | const exits = t.capture(process, 'exit', () => false).args 4 | let failed = false 5 | const { default: validDialects } = (await t.mockImport( 6 | '../dist/esm/valid-dialects.js', 7 | { 8 | '../dist/esm/fail.js': () => (failed = true), 9 | }, 10 | )) as typeof import('../dist/esm/valid-dialects.js') 11 | 12 | t.equal(validDialects(['esm', 'commonjs']), true) 13 | t.equal(validDialects(['commonjs']), true) 14 | t.equal(validDialects(['esm']), true) 15 | 16 | t.equal(validDialects(false), false) 17 | t.strictSame(exits(), [[1]]) 18 | t.equal(failed, true) 19 | failed = false 20 | 21 | t.equal(validDialects(['esm', 'blah']), false) 22 | t.strictSame(exits(), [[1]]) 23 | t.equal(failed, true) 24 | failed = false 25 | -------------------------------------------------------------------------------- /test/valid-exclude.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | const exits = t.capture(process, 'exit', () => false).args 3 | const mf = t.captureFn(() => {}) 4 | const fails = mf.args 5 | const { default: validExclude } = (await t.mockImport( 6 | '../src/valid-exclude.js', 7 | { 8 | '../src/fail.js': mf, 9 | }, 10 | )) as typeof import('../src/valid-exclude.js') 11 | 12 | t.equal(validExclude(['a', 'b']), true) 13 | t.equal(validExclude(['']), true) 14 | 15 | t.equal(validExclude([]), false) 16 | const f = fails() 17 | t.matchOnly(f, [[String]]) 18 | t.matchOnly(exits(), [[1]]) 19 | 20 | t.equal(validExclude(false), false) 21 | t.equal(validExclude(true), false) 22 | t.equal(validExclude(123), false) 23 | -------------------------------------------------------------------------------- /test/valid-exports.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | 3 | const exits = t.capture(process, 'exit', () => false).args 4 | let failed: string | undefined = undefined 5 | const { default: validExports } = (await t.mockImport( 6 | '../dist/esm/valid-exports.js', 7 | { 8 | '../dist/esm/fail.js': (m: string) => (failed = m), 9 | }, 10 | )) as typeof import('../dist/esm/valid-exports.js') 11 | 12 | t.beforeEach(() => (failed = undefined)) 13 | 14 | const cases: [any, boolean][] = [ 15 | [false, false], 16 | [null, false], 17 | [[], false], 18 | [{ x: './src/x.ts' }, false], 19 | [{ './b': { require: './src/blah.ts' } }, false], 20 | [{ './b': './src/blah.ts' }, true], 21 | [{ './b': 'src/b.ts' }, true], 22 | [{ './B': 8 }, false], 23 | [{ './b': { require: './blah.js' } }, true], 24 | ] 25 | 26 | t.plan(cases.length) 27 | 28 | for (const [exp, ok] of cases) { 29 | t.test(JSON.stringify(exp) + ' ' + ok, async t => { 30 | const v = validExports(exp) 31 | if (!ok) { 32 | t.equal(v, false) 33 | t.matchSnapshot(failed, 'message') 34 | t.matchSnapshot(JSON.stringify(exits()), 'exits') 35 | } else { 36 | t.equal(v, true, failed || 'should be valid') 37 | t.equal(failed, undefined) 38 | t.strictSame(exits(), []) 39 | t.matchSnapshot(exp, 'export result') 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /test/valid-external-export.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | import validExternalExport from '../src/valid-external-export.js' 3 | 4 | const cases: [any, boolean][] = [ 5 | [{}, true], 6 | [[], true], 7 | [null, true], 8 | [undefined, true], 9 | [{ require: './src/x.js', import: null }, false], 10 | [{ import: './src/x.js', require: null }, false], 11 | [{ import: './blah/x.js', require: null }, true], 12 | ] 13 | 14 | t.plan(cases.length) 15 | for (const [exp, ok] of cases) { 16 | t.equal( 17 | validExternalExport(exp), 18 | ok, 19 | JSON.stringify(exp) + ' ' + ok, 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /test/valid-extra-dialects.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | import { TshyConfig } from '../src/types.js' 3 | const mockFail = { 4 | default: (..._: any[]) => {}, 5 | } 6 | const fails = t.capture(mockFail, 'default').args 7 | const exits = t.capture(process, 'exit').args 8 | 9 | const { default: validExtraDialects } = (await t.mockImport( 10 | '../src/valid-extra-dialects.js', 11 | { 12 | '../src/fail.js': mockFail, 13 | }, 14 | )) as typeof import('../src/valid-extra-dialects.js') 15 | 16 | const cases: [config: TshyConfig, ok: boolean][] = [ 17 | [{}, true], 18 | [{ commonjsDialects: ['blah'] }, true], 19 | [{ esmDialects: ['blah'] }, true], 20 | [{ esmDialects: ['blah'], commonjsDialects: ['blah'] }, false], 21 | [{ esmDialects: ['blah'], commonjsDialects: ['bloo'] }, true], 22 | [{ sourceDialects: ['blah'], commonjsDialects: ['blah'] }, false], 23 | [{ sourceDialects: ['blah'], commonjsDialects: ['bloo'] }, true], 24 | [{ esmDialects: ['blah'], sourceDialects: ['blah'] }, false], 25 | [{ esmDialects: ['blah'], sourceDialects: ['bloo'] }, true], 26 | [{ esmDialects: ['default'] }, false], 27 | [{ esmDialects: ['import'] }, false], 28 | [{ esmDialects: ['require'] }, false], 29 | [{ esmDialects: ['node'] }, false], 30 | [{ esmDialects: ['commonjs'] }, false], 31 | [{ esmDialects: ['cjs'] }, false], 32 | [{ esmDialects: ['source'] }, false], 33 | [{ sourceDialects: ['source'] }, false], 34 | //@ts-expect-error 35 | [{ esmDialects: [123] }, false], 36 | //@ts-expect-error 37 | [{ commonjsDialects: [123] }, false], 38 | ] 39 | 40 | for (const [config, ok] of cases) { 41 | t.test(JSON.stringify(config), t => { 42 | const valid = validExtraDialects(config) 43 | if (ok) { 44 | t.equal(valid, true, 'is valid') 45 | t.strictSame(fails(), [], 'no fails') 46 | t.strictSame(exits(), [], 'no exits') 47 | } else { 48 | t.not(valid, true, 'not valid') 49 | t.matchSnapshot(fails(), 'failure message') 50 | t.strictSame(exits(), [[1]], 'exit in error') 51 | } 52 | t.end() 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /test/valid-imports.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | import { Package } from '../src/types.js' 3 | 4 | let failed: undefined | string = undefined 5 | 6 | const { default: validImports } = (await t.mockImport( 7 | '../src/valid-imports.js', 8 | { 9 | '../src/fail.js': { 10 | default: (m: string) => (failed = m), 11 | }, 12 | }, 13 | )) as typeof import('../src/valid-imports.js') 14 | 15 | const exits = t.capture(process, 'exit').args 16 | 17 | const cases: [pkg: Omit, ok: boolean][] = 18 | [ 19 | [{}, true], 20 | [{ imports: 'asdf' }, false], 21 | [{ imports: [] }, false], 22 | [{ imports: { '#x': {} } }, true], 23 | [{ imports: { '#x': 'y' } }, true], 24 | [{ imports: { x: 'y' } }, false], 25 | [{ imports: { '#': 'y' } }, false], 26 | [{ imports: { '#x': './src/x' } }, true], 27 | [{ imports: { '#x': ['./src/x'] } }, false], 28 | ] 29 | 30 | for (const [pkg, ok] of cases) { 31 | t.test(JSON.stringify({ pkg }), t => { 32 | const actual = validImports(pkg as Package) 33 | if (!ok) { 34 | t.notOk(actual, 'should not be ok') 35 | t.matchSnapshot(failed, 'failure message') 36 | t.type(failed, 'string', 'got a failure message') 37 | t.strictSame(exits(), [[1]], 'exited in error') 38 | } else { 39 | t.equal(actual, true, 'should be ok') 40 | t.equal(failed, undefined, 'no failure message') 41 | t.strictSame(exits(), [], 'no error exit') 42 | } 43 | failed = undefined 44 | t.end() 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /test/watch.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { SpawnOptions } from 'child_process' 3 | import { WatchOptions } from 'chokidar' 4 | import EventEmitter from 'events' 5 | import t from 'tap' 6 | import { 7 | bin, 8 | options, 9 | rootPJ, 10 | src, 11 | srcNM, 12 | srcPJ, 13 | targets, 14 | } from '../dist/esm/watch.js' 15 | 16 | t.cleanSnapshot = s => s.replace(/\\/g, '/') 17 | 18 | t.matchSnapshot({ options, srcPJ, srcNM, src, rootPJ, targets, bin }) 19 | 20 | if (typeof options.ignored !== 'function') { 21 | throw new Error('expect options.ignored to be a function') 22 | } 23 | t.equal(options.ignored('./src/package.json'), true) 24 | t.equal(options.ignored('./src/package.json'), true) 25 | t.equal(options.ignored('./src/node_modules/xyz'), true) 26 | t.equal(options.ignored('./src/node_modules'), true) 27 | t.equal(options.ignored('./package.json'), false) 28 | t.equal(options.ignored('./src/x.ts'), false) 29 | 30 | const logs = t.capture(console, 'log').args 31 | const errs = t.capture(console, 'error').args 32 | 33 | const mockChalk = { 34 | green: chalk.green, 35 | cyan: { dim: chalk.cyan.dim }, 36 | } 37 | 38 | type SpawnResult = { 39 | code: number | null 40 | signal: NodeJS.Signals | null 41 | } 42 | const spawnOK: SpawnResult = { code: 0, signal: null } 43 | const spawnExitCode: SpawnResult = { code: 1, signal: null } 44 | let spawnResult: SpawnResult = spawnOK 45 | 46 | const mockSpawn = ( 47 | cmd: string, 48 | args: string[], 49 | options: SpawnOptions, 50 | ) => { 51 | t.equal(cmd, process.execPath) 52 | t.strictSame(args, [bin]) 53 | t.strictSame(options, { stdio: 'inherit' }) 54 | const child = new EventEmitter() 55 | setTimeout(() => 56 | child.emit('close', spawnResult.code, spawnResult.signal), 57 | ) 58 | return child 59 | } 60 | 61 | const mockWatcher = new EventEmitter() 62 | const mockChokidar = { 63 | watch: (watchTargets: string[], watchOptions: WatchOptions) => { 64 | t.strictSame(watchTargets, targets) 65 | t.match(watchOptions, options) 66 | return mockWatcher 67 | }, 68 | } 69 | 70 | t.test('build whenever changes happen', async t => { 71 | const { default: watch } = (await t.mockImport( 72 | '../dist/esm/watch.js', 73 | { 74 | chalk: mockChalk, 75 | child_process: { 76 | spawn: mockSpawn, 77 | }, 78 | chokidar: mockChokidar, 79 | }, 80 | )) as typeof import('../dist/esm/watch.js') 81 | watch() 82 | // immediately trigger changes to the pj and other stuff 83 | mockWatcher.emit('all', 'change', srcPJ) 84 | mockWatcher.emit('all', 'change', rootPJ) 85 | mockWatcher.emit('all', 'change', 'src/x.ts') 86 | // that last one should trigger a rebuild 87 | await new Promise(r => setTimeout(r, 100)) 88 | t.matchSnapshot(logs()) 89 | t.matchSnapshot(errs()) 90 | }) 91 | 92 | t.test('build failure', async t => { 93 | spawnResult = spawnExitCode 94 | const { default: watch } = (await t.mockImport( 95 | '../dist/esm/watch.js', 96 | { 97 | chalk: mockChalk, 98 | child_process: { 99 | spawn: mockSpawn, 100 | }, 101 | chokidar: mockChokidar, 102 | }, 103 | )) as typeof import('../dist/esm/watch.js') 104 | watch() 105 | await new Promise(r => setTimeout(r, 100)) 106 | t.matchSnapshot(logs()) 107 | t.matchSnapshot(errs()) 108 | spawnResult = spawnOK 109 | mockWatcher.emit('all', 'change', 'src/x.ts') 110 | await new Promise(r => setTimeout(r, 100)) 111 | t.matchSnapshot(logs()) 112 | t.matchSnapshot(errs()) 113 | }) 114 | -------------------------------------------------------------------------------- /test/which-tsc.ts: -------------------------------------------------------------------------------- 1 | import t from 'tap' 2 | import tsc from '../src/which-tsc.js' 3 | import { accessSync, constants } from 'node:fs' 4 | 5 | t.doesNotThrow( 6 | () => accessSync(tsc, constants.R_OK), 7 | 'tsc is readable', 8 | ) 9 | -------------------------------------------------------------------------------- /test/write-package.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | import { resolve } from 'path' 3 | import t from 'tap' 4 | 5 | t.chdir(t.testdir({})) 6 | const { default: writePackage } = (await t.mockImport( 7 | '../dist/esm/write-package.js', 8 | { 9 | '../dist/esm/package.js': { 10 | default: { 11 | name: 'some package', 12 | }, 13 | }, 14 | }, 15 | )) as typeof import('../dist/esm/write-package.js') 16 | 17 | writePackage() 18 | t.strictSame( 19 | JSON.parse( 20 | readFileSync(resolve(t.testdirName, 'package.json'), 'utf8'), 21 | ), 22 | { name: 'some package' }, 23 | ) 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "declaration": true, 5 | "declarationMap": true, 6 | "inlineSources": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "moduleResolution": "nodenext", 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true, 12 | "sourceMap": true, 13 | "strict": true, 14 | "target": "es2022", 15 | "module": "nodenext", 16 | "composite": true, 17 | "incremental": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tsconfig": "./.tshy/esm.json", 3 | "entryPoints": ["./src/**/*.+(ts|tsx|mts|cts)"], 4 | "navigationLinks": { 5 | "GitHub": "https://github.com/isaacs/tshy", 6 | "isaacs projects": "https://isaacs.github.io/" 7 | } 8 | } 9 | --------------------------------------------------------------------------------