nitro 3.0.260415-beta → 3.0.260429-beta

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 (50) hide show
  1. package/dist/THIRD-PARTY-LICENSES.md +32 -2
  2. package/dist/_build/common.mjs +43 -29
  3. package/dist/_build/rolldown.mjs +1 -1
  4. package/dist/_build/rollup.mjs +2 -2
  5. package/dist/_build/vite.build.mjs +1 -1
  6. package/dist/_chunks/nitro.mjs +5 -5
  7. package/dist/_chunks/nitro2.mjs +1 -1
  8. package/dist/_chunks/nitro3.mjs +1 -1
  9. package/dist/_chunks/nitro4.mjs +2 -2
  10. package/dist/_chunks/utils.mjs +2 -2
  11. package/dist/_common.mjs +1 -1
  12. package/dist/_dev.mjs +3 -2
  13. package/dist/_libs/c12+rc9.mjs +21 -9
  14. package/dist/_libs/hasown+resolve+deepmerge.mjs +47 -34
  15. package/dist/_libs/httpxy.d.mts +1 -3
  16. package/dist/_libs/httpxy.mjs +5 -5
  17. package/dist/_libs/nypm+tinyexec.mjs +129 -98
  18. package/dist/_libs/pkg-types.d.mts +7 -4
  19. package/dist/_libs/plugin-inject.mjs +3 -1
  20. package/dist/_libs/pluginutils.mjs +4 -2
  21. package/dist/_libs/readdirp+chokidar.mjs +2 -1
  22. package/dist/_libs/{unimport+unplugin.mjs → unimport+unplugin+oxc-parser.mjs} +49 -37
  23. package/dist/_libs/unplugin+unimport.d.mts +1 -1
  24. package/dist/_presets.mjs +2 -1
  25. package/dist/cli/_chunks/build.mjs +1 -1
  26. package/dist/cli/_chunks/deploy.mjs +1 -1
  27. package/dist/cli/_chunks/dev.mjs +1 -1
  28. package/dist/cli/_chunks/list.mjs +1 -1
  29. package/dist/cli/_chunks/prepare.mjs +1 -1
  30. package/dist/cli/_chunks/preview.mjs +1 -1
  31. package/dist/cli/_chunks/run.mjs +1 -1
  32. package/dist/docs/0.docs/5.cache.md +53 -9
  33. package/dist/node_modules/ufo/dist/index.mjs +2 -2
  34. package/dist/node_modules/ufo/package.json +9 -9
  35. package/dist/presets/azure/runtime/azure-swa.mjs +2 -0
  36. package/dist/runtime/internal/route-rules.d.mts +1 -0
  37. package/dist/runtime/internal/route-rules.mjs +29 -1
  38. package/dist/runtime/internal/routes/scalar.mjs +1 -0
  39. package/dist/types/index.d.mts +922 -64
  40. package/dist/vite.d.mts +1 -1
  41. package/dist/vite.mjs +11 -5
  42. package/package.json +28 -27
  43. package/dist/_libs/_.mjs +0 -2
  44. package/dist/_libs/_2.mjs +0 -2
  45. package/dist/_libs/_3.mjs +0 -2
  46. package/dist/_libs/_4.mjs +0 -2
  47. package/dist/_libs/_5.mjs +0 -2
  48. package/dist/_libs/_6.mjs +0 -2
  49. package/dist/_libs/_7.mjs +0 -2
  50. package/dist/_libs/escape-string-regexp.mjs +0 -5
@@ -1,47 +1,46 @@
1
- import { i as __toESM } from "../_common.mjs";
2
- import { N as require_picomatch, S as MagicString, T as parse$1, b as getMagicString, ct as join$1, dt as resolve$1, st as isAbsolute$2, y as createUnimport } from "../_build/common.mjs";
1
+ import { i as __toESM, n as __exportAll } from "../_common.mjs";
2
+ import { N as require_picomatch, S as MagicString, T as parse$2, b as getMagicString, ct as isAbsolute$2, ft as resolve$1, lt as join$1, y as createUnimport } from "../_build/common.mjs";
3
3
  import { t as walk } from "./estree-walker.mjs";
