@checkdigit/typescript-config 3.3.0-PR.30-dcb8 → 3.3.0-PR.30-f38e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/builder.mjs CHANGED
@@ -39,21 +39,45 @@ function setup(type2) {
39
39
  });
40
40
  };
41
41
  }
42
- async function builder_default({ type: type2, inDir: inDir2, outDir: outDir2 }) {
42
+ async function builder_default({ type: type2, entryPoint, inDir: inDir2, outDir: outDir2, outFile }) {
43
43
  const messages2 = [];
44
- const allSourceFiles = await getFiles(inDir2);
45
- const productionSourceFiles = allSourceFiles.filter(
46
- // && !file.endsWith('.test.ts') && !file.endsWith('.spec.ts')
47
- (file) => file.endsWith(".ts")
44
+ assert.ok(
45
+ entryPoint === void 0 && outFile === void 0 || entryPoint !== void 0 && outFile !== void 0,
46
+ "entryPoint and outFile must both be provided"
48
47
  );
49
- const configFile = typescript.readConfigFile("./tsconfig.json", (name) => typescript.sys.readFile(name));
50
- const compilerOptions = typescript.parseJsonConfigFileContent(configFile.config, typescript.sys, "./");
48
+ const allSourceFiles = await getFiles(inDir2);
49
+ const productionSourceFiles = entryPoint === void 0 ? allSourceFiles.filter((file) => file.endsWith(".ts")) : [path.join(inDir2, entryPoint)];
51
50
  const program = typescript.createProgram(productionSourceFiles, {
52
- ...compilerOptions.options,
51
+ module: typescript.ModuleKind.ESNext,
52
+ moduleResolution: typescript.ModuleResolutionKind.Bundler,
53
+ target: typescript.ScriptTarget.ESNext,
54
+ declaration: true,
53
55
  noEmitOnError: true,
54
56
  emitDeclarationOnly: true,
55
57
  rootDir: inDir2,
56
- outDir: outDir2
58
+ outDir: outDir2,
59
+ noLib: false,
60
+ skipLibCheck: true,
61
+ strict: true,
62
+ preserveConstEnums: true,
63
+ noImplicitReturns: true,
64
+ noUnusedLocals: true,
65
+ noUnusedParameters: true,
66
+ alwaysStrict: true,
67
+ verbatimModuleSyntax: false,
68
+ noFallthroughCasesInSwitch: true,
69
+ forceConsistentCasingInFileNames: true,
70
+ emitDecoratorMetadata: true,
71
+ experimentalDecorators: true,
72
+ resolveJsonModule: true,
73
+ esModuleInterop: true,
74
+ noUncheckedIndexedAccess: true,
75
+ noPropertyAccessFromIndexSignature: true,
76
+ allowUnusedLabels: false,
77
+ allowUnreachableCode: false,
78
+ noImplicitOverride: true,
79
+ useUnknownInCatchVariables: true,
80
+ exactOptionalPropertyTypes: true
57
81
  });
58
82
  const emitResult = program.emit();
59
83
  const allDiagnostics = typescript.sortAndDeduplicateDiagnostics([
@@ -78,16 +102,18 @@ async function builder_default({ type: type2, inDir: inDir2, outDir: outDir2 })
78
102
  bundle: true,
79
103
  platform: "node",
80
104
  format: type2 === "module" ? "esm" : "cjs",
81
- outdir: outDir2,
82
105
  sourcemap: "inline",
83
106
  sourcesContent: false,
84
- outExtension: { ".js": type2 === "module" ? ".mjs" : ".cjs" },
85
- plugins: [
86
- {
87
- name: "resolve-ts",
88
- setup: setup(type2)
89
- }
90
- ]
107
+ ...outFile === void 0 ? {
108
+ outdir: outDir2,
109
+ outExtension: { ".js": type2 === "module" ? ".mjs" : ".cjs" },
110
+ plugins: [
111
+ {
112
+ name: "resolve-ts",
113
+ setup: setup(type2)
114
+ }
115
+ ]
116
+ } : { outfile: path.join(outDir2, outFile) }
91
117
  });
92
118
  messages2.push(...buildResult.errors.map((error) => `esbuild error: ${error.text}`));
93
119
  messages2.push(...buildResult.warnings.map((warning) => `esbuild warning: ${warning.text}`));
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@checkdigit/typescript-config","version":"3.3.0-PR.30-dcb8","description":"Check Digit standard Typescript configuration","prettier":"@checkdigit/prettier-config","engines":{"node":">=16"},"bin":{"builder":"./bin/builder.mjs"},"peerDependencies":{"@types/node":">=16","esbuild":"0.17.18","typescript":">=5.0.4 <5.1"},"repository":{"type":"git","url":"git+https://github.com/checkdigit/typescript-config.git"},"author":"Check Digit, LLC","license":"MIT","bugs":{"url":"https://github.com/checkdigit/typescript-config/issues"},"homepage":"https://github.com/checkdigit/typescript-config#readme","scripts":{"preversion":"npm test","postversion":"git push && git push --tags","prepare":"npm run build-builder","lint:fix":"eslint -f unix --ext .ts,.mts src --fix","lint":"eslint -f unix --ext .ts,.mts src","prettier":"prettier --ignore-path .gitignore --list-different .","prettier:fix":"prettier --ignore-path .gitignore --write .","test":"npm run ci:compile && npm run ci:test && npm run ci:lint && npm run ci:style","build-builder":"esbuild src/builder/index.mts --bundle --platform=node --format=esm --external:typescript --external:esbuild --outfile=build-builder/builder.mjs && mkdir -p bin && { echo '#!/usr/bin/env node'; cat build-builder/builder.mjs; } > bin/builder.mjs && chmod +x bin/builder.mjs","build-tsc-cjs":"rimraf build-tsc-cjs && tsc --outDir build-tsc-cjs","build-esbuild-cjs":"rimraf build-esbuild-cjs && esbuild ./src/*.ts ./src/*/*.ts --platform=node --bundle --format=cjs --sourcemap=inline --sources-content=false --outdir=build-esbuild-cjs","build-esbuild-cjs-bundle":"rimraf build-esbuild-cjs-bundle && esbuild src/test/index.test.ts --bundle --platform=node --format=cjs --sourcemap=inline --sources-content=false --outfile=build-esbuild-cjs-bundle/test/index.test.cjs","build-esbuild-esm":"rimraf build-esbuild-esm && node bin/builder.mjs --outDir=build-esbuild-esm","build-esbuild-esm-bundle":"rimraf build-esbuild-esm-bundle && esbuild src/test/index.test.ts --bundle --platform=node --format=esm --sourcemap=inline --sources-content=false --outfile=build-esbuild-esm-bundle/test/index.test.mjs","test-jest-esm":"NODE_OPTIONS=\"--experimental-vm-modules\" jest --coverage=false src/*.mts src/*/*.mts src/*/*/*.mts","test-jest-cjs":"jest --coverage=false src/*.ts src/*/*.ts src/*/*/*.ts","test-tsc-cjs":"node --test build-tsc-cjs/test/index.test.js","test-esbuild-cjs":"node --test build-esbuild-cjs/test/index.test.js","test-esbuild-cjs-bundle":"node --test build-esbuild-cjs-bundle/test/index.test.cjs","test-esbuild-esm":"node --test build-esbuild-esm/test/index.test.mjs","test-esbuild-esm-bundle":"echo 'node -test build-esbuild-esm-bundle/test/index.test.mjs'","ci:test":"npm run test-jest-cjs && npm run test-jest-esm &&npm run test-tsc-cjs && npm run test-esbuild-cjs && npm run test-esbuild-cjs-bundle && npm run test-esbuild-esm && npm run test-esbuild-esm-bundle","ci:compile":"npm run build-builder && npm run build-tsc-cjs && npm run build-esbuild-cjs && npm run build-esbuild-cjs-bundle && npm run build-esbuild-esm && npm run build-esbuild-esm-bundle","ci:lint":"npm run lint","ci:style":"npm run prettier"},"devDependencies":{"@checkdigit/prettier-config":"^3.4.0","@types/debug":"^4.1.7","@types/jest":"^29.5.1","@types/uuid":"^9.0.1","@typescript-eslint/eslint-plugin":"^5.59.6","@typescript-eslint/parser":"^5.59.6","debug":"^4.3.4","eslint":"^8.40.0","eslint-config-prettier":"^8.8.0","get-port":"^6.1.2","got":"^11.8.6","jest":"^29.5.0","rimraf":"^5.0.0","ts-jest":"^29.1.0","uuid":"^9.0.0"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint"],"parserOptions":{"project":"./tsconfig.json"},"extends":["eslint:all","plugin:@typescript-eslint/recommended","plugin:@typescript-eslint/recommended-requiring-type-checking","plugin:@typescript-eslint/strict","prettier"],"rules":{"@typescript-eslint/non-nullable-type-assertion-style":"error","capitalized-comments":"off","one-var":"off","sort-keys":"off","sort-imports":"off","func-style":["error","declaration",{"allowArrowFunctions":true}],"no-magic-numbers":["error",{"ignore":[0,1,2]}],"no-undefined":"off","no-ternary":"off"},"overrides":[{"files":["*.spec.mts","*.test.mts","*.spec.ts","*.test.ts"],"rules":{"@typescript-eslint/non-nullable-type-assertion-style":"off","@typescript-eslint/ban-types":"off","@typescript-eslint/require-await":"off","@typescript-eslint/consistent-type-definitions":"off","@typescript-eslint/ban-ts-comment":"off","@typescript-eslint/no-unnecessary-condition":"off","@typescript-eslint/consistent-indexed-object-style":"off","@typescript-eslint/no-unused-vars":"off","@typescript-eslint/no-unsafe-member-access":"off","line-comment-position":"off","no-inline-comments":"off","no-param-reassign":"off","id-length":"off","no-magic-numbers":"off","func-names":"off","no-duplicate-imports":"off","symbol-description":"off","no-invalid-this":"off","max-lines-per-function":"off","max-lines":"off"}}]},"jest":{"moduleFileExtensions":["js","mjs","cjs","ts","mts","json","node"],"extensionsToTreatAsEsm":[".mts"],"transform":{"^.+\\.mts$":["ts-jest",{"isolatedModules":true,"diagnostics":false,"useESM":true}],"^.+\\.ts$":["ts-jest",{"isolatedModules":true,"diagnostics":false,"useESM":false}]},"collectCoverageFrom":["<rootDir>/src/**","!<rootDir>/src/**/*.spec.mts","!<rootDir>/src/**/*.test.mts","!<rootDir>/src/**/*.spec.ts","!<rootDir>/src/**/*.test.ts"],"testMatch":["<rootDir>/src/**/*.spec.ts","<rootDir>/src/**/*.spec.mts"]},"files":["tsconfig.json","SECURITY.md","/src/"]}
1
+ {"name":"@checkdigit/typescript-config","version":"3.3.0-PR.30-f38e","description":"Check Digit standard Typescript configuration","prettier":"@checkdigit/prettier-config","engines":{"node":">=16"},"bin":{"builder":"./bin/builder.mjs"},"peerDependencies":{"@types/node":">=16","esbuild":"0.17.18","typescript":">=5.0.4 <5.1"},"repository":{"type":"git","url":"git+https://github.com/checkdigit/typescript-config.git"},"author":"Check Digit, LLC","license":"MIT","bugs":{"url":"https://github.com/checkdigit/typescript-config/issues"},"homepage":"https://github.com/checkdigit/typescript-config#readme","scripts":{"preversion":"npm test","postversion":"git push && git push --tags","prepare":"npm run build-builder","lint:fix":"eslint -f unix --ext .ts,.mts src --fix","lint":"eslint -f unix --ext .ts,.mts src","prettier":"prettier --ignore-path .gitignore --list-different .","prettier:fix":"prettier --ignore-path .gitignore --write .","test":"npm run ci:compile && npm run ci:test && npm run ci:lint && npm run ci:style","build-builder":"esbuild src/builder/index.mts --bundle --platform=node --format=esm --external:typescript --external:esbuild --outfile=build-builder/builder.mjs && mkdir -p bin && { echo '#!/usr/bin/env node'; cat build-builder/builder.mjs; } > bin/builder.mjs && chmod +x bin/builder.mjs","build-tsc-cjs":"rimraf build-tsc-cjs && tsc --outDir build-tsc-cjs","build-esbuild-cjs":"rimraf build-esbuild-cjs && esbuild ./src/*.ts ./src/*/*.ts --platform=node --bundle --format=cjs --sourcemap=inline --sources-content=false --outdir=build-esbuild-cjs","build-esbuild-cjs-bundle":"rimraf build-esbuild-cjs-bundle && esbuild src/test/index.test.ts --bundle --platform=node --format=cjs --sourcemap=inline --sources-content=false --outfile=build-esbuild-cjs-bundle/test/index.test.cjs","build-esbuild-esm":"rimraf build-esbuild-esm && node bin/builder.mjs --outDir=build-esbuild-esm","build-esbuild-esm-bundle":"rimraf build-esbuild-esm-bundle && esbuild src/test/index.test.ts --bundle --platform=node --format=esm --sourcemap=inline --sources-content=false --outfile=build-esbuild-esm-bundle/test/index.test.mjs","test-jest-esm":"NODE_OPTIONS=\"--experimental-vm-modules\" jest --coverage=false src/*.mts src/*/*.mts src/*/*/*.mts","test-jest-cjs":"jest --coverage=false src/*.ts src/*/*.ts src/*/*/*.ts","test-tsc-cjs":"node --test build-tsc-cjs/test/index.test.js","test-esbuild-cjs":"node --test build-esbuild-cjs/test/index.test.js","test-esbuild-cjs-bundle":"node --test build-esbuild-cjs-bundle/test/index.test.cjs","test-esbuild-esm":"node --test build-esbuild-esm/test/index.test.mjs","test-esbuild-esm-bundle":"echo 'node -test build-esbuild-esm-bundle/test/index.test.mjs'","ci:test":"npm run test-jest-cjs && npm run test-jest-esm &&npm run test-tsc-cjs && npm run test-esbuild-cjs && npm run test-esbuild-cjs-bundle && npm run test-esbuild-esm && npm run test-esbuild-esm-bundle","ci:compile":"npm run build-builder && npm run build-tsc-cjs && npm run build-esbuild-cjs && npm run build-esbuild-cjs-bundle && npm run build-esbuild-esm && npm run build-esbuild-esm-bundle","ci:lint":"npm run lint","ci:style":"npm run prettier"},"devDependencies":{"@checkdigit/prettier-config":"^3.4.0","@types/debug":"^4.1.7","@types/jest":"^29.5.1","@types/uuid":"^9.0.1","@typescript-eslint/eslint-plugin":"^5.59.6","@typescript-eslint/parser":"^5.59.6","debug":"^4.3.4","eslint":"^8.40.0","eslint-config-prettier":"^8.8.0","get-port":"^6.1.2","got":"^11.8.6","jest":"^29.5.0","rimraf":"^5.0.0","ts-jest":"^29.1.0","uuid":"^9.0.0"},"eslintConfig":{"parser":"@typescript-eslint/parser","plugins":["@typescript-eslint"],"parserOptions":{"project":"./tsconfig.json"},"extends":["eslint:all","plugin:@typescript-eslint/recommended","plugin:@typescript-eslint/recommended-requiring-type-checking","plugin:@typescript-eslint/strict","prettier"],"rules":{"@typescript-eslint/non-nullable-type-assertion-style":"error","capitalized-comments":"off","one-var":"off","sort-keys":"off","sort-imports":"off","func-style":["error","declaration",{"allowArrowFunctions":true}],"no-magic-numbers":["error",{"ignore":[0,1,2]}],"no-undefined":"off","no-ternary":"off"},"overrides":[{"files":["*.spec.mts","*.test.mts","*.spec.ts","*.test.ts"],"rules":{"@typescript-eslint/non-nullable-type-assertion-style":"off","@typescript-eslint/ban-types":"off","@typescript-eslint/require-await":"off","@typescript-eslint/consistent-type-definitions":"off","@typescript-eslint/ban-ts-comment":"off","@typescript-eslint/no-unnecessary-condition":"off","@typescript-eslint/consistent-indexed-object-style":"off","@typescript-eslint/no-unused-vars":"off","@typescript-eslint/no-unsafe-member-access":"off","line-comment-position":"off","no-inline-comments":"off","no-param-reassign":"off","id-length":"off","no-magic-numbers":"off","func-names":"off","no-duplicate-imports":"off","symbol-description":"off","no-invalid-this":"off","max-lines-per-function":"off","max-lines":"off","max-statements":"off","no-await-in-loop":"off"}}]},"jest":{"moduleFileExtensions":["js","mjs","cjs","ts","mts","json","node"],"extensionsToTreatAsEsm":[".mts"],"transform":{"^.+\\.mts$":["ts-jest",{"isolatedModules":true,"diagnostics":false,"useESM":true}],"^.+\\.ts$":["ts-jest",{"isolatedModules":true,"diagnostics":false,"useESM":false}]},"collectCoverageFrom":["<rootDir>/src/**","!<rootDir>/src/**/*.spec.mts","!<rootDir>/src/**/*.test.mts","!<rootDir>/src/**/*.spec.ts","!<rootDir>/src/**/*.test.ts"],"testMatch":["<rootDir>/src/**/*.spec.ts","<rootDir>/src/**/*.spec.mts"]},"files":["tsconfig.json","SECURITY.md","/src/"]}
@@ -8,9 +8,31 @@ import typescript from 'typescript';
8
8
  import { PluginBuild, build } from 'esbuild';
9
9
 
10
10
  export interface BuilderOptions {
11
+ /**
12
+ * whether to produce ESM or CommonJS code
13
+ */
11
14
  type: 'module' | 'commonjs';
15
+
16
+ /**
17
+ * the entry point for the bundle, relative to the inDir. if not provided, the files in the inDir will be processed
18
+ * as individual unbundled files
19
+ */
20
+ entryPoint?: string;
21
+
22
+ /**
23
+ * source code
24
+ */
12
25
  inDir: string;
26
+
27
+ /**
28
+ * build directory
29
+ */
13
30
  outDir: string;
31
+
32
+ /**
33
+ * build file, relative to the outDir
34
+ */
35
+ outFile?: string;
14
36
  }
15
37
 
16
38
  /**
@@ -51,27 +73,52 @@ function setup(type: 'module' | 'commonjs') {
51
73
  }
52
74
 
53
75
  // eslint-disable-next-line func-names,max-lines-per-function,max-statements
54
- export default async function ({ type, inDir, outDir }: BuilderOptions): Promise<string[]> {
76
+ export default async function ({ type, entryPoint, inDir, outDir, outFile }: BuilderOptions): Promise<string[]> {
55
77
  const messages: string[] = [];
56
78
 
79
+ assert.ok(
80
+ (entryPoint === undefined && outFile === undefined) || (entryPoint !== undefined && outFile !== undefined),
81
+ 'entryPoint and outFile must both be provided'
82
+ );
83
+
57
84
  /**
58
85
  * Emit declarations using typescript compiler
59
86
  */
60
87
  const allSourceFiles = await getFiles(inDir);
61
- const productionSourceFiles = allSourceFiles.filter(
62
- // && !file.endsWith('.test.ts') && !file.endsWith('.spec.ts')
63
- (file) => file.endsWith('.ts')
64
- );
65
-
66
- const configFile = typescript.readConfigFile('./tsconfig.json', (name) => typescript.sys.readFile(name));
67
- const compilerOptions = typescript.parseJsonConfigFileContent(configFile.config, typescript.sys, './');
88
+ const productionSourceFiles =
89
+ entryPoint === undefined ? allSourceFiles.filter((file) => file.endsWith('.ts')) : [path.join(inDir, entryPoint)];
68
90
 
69
91
  const program = typescript.createProgram(productionSourceFiles, {
70
- ...compilerOptions.options,
92
+ module: typescript.ModuleKind.ESNext,
93
+ moduleResolution: typescript.ModuleResolutionKind.Bundler,
94
+ target: typescript.ScriptTarget.ESNext,
95
+ declaration: true,
71
96
  noEmitOnError: true,
72
97
  emitDeclarationOnly: true,
73
98
  rootDir: inDir,
74
99
  outDir,
100
+ noLib: false,
101
+ skipLibCheck: true,
102
+ strict: true,
103
+ preserveConstEnums: true,
104
+ noImplicitReturns: true,
105
+ noUnusedLocals: true,
106
+ noUnusedParameters: true,
107
+ alwaysStrict: true,
108
+ verbatimModuleSyntax: false,
109
+ noFallthroughCasesInSwitch: true,
110
+ forceConsistentCasingInFileNames: true,
111
+ emitDecoratorMetadata: true,
112
+ experimentalDecorators: true,
113
+ resolveJsonModule: true,
114
+ esModuleInterop: true,
115
+ noUncheckedIndexedAccess: true,
116
+ noPropertyAccessFromIndexSignature: true,
117
+ allowUnusedLabels: false,
118
+ allowUnreachableCode: false,
119
+ noImplicitOverride: true,
120
+ useUnknownInCatchVariables: true,
121
+ exactOptionalPropertyTypes: true,
75
122
  });
76
123
  const emitResult = program.emit();
77
124
  const allDiagnostics = typescript.sortAndDeduplicateDiagnostics([
@@ -101,16 +148,20 @@ export default async function ({ type, inDir, outDir }: BuilderOptions): Promise
101
148
  bundle: true,
102
149
  platform: 'node',
103
150
  format: type === 'module' ? 'esm' : 'cjs',
104
- outdir: outDir,
105
151
  sourcemap: 'inline',
106
152
  sourcesContent: false,
107
- outExtension: { '.js': type === 'module' ? '.mjs' : '.cjs' },
108
- plugins: [
109
- {
110
- name: 'resolve-ts',
111
- setup: setup(type),
112
- },
113
- ],
153
+ ...(outFile === undefined
154
+ ? {
155
+ outdir: outDir,
156
+ outExtension: { '.js': type === 'module' ? '.mjs' : '.cjs' },
157
+ plugins: [
158
+ {
159
+ name: 'resolve-ts',
160
+ setup: setup(type),
161
+ },
162
+ ],
163
+ }
164
+ : { outfile: path.join(outDir, outFile) }),
114
165
  });
115
166
 
116
167
  messages.push(...buildResult.errors.map((error) => `esbuild error: ${error.text}`));
@@ -26,9 +26,59 @@ const exportDefaultFunctionModule = {
26
26
  [`index.ts`]: `export default function () { return 'hello world' }\n`,
27
27
  };
28
28
 
29
- async function write(dir: string, files: Record<string, string>): Promise<void> {
30
- await fs.mkdir(dir, { recursive: true });
31
- await Promise.all(Object.entries(files).map(([name, content]) => fs.writeFile(path.join(dir, name), content)));
29
+ const importExternalModule = {
30
+ [`index.ts`]: `
31
+ import { hello as test } from 'test-esm-module';
32
+ import util from 'node:util';
33
+ export const hello = { test, message: util.format('hello %s', 'world') };
34
+ `,
35
+ };
36
+
37
+ const testNodeModules = {
38
+ [`test-cjs-module`]: {
39
+ source: {
40
+ [`index.js`]: `module.exports.goodbye = 'world';`,
41
+ [`index.d.ts`]: `export declare const goodbye = "world";\n`,
42
+ },
43
+ },
44
+ [`test-esm-module`]: {
45
+ type: 'module',
46
+ source: {
47
+ [`index.js`]: `export const hello = 'world';`,
48
+ [`index.d.ts`]: `export declare const hello = "world";\n`,
49
+ },
50
+ },
51
+ } as const;
52
+
53
+ interface NodeModule {
54
+ [name: string]: {
55
+ type?: 'module' | 'commonjs';
56
+ source: {
57
+ [file: string]: string;
58
+ };
59
+ };
60
+ }
61
+
62
+ async function writeNodeModules(directory: string, nodeModules: NodeModule) {
63
+ const nodeModulesDirectory = path.join(directory, 'node_modules');
64
+ for (const [name, nodeModule] of Object.entries(nodeModules)) {
65
+ const nodeModuleDirectory = path.join(nodeModulesDirectory, name);
66
+ await fs.mkdir(nodeModuleDirectory, { recursive: true });
67
+ await fs.writeFile(
68
+ path.join(nodeModuleDirectory, 'package.json'),
69
+ JSON.stringify({
70
+ type: nodeModule.type ?? 'commonjs',
71
+ })
72
+ );
73
+ for (const [file, content] of Object.entries(nodeModule.source)) {
74
+ await fs.writeFile(path.join(nodeModuleDirectory, file), content);
75
+ }
76
+ }
77
+ }
78
+
79
+ async function write(directory: string, files: Record<string, string>): Promise<void> {
80
+ await fs.mkdir(directory, { recursive: true });
81
+ await Promise.all(Object.entries(files).map(([name, content]) => fs.writeFile(path.join(directory, name), content)));
32
82
  }
33
83
 
34
84
  async function read(dir: string): Promise<Record<string, string>> {
@@ -305,4 +355,59 @@ describe('test builder', () => {
305
355
  const output2 = await import(path.join(outDir, 'index.cjs'));
306
356
  assert.equal(output2.default.default, 'worldworld');
307
357
  });
358
+
359
+ it('should bundle an ESM module that imports a second ESM module', async () => {
360
+ const id = uuid();
361
+ const inDir = path.join(os.tmpdir(), `in-dir-${id}`, 'src');
362
+ const outDir = path.join(os.tmpdir(), `out-dir-${id}`, 'build');
363
+ await write(inDir, twoModules);
364
+ assert.deepEqual(
365
+ await builder({ type: 'module', entryPoint: 'index.ts', outFile: 'index.mjs', inDir, outDir }),
366
+ []
367
+ );
368
+ assert.deepEqual(await read(outDir), {
369
+ 'index.d.ts': 'declare const _default: string;\nexport default _default;\n',
370
+ 'index.mjs':
371
+ 'var hello = "world";\n' +
372
+ '\n' +
373
+ 'var src_default = hello + "world";\n' +
374
+ 'export {\n' +
375
+ ' src_default as default\n' +
376
+ '};\n',
377
+ 'thing.d.ts': 'export declare const hello = "world";\n',
378
+ });
379
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
380
+ const output = await import(path.join(outDir, 'index.mjs'));
381
+ assert.equal(output.default, 'worldworld');
382
+ });
383
+
384
+ it('should bundle an ESM module that imports external modules', async () => {
385
+ const id = uuid();
386
+ const moduleDir = path.join(os.tmpdir(), `in-dir-${id}`);
387
+ const inDir = path.join(moduleDir, 'src');
388
+ const outDir = path.join(os.tmpdir(), `out-dir-${id}`, 'build');
389
+ await write(inDir, importExternalModule);
390
+ await writeNodeModules(moduleDir, testNodeModules);
391
+ assert.deepEqual(
392
+ await builder({ type: 'module', entryPoint: 'index.ts', outFile: 'index.mjs', inDir, outDir }),
393
+ []
394
+ );
395
+ assert.deepEqual(await read(outDir), {
396
+ 'index.d.ts': 'export declare const hello: {\n test: string;\n message: string;\n};\n',
397
+ 'index.mjs':
398
+ 'var hello = "world";\n' +
399
+ '\n' +
400
+ 'import util from "node:util";\n' +
401
+ 'var hello2 = { test: hello, message: util.format("hello %s", "world") };\n' +
402
+ 'export {\n' +
403
+ ' hello2 as hello\n' +
404
+ '};\n',
405
+ });
406
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
407
+ const output = await import(path.join(outDir, 'index.mjs'));
408
+ assert.deepEqual(output.hello, {
409
+ message: 'hello world',
410
+ test: 'world',
411
+ });
412
+ });
308
413
  });