@knighted/duel 3.1.0 → 3.2.0

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/README.md CHANGED
@@ -71,16 +71,14 @@ Assuming an `outDir` of `dist`, running the above will create `dist/esm` and `di
71
71
 
72
72
  ### Module transforms
73
73
 
74
- TypeScript will throw compiler errors when using `import.meta` globals while targeting a CommonJS dual build, but _will not_ throw compiler errors when the inverse is true, i.e. using CommonJS globals (`__filename`, `__dirname`, etc.) while targeting an ES module dual build. There is an [open issue](https://github.com/microsoft/TypeScript/issues/58658) regarding this unexpected behavior. You can use the `--modules` option to have the [differences between ES modules and CommonJS](https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs) transformed by `duel` prior to running compilation with `tsc` so that there are no compilation or runtime errors.
74
+ TypeScript will throw compiler errors when using `import.meta` globals while targeting a CommonJS dual build, but _will not_ throw compiler errors when the inverse is true, i.e. using CommonJS globals (`__filename`, `__dirname`, etc.) while targeting an ES module dual build. There is an [open issue](https://github.com/microsoft/TypeScript/issues/58658) regarding this asymmetry. Prefer the single-switch `--mode` interface: `--mode globals` (equivalent to `--modules`) or `--mode full` (equivalent to `--modules --transform-syntax`) to have the [ESM vs CJS differences](https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs) transformed by `duel` prior to running `tsc` so you avoid compilation or runtime errors. The legacy `--modules`/`--transform-syntax` flags remain supported.
75
75
 
76
76
  `duel` infers the primary vs dual build orientation from your `package.json` `type`:
77
77
 
78
78
  - `"type": "module"` → primary ESM, dual CJS
79
79
  - `"type": "commonjs"` → primary CJS, dual ESM
80
80
 
81
- The `--dirs` flag nests outputs under `outDir/esm` and `outDir/cjs` accordingly.
82
-
83
- Note, there is a slight performance penalty since your project needs to be copied first to run the transforms before compiling with `tsc`.
81
+ The `--dirs` flag nests outputs under `outDir/esm` and `outDir/cjs` accordingly. There is a small performance cost because sources are copied to run the transform before `tsc`.
84
82
 
85
83
  ```json
86
84
  "scripts": {
@@ -88,7 +86,31 @@ Note, there is a slight performance penalty since your project needs to be copie
88
86
  }
