getfilepress 0.1.23 → 0.1.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getfilepress",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "FilePress — file-based Markdown blog engine. Link this package from a content-only site (config + posts), then run `filepress build`.",
@@ -37,6 +37,7 @@
37
37
  "scripts/new-post.ts",
38
38
  "scripts/preview.mjs",
39
39
  "scripts/copy-path-mounts.mjs",
40
+ "scripts/assert-no-genie.mjs",
40
41
  "scripts/create-site.mjs",
41
42
  "scripts/ensure-lease.mjs",
42
43
  "scripts/postinstall.mjs",
@@ -51,7 +52,7 @@
51
52
  "scripts": {
52
53
  "postinstall": "node scripts/postinstall.mjs",
53
54
  "filepress": "node scripts/filepress.mjs",
54
- "test": "pnpm --filter @filepress/core test && pnpm --filter @filepress/import test && pnpm --filter @filepress/app test && tsx --test scripts/sync-sibling-sites.test.ts scripts/siblings/discover.test.ts scripts/new-post.test.ts scripts/preview.test.ts",
55
+ "test": "pnpm --filter @filepress/core test && pnpm --filter @filepress/import test && pnpm --filter @filepress/app test && tsx --test scripts/sync-sibling-sites.test.ts scripts/siblings/discover.test.ts scripts/new-post.test.ts scripts/preview.test.ts scripts/assert-no-genie.test.ts scripts/published-files.test.ts",
55
56
  "check": "pnpm filepress check --site demo",
56
57
  "build": "pnpm filepress build --site demo",
57
58
  "build:demo": "pnpm filepress build --site demo",
@@ -1,12 +1,12 @@
1
1
  <script lang="ts">
2
+ import { browser } from '$app/environment';
3
+ import { shouldMountGenie } from './enabled';
4
+
2
5
  /**
3
- * Mount Genie only in Vite DEV. Dynamic import keeps the panel out of
4
- * production client graphs when this host is tree-shaken / dead-coded.
6
+ * Browser + Vite DEV only. Never prerender the FAB into static HTML.
7
+ * FILEPRESS_GENIE must not force this on in a production build.
5
8
  */
6
- const enabled =
7
- import.meta.env.DEV ||
8
- import.meta.env.FILEPRESS_GENIE === '1' ||
9
- import.meta.env.FILEPRESS_GENIE === 'true';
9
+ const enabled = shouldMountGenie({ browser, viteDev: import.meta.env.DEV });
10
10
  </script>
11
11
 
12
12
  {#if enabled}
@@ -104,10 +104,16 @@
104
104
  ...init,
105
105
  headers: {
106
106
  'content-type': 'application/json',
107
+ accept: 'application/json',
107
108
  ...(init?.headers || {})
108
109
  }
109
110
  });
110
- const data = await res.json();
111
+ const type = res.headers.get('content-type') ?? '';
112
+ const text = await res.text();
113
+ if (!type.includes('application/json')) {
114
+ throw new Error('Genie is only available in filepress dev.');
115
+ }
116
+ const data = JSON.parse(text) as { error?: string };
111
117
  if (!res.ok) throw new Error(data.error || res.statusText);
112
118
  return data;
113
119
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Genie is a local `filepress dev` cockpit. Production / preview / prerender
3
+ * must never mount it — the APIs live on the Vite serve plugin only.
4
+ */
5
+ export function shouldMountGenie(input: { browser: boolean; viteDev: boolean }): boolean {
6
+ return input.browser && input.viteDev;
7
+ }
@@ -3,7 +3,6 @@
3
3
  import criticalTheme from '$critical-theme';
4
4
  import { SiteHeader, SiteFooter, isPathMountHref } from '@filepress/core';
5
5
  import config from '$site-config';
6
- import GenieHost from '$lib/genie/GenieHost.svelte';
7
6
 
8
7
  let { children } = $props();
9
8
 
@@ -35,5 +34,8 @@
35
34
 
36
35
  <SiteFooter site={config} />
37
36
 
