create-shapio 0.5.4 → 0.5.6

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": "create-shapio",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Scaffold a new Shapio project (npx create-shapio my-cms) or a site starter (--site astro|next|sveltekit)",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://shapio.dev",
@@ -1,5 +1,6 @@
1
1
  import { writeFile } from 'node:fs/promises';
2
2
  import { defineConfig } from 'astro/config';
3
+ import { devStaticPaths } from './src/integrations/devStaticPaths.ts';
3
4
 
4
5
  // `.env` (written by `npm run seed`) for the settings this file reads; the environment wins.
5
6
  try {
@@ -46,7 +47,8 @@ export default defineConfig({
46
47
  trailingSlash: 'always',
47
48
  build: { format: 'directory' },
48
49
  server: { headers: { 'Content-Security-Policy': FRAME_ANCESTORS } },
49
- integrations: [frameAncestorsHeaders],
50
+ // devStaticPaths: in `astro dev`, an article created after the server started gets its page without a restart.
51
+ integrations: [frameAncestorsHeaders, devStaticPaths()],
50
52
  /**
51
53
  * Incremental builds: a page whose `cacheKey` (src/lib/cacheKey.ts) and code are unchanged is restored from
52
54
  * the previous build instead of rendered. The cache lives in Astro's cacheDir, `node_modules/.astro`, which
@@ -22,8 +22,8 @@
22
22
  "test": "vitest run"
23
23
  },
24
24
  "dependencies": {
25
- "@shapio/client": "^0.5.4",
26
- "@shapio/visual": "^0.5.4",
25
+ "@shapio/client": "^0.5.6",
26
+ "@shapio/visual": "^0.5.6",
27
27
  "astro": "7.3.5"
28
28
  },
29
29
  "devDependencies": {
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { DEV_WINDOW_MS } from '../lib/snapshotResolver.js';
3
+ import { staticPathsRefresher } from './devStaticPaths.js';
4
+
5
+ describe('staticPathsRefresher', () => {
6
+ it('clears for a page request at most once per dev read window', () => {
7
+ let time = 0;
8
+ const shouldClear = staticPathsRefresher(() => time);
9
+ expect(shouldClear('/en/articles/a-post/')).toBe(true);
10
+ time += DEV_WINDOW_MS - 1;
11
+ expect(shouldClear('/en/articles/another/')).toBe(false);
12
+ expect(shouldClear('/fr/')).toBe(false);
13
+ time += 1;
14
+ expect(shouldClear('/fr/articles/bonjour/?v=1')).toBe(true);
15
+ });
16
+
17
+ it('treats the root and every locale path as a page', () => {
18
+ for (const url of ['/', '/en/', '/en/colophon/', '/fr/a-page/', undefined]) {
19
+ expect(staticPathsRefresher(() => 0)(url)).toBe(true);
20
+ }
21
+ });
22
+
23
+ it('ignores tooling and file requests', () => {
24
+ const shouldClear = staticPathsRefresher(() => 0);
25
+ for (const url of [
26
+ '/_astro/x.css',
27
+ '/@vite/client',
28
+ '/@fs/Users/me/node_modules/astro/dist/runtime.js',
29
+ '/@id/astro:scripts/page.js',
30
+ '/src/styles/site.css',
31
+ '/node_modules/.vite/deps/chunk.js',
32
+ '/favicon.svg',
33
+ '/build.json',
34
+ '/en/articles/cover.webp?w=400',
35
+ ]) {
36
+ expect(shouldClear(url)).toBe(false);
37
+ }
38
+ expect(shouldClear('/en/')).toBe(true);
39
+ });
40
+ });
@@ -0,0 +1,82 @@
1
+ import type { AstroIntegration } from 'astro';
2
+ import { DEV_WINDOW_MS } from '../lib/snapshotResolver.js';
3
+
4
+ /**
5
+ * `astro dev` caches each dynamic route's getStaticPaths result until the route module changes. Content comes
6
+ * from Shapio, not from files, so an article (or a draft, in drafts mode) created after the dev server started
7
+ * has no path and its page 404s.
8
+ *
9
+ * This integration clears that cache before a page request, at most once per DEV_WINDOW_MS (the window the
10
+ * content reads reuse), so getStaticPaths runs again with fresh content. Every page of the starter is a dynamic
11
+ * route (`/[locale]/…`), so every request counts as a page request except Astro's and Vite's own (`/_astro/`,
12
+ * `/@vite`, `/@fs`, `/@id`, `/src/`, `/node_modules/`) and anything that looks like a file. It relies on an Astro
13
+ * internal: Astro clears its route cache when the `astro:content-changed` event reaches the ssr and prerender
14
+ * Vite environments (astro/dist/vite-plugin-app/createAstroServerApp.js, sent by
15
+ * astro/dist/content/vite-plugin-content-virtual-mod.js). Dev only: `astro build` never runs
16
+ * `astro:server:setup`, and the hook does nothing unless the command is `dev`.
17
+ */
18
+
19
+ /** Path prefixes of Astro's and Vite's own dev requests (assets, modules, the client): never a page. */
20
+ export const TOOLING_PATH_PREFIXES = ['/_astro/', '/@vite', '/@fs', '/@id', '/src/', '/node_modules/'];
21
+
22
+ /** The Vite environments that run Astro's renderer and hold its route cache. */
23
+ const RENDER_ENVIRONMENTS = ['ssr', 'prerender'];
24
+
25
+ /** A last path segment with an extension (`favicon.svg`, `build.json`): a file, not a page. */
26
+ const looksLikeFile = (pathname: string) =>
27
+ /\.[A-Za-z0-9]+$/.test(pathname.slice(pathname.lastIndexOf('/') + 1));
28
+
29
+ const isPageRequest = (pathname: string) =>
30
+ !TOOLING_PATH_PREFIXES.some((prefix) => pathname.startsWith(prefix)) && !looksLikeFile(pathname);
31
+
32
+ /**
33
+ * Whether a request should clear the route cache: a page request, when the last clear is at least
34
+ * DEV_WINDOW_MS old.
35
+ */
36
+ export const staticPathsRefresher = (now: () => number = Date.now) => {
37
+ let lastClearAt: number | undefined;
38
+ return (url: string | undefined): boolean => {
39
+ if (!isPageRequest(new URL(url ?? '/', 'http://localhost').pathname)) {
40
+ return false;
41
+ }
42
+ const at = now();
43
+ if (lastClearAt !== undefined && at - lastClearAt < DEV_WINDOW_MS) {
44
+ return false;
45
+ }
46
+ lastClearAt = at;
47
+ return true;
48
+ };
49
+ };
50
+
51
+ /** The dev server Astro hands `astro:server:setup` (Vite's; vite is not a direct dependency here). */
52
+ type DevServer = Parameters<NonNullable<AstroIntegration['hooks']['astro:server:setup']>>[0]['server'];
53
+
54
+ const clearRouteCache = (server: DevServer) => {
55
+ for (const name of RENDER_ENVIRONMENTS) {
56
+ server.environments[name]?.hot.send('astro:content-changed', {});
57
+ }
58
+ };
59
+
60
+ export const devStaticPaths = (): AstroIntegration => {
61
+ let isDev = false;
62
+ return {
63
+ name: 'dev-static-paths',
64
+ hooks: {
65
+ 'astro:config:setup': ({ command }) => {
66
+ isDev = command === 'dev';
67
+ },
68
+ 'astro:server:setup': ({ server }) => {
69
+ if (!isDev) {
70
+ return;
71
+ }
72
+ const shouldClear = staticPathsRefresher();
73
+ server.middlewares.use((request, _response, next) => {
74
+ if (shouldClear(request.url)) {
75
+ clearRouteCache(server);
76
+ }
77
+ next();
78
+ });
79
+ },
80
+ },
81
+ };
82
+ };
@@ -18,9 +18,9 @@
18
18
  "test": "vitest run"
19
19
  },
20
20
  "dependencies": {
21
- "@shapio/client": "^0.5.4",
22
- "@shapio/local": "0.5.4",
23
- "@shapio/visual": "^0.5.4",
21
+ "@shapio/client": "^0.5.6",
22
+ "@shapio/local": "0.5.6",
23
+ "@shapio/visual": "^0.5.6",
24
24
  "next": "16.3.8",
25
25
  "react": "19.3.0",
26
26
  "react-dom": "19.3.0"
@@ -23,8 +23,8 @@
23
23
  "test": "vitest run"
24
24
  },
25
25
  "dependencies": {
26
- "@shapio/client": "^0.5.4",
27
- "@shapio/visual": "^0.5.4"
26
+ "@shapio/client": "^0.5.6",
27
+ "@shapio/visual": "^0.5.6"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@sveltejs/adapter-static": "4.0.0",