Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions create-bestax/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ react (#1012), so restore them on its PR; when the peer range itself moves, chan
silently dropped at ingest.
- Templates pin the library's CSS import and icon setup — a change to bulma-ui's published
exports or flavors (`bestax.css`, `versions/*.css`) may require a template update.
- `templates/vite-ts` follows the `react-ts` starter that `npm create vite@latest` writes.
Comment thread
claude[bot] marked this conversation as resolved.
When Vite or create-vite ships a new major, scaffold that starter, compare its tsconfigs,
devDependencies, `vite.config.ts` and `src/main.tsx` with the template's, and catch up
(#1049). The App, its assets and the README differ on purpose, and so does lint: the
template keeps ESLint with typescript-eslint whatever linter create-vite ships. A file the
catch-up adds or removes has to reach the docs sandboxes too, and
`docs/scripts/sandbox.test.mjs` fails until the files the StackBlitz project copies, with the
ones it names as left out, are exactly the template's. The CodeSandbox project is a Create
React App layout with files of its own, so a new template file that matters there is a
change to its builder, and no test holds it to the template.
- `templates/vite-ts` is also what the docs site's StackBlitz and CodeSandbox buttons build
from (`docs/src/theme/CodeBlock/sandbox/`, read at docs build time). The builders edit
`src/main.tsx` by string pattern, so `docs/scripts/sandbox.test.mjs` runs them against the
Expand Down
1 change: 1 addition & 0 deletions create-bestax/templates/vite-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^24.13.3",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.3",
Expand Down
1 change: 0 additions & 1 deletion create-bestax/templates/vite-ts/src/vite-env.d.ts

This file was deleted.

19 changes: 10 additions & 9 deletions create-bestax/templates/vite-ts/tsconfig.app.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "es2023",
"lib": ["ES2023", "DOM"],
"module": "esnext",
"types": ["vite/client"],
"allowArbitraryExtensions": true,
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
}
20 changes: 15 additions & 5 deletions create-bestax/templates/vite-ts/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "es2023",
"lib": ["ES2023"],
"types": ["node"],
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,

/* Bundler mode */
"module": "nodenext",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"strict": true,
"types": ["vite/client"]

/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}
98 changes: 97 additions & 1 deletion docs/scripts/sandbox.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
* the repo root.
*/
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
import { describe, it } from 'node:test';
Expand Down Expand Up @@ -380,6 +381,101 @@ describe('stackblitzProject', () => {
});
});

/**
* The template files a StackBlitz project leaves out, each with the reason.
* Every other file is in TEMPLATE_FILES, so a file the template gains when it
* catches up with create-vite reaches the sandbox, or fails here, instead of
* the sandbox falling a step behind the scaffold. The CodeSandbox project is
* a Create React App layout, not a copy of the template, so it has no list of
* its own to hold to one.
*/
const LEFT_OUT = {
Comment thread
claude[bot] marked this conversation as resolved.
_gitignore: 'a sandbox is not a repository',
'README.md': 'it describes the starter app, not the example',
'eslint.config.js': 'a sandbox runs no lint, and drops its packages too',
'public/react.svg': 'only the starter App shows it',
'public/vite.svg': 'only the starter App shows it',
'src/App.css': 'only the starter App imports it',
'src/App.tsx': 'the example is written in its place',
};

/**
* The files the template tracks, by path in it. Read from git rather than
* the disk, so a lockfile or dist/ that an install or build inside the
* template leaves behind is not taken for a file the template gained. A
* tracked file deleted but not yet staged is gone from the template already.
*/
function templateFiles() {
return execFileSync('git', ['ls-files', '-z'], {
cwd: TEMPLATE_DIR,
encoding: 'utf8',
})
.split('\0')
.filter(path => path && existsSync(join(TEMPLATE_DIR, path)));
}

