dtsroll 1.2.0 → 1.4.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
@@ -1,4 +1,10 @@
1
- # dtsroll
1
+ <p align="center">
2
+ <img width="150" src="./.github/logo.webp">
3
+ </p>
4
+
5
+ <h2 align="center">
6
+ dtsroll
7
+ </h2>
2
8
 
3
9
  _dtsroll_ is a CLI tool for bundling TypeScript declaration (`.d.ts`) files.
4
10
 
@@ -16,6 +22,11 @@ _dtsroll_ is a CLI tool for bundling TypeScript declaration (`.d.ts`) files.
16
22
 
17
23
  Flattens multiple files into one, reducing TypeScript's file resolution for type checking.
18
24
 
25
+ <p align="center">
26
+ <img width="600" src="./.github/screenshot.webp">
27
+ </p>
28
+
29
+
19
30
  ## Install
20
31
  ```
21
32
  npm install --save-dev dtsroll
@@ -82,6 +93,13 @@ To fix this, _dtsroll_ will display a warning suggesting you move the `@types/*`
82
93
 
83
94
  ## CLI
84
95
 
96
+ ### Usage
97
+ ```sh
98
+ dtsroll [-flags] [...entry dts files]
99
+ ```
100
+
101
+ The default usage is to run `dtsroll` without any flags/arguments in a directory containing a `package.json` file. This allows the configuration to be inferred automatically.
102
+
85
103
  ### --help, -h
86
104
  Display usage instructions.
87
105
 
package/dist/cli.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { cli } from 'cleye';
3
- import { b as bgYellow, a as black, d as dtsroll } from './index--BBQtOEp.mjs';
4
- import { l as logOutput } from './log-output-CwMUFOmQ.mjs';
3
+ import { b as bgYellow, a as black, d as dtsroll, l as logOutput } from './index-BR0PvbQA.mjs';
5
4
  import 'node:path';
6
5
  import 'node:fs/promises';
7
6
  import 'rollup';
@@ -10,7 +9,7 @@ import '@rollup/plugin-node-resolve';
10
9
  import 'byte-size';
11
10
 
12
11
  var name = "dtsroll";
13
- var version = "1.2.0";
12
+ var version = "1.4.0";
14
13
  var description = "Bundle dts files";
15
14
 
16
15
  const argv = cli({
@@ -3,6 +3,7 @@ import fs from 'node:fs/promises';
3
3
  import { rollup } from 'rollup';
4
4
  import { dts } from 'rollup-plugin-dts';
5
5
  import nodeResolve from '@rollup/plugin-node-resolve';
6
+ import byteSize from 'byte-size';
6
7
 
7
8
  let enabled = true;
8
9
  // Support both browser and node environments
@@ -90,8 +91,116 @@ const magenta = kolorist(35, 39);
90
91
  const lightYellow = kolorist(93, 39);
91
92
  const bgYellow = kolorist(43, 49);
92
93
 
93
- const dtsExtension = ".d.ts";
94
+ const cwd = process.cwd();
95
+
96
+ const isPath = (filePath) => filePath[0] === "." || path.isAbsolute(filePath);
97
+ const normalizePath = (filepath) => filepath.replaceAll("\\", "/");
98
+
94
99
  const warningSignUnicode = "\u26A0";
100
+ const warningPrefix = yellow("Warning:");
101
+ const logOutput = (dtsOutput) => {
102
+ console.log(underline("dtsroll"));
103
+ const { inputs } = dtsOutput;
104
+ const isCliInput = inputs[0]?.[1] === void 0;
105
+ console.log(bold(`
106
+ \u{1F4E5} Entry points${isCliInput ? "" : " in package.json"}`));
107
+ console.log(
108
+ inputs.map(([inputFile, inputSource, error]) => {
109
+ const relativeInputFile = path.relative(cwd, inputFile);
110
+ const logPath2 = normalizePath(
111
+ relativeInputFile.length < inputFile.length ? relativeInputFile : inputFile
112
+ );
113
+ if (error) {
114
+ return ` ${lightYellow(`${warningSignUnicode} ${logPath2} ${dim(error)}`)}`;
115
+ }
116
+ return ` \u2192 ${green(logPath2)}${inputSource ? ` ${dim(`from ${inputSource}`)}` : ""}`;
117
+ }).join("\n")
118
+ );
119
+ if ("error" in dtsOutput) {
120
+ console.error(`${red("Error:")} ${dtsOutput.error}`);
121
+ return;
122
+ }
123
+ const {
124
+ outputDirectory,
125
+ output: {
126
+ entries: outputEntries,
127
+ chunks: outputChunks
128
+ },
129
+ size,
130
+ externals
131
+ } = dtsOutput;
132
+ const outputDirectoryRelative = path.relative(cwd, outputDirectory);
133
+ const logPath = `${normalizePath(
134
+ outputDirectoryRelative.length < outputDirectory.length ? outputDirectoryRelative : outputDirectory
135
+ )}/`;
136
+ const logChunk = ({
137
+ file,
138
+ indent,
139
+ bullet,
140
+ color
141
+ }) => {
142
+ const sizeFormatted = byteSize(file.size).toString();
143
+ let log = `${indent}${bullet} ${dim(color(logPath))}${color(file.fileName)} ${sizeFormatted}`;
144
+ const { moduleIds, moduleToPackage } = file;
145
+ log += `
146
+ ${moduleIds.sort().map((moduleId, index) => {
147
+ const isLast = index === moduleIds.length - 1;
148
+ const prefix = `${indent} ${isLast ? "\u2514\u2500 " : "\u251C\u2500 "}`;
149
+ const relativeModuleId = path.relative(cwd, moduleId);
150
+ const logModuleId = normalizePath(
151
+ relativeModuleId.length < moduleId.length ? relativeModuleId : moduleId
152
+ );
153
+ const bareSpecifier = moduleToPackage[moduleId];
154
+ if (bareSpecifier) {
155
+ return `${prefix}${dim(`${magenta(bareSpecifier)} (${logModuleId})`)}`;
156
+ }
157
+ return `${prefix}${dim(logModuleId)}`;
158
+ }).join("\n")}`;
159
+ return log;
160
+ };
161
+ console.log(bold("\n\u{1F4A0} Bundled output"));
162
+ console.log(
163
+ outputEntries.map((file) => logChunk({
164
+ file,
165
+ indent: " ",
166
+ bullet: "\u25CF",
167
+ color: green
168
+ })).join("\n\n")
169
+ );
170
+ if (outputChunks.length > 0) {
171
+ console.log(bold("\n Chunks"));
172
+ console.log(
173
+ outputChunks.map((file) => logChunk({
174
+ file,
175
+ indent: " ",
176
+ bullet: "\u25A0",
177
+ color: yellow
178
+ })).join("\n\n")
179
+ );
180
+ }
181
+ console.log(bold("\n\u2696\uFE0F Size savings"));
182
+ const difference = size.input - size.output;
183
+ const direction = difference > 0 ? "decrease" : "increase";
184
+ const percentage = (Math.abs(difference / size.input) * 100).toFixed(0);
185
+ console.log(` Input source size: ${byteSize(size.input).toString()}`);
186
+ console.log(` Bundled output size: ${byteSize(size.output).toString()}${difference === 0 ? "" : ` (${percentage}% ${direction})`}`);
187
+ if (externals.length > 0) {
188
+ console.log(bold("\n\u{1F4E6} External packages"));
189
+ console.log(
190
+ externals.map(([packageName, reason, devTypePackage]) => {
191
+ let stdout = ` \u2500 ${magenta(packageName)} ${dim(`externalized ${reason}`)}`;
192
+ if (devTypePackage) {
193
+ stdout += `
194
+ ${warningPrefix} ${magenta(devTypePackage)} should not be in devDependencies if ${magenta(packageName)} is externalized`;
195
+ }
196
+ return stdout;
197
+ }).sort().join("\n")
198
+ );
199
+ }
200
+ };
201
+
202
+ const dtsExtensions = [".d.ts", ".d.cts", ".d.mts"];
203
+ const isDts = (fileName) => dtsExtensions.some((extension) => fileName.endsWith(extension));
95
204
 
96
205
  const isValidIdentifier = /^[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*$/u;
97
206
  const reservedWords = /* @__PURE__ */ new Set([
@@ -182,14 +291,44 @@ const getPackageName = (id) => {
182
291
  return id.slice(0, indexOfSlash);
183
292
  };
184
293
 
294
+ const getAllFiles = async (directoryPath, dontShortenPath) => {
295
+ const directoryFiles = await fs.readdir(directoryPath, { withFileTypes: true });
296
+ const fileTree = await Promise.all(
297
+ directoryFiles.map(async (entry) => {
298
+ const filePath = path.join(directoryPath, entry.name);
299
+ if (entry.isDirectory()) {
300
+ const files = await getAllFiles(filePath, true);
301
+ return dontShortenPath ? files : files.map((file) => `./${path.relative(directoryPath, file)}`);
302
+ }
303
+ return dontShortenPath ? filePath : `./${path.relative(directoryPath, filePath)}`;
304
+ })
305
+ );
306
+ return fileTree.flat();
307
+ };
308
+
185
309
  const readPackageJson = async (filePath) => {
186
310
  const packageJsonString = await fs.readFile(filePath, "utf8");
187
311
  return JSON.parse(packageJsonString);
188
312
  };
189
- const getDtsEntryPoints = (packageJson, packageJsonDirectory) => {
313
+ const traverseExports = (exportValue, propertyPath) => {
314
+ if (typeof exportValue === "string") {
315
+ return [[exportValue, propertyPath]];
316
+ }
317
+ if (Array.isArray(exportValue)) {
318
+ return exportValue.flatMap((value, index) => traverseExports(value, `${propertyPath}[${index}]`));
319
+ }
320
+ if (typeof exportValue === "object" && exportValue !== null) {
321
+ return Object.entries(exportValue).flatMap(([property, value]) => {
322
+ const newProperty = propertyNeedsQuotes(property) ? `["${property}"]` : `.${property}`;
323
+ return traverseExports(value, propertyPath + newProperty);
324
+ });
325
+ }
326
+ return [];
327
+ };
328
+ const getDtsEntryPoints = async (packageJson, packageJsonDirectory) => {
190
329
  const entryPoints = {};
191
330
  const addEntry = (subpath, from) => {
192
- if (!subpath.endsWith(dtsExtension)) {
331
+ if (!isDts(subpath)) {
193
332
  return;
194
333
  }
195
334
  const entryPath = path.join(packageJsonDirectory, subpath);
@@ -204,19 +343,23 @@ const getDtsEntryPoints = (packageJson, packageJsonDirectory) => {
204
343
  addEntry(packageJson.typings, "typings");
205
344
  }
206
345
  if (packageJson.exports) {
207
- (function gather(exportValue, propertyPath) {
208
- if (typeof exportValue === "string") {
209
- addEntry(exportValue, propertyPath);
210
- } else if (Array.isArray(exportValue)) {
211
- exportValue.forEach((value, index) => gather(value, `${propertyPath}[${index}]`));
346
+ const subpaths = traverseExports(packageJson.exports, "exports");
347
+ let packageFiles;
348
+ for (const [subpath, fromProperty] of subpaths) {
349
+ if (!subpath.includes("*")) {
350
+ addEntry(subpath, fromProperty);
351
+ continue;
352
+ }
353
+ if (!packageFiles) {
354
+ packageFiles = await getAllFiles(packageJsonDirectory);
212
355
  }
213
- if (typeof exportValue === "object" && exportValue) {
214
- for (const [property, value] of Object.entries(exportValue)) {
215
- const newProperty = propertyNeedsQuotes(property) ? `["${property}"]` : `.${property}`;
216
- gather(value, propertyPath + newProperty);
356
+ const [prefix, suffix] = subpath.split("*", 2);
357
+ for (const file of packageFiles) {
358
+ if (file.startsWith(prefix) && file.endsWith(suffix)) {
359
+ addEntry(file, fromProperty);
217
360
  }
218
361
  }
219
- })(packageJson.exports, "exports");
362
+ }
220
363
  }
221
364
  return entryPoints;
222
365
  };
@@ -282,8 +425,7 @@ const validateInput = async (inputFiles) => {
282
425
  const inputNormalized = isCliInput ? inputFiles.map((i) => [i]) : Object.entries(inputFiles);
283
426
  return await Promise.all(inputNormalized.map(
284
427
  async ([inputFile, inputSource]) => {
285
- const notDts = !inputFile.endsWith(dtsExtension);
286
- if (notDts) {
428
+ if (!isDts(inputFile)) {
287
429
  return [inputFile, inputSource, "Ignoring non-d.ts input"];
288
430
  }
289
431
  const exists = await pathExists(inputFile);
@@ -295,8 +437,6 @@ const validateInput = async (inputFiles) => {
295
437
  ));
296
438
  };
297
439
 
298
- const isPath = ([firstCharacter]) => firstCharacter === "." || firstCharacter === path.sep;
299
-
300
440
  const createExternalizePlugin = (configuredExternals) => {
301
441
  const resolvedBareSpecifiers = /* @__PURE__ */ new Map();
302
442
  const importPath = /* @__PURE__ */ new Map();
@@ -330,11 +470,11 @@ const createExternalizePlugin = (configuredExternals) => {
330
470
  }
331
471
  if (packageName) {
332
472
  externalized.set(packageName, "because unresolvable");
473
+ return {
474
+ id,
475
+ external: true
476
+ };
333
477
  }
334
- return {
335
- id,
336
- external: true
337
- };
338
478
  }
339
479
  };
340
480
  const getPackageEntryPoint = (subpackagePath) => {
@@ -371,17 +511,15 @@ const removeBundledModulesPlugin = (outputDirectory, sizeRef) => {
371
511
  },
372
512
  async generateBundle(options, bundle) {
373
513
  const modules = Object.values(bundle);
374
- const bundledSourceFiles = Array.from(new Set(
375
- modules.flatMap(({ moduleIds }) => moduleIds).filter((moduleId) => (
376
- // To avoid deleting files from symlinked dependencies
377
- moduleId.startsWith(outputDirectory) && !moduleId.includes(nodeModules)
378
- ))
379
- ));
380
- const fileSizes = bundledSourceFiles.map((moduleId) => this.getModuleInfo(moduleId).meta);
514
+ const bundledFiles = Array.from(new Set(modules.flatMap(({ moduleIds }) => moduleIds)));
515
+ const fileSizes = bundledFiles.map((moduleId) => this.getModuleInfo(moduleId).meta);
381
516
  const totalSize = fileSizes.reduce((total, { size }) => total + size, 0);
382
517
  sizeRef.value = totalSize;
383
518
  const outputFiles = new Set(modules.map(({ fileName }) => path.join(options.dir, fileName)));
384
- deleteFiles = bundledSourceFiles.filter((moduleId) => !outputFiles.has(moduleId));
519
+ deleteFiles = bundledFiles.filter((moduleId) => (
520
+ // To avoid deleting files from symlinked dependencies
521
+ moduleId.startsWith(outputDirectory) && !moduleId.includes(nodeModules) && !outputFiles.has(moduleId)
522
+ ));
385
523
  },
386
524
  writeBundle: async () => {
387
525
  await Promise.all(
@@ -393,7 +531,7 @@ const removeBundledModulesPlugin = (outputDirectory, sizeRef) => {
393
531
 
394
532
  const createInputMap = (input, outputDirectory) => Object.fromEntries(
395
533
  input.map((inputFile) => [
396
- inputFile.slice(outputDirectory.length + 1).slice(0, -dtsExtension.length),
534
+ inputFile.slice(outputDirectory.length + 1),
397
535
  inputFile
398
536
  ])
399
537
  );
@@ -409,13 +547,14 @@ const build = async (input, outputDirectory, externals, mode, conditions) => {
409
547
  output: {
410
548
  // sourcemap: true,
411
549
  dir: outputDirectory,
550
+ entryFileNames: "[name]",
412
551
  chunkFileNames: "_dtsroll-chunks/[name].ts"
413
552
  },
414
553
  plugins: [
415
554
  externalizePlugin,
416
555
  removeBundledModulesPlugin(outputDirectory, sizeRef),
417
556
  nodeResolve({
418
- extensions: [".ts", dtsExtension],
557
+ extensions: [".ts", ...dtsExtensions],
419
558
  exportConditions: conditions
420
559
  }),
421
560
  dts({
@@ -452,7 +591,7 @@ const dtsroll = async ({
452
591
  const externals = pkgJson ? pkgJson.getExternals() : /* @__PURE__ */ new Map();
453
592
  if (external && external.length > 0) {
454
593
  if (pkgJson) {
455
- console.warn(`${yellow("Warning:")} The --external flag is only supported when there is no package.json`);
594
+ console.warn(`${warningPrefix} The --external flag is only supported when there is no package.json`);
456
595
  } else {
457
596
  for (const externalDependency of external) {
458
597
  externals.set(externalDependency, "by --external flag");
@@ -461,7 +600,7 @@ const dtsroll = async ({
461
600
  }
462
601
  const manualInput = inputs && inputs.length > 0;
463
602
  const validatedInputs = await validateInput(
464
- manualInput ? inputs.map((file) => path.resolve(file)) : pkgJson?.getDtsEntryPoints()
603
+ manualInput ? inputs.map((file) => path.resolve(file)) : await pkgJson?.getDtsEntryPoints()
465
604
  );
466
605
  const inputFiles = validatedInputs.filter((input) => !input[2]).map(([file]) => file);
467
606
  if (inputFiles.length === 0) {
@@ -532,4 +671,4 @@ const dtsroll = async ({
532
671
  };
533
672
  };
534
673
 
535
- export { black as a, bgYellow as b, bold as c, dtsroll as d, dim as e, green as g, lightYellow as l, magenta as m, red as r, underline as u, warningSignUnicode as w, yellow as y };
674
+ export { black as a, bgYellow as b, dtsroll as d, logOutput as l };
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import 'node:path';
2
- export { d as dtsroll } from './index--BBQtOEp.mjs';
2
+ export { d as dtsroll } from './index-BR0PvbQA.mjs';
3
3
  import 'node:fs/promises';
4
4
  import 'rollup';
5
5
  import 'rollup-plugin-dts';
6
6
  import '@rollup/plugin-node-resolve';
7
+ import 'byte-size';
package/dist/vite.mjs CHANGED
@@ -1,21 +1,22 @@
1
- import { l as logOutput } from './log-output-CwMUFOmQ.mjs';
2
- import { d as dtsroll } from './index--BBQtOEp.mjs';
1
+ import { d as dtsroll, l as logOutput } from './index-BR0PvbQA.mjs';
3
2
  import 'node:path';
4
- import 'byte-size';
5
3
  import 'node:fs/promises';
6
4
  import 'rollup';
7
5
  import 'rollup-plugin-dts';
8
6
  import '@rollup/plugin-node-resolve';
7
+ import 'byte-size';
9
8
 
10
9
  const dtsrollPlugin = (options) => {
11
10
  let built = false;
12
11
  let cwd;
12
+ let noLog = false;
13
13
  return {
14
14
  name: "dtsroll",
15
15
  apply: "build",
16
16
  enforce: "post",
17
- config: ({ root }) => {
17
+ config: ({ root, logLevel }) => {
18
18
  cwd = root;
19
+ noLog = logLevel === "silent";
19
20
  },
20
21
  writeBundle: {
21
22
  sequential: true,
@@ -24,12 +25,15 @@ const dtsrollPlugin = (options) => {
24
25
  if (built) {
25
26
  return;
26
27
  }
27
- logOutput(await dtsroll({
28
+ const output = await dtsroll({
28
29
  cwd,
29
30
  ...options
30
- }));
31
- console.log();
31
+ });
32
32
  built = true;
33
+ if (!noLog) {
34
+ logOutput(output);
35
+ console.log();
36
+ }
33
37
  }
34
38
  }
35
39
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dtsroll",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Bundle dts files",
5
5
  "keywords": [
6
6
  "bundle",
@@ -1,100 +0,0 @@
1
- import path from 'node:path';
2
- import byteSize from 'byte-size';
3
- import { u as underline, c as bold, l as lightYellow, w as warningSignUnicode, e as dim, g as green, r as red, m as magenta, y as yellow } from './index--BBQtOEp.mjs';
4
-
5
- const cwd = process.cwd();
6
-
7
- const logOutput = (dtsOutput) => {
8
- console.log(underline("dtsroll"));
9
- const { inputs } = dtsOutput;
10
- const isCliInput = inputs[0]?.[1] === void 0;
11
- console.log(bold(`
12
- \u{1F4E5} Entry points${isCliInput ? "" : " in package.json"}`));
13
- console.log(
14
- inputs.map(([inputFile, inputSource, error]) => {
15
- const relativeInputFile = path.relative(cwd, inputFile);
16
- const logPath2 = relativeInputFile.length < inputFile.length ? relativeInputFile : inputFile;
17
- if (error) {
18
- return ` ${lightYellow(`${warningSignUnicode} ${logPath2} ${dim(error)}`)}`;
19
- }
20
- return ` \u2192 ${green(logPath2)}${inputSource ? ` ${dim(`from ${inputSource}`)}` : ""}`;
21
- }).join("\n")
22
- );
23
- if ("error" in dtsOutput) {
24
- console.error(`${red("Error:")} ${dtsOutput.error}`);
25
- return;
26
- }
27
- const {
28
- outputDirectory,
29
- output: {
30
- entries: outputEntries,
31
- chunks: outputChunks
32
- },
33
- size,
34
- externals
35
- } = dtsOutput;
36
- const outputDirectoryRelative = path.relative(cwd, outputDirectory);
37
- const logPath = (outputDirectoryRelative.length < outputDirectory.length ? outputDirectoryRelative : outputDirectory) + path.sep;
38
- const logChunk = ({
39
- file,
40
- indent,
41
- bullet,
42
- color
43
- }) => {
44
- const sizeFormatted = byteSize(file.size).toString();
45
- let log = `${indent}${bullet} ${dim(color(logPath))}${color(file.fileName)} ${sizeFormatted}`;
46
- const { moduleIds, moduleToPackage } = file;
47
- log += `
48
- ${moduleIds.sort().map((moduleId, index) => {
49
- const isLast = index === moduleIds.length - 1;
50
- const prefix = `${indent} ${isLast ? "\u2514\u2500 " : "\u251C\u2500 "}`;
51
- const relativeModuleId = path.relative(cwd, moduleId);
52
- const logModuleId = relativeModuleId.length < moduleId.length ? relativeModuleId : moduleId;
53
- const bareSpecifier = moduleToPackage[moduleId];
54
- if (bareSpecifier) {
55
- return `${prefix}${dim(`${magenta(bareSpecifier)} (${logModuleId})`)}`;
56
- }
57
- return `${prefix}${dim(logModuleId)}`;
58
- }).join("\n")}`;
59
- return log;
60
- };
61
- console.log(bold("\n\u{1F4A0} Bundled output"));
62
- console.log(
63
- outputEntries.map((file) => logChunk({
64
- file,
65
- indent: " ",
66
- bullet: "\u25CF",
67
- color: green
68
- })).join("\n\n")
69
- );
70
- if (outputChunks.length > 0) {
71
- console.log(bold("\n Chunks"));
72
- console.log(
73
- outputChunks.map((file) => logChunk({
74
- file,
75
- indent: " ",
76
- bullet: "\u25A0",
77
- color: yellow
78
- })).join("\n\n")
79
- );
80
- }
81
- console.log(bold("\n\u2696\uFE0F Size savings"));
82
- const percentage = ((size.input - size.output) / size.input * 100).toFixed(0);
83
- console.log(` Input source size: ${byteSize(size.input).toString()}`);
84
- console.log(` Bundled output size: ${byteSize(size.output).toString()} (${percentage}% decrease)`);
85
- if (externals.length > 0) {
86
- console.log(bold("\n\u{1F4E6} External packages"));
87
- console.log(
88
- externals.map(([packageName, reason, devTypePackage]) => {
89
- let stdout = ` \u2500 ${magenta(packageName)} ${dim(`externalized ${reason}`)}`;
90
- if (devTypePackage) {
91
- stdout += `
92
- ${yellow("Warning:")} ${magenta(devTypePackage)} should not be in devDependencies if ${magenta(packageName)} is externalized`;
93
- }
94
- return stdout;
95
- }).sort().join("\n")
96
- );
97
- }
98
- };
99
-
100
- export { logOutput as l };