@lukoweb/apitogo 0.2.57 → 0.2.59
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.
- package/dist/cli/cli.js +117 -12
- package/dist/cli/worker.js +4 -1
- package/docs/cli/overview.md +9 -10
- package/docs/cli/setup.md +2 -2
- package/docs/configuration/portal.md +2 -1
- package/docs/mcp/go-live.md +44 -72
- package/docs/mcp/overview.md +39 -57
- package/docs/mcp/setup.md +169 -173
- package/docs/mcp/shape.md +77 -77
- package/docs/mcp/tools.md +52 -96
- package/package.json +1 -1
- package/src/config/v2/gateway-request.ts +5 -2
- package/src/config/v2/index.ts +1 -0
- package/src/config/v2/manifest.ts +25 -0
- package/src/config/v2/resolve.ts +5 -1
- package/src/config/v2/schema.ts +18 -5
- package/src/vite/prerender/utils.ts +89 -76
- package/src/vite/prerender/worker.ts +2 -1
package/docs/mcp/tools.md
CHANGED
|
@@ -1,101 +1,57 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: MCP tools
|
|
3
|
-
description: APItoGo MCP
|
|
3
|
+
description: Every tool of the APItoGo MCP server and the CLI command it runs.
|
|
4
4
|
sidebar_icon: wrench
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
| `package_backend` | Package backend artifact |
|
|
59
|
-
| `deploy_backend` | Deploy API hub |
|
|
60
|
-
| `get_backend_summary` | Backend status |
|
|
61
|
-
| `get_deployment_status` | Deployment job status |
|
|
62
|
-
| `get_gateway_readiness` | Gateway readiness |
|
|
63
|
-
|
|
64
|
-
## Gateway
|
|
65
|
-
|
|
66
|
-
| Tool | Role |
|
|
67
|
-
| ------------------------------------------------------------------ | ------------------------ |
|
|
68
|
-
| `list/get/create/update/patch_gateway_plan` | Plan CRUD |
|
|
69
|
-
| `add_actions_to_gateway_plan` / `remove_actions_from_gateway_plan` | Attach ops |
|
|
70
|
-
| `set_action_public` | Public routes |
|
|
71
|
-
| `upsert_entitlement` | Entitlements |
|
|
72
|
-
| `configure_gateway_baseline` | Baseline policies |
|
|
73
|
-
| `get/update_gateway_settings` | Settings |
|
|
74
|
-
| `get/update_gateway_auth` | Gateway auth |
|
|
75
|
-
| `sync_gateway_plans_from_manifest` | Post-publish action sync |
|
|
76
|
-
| `reorder_gateway_plans_from_manifest` | Plan order |
|
|
77
|
-
|
|
78
|
-
## Infrastructure and deploy
|
|
79
|
-
|
|
80
|
-
| Tool | Role |
|
|
81
|
-
| ------------------------------------------------------ | ---------------- |
|
|
82
|
-
| `activate_module` | Activate modules |
|
|
83
|
-
| `get_infrastructure_status` / Azure variants | Infra status |
|
|
84
|
-
| `list_provisioning_jobs`, `wait_for_job` | Jobs |
|
|
85
|
-
| `get/set/delete_custom_domain` | Custom domain |
|
|
86
|
-
| `get_dev_portal_deploy_config`, `deploy_dev_portal`, … | SWA deploy |
|
|
87
|
-
|
|
88
|
-
## Hand-edit exceptions
|
|
89
|
-
|
|
90
|
-
| Change | Where |
|
|
91
|
-
| ----------------- | -------------------------------------------------------- |
|
|
92
|
-
| Home page content | `landing/LandingPage.tsx` |
|
|
93
|
-
| Site footer | `apitogo.config.json` → `modules.landing.content.footer` |
|
|
94
|
-
|
|
95
|
-
There is **no** `update_dev_portal_landing` tool for arbitrary home content.
|
|
96
|
-
|
|
97
|
-
## Related
|
|
98
|
-
|
|
99
|
-
- [Overview](./overview.md)
|
|
100
|
-
- [Shape](./shape.md)
|
|
101
|
-
- [Go live](./go-live.md)
|
|
7
|
+
Each tool runs one `apitogo` command with `--json` in the project folder and answers with its JSON.
|
|
8
|
+
Every tool takes `directory`: the project folder, absolute or relative to where the server runs
|
|
9
|
+
(default: that folder).
|
|
10
|
+
|
|
11
|
+
## Make and shape a project
|
|
12
|
+
|
|
13
|
+
| Tool | Runs | What it does |
|
|
14
|
+
| ---------------- | -------------------------- | ----------------------------------------------------------------------- |
|
|
15
|
+
| `apitogo_create` | `apitogo create` | A new project folder; `template` `full` or `blank`, optional `services` |
|
|
16
|
+
| `apitogo_init` | `apitogo init` | Prepares an existing folder: `apitogo.config.json` and `apitogo/` only |
|
|
17
|
+
| `apitogo_add` | `apitogo add <service>` | Adds `portal`, `gateway`, `identity` or `plans`; settings in `options` |
|
|
18
|
+
| `apitogo_remove` | `apitogo remove <service>` | Takes a service out of the config; `deleteFiles` also deletes its files |
|
|
19
|
+
| `apitogo_config` | `apitogo config` | Portal name, OpenAPI, docs pages, plans, or a `patch` of the config |
|
|
20
|
+
|
|
21
|
+
`options` of `apitogo_add` and `apitogo_config` are the command's flags in camelCase, for example
|
|
22
|
+
`{"planSku":"pro","planPrice":"19"}` for `--plan-sku pro --plan-price 19`. A flag the command does
|
|
23
|
+
not have is refused before anything runs. `true` and `false` are `--flag` and `--no-flag`; a list
|
|
24
|
+
repeats the flag.
|
|
25
|
+
|
|
26
|
+
## Check, preview, deploy
|
|
27
|
+
|
|
28
|
+
| Tool | Runs | What it does |
|
|
29
|
+
| ---------------- | ---------------- | ------------------------------------------------------------ |
|
|
30
|
+
| `apitogo_check` | `apitogo check` | Checks the config and its files; `offline`, `validateBuild` |
|
|
31
|
+
| `apitogo_plan` | `apitogo plan` | What a deploy would do for each service; changes nothing |
|
|
32
|
+
| `apitogo_deploy` | `apitogo deploy` | Deploys what changed; `only`, `force`, `confirmIrreversible` |
|
|
33
|
+
| `apitogo_status` | `apitogo status` | Each service's state and addresses; `skipLive` is faster |
|
|
34
|
+
|
|
35
|
+
## Platform project
|
|
36
|
+
|
|
37
|
+
| Tool | Runs | What it does |
|
|
38
|
+
| ------------------------ | -------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `apitogo_project_create` | `apitogo project create` | Creates the project; `plan` free, starter or professional |
|
|
40
|
+
| `apitogo_project_use` | `apitogo project use <id>` | Points the folder at another existing project |
|
|
41
|
+
| `apitogo_secrets_list` | `apitogo secrets list` | The names of the project's secrets, never their values |
|
|
42
|
+
| `apitogo_secrets_remove` | `apitogo secrets rm` | Removes a secret |
|
|
43
|
+
| `apitogo_teardown` | `apitogo teardown` | Deletes the project, or with `only` its gateway, identity or plans; needs `confirmProjectName` |
|
|
44
|
+
|
|
45
|
+
## Not a tool, on purpose
|
|
46
|
+
|
|
47
|
+
| Command | Why |
|
|
48
|
+
| --------------------- | --------------------------------------------------------- |
|
|
49
|
+
| `apitogo login` | Opens a browser to sign you in; run it once in a terminal |
|
|
50
|
+
| `apitogo secrets set` | A secret's value must not pass through the agent |
|
|
51
|
+
| `apitogo run` | The local preview keeps running; start it yourself |
|
|
52
|
+
|
|
53
|
+
## Safety hints
|
|
54
|
+
|
|
55
|
+
Tools carry MCP hints so hosts can ask before running them: `apitogo_check`, `apitogo_plan`,
|
|
56
|
+
`apitogo_status` and `apitogo_secrets_list` are read-only; `apitogo_remove`,
|
|
57
|
+
`apitogo_secrets_remove` and `apitogo_teardown` are marked destructive.
|
package/package.json
CHANGED
|
@@ -39,11 +39,13 @@ export type GatewayConfigBody = {
|
|
|
39
39
|
export type DerivedCorsSite = { origin: string; source: "portal" | "docs" };
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* The browser origins of this project's own sites: the portal (from `services.json`) and an
|
|
43
|
-
* (from the config). Only the origin is kept, never a path. Sorted, so the same sites always give the same body.
|
|
42
|
+
* The browser origins of this project's own sites: the portal and its custom domain (from `services.json`) and an
|
|
43
|
+
* external docs site (from the config). Only the origin is kept, never a path. Sorted, so the same sites always give the same body.
|
|
44
44
|
*/
|
|
45
45
|
export function derivedCorsSites(sites: {
|
|
46
46
|
portalUrl?: string;
|
|
47
|
+
/** The portal's custom domain, once it is ready (API-130). */
|
|
48
|
+
portalCustomDomain?: string;
|
|
47
49
|
docsUrl?: string;
|
|
48
50
|
}): DerivedCorsSite[] {
|
|
49
51
|
const originOf = (url?: string) => {
|
|
@@ -58,6 +60,7 @@ export function derivedCorsSites(sites: {
|
|
|
58
60
|
const found = [
|
|
59
61
|
{ origin: originOf(sites.docsUrl), source: "docs" as const },
|
|
60
62
|
{ origin: originOf(sites.portalUrl), source: "portal" as const },
|
|
63
|
+
{ origin: originOf(sites.portalCustomDomain), source: "portal" as const },
|
|
61
64
|
].flatMap((site) => (site.origin ? [{ ...site, origin: site.origin }] : []));
|
|
62
65
|
const seen = new Set<string>();
|
|
63
66
|
return found
|
package/src/config/v2/index.ts
CHANGED
|
@@ -29,6 +29,8 @@ const ServiceManifestSchema = z.looseObject({
|
|
|
29
29
|
portal: z
|
|
30
30
|
.looseObject({
|
|
31
31
|
url: z.string().optional(),
|
|
32
|
+
/** The portal's custom domain once it is ready, such as `https://docs.acme.com` (API-130). */
|
|
33
|
+
customDomain: z.string().optional(),
|
|
32
34
|
docs: z
|
|
33
35
|
.looseObject({ provider: z.string(), url: z.string().optional() })
|
|
34
36
|
.optional(),
|
|
@@ -291,6 +293,29 @@ export function withPortalEntry(
|
|
|
291
293
|
};
|
|
292
294
|
}
|
|
293
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Records the portal's custom domain (an https origin), or removes it when undefined (API-130). The gateway lets the
|
|
298
|
+
* browser call it from there, and `plan` reads it from here, so it agrees with deploy without asking the platform.
|
|
299
|
+
* Returns undefined when nothing changes, so an unchanged deploy leaves the file (and the portal's build hash) alone.
|
|
300
|
+
*/
|
|
301
|
+
export function withPortalCustomDomain(
|
|
302
|
+
existing: ServiceManifest | undefined,
|
|
303
|
+
customDomain: string | undefined,
|
|
304
|
+
): ServiceManifest | undefined {
|
|
305
|
+
if (!existing) return undefined;
|
|
306
|
+
const portal = existing.services.portal;
|
|
307
|
+
if (portal?.customDomain === customDomain) return undefined;
|
|
308
|
+
if (!portal && customDomain === undefined) return undefined;
|
|
309
|
+
const { customDomain: _previous, ...rest } = portal ?? {};
|
|
310
|
+
return {
|
|
311
|
+
...existing,
|
|
312
|
+
services: {
|
|
313
|
+
...existing.services,
|
|
314
|
+
portal: customDomain === undefined ? rest : { ...rest, customDomain },
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
294
319
|
/**
|
|
295
320
|
* Adds the gateway entry a gateway deploy learned. Only public values: the base URL and what the platform says the
|
|
296
321
|
* gateway can do. Entries of other services are kept as they are.
|
package/src/config/v2/resolve.ts
CHANGED
|
@@ -169,7 +169,11 @@ export function resolveConfigV2(
|
|
|
169
169
|
}
|
|
170
170
|
: {
|
|
171
171
|
light: logoSource(configDir, portal.logo.light),
|
|
172
|
-
dark
|
|
172
|
+
// Without a dark image, the light one is used in both color schemes.
|
|
173
|
+
dark: logoSource(
|
|
174
|
+
configDir,
|
|
175
|
+
portal.logo.dark ?? portal.logo.light,
|
|
176
|
+
),
|
|
173
177
|
...(portal.logo.alt ? { alt: portal.logo.alt } : {}),
|
|
174
178
|
},
|
|
175
179
|
}
|
package/src/config/v2/schema.ts
CHANGED
|
@@ -49,12 +49,25 @@ export const PortalSchema = z.strictObject({
|
|
|
49
49
|
landing: path.optional(),
|
|
50
50
|
/** The footer: a React component file shown under every page except the console's (API-129). */
|
|
51
51
|
footer: path.optional(),
|
|
52
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* The logo in the portal header: one image, or one per color scheme (API-129). `dark` is optional; without it the
|
|
54
|
+
* light image is used in both.
|
|
55
|
+
*/
|
|
53
56
|
logo: z
|
|
54
|
-
.union(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
.union(
|
|
58
|
+
[
|
|
59
|
+
path,
|
|
60
|
+
z.strictObject({
|
|
61
|
+
light: path,
|
|
62
|
+
dark: path.optional(),
|
|
63
|
+
alt: z.string().optional(),
|
|
64
|
+
}),
|
|
65
|
+
],
|
|
66
|
+
{
|
|
67
|
+
error:
|
|
68
|
+
'must be an image path or https address, or { "light": "...", "dark": "...", "alt": "..." } (dark and alt are optional)',
|
|
69
|
+
},
|
|
70
|
+
)
|
|
58
71
|
.optional(),
|
|
59
72
|
requireAuth: z
|
|
60
73
|
.union([z.boolean(), z.strictObject({ exclude: pathList })], {
|
|
@@ -1,76 +1,89 @@
|
|
|
1
|
-
import type { RouteObject } from "react-router";
|
|
2
|
-
import { joinUrl } from "../../lib/util/joinUrl.js";
|
|
3
|
-
|
|
4
|
-
const resolveRoutePath = (path: string): string | undefined => {
|
|
5
|
-
const segments = path.split("/");
|
|
6
|
-
if (segments.some((s) => s.startsWith(":") && !s.endsWith("?"))) {
|
|
7
|
-
return undefined;
|
|
8
|
-
}
|
|
9
|
-
return segments.filter((s) => !s.startsWith(":")).join("/") || undefined;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const isSkipped = (path: string) => path.includes("*") || /^\d+$/.test(path);
|
|
13
|
-
|
|
14
|
-
export type RouteRewrite = { source: string; destination: string };
|
|
15
|
-
|
|
16
|
-
type ResolvedRoute = {
|
|
17
|
-
path: string;
|
|
18
|
-
hasStrippedParams: boolean;
|
|
19
|
-
children: ResolvedRoute[];
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const resolveRoutes = (
|
|
23
|
-
routes: RouteObject[],
|
|
24
|
-
parentPath = "",
|
|
25
|
-
): ResolvedRoute[] =>
|
|
26
|
-
routes.flatMap((route) => {
|
|
27
|
-
if (route.path && isSkipped(route.path)) return [];
|
|
28
|
-
|
|
29
|
-
const routePath = route.path ? resolveRoutePath(route.path) : undefined;
|
|
30
|
-
if (route.path && !routePath) return [];
|
|
31
|
-
|
|
32
|
-
const fullPath = routePath
|
|
33
|
-
? routePath.startsWith("/")
|
|
34
|
-
? routePath
|
|
35
|
-
: joinUrl(parentPath, routePath)
|
|
36
|
-
: parentPath;
|
|
37
|
-
|
|
38
|
-
const hasStrippedParams =
|
|
39
|
-
route.path?.split("/").some((s) => s.startsWith(":")) ?? false;
|
|
40
|
-
|
|
41
|
-
return [
|
|
42
|
-
{
|
|
43
|
-
path: fullPath,
|
|
44
|
-
hasStrippedParams,
|
|
45
|
-
children: resolveRoutes(route.children ?? [], fullPath),
|
|
46
|
-
},
|
|
47
|
-
];
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const collectPaths = (
|
|
51
|
-
resolved: ResolvedRoute[],
|
|
52
|
-
parentPath: string,
|
|
53
|
-
): string[] =>
|
|
54
|
-
resolved.flatMap((r) => [
|
|
55
|
-
...(r.path !== parentPath ? [r.path] : []),
|
|
56
|
-
...collectPaths(r.children, r.path),
|
|
57
|
-
]);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
1
|
+
import type { RouteObject } from "react-router";
|
|
2
|
+
import { joinUrl } from "../../lib/util/joinUrl.js";
|
|
3
|
+
|
|
4
|
+
const resolveRoutePath = (path: string): string | undefined => {
|
|
5
|
+
const segments = path.split("/");
|
|
6
|
+
if (segments.some((s) => s.startsWith(":") && !s.endsWith("?"))) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
return segments.filter((s) => !s.startsWith(":")).join("/") || undefined;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const isSkipped = (path: string) => path.includes("*") || /^\d+$/.test(path);
|
|
13
|
+
|
|
14
|
+
export type RouteRewrite = { source: string; destination: string };
|
|
15
|
+
|
|
16
|
+
type ResolvedRoute = {
|
|
17
|
+
path: string;
|
|
18
|
+
hasStrippedParams: boolean;
|
|
19
|
+
children: ResolvedRoute[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const resolveRoutes = (
|
|
23
|
+
routes: RouteObject[],
|
|
24
|
+
parentPath = "",
|
|
25
|
+
): ResolvedRoute[] =>
|
|
26
|
+
routes.flatMap((route) => {
|
|
27
|
+
if (route.path && isSkipped(route.path)) return [];
|
|
28
|
+
|
|
29
|
+
const routePath = route.path ? resolveRoutePath(route.path) : undefined;
|
|
30
|
+
if (route.path && !routePath) return [];
|
|
31
|
+
|
|
32
|
+
const fullPath = routePath
|
|
33
|
+
? routePath.startsWith("/")
|
|
34
|
+
? routePath
|
|
35
|
+
: joinUrl(parentPath, routePath)
|
|
36
|
+
: parentPath;
|
|
37
|
+
|
|
38
|
+
const hasStrippedParams =
|
|
39
|
+
route.path?.split("/").some((s) => s.startsWith(":")) ?? false;
|
|
40
|
+
|
|
41
|
+
return [
|
|
42
|
+
{
|
|
43
|
+
path: fullPath,
|
|
44
|
+
hasStrippedParams,
|
|
45
|
+
children: resolveRoutes(route.children ?? [], fullPath),
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const collectPaths = (
|
|
51
|
+
resolved: ResolvedRoute[],
|
|
52
|
+
parentPath: string,
|
|
53
|
+
): string[] =>
|
|
54
|
+
resolved.flatMap((r) => [
|
|
55
|
+
...(r.path !== parentPath ? [r.path] : []),
|
|
56
|
+
...collectPaths(r.children, r.path),
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The file a prerendered page is written to. `/` is `index.html`; any other page is `<path>.html`, except a page whose
|
|
61
|
+
* last segment is `index`: `/index.html` is the home page's file, and a docs page at `/index` (kept off `/` when the
|
|
62
|
+
* portal has a home page) overwrote it, so the home page was served as the docs page until the browser took over
|
|
63
|
+
* (API-130). Such a page gets a folder of its own instead.
|
|
64
|
+
*/
|
|
65
|
+
export const prerenderFileFor = (urlPath: string): string =>
|
|
66
|
+
urlPath === "/"
|
|
67
|
+
? "/index.html"
|
|
68
|
+
: urlPath.endsWith("/index")
|
|
69
|
+
? `${urlPath}/index.html`
|
|
70
|
+
: `${urlPath}.html`;
|
|
71
|
+
|
|
72
|
+
const collectRewrites = (resolved: ResolvedRoute[]): RouteRewrite[] =>
|
|
73
|
+
resolved.flatMap((r) => [
|
|
74
|
+
...(r.hasStrippedParams && r.path
|
|
75
|
+
? [
|
|
76
|
+
{
|
|
77
|
+
source: `${r.path}/(.+)`,
|
|
78
|
+
destination: prerenderFileFor(r.path),
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
: []),
|
|
82
|
+
...collectRewrites(r.children),
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
export const routesToPaths = (routes: RouteObject[]): string[] =>
|
|
86
|
+
collectPaths(resolveRoutes(routes), "");
|
|
87
|
+
|
|
88
|
+
export const routesToRewrites = (routes: RouteObject[]): RouteRewrite[] =>
|
|
89
|
+
collectRewrites(resolveRoutes(routes));
|
|
@@ -7,6 +7,7 @@ import type { ZudokuConfig } from "../../config/validators/ZudokuConfig.js";
|
|
|
7
7
|
import { runPluginTransformConfig } from "../../lib/core/transform-config.js";
|
|
8
8
|
import { joinUrl } from "../../lib/util/joinUrl.js";
|
|
9
9
|
import type { WorkerResult } from "./prerender.js";
|
|
10
|
+
import { prerenderFileFor } from "./utils.js";
|
|
10
11
|
|
|
11
12
|
type EntryServer = typeof import("../../app/entry.server.js");
|
|
12
13
|
|
|
@@ -47,7 +48,7 @@ const routes = server.getRoutesByConfig(config);
|
|
|
47
48
|
const { basePath } = config;
|
|
48
49
|
|
|
49
50
|
const renderPage = async ({ urlPath }: WorkerData): Promise<WorkerResult> => {
|
|
50
|
-
const filename = urlPath
|
|
51
|
+
const filename = prerenderFileFor(urlPath);
|
|
51
52
|
const pathname = joinUrl(basePath, urlPath);
|
|
52
53
|
const url = joinUrl("http://localhost", pathname);
|
|
53
54
|
const outputPath = path.join(distDir, filename);
|