create-bascik 1.0.0-rc.3 → 1.0.0-rc.5
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/assets/SKILL.md +38 -29
- package/dist/scaffold.d.ts +1 -1
- package/dist/scaffold.js +3 -3
- package/package.json +1 -1
- package/src/scaffold.integration.test.ts +27 -0
- package/src/scaffold.test.ts +3 -2
- package/src/scaffold.ts +3 -3
package/assets/SKILL.md
CHANGED
|
@@ -787,25 +787,28 @@ Components work inside `<head>` to organize metadata and shared links:
|
|
|
787
787
|
|
|
788
788
|
## 8. Build-time Scripts
|
|
789
789
|
|
|
790
|
-
`<script data-bascik-build>` blocks are executed at transpile time as Node.js ESM modules. The script
|
|
790
|
+
`<script data-bascik-build>` blocks are executed at transpile time as Node.js ESM modules. The script exports a default callable function whose returned HTML string replaces the tag. Runs in both dev and build modes.
|
|
791
791
|
|
|
792
792
|
```html
|
|
793
793
|
<script data-bascik-build>
|
|
794
794
|
import { readFile } from 'node:fs/promises';
|
|
795
795
|
import { marked } from 'marked';
|
|
796
|
-
|
|
797
|
-
|
|
796
|
+
|
|
797
|
+
export default async function () {
|
|
798
|
+
const md = await readFile('./content/intro.md', 'utf8');
|
|
799
|
+
return marked(md);
|
|
800
|
+
}
|
|
798
801
|
</script>
|
|
799
802
|
```
|
|
800
803
|
|
|
801
|
-
* Top-level `import` and top-level `await` are supported.
|
|
804
|
+
* Top-level `import` and top-level `await` are supported at module scope.
|
|
802
805
|
* CWD is the project root. Quoted data paths (`readFile('./content/x.md')`) resolve from there.
|
|
803
806
|
* **Importing shared helpers: use `@/` by default.** `import { renderMd } from '@/lib/md-renderer.ts'` resolves against `scripts.importRoot` (default `src`), so the identical import line works from any page or component at any nesting depth. Relative `./` and `../` specifiers still work and resolve against the file that contains the script; use them for helpers that live next to the page or component. Both forms also apply to `data-bascik-server`, `data-bascik-routes`, and the `src="…"` attribute on those tags.
|
|
804
807
|
* **Never use a bare leading `/`.** `import x from '/lib/x.ts'` or `src="/lib/x.ts"` inside a Bascik script is a hard compile error (and a red diagnostic in the VS Code extension), regardless of `onBuildScriptError`. A bare slash is ambiguous between filesystem root and site root. The error names the two valid rewrites: `@/lib/x.ts` (import root) or `./lib/x.ts` (relative). Plain client `<script>` tags are unaffected.
|
|
805
808
|
* **Alias gotchas:** only the exact `@/` prefix is an alias (`@scope/pkg` is a normal package). Aliases are rewritten only inside script blocks; a helper file importing another helper must use `./` or `../`. Add shared build helpers to `pipeline.watchPaths` when outside the pages/components directories. The import-root watcher only invalidates request-time modules, not page compilation caches.
|
|
806
|
-
*
|
|
809
|
+
* Define a callable default export function (`export default async function () { return ...; }`) that returns an HTML string. Standard console methods (`console.log()`, `console.error()`) log to the terminal for debugging only and never become generated markup.
|
|
807
810
|
* Build scripts run before component resolution, so their output can contain component tags.
|
|
808
|
-
* Each uncached build script executes in its own fresh child process, isolated from every other script, regardless of how many siblings are cache misses.
|
|
811
|
+
* Each uncached build script executes in its own fresh child process, isolated from every other script, regardless of how many siblings are cache misses. Results travel across a dedicated result transport. Bounded concurrency is enforced by a memory-aware semaphore, but scripts never share a process or global ESM registry, so detached async output from one script can never bleed into a neighbor.
|
|
809
812
|
* On error, behavior is controlled by three script-specific options in `bascik.config.ts`: `scripts.onBuildScriptError`, `scripts.onRoutesScriptError`, and `scripts.onServerScriptError` (each supports `'warn'`, `'error'`, or `'ignore'`). Defaults are mode-aware: `'warn'` in dev, `'error'` during `--build` and `--server`. For `data-bascik-stream` scripts, an error cannot produce an HTTP 500 because headers are already committed; the slot is emitted empty, the failure is logged at the configured severity, and the document completes.
|
|
810
813
|
* **Stack Trace Remapping:** For `<script data-bascik-build>`, `<script data-bascik-server>`, and `<script data-bascik-stream>` blocks, Bascik automatically intercepts child-process stack traces, filters out noisy Node.js internal files, stack frames, and `Command failed:` headers, and remaps temporary execution files back to your source HTML file and line offset (e.g., `src/pages/dashboard.html:25`). This filters out the noise of internal V8 loader frames and child process execution headers, leaving only the clean, actionable stack trace of your template and helper scripts. In VS Code or terminal emulators, you can Cmd+Click (or Ctrl+Click) the file reference in the error log to jump directly to the failing script's exact line.
|
|
811
814
|
* **Hard error:** combining any of `data-bascik-build`, `data-bascik-routes`, `data-bascik-server`, or `data-bascik-stream` on the same tag throws and aborts the build. Directives are mutually exclusive.
|
|
@@ -887,15 +890,17 @@ With `pipeline.workers: true`, multiple workers share the same cache directory.
|
|
|
887
890
|
|
|
888
891
|
### Rendering and Styling Markdown
|
|
889
892
|
|
|
890
|
-
Install a Markdown parser such as `marked`, read the source in a build script, and
|
|
893
|
+
Install a Markdown parser such as `marked`, read the source in a build script, and return the resulting HTML from the default export:
|
|
891
894
|
|
|
892
895
|
```html
|
|
893
896
|
<script data-bascik-build>
|
|
894
897
|
import { readFile } from 'node:fs/promises';
|
|
895
898
|
import { marked } from 'marked';
|
|
896
899
|
|
|
897
|
-
|
|
898
|
-
|
|
900
|
+
export default async function () {
|
|
901
|
+
const md = await readFile('./content/article.md', 'utf8');
|
|
902
|
+
return marked(md);
|
|
903
|
+
}
|
|
899
904
|
</script>
|
|
900
905
|
```
|
|
901
906
|
|
|
@@ -963,8 +968,8 @@ Use it from any page's `<head>`:
|
|
|
963
968
|
```html
|
|
964
969
|
<head>
|
|
965
970
|
<script data-bascik-build>
|
|
966
|
-
import { canonical } from '
|
|
967
|
-
|
|
971
|
+
import { canonical } from '@/lib/canonical.ts';
|
|
972
|
+
export default async () => await canonical();
|
|
968
973
|
</script>
|
|
969
974
|
</head>
|
|
970
975
|
```
|
|
@@ -1016,25 +1021,28 @@ Dynamic routes allow you to generate multiple static HTML files from a single te
|
|
|
1016
1021
|
```html
|
|
1017
1022
|
<!-- src/pages/blog/[slug].html -->
|
|
1018
1023
|
<script data-bascik-routes>
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1024
|
+
export default async function () {
|
|
1025
|
+
const posts = [
|
|
1026
|
+
{ slug: 'hello-world', title: 'Hello World' },
|
|
1027
|
+
{ slug: 'second-post', title: 'Second Post' }
|
|
1028
|
+
];
|
|
1029
|
+
return posts.map(p => ({
|
|
1030
|
+
params: { slug: p.slug },
|
|
1031
|
+
data: p
|
|
1032
|
+
}));
|
|
1033
|
+
}
|
|
1028
1034
|
</script>
|
|
1029
1035
|
|
|
1030
1036
|
<script data-bascik-build>
|
|
1031
|
-
|
|
1032
|
-
|
|
1037
|
+
export default function () {
|
|
1038
|
+
const { params, data } = JSON.parse(process.env.BASCIK_ROUTE || '{}');
|
|
1039
|
+
return `<h1>${data.title}</h1>`;
|
|
1040
|
+
}
|
|
1033
1041
|
</script>
|
|
1034
1042
|
```
|
|
1035
1043
|
|
|
1036
1044
|
Rules:
|
|
1037
|
-
* Script must
|
|
1045
|
+
* Script must export a callable default function returning an array of route objects with required `params` (matching all bracket names in the template path) and optional `data`. To generate zero routes, return `[]`.
|
|
1038
1046
|
* Route parameters must be URL-safe tokens; characters like `#`, `%`, `&`, `'`, `+`, spaces, leading dots, and Windows device names are disallowed.
|
|
1039
1047
|
* In build scripts, `process.env.BASCIK_ROUTE` provides the current `{ params, data }` payload.
|
|
1040
1048
|
* Dynamic route templates are expanded into concrete static HTML files during `bascik --build` and dev server startup. Route collisions with static pages or other templates cause build errors.
|
|
@@ -1248,7 +1256,7 @@ export default defineConfig({
|
|
|
1248
1256
|
});
|
|
1249
1257
|
```
|
|
1250
1258
|
|
|
1251
|
-
> **Reverse Proxy & CDN Note (`trustProxy`):** When deploying `bascik --server` behind a CDN or load balancer, set `http.trustProxy: true` in `bascik.config.ts`. This ensures per-IP rate limiting and HSTS headers accurately resolve the client from `X-Forwarded-For` and `X-Forwarded-
|
|
1259
|
+
> **Reverse Proxy & CDN Note (`trustProxy`):** When deploying `bascik --server` behind a CDN or load balancer, set `http.trustProxy: true` in `bascik.config.ts`. This ensures per-IP rate limiting, request URL origin reconstruction, and HSTS headers accurately resolve the client and public origin from `X-Forwarded-For`, `X-Forwarded-Proto`, and `X-Forwarded-Host`. Do not enable `trustProxy` if the server is exposed directly to the Internet without a reverse proxy.
|
|
1252
1260
|
|
|
1253
1261
|
> **Health Endpoints & Port Conflicts:** `bascik --server` provides `GET /_health` (returns 200 when ready, 503 during boot or drain). Under `--server`, port conflicts (`EADDRINUSE`) fail fast rather than binding an unexpected port.
|
|
1254
1262
|
|
|
@@ -1393,6 +1401,7 @@ export const POST = async (request: Request): Promise<Response> => {
|
|
|
1393
1401
|
|
|
1394
1402
|
* **Method exports:** `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `HEAD`. Unexported methods return `405 Method Not Allowed` with an accurate `Allow` header.
|
|
1395
1403
|
* **Derived HEAD & Auto OPTIONS:** `HEAD` auto-derives from `GET` (headers only, body stripped); `OPTIONS` auto-responds 204 with `Allow`.
|
|
1404
|
+
* **Catch-all:** a final `[...path]` segment (`src/api/files/[...path].ts`) matches one or more remaining segments (never zero) and gives `params.path` as a decoded `string[]`. Precedence: static, then `[param]`, then catch-all, independent of file order. Malformed encoding, `%2F`/`%5C`, `..` traversal, control characters, and empty interior segments return 400. Single-dot and hidden segments return 404 before matching. It must be a named, final segment; two catch-alls at one position are an error. API routes only.
|
|
1396
1405
|
* **Context argument:** `(request, { params, remoteIp }, { signal })`. Route params are parsed from `[param]` segments. `remoteIp` is proxy-aware when `http.trustProxy` is true. `signal` provides cooperative abort on `http.apiTimeout`.
|
|
1397
1406
|
* **Streaming request body:** `request.body` is a WHATWG stream with `duplex: 'half'`. Handlers call `.json()`, `.text()`, etc. `http.maxBodySize` (default 1 MB) counts streamed bytes and aborts with 413 without memory buffering.
|
|
1398
1407
|
* **Security & secrets:** Handlers run in-process with access to `process.env`. Handler source files in `src/api/` are never served or copied to static builds. Thrown errors return 500 with zero stack/path info leaked to clients. No CORS headers are added by default.
|
|
@@ -1576,7 +1585,7 @@ What to check in compiled output:
|
|
|
1576
1585
|
* **Component resolution:** every custom tag (e.g. `<site-nav>`) should be replaced with expanded HTML. A hyphenated tag still present in `dist/` means no component file matched.
|
|
1577
1586
|
* **Scoped class names:** attributes like `class="bascik__site-nav__nav"` (or a short hash with `minify.identifiers`) confirm CSS scoping ran correctly.
|
|
1578
1587
|
* **Injected `<style>` block:** the `<head>` should contain one combined `<style>` with CSS from all components used on that page.
|
|
1579
|
-
* **Build script output:** `<script data-bascik-build>` is replaced with
|
|
1588
|
+
* **Build script output:** `<script data-bascik-build>` is replaced with the returned HTML string; if missing, check the terminal for a `[bascik] build script error` line.
|
|
1580
1589
|
* **Server script output:** `<script data-bascik-server>` is replaced at request time; if output is missing on a live request, check the terminal for a `[bascik] server script error` line. Remember these scripts run in Node.js, not the browser, they require `bascik --server` or the dev server to execute.
|
|
1581
1590
|
* **Slot and prop values:** verify fallback and injected text appear in the right place.
|
|
1582
1591
|
|
|
@@ -1975,7 +1984,7 @@ Detailed per-framework migration guides live at `/switch/*`. Key patterns that a
|
|
|
1975
1984
|
- **Props:** `defineProps` / component props → `data-bascik-prop-*` attributes (text only).
|
|
1976
1985
|
- **Reactive state:** `ref`, `useState`, etc. → plain `<script>` with vanilla JS. Bascik scopes `id` values so multiple instances stay independent.
|
|
1977
1986
|
- **Routing:** Client-side router → one `.html` file per URL in `src/pages/`. For parameterized routes (`[slug].astro`, `getStaticPaths`, Eleventy pagination), use a dynamic route template such as `src/pages/blog/[slug]/index.html` (trailing-slash URLs) with a `<script data-bascik-routes>` block. Do not generate page files into `src/pages/`. A route param cannot contain `/`; use one template per depth.
|
|
1978
|
-
- **Build-time data:** `onMounted` / `getStaticProps` / frontmatter → `<script data-bascik-build>` (Node.js ESM,
|
|
1987
|
+
- **Build-time data:** `onMounted` / `getStaticProps` / frontmatter → `<script data-bascik-build>` (Node.js ESM, returned HTML string replaces the tag).
|
|
1979
1988
|
- **Content collections (Astro `getCollection`, Eleventy `collections`):** No built-in equivalent. Write a `src/lib/posts.ts` helper that reads `content/` with `fs`, parses front matter with `gray-matter`, validates with `zod`, and sorts by the parsed date (not the file name). Escape every interpolated value. Drafts: skip them when `process.env.BASCIK_BUILD === '1'`. Exclude scripts that read `content/` from `scripts.cache` and add `content/` to `pipeline.watchPaths`.
|
|
1980
1989
|
- **Template logic (Nunjucks/Liquid loops, filters, includes with variables):** Becomes TypeScript helpers in `src/lib/` that return HTML. Only static includes become components. Pages keep `<html>`/`<head>`/`<body>`; a component cannot own them.
|
|
1981
1990
|
- **Feeds, sitemaps, images:** RSS/Atom is a `pipeline.exec` script (`phase: 'post'`) writing to `BASCIK_OUT_DIR`. Sitemap and robots are built in. No image optimization or syntax highlighting built in; do it in your own build step.
|
|
@@ -2016,10 +2025,10 @@ Full guide: `/switch/from-next`. Key Next.js-specific mappings:
|
|
|
2016
2025
|
| Next.js | Bascik |
|
|
2017
2026
|
|---------|--------|
|
|
2018
2027
|
| `app/page.tsx`, `app/posts/[slug]/page.tsx` | `src/pages/index.html`, `src/pages/posts/[slug].html` (`/posts/<slug>`, also served with a trailing slash) |
|
|
2019
|
-
| `generateStaticParams` | `<script data-bascik-routes>`
|
|
2020
|
-
| `generateMetadata` / `metadata` export | A build script in `<head>` that
|
|
2028
|
+
| `generateStaticParams` | `<script data-bascik-routes>` returning `[{ params: { slug: "..." } }]` |
|
|
2029
|
+
| `generateMetadata` / `metadata` export | A build script in `<head>` that returns `<title>` and `<meta>` from a `src/lib` helper; absolute URLs from `BASCIK_SITE_URL` |
|
|
2021
2030
|
| `app/layout.tsx` | Pages keep `<html>`/`<head>`/`<body>`; a head component plus header/footer components |
|
|
2022
|
-
| Server component that maps data to JSX | A `src/lib` helper returning escaped HTML strings,
|
|
2031
|
+
| Server component that maps data to JSX | A `src/lib` helper returning escaped HTML strings, returned by a build script |
|
|
2023
2032
|
| `"use client"` component with `useState`/`useEffect` | A component with a plain `<script>`; persistent state lives in the DOM or `localStorage` |
|
|
2024
2033
|
| `next/link`, `next/image`, `next/font` | `<a>`, `<img>` with `width`/`height`, a self-hosted `@font-face` plus `<link rel="preload">` |
|
|
2025
2034
|
| CSS Modules (`styles.x`) | Paired component `.css`; a class written in the template is scoped |
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare const SITE_HEADER_CSS = ".header {\n position: sticky;\n top: 0
|
|
|
23
23
|
export declare const SITE_HEADER_HTML = "<style>\n.header {\n position: sticky;\n top: 0;\n z-index: 100;\n background: rgba(24, 25, 27, 0.9);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border-bottom: 1px solid var(--border);\n}\n\n.container { max-width: 1140px; margin: 0 auto; padding: 0 28px; }\n\n.nav {\n display: flex;\n align-items: center;\n height: 60px;\n gap: 24px;\n}\n\n.logo {\n font-weight: 700;\n font-size: 1rem;\n color: var(--text);\n letter-spacing: -0.01em;\n flex-shrink: 0;\n}\n.logo:hover { opacity: 0.8; }\n\n.nav-menu {\n list-style: none;\n margin: 0 0 0 auto;\n padding: 0;\n display: flex;\n gap: 4px;\n}\n.nav-menu a {\n display: block;\n padding: 6px 12px;\n font-size: 0.9rem;\n color: var(--text-muted);\n border-radius: var(--r-sm);\n transition: color .15s, background .15s;\n}\n.nav-menu a:hover { color: var(--text); background: var(--elevated); opacity: 1; }\n\n.nav-toggle {\n display: none;\n flex-direction: column;\n justify-content: center;\n gap: 5px;\n width: 36px;\n height: 36px;\n background: none;\n border: none;\n cursor: pointer;\n padding: 6px;\n margin-left: auto;\n border-radius: var(--r-sm);\n}\n.nav-toggle span {\n display: block;\n height: 2px;\n background: var(--text);\n border-radius: 2px;\n}\n\n@media (max-width: 640px) {\n .nav-toggle { display: flex; }\n\n .nav-menu {\n display: none;\n position: absolute;\n top: 60px;\n left: 0;\n right: 0;\n flex-direction: column;\n gap: 0;\n background: var(--surface);\n border-bottom: 1px solid var(--border);\n padding: 8px 16px;\n }\n .nav-menu.is-open { display: flex; }\n .nav-menu li { width: 100%; }\n .nav-menu a { padding: 10px 4px; }\n}\n</style>\n<header class=\"header\">\n <div class=\"container\">\n <nav class=\"nav\">\n <a href=\"/\" class=\"logo\" data-bascik-prop-brand>My Site</a>\n <button id=\"nav-toggle\" class=\"nav-toggle\" aria-label=\"Toggle menu\" aria-expanded=\"false\" data-testid=\"nav-toggle\">\n <span></span><span></span><span></span>\n </button>\n <ul id=\"nav-menu\" class=\"nav-menu\" data-testid=\"nav-menu\">\n <li><a href=\"/\">Home</a></li>\n <li><a href=\"/about\">About</a></li>\n <li><a href=\"/contact\">Contact</a></li>\n </ul>\n </nav>\n </div>\n</header>\n<script>\n var toggle = document.getElementById('nav-toggle');\n var menu = document.getElementById('nav-menu');\n toggle.addEventListener('click', function () {\n var open = toggle.getAttribute('aria-expanded') === 'true';\n toggle.setAttribute('aria-expanded', String(!open));\n menu.classList.toggle('is-open');\n });\n</script>\n";
|
|
24
24
|
export declare const SITE_FOOTER_CSS = ".footer {\n border-top: 1px solid var(--border);\n padding: 28px 0;\n}\n\n.container { max-width: 1140px; margin: 0 auto; padding: 0 28px; }\n\n.footer-inner {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n flex-wrap: wrap;\n}\n\n.footer-copy { font-size: 0.85rem; color: var(--text-muted); }\n\n.footer-nav {\n display: flex;\n gap: 20px;\n}\n.footer-nav a {\n font-size: 0.85rem;\n color: var(--text-muted);\n}\n.footer-nav a:hover { color: var(--text); opacity: 1; }\n\n@media (max-width: 480px) {\n .footer-inner { flex-direction: column; align-items: flex-start; }\n}\n";
|
|
25
25
|
/** Footer with brand prop and build-time year. */
|
|
26
|
-
export declare const SITE_FOOTER_HTML = "<style>\n.footer {\n border-top: 1px solid var(--border);\n padding: 28px 0;\n}\n\n.container { max-width: 1140px; margin: 0 auto; padding: 0 28px; }\n\n.footer-inner {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n flex-wrap: wrap;\n}\n\n.footer-copy { font-size: 0.85rem; color: var(--text-muted); }\n\n.footer-nav {\n display: flex;\n gap: 20px;\n}\n.footer-nav a {\n font-size: 0.85rem;\n color: var(--text-muted);\n}\n.footer-nav a:hover { color: var(--text); opacity: 1; }\n\n@media (max-width: 480px) {\n .footer-inner { flex-direction: column; align-items: flex-start; }\n}\n</style>\n<footer class=\"footer\">\n <div class=\"container\">\n <div class=\"footer-inner\">\n <span class=\"footer-copy\">\n © <script data-bascik-build>
|
|
26
|
+
export declare const SITE_FOOTER_HTML = "<style>\n.footer {\n border-top: 1px solid var(--border);\n padding: 28px 0;\n}\n\n.container { max-width: 1140px; margin: 0 auto; padding: 0 28px; }\n\n.footer-inner {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n flex-wrap: wrap;\n}\n\n.footer-copy { font-size: 0.85rem; color: var(--text-muted); }\n\n.footer-nav {\n display: flex;\n gap: 20px;\n}\n.footer-nav a {\n font-size: 0.85rem;\n color: var(--text-muted);\n}\n.footer-nav a:hover { color: var(--text); opacity: 1; }\n\n@media (max-width: 480px) {\n .footer-inner { flex-direction: column; align-items: flex-start; }\n}\n</style>\n<footer class=\"footer\">\n <div class=\"container\">\n <div class=\"footer-inner\">\n <span class=\"footer-copy\">\n © <script data-bascik-build>export default () => String(new Date().getFullYear());</script>\n <span data-bascik-prop-brand>My Site</span>\n </span>\n <nav class=\"footer-nav\" aria-label=\"Footer navigation\">\n <a href=\"/\">Home</a>\n <a href=\"/about\">About</a>\n <a href=\"/contact\">Contact</a>\n </nav>\n </div>\n </div>\n</footer>\n";
|
|
27
27
|
export declare const FEAT_CARD_CSS = ".fcard {\n background: var(--elevated);\n border: 1px solid var(--border);\n border-radius: var(--r);\n padding: 24px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n transition: border-color .2s, box-shadow .2s, transform .2s;\n}\n.fcard:hover {\n border-color: var(--border-hover);\n box-shadow: 0 0 0 1px var(--accent-dim), 0 8px 32px rgba(0,0,0,0.3);\n transform: translateY(-2px);\n}\n\n.fcard-header {\n font-size: 0.7rem;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: .1em;\n color: var(--accent);\n}\n.fcard-header:empty { display: none; }\n\n.fcard-body { flex: 1; }\n.fcard-body h3 { margin: 0 0 6px; font-size: 1rem; color: var(--text); }\n.fcard-body p { margin: 0; font-size: 0.88rem; color: var(--text-muted); }\n\n.fcard-footer {\n padding-top: 12px;\n border-top: 1px solid var(--border);\n margin-top: auto;\n}\n.fcard-footer:empty { display: none; }\n";
|
|
28
28
|
/**
|
|
29
29
|
* Card component demonstrating named slots.
|
package/dist/scaffold.js
CHANGED
|
@@ -26,10 +26,10 @@ export const PACKAGE_JSON = (name) => JSON.stringify({
|
|
|
26
26
|
e2e: "playwright test --config e2e/playwright.config.ts",
|
|
27
27
|
},
|
|
28
28
|
dependencies: {
|
|
29
|
-
"@bascik/bascik": "^1.0.0-rc.
|
|
29
|
+
"@bascik/bascik": "^1.0.0-rc.5",
|
|
30
30
|
},
|
|
31
31
|
devDependencies: {
|
|
32
|
-
"@bascik/language-server": "^0.1.0-rc.
|
|
32
|
+
"@bascik/language-server": "^0.1.0-rc.2",
|
|
33
33
|
"@playwright/test": "^1.62.0",
|
|
34
34
|
"@types/node": "^24.0.0",
|
|
35
35
|
"@vitest/coverage-v8": "^4.1.10",
|
|
@@ -456,7 +456,7 @@ ${SITE_FOOTER_CSS}</style>
|
|
|
456
456
|
<div class="container">
|
|
457
457
|
<div class="footer-inner">
|
|
458
458
|
<span class="footer-copy">
|
|
459
|
-
© <script data-bascik-build>
|
|
459
|
+
© <script data-bascik-build>export default () => String(new Date().getFullYear());</script>
|
|
460
460
|
<span data-bascik-prop-brand>My Site</span>
|
|
461
461
|
</span>
|
|
462
462
|
<nav class="footer-nav" aria-label="Footer navigation">
|
package/package.json
CHANGED
|
@@ -145,6 +145,9 @@ describe("scaffold-to-compiler integration", () => {
|
|
|
145
145
|
const currentYear = String(new Date().getFullYear());
|
|
146
146
|
expect(indexHtml).toContain(`© ${currentYear}`);
|
|
147
147
|
expect(aboutHtml).toContain(`© ${currentYear}`);
|
|
148
|
+
// Confirm default export handler was scaffolded in site-footer
|
|
149
|
+
const footerHtml = await readFile(join(projectDir, "src/components/site-footer/site-footer.html"), "utf8");
|
|
150
|
+
expect(footerHtml).toContain("export default () => String(new Date().getFullYear())");
|
|
148
151
|
|
|
149
152
|
// 6. Verify representative assets copied to dist
|
|
150
153
|
await expect(access(join(distDir, "favicon.ico"))).resolves.toBeUndefined();
|
|
@@ -207,4 +210,28 @@ describe("scaffold-to-compiler integration", () => {
|
|
|
207
210
|
const combinedOutput = `${buildResult.stdout}\n${buildResult.stderr}`;
|
|
208
211
|
expect(combinedOutput.toLowerCase()).toMatch(/error|failed|syntaxerror/);
|
|
209
212
|
}, 45_000);
|
|
213
|
+
|
|
214
|
+
it("negative control: rejects compilation when a build script relies on legacy stdout console.log", async () => {
|
|
215
|
+
const parentDir = await createTempDir("stdout-control");
|
|
216
|
+
const projectName = "legacy-stdout-site";
|
|
217
|
+
const projectDir = join(parentDir, projectName);
|
|
218
|
+
|
|
219
|
+
await scaffold(projectName, parentDir);
|
|
220
|
+
|
|
221
|
+
const legacyStdoutPage = `<!DOCTYPE html>
|
|
222
|
+
<html>
|
|
223
|
+
<head><title>Legacy Stdout</title></head>
|
|
224
|
+
<body>
|
|
225
|
+
<script data-bascik-build>
|
|
226
|
+
console.log("<p>legacy</p>");
|
|
227
|
+
</script>
|
|
228
|
+
</body>
|
|
229
|
+
</html>`;
|
|
230
|
+
await writeFile(join(projectDir, "src/pages/index.html"), legacyStdoutPage, "utf8");
|
|
231
|
+
|
|
232
|
+
const buildResult = await runBascikBuild(projectDir);
|
|
233
|
+
expect(buildResult.exitCode).not.toBe(0);
|
|
234
|
+
const combinedOutput = `${buildResult.stdout}\n${buildResult.stderr}`;
|
|
235
|
+
expect(combinedOutput).toContain("missing default export");
|
|
236
|
+
}, 45_000);
|
|
210
237
|
});
|
package/src/scaffold.test.ts
CHANGED
|
@@ -132,9 +132,9 @@ describe("PACKAGE_JSON", () => {
|
|
|
132
132
|
expect(pkg.devDependencies["@playwright/test"]).toBeDefined();
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
-
it("
|
|
135
|
+
it("includes Bascik as a dependency", () => {
|
|
136
136
|
const pkg = JSON.parse(PACKAGE_JSON("my-app"));
|
|
137
|
-
expect(pkg.dependencies["@bascik/bascik"]).
|
|
137
|
+
expect(pkg.dependencies["@bascik/bascik"]).toBeDefined();
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
it("uses the supplied name", () => {
|
|
@@ -286,6 +286,7 @@ describe("SITE_FOOTER_HTML", () => {
|
|
|
286
286
|
|
|
287
287
|
it("uses a build-time script for the year", () => {
|
|
288
288
|
expect(SITE_FOOTER_HTML).toContain("data-bascik-build");
|
|
289
|
+
expect(SITE_FOOTER_HTML).toContain("export default () =>");
|
|
289
290
|
expect(SITE_FOOTER_HTML).toContain("getFullYear");
|
|
290
291
|
});
|
|
291
292
|
});
|
package/src/scaffold.ts
CHANGED
|
@@ -32,10 +32,10 @@ export const PACKAGE_JSON = (name: string): string =>
|
|
|
32
32
|
e2e: "playwright test --config e2e/playwright.config.ts",
|
|
33
33
|
},
|
|
34
34
|
dependencies: {
|
|
35
|
-
"@bascik/bascik": "^1.0.0-rc.
|
|
35
|
+
"@bascik/bascik": "^1.0.0-rc.5",
|
|
36
36
|
},
|
|
37
37
|
devDependencies: {
|
|
38
|
-
"@bascik/language-server": "^0.1.0-rc.
|
|
38
|
+
"@bascik/language-server": "^0.1.0-rc.2",
|
|
39
39
|
"@playwright/test": "^1.62.0",
|
|
40
40
|
"@types/node": "^24.0.0",
|
|
41
41
|
"@vitest/coverage-v8": "^4.1.10",
|
|
@@ -491,7 +491,7 @@ ${SITE_FOOTER_CSS}</style>
|
|
|
491
491
|
<div class="container">
|
|
492
492
|
<div class="footer-inner">
|
|
493
493
|
<span class="footer-copy">
|
|
494
|
-
© <script data-bascik-build>
|
|
494
|
+
© <script data-bascik-build>export default () => String(new Date().getFullYear());</script>
|
|
495
495
|
<span data-bascik-prop-brand>My Site</span>
|
|
496
496
|
</span>
|
|
497
497
|
<nav class="footer-nav" aria-label="Footer navigation">
|