/**
* What open.js copies: each key of its TEMPLATE, with the template file the
* raw-loader import bound to that key reads. open.js only runs under webpack,
* so it is read here as text.
*/
function openJsTemplate() {
const source = readFileSync(
join(DOCS, 'src', 'theme', 'CodeBlock', 'sandbox', 'open.js'),
'utf8'
);
const imports = new Map(
[
...source.matchAll(
/^import (\w+) from '!!raw-loader!@site\/\.\.\/create-bestax\/templates\/vite-ts\/([^']+)';$/gm
),
].map(([, name, path]) => [name, path])
);
const body = source.match(/^const TEMPLATE = \{\n([\s\S]*?)\n\};$/m)?.[1];
assert.ok(body, 'open.js no longer declares `const TEMPLATE = { … };`');
return new Map(
[...body.matchAll(/^ {2}'([^']+)': (\w+),$/gm)].map(([, key, name]) => [
key,
imports.get(name),
])
);
}

describe('the template files', () => {
it('each reach the StackBlitz project or are left out for a reason', () => {
const files = templateFiles();
assert.deepEqual(
files.filter(
path => !TEMPLATE_FILES.includes(path) && !Object.hasOwn(LEFT_OUT, path)
),
[],
'Add each file to TEMPLATE_FILES (projects.mjs) and TEMPLATE (open.js), ' +
Comment thread
claude[bot] marked this conversation as resolved.
'or to LEFT_OUT here with the reason the sandbox does without it.'
);
assert.deepEqual(
Object.keys(LEFT_OUT).filter(path => !files.includes(path)),
[],
'LEFT_OUT names a file the template no longer has.'
);
});

// A file TEMPLATE_FILES names and open.js leaves out passes the docs build
// and the builders' tests, and the button then throws on the reader's click.
it("are each copied by open.js's TEMPLATE, from the file of that name", () => {
const copied = openJsTemplate();
assert.deepEqual(
[...copied.keys()].sort(),
[...TEMPLATE_FILES].sort(),
"open.js's TEMPLATE must hold a key for exactly the files in TEMPLATE_FILES."
);
assert.deepEqual(
TEMPLATE_FILES.filter(path => copied.get(path) !== path),
[],
"Each key in open.js's TEMPLATE must be bound to the raw-loader import of that file."
);
});
});

