@docubook/flame 1.6.5 → 1.7.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.
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  runBuildCli
3
- } from "./chunk-OUILOW2S.js";
4
- import "./chunk-WSJC3PRH.js";
3
+ } from "./chunk-7GWATWNE.js";
4
+ import "./chunk-VW7B64WE.js";
5
5
  import "./chunk-EOK6KATZ.js";
6
- import "./chunk-I7OBFORY.js";
6
+ import "./chunk-PL3JZCCY.js";
7
7
  import "./chunk-4IQXHHPF.js";
8
8
 
9
9
  // .docu/node/build.deno.ts
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  runBuild,
3
3
  runBuildCli
4
- } from "./chunk-OUILOW2S.js";
5
- import "./chunk-WSJC3PRH.js";
4
+ } from "./chunk-7GWATWNE.js";
5
+ import "./chunk-VW7B64WE.js";
6
6
  import "./chunk-EOK6KATZ.js";
7
- import "./chunk-I7OBFORY.js";
7
+ import "./chunk-PL3JZCCY.js";
8
8
  import "./chunk-4IQXHHPF.js";
9
9
  export {
10
10
  runBuild,
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  runBuildCli
3
- } from "./chunk-OUILOW2S.js";
4
- import "./chunk-WSJC3PRH.js";
3
+ } from "./chunk-7GWATWNE.js";
4
+ import "./chunk-VW7B64WE.js";
5
5
  import "./chunk-EOK6KATZ.js";
6
- import "./chunk-I7OBFORY.js";
6
+ import "./chunk-PL3JZCCY.js";
7
7
  import "./chunk-4IQXHHPF.js";
8
8
 
9
9
  // .docu/node/build.node.ts
@@ -1,12 +1,16 @@
1
1
  import {
2
2
  DIST_DIR,
3
+ FRAMEWORK_ROOT,
3
4
  PROJECT_ROOT
4
5
  } from "./chunk-4IQXHHPF.js";
5
6
 
6
7
  // .docu/node/deploy.shared.ts
7
8
  import { writeFile, mkdir } from "node:fs/promises";
8
- import { existsSync } from "node:fs";
9
- import { join } from "node:path";
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { join, resolve } from "node:path";
11
+ var FLAME_MAJOR = JSON.parse(
12
+ readFileSync(resolve(FRAMEWORK_ROOT, "package.json"), "utf-8")
13
+ ).version.split(".")[0];
10
14
  var WORKFLOW_DIR = join(PROJECT_ROOT, ".github/workflows");
11
15
  var WORKFLOW_FILE = join(WORKFLOW_DIR, "deploy.yml");
12
16
  var NGINX_CONF = `server {
@@ -54,7 +58,6 @@ var NGINX_CONF = `server {
54
58
  }
55
59
  `;
56
60
  var DOCKERIGNORE = `node_modules
57
- .git
58
61
  *.DS_Store
59
62
  .docu/dist
60
63
  .docu/lib
@@ -117,6 +120,7 @@ var HEADERS_FILE = `/*
117
120
  `;
118
121
  var isDocker = !!process.env.FLAME_DEPLOY_DOCKER;
119
122
  var isSilent = !!process.env.FLAME_DEPLOY_SILENT;
