@docubook/flame 1.5.4 → 1.6.0

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.
@@ -58,10 +58,42 @@ function resolveTailwindBin(): string {
58
58
  return join(dirname(pkgPath), binRel);
59
59
  }
60
60
 
61
- /** Run Tailwind CLI to produce minified CSS. */
62
- async function runTailwind(outputCss: string): Promise<void> {
61
+ /** Compute a cache key from globals.css + theme config content. */
62
+ function tailwindCacheKey(): string {
63
+ const globalsPath = join(STYLES_DIR, "globals.css");
64
+ const globalsContent = existsSync(globalsPath) ? readFileSync(globalsPath, "utf-8") : "";
65
+ let themeSuffix = "";
66
+ try {
67
+ const themeColors = getThemeConfig();
68
+ if (themeColors) {
69
+ themeSuffix = JSON.stringify(themeColors);
70
+ }
71
+ } catch {
72
+ // theme config unavailable — proceed without it
73
+ }
74
+ return createHash("md5")
75
+ .update(globalsContent + themeSuffix)
76
+ .digest("hex")
77
+ .slice(0, 16);
78
+ }
79
+
80
+ /**
81
+ * Build Tailwind CSS with content-based caching.
82
+ * If a CSS file for the current input hash already exists, skip the subprocess.
83
+ * Returns the filename (e.g. "client-abc123.css") and CSS content.
84
+ */
85
+ async function buildTailwindCss(key: string): Promise<{ file: string; content: string }> {
86
+ const cachedFile = `client-${key}.css`;
87
+ const cachedPath = join(ASSETS_DIR, cachedFile);
88
+
89
+ if (existsSync(cachedPath)) {
90
+ const content = readFileSync(cachedPath, "utf-8");
91
+ return { file: cachedFile, content };
92
+ }
93
+
94
+ const tmpCss = join(ASSETS_DIR, `_tmp-${key}.css`);
63
95
  const bin = resolveTailwindBin();
64
- const twArgs = ["-i", join(STYLES_DIR, "globals.css"), "-o", outputCss, "--minify"];
96
+ const twArgs = ["-i", join(STYLES_DIR, "globals.css"), "-o", tmpCss, "--minify"];
65
97
  const isDeno = "Deno" in globalThis;
66
98
  const args = isDeno ? ["run", "-A", bin, ...twArgs] : [bin, ...twArgs];
67
99
  try {
@@ -70,6 +102,31 @@ async function runTailwind(outputCss: string): Promise<void> {
70
102
  const stderr = (err as { stderr?: string }).stderr ?? String(err);
71
103
  throw new Error(`Tailwind CSS build failed:\n${stderr}`, { cause: err });
72
104
  }
105
+
106
+ let cssContent = await readFile(tmpCss, "utf-8");
107
+ await unlink(tmpCss);
108
+
109
+ try {
110
+ const themeColors = getThemeConfig();
111
+ if (themeColors) {
112
+ cssContent = buildThemeCss(cssContent, themeColors);
113
+ }
114
+ } catch (err) {
115
+ console.warn(
116
+ `[flame] Failed to resolve theme config, falling back to globals.css only: ${err instanceof Error ? err.message : String(err)}`
117
+ );
118
+ }
119
+
120
+ // Use the same input-derived key for lookup and output — if inputs change,
121
+ // the key changes, cache busting works without a separate content hash.
122
+ const cssFile = `client-${key}.css`;
123
+ const outPath = join(ASSETS_DIR, cssFile);
124
+
125
+ if (!existsSync(outPath)) {
126
+ await writeFile(outPath, cssContent);
127
+ }
128
+
129
+ return { file: cssFile, content: cssContent };
73
130
  }
74
131
 
75
132
  const NODE_BUILTINS_RE = new RegExp(
@@ -111,8 +168,10 @@ function scanDirLucideIcons(dir: string, set: Set<string>): void {
111
168
  }
112
169
  }
113
170
  }
114
- } catch {
115
- /* skip unreadable dirs */
171
+ } catch (err) {
172
+ console.warn(
173
+ `[flame] Failed to scan lucide icons: ${err instanceof Error ? err.message : String(err)}`
174
+ );
116
175
  }
117
176
  }
118
177
 
