fulmine.js 5.19.0 → 5.19.1

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.
Files changed (2) hide show
  1. package/package.json +7 -5
  2. package/src/cli.js +79 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fulmine.js",
3
- "version": "5.19.0",
3
+ "version": "5.19.1",
4
4
  "description": "Drop-in Express 5 replacement on uWebSockets.js. Your existing middleware keeps working.",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -119,9 +119,9 @@
119
119
  "@commitlint/cli": "^21.2.1",
120
120
  "@commitlint/config-conventional": "^21.2.0",
121
121
  "@eslint/js": "^10.0.1",
122
- "@nestjs/common": "^11.2.1",
123
- "@nestjs/core": "^11.2.1",
124
- "@nestjs/platform-express": "^11.2.1",
122
+ "@nestjs/common": "^12.0.1",
123
+ "@nestjs/core": "^12.0.1",
124
+ "@nestjs/platform-express": "^12.0.1",
125
125
  "@release-it/conventional-changelog": "^12.0.0",
126
126
  "@types/accepts": "^1.3.7",
127
127
  "@types/bytes": "^3.1.5",
@@ -182,7 +182,7 @@
182
182
  "pako": "^3.0.1",
183
183
  "passport": "^0.7.0",
184
184
  "passport-local": "^1.0.0",
185
- "pkg-pr-new": "^0.0.87",
185
+ "pkg-pr-new": "^0.0.88",
186
186
  "prettier": "^3.9.6",
187
187
  "pug": "^3.0.4",
188
188
  "reflect-metadata": "^0.2.2",
@@ -198,6 +198,8 @@
198
198
  "swagger-ui-express": "^5.0.1",
199
199
  "swig": "^1.4.2",
200
200
  "tsd": "^0.33.0",
201
+ "typescript": "^6.0.3",
202
+ "typescript7": "npm:typescript@^7.0.2",
201
203
  "vhost": "^3.0.2"
202
204
  },
