Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

module: add --experimental-strip-types #53725

Merged
merged 8 commits into from
Jul 24, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/tools.yml
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ on:
- acorn
- acorn-walk
- ada
- amaro
- brotli
- c-ares
- cjs-module-lexer
@@ -82,6 +83,14 @@ jobs:
cat temp-output
tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true
rm temp-output
- id: amaro
subsystem: deps
label: dependencies
run: |
./tools/dep_updaters/update-amaro.sh > temp-output
cat temp-output
tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true
rm temp-output
- id: brotli
subsystem: deps
label: dependencies
25 changes: 25 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -130,6 +130,31 @@ The externally maintained libraries used by Node.js are:
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""

- amaro, located at deps/amaro, is licensed as follows:
"""
MIT License

Copyright (c) Marco Ippolito and Amaro contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

- ICU, located at deps/icu-small, is licensed as follows:
"""
UNICODE LICENSE V3
21 changes: 21 additions & 0 deletions deps/amaro/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) Marco Ippolito and Amaro contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
30 changes: 30 additions & 0 deletions deps/amaro/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Amaro

Amaro is a wrapper around `@swc/wasm-typescript`, a WebAssembly port of the SWC TypeScript parser.
It's currently used as an internal in Node.js for [Type Stripping](https://github.com/nodejs/loaders/issues/208), but in the future it will be possible to be upgraded separately by users.
The main goal of this package is to provide a stable API for TypeScript parser, which is unstable and subject to change.

> Amaro means "bitter" in Italian. It's a reference to [Mount Amaro](https://en.wikipedia.org/wiki/Monte_Amaro_(Abruzzo)) on whose slopes this package was conceived.
## How to Install

To install Amaro, run:

```shell
npm install amaro
```

## How to Use

By default Amaro exports a `transformSync` function that performs type stripping.
Stack traces are preserved, by replacing removed types with white spaces.

```javascript
const amaro = require('amaro');
const { code } = amaro.transformSync("const foo: string = 'bar';");
console.log(code); // "const foo = 'bar';"
```

## License (MIT)

See [`LICENSE.md`](./LICENSE.md).
617 changes: 617 additions & 0 deletions deps/amaro/dist/index.js

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions deps/amaro/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "amaro",
"version": "0.0.4",
"description": "Node.js TypeScript wrapper",
"license": "MIT",
"type": "commonjs",
"main": "dist/index.js",
"homepage": "https://github.com/nodejs/amaro#readme",
"bugs": {
"url": "https://github.com/nodejs/amaro/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/nodejs/amaro.git"
},
"scripts": {
"clean": "rimraf dist",
"lint": "biome lint --write",
"format": "biome format --write",
"prepack": "npm run build",
"postpack": "npm run clean",
"build": "rspack build",
"typecheck": "tsc --noEmit",
"test": "node --test ./test"
},
"devDependencies": {
"@biomejs/biome": "1.8.3",
"@rspack/cli": "^0.7.5",
"@rspack/core": "^0.7.5",
"@types/node": "^20.14.11",
"rimraf": "^6.0.1",
"typescript": "^5.5.3"
},
"exports": {
"./package.json": "./package.json"
},
"files": ["dist", "LICENSE.md"]
}
20 changes: 20 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
@@ -862,6 +862,9 @@ export USERNAME="nodejs" # will result in `nodejs` as the value.
<!-- YAML
added: v0.5.2
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/53725
description: Eval now supports experimental type-stripping.
- version: v5.11.0
pr-url: https://github.com/nodejs/node/pull/5348
description: Built-in libraries are now available as predefined variables.
@@ -874,6 +877,9 @@ On Windows, using `cmd.exe` a single quote will not work correctly because it
only recognizes double `"` for quoting. In Powershell or Git bash, both `'`
and `"` are usable.

It is possible to run code containing inline types by passing
[`--experimental-strip-types`][].

### `--experimental-default-type=type`

<!-- YAML
@@ -1035,6 +1041,17 @@ added: v22.5.0

Enable the experimental [`node:sqlite`][] module.

### `--experimental-strip-types`

<!-- YAML
added: REPLACEME
-->

> Stability: 1.0 - Early development
Enable experimental type-stripping for TypeScript files.
For more information, see the [TypeScript type-stripping][] documentation.

### `--experimental-test-coverage`

<!-- YAML
@@ -2892,6 +2909,7 @@ one is included in the list below.
* `--experimental-shadow-realm`
* `--experimental-specifier-resolution`
* `--experimental-sqlite`
* `--experimental-strip-types`
* `--experimental-top-level-await`
* `--experimental-vm-modules`
* `--experimental-wasi-unstable-preview1`
@@ -3416,6 +3434,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[ScriptCoverage]: https://chromedevtools.github.io/devtools-protocol/tot/Profiler#type-ScriptCoverage
[ShadowRealm]: https://github.com/tc39/proposal-shadowrealm
[Source Map]: https://sourcemaps.info/spec.html
[TypeScript type-stripping]: typescript.md#type-stripping
[V8 Inspector integration for Node.js]: debugger.md#v8-inspector-integration-for-nodejs
[V8 JavaScript code coverage]: https://v8project.blogspot.com/2017/12/javascript-code-coverage.html
[V8 code cache]: https://v8.dev/blog/code-caching-for-devs
@@ -3430,6 +3449,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[`--diagnostic-dir`]: #--diagnostic-dirdirectory
[`--experimental-default-type=module`]: #--experimental-default-typetype
[`--experimental-sea-config`]: single-executable-applications.md#generating-single-executable-preparation-blobs
[`--experimental-strip-types`]: #--experimental-strip-types
[`--experimental-wasm-modules`]: #--experimental-wasm-modules
[`--heap-prof-dir`]: #--heap-prof-dir
[`--import`]: #--importmodule
10 changes: 10 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
@@ -4032,6 +4032,16 @@ The public key in the certificate SubjectPublicKeyInfo could not be read.

An error occurred trying to allocate memory. This should never happen.

<a id="ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING"></a>

#### `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`

<!-- YAML
added: REPLACEME
-->

Type stripping is not supported for files descendent of a `node_modules` directory.

[ES Module]: esm.md
[ICU]: intl.md#internationalization-support
[JSON Web Key Elliptic Curve Registry]: https://www.iana.org/assignments/jose/jose.xhtml#web-key-elliptic-curve
1 change: 1 addition & 0 deletions doc/api/index.md
Original file line number Diff line number Diff line change
@@ -41,6 +41,7 @@
* [Modules: ECMAScript modules](esm.md)
* [Modules: `node:module` API](module.md)
* [Modules: Packages](packages.md)
* [Modules: TypeScript](typescript.md)
* [Net](net.md)
* [OS](os.md)
* [Path](path.md)
153 changes: 153 additions & 0 deletions doc/api/typescript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Modules: TypeScript

## Enabling

There are two ways to enable runtime TypeScript support in Node.js:

1. For [full support][] of all of TypeScript's syntax and features, including
using any version of TypeScript, use a third-party package.

2. For lightweight support, you can use the built-in support for
[type stripping][].