@@ -138,7 +197,8 @@ function collectAllLucideIcons(): string[] {
138
197
  /** Build the client JS bundle and Tailwind CSS. */
139
198
  export async function buildClientBundle(): Promise<{ js: string; css: string }> {
140
199
  await mkdir(ASSETS_DIR, { recursive: true });
141
- await cleanOldBundles();
200
+ const twKey = tailwindCacheKey();
201
+ await cleanOldBundles(new Set([`client-${twKey}.css`]));
142
202
 
143
203
  const nodeEnv = process.env.NODE_ENV || "development";
144
204
  const esbuild = await import("esbuild");
@@ -153,10 +213,8 @@ export async function buildClientBundle(): Promise<{ js: string; css: string }>
153
213
  bundle: true,
154
214
  outdir: ASSETS_DIR,
155
215
  entryNames: "client-[hash]",
156
- chunkNames: "chunks/[name]-[hash]",
157
216
  platform: "browser",
158
217
  format: "esm",
159
- splitting: true,
160
218
  minify: nodeEnv === "production",
161
219
  define: { "process.env.NODE_ENV": JSON.stringify(nodeEnv) },
162
220
  jsx: "automatic",
@@ -185,17 +243,17 @@ export async function buildClientBundle(): Promise<{ js: string; css: string }>
185
243
  if (args.namespace === "lucide-virt") {
186
244
  return { path: getLucideRealEntry(), namespace: "file" };
187
245
  }
188
- // Files that do dynamic name lookups (namespace import)
189
- // need the full barrel — bypass the virtual module.
246
+ // mdx-content Icon.tsx uses namespace import for arbitrary
247
+ // user-provided icon names in MDX — keep full barrel there.
190
248
  if (args.importer) {
191
249
  const normalized = normalizeImporterPath(args.importer);
192
- if (
193
- normalized.endsWith("/.docu/components/Lucide.tsx") ||
194
- normalized.includes("/mdx-content/dist/")
195
- ) {
250
+ if (normalized.includes("/mdx-content/dist/")) {
196
251
  return { path: getLucideRealEntry(), namespace: "file" };
197
252
  }
198
253
  }
254
+ // All other files get tree-shaken via the virtual module.
255
+ // Lucide.tsx renders only config-defined icons, which are
256
+ // collected by extractConfigIcons() + collectAllLucideIcons().
199
257
  return { path: args.path, namespace: "lucide-virt" };
200
258
  });
201
259
  build.onLoad({ filter: /.*/, namespace: "lucide-virt" }, () => {
@@ -226,7 +284,6 @@ export async function buildClientBundle(): Promise<{ js: string; css: string }>
226
284
  });
227
285
  },
228
286
  },
229
-
230
287
  ],
231
288
  });
232
289
  } finally {
@@ -235,10 +292,7 @@ export async function buildClientBundle(): Promise<{ js: string; css: string }>
235
292
  await esbuild.stop();
236
293
  }
237
294
 
238
- // With splitting enabled esbuild emits the entry plus shared/dynamic chunks.
239
- // `entryPoint` is set on the user entry AND on every dynamic-import chunk
240
- // (esbuild treats dynamic imports as entry points), so match the resolved
241
- // source path instead of grabbing the first truthy `entryPoint`.
295
+ // The single entry produces one output. Match resolved source path.
242
296
  const { outputs } = result.metafile!;
