wrangler 0.0.0-e95bea2 → 0.0.0-e95e5a0

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 (93) hide show
  1. package/README.md +25 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4886 -0
  4. package/package.json +33 -25
  5. package/pages/functions/buildPlugin.ts +92 -0
  6. package/pages/functions/buildWorker.ts +80 -4
  7. package/pages/functions/filepath-routing.test.ts +136 -25
  8. package/pages/functions/filepath-routing.ts +14 -3
  9. package/pages/functions/routes.ts +6 -1
  10. package/pages/functions/template-plugin.ts +146 -0
  11. package/pages/functions/template-worker.ts +21 -8
  12. package/src/__tests__/config-cache.test.ts +36 -0
  13. package/src/__tests__/configuration.test.ts +1179 -271
  14. package/src/__tests__/dev.test.tsx +382 -25
  15. package/src/__tests__/guess-worker-format.test.ts +28 -1
  16. package/src/__tests__/helpers/mock-account-id.ts +4 -0
  17. package/src/__tests__/helpers/mock-cfetch.ts +5 -1
  18. package/src/__tests__/helpers/mock-console.ts +20 -3
  19. package/src/__tests__/helpers/mock-dialogs.ts +26 -1
  20. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  21. package/src/__tests__/helpers/mock-istty.ts +27 -0
  22. package/src/__tests__/helpers/mock-kv.ts +9 -16
  23. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  24. package/src/__tests__/helpers/run-in-tmp.ts +10 -0
  25. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  26. package/src/__tests__/https-options.test.ts +5 -2
  27. package/src/__tests__/index.test.ts +27 -769
  28. package/src/__tests__/init.test.ts +1516 -0
  29. package/src/__tests__/jest.setup.ts +4 -4
  30. package/src/__tests__/kv.test.ts +172 -126
  31. package/src/__tests__/logger.test.ts +115 -0
  32. package/src/__tests__/pages.test.ts +311 -26
  33. package/src/__tests__/publish.test.ts +2946 -524
  34. package/src/__tests__/r2.test.ts +26 -10
  35. package/src/__tests__/route.test.ts +4 -4
  36. package/src/__tests__/secret.test.ts +55 -34
  37. package/src/__tests__/tail.test.ts +146 -20
  38. package/src/__tests__/user.test.ts +131 -0
  39. package/src/__tests__/whoami.test.tsx +26 -7
  40. package/src/bundle.ts +30 -3
  41. package/src/cfetch/internal.ts +9 -7
  42. package/src/cli.ts +5 -11
  43. package/src/config/README.md +7 -7
  44. package/src/config/config.ts +33 -78
  45. package/src/config/environment.ts +93 -4
  46. package/src/config/index.ts +15 -4
  47. package/src/config/validation-helpers.ts +202 -15
  48. package/src/config/validation.ts +530 -133
  49. package/src/config-cache.ts +45 -0
  50. package/src/create-worker-preview.ts +22 -13
  51. package/src/create-worker-upload-form.ts +32 -9
  52. package/src/dev/dev-vars.ts +36 -0
  53. package/src/dev/dev.tsx +136 -75
  54. package/src/dev/local.tsx +196 -125
  55. package/src/dev/remote.tsx +25 -13
  56. package/src/dev/use-esbuild.ts +23 -5
  57. package/src/dialogs.tsx +16 -6
  58. package/src/entry.ts +119 -11
  59. package/src/environment-variables.ts +3 -1
  60. package/src/errors.ts +11 -0
  61. package/src/https-options.ts +3 -2
  62. package/src/index.tsx +739 -400
  63. package/src/inspect.ts +17 -11
  64. package/src/intl-polyfill.d.ts +139 -0
  65. package/src/{kv/index.tsx → kv.ts} +8 -28
  66. package/src/logger.ts +66 -0
  67. package/src/miniflare-cli/README.md +30 -0
  68. package/src/miniflare-cli/enum-keys.ts +17 -0
  69. package/src/miniflare-cli/index.ts +41 -0
  70. package/src/miniflare-cli/request-context.ts +40 -0
  71. package/src/module-collection.ts +63 -58
  72. package/src/open-in-browser.ts +5 -4
  73. package/src/package-manager.ts +9 -8
  74. package/src/pages.tsx +883 -62
  75. package/src/parse.ts +1 -1
  76. package/src/proxy.ts +32 -21
  77. package/src/publish.ts +276 -161
  78. package/src/sites.tsx +88 -33
  79. package/src/tail/index.ts +2 -2
  80. package/src/tail/printing.ts +13 -8
  81. package/src/update-check.ts +19 -0
  82. package/src/user.tsx +231 -103
  83. package/src/whoami.tsx +2 -1
  84. package/src/worker.ts +15 -6
  85. package/templates/gitignore +171 -0
  86. package/templates/static-asset-facade.js +1 -1
  87. package/wrangler-dist/cli.js +22087 -49340
  88. package/src/__tests__/dev2.test.tsx +0 -152
  89. package/src/__tests__/helpers/mock-user.ts +0 -27
  90. package/src/__tests__/logout.test.ts +0 -50
  91. package/src/__tests__/sentry.test.ts +0 -146
  92. package/src/reporting.ts +0 -131
  93. package/wrangler-dist/cli.js.map +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrangler",
