fastscript 0.1.0 → 0.1.1
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 +3 -0
- package/package.json +10 -3
- package/.github/workflows/ci.yml +0 -17
- package/Dockerfile +0 -9
- package/app/api/auth.js +0 -10
- package/app/api/hello.js +0 -3
- package/app/api/upload.js +0 -9
- package/app/api/webhook.js +0 -10
- package/app/db/migrations/001_init.js +0 -6
- package/app/db/seed.js +0 -5
- package/app/env.schema.js +0 -6
- package/app/middleware.fs +0 -7
- package/app/pages/404.fs +0 -3
- package/app/pages/_layout.fs +0 -17
- package/app/pages/benchmarks.fs +0 -15
- package/app/pages/docs/index.fs +0 -16
- package/app/pages/index.fs +0 -22
- package/app/pages/private.fs +0 -12
- package/app/pages/showcase.fs +0 -14
- package/app/styles.css +0 -14
- package/docs/AI_CONTEXT_PACK_V1.md +0 -25
- package/docs/DEPLOY_GUIDE.md +0 -14
- package/docs/INCIDENT_PLAYBOOK.md +0 -18
- package/docs/INTEROP_RULES.md +0 -7
- package/docs/PLUGIN_API_CONTRACT.md +0 -22
- package/ecosystem.config.cjs +0 -1
- package/examples/fullstack/README.md +0 -10
- package/examples/fullstack/app/api/orders.js +0 -8
- package/examples/fullstack/app/db/migrations/001_init.js +0 -3
- package/examples/fullstack/app/db/seed.js +0 -3
- package/examples/fullstack/app/jobs/send-order-email.js +0 -4
- package/examples/fullstack/app/pages/_layout.fs +0 -1
- package/examples/fullstack/app/pages/index.fs +0 -3
- package/examples/startup-mvp/README.md +0 -8
- package/examples/startup-mvp/app/api/cart.js +0 -9
- package/examples/startup-mvp/app/api/checkout.js +0 -8
- package/examples/startup-mvp/app/db/migrations/001_products.js +0 -6
- package/examples/startup-mvp/app/jobs/send-receipt.js +0 -4
- package/examples/startup-mvp/app/pages/_layout.fs +0 -3
- package/examples/startup-mvp/app/pages/dashboard/index.fs +0 -9
- package/examples/startup-mvp/app/pages/index.fs +0 -18
- package/scripts/bench-report.mjs +0 -36
- package/scripts/release.mjs +0 -21
- package/scripts/smoke-dev.mjs +0 -78
- package/scripts/smoke-start.mjs +0 -41
- package/scripts/test-auth.mjs +0 -26
- package/scripts/test-db.mjs +0 -31
- package/scripts/test-jobs.mjs +0 -15
- package/scripts/test-middleware.mjs +0 -37
- package/scripts/test-roundtrip.mjs +0 -44
- package/scripts/test-validation.mjs +0 -17
- package/scripts/test-webhook-storage.mjs +0 -22
- package/spec/FASTSCRIPT_1000_BUILD_LIST.md +0 -1090
- package/vercel.json +0 -15
- package/vscode/fastscript-language/README.md +0 -12
- package/vscode/fastscript-language/extension.js +0 -24
- package/vscode/fastscript-language/language-configuration.json +0 -6
- package/vscode/fastscript-language/lsp/server.cjs +0 -27
- package/vscode/fastscript-language/lsp/smoke-test.cjs +0 -1
- package/vscode/fastscript-language/package.json +0 -36
- package/vscode/fastscript-language/snippets/fastscript.code-snippets +0 -24
- package/vscode/fastscript-language/syntaxes/fastscript.tmLanguage.json +0 -21
- package/wrangler.toml +0 -5
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastscript",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "JavaScript-first full-stack framework that is simpler and faster.",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"bin": {
|
|
7
|
-
"fastscript": "
|
|
8
|
+
"fastscript": "src/cli.mjs"
|
|
8
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"CHANGELOG.md"
|
|
15
|
+
],
|
|
9
16
|
"scripts": {
|
|
10
17
|
"dev": "node ./src/cli.mjs dev",
|
|
11
18
|
"start": "node ./src/cli.mjs start",
|
|
@@ -47,4 +54,4 @@
|
|
|
47
54
|
"dependencies": {
|
|
48
55
|
"esbuild": "^0.25.11"
|
|
49
56
|
}
|
|
50
|
-
}
|
|
57
|
+
}
|
package/.github/workflows/ci.yml
DELETED
package/Dockerfile
DELETED
package/app/api/auth.js
DELETED
package/app/api/hello.js
DELETED
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
|
-
}
|
package/app/api/webhook.js
DELETED
|
@@ -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
|
-
}
|
package/app/db/seed.js
DELETED
package/app/env.schema.js
DELETED
package/app/middleware.fs
DELETED
package/app/pages/404.fs
DELETED
package/app/pages/_layout.fs
DELETED
|
@@ -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
|
-
}
|
package/app/pages/benchmarks.fs
DELETED
|
@@ -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
|
-
}
|
package/app/pages/docs/index.fs
DELETED
|
@@ -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
|
-
}
|
package/app/pages/index.fs
DELETED
|
@@ -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
|
-
}
|
package/app/pages/private.fs
DELETED
|
@@ -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
|
-
}
|
package/app/pages/showcase.fs
DELETED
|
@@ -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`
|
package/docs/DEPLOY_GUIDE.md
DELETED
|
@@ -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.
|
package/docs/INTEROP_RULES.md
DELETED
|
@@ -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.
|
package/ecosystem.config.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = { apps: [{ name: "fastscript-app", script: "node", args: "./src/cli.mjs start", env: { NODE_ENV: "production", PORT: 4173 } }] };
|
|
@@ -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 +0,0 @@
|
|
|
1
|
-
export default function Layout({ content }) { return `<main>${content}</main>`; }
|
|
@@ -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,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
|
-
}
|
package/scripts/bench-report.mjs
DELETED
|
@@ -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');
|
package/scripts/release.mjs
DELETED
|
@@ -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}`);
|
package/scripts/smoke-dev.mjs
DELETED
|
@@ -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
|
-
}
|