wrangler 0.0.0-e043479 → 0.0.0-e0a0509

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 (96) hide show
  1. package/README.md +27 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4886 -0
  4. package/package.json +38 -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 +1294 -355
  14. package/src/__tests__/dev.test.tsx +592 -12
  15. package/src/__tests__/guess-worker-format.test.ts +37 -18
  16. package/src/__tests__/helpers/mock-account-id.ts +4 -0
  17. package/src/__tests__/helpers/mock-cfetch.ts +8 -4
  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 +130 -0
  27. package/src/__tests__/index.test.ts +129 -753
  28. package/src/__tests__/init.test.ts +1516 -0
  29. package/src/__tests__/jest.setup.ts +13 -5
  30. package/src/__tests__/kv.test.ts +208 -174
  31. package/src/__tests__/logger.test.ts +115 -0
  32. package/src/__tests__/pages.test.ts +327 -0
  33. package/src/__tests__/parse.test.ts +4 -0
  34. package/src/__tests__/publish.test.ts +3313 -505
  35. package/src/__tests__/r2.test.ts +38 -26
  36. package/src/__tests__/route.test.ts +45 -0
  37. package/src/__tests__/secret.test.ts +55 -34
  38. package/src/__tests__/tail.test.ts +146 -20
  39. package/src/__tests__/user.test.ts +131 -0
  40. package/src/__tests__/whoami.test.tsx +26 -7
  41. package/src/bundle.ts +43 -7
  42. package/src/cfetch/internal.ts +9 -5
  43. package/src/cli.ts +5 -11
  44. package/src/config/README.md +7 -7
  45. package/src/config/config.ts +69 -105
  46. package/src/config/environment.ts +93 -4
  47. package/src/config/index.ts +15 -4
  48. package/src/config/validation-helpers.ts +228 -23
  49. package/src/config/validation.ts +559 -154
  50. package/src/config-cache.ts +45 -0
  51. package/src/{api/preview.ts → create-worker-preview.ts} +41 -13
  52. package/src/{api/form_data.ts → create-worker-upload-form.ts} +33 -10
  53. package/src/dev/dev-vars.ts +36 -0
  54. package/src/dev/dev.tsx +418 -0
  55. package/src/dev/local.tsx +312 -0
  56. package/src/dev/remote.tsx +213 -0
  57. package/src/dev/use-esbuild.ts +118 -0
  58. package/src/dialogs.tsx +16 -6
  59. package/src/entry.ts +222 -12
  60. package/src/environment-variables.ts +3 -1
  61. package/src/errors.ts +11 -0
  62. package/src/https-options.ts +123 -0
  63. package/src/index.tsx +878 -482
  64. package/src/inspect.ts +25 -11
  65. package/src/intl-polyfill.d.ts +139 -0
  66. package/src/{kv.tsx → kv.ts} +7 -27
  67. package/src/logger.ts +66 -0
  68. package/src/miniflare-cli/README.md +30 -0
  69. package/src/miniflare-cli/enum-keys.ts +17 -0
  70. package/src/miniflare-cli/index.ts +41 -0
  71. package/src/miniflare-cli/request-context.ts +40 -0
  72. package/src/module-collection.ts +94 -6
  73. package/src/open-in-browser.ts +18 -5
  74. package/src/package-manager.ts +9 -8
  75. package/src/pages.tsx +892 -62
  76. package/src/parse.ts +4 -2
  77. package/src/proxy.ts +128 -55
  78. package/src/publish.ts +305 -199
  79. package/src/selfsigned.d.ts +29 -0
  80. package/src/sites.tsx +88 -33
  81. package/src/tail/index.ts +2 -2
  82. package/src/tail/printing.ts +13 -8
  83. package/src/update-check.ts +19 -0
  84. package/src/user.tsx +244 -104
  85. package/src/whoami.tsx +2 -1
  86. package/src/{api/worker.ts → worker.ts} +15 -29
  87. package/templates/gitignore +171 -0
  88. package/templates/static-asset-facade.js +1 -1
  89. package/wrangler-dist/cli.js +23568 -50489
  90. package/src/__tests__/helpers/mock-user.ts +0 -27
  91. package/src/__tests__/logout.test.ts +0 -50
  92. package/src/__tests__/sentry.test.ts +0 -146
  93. package/src/dev.tsx +0 -960
  94. package/src/guess-worker-format.ts +0 -68
  95. package/src/reporting.ts +0 -115
  96. 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-e043479",
3
+ "version": "0.0.0-e0a0509",
4
4
  "author": "wrangler@cloudflare.com",
5
5
  "description": "Command-line interface for all things Cloudflare Workers",
6
6
  "bin": {
@@ -9,9 +9,9 @@
9
9
  },
10
10
  "license": "MIT OR Apache-2.0",
11
11
  "bugs": {
12
- "url": "https://github.com/cloudflare/wrangler/issues"
12
+ "url": "https://github.com/cloudflare/wrangler2/issues"
13
13
  },
14
- "homepage": "https://github.com/cloudflare/wrangler#readme",
14
+ "homepage": "https://github.com/cloudflare/wrangler2#readme",
15
15
  "keywords": [
16
16
  "wrangler",
17
17
  "cloudflare",
@@ -36,9 +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",
46
+ "selfsigned": "^2.0.1",
42
47
  "semiver": "^1.1.0",
43
48
  "xxhash-wasm": "^1.0.1"
44
49
  },