3
- "version": "0.0.0-e95bea2",
3
+ "version": "0.0.0-e95e5a0",
4
4
  "author": "wrangler@cloudflare.com",
5
5
  "description": "Command-line interface for all things Cloudflare Workers",
6
6
  "bin": {
@@ -36,10 +36,14 @@
36
36
  "cli"
37
37
  ],
38
38
  "dependencies": {
39
- "esbuild": "0.14.23",
40
- "miniflare": "2.3.0",
39
+ "@esbuild-plugins/node-globals-polyfill": "^0.1.1",
40
+ "@esbuild-plugins/node-modules-polyfill": "^0.1.4",
41
+ "blake3-wasm": "^2.1.5",
42
+ "esbuild": "0.14.34",
43
+ "miniflare": "2.4.0",
44
+ "nanoid": "^3.3.3",
41
45
  "path-to-regexp": "^6.2.0",
42
- "selfsigned": "^2.0.0",
46
+ "selfsigned": "^2.0.1",
43
47
  "semiver": "^1.1.0",
44
48
  "xxhash-wasm": "^1.0.1"
45
49
  },
@@ -47,68 +51,72 @@
47
51
  "fsevents": "~2.3.2"
48
52
  },
49
53
  "devDependencies": {
50
- "@iarna/toml": "^2.2.5",
51
- "@sentry/cli": "^1.71.0",
52
- "@sentry/integrations": "^6.17.6",
53
- "@sentry/node": "^6.17.6",
54
+ "@iarna/toml": "^3.0.0",
54
55
  "@types/command-exists": "^1.2.0",
55
56
  "@types/glob-to-regexp": "0.4.1",
56
57
  "@types/mime": "^2.0.3",
57
58
  "@types/prompts": "^2.0.14",
58
59
  "@types/react": "^17.0.37",
59
- "@types/react-test-renderer": "^17.0.1",
60
60
  "@types/serve-static": "^1.13.10",
61
61
  "@types/signal-exit": "^3.0.1",
62
- "@types/ws": "^8.2.1",
63
- "@types/yargs": "^17.0.7",
64
- "chokidar": "^3.5.2",
62
+ "@types/supports-color": "^8.1.1",
63
+ "@types/ws": "^8.5.3",
64
+ "@types/yargs": "^17.0.10",
65
+ "chokidar": "^3.5.3",
65
66
  "clipboardy": "^3.0.0",
66
67
  "cmd-shim": "^4.1.0",
67
68
  "command-exists": "^1.2.9",
68
69
  "devtools-protocol": "^0.0.955664",
69
- "execa": "^6.0.0",
70
+ "dotenv": "^16.0.0",
71
+ "execa": "^6.1.0",
70
72
  "faye-websocket": "^0.11.4",
71
- "finalhandler": "^1.1.2",
72
- "find-up": "^6.2.0",
73
+ "finalhandler": "^1.2.0",
74
+ "find-up": "^6.3.0",
73
75
  "get-port": "^6.1.2",
74
76
  "glob-to-regexp": "0.4.1",
75
77
  "ignore": "^5.2.0",
76
78
  "ink": "^3.2.0",
77
79
  "ink-select-input": "^4.2.1",
80
+ "ink-spinner": "^4.0.3",
78
81
  "ink-table": "^3.0.0",
79
82
  "ink-testing-library": "^2.1.0",
80
- "ink-text-input": "^4.0.2",
83
+ "ink-text-input": "^4.0.3",
81
84
  "jest-fetch-mock": "^3.0.3",
82
85
  "jest-websocket-mock": "^2.3.0",
83
86
  "mime": "^3.0.0",
84
87
  "open": "^8.4.0",
88
+ "pretty-bytes": "^6.0.0",
85
89
  "prompts": "^2.4.2",
86
90
  "react": "^17.0.2",
87
- "react-test-renderer": "^17.0.2",
88
91
  "react-error-boundary": "^3.1.4",
89
- "serve-static": "^1.14.1",
90
- "signal-exit": "^3.0.6",
92
+ "serve-static": "^1.15.0",
93
+ "signal-exit": "^3.0.7",
94
+ "supports-color": "^9.2.2",
95
+ "timeago.js": "^4.0.2",
91
96
  "tmp-promise": "^3.0.3",
92
- "undici": "4.13.0",
93
- "ws": "^8.3.0",
94
- "yargs": "^17.3.0"
97
+ "ts-dedent": "^2.2.0",
98
+ "undici": "^4.15.1",
99
+ "update-check": "^1.5.4",
100
+ "ws": "^8.5.0",
101
+ "yargs": "^17.4.1"
95
102
  },