243
297
  const jsOutput = Object.keys(outputs).find((p) => {
244
298
  const o = outputs[p];
@@ -249,26 +303,7 @@ export async function buildClientBundle(): Promise<{ js: string; css: string }>
249
303
  }
250
304
  const jsFile = basename(jsOutput);
251
305
 
252
- const tmpCss = join(ASSETS_DIR, "_tmp.css");
253
- await runTailwind(tmpCss);
254
-
255
- let cssContent = await readFile(tmpCss, "utf-8");
256
-
257
- try {
258
- const themeColors = getThemeConfig();
259
- if (themeColors) {
260
- cssContent = buildThemeCss(cssContent, themeColors);
261
- }
262
- } catch (err) {
263
- console.warn(
264
- `[flame] Failed to resolve theme config, falling back to globals.css only: ${err instanceof Error ? err.message : String(err)}`
265
- );
266
- }
267
-
268
- const cssHash = createHash("md5").update(cssContent).digest("hex").slice(0, 8);
269
- const cssFile = `client-${cssHash}.css`;
270
- await writeFile(join(ASSETS_DIR, cssFile), cssContent);
271
- await unlink(tmpCss);
306
+ const { file: cssFile } = await buildTailwindCss(twKey);
272
307
 
273
308
  await writeFile(join(ASSETS_DIR, "manifest.json"), JSON.stringify({ js: jsFile, css: cssFile }));
274
309
 
@@ -1,5 +1,7 @@
1
1
  import { join } from "node:path";
2
2
  import { mkdir, unlink } from "node:fs/promises";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { createHash } from "node:crypto";
3
5
  import { resolveTheme, generateThemeCss, presetRegistry } from "@docubook/themes-colors";
4
6
  import { ASSETS_DIR, cleanOldBundles, LIB_DIR, STYLES_DIR, loadDocuConfig } from "./paths";
5
7
  import { resolveRoutes } from "./fs-scanner";
@@ -55,24 +57,86 @@ export function computeInlineThemeCss(): string | undefined {
55
57
  return undefined;
56
58
  }
57
59
 
60
+ /** Compute Tailwind cache key from globals.css + theme config. */
61
+ function twCacheKey(): string {
62
+ const globalsPath = join(STYLES_DIR, "globals.css");
63
+ const globals = existsSync(globalsPath) ? readFileSync(globalsPath, "utf-8") : "";
64
+ let themeSuffix = "";
65
+ try {
66
+ const themeColors = getThemeConfig();
67
+ if (themeColors) themeSuffix = JSON.stringify(themeColors);
68
+ } catch {
69
+ // theme config unavailable — proceed without
70
+ }
71
+ return createHash("md5")
72
+ .update(globals + themeSuffix)
73
+ .digest("hex")
74
+ .slice(0, 16);
75
+ }
76
+
77
+ /** Run Tailwind CLI, caching by content hash. */
78
+ async function buildTailwindCss(key: string): Promise<{ file: string; content: string }> {
79
+ const cachedFile = `client-${key}.css`;
80
+ const cachedPath = join(ASSETS_DIR, cachedFile);
81
+
82
+ if (existsSync(cachedPath)) {
83
+ const content = await Bun.file(cachedPath).text();
84
+ return { file: cachedFile, content };
85
+ }
86
+
87
+ const tmpCss = join(ASSETS_DIR, `_tmp-${key}.css`);
88
+ const proc = Bun.spawn(
89
+ [
90
+ "bun",
91
+ "x",
92
+ "@tailwindcss/cli",
93
+ "-i",
94
+ join(STYLES_DIR, "globals.css"),
95
+ "-o",
96
+ tmpCss,
97
+ "--minify",
98
+ ],
99
+ { stdout: "ignore", stderr: "pipe" }
100
+ );
101
+ await proc.exited;
102
+ if (proc.exitCode !== 0) {
103
+ const err = await new Response(proc.stderr).text();
104
+ throw new Error(`Tailwind CSS build failed:\n${err}`);
105
+ }
106
+
107
+ let cssContent = await Bun.file(tmpCss).text();
108
+ await unlink(tmpCss);
109
+
110
+ try {
111
+ const themeColors = getThemeConfig();
112
+ if (themeColors) cssContent = buildThemeCss(cssContent, themeColors);
113
+ } catch (err) {
114
+ console.warn(
115
+ `[flame] Failed to resolve theme config: ${err instanceof Error ? err.message : String(err)}`
116
+ );
117
+ }
118
+
119
+ // Use the same input-derived key for lookup and output — if inputs change,
120
+ // the key changes, cache busting works without a separate content hash.
121
+ const cssFile = `client-${key}.css`;
122
+ const outPath = join(ASSETS_DIR, cssFile);
123
+ if (!existsSync(outPath)) await Bun.write(outPath, cssContent);
124
+
125
+ return { file: cssFile, content: cssContent };
126
+ }
127
+
58
128
  export async function buildClientBundle(): Promise<{ js: string; css: string }> {
59
129
  await mkdir(ASSETS_DIR, { recursive: true });
60
- await cleanOldBundles();
130
+ const twKey = twCacheKey();
131
+ await cleanOldBundles(new Set([`client-${twKey}.css`]));
61
132
 
62
133
  const nodeEnv = process.env.NODE_ENV || "development";
63
134
  const result = await Bun.build({
64
135
  entrypoints: [join(LIB_DIR, "client.ts")],
65
136
  outdir: ASSETS_DIR,
66
- format: "esm",
67
- splitting: true,
68
- naming: {
69
- entry: "client-[hash].[ext]",
70
- chunk: "chunks/[name]-[hash].[ext]",
71
- asset: "[name]-[hash].[ext]",
72
- },
137
+ naming: "client-[hash].[ext]",
73
138
  target: "browser",
74
139
  minify: nodeEnv === "production",
75
- optimizeImports: ["lucide-react"],
76
140
  define: { "process.env.NODE_ENV": JSON.stringify(nodeEnv) },
77
141
  plugins: [
78
142
  {
@@ -92,7 +156,6 @@ export async function buildClientBundle(): Promise<{ js: string; css: string }>
92
156
  });
93
157
  },
94
158
  },
95
-
96
159
  ],
97
160
  });
