diff --git a/.changeset/afraid-oranges-happen.md b/.changeset/afraid-oranges-happen.md new file mode 100644 index 0000000000000..7544f665d5473 --- /dev/null +++ b/.changeset/afraid-oranges-happen.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +fix: `site.entry-point` should not be a hard deprecation + +To make migration of v1 projects easier, Sites projects should still work, including the `entry-point` field (which currently errors out). This enables `site.entry-point` as a valid entry point, with a deprecation warning. diff --git a/examples/legacy-site-app/public/404.html b/examples/legacy-site-app/public/404.html new file mode 100644 index 0000000000000..5642852452d0b --- /dev/null +++ b/examples/legacy-site-app/public/404.html @@ -0,0 +1,40 @@ + + + + + + + + + +
+

404 Not Found

+

Oh dang! We couldn't find that page.

+ a sad crab is unable to unable to lasso a paper airplane. 404 not found. +
+ + diff --git a/examples/legacy-site-app/public/favicon.ico b/examples/legacy-site-app/public/favicon.ico new file mode 100644 index 0000000000000..cc6c23bd37bb4 Binary files /dev/null and b/examples/legacy-site-app/public/favicon.ico differ diff --git a/examples/legacy-site-app/public/img/200-wrangler-ferris.gif b/examples/legacy-site-app/public/img/200-wrangler-ferris.gif new file mode 100644 index 0000000000000..8853751fac7b3 Binary files /dev/null and b/examples/legacy-site-app/public/img/200-wrangler-ferris.gif differ diff --git a/examples/legacy-site-app/public/img/404-wrangler-ferris.gif b/examples/legacy-site-app/public/img/404-wrangler-ferris.gif new file mode 100644 index 0000000000000..0ac1479fcf6f4 Binary files /dev/null and b/examples/legacy-site-app/public/img/404-wrangler-ferris.gif differ diff --git a/examples/legacy-site-app/public/index.html b/examples/legacy-site-app/public/index.html new file mode 100644 index 0000000000000..d07cf620748f2 --- /dev/null +++ b/examples/legacy-site-app/public/index.html @@ -0,0 +1,40 @@ + + + + + + + + + +
+

200 Success

+

Hello World! Welcome to your Workers Site.