96
103
  "files": [
97
104
  "src",
98
105
  "bin",
99
106
  "pages",
100
107
  "miniflare-config-stubs",
108
+ "miniflare-dist",
101
109
  "wrangler-dist",
102
110
  "templates",
103
111
  "vendor",
104
112
  "import_meta_url.js"
105
113
  ],
106
114
  "scripts": {
107
- "clean": "rm -rf wrangler-dist",
115
+ "clean": "rm -rf wrangler-dist miniflare-dist",
108
116
  "check:type": "tsc",
109
117
  "bundle": "node -r esbuild-register scripts/bundle.ts",
110
118
  "build": "npm run clean && npm run bundle",
111
- "prepublishOnly": "npm run build",
119
+ "prepublishOnly": "SOURCEMAPS=false npm run build",
112
120
  "start": "npm run bundle && NODE_OPTIONS=--enable-source-maps ./bin/wrangler.js",
113
121
  "test": "jest --silent=false --verbose=true",
114
122
  "test-watch": "npm run test -- --runInBand --testTimeout=50000 --watch"
@@ -121,7 +129,7 @@
121
129
  "testTimeout": 30000,
122
130
  "testRegex": ".*.(test|spec)\\.[jt]sx?$",
123
131
  "transformIgnorePatterns": [
124
- "node_modules/(?!find-up|locate-path|p-locate|p-limit|yocto-queue|path-exists|execa|strip-final-newline|npm-run-path|path-key|onetime|mimic-fn|human-signals|is-stream|get-port)"
132
+ "node_modules/(?!find-up|locate-path|p-locate|p-limit|yocto-queue|path-exists|execa|strip-final-newline|npm-run-path|path-key|onetime|mimic-fn|human-signals|is-stream|get-port|supports-color|pretty-bytes)"
125
133
  ],
126
134
  "moduleNameMapper": {
127
135
  "clipboardy": "<rootDir>/src/__tests__/helpers/clipboardy-mock.js",
@@ -0,0 +1,92 @@
1
+ import { access, lstat } from "node:fs/promises";
2
+ import { dirname, relative, resolve } from "node:path";
3
+ import { build } from "esbuild";
4
+
5
+ type Options = {
6
+ routesModule: string;
7
+ outfile: string;
8
+ minify?: boolean;
9
+ sourcemap?: boolean;
10
+ watch?: boolean;
11
+ onEnd?: () => void;
12
+ };
13
+
14
+ export function buildPlugin({
15
+ routesModule,
16
+ outfile = "bundle.js",
17
+ minify = false,
18
+ sourcemap = false,
19
+ watch = false,
20
+ onEnd = () => {},
21
+ }: Options) {
22
+ return build({
23
+ entryPoints: [resolve(__dirname, "../pages/functions/template-plugin.ts")],
24
+ inject: [routesModule],
25
+ bundle: true,
26
+ format: "esm",
27
+ target: "esnext",
28
+ outfile,
29
+ minify,
30
+ sourcemap,
31
+ watch,
32
+ allowOverwrite: true,
33
+ plugins: [
34
+ {
35
+ name: "wrangler notifier and monitor",
36
+ setup(pluginBuild) {
37
+ pluginBuild.onEnd((result) => {
38
+ if (result.errors.length > 0) {
39
+ console.error(
40
+ `${result.errors.length} error(s) and ${result.warnings.length} warning(s) when compiling Worker.`
41
+ );
42
+ } else if (result.warnings.length > 0) {
43
+ console.warn(
44
+ `${result.warnings.length} warning(s) when compiling Worker.`
45
+ );
46
+ onEnd();
47
+ } else {
48
+ console.log("Compiled Worker successfully.");
49
+ onEnd();
50
+ }
51
+ });
52
+ },
53
+ },
54
+ {
55
+ name: "Assets",
56
+ setup(pluginBuild) {
57
+ if (pluginBuild.initialOptions.outfile) {
58
+ const outdir = dirname(pluginBuild.initialOptions.outfile);
59
+
60
+ pluginBuild.onResolve({ filter: /^assets:/ }, async (args) => {
61
+ const directory = resolve(
62
+ args.resolveDir,
63
+ args.path.slice("assets:".length)
64
+ );
65
+
66
+ const exists = await access(directory)
67
+ .then(() => true)
68
+ .catch(() => false);
69
+
70
+ const isDirectory =
71
+ exists && (await lstat(directory)).isDirectory();
72
+
73
+ if (!isDirectory) {
74
+ return {
75
+ errors: [
76
+ {
77
+ text: `'${directory}' does not exist or is not a directory.`,
78
+ },
79
+ ],
80
+ };
81
+ }
82
+
83
+ const path = `assets:./${relative(outdir, directory)}`;
84
+
85
+ return { path, external: true, namespace: "assets" };
86
+ });
87
+ }
88
+ },
89
+ },
90
+ ],
91
+ });
92
+ }
@@ -1,5 +1,7 @@
1
- import path from "node:path";
1
+ import { access, cp, lstat, rm } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
2
3
  import { build } from "esbuild";
4
+ import { nanoid } from "nanoid";
3
5
 
4
6
  type Options = {
5
7
  routesModule: string;
@@ -9,6 +11,7 @@ type Options = {
9
11
  fallbackService?: string;
10
12
  watch?: boolean;
11
13
  onEnd?: () => void;
14
+ buildOutputDirectory?: string;
12
15
  };
13
16
 
14
17
  export function buildWorker({
@@ -19,11 +22,10 @@ export function buildWorker({
19
22
  fallbackService = "ASSETS",
20
23
  watch = false,
21
24
  onEnd = () => {},
25
+ buildOutputDirectory,
22
26
  }: Options) {
23
27
  return build({
24
- entryPoints: [
25
- path.resolve(__dirname, "../pages/functions/template-worker.ts"),
26
- ],
28
+ entryPoints: [resolve(__dirname, "../pages/functions/template-worker.ts")],
27
29
  inject: [routesModule],
28
30
  bundle: true,
29
31
  format: "esm",
@@ -57,6 +59,80 @@ export function buildWorker({
57
59
  });
58
60
  },
59
61
  },
62
+ {
63
+ name: "Assets",
64
+ setup(pluginBuild) {
65
+ const identifiers = new Map<string, string>();
66
+
67
+ pluginBuild.onResolve({ filter: /^assets:/ }, async (args) => {
68
+ const directory = resolve(
69
+ args.resolveDir,
70
+ args.path.slice("assets:".length)
71
+ );
72
+
73
+ const exists = await access(directory)
74
+ .then(() => true)
75
+ .catch(() => false);
76
+
77
+ const isDirectory =
78
+ exists && (await lstat(directory)).isDirectory();
79
+
80
+ if (!isDirectory) {
81
+ return {
82
+ errors: [
83
+ {
84
+ text: `'${directory}' does not exist or is not a directory.`,
85
+ },
86
+ ],
87
+ };
88
+ }
89
+
90
+ // TODO: Consider hashing the contents rather than using a unique identifier every time?
91
+ identifiers.set(directory, nanoid());
92
+ if (!buildOutputDirectory) {
93
+ console.warn(
94
+ "You're attempting to import static assets as part of your Pages Functions, but have not specified a directory in which to put them. You must use 'wrangler pages dev <directory>' rather than 'wrangler pages dev -- <command>' to import static assets in Functions."
95
+ );
96
+ }
97
+ return { path: directory, namespace: "assets" };
98
+ });
99
+
100
+ pluginBuild.onLoad(
101
+ { filter: /.*/, namespace: "assets" },
102
+ async (args) => {
103
+ const identifier = identifiers.get(args.path);
104
+
105
+ if (buildOutputDirectory) {
106
+ const staticAssetsOutputDirectory = join(
107
+ buildOutputDirectory,
108
+ "cdn-cgi",
109
+ "pages-plugins",
110
+ identifier as string
111
+ );
112
+ await rm(staticAssetsOutputDirectory, {
113
+ force: true,
114
+ recursive: true,
115
+ });
116
+ await cp(args.path, staticAssetsOutputDirectory, {
117
+ force: true,
118
+ recursive: true,
119
+ });
120
+
121
+ return {
122
+ // TODO: Watch args.path for changes and re-copy when updated
123
+ contents: `export const onRequest = ({ request, env, functionPath }) => {
124
+ const url = new URL(request.url)
125
+ const relativePathname = url.pathname.split(functionPath)[1] || "/";
126
+ url.pathname = '/cdn-cgi/pages-plugins/${identifier}' + relativePathname
127
+ request = new Request(url.toString(), request)
128
+ return env.ASSETS.fetch(request)
129
+ }`,
130
+ };
131
+ }
132
+ }
133
+ );
134
+ },
135
+ },
60
136
  ],
61
137
  });
62
138
  }
@@ -1,4 +1,4 @@
1
- import { writeFileSync } from "fs";
1
+ import { writeFileSync, mkdirSync } from "fs";
2
2
  import { runInTempDir } from "../../src/__tests__/helpers/run-in-tmp";
3
3
  import { toUrlPath } from "../../src/paths";
4
4
  import { compareRoutes, generateConfigFromFileTree } from "./filepath-routing";
@@ -83,34 +83,144 @@ describe("filepath-routing", () => {
83
83
  `
84
84
  );
85
85
 
86
+ mkdirSync("todos");
87
+
88
+ writeFileSync(
89
+ "todos/[id].ts",
90
+ `
91
+ export function onRequestPost() {}
92
+ export function onRequestDelete() {}
93
+ `
94
+ );
95
+
96
+ mkdirSync("authors");
97
+ mkdirSync("authors/[authorId]");
98
+ mkdirSync("authors/[authorId]/todos");
99
+
100
+ writeFileSync(
101
+ "authors/[authorId]/todos/[todoId].ts",
102
+ `
103
+ export function onRequestPost() {}
104
+ `
105
+ );
106
+
107
+ mkdirSync("books");
108
+
109
+ writeFileSync(
110
+ "books/[[title]].ts",
111
+ `
112
+ export function onRequestPost() {}
113
+ `
114
+ );
115
+
116
+ mkdirSync("cats");
117
+ mkdirSync("cats/[[breed]]");
118
+
119
+ writeFileSync(
120
+ "cats/[[breed]]/blah.ts",
121
+ `
122
+ export function onRequestPost() {}
123
+ `
124
+ );
125
+
126
+ // This won't actually work at runtime.
127
+ writeFileSync(
128
+ "cats/[[breed]]/[[name]].ts",
129
+ `
130
+ export function onRequestPost() {}
131
+ `
132
+ );
133
+
86
134
  const entries = await generateConfigFromFileTree({
87
135
  baseDir: ".",
88
136
  baseURL: "/base" as UrlPath,
89
137
  });
90
- expect(entries).toEqual({
91
- routes: [
92
- {
93
- method: "DELETE",
94
- module: ["bar.ts:onRequestDelete"],
95
- routePath: "/base/bar",
96
- },
97
- {
98
- method: "PUT",
99
- module: ["bar.ts:onRequestPut"],
100
- routePath: "/base/bar",
101
- },
102
- {
103
- method: "GET",
104
- module: ["foo.ts:onRequestGet"],
105
- routePath: "/base/foo",
106
- },
107
- {
108
- method: "POST",
109
- module: ["foo.ts:onRequestPost"],
110
- routePath: "/base/foo",
111
- },
112
- ],
113
- });
138
+ expect(entries).toMatchInlineSnapshot(`
139
+ Object {
140
+ "routes": Array [
141
+ Object {
142
+ "method": "POST",
143
+ "module": Array [
144
+ "authors/[authorId]/todos/[todoId].ts:onRequestPost",
145
+ ],
146
+ "mountPath": "/base/authors/:authorId/todos",
147
+ "routePath": "/base/authors/:authorId/todos/:todoId",
148
+ },
149
+ Object {
150
+ "method": "POST",
151
+ "module": Array [
152
+ "cats/[[breed]]/blah.ts:onRequestPost",
153
+ ],
154
+ "mountPath": "/base/cats/:breed*",
155
+ "routePath": "/base/cats/:breed*/blah",
156
+ },
157
+ Object {
158
+ "method": "POST",
159
+ "module": Array [
160
+ "cats/[[breed]]/[[name]].ts:onRequestPost",
161
+ ],
162
+ "mountPath": "/base/cats/:breed*",
163
+ "routePath": "/base/cats/:breed*/:name*",
164
+ },
165
+ Object {
166
+ "method": "DELETE",
167
+ "module": Array [
168
+ "todos/[id].ts:onRequestDelete",
169
+ ],
170
+ "mountPath": "/base/todos",
171
+ "routePath": "/base/todos/:id",
172
+ },
173
+ Object {
174
+ "method": "POST",
175
+ "module": Array [
176
+ "todos/[id].ts:onRequestPost",
177
+ ],
178
+ "mountPath": "/base/todos",
179
+ "routePath": "/base/todos/:id",
180
+ },
181
+ Object {
182
+ "method": "POST",
183
+ "module": Array [
184
+ "books/[[title]].ts:onRequestPost",
185
+ ],
186
+ "mountPath": "/base/books",
187
+ "routePath": "/base/books/:title*",
188
+ },
189
+ Object {
190
+ "method": "DELETE",
191
+ "module": Array [
192
+ "bar.ts:onRequestDelete",
193
+ ],
194
+ "mountPath": "/base/",
195
+ "routePath": "/base/bar",
196
+ },
197
+ Object {
198
+ "method": "PUT",
199
+ "module": Array [
200
+ "bar.ts:onRequestPut",
201
+ ],
202
+ "mountPath": "/base/",
203
+ "routePath": "/base/bar",
204
+ },
205
+ Object {
206
+ "method": "GET",
207
+ "module": Array [
208
+ "foo.ts:onRequestGet",
209
+ ],
210
+ "mountPath": "/base/",
211
+ "routePath": "/base/foo",
212
+ },
213
+ Object {
214
+ "method": "POST",
215
+ "module": Array [
216
+ "foo.ts:onRequestPost",
217
+ ],
218
+ "mountPath": "/base/",
219
+ "routePath": "/base/foo",
220
+ },
221
+ ],
222
+ }
223
+ `);
114
224
  });
115
225
  });
116
226
  });
@@ -118,6 +228,7 @@ describe("filepath-routing", () => {
118
228
  function routeConfig(routePath: string, method?: string): RouteConfig {
119
229
  return {
120
230
  routePath: toUrlPath(routePath),
231
+ mountPath: toUrlPath("/"),
121
232
  method: method as HTTPMethod,
122
233
  };
123
234
  }
@@ -53,6 +53,7 @@ export async function generateConfigFromFileTree({
53
53
  let routePath = path
54
54
  .relative(baseDir, filepath)
55
55
  .slice(0, -ext.length);
56
+ let mountPath = path.dirname(routePath);
56
57
 
57
58
  if (isIndexFile || isMiddlewareFile) {
58
59
  routePath = path.dirname(routePath);
@@ -61,17 +62,27 @@ export async function generateConfigFromFileTree({
61
62
  if (routePath === ".") {
62
63
  routePath = "";
63
64
  }
65
+ if (mountPath === ".") {
66
+ mountPath = "";
67
+ }
64
68
 
65
69
  routePath = `${baseURL}/${routePath}`;
70
+ mountPath = `${baseURL}/${mountPath}`;
71
+
72
+ routePath = routePath.replace(/\[\[([^\]]+)\]\]/g, ":$1*"); // transform [[id]] => :id*
73
+ routePath = routePath.replaceAll(/\[([^\]]+)\]/g, ":$1"); // transform [id] => :id
74
+ mountPath = mountPath.replace(/\[\[([^\]]+)\]\]/g, ":$1*"); // transform [[id]] => :id*
75
+ mountPath = mountPath.replaceAll(/\[([^\]]+)\]/g, ":$1"); // transform [id] => :id
66
76
 
67
- routePath = routePath.replace(/\[\[(.+)]]/g, ":$1*"); // transform [[id]] => :id*
68
- routePath = routePath.replace(/\[(.+)]/g, ":$1"); // transform [id] => :id
77
+ // These are used as module specifiers so UrlPaths are okay to use even on Windows
78
+ const modulePath = toUrlPath(path.relative(baseDir, filepath));
69
79
 
70
80
  const routeEntry: RouteConfig = {
71
81
  routePath: toUrlPath(routePath),
82
+ mountPath: toUrlPath(mountPath),
72
83
  method: method.toUpperCase() as HTTPMethod,
73
84
  [isMiddlewareFile ? "middleware" : "module"]: [
74
- `${path.relative(baseDir, filepath)}:${exportName}`,
85
+ `${modulePath}:${exportName}`,
75
86
  ],
76
87
  };
77
88
 
@@ -21,6 +21,7 @@ export function isHTTPMethod(
21
21
 
22
22
  export type RoutesCollection = Array<{
23
23
  routePath: UrlPath;
24
+ mountPath: UrlPath;
24
25
  method?: HTTPMethod;
25
26
  modules: string[];
26
27
  middlewares: string[];
@@ -33,6 +34,7 @@ export type Config = {
33
34
 
34
35
  export type RouteConfig = {
35
36
  routePath: UrlPath;
37
+ mountPath: UrlPath;
36
38
  method?: HTTPMethod;
37
39
  middleware?: string | string[];
38
40
  module?: string | string[];
@@ -113,9 +115,11 @@ export function parseConfig(config: Config, baseDir: string) {
113
115
  });
114
116
  }
115
117
 
116
- for (const { routePath, method, ...props } of config.routes ?? []) {
118
+ for (const { routePath, mountPath, method, ...props } of config.routes ??
119
+ []) {
117
120
  routes.push({
118
121
  routePath,
122
+ mountPath,
119
123
  method,
120
124
  middlewares: parseModuleIdentifiers(props.middleware),
121
125
  modules: parseModuleIdentifiers(props.module),
@@ -141,6 +145,7 @@ export const routes = [
141
145
  .map(
142
146
  (route) => ` {
143
147
  routePath: "${route.routePath}",
148
+ mountPath: "${route.mountPath}",
144
149
  method: "${route.method}",
145
150
  middlewares: [${route.middlewares.join(", ")}],
146
151
  modules: [${route.modules.join(", ")}],