89
87
  ```
90
88
 
91
- This feature is still a work in progress regarding transforming `exports` when targeting an ES module build (relies on [`@knighted/module`](https://github.com/knightedcodemonkey/module)).
89
+ For projects that need full syntax lowering, opt in explicitly:
90
+
91
+ ```json
92
+ "scripts": {
93
+ "build": "duel --modules --transform-syntax"
94
+ }
95
+ ```
96
+
97
+ Using the single switch:
98
+
99
+ ```json
100
+ "scripts": {
101
+ "build": "duel --mode globals"
102
+ }
103
+ ```
104
+
105
+ ```json
106
+ "scripts": {
107
+ "build": "duel --mode full"
108
+ }
109
+ ```
110
+
111
+ #### Pre-`tsc` transform (TypeScript 58658)
112
+
113
+ When you enable module transforms (`--mode globals`, `--mode full`, or `--modules`), `duel` copies your sources and runs [`@knighted/module`](https://github.com/knightedcodemonkey/module) **before** `tsc` so the transformed files no longer trigger TypeScript’s asymmetrical module-global errors (see [TypeScript#58658](https://github.com/microsoft/TypeScript/issues/58658)). No extra setup is needed: module transforms are the pre-`tsc` mitigation. If you also select full lowering (`--mode full` or `--transform-syntax`), that pre-`tsc` step performs full lowering instead of globals-only.
92
114
 
93
115
  ## Options
94
116
 
@@ -96,25 +118,16 @@ The available options are limited, because you should define most of them inside
96
118
 
97
119
  - `--project, -p` The path to the project's configuration file. Defaults to `tsconfig.json`.
98
120
  - `--pkg-dir, -k` The directory to start looking for a package.json file. Defaults to `--project` dir.
121
+ - `--mode` Optional shorthand for the module transform mode: `none` (default), `globals` (modules + globals-only), `full` (modules + full syntax lowering). Recommended.
99
122
  - `--modules, -m` Transform module globals for dual build target. Defaults to false.
123
+ - `--transform-syntax, -s` Opt in to full syntax lowering via `@knighted/module` (default is globals-only). Implies `--modules`.
100
124
  - `--dirs, -d` Outputs both builds to directories inside of `outDir`. Defaults to `false`.
101
125
  - `--exports, -e` Generate `package.json` `exports` from build output. Values: `wildcard` | `dir` | `name`.
102
126
 
103
127
  > [!NOTE]
104
128
  > Exports keys are extensionless by design; the target `import`/`require`/`types` entries keep explicit file extensions so Node resolution remains deterministic.
105
129
 
106
- You can run `duel --help` to get the same info. Below is the output of that:
107
-
108
- ```console
109
- Usage: duel [options]
110
-
111
- Options:
112
- --project, -p [path] Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.
113
- --pkg-dir, -k [path] The directory to start looking for a package.json file. Defaults to --project directory.
114
- --modules, -m Transform module globals for dual build target. Defaults to false.
115
- --dirs, -d Output both builds to directories inside of outDir. [esm, cjs].
116
- --help, -h Print this message.
117
- ```
130
+ You can run `duel --help` to get the same info.
118
131
 
119
132
  ## Gotchas
120
133
 
@@ -122,7 +135,7 @@ These are definitely edge cases, and would only really come up if your project m
122
135
 
123
136
  - This is going to work best if your CJS-first project uses file extensions in _relative_ specifiers. This is completely acceptable in CJS projects, and [required in ESM projects](https://nodejs.org/api/esm.html#import-specifiers). This package makes no attempt to rewrite bare specifiers, or remap any relative specifiers to a directory index.
124
137
 
125
- - Unfortunately, `tsc` doesn't support [dual packages](https://nodejs.org/api/packages.html#dual-commonjses-module-packages) completely. One instance of unexpected behavior is when the compiler throws errors for ES module globals when running a dual CJS build, but not for the inverse case, despite both causing runtime errors in Node.js. See the [open issue](https://github.com/microsoft/TypeScript/issues/58658). You can circumvent this with `duel` by using the `--modules` option if your project uses module globals such as `import.meta` properties or `__dirname`, `__filename`, etc. in a CommonJS project.
138
+ - Unfortunately, `tsc` doesn't support [dual packages](https://nodejs.org/api/packages.html#dual-commonjses-module-packages) completely. For mitigation details, see the pre-`tsc` transform note above (`--modules`).
126
139
 
127
140
  - If running `duel` with your project's package.json file open in your editor, you may temporarily see the content replaced. This is because `duel` dynamically creates a new package.json using the `type` necessary for the dual build. Your original package.json will be restored after the build completes.
128
141
 
package/dist/cjs/duel.cjs CHANGED
@@ -184,7 +184,7 @@ const generateExports = async (options) => {
184
184
  const duel = async (args) => {
185
185
  const ctx = await (0, init_js_1.init)(args);
186
186
  if (ctx) {
187
- const { projectDir, tsconfig, configPath, modules, dirs, pkg, exports: exportsOpt, } = ctx;
187
+ const { projectDir, tsconfig, configPath, modules, dirs, transformSyntax, pkg, exports: exportsOpt, } = ctx;
188
188
  const tsc = await (0, find_up_1.findUp)(async (dir) => {
189
189
  const tscBin = (0, node_path_1.join)(dir, 'node_modules', '.bin', 'tsc');
190
190
  try {
@@ -233,6 +233,7 @@ const duel = async (args) => {
233
233
  const runPrimaryBuild = () => {
234
234
  return runBuild(configPath, primaryOutDir);
235
235
  };
236
+ const syntaxMode = transformSyntax ? true : 'globals-only';
236
237
  const updateSpecifiersAndFileExtensions = async (filenames, target, ext) => {
237
238
  for (const filename of filenames) {
238
239
  const dts = /(\.d\.ts)$/;
@@ -258,6 +259,7 @@ const duel = async (args) => {
258
259
  const writeOptions = {
259
260
  target,
260
261
  rewriteSpecifier,
262
+ transformSyntax: syntaxMode,
261
263
  ...(outFilename === filename ? { inPlace: true } : { out: outFilename }),
262
264
  };
263
265
  await (0, module_1.transform)(filename, writeOptions);
@@ -267,7 +269,7 @@ const duel = async (args) => {
267
269
  }
268
270
  };
269
271
  const logSuccess = start => {
270
- (0, util_js_1.log)(`Successfully created a dual ${isCjsBuild ? 'CJS' : 'ESM'} build in ${Math.round(node_perf_hooks_1.performance.now() - start)}ms.`);
272
+ (0, util_js_1.logSuccess)(`Successfully created a dual ${isCjsBuild ? 'CJS' : 'ESM'} build in ${Math.round(node_perf_hooks_1.performance.now() - start)}ms.`);
271
273
  };
272
274
  (0, util_js_1.log)('Starting primary build...');
273
275
  let success = false;
@@ -303,18 +305,12 @@ const duel = async (args) => {
303
305
  ignore: 'node_modules/**',
304
306
  });
305
307
  for (const file of toTransform) {
306
- /**
307
- * Maybe include the option to transform modules implicitly
308
- * (modules: true) so that `exports` are correctly converted
309
- * when targeting a CJS dual build. Depends on @knighted/module
310
- * supporting he `modules` option.
311
- *
312
- * @see https://github.com/microsoft/TypeScript/issues/58658
313
- */
308
+ const isTsLike = /\.[cm]?tsx?$/.test(file);
309
+ const transformSyntaxMode = syntaxMode === true && isTsLike ? 'globals-only' : syntaxMode;
314
310
  await (0, module_1.transform)(file, {
315
311
  out: file,
316
312
  target: isCjsBuild ? 'commonjs' : 'module',
317
- transformSyntax: false,
313
+ transformSyntax: transformSyntaxMode,
318
314
  });
319
315
  }
320
316
  }
package/dist/cjs/init.cjs CHANGED
@@ -41,6 +41,14 @@ const init = async (args) => {
41
41
  type: 'string',
42
42
  short: 'e',
43
43
  },
44
+ 'transform-syntax': {
45
+ type: 'boolean',
46
+ short: 's',
47
+ default: false,
48
+ },
49
+ mode: {
50
+ type: 'string',
51
+ },
44
52
  help: {
45
53
  type: 'boolean',
46
54
  short: 'h',
@@ -55,20 +63,27 @@ const init = async (args) => {
55
63
  return false;
56
64
  }
57
65
  if (parsed.help) {
58
- (0, util_js_1.log)('Usage: duel [options]\n');
59
- (0, util_js_1.log)('Options:');
60
- (0, util_js_1.log)("--project, -p [path] \t Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.");
61
- (0, util_js_1.log)('--pkg-dir, -k [path] \t The directory to start looking for a package.json file. Defaults to --project directory.');
62
- (0, util_js_1.log)('--modules, -m \t\t Transform module globals for dual build target. Defaults to false.');
63
- (0, util_js_1.log)('--dirs, -d \t\t Output both builds to directories inside of outDir. [esm, cjs].');
64
- (0, util_js_1.log)('--exports, -e \t Generate package.json exports. Values: wildcard | dir | name.');
65
- (0, util_js_1.log)('--help, -h \t\t Print this message.');
66
+ const bare = { bare: true };
67
+ (0, util_js_1.log)('Usage: duel [options]\n', 'info', bare);
68
+ (0, util_js_1.log)('Options:', 'info', bare);
69
+ (0, util_js_1.log)("--project, -p [path] \t\t Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.", 'info', bare);
70
+ (0, util_js_1.log)('--pkg-dir, -k [path] \t\t The directory to start looking for a package.json file. Defaults to --project directory.', 'info', bare);
71
+ (0, util_js_1.log)('--modules, -m \t\t\t Transform module globals for dual build target. Defaults to false.', 'info', bare);
72
+ (0, util_js_1.log)('--dirs, -d \t\t\t Output both builds to directories inside of outDir. [esm, cjs].', 'info', bare);
73
+ (0, util_js_1.log)('--exports, -e \t\t\t Generate package.json exports. Values: wildcard | dir | name.', 'info', bare);
74
+ (0, util_js_1.log)('--transform-syntax, -s \t\t Opt in to full syntax lowering via @knighted/module (default is globals-only).', 'info', bare);
75
+ (0, util_js_1.log)('--mode [none|globals|full] \t Optional shorthand for module transforms and syntax lowering.', 'info', bare);
76
+ (0, util_js_1.log)('--help, -h \t\t\t Print this message.', 'info', bare);
66
77
  }
67
78
  else {
68
- const { project, 'target-extension': targetExt, 'pkg-dir': pkgDir, modules, dirs, exports: exportsOpt, } = parsed;
79
+ const { project, 'target-extension': targetExt, 'pkg-dir': pkgDir, modules, dirs, exports: exportsOpt, 'transform-syntax': transformSyntax, mode, } = parsed;
69
80
  let configPath = (0, node_path_1.resolve)(project);
70
81
  let stats = null;
71
82
  let pkg = null;
83
+ if (mode && !['none', 'globals', 'full'].includes(mode)) {
84
+ (0, util_js_1.logError)('--mode expects one of: none | globals | full');
85
+ return false;
86
+ }
72
87
  if (targetExt) {
73
88
  (0, util_js_1.logError)('--target-extension is deprecated. Define "type" in your package.json instead and the dual build will be inferred from that.');
74
89
  return false;
@@ -105,10 +120,30 @@ const init = async (args) => {
105
120
  (0, util_js_1.logError)('--exports expects one of: wildcard | dir | name');
106
121
  return false;
107
122
  }
123
+ let modulesFinal = modules;
124
+ let transformSyntaxFinal = transformSyntax;
125
+ if (mode) {
126
+ if (mode === 'none') {
127
+ modulesFinal = false;
128
+ transformSyntaxFinal = false;
129
+ }
130
+ else if (mode === 'globals') {
131
+ modulesFinal = true;
132
+ transformSyntaxFinal = false;
133
+ }
134
+ else if (mode === 'full') {
135
+ modulesFinal = true;
136
+ transformSyntaxFinal = true;
137
+ }
138
+ }
139
+ else if (transformSyntax && !modules) {
140
+ modulesFinal = true;
141
+ }
108
142
  return {
109
143
  pkg,
110
144
  dirs,
111
- modules,
145
+ modules: modulesFinal,
146
+ transformSyntax: transformSyntaxFinal,
112
147
  exports: exportsOpt,
113
148
  tsconfig,
114
149
  projectDir,
@@ -2,6 +2,7 @@ export function init(args: any): Promise<false | {
2
2
  pkg: import("read-package-up", { with: { "resolution-mode": "import" } }).NormalizedReadResult;
3
3
  dirs: boolean;
4
4
  modules: boolean;
5
+ transformSyntax: boolean;
5
6
  exports: string | undefined;
6
7
  tsconfig: {
7
8
  compilerOptions?: import("get-tsconfig").TsConfigJson.CompilerOptions | undefined;
package/dist/cjs/util.cjs CHANGED
@@ -1,17 +1,43 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getCompileFiles = exports.getRealPathAsFileUrl = exports.logError = exports.log = void 0;
3
+ exports.getCompileFiles = exports.getRealPathAsFileUrl = exports.logWarn = exports.logSuccess = exports.logError = exports.log = void 0;
4
4
  const node_url_1 = require("node:url");
5
5
  const promises_1 = require("node:fs/promises");
6
6
  const node_child_process_1 = require("node:child_process");
7
7
  const node_process_1 = require("node:process");
8
8
  const node_os_1 = require("node:os");
9
- const log = (color = '\x1b[30m', msg = '') => {
9
+ const COLORS = {
10
+ reset: '\x1b[0m',
11
+ info: '\x1b[36m',
12
+ success: '\x1b[32m',
13
+ warn: '\x1b[33m',
14
+ error: '\x1b[31m',
15
+ };
16
+ const log = (msg = '', level = 'info', opts = {}) => {
17
+ const { bare = false } = opts;
18
+ const palette = {
19
+ info: COLORS.info,
20
+ success: COLORS.success,
21
+ warn: COLORS.warn,
22
+ error: COLORS.error,
23
+ };
24
+ const badge = {
25
+ success: '[✓]',
26
+ warn: '[!]',
27
+ error: '[x]',
28
+ info: '[i]',
29
+ }[level];
30
+ const color = palette[level] ?? COLORS.info;
31
+ const prefix = !bare && badge ? `${badge} ` : '';
10
32
  // eslint-disable-next-line no-console
11
- console.log(`${color}%s\x1b[0m`, msg);
33
+ console.log(`${color}${prefix}%s${COLORS.reset}`, msg);
12
34
  };
13
35
  exports.log = log;
14
- const logError = log.bind(null, '\x1b[31m');
36
+ const logSuccess = msg => log(msg, 'success');
37
+ exports.logSuccess = logSuccess;
38
+ const logWarn = msg => log(msg, 'warn');
39
+ exports.logWarn = logWarn;
40
+ const logError = msg => log(msg, 'error');
15
41
  exports.logError = logError;
16
42
  const getRealPathAsFileUrl = async (path) => {
17
43
  const realPath = await (0, promises_1.realpath)(path);
@@ -1,4 +1,6 @@
1
- export function log(color?: string, msg?: string): void;
2
- export const logError: (msg?: string | undefined) => void;
1
+ export function log(msg?: string, level?: string, opts?: {}): void;
2
+ export function logError(msg: any): void;
3
+ export function logSuccess(msg: any): void;
4
+ export function logWarn(msg: any): void;
3
5
  export function getRealPathAsFileUrl(path: any): Promise<string>;
4
6
  export function getCompileFiles(tscBinPath: any, wd?: string): string[];
package/dist/esm/duel.js CHANGED
@@ -9,7 +9,7 @@ import { glob } from 'glob';
9
9
  import { findUp } from 'find-up';
10
10
  import { transform } from '@knighted/module';
11
11
  import { init } from './init.js';
12
- import { getRealPathAsFileUrl, getCompileFiles, logError, log } from './util.js';
12
+ import { getRealPathAsFileUrl, getCompileFiles, log, logError, logSuccess as logSuccessBadge, } from './util.js';
13
13
  const stripKnownExt = path => {
14
14
  return path.replace(/(\.d\.(?:ts|mts|cts)|\.(?:mjs|cjs|js))$/, '');
15
15
  };
@@ -181,7 +181,7 @@ const generateExports = async (options) => {
181
181
  const duel = async (args) => {
182
182
  const ctx = await init(args);
183
183
  if (ctx) {
184
- const { projectDir, tsconfig, configPath, modules, dirs, pkg, exports: exportsOpt, } = ctx;
184
+ const { projectDir, tsconfig, configPath, modules, dirs, transformSyntax, pkg, exports: exportsOpt, } = ctx;
185
185
  const tsc = await findUp(async (dir) => {
186
186
  const tscBin = join(dir, 'node_modules', '.bin', 'tsc');
187
187
  try {
@@ -230,6 +230,7 @@ const duel = async (args) => {
230
230
  const runPrimaryBuild = () => {
231
231
  return runBuild(configPath, primaryOutDir);
232
232
  };
233
+ const syntaxMode = transformSyntax ? true : 'globals-only';
233
234
  const updateSpecifiersAndFileExtensions = async (filenames, target, ext) => {
234
235
  for (const filename of filenames) {
235
236
  const dts = /(\.d\.ts)$/;
@@ -255,6 +256,7 @@ const duel = async (args) => {
255
256
  const writeOptions = {
256
257
  target,
257
258
  rewriteSpecifier,
259
+ transformSyntax: syntaxMode,
258
260
  ...(outFilename === filename ? { inPlace: true } : { out: outFilename }),
259
261
  };
260
262
  await transform(filename, writeOptions);
@@ -264,7 +266,7 @@ const duel = async (args) => {
264
266
  }
265
267
  };
266
268
  const logSuccess = start => {
267
- log(`Successfully created a dual ${isCjsBuild ? 'CJS' : 'ESM'} build in ${Math.round(performance.now() - start)}ms.`);
269
+ logSuccessBadge(`Successfully created a dual ${isCjsBuild ? 'CJS' : 'ESM'} build in ${Math.round(performance.now() - start)}ms.`);
268
270
  };
269
271
  log('Starting primary build...');
270
272
  let success = false;
@@ -300,18 +302,12 @@ const duel = async (args) => {
300
302
  ignore: 'node_modules/**',
301
303
  });
302
304
  for (const file of toTransform) {
303
- /**
304
- * Maybe include the option to transform modules implicitly
305
- * (modules: true) so that `exports` are correctly converted
306
- * when targeting a CJS dual build. Depends on @knighted/module
307
- * supporting he `modules` option.
308
- *
309
- * @see https://github.com/microsoft/TypeScript/issues/58658
310
- */
305
+ const isTsLike = /\.[cm]?tsx?$/.test(file);
306
+ const transformSyntaxMode = syntaxMode === true && isTsLike ? 'globals-only' : syntaxMode;
311
307
  await transform(file, {
312
308
  out: file,
313
309
  target: isCjsBuild ? 'commonjs' : 'module',
314
- transformSyntax: false,
310
+ transformSyntax: transformSyntaxMode,
315
311
  });
316
312
  }
317
313
  }
@@ -2,6 +2,7 @@ export function init(args: any): Promise<false | {
2
2
  pkg: import("read-package-up").NormalizedReadResult;
3
3
  dirs: boolean;
4
4
  modules: boolean;
5
+ transformSyntax: boolean;
5
6
  exports: string | undefined;
6
7
  tsconfig: {
7
8
  compilerOptions?: import("get-tsconfig").TsConfigJson.CompilerOptions | undefined;
package/dist/esm/init.js CHANGED
@@ -38,6 +38,14 @@ const init = async (args) => {
38
38
  type: 'string',
39
39
  short: 'e',
40
40
  },
41
+ 'transform-syntax': {
42
+ type: 'boolean',
43
+ short: 's',
44
+ default: false,
45
+ },
46
+ mode: {
47
+ type: 'string',
48
+ },
41
49
  help: {
42
50
  type: 'boolean',
43
51
  short: 'h',
@@ -52,20 +60,27 @@ const init = async (args) => {
52
60
  return false;
53
61
  }
54
62
  if (parsed.help) {
55
- log('Usage: duel [options]\n');
56
- log('Options:');
57
- log("--project, -p [path] \t Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.");
58
- log('--pkg-dir, -k [path] \t The directory to start looking for a package.json file. Defaults to --project directory.');
59
- log('--modules, -m \t\t Transform module globals for dual build target. Defaults to false.');
60
- log('--dirs, -d \t\t Output both builds to directories inside of outDir. [esm, cjs].');
61
- log('--exports, -e \t Generate package.json exports. Values: wildcard | dir | name.');
62
- log('--help, -h \t\t Print this message.');
63
+ const bare = { bare: true };
64
+ log('Usage: duel [options]\n', 'info', bare);
65
+ log('Options:', 'info', bare);
66
+ log("--project, -p [path] \t\t Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.", 'info', bare);
67
+ log('--pkg-dir, -k [path] \t\t The directory to start looking for a package.json file. Defaults to --project directory.', 'info', bare);
68
+ log('--modules, -m \t\t\t Transform module globals for dual build target. Defaults to false.', 'info', bare);
69
+ log('--dirs, -d \t\t\t Output both builds to directories inside of outDir. [esm, cjs].', 'info', bare);
70
+ log('--exports, -e \t\t\t Generate package.json exports. Values: wildcard | dir | name.', 'info', bare);
71
+ log('--transform-syntax, -s \t\t Opt in to full syntax lowering via @knighted/module (default is globals-only).', 'info', bare);
72
+ log('--mode [none|globals|full] \t Optional shorthand for module transforms and syntax lowering.', 'info', bare);
73
+ log('--help, -h \t\t\t Print this message.', 'info', bare);
63
74
  }
64
75
  else {
65
- const { project, 'target-extension': targetExt, 'pkg-dir': pkgDir, modules, dirs, exports: exportsOpt, } = parsed;
76
+ const { project, 'target-extension': targetExt, 'pkg-dir': pkgDir, modules, dirs, exports: exportsOpt, 'transform-syntax': transformSyntax, mode, } = parsed;
66
77
  let configPath = resolve(project);
67
78
  let stats = null;
68
79
  let pkg = null;
80
+ if (mode && !['none', 'globals', 'full'].includes(mode)) {
81
+ logError('--mode expects one of: none | globals | full');
82
+ return false;
83
+ }
69
84
  if (targetExt) {
70
85
  logError('--target-extension is deprecated. Define "type" in your package.json instead and the dual build will be inferred from that.');
71
86
  return false;
@@ -102,10 +117,30 @@ const init = async (args) => {
102
117
  logError('--exports expects one of: wildcard | dir | name');
103
118
  return false;
104
119
  }
120
+ let modulesFinal = modules;
121
+ let transformSyntaxFinal = transformSyntax;
122
+ if (mode) {
123
+ if (mode === 'none') {
124
+ modulesFinal = false;
125
+ transformSyntaxFinal = false;
126
+ }
127
+ else if (mode === 'globals') {
128
+ modulesFinal = true;
129
+ transformSyntaxFinal = false;
130
+ }
131
+ else if (mode === 'full') {
132
+ modulesFinal = true;
133
+ transformSyntaxFinal = true;
134
+ }
135
+ }
136
+ else if (transformSyntax && !modules) {
137
+ modulesFinal = true;
138
+ }
105
139
  return {
106
140
  pkg,
107
141
  dirs,
108
- modules,
142
+ modules: modulesFinal,
143
+ transformSyntax: transformSyntaxFinal,
109
144
  exports: exportsOpt,
110
145
  tsconfig,
111
146
  projectDir,
@@ -1,4 +1,6 @@
1
- export function log(color?: string, msg?: string): void;
2
- export const logError: (msg?: string | undefined) => void;
1
+ export function log(msg?: string, level?: string, opts?: {}): void;
2
+ export function logError(msg: any): void;
3
+ export function logSuccess(msg: any): void;
4
+ export function logWarn(msg: any): void;
3
5
  export function getRealPathAsFileUrl(path: any): Promise<string>;
4
6
  export function getCompileFiles(tscBinPath: any, wd?: string): string[];
package/dist/esm/util.js CHANGED
@@ -3,11 +3,35 @@ import { realpath } from 'node:fs/promises';
3
3
  import { spawnSync } from 'node:child_process';
4
4
  import { cwd, platform } from 'node:process';
5
5
  import { EOL } from 'node:os';
6
- const log = (color = '\x1b[30m', msg = '') => {
6
+ const COLORS = {
7
+ reset: '\x1b[0m',
8
+ info: '\x1b[36m',
9
+ success: '\x1b[32m',
10
+ warn: '\x1b[33m',
11
+ error: '\x1b[31m',
12
+ };
13
+ const log = (msg = '', level = 'info', opts = {}) => {
14
+ const { bare = false } = opts;
15
+ const palette = {
16
+ info: COLORS.info,
17
+ success: COLORS.success,
18
+ warn: COLORS.warn,
19
+ error: COLORS.error,
20
+ };
21
+ const badge = {
22
+ success: '[✓]',
23
+ warn: '[!]',
24
+ error: '[x]',
25
+ info: '[i]',
26
+ }[level];
27
+ const color = palette[level] ?? COLORS.info;
28
+ const prefix = !bare && badge ? `${badge} ` : '';
7
29
  // eslint-disable-next-line no-console
8
- console.log(`${color}%s\x1b[0m`, msg);
30
+ console.log(`${color}${prefix}%s${COLORS.reset}`, msg);
9
31
  };
10
- const logError = log.bind(null, '\x1b[31m');
32
+ const logSuccess = msg => log(msg, 'success');
33
+ const logWarn = msg => log(msg, 'warn');
34
+ const logError = msg => log(msg, 'error');
11
35
  const getRealPathAsFileUrl = async (path) => {
12
36
  const realPath = await realpath(path);
13
37
  const asFileUrl = pathToFileURL(realPath).href;
@@ -24,4 +48,4 @@ const getCompileFiles = (tscBinPath, wd = cwd()) => {
24
48
  .split(EOL)
25
49
  .filter(path => !/node_modules|^$/.test(path));
26
50
  };
27
- export { log, logError, getRealPathAsFileUrl, getCompileFiles };
51
+ export { log, logError, logSuccess, logWarn, getRealPathAsFileUrl, getCompileFiles };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/duel",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "TypeScript dual packages.",
5
5
  "type": "module",
6
6
  "main": "dist/esm/duel.js",
@@ -67,7 +67,6 @@
67
67
  },
68
68
  "devDependencies": {
69
69
  "@eslint/js": "^9.39.1",
70
- "@knighted/module": "^1.0.0-rc.2",
71
70
  "@tsconfig/recommended": "^1.0.10",
72
71
  "@types/node": "^24.10.1",
73
72
  "c8": "^10.1.3",
@@ -83,6 +82,7 @@
83
82
  "vite": "^7.2.4"
84
83
  },
85
84
  "dependencies": {
85
+ "@knighted/module": "^1.0.0-rc.6",
86
86
  "find-up": "^8.0.0",
87
87
  "get-tsconfig": "^4.13.0",
88
88
  "glob": "^13.0.0",