123
+ var isCi = !!process.env.FLAME_DEPLOY_CI;
120
124
  var log = isSilent ? { info: () => {
121
125
  }, ok: () => {
122
126
  }, created: () => {
@@ -133,22 +137,19 @@ async function runBuild() {
133
137
  process.env.FLAME_BUILD_SILENT = "1";
134
138
  process.env.LOG_LEVEL = "error";
135
139
  }
136
- const { runBuildCli } = await import("./build.impl-VCWNBEBU.js");
140
+ const { runBuildCli } = await import("./build.impl-F4M6HJXA.js");
137
141
  await runBuildCli();
138
142
  }
139
143
  async function writeDockerFiles() {
140
144
  const dockerDir = PROJECT_ROOT;
141
- const pm = detectPkgManager(dockerDir);
142
145
  if (!existsSync(join(dockerDir, "Dockerfile"))) {
143
146
  await writeFile(
144
147
  join(dockerDir, "Dockerfile"),
145
- `FROM ${pm.baseImage} AS builder
148
+ `FROM ghcr.io/docubook/flame-builder:${FLAME_MAJOR} AS builder
146
149
  ENV NODE_ENV=production
147
150
  WORKDIR /app
148
- COPY package.json ${pm.lockFile} ./
149
- RUN ${pm.installCmd}
150
151
  COPY . .
151
- RUN ${pm.runCmd} run build
152
+ RUN flame build
152
153
 
153
154
  FROM nginx:alpine
154
155
  COPY --from=builder /app/.docu/dist /usr/share/nginx/html
@@ -246,6 +247,43 @@ function generateWorkflowYml() {
246
247
  " uses: actions/deploy-pages@v4"
247
248
  ].join("\n");
248
249
  }
250
+ function generateDockerWorkflowYml() {
251
+ const imageName = "ghcr.io/${{ github.repository }}";
252
+ return [
253
+ `name: Build & Push Docker Image`,
254
+ "",
255
+ "on:",
256
+ " push:",
257
+ " branches: [main]",
258
+ " workflow_dispatch:",
259
+ "",
260
+ "permissions:",
261
+ " contents: read",
262
+ " packages: write",
263
+ "",
264
+ "jobs:",
265
+ " build:",
266
+ " runs-on: ubuntu-latest",
267
+ " steps:",
268
+ " - uses: actions/checkout@v4",
269
+ " with:",
270
+ " fetch-depth: 0",
271
+ "",
272
+ " - name: Log in to GHCR",
273
+ " uses: docker/login-action@v3",
274
+ " with:",
275
+ " registry: ghcr.io",
276
+ " username: ${{ github.actor }}",
277
+ " password: ${{ secrets.GITHUB_TOKEN }}",
278
+ "",
279
+ " - name: Build & push",
280
+ " uses: docker/build-push-action@v5",
281
+ " with:",
282
+ " context: .",
283
+ ` tags: ${imageName}:latest`,
284
+ " push: true"
285
+ ].join("\n");
286
+ }
249
287
  async function writeGhaWorkflow() {
250
288
  if (!existsSync(WORKFLOW_FILE)) {
251
289
  await mkdir(WORKFLOW_DIR, { recursive: true });
@@ -253,6 +291,14 @@ async function writeGhaWorkflow() {
253
291
  log.created("\u{1F4C4} Created .github/workflows/deploy.yml");
254
292
  }
255
293
  }
294
+ var DOCKER_WORKFLOW_FILE = join(WORKFLOW_DIR, "deploy-docker.yml");
295
+ async function writeDockerWorkflow() {
296
+ if (!existsSync(DOCKER_WORKFLOW_FILE)) {
297
+ await mkdir(WORKFLOW_DIR, { recursive: true });
298
+ await writeFile(DOCKER_WORKFLOW_FILE, generateDockerWorkflowYml());
299
+ log.created("\u{1F4C4} Created .github/workflows/deploy-docker.yml");
300
+ }
301
+ }
256
302
  async function runDeploy() {
257
303
  log.info("\u{1F4E6} Building for production...\n");
258
304
  await runBuild();
@@ -260,13 +306,20 @@ async function runDeploy() {
260
306
  await writeFile(join(DIST_DIR, "_headers"), HEADERS_FILE);
261
307
  if (isDocker) {
262
308
  await writeDockerFiles();
309
+ if (isCi) await writeDockerWorkflow();
263
310
  } else {
264
311
  await writeGhaWorkflow();
265
312
  }
266
313
  log.ok();
267
314
  log.out(" Output: .docu/dist/");
268
315
  if (isDocker) {
269
- log.out(" Run: docker build -t my-docs . && docker run -p 80:80 my-docs");
316
+ log.out(" Build locally: docker build -t my-docs . && docker run -p 80:80 my-docs");
317
+ if (isCi) {
318
+ log.out(" Push to GitHub \u2014 CI will build & push Docker image to GHCR");
319
+ log.out(" Then pull latest image on your hosting platform (Coolify, etc.)");
320
+ } else {
321
+ log.out(" For Coolify: connect repo, set build pack to Dockerfile");
322
+ }
270
323
  } else {
271
324
  log.out(" Push to GitHub and enable Pages (Settings \u2192 Pages \u2192 Source: GitHub Actions)");
272
325
  }
@@ -13,7 +13,7 @@ import {
13
13
  htmlShell,
14
14
  initSentry,
15
15
  loadPlugins
16
- } from "./chunk-WSJC3PRH.js";
16
+ } from "./chunk-VW7B64WE.js";
17
17
  import {
18
18
  cspHeader,
19
19
  generateNonce,
@@ -21,7 +21,7 @@ import {
21
21
  } from "./chunk-EOK6KATZ.js";
22
22
  import {
23
23
  logger
24
- } from "./chunk-I7OBFORY.js";
24
+ } from "./chunk-PL3JZCCY.js";
25
25
  import {
26
26
  ASSETS_DIR,
27
27
  CACHE_FILE,
@@ -5,7 +5,7 @@ import {
5
5
  // package.json
6
6
  var package_default = {
7
7
  name: "@docubook/flame",
8
- version: "1.6.5",
8
+ version: "1.7.0",
9
9
  description: "A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.",
10
10
  type: "module",
11
11
  bin: {
@@ -14,7 +14,7 @@ import {
14
14
  htmlShell,
15
15
  initSentry,
16
16
  loadPlugins
17
- } from "./chunk-WSJC3PRH.js";
17
+ } from "./chunk-VW7B64WE.js";
18
18
  import {
19
19
  SECURITY_HEADERS,
20
20
  generateNonce,
@@ -27,7 +27,7 @@ import {
27
27
  } from "./chunk-EOK6KATZ.js";
28
28
  import {
29
29
  logger
30
- } from "./chunk-I7OBFORY.js";
30
+ } from "./chunk-PL3JZCCY.js";
31
31
  import {
32
32
  DIST_DIR,
33
33
  DOCS_DIR,
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-EOK6KATZ.js";
8
8
  import {
9
9
  logger
10
- } from "./chunk-I7OBFORY.js";
10
+ } from "./chunk-PL3JZCCY.js";
11
11
  import {
12
12
  DIST_DIR
13
13
  } from "./chunk-4IQXHHPF.js";
@@ -555,8 +555,8 @@ function scanDir(dirPath, docsRoot) {
555
555
  if (entry.startsWith(".") || entry === "assets") continue;
556
556
  const absPath = join(dirPath, entry);
557
557
  try {
558
- const stat = statSync(absPath);
559
- if (stat.isDirectory()) {
558
+ const stat2 = statSync(absPath);
559
+ if (stat2.isDirectory()) {
560
560
  const children = scanDir(absPath, docsRoot);
561
561
  if (children.length > 0) {
562
562
  nodes.push({
@@ -567,7 +567,7 @@ function scanDir(dirPath, docsRoot) {
567
567
  children
568
568
  });
569
569
  }
570
- } else if (stat.isFile() && isDocFile(entry)) {
570
+ } else if (stat2.isFile() && isDocFile(entry)) {
571
571
  nodes.push({
572
572
  name: entry,
573
573
  relPath: normalizePath(relative(docsRoot, absPath).replace(/\.(mdx|md)$/, "")),
@@ -1073,6 +1073,7 @@ function captureException(err, context) {
1073
1073
 
1074
1074
  // .docu/node/git.ts
1075
1075
  import { execFile as execFile2 } from "node:child_process";
1076
+ import { stat } from "node:fs/promises";
1076
1077
  function runGit(args) {
1077
1078
  return new Promise((resolve4, reject) => {
1078
1079
  execFile2("git", args, { maxBuffer: 16 * 1024 * 1024 }, (err, stdout) => {
@@ -1098,6 +1099,14 @@ async function getGitLastModified(filePath) {
1098
1099
  return null;
1099
1100
  }
1100
1101
  }
1102
+ async function getFilesystemMtime(filePath) {
1103
+ try {
1104
+ const stats = await stat(filePath);
1105
+ return stats.mtime.toISOString();
1106
+ } catch {
1107
+ return null;
1108
+ }
1109
+ }
1101
1110
  async function getGitLastModifiedBatch(filePaths) {
1102
1111
  const result = /* @__PURE__ */ new Map();
1103
1112
  if (filePaths.length === 0) return result;
@@ -1208,7 +1217,7 @@ async function compileMdx(rawMdx, filePath, gitDates, remarkPlugins, rehypePlugi
1208
1217
  frontmatter: {},
1209
1218
  components
1210
1219
  });
1211
- const date = frontmatter.date || gitDates?.get(filePath) || await getGitLastModified(filePath) || void 0;
1220
+ const date = frontmatter.date || gitDates?.get(filePath) || await getGitLastModified(filePath) || await getFilesystemMtime(filePath) || void 0;
1212
1221
  return {
1213
1222
  content,
1214
1223
  compiledSource: serialized.compiledSource,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-I7OBFORY.js";
3
+ } from "./chunk-PL3JZCCY.js";
4
4
  import "./chunk-4IQXHHPF.js";
5
5
 
6
6
  // .docu/node/clean.ts
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runDeploy
3
- } from "./chunk-P2P2NB3K.js";
3
+ } from "./chunk-4NI6P6NM.js";
4
4
  import "./chunk-4IQXHHPF.js";
5
5
 
6
6
  // .docu/node/deploy.deno.ts
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runDeploy
3
- } from "./chunk-P2P2NB3K.js";
3
+ } from "./chunk-4NI6P6NM.js";
4
4
  import "./chunk-4IQXHHPF.js";
5
5
 
6
6
  // .docu/node/deploy.node.ts
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  runPreview
3
- } from "./chunk-74AOEDLW.js";
3
+ } from "./chunk-VJKIFRLV.js";
4
4
  import "./chunk-EOK6KATZ.js";
5
- import "./chunk-I7OBFORY.js";
5
+ import "./chunk-PL3JZCCY.js";
6
6
  import "./chunk-4IQXHHPF.js";
7
7
 
8
8
  // .docu/node/preview.deno.ts
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  runPreview
3
- } from "./chunk-74AOEDLW.js";
3
+ } from "./chunk-VJKIFRLV.js";
4
4
  import "./chunk-EOK6KATZ.js";
5
- import "./chunk-I7OBFORY.js";
5
+ import "./chunk-PL3JZCCY.js";
6
6
  import "./chunk-4IQXHHPF.js";
7
7
 
8
8
  // .docu/node/preview.node.ts
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  runServer
3
- } from "./chunk-Y4PEPREZ.js";
4
- import "./chunk-WSJC3PRH.js";
3
+ } from "./chunk-UDDWU5NP.js";
4
+ import "./chunk-VW7B64WE.js";
5
5
  import "./chunk-EOK6KATZ.js";
6
- import "./chunk-I7OBFORY.js";
6
+ import "./chunk-PL3JZCCY.js";
7
7
  import "./chunk-4IQXHHPF.js";
8
8
 
9
9
  // .docu/node/server.deno.ts
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  runServer
3
- } from "./chunk-Y4PEPREZ.js";
4
- import "./chunk-WSJC3PRH.js";
3
+ } from "./chunk-UDDWU5NP.js";
4
+ import "./chunk-VW7B64WE.js";
5
5
  import "./chunk-EOK6KATZ.js";
6
- import "./chunk-I7OBFORY.js";
6
+ import "./chunk-PL3JZCCY.js";
7
7
  import "./chunk-4IQXHHPF.js";
8
8
 
9
9
  // .docu/node/server.node.ts
@@ -9,9 +9,13 @@
9
9
  */
10
10
 
11
11
  import { writeFile, mkdir } from "node:fs/promises";
12
- import { existsSync } from "node:fs";
13
- import { join } from "node:path";
14
- import { DIST_DIR, PROJECT_ROOT } from "./paths";
12
+ import { existsSync, readFileSync } from "node:fs";
13
+ import { join, resolve } from "node:path";
14
+ import { DIST_DIR, PROJECT_ROOT, FRAMEWORK_ROOT } from "./paths";
15
+
16
+ const FLAME_MAJOR = JSON.parse(
17
+ readFileSync(resolve(FRAMEWORK_ROOT, "package.json"), "utf-8")
18
+ ).version.split(".")[0];
15
19
 
16
20
  const WORKFLOW_DIR = join(PROJECT_ROOT, ".github/workflows");
17
21
  const WORKFLOW_FILE = join(WORKFLOW_DIR, "deploy.yml");
@@ -62,7 +66,6 @@ export const NGINX_CONF = `server {
62
66
  `;
63
67
 
64
68
  export const DOCKERIGNORE = `node_modules
65
- .git
66
69
  *.DS_Store
67
70
  .docu/dist
68
71
  .docu/lib
@@ -140,6 +143,7 @@ export const HEADERS_FILE = `/*
140
143
 
141
144
  const isDocker = !!process.env.FLAME_DEPLOY_DOCKER;
142
145
  const isSilent = !!process.env.FLAME_DEPLOY_SILENT;
146
+ const isCi = !!process.env.FLAME_DEPLOY_CI;
143
147
 
144
148
  /** Logger that no-ops all non-error output in silent mode. */
145
149
  const log = isSilent
@@ -163,18 +167,15 @@ async function runBuild() {
163
167
 
164
168
  async function writeDockerFiles() {
165
169
  const dockerDir = PROJECT_ROOT;
166
- const pm = detectPkgManager(dockerDir);
167
170
 
168
171
  if (!existsSync(join(dockerDir, "Dockerfile"))) {
169
172
  await writeFile(
170
173
  join(dockerDir, "Dockerfile"),
171
- `FROM ${pm.baseImage} AS builder
174
+ `FROM ghcr.io/docubook/flame-builder:${FLAME_MAJOR} AS builder
172
175
  ENV NODE_ENV=production
173
176
  WORKDIR /app
174
- COPY package.json ${pm.lockFile} ./
175
- RUN ${pm.installCmd}
176
177
  COPY . .
177
- RUN ${pm.runCmd} run build
178
+ RUN flame build
178
179
 
179
180
  FROM nginx:alpine
180
181
  COPY --from=builder /app/.docu/dist /usr/share/nginx/html
@@ -280,6 +281,44 @@ function generateWorkflowYml(): string {
280
281
  ].join("\n");
281
282
  }
282
283
 
284
+ function generateDockerWorkflowYml(): string {
285
+ const imageName = "ghcr.io/${{ github.repository }}";
286
+ return [
287
+ `name: Build & Push Docker Image`,
288
+ "",
289
+ "on:",
290
+ " push:",
291
+ " branches: [main]",
292
+ " workflow_dispatch:",
293
+ "",
294
+ "permissions:",
295
+ " contents: read",
296
+ " packages: write",
297
+ "",
298
+ "jobs:",
299
+ " build:",
300
+ " runs-on: ubuntu-latest",
301
+ " steps:",
302
+ " - uses: actions/checkout@v4",
303
+ " with:",
304
+ " fetch-depth: 0",
305
+ "",
306
+ " - name: Log in to GHCR",
307
+ " uses: docker/login-action@v3",
308
+ " with:",
309
+ " registry: ghcr.io",
310
+ " username: ${{ github.actor }}",
311
+ " password: ${{ secrets.GITHUB_TOKEN }}",
312
+ "",
313
+ " - name: Build & push",
314
+ " uses: docker/build-push-action@v5",
315
+ " with:",
316
+ " context: .",
317
+ ` tags: ${imageName}:latest`,
318
+ " push: true",
319
+ ].join("\n");
320
+ }
321
+
283
322
  async function writeGhaWorkflow() {
284
323
  if (!existsSync(WORKFLOW_FILE)) {
285
324
  await mkdir(WORKFLOW_DIR, { recursive: true });
@@ -288,6 +327,16 @@ async function writeGhaWorkflow() {
288
327
  }
289
328
  }
290
329
 
330
+ const DOCKER_WORKFLOW_FILE = join(WORKFLOW_DIR, "deploy-docker.yml");
331
+
332
+ async function writeDockerWorkflow() {
333
+ if (!existsSync(DOCKER_WORKFLOW_FILE)) {
334
+ await mkdir(WORKFLOW_DIR, { recursive: true });
335
+ await writeFile(DOCKER_WORKFLOW_FILE, generateDockerWorkflowYml());
336
+ log.created("📄 Created .github/workflows/deploy-docker.yml");
337
+ }
338
+ }
339
+
291
340
  export async function runDeploy(): Promise<void> {
292
341
  log.info("📦 Building for production...\n");
293
342
  await runBuild();
@@ -298,6 +347,7 @@ export async function runDeploy(): Promise<void> {
298
347
 
299
348
  if (isDocker) {
300
349
  await writeDockerFiles();
350
+ if (isCi) await writeDockerWorkflow();
301
351
  } else {
302
352
  await writeGhaWorkflow();
303
353
  }
@@ -305,7 +355,13 @@ export async function runDeploy(): Promise<void> {
305
355
  log.ok();
306
356
  log.out(" Output: .docu/dist/");
307
357
  if (isDocker) {
308
- log.out(" Run: docker build -t my-docs . && docker run -p 80:80 my-docs");
358
+ log.out(" Build locally: docker build -t my-docs . && docker run -p 80:80 my-docs");
359
+ if (isCi) {
360
+ log.out(" Push to GitHub — CI will build & push Docker image to GHCR");
361
+ log.out(" Then pull latest image on your hosting platform (Coolify, etc.)");
362
+ } else {
363
+ log.out(" For Coolify: connect repo, set build pack to Dockerfile");
364
+ }
309
365
  } else {
310
366
  log.out(" Push to GitHub and enable Pages (Settings → Pages → Source: GitHub Actions)");
311
367
  }
@@ -8,18 +8,24 @@
8
8
  */
9
9
 
10
10
  import { mkdir } from "node:fs/promises";
11
- import { existsSync } from "node:fs";
12
- import { join } from "node:path";
13
- import { DIST_DIR, PROJECT_ROOT } from "./paths";
11
+ import { existsSync, readFileSync } from "node:fs";
12
+ import { join, resolve } from "node:path";
13
+ import { DIST_DIR, PROJECT_ROOT, FRAMEWORK_ROOT } from "./paths";
14
14
  import { HEADERS_FILE, NGINX_CONF, DOCKERIGNORE } from "./deploy.shared";
15
15
 
16
+ const FLAME_PKG = JSON.parse(readFileSync(resolve(FRAMEWORK_ROOT, "package.json"), "utf-8"));
17
+ const FLAME_VERSION = FLAME_PKG.version;
18
+ const FLAME_MAJOR = FLAME_VERSION.split(".")[0];
19
+
16
20
  export { HEADERS_FILE, NGINX_CONF, DOCKERIGNORE };
17
21
 
18
22
  const WORKFLOW_DIR = join(PROJECT_ROOT, ".github/workflows");
19
23
  const WORKFLOW_FILE = join(WORKFLOW_DIR, "deploy.yml");
24
+ const DOCKER_WORKFLOW_FILE = join(WORKFLOW_DIR, "deploy-docker.yml");
20
25
 
21
26
  const isDocker = !!process.env.FLAME_DEPLOY_DOCKER;
22
27
  const isSilent = !!process.env.FLAME_DEPLOY_SILENT;
28
+ const isCi = !!process.env.FLAME_DEPLOY_CI;
23
29
 
24
30
  /** Logger that no-ops all non-error output in silent mode. */
25
31
  const log = isSilent
@@ -43,13 +49,11 @@ async function runBuild() {
43
49
  }
44
50
  }
45
51
 
46
- export const DOCKERFILE_BUN = `FROM oven/bun:1-debian AS builder
52
+ export const DOCKERFILE_BUN = `FROM ghcr.io/docubook/flame-builder:${FLAME_MAJOR} AS builder
47
53
  ENV NODE_ENV=production
48
54
  WORKDIR /app
49
- COPY package.json bun.lock ./
50
- RUN bun install --frozen-lockfile
51
55
  COPY . .
52
- RUN bun run build
56
+ RUN flame build
53
57
 
54
58
  FROM nginx:alpine
55
59
  COPY --from=builder /app/.docu/dist /usr/share/nginx/html
@@ -85,6 +89,52 @@ async function writeGhaWorkflow() {
85
89
  }
86
90
  }
87
91
 
92
+ function generateDockerWorkflowYml(): string {
93
+ const imageName = "ghcr.io/${{ github.repository }}";
94
+ return [
95
+ `name: Build & Push Docker Image`,
96
+ "",
97
+ "on:",
98
+ " push:",
99
+ " branches: [main]",
100
+ " workflow_dispatch:",
101
+ "",
102
+ "permissions:",
103
+ " contents: read",
104
+ " packages: write",
105
+ "",
106
+ "jobs:",
107
+ " build:",
108
+ " runs-on: ubuntu-latest",
109
+ " steps:",
110
+ " - uses: actions/checkout@v4",
111
+ " with:",
112
+ " fetch-depth: 0",
113
+ "",
114
+ " - name: Log in to GHCR",
115
+ " uses: docker/login-action@v3",
116
+ " with:",
117
+ " registry: ghcr.io",
118
+ " username: ${{ github.actor }}",
119
+ " password: ${{ secrets.GITHUB_TOKEN }}",
120
+ "",
121
+ " - name: Build & push",
122
+ " uses: docker/build-push-action@v5",
123
+ " with:",
124
+ " context: .",
125
+ ` tags: ${imageName}:latest`,
126
+ " push: true",
127
+ ].join("\n");
128
+ }
129
+
130
+ async function writeDockerWorkflow() {
131
+ if (!existsSync(DOCKER_WORKFLOW_FILE)) {
132
+ await mkdir(WORKFLOW_DIR, { recursive: true });
133
+ await Bun.write(DOCKER_WORKFLOW_FILE, generateDockerWorkflowYml());
134
+ log.created("📄 Created .github/workflows/deploy-docker.yml");
135
+ }
136
+ }
137
+
88
138
  async function deploy() {
89
139
  log.info("📦 Building for production...\n");
90
140
  await runBuild();
@@ -95,6 +145,7 @@ async function deploy() {
95
145
 
96
146
  if (isDocker) {
97
147
  await writeDockerFiles();
148
+ if (isCi) await writeDockerWorkflow();
98
149
  } else {
99
150
  await writeGhaWorkflow();
100
151
  }
@@ -102,7 +153,13 @@ async function deploy() {
102
153
  log.ok();
103
154
  log.out(" Output: .docu/dist/");
104
155
  if (isDocker) {
105
- log.out(" Run: docker build -t my-docs . && docker run -p 80:80 my-docs");
156
+ log.out(" Build locally: docker build -t my-docs . && docker run -p 80:80 my-docs");
157
+ if (isCi) {
158
+ log.out(" Push to GitHub — CI will build & push Docker image to GHCR");
159
+ log.out(" Then pull latest image on your hosting platform (Coolify, etc.)");
160
+ } else {
161
+ log.out(" For Coolify: connect repo, set build pack to Dockerfile");
162
+ }
106
163
  } else {
107
164
  log.out(" Push to GitHub and enable Pages (Settings → Pages → Source: GitHub Actions)");
108
165
  }
package/.docu/node/git.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { execFile } from "node:child_process";
8
+ import { stat } from "node:fs/promises";
8
9
 
9
10
  function runGit(args: string[]): Promise<string> {
10
11
  return new Promise((resolve, reject) => {
@@ -35,6 +36,19 @@ export async function getGitLastModified(filePath: string): Promise<string | nul
35
36
  }
36
37
  }
37
38
 
39
+ /**
40
+ * Fallback to filesystem mtime when git is unavailable (shallow clone, no .git, etc.).
41
+ * Returns ISO 8601 date string or null.
42
+ */
43
+ export async function getFilesystemMtime(filePath: string): Promise<string | null> {
44
+ try {
45
+ const stats = await stat(filePath);
46
+ return stats.mtime.toISOString();
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
38
52
  /** Batch git last modified dates for multiple files in a single spawn */
39
53
  export async function getGitLastModifiedBatch(filePaths: string[]): Promise<Map<string, string>> {
40
54
  const result = new Map<string, string>();
package/.docu/node/mdx.ts CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  MDXRemote,
10
10
  } from "@docubook/core";
11
11
  import { createMdxComponents } from "@docubook/mdx-content";
12
- import { getGitLastModified, getGitLastModifiedBatch } from "./git";
12
+ import { getGitLastModified, getGitLastModifiedBatch, getFilesystemMtime } from "./git";
13
13
 
14
14
  /**
15
15
  * Return the value with `.html` appended, or null if the value should be left
@@ -161,6 +161,7 @@ export async function compileMdx(
161
161
  frontmatter.date ||
162
162
  gitDates?.get(filePath) ||
163
163
  (await getGitLastModified(filePath)) ||
164
+ (await getFilesystemMtime(filePath)) ||
164
165
  undefined;
165
166
 
166
167
  return {
package/bin/cli.js CHANGED
@@ -58,8 +58,10 @@ if (themeIndex !== -1 && themeIndex + 1 < process.argv.length) {
58
58
  }
59
59
  const hasDocker = process.argv.includes("--docker");
60
60
  const hasSilent = process.argv.includes("--silent");
61
+ const hasCi = process.argv.includes("--ci");
61
62
  if (hasDocker) process.env.FLAME_DEPLOY_DOCKER = "1";
62
63
  if (hasSilent) process.env.FLAME_DEPLOY_SILENT = "1";
64
+ if (hasCi) process.env.FLAME_DEPLOY_CI = "1";
63
65
 
64
66
  // Production mode for build/preview/deploy — ensures minified client bundle
65
67
  // on all platforms (Coolify, Vercel, etc.) without requiring the user to
@@ -88,7 +90,8 @@ if (!command || command === "--help" || command === "-h") {
88
90
  Options:
89
91
  --help Show this help message
90
92
  --theme <name> Override theme preset (e.g. freshlime, coffee). Works with dev, build, preview.
91
- --docker Generate Docker deployment files (Dockerfile + nginx.conf). Works with deploy.
93
+ --docker Generate Docker deployment files (Dockerfile + nginx.conf + .dockerignore). Works with deploy.
94
+ --ci Generate CI workflow (.github/workflows/deploy-docker.yml) for auto build & push to GHCR. Use with --docker.
92
95
  --silent Suppress non-essential output. Works with deploy.
93
96
  `);
94
97
  process.exit(0);
@@ -127,8 +130,8 @@ if (command === "init") {
127
130
  pkg.scripts = {
128
131
  dev: `${flameCmd} dev`,
129
132
  build: `NODE_ENV=production ${flameCmd} build`,
130
- preview: `NODE_ENV=production ${flameCmd} preview`,
131
- deploy: `NODE_ENV=production ${flameCmd} deploy`,
133
+ preview: `${flameCmd} preview`,
134
+ deploy: `${flameCmd} deploy`,
132
135
  };
133
136
 
134
137
  // Generate deno.json with nodeModulesDir for npm compat.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docubook/flame",
3
- "version": "1.6.5",
3
+ "version": "1.7.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,10 +55,10 @@
55
55
  "react-dom": "^19.2.7",
56
56
  "unified": "^11.0.0",
57
57
  "@docubook/core": "^1.8.2",
58
- "@docubook/runt": "^1.0.0",
59
- "@docubook/mdx-content": "^3.4.5",
58
+ "@docubook/ui-react": "^1.0.0",
60
59
  "@docubook/themes-colors": "^1.0.2",
61
- "@docubook/ui-react": "^1.0.0"
60
+ "@docubook/runt": "^1.0.0",
61
+ "@docubook/mdx-content": "^3.4.5"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "@sentry/bun": "^10.0.0"
@@ -13,6 +13,19 @@ npm run deploy # or: bun run deploy / deno task deploy
13
13
 
14
14
  This builds the site, adds `.nojekyll`, and generates a GitHub Actions workflow on first run.
15
15
 
16
+ ## Docker (self-hosted / VPS)
17
+
18
+ ```bash
19
+ flame deploy --docker
20
+ ```
21
+
22
+ Generates `Dockerfile` + `nginx.conf` + `.dockerignore`. Build and run:
23
+
24
+ ```bash
25
+ docker build -t my-docs .
26
+ docker run -p 80:80 my-docs
27
+ ```
28
+
16
29
  ## Manual
17
30
 
18
31
  ```bash
@@ -5,8 +5,8 @@
5
5
  "scripts": {
6
6
  "dev": "flame dev",
7
7
  "build": "NODE_ENV=production flame build",
8
- "preview": "NODE_ENV=production flame preview",
9
- "deploy": "NODE_ENV=production flame deploy"
8
+ "preview": "flame preview",
9
+ "deploy": "flame deploy"
10
10
  },
11
11
  "dependencies": {
12
12
  "@docubook/flame": "latest"