+ a happy crab is wearing a cowboy hat and holding a lasso. 200 success. +
+ + diff --git a/examples/legacy-site-app/workers-site/.gitignore b/examples/legacy-site-app/workers-site/.gitignore new file mode 100644 index 0000000000000..7915249fd6155 --- /dev/null +++ b/examples/legacy-site-app/workers-site/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +worker diff --git a/examples/legacy-site-app/workers-site/index.js b/examples/legacy-site-app/workers-site/index.js new file mode 100644 index 0000000000000..806f91a5a189a --- /dev/null +++ b/examples/legacy-site-app/workers-site/index.js @@ -0,0 +1,81 @@ +import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler' + +/** + * The DEBUG flag will do two things that help during development: + * 1. we will skip caching on the edge, which makes it easier to + * debug. + * 2. we will return an error message on exception in your Response rather + * than the default 404.html page. + */ +const DEBUG = false + +addEventListener('fetch', event => { + event.respondWith(handleEvent(event)) +}) + +async function handleEvent(event) { + let options = {} + + /** + * You can add custom logic to how we fetch your assets + * by configuring the function `mapRequestToAsset` + */ + // options.mapRequestToAsset = handlePrefix(/^\/docs/) + + try { + if (DEBUG) { + // customize caching + options.cacheControl = { + bypassCache: true, + } + } + + const page = await getAssetFromKV(event, options) + + // allow headers to be altered + const response = new Response(page.body, page) + + response.headers.set('X-XSS-Protection', '1; mode=block') + response.headers.set('X-Content-Type-Options', 'nosniff') + response.headers.set('X-Frame-Options', 'DENY') + response.headers.set('Referrer-Policy', 'unsafe-url') + response.headers.set('Feature-Policy', 'none') + + return response + + } catch (e) { + // if an error is thrown try to serve the asset at 404.html + if (!DEBUG) { + try { + let notFoundResponse = await getAssetFromKV(event, { + mapRequestToAsset: req => new Request(`${new URL(req.url).origin}/404.html`, req), + }) + + return new Response(notFoundResponse.body, { ...notFoundResponse, status: 404 }) + } catch (e) {} + } + + return new Response(e.message || e.toString(), { status: 500 }) + } +} + +/** + * Here's one example of how to modify a request to + * remove a specific prefix, in this case `/docs` from + * the url. This can be useful if you are deploying to a + * route on a zone, or if you only want your static content + * to exist at a specific path. + */ +function handlePrefix(prefix) { + return request => { + // compute the default (e.g. / -> index.html) + let defaultAssetKey = mapRequestToAsset(request) + let url = new URL(defaultAssetKey.url) + + // strip the prefix from the path for lookup + url.pathname = url.pathname.replace(prefix, '/') + + // inherit all other props from the default request + return new Request(url.toString(), defaultAssetKey) + } +} diff --git a/examples/legacy-site-app/workers-site/package-lock.json b/examples/legacy-site-app/workers-site/package-lock.json new file mode 100644 index 0000000000000..8c7b88b240391 --- /dev/null +++ b/examples/legacy-site-app/workers-site/package-lock.json @@ -0,0 +1,20 @@ +{ + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@cloudflare/kv-asset-handler": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.1.2.tgz", + "integrity": "sha512-otES1gV5mEhNh82p/sJERPMMrC7UOLV2JyfKf4e3EX1TmMkZ3N8IDGAqRNsoRU8UYTO7wc83I7pH1p4ozAdgMQ==", + "requires": { + "mime": "^2.5.2" + } + }, + "mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==" + } + } +} diff --git a/examples/legacy-site-app/workers-site/package.json b/examples/legacy-site-app/workers-site/package.json new file mode 100644 index 0000000000000..2b4250e7ab3ae --- /dev/null +++ b/examples/legacy-site-app/workers-site/package.json @@ -0,0 +1,10 @@ +{ + "private": true, + "version": "1.0.0", + "description": "A template for kick starting a Cloudflare Workers project", + "main": "index.js", + "license": "MIT", + "dependencies": { + "@cloudflare/kv-asset-handler": "~0.1.2" + } +} diff --git a/examples/legacy-site-app/wrangler.toml b/examples/legacy-site-app/wrangler.toml new file mode 100644 index 0000000000000..420a605a0256d --- /dev/null +++ b/examples/legacy-site-app/wrangler.toml @@ -0,0 +1,6 @@ +account_id = "" +name = "legacy-site-app" +type = "webpack" +workers_dev = true +site = { bucket = "./public" } +compatibility_date = "2022-04-24" diff --git a/packages/wrangler/src/__tests__/configuration.test.ts b/packages/wrangler/src/__tests__/configuration.test.ts index 428a41250f99e..f22cbc561d095 100644 --- a/packages/wrangler/src/__tests__/configuration.test.ts +++ b/packages/wrangler/src/__tests__/configuration.test.ts @@ -7,6 +7,8 @@ import type { RawEnvironment, } from "../config"; +const isWindows = process.platform === "win32"; + describe("normalizeAndValidateConfig()", () => { it("should use defaults for empty configuration", () => { const { config, diagnostics } = normalizeAndValidateConfig({}, undefined, { @@ -230,8 +232,9 @@ describe("normalizeAndValidateConfig()", () => { const expectedConfig: RawConfig = { site: { bucket: "BUCKET", - include: ["INCLUDE_1", "INCLUDE_2"], + "entry-point": "my-site", exclude: ["EXCLUDE_1", "EXCLUDE_2"], + include: ["INCLUDE_1", "INCLUDE_2"], }, }; @@ -243,13 +246,33 @@ describe("normalizeAndValidateConfig()", () => { expect(config).toEqual(expect.objectContaining(expectedConfig)); expect(diagnostics.hasErrors()).toBe(false); - expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.hasWarnings()).toBe(true); + if (!isWindows) { + expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - DEPRECATION: \\"site.entry-point\\": + Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file: + \`\`\` + main = \\"my-site/index.js\\" + \`\`\`" + `); + } else { + expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - DEPRECATION: \\"site.entry-point\\": + Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file: + \`\`\` + main = \\"my-site\\index.js\\" + \`\`\`" + `); + } }); it("should error if `site` config is missing `bucket`", () => { const expectedConfig: RawConfig = { // @ts-expect-error we're intentionally passing an invalid configuration here site: { + "entry-point": "workers-site", include: ["INCLUDE_1", "INCLUDE_2"], exclude: ["EXCLUDE_1", "EXCLUDE_2"], }, @@ -262,18 +285,38 @@ describe("normalizeAndValidateConfig()", () => { ); expect(config).toEqual(expect.objectContaining(expectedConfig)); + expect(diagnostics.hasWarnings()).toBe(true); expect(diagnostics.hasErrors()).toBe(true); + if (!isWindows) { + expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - DEPRECATION: \\"site.entry-point\\": + Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file: + \`\`\` + main = \\"workers-site/index.js\\" + \`\`\`" + `); + } else { + expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - DEPRECATION: \\"site.entry-point\\": + Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file: + \`\`\` + main = \\"workers-site\\index.js\\" + \`\`\`" + `); + } expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` "Processing wrangler configuration: - \\"site.bucket\\" is a required field." `); - expect(diagnostics.hasWarnings()).toBe(false); }); it("should error on invalid `site` values", () => { const expectedConfig = { site: { bucket: "BUCKET", + "entry-point": 111, include: [222, 333], exclude: [444, 555], }, @@ -286,17 +329,37 @@ describe("normalizeAndValidateConfig()", () => { ); expect(config).toEqual(expect.objectContaining(expectedConfig)); - expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.hasWarnings()).toBe(true); expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` "Processing wrangler configuration: - Expected \\"sites.include.[0]\\" to be of type string but got 222. - Expected \\"sites.include.[1]\\" to be of type string but got 333. - Expected \\"sites.exclude.[0]\\" to be of type string but got 444. - - Expected \\"sites.exclude.[1]\\" to be of type string but got 555." + - Expected \\"sites.exclude.[1]\\" to be of type string but got 555. + - Expected \\"site.entry-point\\" to be of type string but got 111." + `); + if (!isWindows) { + expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - DEPRECATION: \\"site.entry-point\\": + Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file: + \`\`\` + main = \\"111/index.js\\" + \`\`\`" + `); + } else { + expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - DEPRECATION: \\"site.entry-point\\": + Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file: + \`\`\` + main = \\"111\\index.js\\" + \`\`\`" `); + } }); - it("should error with a deprecation warning if entry-point is defined", async () => { + it("should log a deprecation warning if entry-point is defined", async () => { const { config, diagnostics } = normalizeAndValidateConfig( { site: { @@ -311,22 +374,32 @@ describe("normalizeAndValidateConfig()", () => { expect(config.site).toMatchInlineSnapshot(` Object { "bucket": "some/path", + "entry-point": "some/other/script.js", "exclude": Array [], "include": Array [], } `); expect(diagnostics.hasWarnings()).toBe(true); - expect(diagnostics.hasErrors()).toBe(true); - expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(` + expect(diagnostics.hasErrors()).toBe(false); + if (!isWindows) { + expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(` "Processing wrangler configuration: - - Unexpected fields found in site field: \\"entry-point\\"" + - DEPRECATION: \\"site.entry-point\\": + Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file: + \`\`\` + main = \\"some/other/script.js\\" + \`\`\`" `); - expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + } else { + expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(` "Processing wrangler configuration: - - NO LONGER SUPPORTED: \\"site.entry-point\\": - The \`site.entry-point\` config field is no longer used. - The entry-point should be specified via the command line or the \`main\` config field." + - DEPRECATION: \\"site.entry-point\\": + Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file: + \`\`\` + main = \\"some\\other\\script.js\\" + \`\`\`" `); + } }); }); diff --git a/packages/wrangler/src/__tests__/publish.test.ts b/packages/wrangler/src/__tests__/publish.test.ts index da3ee66fd350b..282dec0aa1f94 100644 --- a/packages/wrangler/src/__tests__/publish.test.ts +++ b/packages/wrangler/src/__tests__/publish.test.ts @@ -758,10 +758,19 @@ export default{ " `); - expect(std.warn).toMatchInlineSnapshot(`""`); + expect(std.warn).toMatchInlineSnapshot(` + "▲ [WARNING] Processing wrangler.toml configuration: + - Because you've defined a [site] configuration, we've defaulting to \\"workers-site\\" for the deprecated \`site.entry-point\`field. + Add the top level \`main\` field to your configuration file: + \`\`\` + main = \\"workers-site/index.js\\" + \`\`\` + + " + `); }); - it("should error if there is a `site.entry-point` configuration", async () => { + it("should warn if there is a `site.entry-point` configuration", async () => { const assets = [ { filePath: "assets/file-1.txt", content: "Content of file-1" }, { filePath: "assets/file-2.txt", content: "Content of file-2" }, @@ -784,35 +793,102 @@ export default{ mockListKVNamespacesRequest(kvNamespace); mockKeyListRequest(kvNamespace.id, []); mockUploadAssetsToKVRequest(kvNamespace.id, assets); + await runWrangler("publish ./index.js"); - await expect(runWrangler("publish ./index.js")).rejects - .toThrowErrorMatchingInlineSnapshot(` - "Processing wrangler.toml configuration: - - NO LONGER SUPPORTED: \\"site.entry-point\\": - The \`site.entry-point\` config field is no longer used. - The entry-point should be specified via the command line or the \`main\` config field." - `); + expect(std).toMatchInlineSnapshot(` + Object { + "debug": "", + "err": "", + "out": "Reading assets/file-1.txt... + Uploading as assets/file-1.2ca234f380.txt... + Reading assets/file-2.txt... + Uploading as assets/file-2.5938485188.txt... + ↗️ Done syncing assets + Uploaded test-name (TIMINGS) + Published test-name (TIMINGS) + test-name.test-sub-domain.workers.dev", + "warn": "▲ [WARNING] Processing wrangler.toml configuration: + - DEPRECATION: \\"site.entry-point\\": + Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file: + \`\`\` + main = \\"index.js\\" + \`\`\` - expect(std.out).toMatchInlineSnapshot(` - " - If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new." + ", + } `); - expect(std.err).toMatchInlineSnapshot(` - "X [ERROR] Processing wrangler.toml configuration: - - NO LONGER SUPPORTED: \\"site.entry-point\\": - The \`site.entry-point\` config field is no longer used. - The entry-point should be specified via the command line or the \`main\` config field. + }); - " - `); - expect(std.warn).toMatchInlineSnapshot(` - "▲ [WARNING] Processing wrangler.toml configuration: - - Unexpected fields found in site field: \\"entry-point\\" + it("should resolve site.entry-point relative to wrangler.toml", async () => { + const assets = [ + { filePath: "assets/file-1.txt", content: "Content of file-1" }, + { filePath: "assets/file-2.txt", content: "Content of file-2" }, + ]; + const kvNamespace = { + title: "__test-name-workers_sites_assets", + id: "__test-name-workers_sites_assets-id", + }; + fs.mkdirSync("my-site"); + process.chdir("my-site"); + writeWranglerToml({ + site: { + bucket: "assets", + "entry-point": "my-entry", + }, + }); + fs.mkdirSync("my-entry"); + fs.writeFileSync("my-entry/index.js", "export default {}"); + writeAssets(assets); + process.chdir(".."); + mockUploadWorkerRequest(); + mockSubDomainRequest(); + mockListKVNamespacesRequest(kvNamespace); + mockKeyListRequest(kvNamespace.id, []); + mockUploadAssetsToKVRequest(kvNamespace.id, assets); + await runWrangler("publish --config ./my-site/wrangler.toml"); - " + expect(std).toMatchInlineSnapshot(` + Object { + "debug": "", + "err": "", + "out": "Reading assets/file-1.txt... + Uploading as assets/file-1.2ca234f380.txt... + Reading assets/file-2.txt... + Uploading as assets/file-2.5938485188.txt... + ↗️ Done syncing assets + Uploaded test-name (TIMINGS) + Published test-name (TIMINGS) + test-name.test-sub-domain.workers.dev", + "warn": "▲ [WARNING] Processing my-site/wrangler.toml configuration: + - DEPRECATION: \\"site.entry-point\\": + Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file: + \`\`\` + main = \\"my-entry/index.js\\" + \`\`\` + + ", + } `); }); + it("should error if both main and site.entry-point are specified", async () => { + writeWranglerToml({ + main: "some-entry", + site: { + bucket: "some-bucket", + "entry-point": "./index.js", + }, + }); + + await expect(runWrangler("publish")).rejects + .toThrowErrorMatchingInlineSnapshot(` + "Processing wrangler.toml configuration: + - Don't define both the \`main\` and \`site.entry-point\` fields in your configuration. + They serve the same purpose: to point to the entry-point of your worker. + Delete the \`site.entry-point\` field from your config." + `); + }); + it("should error if there is no entry-point specified", async () => { writeWranglerToml(); writeWorkerSource(); diff --git a/packages/wrangler/src/config/validation.ts b/packages/wrangler/src/config/validation.ts index 0a11bc4c51939..297f26b149d86 100644 --- a/packages/wrangler/src/config/validation.ts +++ b/packages/wrangler/src/config/validation.ts @@ -85,16 +85,6 @@ export function normalizeAndValidateConfig( true ); - deprecated( - diagnostics, - rawConfig, - `site.entry-point`, - `The \`site.entry-point\` config field is no longer used.\nThe entry-point should be specified via the command line or the \`main\` config field.`, - false, - "NO LONGER SUPPORTED", - "error" - ); - validateOptionalProperty( diagnostics, "", @@ -204,7 +194,12 @@ export function normalizeAndValidateConfig( diagnostics, rawConfig.migrations ?? [] ), - site: normalizeAndValidateSite(diagnostics, rawConfig.site), + site: normalizeAndValidateSite( + diagnostics, + configPath, + rawConfig, + activeEnv.main + ), wasm_modules: normalizeAndValidateModulePaths( diagnostics, configPath, @@ -454,15 +449,86 @@ function normalizeAndValidateMigrations( */ function normalizeAndValidateSite( diagnostics: Diagnostics, - rawSite: Config["site"] + configPath: string | undefined, + rawConfig: RawConfig, + mainEntryPoint: string | undefined ): Config["site"] { - if (rawSite !== undefined) { - const { bucket, include = [], exclude = [], ...rest } = rawSite; - validateAdditionalProperties(diagnostics, "site", Object.keys(rest), []); + if (rawConfig?.site !== undefined) { + const { bucket, include = [], exclude = [], ...rest } = rawConfig.site; + + validateAdditionalProperties(diagnostics, "site", Object.keys(rest), [ + "entry-point", + ]); validateRequiredProperty(diagnostics, "site", "bucket", bucket, "string"); validateTypedArray(diagnostics, "sites.include", include, "string"); validateTypedArray(diagnostics, "sites.exclude", exclude, "string"); - return { bucket, include, exclude }; + validateOptionalProperty( + diagnostics, + "site", + "entry-point", + rawConfig.site["entry-point"], + "string" + ); + + deprecated( + diagnostics, + rawConfig, + `site.entry-point`, + `Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file:\n` + + `\`\`\`\n` + + `main = "${path.join( + String(rawConfig.site["entry-point"]) || "workers-site", + path.extname(String(rawConfig.site["entry-point"]) || "workers-site") + ? "" + : "index.js" + )}"\n` + + `\`\`\``, + false, + undefined, + "warning" + ); + + let siteEntryPoint = rawConfig.site["entry-point"]; + + if (!mainEntryPoint && !rawConfig.site["entry-point"]) { + // this means that we're defaulting to "workers-site" + // so let's add the deprecation warning + diagnostics.warnings.push( + `Because you've defined a [site] configuration, we've defaulting to "workers-site" for the deprecated \`site.entry-point\`field.\n` + + `Add the top level \`main\` field to your configuration file:\n` + + `\`\`\`\n` + + `main = "workers-site/index.js"\n` + + `\`\`\`` + ); + siteEntryPoint = "workers-site"; + } + + if (mainEntryPoint && rawConfig.site["entry-point"]) { + diagnostics.errors.push( + `Don't define both the \`main\` and \`site.entry-point\` fields in your configuration.\n` + + `They serve the same purpose: to point to the entry-point of your worker.\n` + + `Delete the \`site.entry-point\` field from your config.` + ); + siteEntryPoint = rawConfig.site["entry-point"]; + } + + if (configPath && siteEntryPoint) { + // rewrite the path to be relative to the config file + siteEntryPoint = path.relative( + process.cwd(), + path.join( + path.dirname(configPath), + rawConfig.site["entry-point"] || "workers-site" + ) + ); + } + + return { + bucket, + "entry-point": siteEntryPoint, + include, + exclude, + }; } return undefined; } diff --git a/packages/wrangler/src/entry.ts b/packages/wrangler/src/entry.ts index 05c524a797baf..332bbc1628921 100644 --- a/packages/wrangler/src/entry.ts +++ b/packages/wrangler/src/entry.ts @@ -1,5 +1,5 @@ import assert from "node:assert"; -import { existsSync } from "node:fs"; +import { existsSync, lstatSync } from "node:fs"; import path from "node:path"; import * as esbuild from "esbuild"; import { execaCommand } from "execa"; @@ -25,13 +25,20 @@ export async function getEntry( ): Promise { let file: string; let directory = process.cwd(); + let isSiteEntryPoint = false; if (args.script) { // If the script name comes from the command line it is relative to the current working directory. file = path.resolve(args.script); } else if (config.main === undefined) { - throw new Error( - `Missing entry-point: The entry-point should be specified via the command line (e.g. \`wrangler ${command} path/to/script\`) or the \`main\` config field.` - ); + if (config.site?.["entry-point"]) { + isSiteEntryPoint = true; + directory = path.resolve(path.dirname(config.configPath ?? ".")); + file = path.resolve(config.site?.["entry-point"]); + } else { + throw new Error( + `Missing entry-point: The entry-point should be specified via the command line (e.g. \`wrangler ${command} path/to/script\`) or the \`main\` config field.` + ); + } } else { directory = path.resolve(path.dirname(config.configPath ?? ".")); file = path.resolve(directory, config.main); @@ -39,6 +46,13 @@ export async function getEntry( await runCustomBuild(file, config.build); + if (isSiteEntryPoint) { + // site.entry-point could be a directory + if (existsSync(file) && lstatSync(file).isDirectory()) { + file = path.resolve(file, "index.js"); + } + } + if (fileExists(file) === false) { throw new Error( `Could not resolve "${path.relative(process.cwd(), file)}".`