98
161
 
@@ -104,50 +167,13 @@ export async function buildClientBundle(): Promise<{ js: string; css: string }>
104
167
  if (!result.outputs[0]) {
105
168
  throw new Error("Client bundle produced no output files");
106
169
  }
107
- // With splitting enabled Bun emits entry + chunk artifacts; select the entry
108
- // explicitly rather than by position (chunks may precede it in the array).
109
170
  const entry = result.outputs.find((o) => o.kind === "entry-point");
110
171
  if (!entry) {
111
172
  throw new Error("Client bundle produced no entry-point output");
112
173
  }
113
174
  const jsFile = entry.path.split("/").pop()!;
114
- const tmpCss = join(ASSETS_DIR, "_tmp.css");
115
- const proc = Bun.spawn(
116
- [
117
- "bun",
118
- "x",
119
- "@tailwindcss/cli",
120
- "-i",
121
- join(STYLES_DIR, "globals.css"),
122
- "-o",
123
- tmpCss,
124
- "--minify",
125
- ],
126
- { stdout: "ignore", stderr: "pipe" }
127
- );
128
- await proc.exited;
129
- if (proc.exitCode !== 0) {
130
- const err = await new Response(proc.stderr).text();
131
- throw new Error(`Tailwind CSS build failed:\n${err}`);
132
- }
133
175
 
134
- let cssContent = await Bun.file(tmpCss).text();
135
-
136
- try {
137
- const themeColors = getThemeConfig();
138
- if (themeColors) {
139
- cssContent = buildThemeCss(cssContent, themeColors);
140
- }
141
- } catch (err) {
142
- console.warn(
143
- `[flame] Failed to resolve theme config, falling back to globals.css only: ${err instanceof Error ? err.message : String(err)}`
144
- );
145
- }
146
-
147
- const cssHash = new Bun.CryptoHasher("md5").update(cssContent).digest("hex").slice(0, 8);
148
- const cssFile = `client-${cssHash}.css`;
149
- await Bun.write(join(ASSETS_DIR, cssFile), cssContent);
150
- await unlink(tmpCss);
176
+ const { file: cssFile } = await buildTailwindCss(twKey);
151
177
 
152
178
  await Bun.write(join(ASSETS_DIR, "manifest.json"), JSON.stringify({ js: jsFile, css: cssFile }));
153
179
 
@@ -32,11 +32,12 @@ export const DOCU_CONFIG_PATH = join(PROJECT_ROOT, "docu.json");
32
32
  // Config singleton
33
33
  let _config: DocuConfig | null = null;
34
34
 