203
205
  "contributors": [
package/src/cli.js CHANGED
@@ -143,19 +143,42 @@ function collectFiles(dir) {
143
143
  }
144
144
 
145
145
  /**
146
- * The TypeScript compiler belonging to the project being migrated, or null when it has none.
146
+ * A reader for the .ts files of the project being migrated, or null when it has no TypeScript.
147
147
  *
148
148
  * acorn cannot read TypeScript, and shipping a parser that can would put megabytes into this
149
149
  * package for a command most people run once. A TypeScript project already has the compiler, so
150
150
  * it is resolved from there. A project without one is told its .ts files were left alone rather
151
151
  * than having them quietly skipped, which is what happened before they were looked at at all.
152
152
  *
153
+ * typescript 7 is the compiler rewritten in Go, and it publishes no JavaScript parser any more:
154
+ * require("typescript") gives back a version number and nothing else. Its scanner survives, on an
155
+ * ESM-only subpath that require() reads on every node this package supports, so 7 gets the token
156
+ * walk and 6 keeps the tree.
157
+ *
153
158
  * @param {string} target directory being migrated
154
- * @returns {any|null}
159
+ * @returns {((source: string, fileName: string, seen?: Set<string>) => {start: number, end: number}[])|null}
155
160
  */
156
161
  function loadTypeScript(target) {
162
+ /** @param {string} name */
163
+ const load = (name) => require(require.resolve(name, { paths: [target, process.cwd()] }));
164
+
157
165
  try {
158
- return require(require.resolve("typescript", { paths: [target, process.cwd()] }));
166
+ const ts = load("typescript");
167
+ if (typeof ts.createSourceFile === "function") {
168
+ return (source, fileName, seen) => findSpecifiersTypeScript(source, fileName, ts, seen);
169
+ }
170
+ } catch {
171
+ return null;
172
+ }
173
+
174
+ try {
175
+ const { createScanner } = load("typescript/unstable/ast/scanner");
176
+ const { LanguageVariant, SyntaxKind } = load("typescript/unstable/ast");
177
+ // an unstable subpath, so the name the token loop stops on is checked here rather than read
178
+ // as undefined and scanned past the end of the file forever
179
+ if (SyntaxKind.EndOfFile === undefined) return null;
180
+ const ts = { createScanner, LanguageVariant, SyntaxKind };
181
+ return (source, fileName, seen) => findSpecifiersScanner(source, fileName, ts, seen);
159
182
  } catch {
160
183
  return null;
161
184
  }
@@ -222,6 +245,53 @@ function findSpecifiersTypeScript(source, fileName, ts, seen) {
222
245
  return found;
223
246
  }
224
247
 
248
+ /**
249
+ * The same specifiers again, read from the token stream instead of from a tree. typescript 7 has
250
+ * no parser to give a tree, and its scanner already does the part that a search over the text gets
251
+ * wrong: comments, template literals and strings that only look like imports are not tokens here.
252
+ *
253
+ * @param {string} source
254
+ * @param {string} fileName decides whether JSX is allowed, as above
255
+ * @param {any} ts the scanner and the two enums loadTypeScript kept
256
+ * @param {Set<string>} [seen] as in findSpecifiers
257
+ * @returns {{start: number, end: number}[]}
258
+ */
259
+ function findSpecifiersScanner(source, fileName, ts, seen) {
260
+ const kind = ts.SyntaxKind;
261
+ const scanner = ts.createScanner(
262
+ true,
263
+ fileName.endsWith(".tsx") ? ts.LanguageVariant.JSX : ts.LanguageVariant.Standard,
264
+ source
265
+ );
266
+
267
+ /** @type {{start: number, end: number}[]} */
268
+ const found = [];
269
+ // the three tokens before the one in hand, newest first
270
+ let back1 = -1;
271
+ let back2 = -1;
272
+ let back3 = -1;
273
+
274
+ for (let token = scanner.scan(); token !== kind.EndOfFile; token = scanner.scan()) {
275
+ // from "express", and import("express") or require("express") as a call. The dot rules out
276
+ // obj.require("express"), which is somebody else's require and not a module specifier
277
+ const isFrom = back1 === kind.FromKeyword;
278
+ const isRequire = back2 === kind.RequireKeyword && back3 !== kind.DotToken && back3 !== kind.QuestionDotToken;
279
+ const isCall = back1 === kind.OpenParenToken && (back2 === kind.ImportKeyword || isRequire);
280
+ if (token === kind.StringLiteral && (isFrom || isCall)) {
281
+ const text = scanner.getTokenValue();
282
+ if (text === FROM) {
283
+ found.push({ start: scanner.getTokenStart(), end: scanner.getTokenEnd() });
284
+ } else if (seen && BUILT_IN_INSTEAD[text]) {
285
+ seen.add(text);
286
+ }
287
+ }
288
+ back3 = back2;
289
+ back2 = back1;
290
+ back1 = token;
291
+ }
292
+ return found;
293
+ }
294
+
225
295
  /**
226
296
  * The string literals naming the module, found through the parser rather than by searching the
227
297
  * text. "express" appears inside express-session, inside comments and inside strings that are not
@@ -874,7 +944,7 @@ Options:
874
944
 
875
945
  // resolved once, and only if there is anything to use it on
876
946
  const hasTypeScriptFiles = files.some((file) => TYPESCRIPT_EXTENSIONS.has(path.extname(file)));
877
- const ts = hasTypeScriptFiles ? loadTypeScript(target) : null;
947
+ const readTypeScript = hasTypeScriptFiles ? loadTypeScript(target) : null;
878
948
 
879
949
  for (const file of files) {
880
950
  const source = fs.readFileSync(file, "utf8");
@@ -883,14 +953,15 @@ Options:
883
953
  if (!source.includes(FROM) && !Object.keys(BUILT_IN_INSTEAD).some((name) => source.includes(name))) continue;
884
954
 
885
955
  const isTypeScript = TYPESCRIPT_EXTENSIONS.has(path.extname(file));
886
- if (isTypeScript && !ts) {
956
+ if (isTypeScript && !readTypeScript) {
887
957
  needTypeScript.push(path.relative(target, file));
888
958
  continue;
889
959
  }
890
960
 
891
- const specifiers = isTypeScript
892
- ? findSpecifiersTypeScript(source, file, ts, builtInInstead)
893
- : findSpecifiers(source, builtInInstead);
961
+ const specifiers =
962
+ isTypeScript && readTypeScript
963
+ ? readTypeScript(source, file, builtInInstead)
964
+ : findSpecifiers(source, builtInInstead);
894
965
  if (specifiers === null) {
895
966
  unparsed.push(path.relative(target, file));
896
967
  continue;