@@ -46,10 +51,7 @@
46
51
  "fsevents": "~2.3.2"
47
52
  },
48
53
  "devDependencies": {
49
- "@iarna/toml": "^2.2.5",
50
- "@sentry/cli": "^1.71.0",
51
- "@sentry/integrations": "^6.17.6",
52
- "@sentry/node": "^6.17.6",
54
+ "@iarna/toml": "^3.0.0",
53
55
  "@types/command-exists": "^1.2.0",
54
56
  "@types/glob-to-regexp": "0.4.1",
55
57
  "@types/mime": "^2.0.3",
@@ -57,55 +59,65 @@
57
59
  "@types/react": "^17.0.37",
58
60
  "@types/serve-static": "^1.13.10",
59
61
  "@types/signal-exit": "^3.0.1",
60
- "@types/ws": "^8.2.1",
61
- "@types/yargs": "^17.0.7",
62
- "chokidar": "^3.5.2",
62
+ "@types/supports-color": "^8.1.1",
63
+ "@types/ws": "^8.5.3",
64
+ "@types/yargs": "^17.0.10",
65
+ "@webcontainer/env": "^1.0.1",
66
+ "chokidar": "^3.5.3",
63
67
  "clipboardy": "^3.0.0",
64
68
  "cmd-shim": "^4.1.0",
65
69
  "command-exists": "^1.2.9",
66
70
  "devtools-protocol": "^0.0.955664",
67
- "execa": "^6.0.0",
71
+ "dotenv": "^16.0.0",
72
+ "execa": "^6.1.0",
68
73
  "faye-websocket": "^0.11.4",
69
- "finalhandler": "^1.1.2",
70
- "find-up": "^6.2.0",
74
+ "finalhandler": "^1.2.0",
75
+ "find-up": "^6.3.0",
71
76
  "get-port": "^6.1.2",
72
77
  "glob-to-regexp": "0.4.1",
73
78
  "ignore": "^5.2.0",
74
79
  "ink": "^3.2.0",
75
80
  "ink-select-input": "^4.2.1",
81
+ "ink-spinner": "^4.0.3",
76
82
  "ink-table": "^3.0.0",
77
83
  "ink-testing-library": "^2.1.0",
78
- "ink-text-input": "^4.0.2",
84
+ "ink-text-input": "^4.0.3",
79
85
  "jest-fetch-mock": "^3.0.3",
80
86
  "jest-websocket-mock": "^2.3.0",
81
87
  "mime": "^3.0.0",
82
88
  "open": "^8.4.0",
89
+ "pretty-bytes": "^6.0.0",
83
90
  "prompts": "^2.4.2",
84
91
  "react": "^17.0.2",
85
92
  "react-error-boundary": "^3.1.4",
86
- "serve-static": "^1.14.1",
87
- "signal-exit": "^3.0.6",
93
+ "serve-static": "^1.15.0",
94
+ "signal-exit": "^3.0.7",
95
+ "supports-color": "^9.2.2",
96
+ "timeago.js": "^4.0.2",
88
97
  "tmp-promise": "^3.0.3",
89
- "undici": "4.13.0",
90
- "ws": "^8.3.0",
91
- "yargs": "^17.3.0"
98
+ "ts-dedent": "^2.2.0",
99
+ "undici": "^4.15.1",
100
+ "update-check": "^1.5.4",
101
+ "ws": "^8.5.0",
102
+ "yargs": "^17.4.1"
92
103
  },
93
104
  "files": [
94
105
  "src",
95
106
  "bin",
96
107
  "pages",
97
108
  "miniflare-config-stubs",
109
+ "miniflare-dist",
98
110
  "wrangler-dist",
99
111
  "templates",
100
112
  "vendor",
101
113
  "import_meta_url.js"
102
114
  ],
103
115
  "scripts": {
104
- "clean": "rm -rf wrangler-dist",
116
+ "clean": "rm -rf wrangler-dist miniflare-dist",
105
117
  "check:type": "tsc",
106
118
  "bundle": "node -r esbuild-register scripts/bundle.ts",
107
119
  "build": "npm run clean && npm run bundle",
108
- "prepublishOnly": "npm run build",
120
+ "prepublishOnly": "SOURCEMAPS=false npm run build",
109
121
  "start": "npm run bundle && NODE_OPTIONS=--enable-source-maps ./bin/wrangler.js",
110
122
  "test": "jest --silent=false --verbose=true",
111
123
  "test-watch": "npm run test -- --runInBand --testTimeout=50000 --watch"
@@ -118,10 +130,11 @@
118
130
  "testTimeout": 30000,
119
131
  "testRegex": ".*.(test|spec)\\.[jt]sx?$",
120
132
  "transformIgnorePatterns": [
121
- "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)"
133
+ "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)"
122
134
  ],
123
135
  "moduleNameMapper": {
124
- "clipboardy": "<rootDir>/src/__tests__/helpers/clipboardy-mock.js"
136
+ "clipboardy": "<rootDir>/src/__tests__/helpers/clipboardy-mock.js",
137
+ "miniflare/cli": "<rootDir>/../../node_modules/miniflare/dist/src/cli.js"
125
138
  },
126
139
  "transform": {
127
140
  "^.+\\.c?(t|j)sx?$": [
@@ -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(", ")}],