create-bascik 1.0.0-rc.8 → 1.0.0-rc.9

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/README.md CHANGED
@@ -10,13 +10,13 @@ Interactive project initializer for [Bascik](https://bascik.dev).
10
10
  Scaffold a new Bascik project in one command:
11
11
 
12
12
  ```sh
13
- npm create bascik@latest my-site -y
13
+ npm create bascik@rc my-site -y
14
14
  ```
15
15
 
16
16
  Pass a project name as an argument or run without arguments to be prompted:
17
17
 
18
18
  ```sh
19
- npm create bascik@latest
19
+ npm create bascik@rc
20
20
  ```
21
21
 
22
22
  ## What it scaffolds
@@ -18,7 +18,7 @@ Without it, download every skill into the shared `.agents/skills/` folder (use `
18
18
  mkdir -p .agents/skills && curl -fsSL https://bascik.dev/assets/skills.tar.gz | tar -xz -C .agents/skills
19
19
  ```
20
20
 
21
- New projects created with `npm create bascik@latest` include these skills already.
21
+ New projects created with `npm create bascik@rc` include these skills already.
22
22
 
23
23
  ## Skills
24
24
 
@@ -88,7 +88,7 @@ New components are picked up by the running dev server automatically; no restart
88
88
  9. **Never write generated files into `src/`.** Generated output goes to `dist/` (`BASCIK_OUT_DIR`) from an existing `pipeline.exec` script.
89
89
  10. **Client scripts are not bundled.** Bare imports such as `import x from 'pkg'` fail in the browser. They work only in Node-side scripts (`data-bascik-build`, `data-bascik-server`).
90
90
  11. **Extract shared layout.** Repeated `<head>` tags, headers, and footers become components (`<site-head />`, `<site-header />`, `<site-footer />`). Pages keep their own `<html>`, `<head>`, and `<body>`.
91
- 12. **Script directives are exclusive.** One `<script>` tag takes at most one of `data-bascik-build`, `data-bascik-routes`, `data-bascik-server`, `data-bascik-stream`.
91
+ 12. **Script directives are exclusive.** One `<script>` tag takes at most one of `data-bascik-build`, `data-bascik-build-once`, `data-bascik-routes`, `data-bascik-server`, `data-bascik-stream`. `data-bascik-build-once` runs once per file with no page context.
92
92
 
93
93
  ## Slots, props, and attributes
94
94
 
@@ -104,14 +104,15 @@ Pass props as `data-bascik-prop-<name>="..."` on the usage tag. A plain attribut
104
104
 
105
105
  ## Script tags at a glance
106
106
 
107
- | Tag | Runs | Use for |
108
- | ----------------------------- | --------------------------- | -------------------------------------------------------------------- |
109
- | `<script>` | Browser | Component and page behavior (scoped, IIFE-wrapped) |
110
- | `<script data-bascik-build>` | Node, at build time | Markup from files, Markdown, APIs; output replaces the tag |
111
- | `<script data-bascik-routes>` | Node, at build time | Expanding `[slug].html` templates into many pages |
112
- | `<script data-bascik-server>` | Node, per request | Personalized markup (needs `bascik --server` or a serverless target) |
113
- | `<script data-bascik-stream>` | Node, per request, streamed | Slow per-request blocks that should not delay the page shell |
114
- | `<script data-bascik-dev>` | Browser, dev only | Debug helpers removed from production builds |
107
+ | Tag | Runs | Use for |
108
+ | --------------------------------- | --------------------------- | ---------------------------------------------------------------------- |
109
+ | `<script>` | Browser | Component and page behavior (scoped, IIFE-wrapped) |
110
+ | `<script data-bascik-build>` | Node, at build time | Markup from files, Markdown, APIs; output replaces the tag |
111
+ | `<script data-bascik-build-once>` | Node, once per file | Markup shared by every use of a component or every route of a template |
112
+ | `<script data-bascik-routes>` | Node, at build time | Expanding `[slug].html` templates into many pages |
113
+ | `<script data-bascik-server>` | Node, per request | Personalized markup (needs `bascik --server` or a serverless target) |
114
+ | `<script data-bascik-stream>` | Node, per request, streamed | Slow per-request blocks that should not delay the page shell |
115
+ | `<script data-bascik-dev>` | Browser, dev only | Debug helpers removed from production builds |
115
116
 
116
117
  Node-side scripts export a default function that returns an HTML string. Details are in the `bascik-scripts` skill.
117
118
 
@@ -131,7 +132,7 @@ npx @bascik/language-server --check # the per-file checks on their own (bascik
131
132
 
132
133
  `bascik --check` and the linter are not two overlapping tools. `bascik --check` answers project questions (duplicate routes, unused components, config), and the linter answers per-file questions (attribute typos, CSS that cannot be scoped). `bascik --check` runs the linter too, so use it, and reach for the linter alone only on a folder outside the project.
133
134
 
134
- New project: `npm create bascik@latest my-site -y`. Production builds need a site URL for `sitemap.xml` and `robots.txt`: `BASCIK_SITE_URL=https://example.com bascik --build` (or `--site-url`, or `.env`). Full CLI and error output: `read-doc.mjs /cli`.
135
+ New project: `npm create bascik@rc my-site -y`. Production builds need a site URL for `sitemap.xml` and `robots.txt`: `BASCIK_SITE_URL=https://example.com bascik --build` (or `--site-url`, or `.env`). Full CLI and error output: `read-doc.mjs /cli`.
135
136
 
136
137
  ## Verify every change
137
138
 
@@ -195,7 +196,7 @@ The script downloads from bascik.dev, maps files to the project's configured fol
195
196
 
196
197
  ## Build a new site
197
198
 
198
- 1. `npm create bascik@latest my-site -y`, or read the existing project first with `list-components.mjs`. Add `BASCIK_SITE_URL=https://example.com` to `.env`.
199
+ 1. `npm create bascik@rc my-site -y`, or read the existing project first with `list-components.mjs`. Add `BASCIK_SITE_URL=https://example.com` to `.env`.
199
200
  2. Plan the pages and URLs (one purpose and one `<h1>` each), the shared layout (head, header, footer), repeated content (a collection in `content/`), and anything that needs a server (API routes, per-request markup). Ask when the plan is unclear.
200
201
  3. Copy the foundation: `add-example.mjs starter-site`, or only the parts you need. Then replace the example name, navigation (`src/components/site-header/links.ts`), colors (tokens in `src/pages/css/styles.css`), and every page's `seo({ title, description })`.
201
202
  4. Add pages, components (`new-component.mjs`), and interactive examples. Load the companion skill for each area.
@@ -113,7 +113,7 @@ export default defineConfig({
113
113
  1. **One site, one project:** each site has its own config, `src/pages/`, `.env`, and `dist/`. Run Bascik from the site folder.
114
114
  2. **Tag names are unique across all of a site's roots.** Roots never nest or repeat, including through symlinks.
115
115
  3. **List the site's own folder first.** `bascik add` and `add-example.mjs` write into the first root.
116
- 4. **Shared components import shared helpers with relative paths** (`../../scripts/links.ts`) unless every site points `scripts.importRoot` at the same folder. `@/` resolves against the consuming site, and a broken import fails that site's build even when no page uses the component.
116
+ 4. **Shared components import shared helpers with relative paths** (`../../scripts/links.ts`) unless every site points `scripts.importRoot` at the same folder. `@/` resolves against the consuming site, and a broken import fails that site's build: a `data-bascik-build-once` script fails it even when no page uses the component, and a `data-bascik-build` script fails it on the first page that does.
117
117
  5. **Watch shared helper and style folders** with `pipeline.watchPaths`; only component roots are watched automatically.
118
118
  6. **Share tokens as a stylesheet:** `assets: { inlineStyles: ['../../shared/styles/tokens.css'] }`.
119
119
  7. **One Bascik version** for every site. Task runners (Turborepo, Nx) must list `shared/**` as a build input and `BASCIK_SITE_URL` as an env input.
@@ -13,20 +13,21 @@ Bascik runs Node.js code inside special `<script>` tags. Each is a standard ESM
13
13
 
14
14
  ## Pick the right tag
15
15
 
16
- | Need | Tag | Runs |
17
- | ---------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------- |
18
- | HTML from files, Markdown, JSON, or an API, the same for every visitor | `data-bascik-build` | Build and dev, once per page compile (cached) |
19
- | Many pages from one `[param].html` template | `data-bascik-routes` | Build and dev startup |
20
- | Markup that depends on the request (cookies, headers, query) | `data-bascik-server` | Every request, before the response starts |
21
- | A slow per-request block that should not delay the page shell | `data-bascik-stream` | Every request, streamed in document order |
22
- | A JSON or HTTP endpoint | `src/api/<name>.ts` exporting `GET`, `POST`, ... | Every request |
16
+ | Need | Tag | Runs |
17
+ | ---------------------------------------------------------------- | ------------------------------------------------ | ---------------------------------------------------------------- |
18
+ | Page-specific or per-component-use HTML | `data-bascik-build` | Build and dev, once per use (cached by inputs) |
19
+ | Identical HTML shared by every use of a component or every route | `data-bascik-build-once` | Once per file (component registration or route template), cached |
20
+ | Many pages from one `[param].html` template | `data-bascik-routes` | Build and dev startup |
21
+ | Markup that depends on the request (cookies, headers, query) | `data-bascik-server` | Every request, before the response starts |
22
+ | A slow per-request block that should not delay the page shell | `data-bascik-stream` | Every request, streamed in document order |
23
+ | A JSON or HTTP endpoint | `src/api/<name>.ts` exporting `GET`, `POST`, ... | Every request |
23
24
 
24
25
  Prefer build-time work. Server, stream, and API code needs `bascik --server` (or a serverless target such as Cloudflare); a plain static host cannot run it.
25
26
 
26
27
  ## Rules for every Node-side script
27
28
 
28
29
  1. **Export a default function that returns an HTML string.** `console.log()` output goes to the terminal only; it never becomes markup.
29
- 2. **One directive per tag.** Combining `data-bascik-build`, `-routes`, `-server`, or `-stream` is a hard error.
30
+ 2. **One directive per tag.** Combining `data-bascik-build`, `data-bascik-build-once`, `-routes`, `-server`, or `-stream` is a hard error.
30
31
  3. **Imports:** top-level `import` and `await` work. Use `@/` for shared helpers: `import { renderMd } from '@/lib/md.ts'` resolves against `scripts.importRoot` (default `src`) from any depth. `./` and `../` resolve against the file containing the script. Bare npm packages work.
31
32
  4. **Never import with a bare leading `/`.** `import x from '/lib/x.ts'` and `src="/lib/x.ts"` are hard errors. Use `@/lib/x.ts` or `./lib/x.ts`.
32
33
  5. **Helpers import helpers with relative paths.** `@/` is rewritten only inside script tags; a `.ts` helper that imports another helper uses `./other.ts` (with the extension) and `import type` for type-only imports. tsconfig `paths` do not apply.
@@ -53,7 +54,7 @@ Prefer build-time work. Server, stream, and API code needs `bascik --server` (or
53
54
  - Each uncached script runs in its own child process (about 50 to 150 ms). Output is cached on disk in `node_modules/.cache/bascik/script-cache/`.
54
55
  - **Cache gotcha:** the cache key covers the script, its statically referenced local files, installed package versions, the page path, site URL, and base. It cannot see network responses, `readdir` results, or computed paths. Exclude scripts that read remote or directory data with `scripts.cache.exclude`, and declare environment variables a script reads in `scripts.cache.environment`.
55
56
  - **Fetch once:** read data once per page in one script, then feed it to the parts that need it, instead of fetching per component instance.
56
- - **Component gotcha:** a build script inside a component runs once for the whole site unless it is page-aware. If it calls a helper that reads `BASCIK_PAGE_PATH` (canonical URLs, active nav links), write `<script data-bascik-build="page">`. The value must be exactly `page`; `"pages"` runs once, and the linter reports it as `unknown-directive-value` (in the editor, and in `bascik --check` when `@bascik/language-server` is installed).
57
+ - **Component context:** `data-bascik-build` runs for each use with that page's context. Use `data-bascik-build-once` for identical output with no page or route context: in a component every use shares one run, and in a dynamic route template every route shares one run. Its output is cached like any build script. Use a build-wide `pre` exec script when work should be shared across different files.
57
58
  - Errors follow `scripts.onBuildScriptError` (`'error'` by default): dev shows the error on that page and keeps serving; `--build` fails.
58
59
 
59
60
  Per-page values come from environment variables: `BASCIK_PAGE_PATH` (`/about`), `BASCIK_PAGE_FILE`, `BASCIK_SOURCE_FILE`, `BASCIK_PAGES_DIR`, `BASCIK_BUILD` (`"1"` in `--build`), `BASCIK_SITE_URL` (absent when unset), `BASCIK_BASE`, and `BASCIK_ROUTE` (dynamic routes). Details: `read-doc.mjs /build-scripts`, `/environment-variables`, `/how-to/page-aware-scripts`.
@@ -24,7 +24,7 @@ Bascik outputs static HTML, so search engines and AI crawlers read every word wi
24
24
 
25
25
  ## Rules (must follow)
26
26
 
27
- 1. **Write per-page tags from the page, not from a shared component.** A component's build script runs once for the whole site unless it is marked `data-bascik-build="page"`, so every page would get the first page's canonical URL.
27
+ 1. **Build tags in the context of the page that uses them.** A build script inside a component runs for each use, with that page's environment variables; no page-aware marker is needed.
28
28
  2. **Never hard-code the domain.** URLs come from `BASCIK_SITE_URL` (CI variable or `.env`), `BASCIK_BASE`, and `BASCIK_PAGE_PATH`. `bascik --build` fails without the site URL because it writes the sitemap and robots.txt.
29
29
  3. **Escape every value** you interpolate into a tag. For JSON-LD, `JSON.stringify` and replace `<` with `\u003c` so data can never close the script tag. Describe only what is visible on the page.
30
30
  4. **Dynamic routes** read the item from `BASCIK_ROUTE` in the head build script and pass its fields to the tag helper.
@@ -30,7 +30,7 @@ export declare const SITE_HEADER_CSS = ".header {\n position: sticky;\n top: 0
30
30
  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";
31
31
  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";
32
32
  /** Footer with brand prop and build-time year. */
33
- 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 &copy; <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";
33
+ 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 &copy; <script data-bascik-build-once>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";
34
34
  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";
35
35
  /**
36
36
  * Card component demonstrating named slots.
@@ -42,7 +42,7 @@ export declare const MY_COUNTER_CSS = ".counter {\n display: inline-flex;\n al
42
42
  export declare const MY_COUNTER_HTML = "<style>\n.counter {\n display: inline-flex;\n align-items: center;\n gap: 16px;\n font-size: 1.25rem;\n}\n\n.count-val {\n font-family: var(--mono);\n font-size: 1.5rem;\n min-width: 2ch;\n text-align: center;\n color: var(--accent);\n}\n\n.btn {\n display: inline-flex; align-items: center; gap: 8px;\n padding: 10px 22px; border: none; border-radius: var(--r-sm);\n font-size: 0.9rem; font-weight: 600; cursor: pointer;\n text-decoration: none; transition: filter .15s, opacity .15s; line-height: 1;\n}\n.btn-primary { background: var(--accent); color: #18191b; }\n.btn-ghost { background: transparent; color: var(--text); border: 1px solid var(--border); }\n@media (hover: hover) {\n .btn-primary:hover { filter: brightness(0.88); opacity: 1; }\n .btn-ghost:hover { border-color: var(--accent); color: var(--accent); opacity: 1; }\n}\n</style>\n<div class=\"counter\">\n <button id=\"btn-dec\" class=\"btn btn-ghost\" data-testid=\"counter-dec\">\u2212</button>\n <span id=\"count-val\" class=\"count-val\" data-testid=\"counter-val\">0</span>\n <button id=\"btn-inc\" class=\"btn btn-primary\" data-testid=\"counter-inc\">+</button>\n</div>\n<script>\n (function () {\n var count = 0;\n var val = document.getElementById('count-val');\n document.getElementById('btn-dec').addEventListener('click', function () {\n val.textContent = --count;\n });\n document.getElementById('btn-inc').addEventListener('click', function () {\n val.textContent = ++count;\n });\n }());\n</script>\n";
43
43
  export declare const SITE_META_TEST = "import { describe, it, expect } from 'vitest';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\ndescribe('site-meta component', () => {\n const path = join(process.cwd(), 'src/components/site-meta/site-meta.html');\n\n it('contains charset, viewport, favicon, and stylesheet link tags', async () => {\n const html = await readFile(path, 'utf8');\n expect(html).toContain('charset=\"UTF-8\"');\n expect(html).toContain('name=\"viewport\"');\n expect(html).toContain('name=\"description\"');\n expect(html).toContain('favicon.ico');\n expect(html).toContain('favicon-32x32.png');\n expect(html).toContain('favicon.svg');\n expect(html).toContain('apple-touch-icon.png');\n expect(html).toContain('styles.css');\n });\n});\n";
44
44
  export declare const SITE_HEADER_TEST = "import { describe, it, expect } from 'vitest';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\ndescribe('site-header component', () => {\n const path = join(process.cwd(), 'src/components/site-header/site-header.html');\n\n it('renders brand prop placeholder and navigation links', async () => {\n const html = await readFile(path, 'utf8');\n expect(html).toContain('data-bascik-prop-brand');\n expect(html).toContain('href=\"/\"');\n expect(html).toContain('href=\"/about\"');\n expect(html).toContain('href=\"/contact\"');\n expect(html).toContain('id=\"nav-toggle\"');\n });\n});\n";
45
- export declare const SITE_FOOTER_TEST = "import { describe, it, expect } from 'vitest';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\ndescribe('site-footer component', () => {\n const path = join(process.cwd(), 'src/components/site-footer/site-footer.html');\n\n it('renders brand prop placeholder and build-time year script', async () => {\n const html = await readFile(path, 'utf8');\n expect(html).toContain('data-bascik-prop-brand');\n expect(html).toContain('data-bascik-build');\n expect(html).toContain('getFullYear');\n });\n});\n";
45
+ export declare const SITE_FOOTER_TEST = "import { describe, it, expect } from 'vitest';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\ndescribe('site-footer component', () => {\n const path = join(process.cwd(), 'src/components/site-footer/site-footer.html');\n\n it('renders brand prop placeholder and build-once year script', async () => {\n const html = await readFile(path, 'utf8');\n expect(html).toContain('data-bascik-prop-brand');\n expect(html).toContain('data-bascik-build-once');\n expect(html).toContain('getFullYear');\n });\n});\n";
46
46
  export declare const FEAT_CARD_TEST = "import { describe, it, expect } from 'vitest';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\ndescribe('feat-card component', () => {\n const path = join(process.cwd(), 'src/components/feat-card/feat-card.html');\n\n it('defines header, default, and footer slots', async () => {\n const html = await readFile(path, 'utf8');\n expect(html).toContain('data-bascik-slot=\"header\"');\n expect(html).toContain('data-bascik-slot');\n expect(html).toContain('data-bascik-slot=\"footer\"');\n });\n});\n";
47
47
  export declare const MY_COUNTER_TEST = "import { describe, it, expect } from 'vitest';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\ndescribe('my-counter component', () => {\n const path = join(process.cwd(), 'src/components/my-counter/my-counter.html');\n\n it('renders increment and decrement controls with scoped script', async () => {\n const html = await readFile(path, 'utf8');\n expect(html).toContain('id=\"btn-dec\"');\n expect(html).toContain('id=\"btn-inc\"');\n expect(html).toContain('id=\"count-val\"');\n expect(html).toContain('addEventListener');\n });\n});\n";
48
48
  export declare const indexPage: (brand: string) => string;
package/dist/scaffold.js CHANGED
@@ -37,10 +37,10 @@ export const PACKAGE_JSON = (name) => JSON.stringify({
37
37
  e2e: "playwright test --config e2e/playwright.config.ts",
38
38
  },
39
39
  dependencies: {
40
- "@bascik/bascik": "^1.0.0-rc.8",
40
+ "@bascik/bascik": "^1.0.0-rc.9",
41
41
  },
42
42
  devDependencies: {
43
- "@bascik/language-server": "^0.1.0-rc.4",
43
+ "@bascik/language-server": "^0.1.0-rc.5",
44
44
  "@playwright/test": "^1.62.0",
45
45
  "@types/node": "^24.0.0",
46
46
  "@vitest/coverage-v8": "^4.1.10",
@@ -465,7 +465,7 @@ ${SITE_FOOTER_CSS}</style>
465
465
  <div class="container">
466
466
  <div class="footer-inner">
467
467
  <span class="footer-copy">
468
- &copy; <script data-bascik-build>export default () => String(new Date().getFullYear());</script>
468
+ &copy; <script data-bascik-build-once>export default () => String(new Date().getFullYear());</script>
469
469
  <span data-bascik-prop-brand>My Site</span>
470
470
  </span>
471
471
  <nav class="footer-nav" aria-label="Footer navigation">
@@ -619,10 +619,10 @@ import { join } from 'node:path';
619
619
  describe('site-footer component', () => {
620
620
  const path = join(process.cwd(), 'src/components/site-footer/site-footer.html');
621
621
 
622
- it('renders brand prop placeholder and build-time year script', async () => {
622
+ it('renders brand prop placeholder and build-once year script', async () => {
623
623
  const html = await readFile(path, 'utf8');
624
624
  expect(html).toContain('data-bascik-prop-brand');
625
- expect(html).toContain('data-bascik-build');
625
+ expect(html).toContain('data-bascik-build-once');
626
626
  expect(html).toContain('getFullYear');
627
627
  });
628
628
  });
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "starter",
10
10
  "static site generator"
11
11
  ],
12
- "version": "1.0.0-rc.8",
12
+ "version": "1.0.0-rc.9",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git+https://github.com/bascikdev/bascik.git",
@@ -336,8 +336,8 @@ describe("SITE_FOOTER_HTML", () => {
336
336
  expect(SITE_FOOTER_HTML).not.toContain("${SITE_FOOTER_CSS}");
337
337
  });
338
338
 
339
- it("uses a build-time script for the year", () => {
340
- expect(SITE_FOOTER_HTML).toContain("data-bascik-build");
339
+ it("uses a build-once script for the year", () => {
340
+ expect(SITE_FOOTER_HTML).toContain("<script data-bascik-build-once>");
341
341
  expect(SITE_FOOTER_HTML).toContain("export default () =>");
342
342
  expect(SITE_FOOTER_HTML).toContain("getFullYear");
343
343
  });
package/src/scaffold.ts CHANGED
@@ -48,10 +48,10 @@ export const PACKAGE_JSON = (name: string): string =>
48
48
  e2e: "playwright test --config e2e/playwright.config.ts",
49
49
  },
50
50
  dependencies: {
51
- "@bascik/bascik": "^1.0.0-rc.8",
51
+ "@bascik/bascik": "^1.0.0-rc.9",
52
52
  },
53
53
  devDependencies: {
54
- "@bascik/language-server": "^0.1.0-rc.4",
54
+ "@bascik/language-server": "^0.1.0-rc.5",
55
55
  "@playwright/test": "^1.62.0",
56
56
  "@types/node": "^24.0.0",
57
57
  "@vitest/coverage-v8": "^4.1.10",
@@ -507,7 +507,7 @@ ${SITE_FOOTER_CSS}</style>
507
507
  <div class="container">
508
508
  <div class="footer-inner">
509
509
  <span class="footer-copy">
510
- &copy; <script data-bascik-build>export default () => String(new Date().getFullYear());</script>
510
+ &copy; <script data-bascik-build-once>export default () => String(new Date().getFullYear());</script>
511
511
  <span data-bascik-prop-brand>My Site</span>
512
512
  </span>
513
513
  <nav class="footer-nav" aria-label="Footer navigation">
@@ -669,10 +669,10 @@ import { join } from 'node:path';
669
669
  describe('site-footer component', () => {
670
670
  const path = join(process.cwd(), 'src/components/site-footer/site-footer.html');
671
671
 
672
- it('renders brand prop placeholder and build-time year script', async () => {
672
+ it('renders brand prop placeholder and build-once year script', async () => {
673
673
  const html = await readFile(path, 'utf8');
674
674
  expect(html).toContain('data-bascik-prop-brand');
675
- expect(html).toContain('data-bascik-build');
675
+ expect(html).toContain('data-bascik-build-once');
676
676
  expect(html).toContain('getFullYear');
677
677
  });
678
678
  });