38
- <!-- Dev-only Genie Mode (tree-shaken when import.meta.env.DEV is false). -->
39
- <GenieHost />
37
+ {#if import.meta.env.DEV}
38
+ {#await import('$lib/genie/GenieHost.svelte') then { default: GenieHost }}
39
+ <GenieHost />
40
+ {/await}
41
+ {/if}
@@ -1,10 +1,8 @@
1
- import { dev } from '$app/environment';
2
-
3
1
  // Fully static site: prerender every route at build time (D2, adapter-static).
4
2
  export const prerender = true;
5
3
  export const trailingSlash = 'never';
6
4
 
7
- // Production ships plain HTML (no hydration). That avoids intermittent SvelteKit
8
- // "500 Internal Error" pages when CDN asset hashes race during deploys or when
9
- // client navigation can't load __data.json. Keep CSR in `pnpm dev` for Genie.
10
- export const csr = dev;
5
+ // Production ships plain HTML (no hydration). Use Vite's DEV flag — not
6
+ // `$app/environment` `dev` so a static host cannot bake `csr: true` and
7
+ // hydrate Genie. Keep CSR in `filepress dev` for the local cockpit.
8
+ export const csr = import.meta.env.DEV;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Fail a FilePress `build/` if Genie leaked into the static output.
3
+ * Genie is serve-only (`filepress dev`). Production must not ship the panel,
4
+ * its CSS/JS chunk names, or `/__filepress/genie` calls.
5
+ */
6
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
7
+ import { extname, join, relative } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ const MARKERS = ['GeniePanel', 'GenieHost', 'genie-fab', '/__filepress/genie'];
11
+ const TEXT_EXT = new Set(['.html', '.js', '.css', '.json', '.txt', '.svg', '.xml']);
12
+
13
+ export function assertNoGenieInBuild(buildDir) {
14
+ if (!existsSync(buildDir)) {
15
+ throw new Error(`filepress: Genie leak check skipped — missing build dir ${buildDir}`);
16
+ }
17
+ const hits = [];
18
+ walk(buildDir, (file) => {
19
+ if (!TEXT_EXT.has(extname(file).toLowerCase())) return;
20
+ const text = readFileSync(file, 'utf8');
21
+ for (const marker of MARKERS) {
22
+ if (text.includes(marker)) {
23
+ hits.push(`${relative(buildDir, file)}: ${marker}`);
24
+ }
25
+ }
26
+ });
27
+ if (hits.length > 0) {
28
+ throw new Error(
29
+ `filepress: Genie leaked into the static build (dev-only).\n` +
30
+ hits.map((h) => ` ${h}`).join('\n')
31
+ );
32
+ }
33
+ }
34
+
35
+ function walk(dir, visit) {
36
+ for (const name of readdirSync(dir)) {
37
+ const abs = join(dir, name);
38
+ if (statSync(abs).isDirectory()) walk(abs, visit);
39
+ else visit(abs);
40
+ }
41
+ }
42
+
43
+ const isMain =
44
+ Boolean(process.argv[1]) && fileURLToPath(import.meta.url) === process.argv[1];
45
+ if (isMain) {
46
+ try {
47
+ assertNoGenieInBuild(process.argv[2] ?? '');
48
+ } catch (err) {
49
+ console.error(err instanceof Error ? err.message : err);
50
+ process.exit(1);
51
+ }
52
+ }
@@ -9,6 +9,7 @@
9
9
  import { cpSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
10
10
  import { dirname, join, resolve } from 'node:path';
11
11
  import { fileURLToPath } from 'node:url';
12
+ import { assertNoGenieInBuild } from './assert-no-genie.mjs';
12
13
 
13
14
  const siteRoot = resolve(process.argv[2] ?? '');
14
15
  const buildDir = join(siteRoot, 'build');
@@ -23,6 +24,13 @@ if (!existsSync(buildDir)) {
23
24
  process.exit(0);
24
25
  }
25
26
 
27
+ try {
28
+ assertNoGenieInBuild(buildDir);
29
+ } catch (err) {
30
+ console.error(err instanceof Error ? err.message : err);
31
+ process.exit(1);
32
+ }
33
+
26
34
  const headersDest = join(buildDir, '_headers');
27
35
  if (existsSync(headersDest)) {
28
36
  console.log('filepress: kept site _headers');