@techninja/clearstack 0.4.15 → 0.4.17
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/docs/OG_METADATA_SPEC.md
CHANGED
|
@@ -24,14 +24,14 @@ Both are driven by the same `clearstack.routes.json` config and data sources.
|
|
|
24
24
|
"/trait/:slug": {
|
|
25
25
|
"title": "{slug.name}",
|
|
26
26
|
"description": "{slug.description}",
|
|
27
|
-
"image": "{slug.
|
|
28
|
-
"data": "
|
|
27
|
+
"image": "{slug.image}",
|
|
28
|
+
"data": "data/traits-og.json",
|
|
29
29
|
"ogTemplate": "trait"
|
|
30
30
|
},
|
|
31
31
|
"/shop/product/:sku": {
|
|
32
32
|
"title": "{sku.name} | {store.name}",
|
|
33
33
|
"description": "{sku.description}",
|
|
34
|
-
"image": "{
|
|
34
|
+
"image": "{sku.images.0}",
|
|
35
35
|
"data": "src/data/products.json",
|
|
36
36
|
"ogTemplate": "product"
|
|
37
37
|
}
|
|
@@ -111,6 +111,53 @@ clearstack build all --url=https://mysite.com --site=MySite --out=dist
|
|
|
111
111
|
| `--logo` | Logo path or URL |
|
|
112
112
|
| `--slug` | Single item slug (fast iteration) |
|
|
113
113
|
|
|
114
|
+
## Cloudflare Pages Routing
|
|
115
|
+
|
|
116
|
+
Cloudflare Pages evaluates `_redirects` **before** checking the filesystem.
|
|
117
|
+
The standard SPA catch-all `/* /index.html 200` will intercept all pre-rendered
|
|
118
|
+
pages before they can be served. Since CF Pages serves `.html` files natively
|
|
119
|
+
for extensionless URLs when a matching file exists on the filesystem, no extra
|
|
120
|
+
rewrite rules are needed — just ensure the catch-all is the **only** rule and
|
|
121
|
+
that it comes after any explicit redirects:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
/old-path /new-path 301
|
|
125
|
+
/* /index.html 200
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The pre-rendered files (`shop/product/SM-TEE.html`) will be served directly by
|
|
129
|
+
CF Pages' static file layer before the `_redirects` rules are evaluated.
|
|
130
|
+
|
|
131
|
+
## Canonical Tags
|
|
132
|
+
|
|
133
|
+
Inject `<link rel="canonical">` into every pre-rendered page so Google
|
|
134
|
+
deduplicates the static HTML shell from the SPA view at the same URL. Do this
|
|
135
|
+
as a post-processing step in `build.js` after `buildOG` runs:
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
import { readdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
139
|
+
import { resolve } from 'node:path';
|
|
140
|
+
|
|
141
|
+
function injectCanonicals(distDir, baseUrl) {
|
|
142
|
+
const prefixes = ['shop/product', 'trait', 'gene'];
|
|
143
|
+
for (const prefix of prefixes) {
|
|
144
|
+
const dir = resolve(distDir, prefix);
|
|
145
|
+
if (!existsSync(dir)) continue;
|
|
146
|
+
for (const file of readdirSync(dir)) {
|
|
147
|
+
if (!file.endsWith('.html')) continue;
|
|
148
|
+
const slug = file.slice(0, -5);
|
|
149
|
+
const url = `${baseUrl}/${prefix}/${slug}`;
|
|
150
|
+
const filePath = resolve(dir, file);
|
|
151
|
+
let html = readFileSync(filePath, 'utf-8');
|
|
152
|
+
if (!html.includes('rel="canonical"')) {
|
|
153
|
+
html = html.replace('</head>', ` <link rel="canonical" href="${url}" />\n </head>`);
|
|
154
|
+
writeFileSync(filePath, html);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
114
161
|
## Requirements
|
|
115
162
|
|
|
116
163
|
- Zero config for basic cases (route title + description → OG tags)
|
package/lib/watch-runner.js
CHANGED
|
@@ -38,6 +38,7 @@ export function runKeys(specRows, keys, ctx) {
|
|
|
38
38
|
render(rows, lastCheck.value, watchDirs, currentViolations.value);
|
|
39
39
|
};
|
|
40
40
|
const raw = await row.run({ quiet: true, onProgress });
|
|
41
|
+
if (raw?.pass === null) { setImmediate(next); return; } // cancelled — leave row as-is
|
|
41
42
|
const elapsed = ((Date.now() - t0) / 1000).toFixed(1) + 's';
|
|
42
43
|
row.result = typeof raw === 'boolean' ? { pass: raw } : raw;
|
|
43
44
|
row.pass = row.result.pass;
|
package/lib/watch.js
CHANGED
|
@@ -52,7 +52,16 @@ export async function startWatch(projectDir) {
|
|
|
52
52
|
const extra = keyBound.map(({ keyBinding, name }) => ` ${keyBinding} ${name.replace(/\s*\(.*\)/, '')}`).join(' ');
|
|
53
53
|
outer.setLabel(` {blue-fg}\u2665{/blue-fg} Clearstack Spec Watch q quit \u2191\u2193 scroll c copy f fix${extra} `);
|
|
54
54
|
for (const row of keyBound) {
|
|
55
|
-
screen.key([row.keyBinding], () =>
|
|
55
|
+
screen.key([row.keyBinding], () => {
|
|
56
|
+
if (row.pass === null && row.cancel) {
|
|
57
|
+
row.cancel();
|
|
58
|
+
row.pass = false;
|
|
59
|
+
row.detail = 'cancelled';
|
|
60
|
+
render(rows, lastCheck.value, watchDirs, currentViolations.value);
|
|
61
|
+
} else {
|
|
62
|
+
run(new Set([row.key]));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
56
65
|
}
|
|
57
66
|
}
|
|
58
67
|
|
package/package.json
CHANGED
|
@@ -24,14 +24,14 @@ Both are driven by the same `clearstack.routes.json` config and data sources.
|
|
|
24
24
|
"/trait/:slug": {
|
|
25
25
|
"title": "{slug.name}",
|
|
26
26
|
"description": "{slug.description}",
|
|
27
|
-
"image": "{slug.
|
|
28
|
-
"data": "
|
|
27
|
+
"image": "{slug.image}",
|
|
28
|
+
"data": "data/traits-og.json",
|
|
29
29
|
"ogTemplate": "trait"
|
|
30
30
|
},
|
|
31
31
|
"/shop/product/:sku": {
|
|
32
32
|
"title": "{sku.name} | {store.name}",
|
|
33
33
|
"description": "{sku.description}",
|
|
34
|
-
"image": "{
|
|
34
|
+
"image": "{sku.images.0}",
|
|
35
35
|
"data": "src/data/products.json",
|
|
36
36
|
"ogTemplate": "product"
|
|
37
37
|
}
|
|
@@ -111,6 +111,53 @@ clearstack build all --url=https://mysite.com --site=MySite --out=dist
|
|
|
111
111
|
| `--logo` | Logo path or URL |
|
|
112
112
|
| `--slug` | Single item slug (fast iteration) |
|
|
113
113
|
|
|
114
|
+
## Cloudflare Pages Routing
|
|
115
|
+
|
|
116
|
+
Cloudflare Pages evaluates `_redirects` **before** checking the filesystem.
|
|
117
|
+
The standard SPA catch-all `/* /index.html 200` will intercept all pre-rendered
|
|
118
|
+
pages before they can be served. Since CF Pages serves `.html` files natively
|
|
119
|
+
for extensionless URLs when a matching file exists on the filesystem, no extra
|
|
120
|
+
rewrite rules are needed — just ensure the catch-all is the **only** rule and
|
|
121
|
+
that it comes after any explicit redirects:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
/old-path /new-path 301
|
|
125
|
+
/* /index.html 200
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The pre-rendered files (`shop/product/SM-TEE.html`) will be served directly by
|
|
129
|
+
CF Pages' static file layer before the `_redirects` rules are evaluated.
|
|
130
|
+
|
|
131
|
+
## Canonical Tags
|
|
132
|
+
|
|
133
|
+
Inject `<link rel="canonical">` into every pre-rendered page so Google
|
|
134
|
+
deduplicates the static HTML shell from the SPA view at the same URL. Do this
|
|
135
|
+
as a post-processing step in `build.js` after `buildOG` runs:
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
import { readdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
139
|
+
import { resolve } from 'node:path';
|
|
140
|
+
|
|
141
|
+
function injectCanonicals(distDir, baseUrl) {
|
|
142
|
+
const prefixes = ['shop/product', 'trait', 'gene'];
|
|
143
|
+
for (const prefix of prefixes) {
|
|
144
|
+
const dir = resolve(distDir, prefix);
|
|
145
|
+
if (!existsSync(dir)) continue;
|
|
146
|
+
for (const file of readdirSync(dir)) {
|
|
147
|
+
if (!file.endsWith('.html')) continue;
|
|
148
|
+
const slug = file.slice(0, -5);
|
|
149
|
+
const url = `${baseUrl}/${prefix}/${slug}`;
|
|
150
|
+
const filePath = resolve(dir, file);
|
|
151
|
+
let html = readFileSync(filePath, 'utf-8');
|
|
152
|
+
if (!html.includes('rel="canonical"')) {
|
|
153
|
+
html = html.replace('</head>', ` <link rel="canonical" href="${url}" />\n </head>`);
|
|
154
|
+
writeFileSync(filePath, html);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
114
161
|
## Requirements
|
|
115
162
|
|
|
116
163
|
- Zero config for basic cases (route title + description → OG tags)
|