## Full TypeScript support

To use TypeScript with full support for all TypeScript features, including
`tsconfig.json`, you can use a third-party package. These instructions use
[`tsx`][] as an example but there are many other similar libraries available.

1. Install the package as a development dependency using whatever package
manager you're using for your project. For example, with `npm`:

```bash
npm install --save-dev tsx
```

2. Then you can run your TypeScript code via:

```bash
npx tsx your-file.ts
```

Or alternatively, you can run with `node` via:

```bash
node --import=tsx your-file.ts
```

## Type stripping

<!-- YAML
added: REPLACEME
-->

> Stability: 1.0 - Early development
The flag [`--experimental-strip-types`][] enables Node.js to run TypeScript
files that contain only type annotations. Such files contain no TypeScript
features that require transformation, such as enums or namespaces. Node.js will
replace inline type annotations with whitespace, and no type checking is
performed. TypeScript features that depend on settings within `tsconfig.json`,
such as paths or converting newer JavaScript syntax to older standards, are
intentionally unsupported. To get fuller TypeScript support, including support
for enums and namespaces and paths, see [Full TypeScript support][].

The type stripping feature is designed to be lightweight.
By intentionally not supporting syntaxes that require JavaScript code
generation, and by replacing inline types with whitespace, Node.js can run
TypeScript code without the need for source maps.

### Determining module system

Node.js supports both [CommonJS][] and [ES Modules][] syntax in TypeScript
files. Node.js will not convert from one module system to another; if you want
your code to run as an ES module, you must use `import` and `export` syntax, and
if you want your code to run as CommonJS you must use `require` and
`module.exports`.

* `.ts` files will have their module system determined [the same way as `.js`
files.][] To use `import` and `export` syntax, add `"type": "module"` to the
nearest parent `package.json`.
* `.mts` files will always be run as ES modules, similar to `.mjs` files.
* `.cts` files will always be run as CommonJS modules, similar to `.cjs` files.
* `.tsx` files are unsupported.

As in JavaScript files, [file extensions are mandatory][] in `import` statements
and `import()` expressions: `import './file.ts'`, not `import './file'`. Because
of backward compatibility, file extensions are also mandatory in `require()`
calls: `require('./file.ts')`, not `require('./file')`, similar to how the
`.cjs` extension is mandatory in `require` calls in CommonJS files.

The `tsconfig.json` option `allowImportingTsExtensions` will allow the
TypeScript compiler `tsc` to type-check files with `import` specifiers that
include the `.ts` extension.

### Unsupported TypeScript features

Since Node.js is only removing inline types, any TypeScript features that
involve _replacing_ TypeScript syntax with new JavaScript syntax will error.
This is by design. To run TypeScript with such features, see
[Full TypeScript support][].

The most prominent unsupported features that require transformation are:

* `Enum`
* `experimentalDecorators`
* `namespaces`
* parameter properties

In addition, Node.js does not read `tsconfig.json` files and does not support
features that depend on settings within `tsconfig.json`, such as paths or
converting newer JavaScript syntax into older standards.

### Importing types without `type` keyword

Due to the nature of type stripping, the `type` keyword is necessary to
correctly strip type imports. Without the `type` keyword, Node.js will treat the
import as a value import, which will result in a runtime error. The tsconfig
option [`verbatimModuleSyntax`][] can be used to match this behavior.

This example will work correctly:

```ts
import type { Type1, Type2 } from './module.ts';
import { fn, type FnParams } from './fn.ts';
```

This will result in a runtime error:

```ts
import { Type1, Type2 } from './module.ts';
import { fn, FnParams } from './fn.ts';
```

### Non-file forms of input

Type stripping can be enabled for `--eval` and STDIN input. The module system
will be determined by `--input-type`, as it is for JavaScript.

TypeScript syntax is unsupported in the REPL, `--print`, `--check`, and
`inspect`.

### Source maps

Since inline types are replaced by whitespace, source maps are unnecessary for
correct line numbers in stack traces; and Node.js does not generate them. For
source maps support, see [Full TypeScript support][].

### Type stripping in dependencies

To discourage package authors from publishing packages written in TypeScript,
Node.js will by default refuse to handle TypeScript files inside folders under
a `node_modules` path.

[CommonJS]: modules.md
[ES Modules]: esm.md
[Full TypeScript support]: #full-typescript-support
[`--experimental-strip-types`]: cli.md#--experimental-strip-types
[`tsx`]: https://tsx.is/
[`verbatimModuleSyntax`]: https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax
[file extensions are mandatory]: esm.md#mandatory-file-extensions
[full support]: #full-typescript-support
[the same way as `.js` files.]: packages.md#determining-module-system
[type stripping]: #type-stripping
7 changes: 7 additions & 0 deletions doc/contributing/maintaining/maintaining-dependencies.md
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ This a list of all the dependencies:

