@panaversity/ksor 0.0.44 → 0.0.45
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/CHANGELOG.md +73 -0
- package/dist/cli.mjs +1 -0
- package/docs/deploying.md +28 -0
- package/package.json +1 -1
- package/templates/scaffold/.agents/skills/intake-interview/SKILL.md +69 -43
- package/templates/scaffold/.claude/skills/intake-interview/SKILL.md +69 -43
- package/templates/scaffold/README.md +512 -287
- package/templates/scaffold/package.json +1 -0
- package/templates/scaffold/system/site/preview.mjs +93 -0
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "pnpm -C system/site dev",
|
|
8
8
|
"build": "ksor build && pnpm -C system/site build",
|
|
9
|
+
"preview": "node system/site/preview.mjs",
|
|
9
10
|
"check": "node .agents/skills/format-checker/check.mjs",
|
|
10
11
|
"provision": "pnpm schema && pnpm grant",
|
|
11
12
|
"serve": "ksor serve",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serve the STATIC EXPORT, so the build has somewhere to land.
|
|
3
|
+
*
|
|
4
|
+
* The site is `output: "export"` — there is no server to start, which is what
|
|
5
|
+
* makes the record hostable anywhere. But it also means the `start` script
|
|
6
|
+
* every other project has does not exist here, and running one is the first
|
|
7
|
+
* instinct after a build; the package manager then reports a missing script,
|
|
8
|
+
* which explains nothing about why there is nothing to start.
|
|
9
|
+
*
|
|
10
|
+
* So this is the answer to "and now how do I look at it". It is node:http and
|
|
11
|
+
* nothing else — no dependency to install and no network fetch, so it works
|
|
12
|
+
* offline and behind a firewall, for the same reason the build itself
|
|
13
|
+
* downloads nothing.
|
|
14
|
+
*
|
|
15
|
+
* Names no package manager on purpose: this file ships in the npm and bun
|
|
16
|
+
* scaffolds too, and those must not carry another manager's vocabulary
|
|
17
|
+
* (asserted by `init-manager.integration.test.ts`).
|
|
18
|
+
*
|
|
19
|
+
* It is a PREVIEW, not a deployment: single process, no compression, no
|
|
20
|
+
* caching headers. Put the directory behind a real web server or a static
|
|
21
|
+
* host for anything else.
|
|
22
|
+
*/
|
|
23
|
+
import { createReadStream, statSync } from "node:fs";
|
|
24
|
+
import { createServer } from "node:http";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import process from "node:process";
|
|
27
|
+
|
|
28
|
+
const ROOT = path.resolve(import.meta.dirname, "out");
|
|
29
|
+
const PORT = Number(process.env.PORT ?? 3000);
|
|
30
|
+
|
|
31
|
+
const TYPES = new Map([
|
|
32
|
+
[".html", "text/html; charset=utf-8"],
|
|
33
|
+
[".md", "text/markdown; charset=utf-8"],
|
|
34
|
+
[".txt", "text/plain; charset=utf-8"],
|
|
35
|
+
[".json", "application/json; charset=utf-8"],
|
|
36
|
+
[".js", "text/javascript; charset=utf-8"],
|
|
37
|
+
[".css", "text/css; charset=utf-8"],
|
|
38
|
+
[".svg", "image/svg+xml"],
|
|
39
|
+
[".png", "image/png"],
|
|
40
|
+
[".jpg", "image/jpeg"],
|
|
41
|
+
[".webp", "image/webp"],
|
|
42
|
+
[".ico", "image/x-icon"],
|
|
43
|
+
[".woff2", "font/woff2"],
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
statSync(ROOT);
|
|
48
|
+
} catch {
|
|
49
|
+
console.error(`preview: ${ROOT} does not exist — run the build first.`);
|
|
50
|
+
process.exit(3);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The file a request resolves to, or null when it escapes the export. */
|
|
54
|
+
function resolve(urlPath) {
|
|
55
|
+
const decoded = decodeURIComponent(urlPath.split("?")[0]);
|
|
56
|
+
// Contain every request inside the export: a `..` that resolves outside it
|
|
57
|
+
// is refused rather than served, even in a preview.
|
|
58
|
+
const target = path.resolve(ROOT, `.${decoded}`);
|
|
59
|
+
if (target !== ROOT && !target.startsWith(ROOT + path.sep)) return null;
|
|
60
|
+
|
|
61
|
+
for (const candidate of [target, path.join(target, "index.html"), `${target}.html`]) {
|
|
62
|
+
try {
|
|
63
|
+
if (statSync(candidate).isFile()) return candidate;
|
|
64
|
+
} catch {
|
|
65
|
+
// Try the next shape.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
createServer((req, res) => {
|
|
72
|
+
const file = resolve(req.url ?? "/");
|
|
73
|
+
if (file === null) {
|
|
74
|
+
const notFound = path.join(ROOT, "404.html");
|
|
75
|
+
try {
|
|
76
|
+
statSync(notFound);
|
|
77
|
+
res.writeHead(404, { "content-type": "text/html; charset=utf-8" });
|
|
78
|
+
createReadStream(notFound).pipe(res);
|
|
79
|
+
return;
|
|
80
|
+
} catch {
|
|
81
|
+
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
82
|
+
res.end("404\n");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
res.writeHead(200, {
|
|
87
|
+
"content-type": TYPES.get(path.extname(file)) ?? "application/octet-stream",
|
|
88
|
+
});
|
|
89
|
+
createReadStream(file).pipe(res);
|
|
90
|
+
}).listen(PORT, () => {
|
|
91
|
+
console.log(`preview: serving ${path.relative(process.cwd(), ROOT)} on http://localhost:${PORT}`);
|
|
92
|
+
console.log(" this is the STATIC EXPORT — the same bytes a host would serve.");
|
|
93
|
+
});
|