Internals
Diagnostics
Bascik includes a static analysis engine for project diagnostics (bascik --check) and a stack trace remapping utility (stack-trace.ts) that links runtime script errors back to source HTML files.
Overview
Diagnostics in Bascik operate across two distinct phases:
- Build-time static analysis (
check.ts): Scans source pages and components before compilation to detect missing component definitions, unused component files, and invalid tag syntax without executing arbitrary code. - Runtime error remapping (
stack-trace.ts): Intercepts unhandled exceptions in<script data-bascik-build>and<script data-bascik-server>blocks, remapping Node.js stack traces from ephemeral temp files back to the original source HTML file and line offset.
Static Project Analysis (check.ts)
Running bascik --check validates project markup without starting a full build or web server. If errors are detected, the process exits with code 1, making it suitable for CI/CD status checks.
Tag Extraction (extractCustomTags)
To locate custom component usages, extractCustomTags scans page and component HTML for custom hyphenated element tags (<user-card>, <nav-header>):
export const extractCustomTags = (html: string): Set<string> => {
const stripped = stripElementContents(html.replace(/<!--[\s\S]*?-->/g, ""));
const tags = new Set<string>();
const re = /<([a-z][a-z0-9]*(?:-[a-z0-9]+)+)[\s\/>]/gi;
let m: RegExpExecArray | null;
while ((m = re.exec(stripped)) !== null) {
tags.add(m[1].toLowerCase());
}
return tags;
}; Standard HTML tags (<div>, <p>, <span>) contain no hyphens and are ignored.
Raw-Text Content Stripping (stripElementContents)
Source templates often contain sample code, embedded JSON-LD, or CSS rules that mention custom tag names in string literals or comments (such as '<my-tag>' inside a tutorial snippet). Unchecked, these strings would produce false-positive unknown tag errors.
Before custom tags are extracted, stripElementContents removes the inner content of elements that legitimately contain raw text:
- Standard protected elements:
<script>,<style>,<textarea> - User-configured preserve tags:
scoping.preserve(which defaults to["code"])
const stripElementContents = (html: string): string => {
const extra = (BascikConfig.scoping?.preserve ?? [])
.map((t) => String(t).replace(/[^a-zA-Z0-9-]/g, ""))
.filter(Boolean);
const protectedTags = ["script", "style", "textarea", ...extra];
const re = new RegExp(
`<(${protectedTags.join("|")})(\\s[^>]*)?>[\\s\\S]*?</\\1>`,
"gi",
);
let prev: string;
let out = html;
do {
prev = out;
out = out.replace(re, "<$1$2></$1>");
} while (out !== prev);
return out;
}; A loop runs until output stabilizes to handle nested tags (such as <code>...<code>...</code>...</code>).
Build Script Presence Heuristic
<script data-bascik-build> blocks execute arbitrary JavaScript at build time and can output component markup dynamically. Running build scripts during bascik --check would be slow and could cause unwanted side effects.
Rather than disabling the unused component check project-wide, check.ts checks build script source text for string literal references (e.g. "my-card", 'my-card', or `my-card`). A component is marked as potentially used only when its name appears as a string literal in a build script. Unreferenced components continue to be reported as unused warnings.
The Findings Model & Extension Points
checkProject returns a structured data model rather than writing directly to terminal streams:
export type FindingSeverity = "error" | "warning";
export interface FindingLocation {
filePath: string;
line?: number;
}
export interface CheckFinding {
category: string;
severity: FindingSeverity;
message: string;
locations: FindingLocation[];
suggestion?: string;
}
export interface CheckFindings {
errors: number;
warnings: number;
pagesChecked: number;
componentsChecked: number;
items: CheckFinding[];
} This model decouples diagnostic analysis from presentation:
formatFindingsHuman(findings): Groups findings by category, renders file and line locations, suggests near-miss matches, and provides category descriptions.formatFindingsJson(findings): Serializes the findings model to a stable JSON schema for CI automation.
Adding a New Check
To add a new validation check:
- Perform static analysis during the scanning pass in
checkProject. - Push a new
CheckFindingobject toitemswith an appropriate category identifier, severity ("error"or"warning"), message, and location array. - Add a category entry to
CATEGORY_METAincheck.tswith a human-readable title and explanation for the terminal formatter.
Diagnostics Output Summary
bascik --check produces categorized diagnostic reporting:
| Diagnostic Type | Category | Severity | Exit Code | Description |
|---|---|---|---|---|
| Unknown Component Tag | unmatched-tag | Warning | 0 | A hyphenated tag was used in HTML, but no matching file exists in src/components/. Ships unchanged. |
| Unused Component File | unused-component | Warning | 0 | A component file exists in src/components/, but is never referenced in any page, component, or build script literal. |
| Config Validation | config-validation | Error/Warning | Depends | Configuration validation results projected into findings with key-based severity mapping. |
| Missing Site URL | missing-site-url | Error | 1 | generate.sitemap and/or generate.robots is enabled but no site URL is available. |
| Pages Directory Issue | pages-directory | Error | 1 | Configured pages directory cannot be read or contains no page HTML files. |
| Duplicate Component Name | duplicate-component-name | Error | 1 | Multiple component files define the same custom tag name. |
| Circular Component Reference | circular-component-reference | Error | 1 | Direct or indirect component graph cycle detected (full cycle path included). |
| Unknown Bascik Directive | unknown-bascik-attribute | Warning | 0 | A data-bascik-* attribute is unrecognized and likely a typo. |
| Script Mode Conflict | script-mode-conflict | Error | 1 | A script tag has both data-bascik-build and data-bascik-server. |
| Duplicate Route Resolution | duplicate-route-resolution | Error | 1 | Multiple pages resolve to the same route path. |
| API Route Missing Handler | missing-method-handler | Error | 1 | An API route file exports no recognized HTTP method handler (GET, POST, etc.). |
| API Route Collision | route-collision | Error | 1 | Multiple API route files resolve to the same endpoint URL path. |
| API Route Invalid Case | invalid-method-case | Warning | 0 | Method export name is not uppercase (e.g. get instead of GET). |
| Component Order Convention | component-structure-order | Warning | 0 | Advises <style> above markup and <script> below markup in component templates. |
missing-required-prop is intentionally omitted from the diagnostics model. The low-cost project-wide signal is noisy for real sites (conditional render paths and dynamic content supply), and --check favors actionable findings over speculative warnings.
Worked Extension Examples
- Unknown directive checks: scan stripped HTML for
data-bascik-*, compare against known directive sets, and emitunknown-bascik-attributewarnings with line locations. - Cycle checks: build a component dependency graph from custom-tag occurrences, then DFS for back-edges and report each full cycle path.
- Config reuse checks: call
validateUserConfig(...), map each returned key to check-level severity, and emitconfig-validationfindings instead of writing directly to stderr.
Aggregated Build Errors
Production transpilation settles every page job before deciding whether the build succeeded. Each failure is normalized into three fields: the source page path, the processing stage, and the original error message. The CLI prints one grouped report and exits with code 1:
Build failed with 2 page errors:
src/pages/about.html
validate markup: Page does not contain a non-empty <body> element
src/pages/blog/post.html
write output: EACCES: permission denied Stages include validate markup, component expansion, create output directory, write output, transpile page, and worker transpile. Missing or unreadable configured source directories fail before page processing begins. The one exception is the default components root: a missing src/components/ means the project has no components, and an unmatched tag is still reported. Any other missing components root fails with a message that names the path. A subdirectory that disappears during recursive traversal is treated as a file-watch race: Bascik warns and continues scanning the remaining tree.
In dev mode the same page records are logged, but they do not reject the batch. This allows boot to complete and healthy pages to remain available while a failed page waits for the next save. Unmatched custom component tags emit a warning by default (and exit with code 1 when --strict is passed).
Source Map & Stack Trace Remapping (stack-trace.ts)
During build and server execution, Bascik extracts <script data-bascik-build> and <script data-bascik-server> blocks into ephemeral temporary files before executing them with Node.js.
When a script throws an unhandled exception, Node.js formats the stack trace using the temporary file path and a 1-based line number relative to the temporary file's start:
Error: Failed to fetch API data
at file:///tmp/bascik-script-a1b2c3.mjs:4:11 The cleanStackTrace Utility
cleanStackTrace intercepts raw trace strings and converts ephemeral file references back to the source HTML document:
export const cleanStackTrace = (
rawTrace: string,
tmpPath: string,
realPath: string,
lineOffset: number,
): string => {
if (!rawTrace) return rawTrace;
const escapedTmpPath = tmpPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
let fileUri = tmpPath;
try {
fileUri = pathToFileURL(tmpPath).href;
} catch {}
const escapedFileUri = fileUri.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(`(?:${escapedFileUri}|${escapedTmpPath}):(\\d+)`, "g");
return rawTrace.replace(regex, (match, lineStr) => {
const lineNum = parseInt(lineStr, 10);
const mappedLine = lineOffset + lineNum - 1;
return `${realPath}:${mappedLine}`;
});
}; Terminal Link Integration
With cleanStackTrace applied, error output in the terminal references actual workspace files:
Error: Failed to fetch API data
at src/pages/dashboard.html:28 Developers can click or Cmd+Click the path in VS Code or supported terminals to jump straight to the exact line in their source HTML file.