35
- /** Clean stale client bundles and split chunks from a previous build. */
36
- export async function cleanOldBundles() {
35
+ /** Clean stale client bundles from a previous build. */
36
+ export async function cleanOldBundles(preserve?: Set<string>) {
37
37
  try {
38
38
  const files = await readdir(ASSETS_DIR);
39
39
  for (const file of files) {
40
+ if (preserve?.has(file)) continue;
40
41
  if (file.startsWith("client.") || file.startsWith("client-")) {
41
42
  await unlink(join(ASSETS_DIR, file));
42
43
  }
@@ -46,12 +47,13 @@ export async function cleanOldBundles() {
46
47
  console.error("Failed to clean old bundles:", (err as Error).message);
47
48
  }
48
49
  }
49
- // Stale split chunks accumulate across builds (content-hashed names); drop
50
- // the whole chunks dir so only the new build's chunks remain.
51
50
  try {
52
51
  await rm(join(ASSETS_DIR, "chunks"), { recursive: true, force: true });
53
52
  } catch (err) {
54
- console.error("Failed to clean old chunks:", (err as Error).message);
53
+ // chunks dir may not exist, or permission error — log to avoid silent failure
54
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
55
+ console.warn("[flame] Failed to clean chunks dir:", (err as Error).message);
56
+ }
55
57
  }
56
58
  }
57
59
 
@@ -279,7 +279,8 @@ export function serveStatic(pathname: string): Response | null {
279
279
 
280
280
  export function serverErrorResponse(error: unknown): Response {
281
281
  const msg = error instanceof Error ? error.message : "Unknown error";
282
- const st = error instanceof Error ? error.stack : undefined;
282
+ const st =
283
+ process.env.NODE_ENV !== "production" && error instanceof Error ? error.stack : undefined;
283
284
  return new Response(errorHtml(msg, st), {
284
285
  status: 500,
285
286
  headers: {
package/bin/cli.js CHANGED
@@ -12,10 +12,13 @@ const __dirname = import.meta.dirname;
12
12
  // Deno's npm compat layer may expose `Bun` via globals, check execPath first.
13
13
  const runtime =
14
14
  process.env.FLAME_RUNTIME ||
15
- (process.execPath.includes("deno") ? "deno" :
16
- typeof Bun !== "undefined" ? "bun" :
17
- typeof Deno !== "undefined" ? "deno" :
18
- "node");
15
+ (process.execPath.includes("deno")
16
+ ? "deno"
17
+ : typeof Bun !== "undefined"
18
+ ? "bun"
19
+ : typeof Deno !== "undefined"
20
+ ? "deno"
21
+ : "node");
19
22
 
20
23
  const COMMAND_MAP = {
21
24
  bun: {
@@ -48,11 +51,15 @@ if (!COMMAND_MAP) {
48
51
 
49
52
  const command = process.argv[2];
50
53
 
51
- // Parse --theme flag: set env before importing build script
54
+ // Parse flags
52
55
  const themeIndex = process.argv.indexOf("--theme");
53
56
  if (themeIndex !== -1 && themeIndex + 1 < process.argv.length) {
54
57
  process.env.FLAME_THEME = process.argv[themeIndex + 1];
55
58
  }
59
+ const hasDocker = process.argv.includes("--docker");
60
+ const hasSilent = process.argv.includes("--silent");
61
+ if (hasDocker) process.env.FLAME_DEPLOY_DOCKER = "1";
62
+ if (hasSilent) process.env.FLAME_DEPLOY_SILENT = "1";
56
63
 
57
64
  if (!command || command === "--help" || command === "-h") {
58
65
  console.log(`
@@ -71,6 +78,8 @@ if (!command || command === "--help" || command === "-h") {
71
78
  Options:
72
79
  --help Show this help message
73
80
  --theme <name> Override theme preset (e.g. freshlime, coffee). Works with dev, build, preview.
81
+ --docker Generate Docker deployment files (Dockerfile + nginx.conf). Works with deploy.
82
+ --silent Suppress non-essential output. Works with deploy.
74
83
  `);
75
84
  process.exit(0);
76
85
  }
@@ -120,11 +129,15 @@ if (command === "init") {
120
129
  }
121
130
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
122
131
 
132
+ // Runtime detection via shebang (#!/usr/bin/env node) makes typeof Bun
133
+ // unavailable — check for bun.lock as a reliable Bun indicator.
134
+ const isBun = existsSync(join(targetDir, "bun.lock"));
135
+ const pkgManager = runtime === "deno" ? "deno" : isBun ? "bun" : "node";
123
136
  const nextSteps = {
124
137
  bun: " bun install\n bun run dev",
125
138
  node: " npm install\n npm run dev",
126
139
  deno: " deno task dev\n\n ⚠️ If you see a freshness error, run:\n DENO_ALLOW_NEWER=true deno task dev",
127
- }[runtime];
140
+ }[pkgManager];
128
141
  console.log(`\n ✓ Project scaffolded!\n\n Next steps:\n${nextSteps}\n`);
129
142
  process.exit(0);
130
143
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docubook/flame",
3
- "version": "1.5.4",
3
+ "version": "1.6.0",
4
4
  "description": "A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -55,7 +55,7 @@
55
55
  "react-dom": "^19.2.7",
56
56
  "unified": "^11.0.0",
57
57
  "@docubook/core": "^1.8.2",
58
- "@docubook/mdx-content": "^3.4.4",
58
+ "@docubook/mdx-content": "^3.4.5",
59
59
  "@docubook/runt": "^1.0.0",
60
60
  "@docubook/themes-colors": "^1.0.2",
61
61
  "@docubook/ui-react": "^1.0.0"
@@ -1,12 +0,0 @@
1
- import {
2
- runBuild,
3
- runBuildCli
4
- } from "./chunk-NYCIAZTI.js";
5
- import "./chunk-C67LPMQA.js";
6
- import "./chunk-EOK6KATZ.js";
7
- import "./chunk-GXQB4ETN.js";
8
- import "./chunk-J5NMYSBJ.js";
9
- export {
10
- runBuild,
11
- runBuildCli
12
- };
@@ -1,79 +0,0 @@
1
- import {
2
- DIST_DIR,
3
- PROJECT_ROOT
4
- } from "./chunk-J5NMYSBJ.js";
5
-
6
- // .docu/node/deploy.shared.ts
7
- import { writeFile, mkdir } from "node:fs/promises";
8
- import { existsSync } from "node:fs";
9
- import { join } from "node:path";
10
- var WORKFLOW_DIR = join(PROJECT_ROOT, ".github/workflows");
11
- var WORKFLOW_FILE = join(WORKFLOW_DIR, "deploy.yml");
12
- async function runDeploy() {
13
- console.log("\u{1F4E6} Building for production...\n");
14
- process.env.NODE_ENV = "production";
15
- const { runBuildCli } = await import("./build.impl-CPFOQ7JW.js");
16
- await runBuildCli();
17
- await writeFile(join(DIST_DIR, ".nojekyll"), "");
18
- if (!existsSync(WORKFLOW_FILE)) {
19
- await mkdir(WORKFLOW_DIR, { recursive: true });
20
- await writeFile(WORKFLOW_FILE, GITHUB_ACTIONS_WORKFLOW);
21
- console.log("\n\u{1F4C4} Created .github/workflows/deploy.yml");
22
- }
23
- console.log("\n\u2705 Ready to deploy!");
24
- console.log(" Output: .docu/dist/");
25
- console.log(" Push to GitHub and enable Pages (Settings \u2192 Pages \u2192 Source: GitHub Actions)");
26
- }
27
- var GITHUB_ACTIONS_WORKFLOW = `name: Deploy to GitHub Pages
28
-
29
- on:
30
- push:
31
- branches: [main]
32
- workflow_dispatch:
33
-
34
- permissions:
35
- contents: read
36
- pages: write
37
- id-token: write
38
-
39
- concurrency:
40
- group: "pages"
41
- cancel-in-progress: false
42
-
43
- jobs:
44
- build:
45
- runs-on: ubuntu-latest
46
- steps:
47
- - uses: actions/checkout@v4
48
- with:
49
- fetch-depth: 0
50
-
51
- - uses: actions/setup-node@v4
52
- with:
53
- node-version: 22
54
-
55
- - run: npm install
56
-
57
- - run: npm run build
58
-
59
- - name: Add .nojekyll
60
- run: touch .docu/dist/.nojekyll
61
-
62
- - uses: actions/upload-pages-artifact@v3
63
- with:
64
- path: .docu/dist
65
-
66
- deploy:
67
- environment:
68
- name: github-pages
69
- url: \${{ steps.deployment.outputs.page_url }}
70
- runs-on: ubuntu-latest
71
- needs: build
72
- steps:
73
- - id: deployment
74
- uses: actions/deploy-pages@v4
75
- `;
76
-
77
- export {
78
- runDeploy
79
- };