4
4
  import { t as remapping } from "./remapping.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import fs, { promises } from "node:fs";
7
7
  import path, { basename, dirname, extname, isAbsolute, normalize, resolve } from "node:path";
8
8
  import process$1 from "node:process";
9
+ import { parseSync } from "rolldown/utils";
9
10
  import { Buffer } from "node:buffer";
10
11
  import * as querystring from "node:querystring";
11
- async function detectImportsAcorn(code, ctx, options) {
12
- const s = getMagicString(code);
13
- const map = await ctx.getImportMap();
14
- let matchedImports = [];
15
- const enableAutoImport = options?.autoImport !== false;
16
- const enableTransformVirtualImports = options?.transformVirtualImports !== false && ctx.options.virtualImports?.length;
17
- if (enableAutoImport || enableTransformVirtualImports) {
18
- const ast = parse$1(s.original, {
19
- sourceType: "module",
20
- ecmaVersion: "latest",
21
- locations: true
22
- });
23
- const virtualImports = createVirtualImportsAcronWalker(map, ctx.options.virtualImports);
24
- const scopes = traveseScopes(ast, enableTransformVirtualImports ? virtualImports.walk : {});
25
- if (enableAutoImport) {
26
- const identifiers = scopes.unmatched;
27
- matchedImports.push(...Array.from(identifiers).map((name) => {
28
- const item = map.get(name);
29
- if (item && !item.disabled) return item;
30
- return null;
31
- }).filter(Boolean));
32
- for (const addon of ctx.addons) matchedImports = await addon.matchImports?.call(ctx, identifiers, matchedImports) || matchedImports;
12
+ function createEstreeDetector(parse) {
13
+ return async (code, ctx, options) => {
14
+ const s = getMagicString(code);
15
+ const map = await ctx.getImportMap();
16
+ let matchedImports = [];
17
+ const enableAutoImport = options?.autoImport !== false;
18
+ const enableTransformVirtualImports = options?.transformVirtualImports !== false && ctx.options.virtualImports?.length;
19
+ if (enableAutoImport || enableTransformVirtualImports) {
20
+ const ast = parse(s.original);
21
+ const virtualImports = createVirtualImports(map, ctx.options.virtualImports);
22
+ const scopes = traveseScopes(ast, enableTransformVirtualImports ? virtualImports.walk : {});
23
+ if (enableAutoImport) {
24
+ const identifiers = scopes.unmatched;
25
+ matchedImports.push(...Array.from(identifiers).map((name) => {
26
+ const item = map.get(name);
27
+ if (item && !item.disabled) return item;
28
+ return null;
29
+ }).filter(Boolean));
30
+ for (const addon of ctx.addons) matchedImports = await addon.matchImports?.call(ctx, identifiers, matchedImports) || matchedImports;
31
+ }
32
+ virtualImports.ranges.forEach(([start, end]) => {
33
+ s.remove(start, end);
34
+ });
35
+ matchedImports.push(...virtualImports.imports);
33
36
  }
34
- virtualImports.ranges.forEach(([start, end]) => {
35
- s.remove(start, end);
36
- });
37
- matchedImports.push(...virtualImports.imports);
38
- }
39
- return {
40
- s,
41
- strippedCode: code.toString(),
42
- matchedImports,
43
- isCJSContext: false,
44
- firstOccurrence: 0
37
+ return {
38
+ s,
39
+ strippedCode: code.toString(),
40
+ matchedImports,
41
+ isCJSContext: false,
42
+ firstOccurrence: 0
43
+ };
45
44
  };
46
45
  }
47
46
  function traveseScopes(ast, additionalWalk) {
@@ -164,7 +163,7 @@ function traveseScopes(ast, additionalWalk) {
164
163
  scopes
165
164
  };
166
165
  }
167
- function createVirtualImportsAcronWalker(importMap, virtualImports = []) {
166
+ function createVirtualImports(importMap, virtualImports = []) {
168
167
  const imports = [];
169
168
  const ranges = [];
170
169
  return {
@@ -190,6 +189,14 @@ function createVirtualImportsAcronWalker(importMap, virtualImports = []) {
190
189
  } }
191
190
  };
192
191
  }
192
+ var detect_acorn_exports = /* @__PURE__ */ __exportAll({ detectImportsAcorn: () => detectImportsAcorn });
193
+ const detectImportsAcorn = createEstreeDetector((code) => parse$2(code, {
194
+ sourceType: "module",
195
+ ecmaVersion: "latest",
196
+ locations: true
197
+ }));
198
+ var detect_oxc_exports = /* @__PURE__ */ __exportAll({ detectImportsOxc: () => detectImportsOxc });
199
+ const detectImportsOxc = createEstreeDetector((code) => parseSync("", code, { sourceType: "module" }).program);
193
200
  var import_picomatch = /* @__PURE__ */ __toESM(require_picomatch(), 1);
194
201
  function toArray$2(array) {
195
202
  array = array || [];
@@ -1602,6 +1609,11 @@ function createFilter(include, exclude, options) {
1602
1609
  return !includeMatchers.length;
1603
1610
  };
1604
1611
  }
1612
+ var unplugin_exports = /* @__PURE__ */ __exportAll({
1613
+ default: () => unplugin,
1614
+ defaultExcludes: () => defaultExcludes,
1615
+ defaultIncludes: () => defaultIncludes
1616
+ });
1605
1617
  const defaultIncludes = [
1606
1618
  /\.[jt]sx?$/,
1607
1619
  /\.vue$/,
@@ -1638,4 +1650,4 @@ const unplugin = createUnplugin((options = {}) => {
1638
1650
  }
1639
1651
  };
1640
1652
  });
1641
- export { detectImportsAcorn as a, createVirtualImportsAcronWalker as i, defaultIncludes as n, traveseScopes as o, unplugin as r, defaultExcludes as t };
1653
+ export { detect_oxc_exports as n, detect_acorn_exports as r, unplugin_exports as t };
@@ -367,7 +367,7 @@ interface InjectImportsOptions {
367
367
  *
368
368
  * @default 'regex'
369
369
  */
370
- parser?: 'acorn' | 'regex';
370
+ parser?: 'acorn' | 'oxc' | 'regex';
371
371
  /**
372
372
  * Inject the imports at the end of other imports
373
373
  *
package/dist/_presets.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { B as f, F as prettyPath$1, G as findNearestFile, H as v, J as readPackageJSON, L as writeFile$2, M as glob, R, W as findFile, Y as i, Z as a, at as dirname$1, ct as join$1, dt as resolve$1, et as r, nt as resolveModulePath, q as readGitConfig, ut as relative$1 } from "./_build/common.mjs";
1
+ import { F as prettyPath$1, G as findFile, J as readGitConfig, K as findNearestFile, L as writeFile$2, M as glob, Q as a, U as v, V as f, X as i, Y as readPackageJSON, dt as relative$1, ft as resolve$1, lt as join$1, ot as dirname$1, rt as resolveModulePath, tt as r, z as R } from "./_build/common.mjs";
2
2
  import { r as resolveCompatibilityDatesFromEnv, t as formatCompatibilityDate } from "./_libs/compatx.mjs";
3
3
  import { t as Router } from "./_chunks/nitro3.mjs";
4
4
  import { t as importDep } from "./_chunks/utils.mjs";
@@ -1365,6 +1365,7 @@ async function generateFunctionFiles(nitro) {
1365
1365
  launcherType: "Nodejs",
1366
1366
  shouldAddHelpers: false,
1367
1367
  supportsResponseStreaming: true,
1368
+ ...nitro.options.sourcemap ? { shouldAddSourcemapSupport: true } : {},
1368
1369
  ...nitro.options.vercel?.functions
1369
1370
  };
1370
1371
  if (Array.isArray(baseFunctionConfig.experimentalTriggers) && baseFunctionConfig.experimentalTriggers.length > 0) nitro.logger.warn("`experimentalTriggers` on the base `vercel.functions` config applies to the catch-all function and is likely not what you want. Routes with queue triggers are not accessible on the web. Use `vercel.functionRules` to attach triggers to specific routes instead.");
@@ -1,4 +1,4 @@
1
- import { dt as resolve } from "../../_build/common.mjs";
1
+ import { ft as resolve } from "../../_build/common.mjs";
2
2
  import { t as defineCommand } from "../../_libs/citty.mjs";
3
3
  import { t as commonArgs } from "./common.mjs";
4
4
  import { build, copyPublicAssets, createNitro, prepare, prerender } from "nitro/builder";
@@ -1,4 +1,4 @@
1
- import { dt as resolve, m as getBuildInfo, ut as relative } from "../../_build/common.mjs";
1
+ import { dt as relative, ft as resolve, m as getBuildInfo } from "../../_build/common.mjs";
2
2
  import { t as defineCommand } from "../../_libs/citty.mjs";
3
3
  import build_default, { buildArgs } from "./build.mjs";
4
4
  import consola from "consola";
@@ -1,4 +1,4 @@
1
- import { dt as resolve } from "../../_build/common.mjs";
1
+ import { ft as resolve } from "../../_build/common.mjs";
2
2
  import { t as NitroDevServer } from "../../_dev.mjs";
3
3
  import { t as defineCommand } from "../../_libs/citty.mjs";
4
4
  import { t as commonArgs } from "./common.mjs";
@@ -1,4 +1,4 @@
1
- import { dt as resolve } from "../../_build/common.mjs";
1
+ import { ft as resolve } from "../../_build/common.mjs";
2
2
  import { t as defineCommand } from "../../_libs/citty.mjs";
3
3
  import { consola as consola$1 } from "consola";
4
4
  import { listTasks, loadOptions } from "nitro/builder";
@@ -1,4 +1,4 @@
1
- import { dt as resolve } from "../../_build/common.mjs";
1
+ import { ft as resolve } from "../../_build/common.mjs";
2
2
  import { t as defineCommand } from "../../_libs/citty.mjs";
3
3
  import { t as commonArgs } from "./common.mjs";
4
4
  import { createNitro, writeTypes } from "nitro/builder";
@@ -1,4 +1,4 @@
1
- import { dt as resolve } from "../../_build/common.mjs";
1
+ import { ft as resolve } from "../../_build/common.mjs";
2
2
  import { t as defineCommand } from "../../_libs/citty.mjs";
3
3
  import { t as startPreview } from "../../_chunks/nitro4.mjs";
4
4
  import { t as commonArgs } from "./common.mjs";
@@ -1,4 +1,4 @@
1
- import { dt as resolve } from "../../_build/common.mjs";
1
+ import { ft as resolve } from "../../_build/common.mjs";
2
2
  import { t as defineCommand } from "../../_libs/citty.mjs";
3
3
  import { consola as consola$1 } from "consola";
4
4
  import destr from "destr";
@@ -357,7 +357,59 @@ When `swr` is disabled and a cached value has expired:
357
357
  1. The stale entry is cleared.
358
358
  2. The client waits for the function/handler to resolve with a fresh value.
359
359
 
360
- ## Cache keys and invalidation
360
+ ## Cache invalidation
361
+ Cached entries can be invalidated programmatically at runtime (for example from a webhook when the underlying data changes) without waiting for `maxAge` to expire.
362
+
363
+ ### `.invalidate()` method
364
+
365
+ Every function created with `defineCachedFunction` exposes an `.invalidate()` method. Arguments are passed through `getKey` to generate the cache key.
366
+
367
+ ```ts
368
+ import { defineCachedFunction } from "nitro/cache";
369
+
370
+ const cachedGHStars = defineCachedFunction(async (repo: string) => {
371
+ const data = await fetch(`https://api.github.com/repos/${repo}`).then(res => res.json());
372
+ return data.stargazers_count;
373
+ }, {
374
+ maxAge: 60 * 60,
375
+ name: "ghStars",
376
+ getKey: (repo: string) => repo,
377
+ });
378
+
379
+ await cachedGHStars("unjs/nitro"); // populates the cache
380
+ await cachedGHStars.invalidate("unjs/nitro"); // removes the entry
381
+ await cachedGHStars("unjs/nitro"); // re-invokes the function
382
+ ```
383
+
384
+ If no cached entry matches the given arguments, `.invalidate()` resolves without error and leaves storage unchanged.
385
+
386
+ ### `invalidateCache()` helper
387
+
388
+ `invalidateCache` is a helper from `ocache`. It invalidates a cached entry from the cache options used to define it, so invalidation can live anywhere in your app, independent of where the cached function is defined.
389
+
390
+ Pass the same `name`, `group`, `base`, and `getKey` used when defining the function, along with the `args` identifying the entry to remove:
391
+
392
+ ```ts
393
+ import { invalidateCache } from "ocache";
394
+
395
+ await invalidateCache({
396
+ options: {
397
+ name: "ghStars",
398
+ group: "nitro/functions",
399
+ getKey: (repo: string) => repo,
400
+ },
401
+ args: ["unjs/nitro"],
402
+ });
403
+ ```
404
+
405
+ <important>
406
+
407
+ The `name`, `group`, `base`, and `getKey` passed to `invalidateCache` must match the ones used when the cached function was defined. Mismatched options resolve to a different storage key and will not invalidate the intended entry. :br :br
408
+ Nitro defaults to `group: 'nitro/functions'` for cached functions and `group: 'nitro/handlers'` for cached handlers (or `'nitro/route-rules'` when using [route rules](#using-route-rules)).
409
+ </important>
410
+
411
+ ## Cache keys
412
+
361
413
  When using the `defineCachedFunction` or `defineCachedHandler` functions, the cache key is generated using the following pattern:
362
414
 
363
415
  ```ts
@@ -384,14 +436,6 @@ Will generate the following cache key:
384
436
  cache:nitro/functions:getAccessToken:default.json
385
437
  ```
386
438
 
387
- You can invalidate the cached function entry with:
388
-
389
- ```ts
390
- import { useStorage } from "nitro/storage";
391
-
392
- await useStorage('cache').removeItem('nitro/functions:getAccessToken:default.json')
393
- ```
394
-
395
439
  <note>
396
440
 
397
441
  For cached handlers, the cache key includes a hash of the URL path and, when using the [`varies`](#handler-only-options) option, hashes of the specified header values appended to the key.
@@ -286,8 +286,8 @@ function withoutBase(input, base) {
286
286
  if (nextChar && nextChar !== "/" && nextChar !== "?") {
287
287
  return input;
288
288
  }
289
- const trimmed = input.slice(_base.length);
290
- return trimmed[0] === "/" ? trimmed : "/" + trimmed;
289
+ const trimmed = input.slice(_base.length).replace(/^\/+/, "");
290
+ return "/" + trimmed;
291
291
  }
292
292
  function withQuery(input, query) {
293
293
  const parsed = parseURL(input);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ufo",
3
- "version": "1.6.3",
3
+ "version": "1.6.4",
4
4
  "description": "URL utils for humans",
5
5
  "repository": "unjs/ufo",
6
6
  "license": "MIT",
@@ -31,18 +31,18 @@
31
31
  "test": "pnpm lint && vitest run --typecheck"
32
32
  },
33
33
  "devDependencies": {
34
- "@types/node": "^25.0.8",
35
- "@vitest/coverage-v8": "^4.0.17",
36
- "automd": "^0.4.2",
34
+ "@types/node": "^25.6.0",
35
+ "@vitest/coverage-v8": "^4.1.5",
36
+ "automd": "^0.4.3",
37
37
  "changelogen": "^0.6.2",
38
- "eslint": "^9.39.2",
38
+ "eslint": "^10.2.1",
39
39
  "eslint-config-unjs": "^0.6.2",
40
40
  "jiti": "^2.6.1",
41
- "prettier": "^3.7.4",
42
- "typescript": "^5.9.3",
41
+ "prettier": "^3.8.3",
42
+ "typescript": "^6.0.3",
43
43
  "unbuild": "^3.6.1",
44
44
  "untyped": "^2.0.0",
45
- "vitest": "^4.0.17"
45
+ "vitest": "^4.1.5"
46
46
  },
47
- "packageManager": "pnpm@10.28.0"
47
+ "packageManager": "pnpm@10.33.2"
48
48
  }
@@ -16,6 +16,8 @@ export async function handle(context, req) {
16
16
  }
17
17
  const request = new Request(url, {
18
18
  method: req.method || undefined,
19
+
20
+
19
21
  body: req.bufferBody ?? req.rawBody
20
22
  });
21
23
  const response = await nitroApp.fetch(request);
@@ -8,4 +8,5 @@ export declare const redirect: RouteRuleCtor<"redirect">;
8
8
  export declare const proxy: RouteRuleCtor<"proxy">;
9
9
  export declare const cache: RouteRuleCtor<"cache">;
10
10
  export declare const basicAuth: RouteRuleCtor<"auth">;
11
+ export declare function isPathInScope(pathname: string, base: string): boolean;
11
12
  export {};
@@ -1,4 +1,4 @@
1
- import { proxyRequest, redirect as sendRedirect, requireBasicAuth } from "h3";
1
+ import { HTTPError, proxyRequest, redirect as sendRedirect, requireBasicAuth } from "h3";
2
2
  import { joinURL, withQuery, withoutBase } from "ufo";
3
3
  import { defineCachedHandler } from "./cache.mjs";
4
4
 
@@ -17,7 +17,12 @@ export const redirect = ((m) => function redirectRouteRule(event) {
17
17
  let targetPath = event.url.pathname + event.url.search;
18
18
  const strpBase = m.options._redirectStripBase;
19
19
  if (strpBase) {
20
+ if (!isPathInScope(event.url.pathname, strpBase)) {
21
+ throw new HTTPError({ status: 400 });
22
+ }
20
23
  targetPath = withoutBase(targetPath, strpBase);
24
+ } else if (targetPath.startsWith("//")) {
25
+ targetPath = targetPath.replace(/^\/+/, "/");
21
26
  }
22
27
  target = joinURL(target.slice(0, -3), targetPath);
23
28
  } else if (event.url.search) {
@@ -35,7 +40,12 @@ export const proxy = ((m) => function proxyRouteRule(event) {
35
40
  let targetPath = event.url.pathname + event.url.search;
36
41
  const strpBase = m.options._proxyStripBase;
37
42
  if (strpBase) {
43
+ if (!isPathInScope(event.url.pathname, strpBase)) {
44
+ throw new HTTPError({ status: 400 });
45
+ }
38
46
  targetPath = withoutBase(targetPath, strpBase);
47
+ } else if (targetPath.startsWith("//")) {
48
+ targetPath = targetPath.replace(/^\/+/, "/");
39
49
  }
40
50
  target = joinURL(target.slice(0, -3), targetPath);
41
51
  } else if (event.url.search) {
@@ -72,3 +82,21 @@ export const basicAuth = /* @__PURE__ */ Object.assign(((m) => async function au
72
82
  await requireBasicAuth(event, m.options);
73
83
  return next();
74
84
  }), { order: -1 });
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+ export function isPathInScope(pathname, base) {
94
+ let canonical;
95
+ try {
96
+ const pre = pathname.replace(/%2f/gi, "/").replace(/%5c/gi, "\\");
97
+ canonical = new URL(pre, "http://_").pathname;
98
+ } catch {
99
+ return false;
100
+ }
101
+ return !base || canonical === base || canonical.startsWith(base + "/");
102
+ }
@@ -11,6 +11,7 @@ export default defineHandler((event) => {
11
11
  const scalarConfig = {
12
12
  ..._config,
13
13
  url: openAPIEndpoint,
14
+ // @ts-expect-error
14
15
  spec: {
15
16
  url: openAPIEndpoint,
16
17
  ..._config?.spec