fastscript 0.1.0 → 1.0.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +9 -2
  2. package/README.md +15 -0
  3. package/package.json +19 -5
  4. package/src/build.mjs +15 -3
  5. package/src/compat.mjs +10 -2
  6. package/src/create.mjs +6 -6
  7. package/src/db-cli.mjs +9 -13
  8. package/src/fs-normalize.mjs +215 -7
  9. package/src/language-spec.mjs +58 -0
  10. package/src/module-loader.mjs +47 -0
  11. package/.github/workflows/ci.yml +0 -17
  12. package/Dockerfile +0 -9
  13. package/app/api/auth.js +0 -10
  14. package/app/api/hello.js +0 -3
  15. package/app/api/upload.js +0 -9
  16. package/app/api/webhook.js +0 -10
  17. package/app/db/migrations/001_init.js +0 -6
  18. package/app/db/seed.js +0 -5
  19. package/app/env.schema.js +0 -6
  20. package/app/middleware.fs +0 -7
  21. package/app/pages/404.fs +0 -3
  22. package/app/pages/_layout.fs +0 -17
  23. package/app/pages/benchmarks.fs +0 -15
  24. package/app/pages/docs/index.fs +0 -16
  25. package/app/pages/index.fs +0 -22
  26. package/app/pages/private.fs +0 -12
  27. package/app/pages/showcase.fs +0 -14
  28. package/app/styles.css +0 -14
  29. package/docs/AI_CONTEXT_PACK_V1.md +0 -25
  30. package/docs/DEPLOY_GUIDE.md +0 -14
  31. package/docs/INCIDENT_PLAYBOOK.md +0 -18
  32. package/docs/INTEROP_RULES.md +0 -7
  33. package/docs/PLUGIN_API_CONTRACT.md +0 -22
  34. package/ecosystem.config.cjs +0 -1
  35. package/examples/fullstack/README.md +0 -10
  36. package/examples/fullstack/app/api/orders.js +0 -8
  37. package/examples/fullstack/app/db/migrations/001_init.js +0 -3
  38. package/examples/fullstack/app/db/seed.js +0 -3
  39. package/examples/fullstack/app/jobs/send-order-email.js +0 -4
  40. package/examples/fullstack/app/pages/_layout.fs +0 -1
  41. package/examples/fullstack/app/pages/index.fs +0 -3
  42. package/examples/startup-mvp/README.md +0 -8
  43. package/examples/startup-mvp/app/api/cart.js +0 -9
  44. package/examples/startup-mvp/app/api/checkout.js +0 -8
  45. package/examples/startup-mvp/app/db/migrations/001_products.js +0 -6
  46. package/examples/startup-mvp/app/jobs/send-receipt.js +0 -4
  47. package/examples/startup-mvp/app/pages/_layout.fs +0 -3
  48. package/examples/startup-mvp/app/pages/dashboard/index.fs +0 -9
  49. package/examples/startup-mvp/app/pages/index.fs +0 -18
  50. package/scripts/bench-report.mjs +0 -36
  51. package/scripts/release.mjs +0 -21
  52. package/scripts/smoke-dev.mjs +0 -78
  53. package/scripts/smoke-start.mjs +0 -41
  54. package/scripts/test-auth.mjs +0 -26
  55. package/scripts/test-db.mjs +0 -31
  56. package/scripts/test-jobs.mjs +0 -15
  57. package/scripts/test-middleware.mjs +0 -37
  58. package/scripts/test-roundtrip.mjs +0 -44
  59. package/scripts/test-validation.mjs +0 -17
  60. package/scripts/test-webhook-storage.mjs +0 -22
  61. package/spec/FASTSCRIPT_1000_BUILD_LIST.md +0 -1090
  62. package/vercel.json +0 -15
  63. package/vscode/fastscript-language/README.md +0 -12
  64. package/vscode/fastscript-language/extension.js +0 -24
  65. package/vscode/fastscript-language/language-configuration.json +0 -6
  66. package/vscode/fastscript-language/lsp/server.cjs +0 -27
  67. package/vscode/fastscript-language/lsp/smoke-test.cjs +0 -1
  68. package/vscode/fastscript-language/package.json +0 -36
  69. package/vscode/fastscript-language/snippets/fastscript.code-snippets +0 -24
  70. package/vscode/fastscript-language/syntaxes/fastscript.tmLanguage.json +0 -21
  71. package/wrangler.toml +0 -5
