@docubook/flame 1.5.4 → 1.6.1
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/.docu/lib/build.deno.js +4 -4
- package/.docu/lib/build.impl-5VMX2INF.js +12 -0
- package/.docu/lib/build.node.js +4 -4
- package/.docu/lib/{chunk-GXQB4ETN.js → chunk-2VUUBIN3.js} +2 -2
- package/.docu/lib/{chunk-J5NMYSBJ.js → chunk-4IQXHHPF.js} +5 -2
- package/.docu/lib/chunk-EINCC7ET.js +271 -0
- package/.docu/lib/{chunk-BDWN6AHQ.js → chunk-GC62TPBC.js} +4 -4
- package/.docu/lib/{chunk-C67LPMQA.js → chunk-IJFMQFWW.js} +51 -25
- package/.docu/lib/{chunk-XLH7Z4QO.js → chunk-P3ZT4S3D.js} +2 -2
- package/.docu/lib/{chunk-NYCIAZTI.js → chunk-XX2VVJMV.js} +12 -5
- package/.docu/lib/clean.js +2 -2
- package/.docu/lib/deploy.deno.js +2 -2
- package/.docu/lib/deploy.node.js +2 -2
- package/.docu/lib/preview.deno.js +3 -3
- package/.docu/lib/preview.node.js +3 -3
- package/.docu/lib/server.deno.js +4 -4
- package/.docu/lib/server.node.js +4 -4
- package/.docu/node/build.impl.ts +9 -3
- package/.docu/node/client.ts +38 -13
- package/.docu/node/deploy.shared.ts +284 -63
- package/.docu/node/deploy.ts +88 -31
- package/.docu/node/html.shared.ts +2 -0
- package/.docu/node/html.ts +4 -27
- package/.docu/node/hydrate.node.ts +74 -39
- package/.docu/node/hydrate.ts +74 -48
- package/.docu/node/paths.ts +7 -5
- package/.docu/node/server-routes.ts +2 -1
- package/bin/cli.js +19 -6
- package/package.json +4 -4
- package/.docu/lib/build.impl-CPFOQ7JW.js +0 -12
- package/.docu/lib/chunk-VKMZLLG5.js +0 -79
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runPreview
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-P3ZT4S3D.js";
|
|
4
4
|
import "./chunk-EOK6KATZ.js";
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-2VUUBIN3.js";
|
|
6
|
+
import "./chunk-4IQXHHPF.js";
|
|
7
7
|
|
|
8
8
|
// .docu/node/preview.node.ts
|
|
9
9
|
import { nodeAdapter } from "@docubook/runt";
|
package/.docu/lib/server.deno.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runServer
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-GC62TPBC.js";
|
|
4
|
+
import "./chunk-IJFMQFWW.js";
|
|
5
5
|
import "./chunk-EOK6KATZ.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-2VUUBIN3.js";
|
|
7
|
+
import "./chunk-4IQXHHPF.js";
|
|
8
8
|
|
|
9
9
|
// .docu/node/server.deno.ts
|
|
10
10
|
import { denoAdapter } from "@docubook/runt";
|
package/.docu/lib/server.node.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runServer
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-GC62TPBC.js";
|
|
4
|
+
import "./chunk-IJFMQFWW.js";
|
|
5
5
|
import "./chunk-EOK6KATZ.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-2VUUBIN3.js";
|
|
7
|
+
import "./chunk-4IQXHHPF.js";
|
|
8
8
|
|
|
9
9
|
// .docu/node/server.node.ts
|
|
10
10
|
import { nodeAdapter } from "@docubook/runt";
|
package/.docu/node/build.impl.ts
CHANGED
|
@@ -31,7 +31,7 @@ import { loadPlugins } from "./plugin-loader";
|
|
|
31
31
|
import { BuildPluginBuilder } from "./plugin-builder";
|
|
32
32
|
import { scanMdxFiles } from "./utils";
|
|
33
33
|
import type { BuildCache, CliArgs } from "./types";
|
|
34
|
-
import { generateNonce } from "./security";
|
|
34
|
+
import { generateNonce, cspHeader } from "./security";
|
|
35
35
|
import type { PageMeta, PageContext } from "./plugin";
|
|
36
36
|
import { buildSeoMeta } from "./seo";
|
|
37
37
|
import DocsPage from "../pages/docs/[[...slug]]";
|
|
@@ -149,12 +149,14 @@ async function renderDocsPage(
|
|
|
149
149
|
const depth = slug ? slug.split("/").length : 1;
|
|
150
150
|
const favicon = docuConfig.meta?.favicon || "/docs/assets/images/favicon.ico";
|
|
151
151
|
const seo = buildSeoMeta(docuConfig, frontmatter, slug || "");
|
|
152
|
+
const csp = cspHeader(nonce, process.env.NODE_ENV !== "production");
|
|
152
153
|
let html = htmlShell({
|
|
153
154
|
title,
|
|
154
155
|
description,
|
|
155
156
|
body,
|
|
156
157
|
favicon,
|
|
157
158
|
seo,
|
|
159
|
+
csp,
|
|
158
160
|
css: assetManifest.css,
|
|
159
161
|
js: assetManifest.js,
|
|
160
162
|
nonce,
|
|
@@ -349,15 +351,17 @@ export async function runBuild(): Promise<void> {
|
|
|
349
351
|
const landingPage = React.createElement(IndexPage);
|
|
350
352
|
const landingFavicon = docuConfig.meta?.favicon || "/docs/assets/images/favicon.ico";
|
|
351
353
|
const landingSeo = buildSeoMeta(docuConfig, docuConfig.meta as Record<string, unknown>, "");
|
|
354
|
+
const landingNonce = generateNonce();
|
|
352
355
|
const landingHtml = htmlShell({
|
|
353
356
|
title: docuConfig.meta?.title || "DocuBook",
|
|
354
357
|
description: docuConfig.meta?.description || "",
|
|
355
358
|
body: renderToString(landingPage),
|
|
356
359
|
favicon: landingFavicon,
|
|
357
360
|
seo: landingSeo,
|
|
361
|
+
csp: cspHeader(landingNonce, process.env.NODE_ENV !== "production"),
|
|
358
362
|
css: assetManifest.css,
|
|
359
363
|
js: assetManifest.js,
|
|
360
|
-
nonce:
|
|
364
|
+
nonce: landingNonce,
|
|
361
365
|
themeCss: inlineThemeCss,
|
|
362
366
|
});
|
|
363
367
|
await writeFile(join(DIST_DIR, "index.html"), landingHtml);
|
|
@@ -368,15 +372,17 @@ export async function runBuild(): Promise<void> {
|
|
|
368
372
|
React.createElement(NotFoundPage)
|
|
369
373
|
);
|
|
370
374
|
const notFoundFavicon = docuConfig.meta?.favicon || "/docs/assets/images/favicon.ico";
|
|
375
|
+
const notFoundNonce = generateNonce();
|
|
371
376
|
const notFoundHtml = htmlShell({
|
|
372
377
|
title: "404 - Not Found",
|
|
373
378
|
description: "",
|
|
374
379
|
body: renderToString(notFoundPage),
|
|
375
380
|
favicon: notFoundFavicon,
|
|
376
381
|
headExtra: ['<meta name="robots" content="noindex,follow">'],
|
|
382
|
+
csp: cspHeader(notFoundNonce, process.env.NODE_ENV !== "production"),
|
|
377
383
|
css: assetManifest.css,
|
|
378
384
|
js: assetManifest.js,
|
|
379
|
-
nonce:
|
|
385
|
+
nonce: notFoundNonce,
|
|
380
386
|
themeCss: inlineThemeCss,
|
|
381
387
|
});
|
|
382
388
|
await writeFile(join(DIST_DIR, "404.html"), notFoundHtml);
|
package/.docu/node/client.ts
CHANGED
|
@@ -25,6 +25,8 @@ function mountIsland(
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function mountIslands() {
|
|
28
|
+
// forceCreate: SSR sidebar renders <Menu> only; client renders full <Sidebar>
|
|
29
|
+
// (DesktopSidebar + MobileBar) — structure mismatch forces full createRoot.
|
|
28
30
|
mountIsland(
|
|
29
31
|
"sidebar-island",
|
|
30
32
|
(el) => {
|
|
@@ -38,18 +40,16 @@ function mountIslands() {
|
|
|
38
40
|
true
|
|
39
41
|
);
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
true
|
|
52
|
-
);
|
|
43
|
+
// mobile-bar-island SSR div is empty (data attributes only),
|
|
44
|
+
// so hydrateRoot child check falls through to createRoot automatically.
|
|
45
|
+
mountIsland("mobile-bar-island", (el) => {
|
|
46
|
+
const tocs: TocItem[] = safeParseTocs(el.dataset.tocs);
|
|
47
|
+
return React.createElement(MobileBar, {
|
|
48
|
+
tocs,
|
|
49
|
+
title: el.dataset.title || "",
|
|
50
|
+
repoUrl: el.dataset.repo || "",
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
53
|
|
|
54
54
|
mountIsland("toc-island", (el) => {
|
|
55
55
|
const tocs: TocItem[] = safeParseTocs(el.dataset.tocs);
|
|
@@ -69,7 +69,8 @@ function hydrateMdxContent() {
|
|
|
69
69
|
try {
|
|
70
70
|
const compiledSource = JSON.parse(sourceEl.textContent || "");
|
|
71
71
|
const components = createMdxComponents();
|
|
72
|
-
|
|
72
|
+
hydrateRoot(
|
|
73
|
+
island,
|
|
73
74
|
React.createElement(MDXRemote, { compiledSource, scope: {}, frontmatter: {}, components })
|
|
74
75
|
);
|
|
75
76
|
} catch (e) {
|
|
@@ -82,3 +83,27 @@ if (document.readyState === "loading") {
|
|
|
82
83
|
} else {
|
|
83
84
|
mountIslands();
|
|
84
85
|
}
|
|
86
|
+
|
|
87
|
+
// ── Hash scroll compensation ──────────────────────────────────────
|
|
88
|
+
// After hydration, lazy-rendered content (Mermaid via IntersectionObserver) can
|
|
89
|
+
// shift layout and push the hash target (#section-2) off-screen.
|
|
90
|
+
// Poll with rAF for ~1s and re-scroll if the target is below viewport.
|
|
91
|
+
function scrollToHashOnLoad() {
|
|
92
|
+
const hash = window.location.hash;
|
|
93
|
+
if (!hash || hash === "#") return;
|
|
94
|
+
const id = hash.slice(1);
|
|
95
|
+
const deadline = performance.now() + 1000;
|
|
96
|
+
function tick() {
|
|
97
|
+
const el = document.getElementById(id);
|
|
98
|
+
if (el) {
|
|
99
|
+
const top = el.getBoundingClientRect().top;
|
|
100
|
+
if (top <= window.innerHeight - 100 && top >= 0) return; // already in view
|
|
101
|
+
el.scrollIntoView();
|
|
102
|
+
return; // scrolled once, done
|
|
103
|
+
}
|
|
104
|
+
if (performance.now() < deadline) requestAnimationFrame(tick);
|
|
105
|
+
}
|
|
106
|
+
requestAnimationFrame(tick);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
scrollToHashOnLoad();
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Runtime-neutral deploy —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Runtime-neutral deploy — 3 modes:
|
|
3
|
+
* 1. `flame deploy` → build + generate GitHub Actions workflow
|
|
4
|
+
* 2. `flame deploy --docker` → build + generate Docker deployment files
|
|
5
|
+
* 3. `flame deploy --docker --silent` → same as #2, minimal output
|
|
6
|
+
*
|
|
7
|
+
* Mirror of deploy.ts (Bun-only, protected) for Node.js and Deno.
|
|
8
|
+
* Runs the neutral build in-process, then prepares .docu/dist.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
import { writeFile, mkdir } from "node:fs/promises";
|
|
@@ -12,74 +16,291 @@ import { DIST_DIR, PROJECT_ROOT } from "./paths";
|
|
|
12
16
|
const WORKFLOW_DIR = join(PROJECT_ROOT, ".github/workflows");
|
|
13
17
|
const WORKFLOW_FILE = join(WORKFLOW_DIR, "deploy.yml");
|
|
14
18
|
|
|
15
|
-
export
|
|
16
|
-
|
|
19
|
+
export const NGINX_CONF = `server {
|
|
20
|
+
listen 80;
|
|
21
|
+
server_name _;
|
|
22
|
+
root /usr/share/nginx/html;
|
|
23
|
+
index index.html;
|
|
24
|
+
|
|
25
|
+
gzip on;
|
|
26
|
+
gzip_types text/html text/css application/javascript image/svg+xml;
|
|
27
|
+
|
|
28
|
+
# Security headers (HSTS effective when HTTPS is terminated upstream)
|
|
29
|
+
add_header X-Frame-Options "DENY" always;
|
|
30
|
+
add_header X-Content-Type-Options "nosniff" always;
|
|
31
|
+
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
|
|
32
|
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
33
|
+
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
|
34
|
+
|
|
35
|
+
location /assets/ {
|
|
36
|
+
expires 1y;
|
|
37
|
+
add_header Cache-Control "public, immutable";
|
|
38
|
+
add_header X-Frame-Options "DENY" always;
|
|
39
|
+
add_header X-Content-Type-Options "nosniff" always;
|
|
40
|
+
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
|
|
41
|
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
42
|
+
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
location /docs/assets/ {
|
|
46
|
+
expires 7d;
|
|
47
|
+
add_header Cache-Control "public";
|
|
48
|
+
add_header X-Frame-Options "DENY" always;
|
|
49
|
+
add_header X-Content-Type-Options "nosniff" always;
|
|
50
|
+
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
|
|
51
|
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
52
|
+
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
location / {
|
|
56
|
+
try_files $uri $uri.html $uri/ =404;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
export const DOCKERIGNORE = `node_modules
|
|
62
|
+
.git
|
|
63
|
+
*.DS_Store
|
|
64
|
+
.docu/dist
|
|
65
|
+
.docu/lib
|
|
66
|
+
.env
|
|
67
|
+
.env.*
|
|
68
|
+
.npmrc
|
|
69
|
+
*.log
|
|
70
|
+
`;
|
|
17
71
|
|
|
72
|
+
export function detectPkgManager(dir: string): {
|
|
73
|
+
baseImage: string;
|
|
74
|
+
lockFile: string;
|
|
75
|
+
installCmd: string;
|
|
76
|
+
runCmd: string;
|
|
77
|
+
cache: string;
|
|
78
|
+
setupAction: string;
|
|
79
|
+
} {
|
|
80
|
+
const bunLockFile = existsSync(join(dir, "bun.lock"))
|
|
81
|
+
? "bun.lock"
|
|
82
|
+
: existsSync(join(dir, "bun.lockb"))
|
|
83
|
+
? "bun.lockb"
|
|
84
|
+
: null;
|
|
85
|
+
if (bunLockFile) {
|
|
86
|
+
return {
|
|
87
|
+
baseImage: "oven/bun:1",
|
|
88
|
+
lockFile: bunLockFile,
|
|
89
|
+
installCmd: "bun install --frozen-lockfile",
|
|
90
|
+
runCmd: "bun",
|
|
91
|
+
cache: "",
|
|
92
|
+
setupAction: "bun",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (existsSync(join(dir, "pnpm-lock.yaml"))) {
|
|
96
|
+
return {
|
|
97
|
+
baseImage: "node:22-alpine",
|
|
98
|
+
lockFile: "pnpm-lock.yaml",
|
|
99
|
+
installCmd: "corepack enable && pnpm install --frozen-lockfile",
|
|
100
|
+
runCmd: "pnpm",
|
|
101
|
+
cache: "pnpm",
|
|
102
|
+
setupAction: "pnpm",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (existsSync(join(dir, "yarn.lock"))) {
|
|
106
|
+
return {
|
|
107
|
+
baseImage: "node:22-alpine",
|
|
108
|
+
lockFile: "yarn.lock",
|
|
109
|
+
installCmd: "yarn install --frozen-lockfile",
|
|
110
|
+
runCmd: "yarn",
|
|
111
|
+
cache: "yarn",
|
|
112
|
+
setupAction: "node",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// default: npm
|
|
116
|
+
return {
|
|
117
|
+
baseImage: "node:22-alpine",
|
|
118
|
+
lockFile: "package-lock.json",
|
|
119
|
+
installCmd: "npm ci",
|
|
120
|
+
runCmd: "npm",
|
|
121
|
+
cache: "npm",
|
|
122
|
+
setupAction: "node",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const HEADERS_FILE = `/*
|
|
127
|
+
X-Frame-Options: DENY
|
|
128
|
+
X-Content-Type-Options: nosniff
|
|
129
|
+
Referrer-Policy: strict-origin-when-cross-origin
|
|
130
|
+
|
|
131
|
+
/assets/*
|
|
132
|
+
Cache-Control: public, max-age=31536000, immutable
|
|
133
|
+
`;
|
|
134
|
+
|
|
135
|
+
const isDocker = !!process.env.FLAME_DEPLOY_DOCKER;
|
|
136
|
+
const isSilent = !!process.env.FLAME_DEPLOY_SILENT;
|
|
137
|
+
|
|
138
|
+
/** Logger that no-ops all non-error output in silent mode. */
|
|
139
|
+
const log = isSilent
|
|
140
|
+
? { info: () => {}, ok: () => {}, created: () => {}, out: () => {} }
|
|
141
|
+
: {
|
|
142
|
+
info: (m: string) => console.log(m),
|
|
143
|
+
ok: () => console.log("\n✅ Ready to deploy!"),
|
|
144
|
+
created: (m: string) => console.log(m),
|
|
145
|
+
out: (m: string) => console.log(m),
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
async function runBuild() {
|
|
18
149
|
process.env.NODE_ENV = "production";
|
|
150
|
+
if (isSilent) {
|
|
151
|
+
process.env.FLAME_BUILD_SILENT = "1";
|
|
152
|
+
process.env.LOG_LEVEL = "error";
|
|
153
|
+
}
|
|
19
154
|
const { runBuildCli } = await import("./build.impl");
|
|
20
155
|
await runBuildCli();
|
|
156
|
+
}
|
|
21
157
|
|
|
22
|
-
|
|
23
|
-
|
|
158
|
+
async function writeDockerFiles() {
|
|
159
|
+
const dockerDir = PROJECT_ROOT;
|
|
160
|
+
const pm = detectPkgManager(dockerDir);
|
|
161
|
+
|
|
162
|
+
if (!existsSync(join(dockerDir, "Dockerfile"))) {
|
|
163
|
+
await writeFile(
|
|
164
|
+
join(dockerDir, "Dockerfile"),
|
|
165
|
+
`FROM ${pm.baseImage} AS builder
|
|
166
|
+
WORKDIR /app
|
|
167
|
+
COPY package.json ${pm.lockFile} ./
|
|
168
|
+
RUN ${pm.installCmd}
|
|
169
|
+
COPY . .
|
|
170
|
+
RUN ${pm.runCmd} run build
|
|
171
|
+
|
|
172
|
+
FROM nginx:alpine
|
|
173
|
+
COPY --from=builder /app/.docu/dist /usr/share/nginx/html
|
|
174
|
+
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
175
|
+
USER nginx
|
|
176
|
+
EXPOSE 80
|
|
177
|
+
CMD ["nginx", "-g", "daemon off;"]
|
|
178
|
+
`
|
|
179
|
+
);
|
|
180
|
+
log.created("📄 Created Dockerfile");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!existsSync(join(dockerDir, "nginx.conf"))) {
|
|
184
|
+
await writeFile(join(dockerDir, "nginx.conf"), NGINX_CONF);
|
|
185
|
+
log.created("📄 Created nginx.conf");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!existsSync(join(dockerDir, ".dockerignore"))) {
|
|
189
|
+
await writeFile(join(dockerDir, ".dockerignore"), DOCKERIGNORE);
|
|
190
|
+
log.created("📄 Created .dockerignore");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function generateWorkflowYml(): string {
|
|
195
|
+
const pm = detectPkgManager(PROJECT_ROOT);
|
|
196
|
+
|
|
197
|
+
const setupSteps: string[] = [
|
|
198
|
+
" - uses: actions/checkout@v7",
|
|
199
|
+
" with:",
|
|
200
|
+
" fetch-depth: 0",
|
|
201
|
+
"",
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
if (pm.setupAction === "bun") {
|
|
205
|
+
setupSteps.push(
|
|
206
|
+
" - uses: oven-sh/setup-bun@v2",
|
|
207
|
+
" with:",
|
|
208
|
+
" bun-version: latest",
|
|
209
|
+
""
|
|
210
|
+
);
|
|
211
|
+
} else if (pm.setupAction === "pnpm") {
|
|
212
|
+
setupSteps.push(
|
|
213
|
+
" - uses: pnpm/action-setup@v6",
|
|
214
|
+
"",
|
|
215
|
+
" - uses: actions/setup-node@v6",
|
|
216
|
+
" with:",
|
|
217
|
+
" node-version: 22",
|
|
218
|
+
` cache: ${pm.cache}`,
|
|
219
|
+
""
|
|
220
|
+
);
|
|
221
|
+
} else {
|
|
222
|
+
// npm / yarn
|
|
223
|
+
setupSteps.push(
|
|
224
|
+
" - uses: actions/setup-node@v6",
|
|
225
|
+
" with:",
|
|
226
|
+
" node-version: 22",
|
|
227
|
+
` cache: ${pm.cache}`,
|
|
228
|
+
""
|
|
229
|
+
);
|
|
230
|
+
}
|
|
24
231
|
|
|
25
|
-
|
|
232
|
+
return [
|
|
233
|
+
`name: Deploy to GitHub Pages`,
|
|
234
|
+
"",
|
|
235
|
+
"on:",
|
|
236
|
+
" push:",
|
|
237
|
+
" branches: [main]",
|
|
238
|
+
" workflow_dispatch:",
|
|
239
|
+
"",
|
|
240
|
+
"permissions:",
|
|
241
|
+
" contents: read",
|
|
242
|
+
" pages: write",
|
|
243
|
+
" id-token: write",
|
|
244
|
+
"",
|
|
245
|
+
"concurrency:",
|
|
246
|
+
' group: "pages"',
|
|
247
|
+
" cancel-in-progress: false",
|
|
248
|
+
"",
|
|
249
|
+
"jobs:",
|
|
250
|
+
" build:",
|
|
251
|
+
" runs-on: ubuntu-latest",
|
|
252
|
+
" steps:",
|
|
253
|
+
...setupSteps,
|
|
254
|
+
` - run: ${pm.installCmd}`,
|
|
255
|
+
"",
|
|
256
|
+
` - run: ${pm.runCmd} run build`,
|
|
257
|
+
"",
|
|
258
|
+
" - name: Add .nojekyll",
|
|
259
|
+
" run: touch .docu/dist/.nojekyll",
|
|
260
|
+
"",
|
|
261
|
+
" - uses: actions/upload-pages-artifact@v3",
|
|
262
|
+
" with:",
|
|
263
|
+
" path: .docu/dist",
|
|
264
|
+
"",
|
|
265
|
+
" deploy:",
|
|
266
|
+
" environment:",
|
|
267
|
+
" name: github-pages",
|
|
268
|
+
" url: ${{ steps.deployment.outputs.page_url }}",
|
|
269
|
+
" runs-on: ubuntu-latest",
|
|
270
|
+
" needs: build",
|
|
271
|
+
" steps:",
|
|
272
|
+
" - id: deployment",
|
|
273
|
+
" uses: actions/deploy-pages@v4",
|
|
274
|
+
].join("\n");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function writeGhaWorkflow() {
|
|
26
278
|
if (!existsSync(WORKFLOW_FILE)) {
|
|
27
279
|
await mkdir(WORKFLOW_DIR, { recursive: true });
|
|
28
|
-
await writeFile(WORKFLOW_FILE,
|
|
29
|
-
|
|
280
|
+
await writeFile(WORKFLOW_FILE, generateWorkflowYml());
|
|
281
|
+
log.created("📄 Created .github/workflows/deploy.yml");
|
|
30
282
|
}
|
|
31
|
-
|
|
32
|
-
console.log("\n✅ Ready to deploy!");
|
|
33
|
-
console.log(" Output: .docu/dist/");
|
|
34
|
-
console.log(" Push to GitHub and enable Pages (Settings → Pages → Source: GitHub Actions)");
|
|
35
283
|
}
|
|
36
284
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
fetch-depth: 0
|
|
60
|
-
|
|
61
|
-
- uses: actions/setup-node@v4
|
|
62
|
-
with:
|
|
63
|
-
node-version: 22
|
|
64
|
-
|
|
65
|
-
- run: npm install
|
|
66
|
-
|
|
67
|
-
- run: npm run build
|
|
68
|
-
|
|
69
|
-
- name: Add .nojekyll
|
|
70
|
-
run: touch .docu/dist/.nojekyll
|
|
71
|
-
|
|
72
|
-
- uses: actions/upload-pages-artifact@v3
|
|
73
|
-
with:
|
|
74
|
-
path: .docu/dist
|
|
75
|
-
|
|
76
|
-
deploy:
|
|
77
|
-
environment:
|
|
78
|
-
name: github-pages
|
|
79
|
-
url: \${{ steps.deployment.outputs.page_url }}
|
|
80
|
-
runs-on: ubuntu-latest
|
|
81
|
-
needs: build
|
|
82
|
-
steps:
|
|
83
|
-
- id: deployment
|
|
84
|
-
uses: actions/deploy-pages@v4
|
|
85
|
-
`;
|
|
285
|
+
export async function runDeploy(): Promise<void> {
|
|
286
|
+
log.info("📦 Building for production...\n");
|
|
287
|
+
await runBuild();
|
|
288
|
+
|
|
289
|
+
// Common: .nojekyll + _headers
|
|
290
|
+
await writeFile(join(DIST_DIR, ".nojekyll"), "");
|
|
291
|
+
await writeFile(join(DIST_DIR, "_headers"), HEADERS_FILE);
|
|
292
|
+
|
|
293
|
+
if (isDocker) {
|
|
294
|
+
await writeDockerFiles();
|
|
295
|
+
} else {
|
|
296
|
+
await writeGhaWorkflow();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
log.ok();
|
|
300
|
+
log.out(" Output: .docu/dist/");
|
|
301
|
+
if (isDocker) {
|
|
302
|
+
log.out(" Run: docker build -t my-docs . && docker run -p 80:80 my-docs");
|
|
303
|
+
} else {
|
|
304
|
+
log.out(" Push to GitHub and enable Pages (Settings → Pages → Source: GitHub Actions)");
|
|
305
|
+
}
|
|
306
|
+
}
|