describe('codesandboxFiles', () => {
const files = codesandboxFiles(INPUT);
const pkg = files['package.json'].content;
Expand Down
2 changes: 0 additions & 2 deletions docs/src/theme/CodeBlock/sandbox/open.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import packageJson from '!!raw-loader!@site/../create-bestax/templates/vite-ts/p
import bestaxSvg from '!!raw-loader!@site/../create-bestax/templates/vite-ts/public/bestax.svg';
import indexCss from '!!raw-loader!@site/../create-bestax/templates/vite-ts/src/index.css';
import mainTsx from '!!raw-loader!@site/../create-bestax/templates/vite-ts/src/main.tsx';
import viteEnv from '!!raw-loader!@site/../create-bestax/templates/vite-ts/src/vite-env.d.ts';
import tsconfigApp from '!!raw-loader!@site/../create-bestax/templates/vite-ts/tsconfig.app.json';
import tsconfig from '!!raw-loader!@site/../create-bestax/templates/vite-ts/tsconfig.json';
import tsconfigNode from '!!raw-loader!@site/../create-bestax/templates/vite-ts/tsconfig.node.json';
Expand Down Expand Up @@ -36,7 +35,6 @@ const TEMPLATE = {
'public/bestax.svg': bestaxSvg,
'src/index.css': indexCss,
'src/main.tsx': mainTsx,
'src/vite-env.d.ts': viteEnv,
'tsconfig.app.json': tsconfigApp,
'tsconfig.json': tsconfig,
'tsconfig.node.json': tsconfigNode,
Expand Down
1 change: 0 additions & 1 deletion docs/src/theme/CodeBlock/sandbox/projects.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export const TEMPLATE_FILES = [
'public/bestax.svg',
'src/index.css',
'src/main.tsx',
'src/vite-env.d.ts',
'tsconfig.app.json',
'tsconfig.json',
'tsconfig.node.json',
Expand Down
1 change: 1 addition & 0 deletions scripts/coverage-floors.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"scripts/lib/shell-words.mjs": { "lines": 100, "branches": 100, "functions": 100 },
"scripts/lib/skills.mjs": { "lines": 100, "branches": 87, "functions": 100 },
"scripts/lib/sync-skills.mjs": { "lines": 97, "branches": 87, "functions": 100 },
"scripts/lib/template-tsconfig.mjs": { "lines": 100, "branches": 82, "functions": 100 },
"scripts/lib/theme-vars.mjs": { "lines": 100, "branches": 100, "functions": 100 },
"scripts/lib/version-regression.mjs": { "lines": 99, "branches": 95, "functions": 100 },
"scripts/mcp-registry-publish.mjs": { "lines": 100, "branches": 95, "functions": 100 },
Expand Down
61 changes: 25 additions & 36 deletions scripts/docs-fence-types.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
* setter to a typed callback, and still render. The ESLint pass in
* eslint-plugin-docs.test.mjs reads the same fences, but it checks the
* plugin's rules, not the types. This compiles each fence as its own `.tsx`
* module against the library's build, with the compiler options a new Vite
* `react-ts` app starts with, and fails on any error. A `jsx` fence compiles
* as `.tsx` too, since the site renders it the same way, so it is held to the
* same types.
* module against the library's build, with the compiler options of the app
* `npm create bestax -t vite-ts` scaffolds, read from that template, and
* fails on any error. A `jsx` fence compiles as `.tsx` too, since the site
* renders it the same way, so it is held to the same types.
*
* Each fence is read the way the site runs it. The live editor drops a live
* fence's `import` and `export default` lines (`stripModuleLines`, in
Expand Down Expand Up @@ -51,6 +51,7 @@ import { dirname, join, relative, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, it } from 'node:test';
import { fenceSpans, splitLines } from './lib/api-page.mjs';
import { appCompilerOptions } from './lib/template-tsconfig.mjs';
import { toExampleModule } from '../docs/src/theme/CodeBlock/sandbox/example.mjs';
import {
DOCS_ONLY_NAMES,
Expand All @@ -76,32 +77,20 @@ const DOCS_ONLY_MODULE = join(
const UNUSED = new Set([6133, 6192]);

/**
* The `compilerOptions` of a new Vite `react-ts` app's `tsconfig.app.json`,
* with `strict` spelled out (TypeScript 6 turns it on by default; earlier
* versions do not) and `allowJs` added so the docs-only components bind from
* their JavaScript sources. `types` is empty because the template's
* `vite/client` only declares asset imports, and the fences' imports are
* dropped.
* The compiler options the vite-ts template compiles `src/` with, changed
* only where this harness is not that app.
*/
const VITE_REACT_TS = {
target: 'es2023',
lib: ['ES2023', 'DOM'],
module: 'esnext',
types: [],
skipLibCheck: true,
moduleResolution: 'bundler',
allowImportingTsExtensions: true,
verbatimModuleSyntax: true,
moduleDetection: 'force',
noEmit: true,
jsx: 'react-jsx',
strict: true,
noUnusedLocals: true,
noUnusedParameters: true,
erasableSyntaxOnly: true,
noFallthroughCasesInSwitch: true,
allowJs: true,
};
function fenceCompilerOptions() {
return {
...appCompilerOptions(),
// The template's `vite/client` resolves from a scaffold's own install, not
// from the template in this repo, and it declares only asset imports, a
// kind of import no fence keeps.
types: [],
// The docs-only `ProfileCard` the live scope binds is a `.jsx` file.
allowJs: true,
};
}

/** The live fences in one markdown source, with their opening line index. */
export function liveFences(src) {
Expand Down Expand Up @@ -173,12 +162,12 @@ function loadTools() {
const libraryNames = Object.keys(library).filter(name =>
/^[A-Za-z_$][\w$]*$/.test(name)
);
const { options, errors } = ts.convertCompilerOptionsFromJson(
VITE_REACT_TS,
LIBRARY_DIR
);
assert.deepEqual(errors, [], 'the compiler options did not parse');
return { ts, options, libraryNames, docsOnlyModule: DOCS_ONLY_MODULE };
return {
ts,
options: fenceCompilerOptions(),
libraryNames,
docsOnlyModule: DOCS_ONLY_MODULE,
};
}

/**
Expand Down Expand Up @@ -493,7 +482,7 @@ describe('the docs live examples type-check', () => {
assert.deepEqual(
problems.filter(p => !p.startsWith('fixture/')),
[],
'Fix the example so it compiles in a new Vite react-ts app. One that ' +
'Fix the example so it compiles in a new vite-ts scaffold. One that ' +
`is wrong on purpose takes \`${OPT_OUT}\` in its info string.`
);
});
Expand Down
Loading
Loading