package/app/api/auth.js DELETED
@@ -1,10 +0,0 @@
1
- export async function POST(ctx) {
2
- const user = { id: "u_1", name: "Dev" };
3
- ctx.auth.login(user);
4
- return ctx.helpers.json({ ok: true, user });
5
- }
6
-
7
- export async function DELETE(ctx) {
8
- ctx.auth.logout();
9
- return ctx.helpers.json({ ok: true });
10
- }
package/app/api/hello.js DELETED
@@ -1,3 +0,0 @@
1
- export async function GET() {
2
- return { status: 200, json: { ok: true, message: "Hello from FastScript API" } };
3
- }
package/app/api/upload.js DELETED
@@ -1,9 +0,0 @@
1
- export const schemas = {
2
- POST: { key: "string", content: "string" }
3
- };
4
-
5
- export async function POST(ctx) {
6
- const body = await ctx.input.validateBody(schemas.POST);
7
- const put = ctx.storage.put(body.key, Buffer.from(body.content, "utf8"));
8
- return ctx.helpers.json({ ok: true, ...put, url: ctx.storage.url(body.key) });
9
- }
@@ -1,10 +0,0 @@
1
- import { verifyWebhookRequest } from "../../src/webhook.mjs";
2
-
3
- export async function POST(ctx) {
4
- const result = await verifyWebhookRequest(ctx.req, {
5
- secret: process.env.WEBHOOK_SECRET || "dev-secret",
6
- replayDir: ".fastscript"
7
- });
8
- if (!result.ok) return ctx.helpers.json({ ok: false, reason: result.reason }, 401);
9
- return ctx.helpers.json({ ok: true });
10
- }
@@ -1,6 +0,0 @@
1
- export async function up(db) {
2
- const users = db.collection("users");
3
- if (!users.get("u_1")) {
4
- users.set("u_1", { id: "u_1", name: "Dev" });
5
- }
6
- }
package/app/db/seed.js DELETED
@@ -1,5 +0,0 @@
1
- export async function seed(db) {
2
- db.transaction((tx) => {
3
- tx.collection("posts").set("hello", { id: "hello", title: "First Post", published: true });
4
- });
5
- }
package/app/env.schema.js DELETED
@@ -1,6 +0,0 @@
1
- export const schema = {
2
- SESSION_SECRET: "string?",
3
- DATABASE_URL: "string?",
4
- REDIS_URL: "string?",
5
- NODE_ENV: "string?",
6
- };
package/app/middleware.fs DELETED
@@ -1,7 +0,0 @@
1
- export async function middleware(ctx, next) {
2
- const protectedRoute = ctx.pathname.startsWith("/private");
3
- if (protectedRoute && !ctx.user) {
4
- return ctx.helpers.redirect("/");
5
- }
6
- return next();
7
- }
package/app/pages/404.fs DELETED
@@ -1,3 +0,0 @@
1
- export default function NotFound() {
2
- return `<section><h1>404</h1><p>Page not found.</p></section>`;
3
- }
@@ -1,17 +0,0 @@
1
- export default function Layout({ content, pathname, user }) {
2
- return `
3
- <header class="nav">
4
- <a href="/">FastScript</a>
5
- <nav>
6
- <a href="/">Home</a>
7
- <a href="/docs">Docs</a>
8
- <a href="/benchmarks">Benchmarks</a>
9
- <a href="/showcase">Showcase</a>
10
- <a href="/private">Private</a>
11
- </nav>
12
- <small>${user ? "Signed in" : "Guest"}</small>
13
- </header>
14
- <main class="page">${content}</main>
15
- <footer class="footer">Built with FastScript • JS-first • .fs-native</footer>
16
- `;
17
- }
@@ -1,15 +0,0 @@
1
- export default function Benchmarks() {
2
- return `
3
- <section>
4
- <p class="eyebrow">Benchmarks</p>
5
- <h1>Real budgets for 3G-first apps</h1>
6
- <div class="grid">
7
- <article><h3>JS first load</h3><p><strong>1.43KB</strong> gzip (budget 30KB)</p></article>
8
- <article><h3>CSS first load</h3><p><strong>0.38KB</strong> gzip (budget 10KB)</p></article>
9
- <article><h3>Build speed</h3><p>Sub-second warm build target</p></article>
10
- <article><h3>Quality</h3><p>validate + smoke + tests</p></article>
11
- </div>
12
- <p>Report generated by <code>npm run bench:report</code>.</p>
13
- </section>
14
- `;
15
- }
@@ -1,16 +0,0 @@
1
- export default function DocsIndex() {
2
- return `
3
- <section>
4
- <p class="eyebrow">Docs</p>
5
- <h1>FastScript Documentation</h1>
6
- <ul>
7
- <li><code>pages/</code> for file-based routing</li>
8
- <li><code>api/</code> for server endpoints</li>
9
- <li><code>middleware.fs</code> for guards/security</li>
10
- <li><code>db/</code> for migrations and seeds</li>
11
- <li><code>jobs/</code> for worker handlers</li>
12
- </ul>
13
- <p>Quality gate: <code>npm run qa:all</code></p>
14
- </section>
15
- `;
16
- }
@@ -1,22 +0,0 @@
1
- export default function Home() {
2
- return `
3
- <section class="hero">
4
- <p class="eyebrow">FastScript v0.1</p>
5
- <h1>Full-stack speed with readable .fs syntax.</h1>
6
- <p>SSR, API routes, auth, DB, jobs, storage, and deploy adapters in one stack.</p>
7
- <div class="hero-links">
8
- <a href="/docs">Read docs</a>
9
- <a href="/benchmarks">See benchmarks</a>
10
- <a href="/showcase">View showcase</a>
11
- </div>
12
- </section>
13
- `;
14
- }
15
-
16
- export function hydrate({ root }) {
17
- for (const a of root.querySelectorAll('.hero-links a')) {
18
- a.style.transition = 'opacity .2s ease';
19
- a.addEventListener('mouseenter', () => (a.style.opacity = '0.8'));
20
- a.addEventListener('mouseleave', () => (a.style.opacity = '1'));
21
- }
22
- }
@@ -1,12 +0,0 @@
1
- export default function PrivatePage({ user }) {
2
- return `<section><h1>Private</h1><p>Hello ${user?.name ?? "anonymous"}</p></section>`;
3
- }
4
-
5
- export async function GET(ctx) {
6
- try {
7
- const user = ctx.auth.requireUser();
8
- return ctx.helpers.json({ ok: true, user });
9
- } catch {
10
- return ctx.helpers.redirect("/");
11
- }
12
- }
@@ -1,14 +0,0 @@
1
- export default function Showcase() {
2
- return `
3
- <section>
4
- <p class="eyebrow">Showcase</p>
5
- <h1>What you can build with FastScript</h1>
6
- <div class="grid">
7
- <article><h3>Starter app</h3><p>SSR + hydration + actions.</p></article>
8
- <article><h3>Commerce API</h3><p>Orders endpoint + queue job email.</p></article>
9
- <article><h3>Secure webhook</h3><p>Signature verify + replay protection.</p></article>
10
- <article><h3>Portable exports</h3><p>.fs -> .js/.ts with one command.</p></article>
11
- </div>
12
- </section>
13
- `;
14
- }
package/app/styles.css DELETED
@@ -1,14 +0,0 @@
1
- :root { color-scheme: dark; }
2
- * { box-sizing: border-box; }
3
- body { margin: 0; font: 16px/1.7 ui-sans-serif, system-ui; background: #050505; color: #fff; }
4
- .nav { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 14px 24px; border-bottom: 1px solid #1f1f1f; position: sticky; top: 0; background: rgba(5,5,5,.9); backdrop-filter: blur(6px); }
5
- .nav nav a { color: #d3d3ff; text-decoration: none; margin-right: 12px; }
6
- .page { max-width: 1080px; margin: 0 auto; padding: 44px 24px; }
7
- .hero h1, h1 { font-size: clamp(2rem, 5vw, 3.6rem); line-height: 1.07; margin: 0 0 12px; }
8
- .eyebrow { color: #9f92ff; font-size: 12px; text-transform: uppercase; letter-spacing: .12em; }
9
- .hero-links { display: flex; gap: 12px; margin-top: 16px; }
10
- .hero-links a { color: #fff; border: 1px solid #353535; padding: 8px 12px; border-radius: 10px; text-decoration: none; }
11
- .grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 14px; }
12
- .grid article { border: 1px solid #202020; border-radius: 12px; padding: 14px; background: #090909; }
13
- .footer { border-top: 1px solid #1f1f1f; padding: 24px; color: #8a8a8a; }
14
- @media (max-width: 800px) { .grid { grid-template-columns: 1fr; } .nav { flex-wrap: wrap; } }
@@ -1,25 +0,0 @@
1
- # FastScript AI Context Pack v1
2
-
3
- ## Core Contracts
4
- - `.fs` primary, `.js` compatible.
5
- - Route pages live in `app/pages`.
6
- - API routes live in `app/api`.
7
- - Optional `app/middleware.fs` for global middleware.
8
- - Optional `load(ctx)` and HTTP methods (`GET/POST/PUT/PATCH/DELETE`).
9
-
10
- ## Validation
11
- - Use `ctx.input.validateBody(schema)` and `ctx.input.validateQuery(schema)`.
12
- - Use `schemas` export in route modules to auto-enforce request shape.
13
-
14
- ## Runtime
15
- - SSR + hydration (`export function hydrate({ root })`).
16
- - Queue available via `ctx.queue`.
17
- - DB available via `ctx.db`.
18
- - Auth available via `ctx.auth`.
19
-
20
- ## Quality Gates
21
- - `npm run validate`
22
- - `npm run test:core`
23
- - `npm run smoke:dev`
24
- - `npm run smoke:start`
25
- - `npm run qa:all`
@@ -1,14 +0,0 @@
1
- # FastScript Deploy Guide
2
-
3
- ## Node/PM2
4
- - `npm run build`
5
- - `npm run deploy:node`
6
- - `pm2 start ecosystem.config.cjs`
7
-
8
- ## Vercel
9
- - `npm run deploy:vercel`
10
- - import project in Vercel
11
-
12
- ## Cloudflare
13
- - `npm run deploy:cloudflare`
14
- - `wrangler deploy`
@@ -1,18 +0,0 @@
1
- # FastScript Incident Playbook
2
-
3
- ## Rollback
4
- 1. Keep previous `dist/` artifact.
5
- 2. Stop current process.
6
- 3. Start previous artifact with `fastscript start`.
7
-
8
- ## Session Key Rotation
9
- 1. Set new `SESSION_SECRET`.
10
- 2. Restart app.
11
- 3. Existing sessions are invalidated.
12
-
13
- ## Backup
14
- - Back up `.fastscript/` database and job files every hour.
15
-
16
- ## Verification
17
- - Run `npm run smoke:start`.
18
- - Check logs for `request_error` and high 5xx rate.
@@ -1,7 +0,0 @@
1
- # FastScript Interop Rules
2
-
3
- 1. Prefer ESM imports.
4
- 2. CJS is supported via esbuild bundling.
5
- 3. `.fs` can import `.js/.mjs/.cjs/.json`.
6
- 4. `importAny()` handles default-only modules.
7
- 5. Use `resolveExport(mod, ["named", "default"])` for unknown module shapes.
@@ -1,22 +0,0 @@
1
- # FastScript Plugin API Contract
2
-
3
- ## Hook Surface
4
- - `middleware(ctx, next)`
5
- - `onBuildStart(ctx)`
6
- - `onBuildEnd(ctx)`
7
- - `onRequestStart(ctx)`
8
- - `onRequestEnd(ctx)`
9
-
10
- ## Plugin Shape
11
- ```js
12
- export default {
13
- name: "my-plugin",
14
- setup(api) {
15
- api.hooks.middleware(async (ctx, next) => next());
16
- }
17
- }
18
- ```
19
-
20
- ## Stability
21
- - Contract version: `1`.
22
- - Breaking changes require major version bump.
@@ -1 +0,0 @@
1
- module.exports = { apps: [{ name: "fastscript-app", script: "node", args: "./src/cli.mjs start", env: { NODE_ENV: "production", PORT: 4173 } }] };
@@ -1,10 +0,0 @@
1
- # FastScript Fullstack Example
2
-
3
- Features:
4
- - SSR pages + hydrate
5
- - API actions with schema validation
6
- - Middleware route guards
7
- - Session auth
8
- - DB migration + seed
9
- - Queue + worker
10
- - Webhook signature verification helper
@@ -1,8 +0,0 @@
1
- export const schemas = { POST: { sku: "string", qty: "int" } };
2
- export async function POST(ctx) {
3
- const body = await ctx.input.validateBody(schemas.POST);
4
- const order = { id: Date.now().toString(36), ...body };
5
- ctx.db.collection("orders").set(order.id, order);
6
- ctx.queue.enqueue("send-order-email", { orderId: order.id });
7
- return ctx.helpers.json({ ok: true, order });
8
- }
@@ -1,3 +0,0 @@
1
- export async function up(db) {
2
- db.collection("orders");
3
- }
@@ -1,3 +0,0 @@
1
- export async function seed(db) {
2
- db.collection("orders").set("demo", { id: "demo", sku: "starter", qty: 1 });
3
- }
@@ -1,4 +0,0 @@
1
- export const name = "send-order-email";
2
- export async function handle(payload) {
3
- console.log("email job", payload.orderId);
4
- }
@@ -1 +0,0 @@
1
- export default function Layout({ content }) { return `<main>${content}</main>`; }
@@ -1,3 +0,0 @@
1
- export default function Home() {
2
- return `<section><h1>FastScript Fullstack</h1><p>Production-style starter.</p></section>`;
3
- }
@@ -1,8 +0,0 @@
1
- # Startup MVP (FastScript)
2
-
3
- - Product catalog page
4
- - Cart + checkout APIs
5
- - Queue-backed receipt job
6
- - Dashboard orders page
7
-
8
- Use this folder as your startup baseline.
@@ -1,9 +0,0 @@
1
- export const schemas = { POST: { productId: "string", qty: "int" } };
2
-
3
- export async function POST(ctx) {
4
- const body = await ctx.input.validateBody(schemas.POST);
5
- const cart = ctx.db.collection("carts");
6
- const id = `c_${Date.now()}`;
7
- cart.set(id, { id, ...body });
8
- return ctx.helpers.json({ ok: true, id });
9
- }
@@ -1,8 +0,0 @@
1
- export async function POST(ctx) {
2
- const items = ctx.db.collection("carts").all();
3
- const total = items.reduce((s, i) => s + i.qty * 10, 0);
4
- const id = `o_${Date.now()}`;
5
- ctx.db.collection("orders").set(id, { id, total, status: "paid" });
6
- ctx.queue.enqueue("send-receipt", { orderId: id });
7
- return ctx.helpers.json({ ok: true, orderId: id, total });
8
- }
@@ -1,6 +0,0 @@
1
- export async function up(db) {
2
- const products = db.collection("products");
3
- if (!products.get("p1")) products.set("p1", { id: "p1", name: "Starter Plan", price: 19 });
4
- if (!products.get("p2")) products.set("p2", { id: "p2", name: "Growth Plan", price: 49 });
5
- if (!products.get("p3")) products.set("p3", { id: "p3", name: "Scale Plan", price: 99 });
6
- }
@@ -1,4 +0,0 @@
1
- export const name = "send-receipt";
2
- export async function handle(payload) {
3
- console.log("send receipt", payload.orderId);
4
- }
@@ -1,3 +0,0 @@
1
- export default function Layout({ content }) {
2
- return `<main style="max-width:1100px;margin:0 auto;padding:24px;font-family:ui-sans-serif,system-ui">${content}</main>`;
3
- }
@@ -1,9 +0,0 @@
1
- export async function load(ctx) {
2
- const orders = ctx.db.collection("orders").all();
3
- return { orders };
4
- }
5
-
6
- export default function Dashboard({ orders }) {
7
- const rows = (orders || []).map((o) => `<li>${o.id} • ${o.total} • ${o.status}</li>`).join("");
8
- return `<section><h1>Dashboard</h1><ul>${rows}</ul></section>`;
9
- }
@@ -1,18 +0,0 @@
1
- export async function load(ctx) {
2
- const products = ctx.db.collection("products").all();
3
- return { products };
4
- }
5
-
6
- export default function Home({ products }) {
7
- const cards = (products || []).map((p) => `<article style="border:1px solid #ddd;padding:12px;border-radius:10px"><h3>${p.name}</h3><p>$${p.price}</p><button data-add="${p.id}">Add to cart</button></article>`).join("");
8
- return `<section><h1>Startup MVP Store</h1><div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px">${cards}</div><p><a href="/dashboard">Dashboard</a></p></section>`;
9
- }
10
-
11
- export function hydrate({ root }) {
12
- for (const b of root.querySelectorAll('[data-add]')) {
13
- b.addEventListener('click', async () => {
14
- await fetch('/api/cart', { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify({ productId: b.getAttribute('data-add'), qty: 1 })});
15
- alert('Added to cart');
16
- });
17
- }
18
- }
@@ -1,36 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
3
- import { gzipSync } from "node:zlib";
4
- import { performance } from "node:perf_hooks";
5
- import { runBuild } from "../src/build.mjs";
6
-
7
- function gzipSize(path) {
8
- if (!existsSync(path)) return 0;
9
- return gzipSync(readFileSync(path), { level: 9 }).byteLength;
10
- }
11
-
12
- function kb(n) {
13
- return (n / 1024).toFixed(2);
14
- }
15
-
16
- const t0 = performance.now();
17
- await runBuild();
18
- const t1 = performance.now();
19
-
20
- const dist = resolve('dist');
21
- const manifest = JSON.parse(readFileSync(join(dist, 'fastscript-manifest.json'), 'utf8'));
22
- const jsAssets = [join(dist, 'router.js')];
23
- if (manifest.layout) jsAssets.push(join(dist, manifest.layout.replace(/^\.\//, '')));
24
- const home = manifest.routes.find((r) => r.path === '/');
25
- if (home?.module) jsAssets.push(join(dist, home.module.replace(/^\.\//, '')));
26
- const cssAssets = [join(dist, 'styles.css')];
27
-
28
- const js = jsAssets.reduce((s, p) => s + gzipSize(p), 0);
29
- const css = cssAssets.reduce((s, p) => s + gzipSize(p), 0);
30
-
31
- const md = `# FastScript Benchmark Report\n\n- Build time: ${(t1 - t0).toFixed(2)}ms\n- Routes: ${manifest.routes.length}\n- API routes: ${manifest.apiRoutes?.length ?? 0}\n- JS first-load gzip: ${kb(js)}KB\n- CSS first-load gzip: ${kb(css)}KB\n\n## Budgets\n- JS budget (30KB): ${js <= 30 * 1024 ? 'PASS' : 'FAIL'}\n- CSS budget (10KB): ${css <= 10 * 1024 ? 'PASS' : 'FAIL'}\n`;
32
-
33
- const outDir = resolve('benchmarks');
34
- mkdirSync(outDir, { recursive: true });
35
- writeFileSync(join(outDir, 'latest-report.md'), md, 'utf8');
36
- console.log('bench report written: benchmarks/latest-report.md');
@@ -1,21 +0,0 @@
1
- import { readFileSync, writeFileSync, existsSync } from "node:fs";
2
-
3
- const pkgPath = "package.json";
4
- const changelogPath = "CHANGELOG.md";
5
-
6
- const level = process.argv[2] || "patch";
7
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
8
- const [maj, min, pat] = pkg.version.split(".").map(Number);
9
- let next = [maj, min, pat];
10
- if (level === "major") next = [maj + 1, 0, 0];
11
- else if (level === "minor") next = [maj, min + 1, 0];
12
- else next = [maj, min, pat + 1];
13
-
14
- pkg.version = next.join(".");
15
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
16
-
17
- const entry = `## v${pkg.version} - ${new Date().toISOString().slice(0, 10)}\n- release prep\n\n`;
18
- const prev = existsSync(changelogPath) ? readFileSync(changelogPath, "utf8") : "# Changelog\n\n";
19
- writeFileSync(changelogPath, prev + entry, "utf8");
20
-
21
- console.log(`version bumped to ${pkg.version}`);
@@ -1,78 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { setTimeout as sleep } from "node:timers/promises";
3
-
4
- async function waitFor(url, ms = 20000) {
5
- const start = Date.now();
6
- let lastErr = null;
7
- while (Date.now() - start < ms) {
8
- try {
9
- const r = await fetch(url);
10
- if (r.status >= 200 && r.status < 600) return r;
11
- } catch (e) {
12
- lastErr = e;
13
- }
14
- await sleep(300);
15
- }
16
- throw new Error(`Timeout waiting for ${url}${lastErr ? `: ${lastErr.message}` : ""}`);
17
- }
18
-
19
- const proc = spawn(process.execPath, ["./src/cli.mjs", "dev"], {
20
- cwd: process.cwd(),
21
- stdio: ["ignore", "pipe", "pipe"],
22
- env: { ...process.env },
23
- });
24
-
25
- let output = "";
26
- proc.stdout.on("data", (d) => (output += d.toString()));
27
- proc.stderr.on("data", (d) => (output += d.toString()));
28
-
29
- try {
30
- await waitFor("http://localhost:4173/");
31
-
32
- const home = await fetch("http://localhost:4173/");
33
- if (home.status !== 200) throw new Error(`Home status ${home.status}`);
34
- const html = await home.text();
35
- if (!html.includes("FastScript")) throw new Error("Home SSR did not include FastScript text");
36
-
37
- const api = await fetch("http://localhost:4173/api/hello");
38
- if (api.status !== 200) throw new Error(`API status ${api.status}`);
39
- const apiJson = await api.json();
40
- if (!apiJson.ok) throw new Error("API JSON did not return ok=true");
41
-
42
- const privateRes = await fetch("http://localhost:4173/private", { redirect: "manual" });
43
- if (!(privateRes.status === 302 || privateRes.status === 307)) {
44
- throw new Error(`Expected redirect on /private, got ${privateRes.status}`);
45
- }
46
-
47
- const login = await fetch("http://localhost:4173/api/auth", {
48
- method: "POST",
49
- headers: { "content-type": "application/json" },
50
- body: JSON.stringify({}),
51
- redirect: "manual",
52
- });
53
- if (login.status !== 200) throw new Error(`Auth login failed: ${login.status}`);
54
- const setCookie = login.headers.get("set-cookie");
55
- if (!setCookie || !setCookie.includes("fs_session=")) throw new Error("Missing fs_session cookie on login");
56
-
57
- const bad = await fetch("http://localhost:4173/api/auth", {
58
- method: "POST",
59
- headers: { "content-type": "application/json", accept: "application/json" },
60
- body: "{bad",
61
- });
62
- if (bad.status !== 400) throw new Error(`Expected 400 on invalid JSON, got ${bad.status}`);
63
-
64
- const upload = await fetch("http://localhost:4173/api/upload", {
65
- method: "POST",
66
- headers: { "content-type": "application/json", accept: "application/json" },
67
- body: JSON.stringify({ key: "smoke/one.txt", content: "hello" }),
68
- });
69
- if (upload.status !== 200) throw new Error(`Upload failed: ${upload.status}`);
70
- const up = await upload.json();
71
- const blob = await fetch(`http://localhost:4173${up.url}`);
72
- if (blob.status !== 200) throw new Error(`Uploaded blob fetch failed: ${blob.status}`);
73
-
74
- console.log("smoke-dev pass: SSR, API, middleware redirect, auth cookie, validation, upload");
75
- } finally {
76
- proc.kill("SIGTERM");
77
- await sleep(400);
78
- }
@@ -1,41 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { setTimeout as sleep } from "node:timers/promises";
3
-
4
- async function waitFor(url, ms = 20000) {
5
- const start = Date.now();
6
- while (Date.now() - start < ms) {
7
- try {
8
- const r = await fetch(url);
9
- if (r.status >= 200) return;
10
- } catch {}
11
- await sleep(300);
12
- }
13
- throw new Error(`Timeout waiting for ${url}`);
14
- }
15
-
16
- const build = spawn(process.execPath, ["./src/cli.mjs", "build"], { cwd: process.cwd(), stdio: "inherit" });
17
- await new Promise((resolve, reject) => {
18
- build.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`build failed: ${code}`))));
19
- });
20
-
21
- const proc = spawn(process.execPath, ["./src/cli.mjs", "start"], {
22
- cwd: process.cwd(),
23
- stdio: ["ignore", "pipe", "pipe"],
24
- env: { ...process.env, PORT: "4173", NODE_ENV: "production" },
25
- });
26
-
27
- try {
28
- await waitFor("http://localhost:4173/");
29
- const home = await fetch("http://localhost:4173/");
30
- if (home.status !== 200) throw new Error(`start home status ${home.status}`);
31
- const reqId = home.headers.get("x-request-id");
32
- if (!reqId) throw new Error("missing x-request-id header");
33
-
34
- const api = await fetch("http://localhost:4173/api/hello", { headers: { accept: "application/json" } });
35
- if (api.status !== 200) throw new Error(`start api status ${api.status}`);
36
-
37
- console.log("smoke-start pass: production adapter serving SSR/API with request IDs");
38
- } finally {
39
- proc.kill("SIGTERM");
40
- await sleep(300);
41
- }
@@ -1,26 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { rmSync } from "node:fs";
3
- import { resolve } from "node:path";
4
- import { createSessionManager, parseCookies, serializeCookie } from "../src/auth.mjs";
5
-
6
- const dir = resolve('.tmp-auth-tests');
7
- rmSync(dir, { recursive: true, force: true });
8
-
9
- const sm = createSessionManager({ dir, cookieName: 't' });
10
- const token = sm.create({ id: 'u1' }, 60);
11
- assert.ok(token.split('.').length === 3);
12
- assert.equal(sm.read(token).user.id, 'u1');
13
-
14
- const rotated = sm.rotate(token, 60);
15
- assert.ok(rotated);
16
- assert.equal(sm.read(token), null);
17
- assert.equal(sm.read(rotated).user.id, 'u1');
18
-
19
- sm.delete(rotated);
20
- assert.equal(sm.read(rotated), null);
21
-
22
- const c = serializeCookie('a', 'b', { path: '/', maxAge: 10, httpOnly: true });
23
- assert.ok(c.includes('a=b'));
24
- assert.equal(parseCookies('a=b; x=y').x, 'y');
25
-
26
- console.log('test-auth pass');