@lukoweb/apitogo 0.2.64 → 0.2.66
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 +9 -3
- package/dist/cli/worker.js +6 -1
- package/dist/declarations/cli/platform/tools/infrastructure.d.ts +1 -0
- package/package.json +1 -1
- package/src/config/v2/wrapper.ts +8 -4
- package/src/lib/components/Meta.tsx +49 -46
- package/src/vite/prerender/utils.ts +12 -0
- package/src/vite/prerender/worker.ts +2 -2
package/dist/cli/cli.js
CHANGED
|
@@ -8519,10 +8519,13 @@ function buildSiteConfig(resolved, plan2, theme, extras = {}) {
|
|
|
8519
8519
|
}
|
|
8520
8520
|
] : []
|
|
8521
8521
|
];
|
|
8522
|
+
const logo = portal?.logo ? siteLogo(portal) : void 0;
|
|
8522
8523
|
return {
|
|
8524
|
+
// The browser tab's icon is the project's logo; without one, the portal's own icon (API-134).
|
|
8525
|
+
...logo ? { metadata: { favicon: logo.src.light } } : {},
|
|
8523
8526
|
site: {
|
|
8524
8527
|
title: portal?.name ?? resolved.project.name,
|
|
8525
|
-
...
|
|
8528
|
+
...logo ? { logo } : {},
|
|
8526
8529
|
...plan2.preview ? { banner: { message: PREVIEW_BANNER_MESSAGE, color: "caution" } } : {}
|
|
8527
8530
|
},
|
|
8528
8531
|
modules: {
|
|
@@ -11430,7 +11433,7 @@ import {
|
|
|
11430
11433
|
// package.json
|
|
11431
11434
|
var package_default = {
|
|
11432
11435
|
name: "@lukoweb/apitogo",
|
|
11433
|
-
version: "0.2.
|
|
11436
|
+
version: "0.2.66",
|
|
11434
11437
|
type: "module",
|
|
11435
11438
|
sideEffects: [
|
|
11436
11439
|
"**/*.css",
|
|
@@ -23190,6 +23193,9 @@ function waitForJobStepLabel(kind, moduleType) {
|
|
|
23190
23193
|
return "Job";
|
|
23191
23194
|
}
|
|
23192
23195
|
}
|
|
23196
|
+
function deletionFinished(error) {
|
|
23197
|
+
return error instanceof ProjectGoneError || error instanceof PlatformApiError && error.status === 404;
|
|
23198
|
+
}
|
|
23193
23199
|
function projectDeletionNotFoundResult(last, jobId) {
|
|
23194
23200
|
return last ?? {
|
|
23195
23201
|
status: "Completed",
|
|
@@ -23251,7 +23257,7 @@ function pollProvisionJob(poll, options) {
|
|
|
23251
23257
|
stepLabel: options.stepLabel,
|
|
23252
23258
|
progress: options.progress?.child(options.stepLabel),
|
|
23253
23259
|
onPollError: options.kind === "projectDeletion" ? (error, last) => {
|
|
23254
|
-
if (error
|
|
23260
|
+
if (deletionFinished(error)) {
|
|
23255
23261
|
return {
|
|
23256
23262
|
status: "succeeded",
|
|
23257
23263
|
value: projectDeletionNotFoundResult(last, options.jobId),
|
package/dist/cli/worker.js
CHANGED
|
@@ -115,6 +115,11 @@ var ProtectedRoutesSchema = z.pipe(
|
|
|
115
115
|
|
|
116
116
|
// src/vite/prerender/utils.ts
|
|
117
117
|
var prerenderFileFor = (urlPath) => urlPath === "/" ? "/index.html" : urlPath.endsWith("/index") ? `${urlPath}/index.html` : `${urlPath}.html`;
|
|
118
|
+
var SCRIPT_SAFE_LT = `${"\\"}u003c`;
|
|
119
|
+
var redirectPageHtml = (to) => (
|
|
120
|
+
// Every `<` of the target becomes SCRIPT_SAFE_LT, so a target can never close the script early.
|
|
121
|
+
`<!doctype html><link rel="icon" href="data:,"><script>window.location.href=${JSON.stringify(to).replaceAll("<", SCRIPT_SAFE_LT)};</script>`
|
|
122
|
+
);
|
|
118
123
|
|
|
119
124
|
// src/vite/prerender/worker.ts
|
|
120
125
|
var { template, distDir, serverConfigPath, entryServerPath, writeRedirects } = Piscina.workerData;
|
|
@@ -149,7 +154,7 @@ var renderPage = async ({ urlPath }) => {
|
|
|
149
154
|
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
150
155
|
const redirectTo = response.headers.get("Location") ?? "";
|
|
151
156
|
if (writeRedirects) {
|
|
152
|
-
const redirectHtml =
|
|
157
|
+
const redirectHtml = redirectPageHtml(redirectTo);
|
|
153
158
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
154
159
|
await fs.writeFile(outputPath, redirectHtml);
|
|
155
160
|
}
|
|
@@ -3,6 +3,7 @@ import { type ApitogoConfig } from "../config.js";
|
|
|
3
3
|
import { recentJobMessages, type ProgressReporter } from "../helpers/progress.js";
|
|
4
4
|
export type WaitForJobKind = "module" | "deployment" | "azureInfrastructure" | "projectDeletion" | "devPortalDeploy" | "devPortalBackendUpdate";
|
|
5
5
|
export declare function waitForJobStepLabel(kind: WaitForJobKind, moduleType?: string): string;
|
|
6
|
+
export declare function deletionFinished(error: unknown): boolean;
|
|
6
7
|
export declare function handleActivateModule(client: PlatformApiClient, config: ApitogoConfig, args: {
|
|
7
8
|
projectId?: string;
|
|
8
9
|
moduleType: string;
|
package/package.json
CHANGED
package/src/config/v2/wrapper.ts
CHANGED
|
@@ -400,10 +400,13 @@ export function buildSiteConfig(
|
|
|
400
400
|
]
|
|
401
401
|
: []),
|
|
402
402
|
];
|
|
403
|
+
const logo = portal?.logo ? siteLogo(portal) : undefined;
|
|
403
404
|
return {
|
|
405
|
+
// The browser tab's icon is the project's logo; without one, the portal's own icon (API-134).
|
|
406
|
+
...(logo ? { metadata: { favicon: logo.src.light } } : {}),
|
|
404
407
|
site: {
|
|
405
408
|
title: portal?.name ?? resolved.project.name,
|
|
406
|
-
...(
|
|
409
|
+
...(logo ? { logo } : {}),
|
|
407
410
|
...(plan.preview
|
|
408
411
|
? { banner: { message: PREVIEW_BANNER_MESSAGE, color: "caution" } }
|
|
409
412
|
: {}),
|
|
@@ -518,9 +521,10 @@ export const PREVIEW_BANNER_MESSAGE =
|
|
|
518
521
|
* The header logo as the site takes it: a file of the portal's `public/` folder becomes its address on the site, an https
|
|
519
522
|
* address stays as it is (API-129). `check` refuses a file outside `public/`.
|
|
520
523
|
*/
|
|
521
|
-
function siteLogo(
|
|
522
|
-
|
|
523
|
-
|
|
524
|
+
function siteLogo(portal: NonNullable<ResolvedConfigV2["portal"]>): {
|
|
525
|
+
src: { light: string; dark: string };
|
|
526
|
+
alt: string;
|
|
527
|
+
} {
|
|
524
528
|
const logo = portal.logo as NonNullable<typeof portal.logo>;
|
|
525
529
|
const publicDir = path.join(portal.dir, "public");
|
|
526
530
|
const src = (file: string) =>
|
|
@@ -1,46 +1,49 @@
|
|
|
1
|
-
import { Helmet } from "@zudoku/react-helmet-async";
|
|
2
|
-
import type { PropsWithChildren } from "react";
|
|
3
|
-
import { useLocation } from "react-router";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
)}
|
|
28
|
-
{meta?.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{meta?.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{meta?.
|
|
38
|
-
<meta
|
|
39
|
-
)
|
|
40
|
-
{meta?.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
1
|
+
import { Helmet } from "@zudoku/react-helmet-async";
|
|
2
|
+
import type { PropsWithChildren } from "react";
|
|
3
|
+
import { useLocation } from "react-router";
|
|
4
|
+
// A site without its own icon would leave the browser asking for /favicon.ico, a 404 and a console error on every page
|
|
5
|
+
// (API-134). The build publishes this file with a hashed name, so no project has to carry it.
|
|
6
|
+
import defaultFavicon from "../assets/powered-by-icon.png";
|
|
7
|
+
import { joinUrl } from "../util/joinUrl.js";
|
|
8
|
+
import { useZudoku } from "./context/ZudokuContext.js";
|
|
9
|
+
|
|
10
|
+
export const Meta = ({ children }: PropsWithChildren) => {
|
|
11
|
+
const { options } = useZudoku();
|
|
12
|
+
const { metadata: meta } = options;
|
|
13
|
+
const location = useLocation();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<>
|
|
17
|
+
<Helmet titleTemplate={meta?.title} defaultTitle={meta?.defaultTitle}>
|
|
18
|
+
{options.canonicalUrlOrigin && (
|
|
19
|
+
<link
|
|
20
|
+
rel="canonical"
|
|
21
|
+
href={joinUrl(
|
|
22
|
+
options.canonicalUrlOrigin,
|
|
23
|
+
options.basePath,
|
|
24
|
+
location.pathname,
|
|
25
|
+
)}
|
|
26
|
+
/>
|
|
27
|
+
)}
|
|
28
|
+
{meta?.description && (
|
|
29
|
+
<meta name="description" content={meta.description} />
|
|
30
|
+
)}
|
|
31
|
+
<link rel="icon" href={meta?.favicon ?? defaultFavicon} />
|
|
32
|
+
{meta?.generator && <meta name="generator" content={meta.generator} />}
|
|
33
|
+
{meta?.applicationName && (
|
|
34
|
+
<meta name="application-name" content={meta.applicationName} />
|
|
35
|
+
)}
|
|
36
|
+
{meta?.referrer && <meta name="referrer" content={meta.referrer} />}
|
|
37
|
+
{meta?.keywords && meta.keywords.length > 0 && (
|
|
38
|
+
<meta name="keywords" content={meta.keywords.join(", ")} />
|
|
39
|
+
)}
|
|
40
|
+
{meta?.authors?.map((author) => (
|
|
41
|
+
<meta key={author} name="author" content={author} />
|
|
42
|
+
))}
|
|
43
|
+
{meta?.creator && <meta name="creator" content={meta.creator} />}
|
|
44
|
+
{meta?.publisher && <meta name="publisher" content={meta.publisher} />}
|
|
45
|
+
</Helmet>
|
|
46
|
+
{children}
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
@@ -87,3 +87,15 @@ export const routesToPaths = (routes: RouteObject[]): string[] =>
|
|
|
87
87
|
|
|
88
88
|
export const routesToRewrites = (routes: RouteObject[]): RouteRewrite[] =>
|
|
89
89
|
collectRewrites(resolveRoutes(routes));
|
|
90
|
+
|
|
91
|
+
/** `<` as a JavaScript string escape: a backslash, then u003c. */
|
|
92
|
+
const SCRIPT_SAFE_LT = `${"\\"}u003c`;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The page written for a route that redirects, such as `/` of a portal without a home page of its own. It declares an
|
|
96
|
+
* empty icon: without one the browser asks for /favicon.ico before the script runs, a 404 and a console error on every
|
|
97
|
+
* visit of the home page (API-134).
|
|
98
|
+
*/
|
|
99
|
+
export const redirectPageHtml = (to: string): string =>
|
|
100
|
+
// Every `<` of the target becomes SCRIPT_SAFE_LT, so a target can never close the script early.
|
|
101
|
+
`<!doctype html><link rel="icon" href="data:,"><script>window.location.href=${JSON.stringify(to).replaceAll("<", SCRIPT_SAFE_LT)};</script>`;
|
|
@@ -7,7 +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
|
+
import { prerenderFileFor, redirectPageHtml } from "./utils.js";
|
|
11
11
|
|
|
12
12
|
type EntryServer = typeof import("../../app/entry.server.js");
|
|
13
13
|
|
|
@@ -75,7 +75,7 @@ const renderPage = async ({ urlPath }: WorkerData): Promise<WorkerResult> => {
|
|
|
75
75
|
const redirectTo = response.headers.get("Location") ?? "";
|
|
76
76
|
|
|
77
77
|
if (writeRedirects) {
|
|
78
|
-
const redirectHtml =
|
|
78
|
+
const redirectHtml = redirectPageHtml(redirectTo);
|
|
79
79
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
80
80
|
await fs.writeFile(outputPath, redirectHtml);
|
|
81
81
|
}
|