* [acorn][]
* [ada][]
* [amaro][]
* [base64][]
* [brotli][]
* [c-ares][]
@@ -168,6 +169,11 @@ an abstract syntax tree walker for the ESTree format.
The [ada](https://github.com/ada-url/ada) dependency is a
fast and spec-compliant URL parser written in C++.

### amaro

The [amaro](https://www.npmjs.com/package/amaro) dependency is a wrapper around the
WebAssembly version of the SWC JavaScript/TypeScript parser.

### brotli

The [brotli](https://github.com/google/brotli) dependency is
@@ -336,6 +342,7 @@ performance improvements not currently available in standard zlib.

[acorn]: #acorn
[ada]: #ada
[amaro]: #amaro
[base64]: #base64
[brotli]: #brotli
[c-ares]: #c-ares
3 changes: 3 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
@@ -194,6 +194,9 @@ Enable module mocking in the test runner.
.It Fl -experimental-test-snapshots
Enable snapshot testing in the test runner.
.
.It Fl -experimental-strip-types
Enable experimental type-stripping for TypeScript files.
.
.It Fl -experimental-eventsource
Enable experimental support for the EventSource Web API.
.
3 changes: 3 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
@@ -1834,6 +1834,9 @@ E('ERR_UNSUPPORTED_ESM_URL_SCHEME', (url, supported) => {
msg += `. Received protocol '${url.protocol}'`;
return msg;
}, Error);
E('ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING',
'Stripping types is currently unsupported for files under node_modules, for "%s"',
Error);
E('ERR_UNSUPPORTED_RESOLVE_REQUEST',
'Failed to resolve module specifier "%s" from "%s": Invalid relative URL or base scheme is not hierarchical.',
TypeError);
8 changes: 6 additions & 2 deletions lib/internal/main/eval_string.js
Original file line number Diff line number Diff line change
@@ -14,15 +14,19 @@ const {
markBootstrapComplete,
} = require('internal/process/pre_execution');
const { evalModuleEntryPoint, evalScript } = require('internal/process/execution');
const { addBuiltinLibsToObject } = require('internal/modules/helpers');
const { addBuiltinLibsToObject, tsParse } = require('internal/modules/helpers');

const { getOptionValue } = require('internal/options');

prepareMainThreadExecution();
addBuiltinLibsToObject(globalThis, '<eval>');
markBootstrapComplete();

const source = getOptionValue('--eval');
const code = getOptionValue('--eval');
const source = getOptionValue('--experimental-strip-types') ?
tsParse(code) :
code;

const print = getOptionValue('--print');
const shouldLoadESM = getOptionValue('--import').length > 0 || getOptionValue('--experimental-loader').length > 0;
if (getOptionValue('--input-type') === 'module' ||
125 changes: 112 additions & 13 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
@@ -146,6 +146,7 @@ const { safeGetenv } = internalBinding('credentials');
const {
getCjsConditions,
initializeCjsConditions,
isUnderNodeModules,
loadBuiltinModule,
makeRequireFunction,
setHasStartedUserCJSExecution,
@@ -168,6 +169,7 @@ const {
ERR_REQUIRE_CYCLE_MODULE,
ERR_REQUIRE_ESM,
ERR_UNKNOWN_BUILTIN_MODULE,
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING,
},
setArrowMessage,
} = require('internal/errors');
@@ -428,9 +430,18 @@ function initializeCJS() {
Module.runMain =
require('internal/modules/run_main').executeUserEntryPoint;

const tsEnabled = getOptionValue('--experimental-strip-types');
if (tsEnabled) {
emitExperimentalWarning('Type Stripping');
Module._extensions['.cts'] = loadCTS;
Module._extensions['.ts'] = loadTS;
}
if (getOptionValue('--experimental-require-module')) {
emitExperimentalWarning('Support for loading ES Module in require()');
Module._extensions['.mjs'] = loadESMFromCJS;
if (tsEnabled) {
Module._extensions['.mts'] = loadESMFromCJS;
}
}
}

@@ -639,10 +650,24 @@ function resolveExports(nmPath, request) {

// We don't cache this in case user extends the extensions.
function getDefaultExtensions() {
const extensions = ObjectKeys(Module._extensions);
let extensions = ObjectKeys(Module._extensions);
const tsEnabled = getOptionValue('--experimental-strip-types');
if (tsEnabled) {
extensions = ArrayPrototypeFilter(extensions, (ext) =>
ext !== '.ts' || Module._extensions['.ts'] !== loadTS ||
ext !== '.cts' || Module._extensions['.ts'] !== loadCTS,
);
}

if (!getOptionValue('--experimental-require-module')) {
return extensions;
}

if (tsEnabled) {
extensions = ArrayPrototypeFilter(extensions, (ext) =>
ext !== '.mts' || Module._extensions['.mts'] !== loadESMFromCJS,
);
}
// If the .mjs extension is added by --experimental-require-module,
// remove it from the supported default extensions to maintain
// compatibility.
@@ -1279,6 +1304,12 @@ Module.prototype.load = function(filename) {
throw new ERR_REQUIRE_ESM(filename, true);
}

if (getOptionValue('--experimental-strip-types')) {
if (StringPrototypeEndsWith(filename, '.mts') && !Module._extensions['.mts']) {
throw new ERR_REQUIRE_ESM(filename, true);
}
}

Module._extensions[extension](this, filename);
this.loaded = true;

@@ -1322,7 +1353,14 @@ let hasPausedEntry = false;
* @param {string} filename Absolute path of the file.
*/
function loadESMFromCJS(mod, filename) {
const source = getMaybeCachedSource(mod, filename);
let source = getMaybeCachedSource(mod, filename);
if (getOptionValue('--experimental-strip-types') && path.extname(filename) === '.mts') {
if (isUnderNodeModules(filename)) {
throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename);
}
const { tsParse } = require('internal/modules/helpers');
source = tsParse(source);
}
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
const isMain = mod[kIsMainSymbol];
if (isMain) {
@@ -1529,6 +1567,77 @@ function getMaybeCachedSource(mod, filename) {
return content;
}

function loadCTS(module, filename) {
if (isUnderNodeModules(filename)) {
throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename);
}
const source = getMaybeCachedSource(module, filename);
const { tsParse } = require('internal/modules/helpers');
const content = tsParse(source);
module._compile(content, filename, 'commonjs');
}

/**
* Built-in handler for `.ts` files.
* @param {Module} module The module to compile
* @param {string} filename The file path of the module
*/
function loadTS(module, filename) {
if (isUnderNodeModules(filename)) {
throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename);
}
// If already analyzed the source, then it will be cached.
const source = getMaybeCachedSource(module, filename);
const { tsParse } = require('internal/modules/helpers');
const content = tsParse(source);
let format;
const pkg = packageJsonReader.getNearestParentPackageJSON(filename);
// Function require shouldn't be used in ES modules.
if (pkg?.data.type === 'module') {
if (getOptionValue('--experimental-require-module')) {
module._compile(content, filename, 'module');
return;
}

const parent = module[kModuleParent];
const parentPath = parent?.filename;
const packageJsonPath = path.resolve(pkg.path, 'package.json');
const usesEsm = containsModuleSyntax(content, filename);
const err = new ERR_REQUIRE_ESM(filename, usesEsm, parentPath,
packageJsonPath);
// Attempt to reconstruct the parent require frame.
if (Module._cache[parentPath]) {
let parentSource;
try {
parentSource = tsParse(fs.readFileSync(parentPath, 'utf8'));
} catch {
// Continue regardless of error.
}
if (parentSource) {
reconstructErrorStack(err, parentPath, parentSource);
}
}
throw err;
} else if (pkg?.data.type === 'commonjs') {
format = 'commonjs';
}

module._compile(content, filename, format);
};

function reconstructErrorStack(err, parentPath, parentSource) {
const errLine = StringPrototypeSplit(
StringPrototypeSlice(err.stack, StringPrototypeIndexOf(
err.stack, ' at ')), '\n', 1)[0];
const { 1: line, 2: col } =
RegExpPrototypeExec(/(\d+):(\d+)\)/, errLine) || [];
if (line && col) {
const srcLine = StringPrototypeSplit(parentSource, '\n')[line - 1];
const frame = `${parentPath}:${line}\n${srcLine}\n${StringPrototypeRepeat(' ', col - 1)}^\n`;
setArrowMessage(err, frame);
}
}

/**
* Built-in handler for `.js` files.
* @param {Module} module The module to compile
@@ -1564,17 +1673,7 @@ Module._extensions['.js'] = function(module, filename) {
// Continue regardless of error.
}
if (parentSource) {
const errLine = StringPrototypeSplit(
StringPrototypeSlice(err.stack, StringPrototypeIndexOf(
err.stack, ' at ')), '\n', 1)[0];
const { 1: line, 2: col } =
RegExpPrototypeExec(/(\d+):(\d+)\)/, errLine) || [];
if (line && col) {
const srcLine = StringPrototypeSplit(parentSource, '\n')[line - 1];
const frame = `${parentPath}:${line}\n${srcLine}\n${
StringPrototypeRepeat(' ', col - 1)}^\n`;
setArrowMessage(err, frame);
}
reconstructErrorStack(err, parentPath, parentSource);
}
}
throw err;
6 changes: 6 additions & 0 deletions lib/internal/modules/esm/formats.js
Original file line number Diff line number Diff line change
@@ -23,6 +23,12 @@ if (experimentalWasmModules) {
extensionFormatMap['.wasm'] = 'wasm';
}

if (getOptionValue('--experimental-strip-types')) {
extensionFormatMap['.ts'] = 'module-typescript';
extensionFormatMap['.mts'] = 'module-typescript';
extensionFormatMap['.cts'] = 'commonjs-typescript';
}

/**
* @param {string} mime
* @returns {string | null}
53 changes: 44 additions & 9 deletions lib/internal/modules/esm/get_format.js
Original file line number Diff line number Diff line change
@@ -95,6 +95,18 @@ function underNodeModules(url) {
}

let typelessPackageJsonFilesWarnedAbout;
function warnTypelessPackageJsonFile(pjsonPath, url) {
typelessPackageJsonFilesWarnedAbout ??= new SafeSet();
if (!typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) {
const warning = `${url} parsed as an ES module because module syntax was detected;` +
` to avoid the performance penalty of syntax detection, add "type": "module" to ${pjsonPath}`;
process.emitWarning(warning, {
code: 'MODULE_TYPELESS_PACKAGE_JSON',
});
typelessPackageJsonFilesWarnedAbout.add(pjsonPath);
}
}

/**
* @param {URL} url
* @param {{parentURL: string; source?: Buffer}} context
@@ -130,15 +142,38 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
if (format === 'module') {
// This module has a .js extension, a package.json with no `type` field, and ESM syntax.
// Warn about the missing `type` field so that the user can avoid the performance penalty of detection.
typelessPackageJsonFilesWarnedAbout ??= new SafeSet();
if (!typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) {
const warning = `${url} parsed as an ES module because module syntax was detected;` +
` to avoid the performance penalty of syntax detection, add "type": "module" to ${pjsonPath}`;
process.emitWarning(warning, {
code: 'MODULE_TYPELESS_PACKAGE_JSON',
});
typelessPackageJsonFilesWarnedAbout.add(pjsonPath);
}
warnTypelessPackageJsonFile(pjsonPath, url);
}
return format;
}
}
}
if (ext === '.ts' && getOptionValue('--experimental-strip-types')) {
const { type: packageType, pjsonPath } = getPackageScopeConfig(url);
if (packageType !== 'none') {
return `${packageType}-typescript`;
}
// The controlling `package.json` file has no `type` field.
switch (getOptionValue('--experimental-default-type')) {
case 'module': { // The user explicitly passed `--experimental-default-type=module`.
// An exception to the type flag making ESM the default everywhere is that package scopes under `node_modules`
// should retain the assumption that a lack of a `type` field means CommonJS.
return underNodeModules(url) ? 'commonjs-typescript' : 'module-typescript';
}
case 'commonjs': { // The user explicitly passed `--experimental-default-type=commonjs`.
return 'commonjs-typescript';
}
default: { // The user did not pass `--experimental-default-type`.
// `source` is undefined when this is called from `defaultResolve`;
// but this gets called again from `defaultLoad`/`defaultLoadSync`.
const { tsParse } = require('internal/modules/helpers');
const parsedSource = tsParse(source);
const detectedFormat = detectModuleFormat(parsedSource, url);
const format = detectedFormat ? `${detectedFormat}-typescript` : 'commonjs-typescript';
if (format === 'module-typescript') {
// This module has a .js extension, a package.json with no `type` field, and ESM syntax.
// Warn about the missing `type` field so that the user can avoid the performance penalty of detection.
warnTypelessPackageJsonFile(pjsonPath, url);
}
return format;
}
10 changes: 10 additions & 0 deletions lib/internal/modules/esm/load.js
Original file line number Diff line number Diff line change
@@ -18,12 +18,16 @@ const defaultType =
getOptionValue('--experimental-default-type');

const { Buffer: { from: BufferFrom } } = require('buffer');
const {
isUnderNodeModules,
} = require('internal/modules/helpers');

const { URL } = require('internal/url');
const {
ERR_INVALID_URL,
ERR_UNKNOWN_MODULE_FORMAT,
ERR_UNSUPPORTED_ESM_URL_SCHEME,
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING,
} = require('internal/errors').codes;

const DATA_URL_PATTERN = /^[^/]+\/[^,;]+(?:[^,]*?)(;base64)?,([\s\S]*)$/;
@@ -147,6 +151,12 @@ async function defaultLoad(url, context = kEmptyObject) {
format = 'commonjs-sync';
}

if (getOptionValue('--experimental-strip-types') &&
(format === 'module-typescript' || format === 'commonjs-typescript') &&
isUnderNodeModules(url)) {
throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(url);
}

return {
__proto__: null,
format,
7 changes: 6 additions & 1 deletion lib/internal/modules/esm/loader.js
Original file line number Diff line number Diff line change
@@ -579,13 +579,18 @@ class ModuleLoader {
this.#customizations.loadSync(url, context) :
defaultLoadSync(url, context);
let format = result?.format;
if (format === 'module') {
if (format === 'module' || format === 'module-typescript') {
throw new ERR_REQUIRE_ESM(url, true);
}
if (format === 'commonjs') {
format = 'require-commonjs';
result = { __proto__: result, format };
}
if (format === 'commonjs-typescript') {
format = 'require-commonjs-typescript';
result = { __proto__: result, format };
}

this.validateLoadResult(url, format);
return result;
}
29 changes: 29 additions & 0 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
const {
ArrayPrototypeMap,
Boolean,
FunctionPrototypeCall,
JSONParse,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
@@ -37,6 +38,7 @@ const { readFileSync } = require('fs');
const { dirname, extname, isAbsolute } = require('path');
const {
loadBuiltinModule,
tsParse,
stripBOM,
urlToFilename,
} = require('internal/modules/helpers');
@@ -302,6 +304,15 @@ translators.set('require-commonjs', (url, source, isMain) => {
return createCJSModuleWrap(url, source);
});

// Handle CommonJS modules referenced by `require` calls.
// This translator function must be sync, as `require` is sync.
translators.set('require-commonjs-typescript', (url, source, isMain) => {
emitExperimentalWarning('Type Stripping');
assert(cjsParse);
const code = tsParse(stringify(source));
return createCJSModuleWrap(url, code);
});

// Handle CommonJS modules referenced by `import` statements or expressions,
// or as the initial entry point when the ESM loader handles a CommonJS entry.
translators.set('commonjs', async function commonjsStrategy(url, source,
@@ -510,3 +521,21 @@ translators.set('wasm', async function(url, source) {
}
}).module;
});

// Strategy for loading a commonjs TypeScript module
translators.set('commonjs-typescript', function(url, source) {
emitExperimentalWarning('Type Stripping');
assertBufferSource(source, false, 'load');
const code = tsParse(stringify(source));
debug(`Translating TypeScript ${url}`);
return FunctionPrototypeCall(translators.get('commonjs'), this, url, code, false);
});

// Strategy for loading an esm TypeScript module
translators.set('module-typescript', function(url, source) {
emitExperimentalWarning('Type Stripping');
assertBufferSource(source, false, 'load');
const code = tsParse(stringify(source));
debug(`Translating TypeScript ${url}`);
return FunctionPrototypeCall(translators.get('module'), this, url, code, false);
});
25 changes: 25 additions & 0 deletions lib/internal/modules/helpers.js
Original file line number Diff line number Diff line change
@@ -2,13 +2,15 @@

const {
ArrayPrototypeForEach,
ArrayPrototypeIncludes,
ObjectDefineProperty,
ObjectPrototypeHasOwnProperty,
SafeMap,
SafeSet,
StringPrototypeCharCodeAt,
StringPrototypeIncludes,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
} = primordials;
const {
@@ -298,14 +300,37 @@ function getBuiltinModule(id) {
return normalizedId ? require(normalizedId) : undefined;
}

let parseTS;

function lazyLoadTSParser() {
parseTS ??= require('internal/deps/amaro/dist/index').transformSync;
return parseTS;
}

function tsParse(source) {
if (!source || typeof source !== 'string') { return; }
const transformSync = lazyLoadTSParser();
const { code } = transformSync(source);
return code;
}

function isUnderNodeModules(filename) {
const resolvedPath = path.resolve(filename);
const normalizedPath = path.normalize(resolvedPath);
const splitPath = StringPrototypeSplit(normalizedPath, path.sep);
return ArrayPrototypeIncludes(splitPath, 'node_modules');
}

module.exports = {
addBuiltinLibsToObject,
getBuiltinModule,
getCjsConditions,
initializeCjsConditions,
isUnderNodeModules,
loadBuiltinModule,
makeRequireFunction,
normalizeReferrerURL,
tsParse,
stripBOM,
toRealPath,
hasStartedUserCJSExecution() {
8 changes: 8 additions & 0 deletions lib/internal/modules/run_main.js
Original file line number Diff line number Diff line change
@@ -81,6 +81,14 @@ function shouldUseESMLoader(mainPath) {
if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) { return true; }
if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs')) { return false; }

if (getOptionValue('--experimental-strip-types')) {
// This ensures that --experimental-default-type=commonjs and .mts files are treated as commonjs
if (getOptionValue('--experimental-default-type') === 'commonjs') { return false; }
if (mainPath && StringPrototypeEndsWith(mainPath, '.cts')) { return false; }
// This will likely change in the future to start with commonjs loader by default
if (mainPath && StringPrototypeEndsWith(mainPath, '.mts')) { return true; }
}

const type = getNearestParentPackageJSONType(mainPath);

// No package.json or no `type` field.
1 change: 1 addition & 0 deletions node.gyp
Original file line number Diff line number Diff line change
@@ -56,6 +56,7 @@
'deps/acorn/acorn/dist/acorn.js',
'deps/acorn/acorn-walk/dist/walk.js',
'deps/minimatch/index.js',
'deps/amaro/dist/index.js',
'<@(node_builtin_shareable_builtins)',
],
'node_sources': [
6 changes: 6 additions & 0 deletions src/amaro_version.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This is an auto generated file, please do not edit.
// Refer to tools/dep_updaters/update-amaro.sh
#ifndef SRC_AMARO_VERSION_H_
#define SRC_AMARO_VERSION_H_
#define AMARO_VERSION "0.0.4"
#endif // SRC_AMARO_VERSION_H_
2 changes: 2 additions & 0 deletions src/node_metadata.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "node_metadata.h"
#include "acorn_version.h"
#include "ada.h"
#include "amaro_version.h"
#include "ares.h"
#include "brotli/encode.h"
#include "cjs_module_lexer_version.h"
@@ -116,6 +117,7 @@ Metadata::Versions::Versions() {
acorn = ACORN_VERSION;
cjs_module_lexer = CJS_MODULE_LEXER_VERSION;
uvwasi = UVWASI_VERSION_STRING;
amaro = AMARO_VERSION;

#if HAVE_OPENSSL
openssl = GetOpenSSLVersion();
1 change: 1 addition & 0 deletions src/node_metadata.h
Original file line number Diff line number Diff line change
@@ -51,6 +51,7 @@ namespace node {
V(sqlite) \
V(ada) \
V(nbytes) \
V(amaro) \
NODE_VERSIONS_KEY_UNDICI(V) \
V(cjs_module_lexer)

4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
@@ -785,6 +785,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"ES module to preload (option can be repeated)",
&EnvironmentOptions::preload_esm_modules,
kAllowedInEnvvar);
AddOption("--experimental-strip-types",
"Experimental type-stripping for TypeScript files.",
&EnvironmentOptions::experimental_strip_types,
kAllowedInEnvvar);
AddOption("--interactive",
"always enter the REPL even if stdin does not appear "
"to be a terminal",
2 changes: 2 additions & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
@@ -233,6 +233,8 @@ class EnvironmentOptions : public Options {

std::vector<std::string> preload_esm_modules;

bool experimental_strip_types = false;

std::vector<std::string> user_argv;

bool report_exclude_network = false;
166 changes: 166 additions & 0 deletions test/es-module/test-typescript-commonjs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import { match, strictEqual } from 'node:assert';
import { test } from 'node:test';

test('require a .ts file with explicit extension succeeds', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--eval',
'require("./test-typescript.ts")',
'--no-warnings',
], {
cwd: fixtures.path('typescript/ts'),
});

strictEqual(result.stderr, '');
strictEqual(result.stdout, 'Hello, TypeScript!\n');
strictEqual(result.code, 0);
});

// TODO(marco-ippolito) This test should fail because extensionless require
// but it's behaving like a .js file
test('eval require a .ts file with implicit extension fails', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--eval',
'require("./test-typescript")',
'--no-warnings',
], {
cwd: fixtures.path('typescript/ts'),
});

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

// TODO(marco-ippolito) This test should fail because extensionless require
// but it's behaving like a .js file
test('require a .ts file with implicit extension fails', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--no-warnings',
fixtures.path('typescript/cts/test-extensionless-require.ts'),
]);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('expect failure of an .mts file with CommonJS syntax', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/cts/test-cts-but-module-syntax.cts'),
]);

strictEqual(result.stdout, '');
match(result.stderr, /To load an ES module, set "type": "module" in the package\.json or use the \.mjs extension\./);
strictEqual(result.code, 1);
});

test('execute a .cts file importing a .cts file', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--no-warnings',
fixtures.path('typescript/cts/test-require-commonjs.cts'),
]);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('execute a .cts file importing a .ts file export', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--no-warnings',
fixtures.path('typescript/cts/test-require-ts-file.cts'),
]);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('execute a .cts file importing a .mts file export', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/cts/test-require-mts-module.cts'),
]);

strictEqual(result.stdout, '');
match(result.stderr, /Error \[ERR_REQUIRE_ESM\]: require\(\) of ES Module/);
strictEqual(result.code, 1);
});

test('execute a .cts file importing a .mts file export', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-require-module',
fixtures.path('typescript/cts/test-require-mts-module.cts'),
]);

match(result.stderr, /Support for loading ES Module in require\(\) is an experimental feature and might change at any time/);
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('expect failure of a .cts file with default type module', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-default-type=module', // Keeps working with commonjs
'--no-warnings',
fixtures.path('typescript/cts/test-require-commonjs.cts'),
]);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('expect failure of a .cts file in node_modules', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/cts/test-cts-node_modules.cts'),
]);

strictEqual(result.stdout, '');
match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
strictEqual(result.code, 1);
});

test('expect failure of a .ts file in node_modules', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/cts/test-ts-node_modules.cts'),
]);

strictEqual(result.stdout, '');
match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
strictEqual(result.code, 1);
});

test('expect failure of a .cts requiring esm without default type module', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/cts/test-mts-node_modules.cts'),
]);

strictEqual(result.stdout, '');
match(result.stderr, /ERR_REQUIRE_ESM/);
strictEqual(result.code, 1);
});

test('expect failure of a .cts file requiring esm in node_modules', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-require-module',
fixtures.path('typescript/cts/test-mts-node_modules.cts'),
]);

strictEqual(result.stdout, '');
match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
strictEqual(result.code, 1);
});
84 changes: 84 additions & 0 deletions test/es-module/test-typescript-eval.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { spawnPromisified } from '../common/index.mjs';
import { match, strictEqual } from 'node:assert';
import { test } from 'node:test';

test('eval TypeScript ESM syntax', async () => {
const result = await spawnPromisified(process.execPath, [
'--input-type=module',
'--experimental-strip-types',
'--eval',
`import util from 'node:util'
const text: string = 'Hello, TypeScript!'
console.log(util.styleText('red', text));`]);

match(result.stderr, /Type Stripping is an experimental feature and might change at any time/);
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('eval TypeScript CommonJS syntax', async () => {
const result = await spawnPromisified(process.execPath, [
'--input-type=commonjs',
'--experimental-strip-types',
'--eval',
`const util = require('node:util');
const text: string = 'Hello, TypeScript!'
console.log(util.styleText('red', text));`,
'--no-warnings']);
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.stderr, '');
strictEqual(result.code, 0);
});

test('eval TypeScript CommonJS syntax by default', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--eval',
`const util = require('node:util');
const text: string = 'Hello, TypeScript!'
console.log(util.styleText('red', text));`,
'--no-warnings']);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('TypeScript ESM syntax not specified', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--eval',
`import util from 'node:util'
const text: string = 'Hello, TypeScript!'
console.log(text);`]);
match(result.stderr, /ExperimentalWarning: Type Stripping is an experimental/);
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('expect fail eval TypeScript CommonJS syntax with input-type module', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--input-type=module',
'--eval',
`const util = require('node:util');
const text: string = 'Hello, TypeScript!'
console.log(util.styleText('red', text));`]);

strictEqual(result.stdout, '');
match(result.stderr, /require is not defined in ES module scope, you can use import instead/);
strictEqual(result.code, 1);
});

test('expect fail eval TypeScript CommonJS syntax with input-type module', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--input-type=commonjs',
'--eval',
`import util from 'node:util'
const text: string = 'Hello, TypeScript!'
console.log(util.styleText('red', text));`]);
strictEqual(result.stdout, '');
match(result.stderr, /Cannot use import statement outside a module/);
strictEqual(result.code, 1);
});
97 changes: 97 additions & 0 deletions test/es-module/test-typescript-module.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import { match, strictEqual } from 'node:assert';
import { test } from 'node:test';

test('expect failure of a .mts file with CommonJS syntax', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/mts/test-mts-but-commonjs-syntax.mts'),
]);

strictEqual(result.stdout, '');
match(result.stderr, /require is not defined in ES module scope, you can use import instead/);
strictEqual(result.code, 1);
});

test('execute an .mts file importing an .mts file', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/mts/test-import-module.mts'),
]);

match(result.stderr, /Type Stripping is an experimental feature and might change at any time/);
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('execute an .mts file importing a .ts file', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-default-type=module', // this should fail
'--no-warnings',
fixtures.path('typescript/mts/test-import-ts-file.mts'),
]);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('execute an .mts file importing a .cts file', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--no-warnings',
'--no-warnings',
fixtures.path('typescript/mts/test-import-commonjs.mts'),
]);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('execute an .mts file with wrong default module', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-default-type=commonjs',
fixtures.path('typescript/mts/test-import-module.mts'),
]);

strictEqual(result.stdout, '');
match(result.stderr, /Error \[ERR_REQUIRE_ESM\]: require\(\) of ES Module/);
strictEqual(result.code, 1);
});

test('execute an .mts file from node_modules', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/mts/test-mts-node_modules.mts'),
]);

match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});

test('execute a .cts file from node_modules', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/mts/test-cts-node_modules.mts'),
]);

match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});

test('execute a .ts file from node_modules', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/mts/test-ts-node_modules.mts'),
]);

match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});
229 changes: 229 additions & 0 deletions test/es-module/test-typescript.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import { match, strictEqual } from 'node:assert';
import { test } from 'node:test';

test('execute a TypeScript file', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/ts/test-typescript.ts'),
]);

match(result.stderr, /Type Stripping is an experimental feature and might change at any time/);
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('execute a TypeScript file with imports', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-default-type=module',
'--no-warnings',
fixtures.path('typescript/ts/test-import-foo.ts'),
]);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('execute a TypeScript file with node_modules', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-default-type=module',
'--no-warnings',
fixtures.path('typescript/ts/test-typescript-node-modules.ts'),
]);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('expect error when executing a TypeScript file with imports with no extensions', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-default-type=module',
fixtures.path('typescript/ts/test-import-no-extension.ts'),
]);

match(result.stderr, /Error \[ERR_MODULE_NOT_FOUND\]:/);
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});

test('expect error when executing a TypeScript file with enum', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/ts/test-enums.ts'),
]);

// This error should be thrown during transformation
match(result.stderr, /TypeScript enum is not supported in strip-only mode/);
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});

test('expect error when executing a TypeScript file with experimental decorators', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/ts/test-experimental-decorators.ts'),
]);
// This error should be thrown at runtime
match(result.stderr, /Invalid or unexpected token/);
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});

test('expect error when executing a TypeScript file with namespaces', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/ts/test-namespaces.ts'),
]);
// This error should be thrown during transformation
match(result.stderr, /TypeScript namespace declaration is not supported in strip-only mode/);
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});

test('execute a TypeScript file with type definition', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--no-warnings',
fixtures.path('typescript/ts/test-import-types.ts'),
]);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('execute a TypeScript file with type definition but no type keyword', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-default-type=module',
fixtures.path('typescript/ts/test-import-no-type-keyword.ts'),
]);

match(result.stderr, /does not provide an export named 'MyType'/);
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});

test('execute a TypeScript file with CommonJS syntax', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--no-warnings',
fixtures.path('typescript/ts/test-commonjs-parsing.ts'),
]);
strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('execute a TypeScript file with ES module syntax', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-default-type=module',
'--no-warnings',
fixtures.path('typescript/ts/test-module-typescript.ts'),
]);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('expect failure of a TypeScript file requiring ES module syntax', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-require-module',
fixtures.path('typescript/ts/test-require-module.ts'),
]);

match(result.stderr, /Support for loading ES Module in require\(\) is an experimental feature and might change at any time/);
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('expect stack trace of a TypeScript file to be correct', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/ts/test-whitespacing.ts'),
]);

strictEqual(result.stdout, '');
match(result.stderr, /test-whitespacing\.ts:5:7/);
strictEqual(result.code, 1);
});

test('execute CommonJS TypeScript file from node_modules with require-module', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-default-type=module',
'--experimental-strip-types',
fixtures.path('typescript/ts/test-import-ts-node-modules.ts'),
]);

match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});

test('execute a TypeScript file with CommonJS syntax but default type module', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-default-type=module',
fixtures.path('typescript/ts/test-commonjs-parsing.ts'),
]);
strictEqual(result.stdout, '');
match(result.stderr, /require is not defined in ES module scope, you can use import instead/);
strictEqual(result.code, 1);
});

test('execute a TypeScript file with CommonJS syntax requiring .cts', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--no-warnings',
fixtures.path('typescript/ts/test-require-cts.ts'),
]);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('execute a TypeScript file with CommonJS syntax requiring .mts', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
fixtures.path('typescript/ts/test-require-mts.ts'),
]);

strictEqual(result.stdout, '');
match(result.stderr, /Error \[ERR_REQUIRE_ESM\]: require\(\) of ES Module/);
strictEqual(result.code, 1);
});

test('execute a TypeScript file with CommonJS syntax requiring .mts with require-module', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-require-module',
fixtures.path('typescript/ts/test-require-mts.ts'),
]);

match(result.stderr, /Support for loading ES Module in require\(\) is an experimental feature and might change at any time/);
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('execute a TypeScript file with CommonJS syntax requiring .mts with require-module', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--experimental-default-type=commonjs',
'--no-warnings',
fixtures.path('typescript/ts/test-require-cts.ts'),
]);

strictEqual(result.stderr, '');
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});
5 changes: 5 additions & 0 deletions test/fixtures/typescript/cts/node_modules/bar/bar.ts
13 changes: 13 additions & 0 deletions test/fixtures/typescript/cts/node_modules/bar/package.json
1 change: 1 addition & 0 deletions test/fixtures/typescript/cts/node_modules/baz/baz.mts
14 changes: 14 additions & 0 deletions test/fixtures/typescript/cts/node_modules/baz/package.json
5 changes: 5 additions & 0 deletions test/fixtures/typescript/cts/node_modules/foo/foo.cts
14 changes: 14 additions & 0 deletions test/fixtures/typescript/cts/node_modules/foo/package.json
3 changes: 3 additions & 0 deletions test/fixtures/typescript/cts/test-commonjs-export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const foo: string = 'Hello, TypeScript!';

module.exports = { foo };
5 changes: 5 additions & 0 deletions test/fixtures/typescript/cts/test-cts-but-module-syntax.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import util from 'node:util';

export const text: string = 'Hello, TypeScript!';

console.log(util.styleText(['bold', 'red'], text));
3 changes: 3 additions & 0 deletions test/fixtures/typescript/cts/test-cts-export-foo.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const foo: string = 'Hello, TypeScript!';

module.exports = { foo };
5 changes: 5 additions & 0 deletions test/fixtures/typescript/cts/test-cts-node_modules.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { foo } = require('foo');

interface Foo {};

console.log(foo);
3 changes: 3 additions & 0 deletions test/fixtures/typescript/cts/test-extensionless-require.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { foo } = require('./test-commonjs-export');

console.log(foo);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/cts/test-mts-node_modules.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { baz } = require('baz');

interface Foo { };

console.log(baz);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/cts/test-require-commonjs.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { foo } = require('./test-cts-export-foo.cts');

interface Foo {};

console.log(foo);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/cts/test-require-mts-module.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { foo } = require('../mts/test-mts-export-foo.mts');

interface Foo {};

console.log(foo);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/cts/test-require-ts-file.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { foo } = require('./test-commonjs-export.ts');

interface Foo {};

console.log(foo);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/cts/test-ts-node_modules.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { bar } = require('bar');

interface Foo { };

console.log(bar);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/mts/node_modules/bar/bar.ts
13 changes: 13 additions & 0 deletions test/fixtures/typescript/mts/node_modules/bar/package.json
1 change: 1 addition & 0 deletions test/fixtures/typescript/mts/node_modules/baz/baz.mts
13 changes: 13 additions & 0 deletions test/fixtures/typescript/mts/node_modules/baz/package.json
5 changes: 5 additions & 0 deletions test/fixtures/typescript/mts/node_modules/foo/foo.cts
14 changes: 14 additions & 0 deletions test/fixtures/typescript/mts/node_modules/foo/package.json
5 changes: 5 additions & 0 deletions test/fixtures/typescript/mts/test-cts-node_modules.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { foo } from 'foo';

interface Foo { };

console.log(foo);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/mts/test-import-commonjs.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { foo } from '../cts/test-cts-export-foo.cts';

interface Foo {};

console.log(foo);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/mts/test-import-module.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { foo } from './test-mts-export-foo.mts';

interface Foo {};

console.log(foo);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/mts/test-import-ts-file.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { foo } from './test-module-export.ts';

interface Foo {};

console.log(foo);
1 change: 1 addition & 0 deletions test/fixtures/typescript/mts/test-module-export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo: string = 'Hello, TypeScript!';
9 changes: 9 additions & 0 deletions test/fixtures/typescript/mts/test-mts-but-commonjs-syntax.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const util = require('node:util');

const text: string = 'Hello, TypeScript!';

console.log(util.styleText(['bold', 'red'], text));

module.exports = {
text
};
1 change: 1 addition & 0 deletions test/fixtures/typescript/mts/test-mts-export-foo.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo: string = 'Hello, TypeScript!';
5 changes: 5 additions & 0 deletions test/fixtures/typescript/mts/test-mts-node_modules.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { baz } from 'baz';

interface Foo {};

console.log(baz);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/mts/test-ts-node_modules.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { bar } from 'bar';

interface Foo {};

console.log(bar);
3 changes: 3 additions & 0 deletions test/fixtures/typescript/ts/node_modules/bar/bar.ts
13 changes: 13 additions & 0 deletions test/fixtures/typescript/ts/node_modules/bar/package.json
1 change: 1 addition & 0 deletions test/fixtures/typescript/ts/node_modules/foo/foo.js
14 changes: 14 additions & 0 deletions test/fixtures/typescript/ts/node_modules/foo/package.json
9 changes: 9 additions & 0 deletions test/fixtures/typescript/ts/test-commonjs-parsing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const util = require('node:util');

const text: string = 'Hello, TypeScript!';

console.log(util.styleText(['bold', 'red'], text));

module.exports = {
text
};
13 changes: 13 additions & 0 deletions test/fixtures/typescript/ts/test-enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
enum Color {
Red,
Green,
Blue,
}

console.log(Color.Red);
console.log(Color.Green);
console.log(Color.Blue);

console.log(Color[0]);
console.log(Color[1]);
console.log(Color[2]);
14 changes: 14 additions & 0 deletions test/fixtures/typescript/ts/test-experimental-decorators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}

@sealed
class BugReport {
type = "report";
title: string;

constructor(t: string) {
this.title = t;
}
}
1 change: 1 addition & 0 deletions test/fixtures/typescript/ts/test-export-foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo: string = "Hello, TypeScript!";
5 changes: 5 additions & 0 deletions test/fixtures/typescript/ts/test-import-foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { foo } from './test-export-foo.ts';

interface Foo {};

console.log(foo);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/ts/test-import-no-extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { foo } from './test-no-extensions';

interface Foo {};

console.log(foo);
7 changes: 7 additions & 0 deletions test/fixtures/typescript/ts/test-import-no-type-keyword.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { MyType } from './test-types.d.ts';

const myVar: MyType = {
foo: 'Hello, TypeScript!'
};

console.log(myVar.foo);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/ts/test-import-ts-node-modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { bar } from 'bar';

interface Bar {};

console.log(bar);
7 changes: 7 additions & 0 deletions test/fixtures/typescript/ts/test-import-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { MyType } from './test-types.d.ts';

const myVar: MyType = {
foo: 'Hello, TypeScript!'
};

console.log(myVar.foo);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/ts/test-module-typescript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import util from 'node:util';

export const text: string = 'Hello, TypeScript!';

console.log(util.styleText("red", text));
9 changes: 9 additions & 0 deletions test/fixtures/typescript/ts/test-namespaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// <reference path="a.ts" />
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
1 change: 1 addition & 0 deletions test/fixtures/typescript/ts/test-no-extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo: string = 'Hello, TypeScript!';
5 changes: 5 additions & 0 deletions test/fixtures/typescript/ts/test-require-cts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { foo } = require('../cts/test-cts-export-foo.cts');

interface Foo {};

console.log(foo);
3 changes: 3 additions & 0 deletions test/fixtures/typescript/ts/test-require-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { foo } = require('../mts/test-mts-export-foo.mts');

console.log(foo);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/ts/test-require-mts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { foo } = require('../mts/test-mts-export-foo.mts');

interface Foo { };

console.log(foo);
3 changes: 3 additions & 0 deletions test/fixtures/typescript/ts/test-types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type MyType = {
foo: string;
};
3 changes: 3 additions & 0 deletions test/fixtures/typescript/ts/test-typescript-node-modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { foo } from 'foo';

console.log(foo);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/ts/test-typescript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const str: string = "Hello, TypeScript!";
interface Foo {
bar: string;
}
console.log(str);
5 changes: 5 additions & 0 deletions test/fixtures/typescript/ts/test-whitespacing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
interface Foo {
bar: string;
}

throw new Error("Whitespacing");
1 change: 1 addition & 0 deletions test/parallel/test-process-versions.js
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ const expected_keys = [
'ada',
'cjs_module_lexer',
'nbytes',
'amaro',
];

const hasUndici = process.config.variables.node_builtin_shareable_builtins.includes('deps/undici/undici.js');
83 changes: 83 additions & 0 deletions tools/dep_updaters/update-amaro.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/bin/sh

# Shell script to update amaro in the source tree to the latest release.

# This script must be in the tools directory when it runs because it uses the
# script source file path to determine directories to work in.

set -ex

BASE_DIR=$(cd "$(dirname "$0")/../.." && pwd)
[ -z "$NODE" ] && NODE="$BASE_DIR/out/Release/node"
[ -x "$NODE" ] || NODE=$(command -v node)
DEPS_DIR="$BASE_DIR/deps"
NPM="$DEPS_DIR/npm/bin/npm-cli.js"

# shellcheck disable=SC1091
. "$BASE_DIR/tools/dep_updaters/utils.sh"

NEW_VERSION=$("$NODE" "$NPM" view amaro dist-tags.latest)

CURRENT_VERSION=$("$NODE" -p "require('./deps/amaro/package.json').version")

# This function exit with 0 if new version and current version are the same
compare_dependency_version "amaro" "$NEW_VERSION" "$CURRENT_VERSION"

cd "$( dirname "$0" )/../.." || exit

echo "Making temporary workspace..."

WORKSPACE=$(mktemp -d 2> /dev/null || mktemp -d -t 'tmp')

cleanup () {
EXIT_CODE=$?
[ -d "$WORKSPACE" ] && rm -rf "$WORKSPACE"
exit $EXIT_CODE
}

trap cleanup INT TERM EXIT

cd "$WORKSPACE"

echo "Fetching amaro source archive..."

"$NODE" "$NPM" pack "amaro@$NEW_VERSION"

amaro_TGZ="amaro-$NEW_VERSION.tgz"

log_and_verify_sha256sum "amaro" "$amaro_TGZ"

cp ./* "$DEPS_DIR/amaro/LICENSE"

rm -r "$DEPS_DIR/amaro"/*

tar -xf "$amaro_TGZ"

cd package

rm -rf node_modules

mv ./* "$DEPS_DIR/amaro"

# update version information in src/undici_version.h
cat > "$ROOT/src/amaro_version.h" <<EOF
// This is an auto generated file, please do not edit.
// Refer to tools/dep_updaters/update-amaro.sh
#ifndef SRC_AMARO_VERSION_H_
#define SRC_AMARO_VERSION_H_
#define AMARO_VERSION "$NEW_VERSION"
#endif // SRC_AMARO_VERSION_H_
EOF

echo "All done!"
echo ""
echo "Please git add amaro, commit the new version:"
echo ""
echo "$ git add -A deps/amaro"
echo "$ git commit -m \"deps: update amaro to $NEW_VERSION\""
echo ""

# Update the version number on maintaining-dependencies.md
# and print the new version as the last line of the script as we need
# to add it to $GITHUB_ENV variable
finalize_version_update "amaro" "$NEW_VERSION" "src/amaro_version.h"
2 changes: 2 additions & 0 deletions tools/license-builder.sh
Original file line number Diff line number Diff line change
@@ -38,6 +38,8 @@ licenseText="$(cat "${rootdir}/deps/cjs-module-lexer/LICENSE")"
addlicense "cjs-module-lexer" "deps/cjs-module-lexer" "$licenseText"
licenseText="$(cat "${rootdir}/deps/v8/third_party/ittapi/LICENSES/BSD-3-Clause.txt")"
addlicense "ittapi" "deps/v8/third_party/ittapi" "$licenseText"
licenseText="$(cat "${rootdir}/deps/amaro/LICENSE.md")"
addlicense "amaro" "deps/amaro" "$licenseText"
if [ -f "${rootdir}/deps/icu/LICENSE" ]; then
# ICU 57 and following. Drop the BOM
licenseText="$(sed -e '1s/^[^a-zA-Z ]*ICU/ICU/' -e :a -e 's/<[^>]*>//g;s/ / /g;s/ +$//;/</N;//ba' "${rootdir}/deps/icu/LICENSE")"