Internals
Create App
The create/ folder is a small standalone package that scaffolds a fresh Bascik project. It is separate from the main package because it is meant to be run as a user-facing CLI, not as a workspace dependency.
Code structure
The package's source files have distinct responsibilities:
create/src/scaffold.ts: pure data and file-writing logic. All generated file content lives here as exported string constants and functions. No I/O beyondfs/promises. This is what the tests cover.create/src/agent-setup.ts: the coding-assistant setup shared withbascik init: theAGENTS.mdbrief and theCLAUDE.mdthat imports it (merged into existing files), the agent skills install through the skills CLI, and the prompt helper that keeps a run without a terminal from hanging.create/src/index.ts: the CLI entry point. Handles the prompts (the project name and the agent skills question; the template comes only from--template, and isdefaultwithout it), and spawns the skills CLI,npm installandnpm run dev.create-cli.integration.test.tsruns it end to end.create/src/cli.ts: parses-y,--no-install,--no-dev,--no-skills,--template <name>, and the positional project name, and refuses an unknown flag with the closest known one. Because npm keeps flags written before--for itself, it also honors thenpm_config_yes,npm_config_template,npm_config_skillsandnpm_config_installvariables npm sets instead (npm sets a--no-*flag's variable to the empty string). npm takes--no-devas its owndevsetting and sets nothing, so that flag only works after--.
Keeping them split means you can test every generated file without invoking the CLI or touching the filesystem.
validateProjectName refuses ., .., an absolute path, spaces and empty or hidden path segments, so the project is always a new folder under the current one; its last segment names the site, and lowercased, the package. Before writing anything, scaffold checks the target: a file where the project folder goes, or a folder that already has one of the starter's own files (or a file where one of its folders goes), throws a ScaffoldTargetError naming them, which the CLI prints in one line and exits 1. AGENTS.md, CLAUDE.md and .vscode/ are not in the way, since those are merged. Unrelated files such as an existing .hintrc are left untouched. Any other filesystem error is printed as Error: could not create <name>: <reason>.
What the CLI generates
Running npx create-bascik <name> writes this structure:
<name>/
package.json
vite.config.ts
.gitignore
.vscode/
launch.json
extensions.json
settings.json
AGENTS.md project brief for coding assistants
CLAUDE.md @AGENTS.md, so Claude Code reads the brief
.agents/skills/ Bascik agent skills, installed by the skills CLI on Y
.claude/skills/ the same skills for Claude Code
skills-lock.json written by the skills CLI
e2e/
playwright.config.ts
app.spec.ts
src/
pages/
favicon.ico
assets/
favicon-32x32.png
favicon.svg
apple-touch-icon.png
index.html
about.html
contact.html
404.html
css/
styles.css
components/
site-meta/
site-meta.html
site-meta.test.ts
site-header/
site-header.html
site-header.test.ts
site-footer/
site-footer.html
site-footer.test.ts
feature-card/
feature-card.html
feature-card.test.ts
my-counter/
my-counter.html
my-counter.test.ts
contact-form/
contact-form.html
contact-form.test.ts The feature-card component demonstrates named slots and semantic selectors, with only a short .body class where a class is useful. The my-counter component demonstrates scoped JS with two independent instances on the home page, and contact-form a contact form. Component scripts are written in TypeScript (<script type="text/typescript">). Every component includes co-located unit tests, package.json includes npm run lint (bascik --check, which runs the @bascik/language-server linter and the project-wide checks), .vscode/extensions.json recommends the official Bascik VS Code extension, and .vscode/settings.json sets "html.validate.scripts": false so VS Code does not join a page's separate <script> blocks into one file and report false errors. vite.config.ts configures Vitest with V8 code coverage, and e2e/ includes Playwright browser specs testing page navigation, counter interaction, and mobile menu toggling. Third-party linters such as Webhint are opt-in: setup creates no config or dependency for them. The linting guide has copyable settings.
After scaffolding, the CLI asks whether to install the agent skills, then offers to run npm install and npm run dev. All three default to yes, -y answers yes to each, and --no-skills, --no-install and --no-dev skip one. The dev server question is skipped when the install is, and npm run dev runs only when npm install exits 0; otherwise the CLI prints the next steps, npm install included.
Agent skills
The skills are not bundled in the package. On yes, the CLI runs the pinned skills CLI in the new project:
npx --prefer-offline -y skills@1.7.4 add bascikdev/bascik#v<version> -a universal -a claude-code --copy -y --skill bascik bascik-compatibility ... <version> is the @bascik/bascik version the scaffold depends on (BASCIK_VERSION in scaffold.ts), so the skills match the framework. When that tag is missing, such as before a release is tagged, it retries from the default branch. It does not retry when npx itself failed to reach the npm registry, and it runs npx with npm_config_fetch_retries=0 and a 20-second fetch timeout (unless you set your own), so an offline first run, with the skills CLI not yet in npm's cache, fails in about a second instead of minutes. When the install fails, or the reader answers no, it prints the command to run later, pinned to the same tag, and carries on. --copy writes real folders instead of symlinks, which Windows needs. The skills CLI has no option to leave a skill out and ignores names it does not know, so PROJECT_SKILLS in agent-setup.ts lists every skill except the opt-in bascik-refactor, and a test fails when it no longer matches the repository's skills/ folder.
BASCIK_SKILLS_SOURCE replaces the GitHub source, for example with a local checkout (BASCIK_SKILLS_SOURCE=$PWD/.. node dist/index.js my-site -y), and is used as given with no tag. The integration tests use it to install this branch's skills without GitHub.
agent-setup.ts has an identical copy at pkg/src/lib/agent-setup.ts for bascik init; a unit test fails when the two differ. The cloudflare template adds src/pages/edge.html (a server-script demo page), src/api/hello.ts, the @bascik/adapter-cloudflare and wrangler dev dependencies, and build, preview, and deploy scripts for Cloudflare Workers. The netlify template adds the same src/pages/edge.html and src/api/hello.ts, the @bascik/adapter-netlify and netlify-cli dev dependencies, a netlify.toml (build command and the publish folder, with Netlify's URL used as the site URL), a .gitignore that also excludes publish/ and .netlify/, and build, preview, and deploy scripts that build with bascik --build --target netlify, run bascik-netlify-install, and call the Netlify CLI through npx.
Why the generated app uses npm
The scaffold runs npm install and npm run dev so users do not need Yarn or pnpm to get started. The repo itself uses Yarn workspaces for contributor work, but the generated site is designed to feel like a regular app from a standard Node CLI.
Modifying the scaffold
All generated file content is defined as string constants in scaffold.ts. To change what a new project looks like, edit the relevant constant there. The .vscode/settings.json and .vscode/extensions.json content lives in vscode-settings.ts instead, which has an identical copy at pkg/src/lib/vscode-settings.ts for bascik init; a unit test fails when the two differ. Both commands merge into .vscode files a folder already has: they add the missing setting or recommendation, keep comments and other entries, keep a value the user already set, and leave a file that is not valid JSON alone with a printed note saying what to add. An existing launch.json is never replaced. Third-party linter configs are never created or modified. Keep optional examples in the linting guide, not in the scaffold. After any change, rebuild before testing:
cd create
npm run build The npm link symlink points at the create/ directory, so a fresh dist/ is picked up immediately without relinking. npm link also runs prepare, which builds dist/, so the initial link after a fresh checkout needs no separate build step.
Tests
The scaffold is fully unit-tested. Run the tests from the create/ directory:
cd create
npm test Tests mock fs/promises and verify that every expected file is written with the right content. If you add or rename a generated file, add a corresponding test case in scaffold.test.ts.
Lockfiles and package managers
Contributors use Yarn at the monorepo root with yarn.lock.
Generated projects intentionally use npm, and each generated project gets its own package-lock.json.
Testing create app locally
From the repo root, run:
yarn create:test-site This builds create-bascik and runs the scaffolding CLI to create a test project at my-site/. It skips the generated project's npm install, then builds the site using the @bascik/bascik package already linked from pkg/ in the monorepo workspace. This lets CI test the current package changes before an RC is published.
The -y flag selects the defaults non-interactively, while --no-skills, --no-install and --no-dev skip the agent skills, npm install and dev server startup. --template cloudflare selects the Cloudflare Workers template and --template netlify the Netlify template; through npm create it must follow a -- separator (npm create bascik my-site -- --template cloudflare).
Build complete Cleanup after local testing
To clean up after testing:
rm -rf my-site