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
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"tailwindCSS.lint.suggestCanonicalClasses": "ignore",
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true,
"[typescript]": {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"conventional-changelog-conventionalcommits": "catalog:",
"eslint-plugin-tailwind-canonical-classes": "^1.3.1",
"semantic-release": "catalog:",
"tsx": "^4.21.0",
"vite-plus": "catalog:"
},
"engines": {
Expand All @@ -26,7 +27,8 @@
"pnpm": {
"onlyBuiltDependencies": [
"esbuild",
"lightningcss"
"lightningcss",
"sharp"
]
}
}
4,774 changes: 4,538 additions & 236 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,18 @@ catalog:
"@semantic-release/commit-analyzer": ^13.0.1
"@semantic-release/github": ^11.0.1
"@semantic-release/release-notes-generator": ^14.0.3
"@tanstack/react-start": ^1.167.16
"@types/node": ^24
conventional-changelog-conventionalcommits: ^9.3.1
semantic-release: ^25.0.3
tailwindcss: ^4.1.18
typescript: ^5
vite: npm:@voidzero-dev/vite-plus-core@latest
vite: npm:@voidzero-dev/vite-plus-core@^0.1.16
vite-plus: latest
vitest: npm:@voidzero-dev/vite-plus-test@latest

catalogMode: prefer

overrides:
vite: "catalog:"
vitest: "catalog:"

peerDependencyRules:
allowAny:
- vite
Expand Down
16 changes: 0 additions & 16 deletions website/.cta.json

This file was deleted.

1 change: 1 addition & 0 deletions website/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_PROJECT_NAME = Colander
5 changes: 5 additions & 0 deletions website/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
VITE_PROJECT_NAME=Colander
VITE_PACKAGE_NAME=@klinking/colander
VITE_GITHUB_REPO_URL=https://github.com/klinking/colander
VITE_GITHUB_MAIN_BRANCH=main
VITE_AUTHOR_NAME="Chris Klink"
3 changes: 2 additions & 1 deletion website/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ node_modules
dist
dist-ssr
*.local
.env
.nitro
.tanstack
.wrangler
.netlify
.output
.vinxi
__unconfig*
todos.json
api-data/symbols.gen.json
src/examples/*.highlighted.gen.json
6 changes: 6 additions & 0 deletions website/netlify.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[build]
command = "pnpm build"
publish = "dist/client"

[functions]
directory = ".netlify/v1/functions"
19 changes: 9 additions & 10 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"scripts": {
"dev": "vite dev --port 3000",
"build": "vite build",
"build": "tsx scripts/extract-api.ts && vite build",
"preview": "vite preview",
"test": "vp test run"
},
Expand All @@ -21,9 +21,9 @@
"@fontsource/unica-one": "^5.2.8",
"@js-temporal/polyfill": "^0.5.1",
"@markdoc/markdoc": "^0.5.6",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/react-devtools": "^0.10.0",
"@tanstack/react-router": "^1.166.11",
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-devtools": "^0.10.2",
"@tanstack/react-router": "^1.168.10",
"@tanstack/react-router-devtools": "^1.166.11",
"@tanstack/react-start": "^1.167.16",
"@unpic/react": "^1.0.2",
Expand All @@ -37,24 +37,23 @@
"shadcn": "^4.1.0",
"shiki": "^4.0.2",
"tailwind-merge": "^3.5.0",
"tailwindcss": "catalog:",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@netlify/vite-plugin-tanstack-start": "^1.3.3",
"@tanstack/devtools-vite": "latest",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.0",
"@types/node": "catalog:",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^5.1.4",
"@vitejs/plugin-react": "^6.0.1",
"jsdom": "^28.1.0",
"tailwindcss": "^4.2.2",
"ts-morph": "^27.0.2",
"tsx": "^4.21.0",
"typescript": "catalog:",
"vite": "catalog:",
"vite": "^8.0.8",
"vite-imagetools": "^10.0.0",
"vite-plus": "catalog:",
"vitest": "catalog:"
"vitest": "^4.1.4"
}
}
46 changes: 46 additions & 0 deletions website/plugins/extract-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { execSync } from "node:child_process";
import path from "node:path";
import type { Plugin, ResolvedConfig } from "vite";

function runExtract(rootDir: string) {
execSync("tsx scripts/extract-api.ts", {
cwd: rootDir,
stdio: "inherit",
});
}

export function extractApi(): Plugin {
let rootDir: string;
let packageSrcDir: string;

return {
name: "extract-api",
apply: "serve",

configResolved(config: ResolvedConfig) {
rootDir = config.root;
packageSrcDir = path.resolve(rootDir, "../package/src");
try {
runExtract(rootDir);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error(`[extract-api] initial extraction failed:\n ${msg}`);
}
},

configureServer(server) {
server.watcher.add(packageSrcDir);
server.watcher.on("change", (changedPath) => {
if (path.resolve(changedPath).startsWith(path.resolve(packageSrcDir))) {
try {
runExtract(rootDir);
server.ws.send({ type: "full-reload" });
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error(`[extract-api] re-extraction failed:\n ${msg}`);
}
}
});
},
};
}
8 changes: 5 additions & 3 deletions website/plugins/fluid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ function readRemPerUnit(rootDir: string): number {
}

function loadConfig(configPath: string): FluidConfig {
const json = execSync(
`npx tsx -e "import c from '${configPath}'; console.log(JSON.stringify(c))"`,
const raw = execSync(
`vp exec tsx -e "import c from '${configPath}'; console.log(JSON.stringify(c))"`,
{ encoding: "utf-8" },
);
return JSON.parse(json);
// vp exec prints a banner before the output — extract the JSON line
const jsonLine = raw.trim().split("\n").pop()!;
return JSON.parse(jsonLine);
}

function clampLine(
Expand Down
131 changes: 131 additions & 0 deletions website/plugins/highlight-examples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { execFileSync } from "node:child_process";
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import { codeToHtml } from "shiki";
import type { Plugin, ResolvedConfig } from "vite";
import { stripTypes } from "../src/lib/strip-types.ts";

interface HighlightedExample {
tsHtml: string;
jsHtml: string;
language: string;
}

function formatCode(source: string, fileName: string, rootDir: string): string {
try {
return execFileSync("npx", ["vp", "fmt", "--stdin-filepath", fileName], {
input: source,
encoding: "utf-8",
cwd: path.resolve(rootDir, ".."),
});
} catch {
return source;
}
}

async function highlight(code: string, lang: string): Promise<string> {
return codeToHtml(code.trimEnd(), {
lang,
theme: "github-dark-default",
});
}

async function processExample(
file: string,
examplesDir: string,
rootDir: string,
): Promise<HighlightedExample> {
const filePath = path.join(examplesDir, file);
const tsRaw = readFileSync(filePath, "utf-8");
const jsRaw = stripTypes(tsRaw, file);
const isTsx = file.endsWith(".tsx");
const tsLang = isTsx ? "tsx" : "ts";
const jsLang = isTsx ? "jsx" : "js";

const tsFormatted = formatCode(tsRaw, file, rootDir);
const jsFormatted = formatCode(
jsRaw,
file.replace(/\.tsx?$/, jsLang === "jsx" ? ".jsx" : ".js"),
rootDir,
);

const [tsHtml, jsHtml] = await Promise.all([
highlight(tsFormatted, tsLang),
highlight(jsFormatted, jsLang),
]);

return { tsHtml, jsHtml, language: tsLang };
}

function outputPath(examplesDir: string, file: string): string {
const base = file.replace(/\.tsx?$/, "");
return path.join(examplesDir, `${base}.highlighted.gen.json`);
}

async function generateAll(examplesDir: string, rootDir: string) {
const files = readdirSync(examplesDir).filter(
(f) => /\.[jt]sx?$/.test(f) && !f.includes(".gen."),
);

await Promise.all(
files.map(async (file) => {
const data = await processExample(file, examplesDir, rootDir);
writeFileSync(outputPath(examplesDir, file), JSON.stringify(data));
}),
);

console.log(
`[highlight-examples] generated ${files.length} highlighted example(s)`,
);
}

async function generateOne(file: string, examplesDir: string, rootDir: string) {
const data = await processExample(file, examplesDir, rootDir);
writeFileSync(outputPath(examplesDir, file), JSON.stringify(data));
console.log(`[highlight-examples] regenerated ${file}`);
}

export function highlightExamples(): Plugin {
let rootDir: string;
let examplesDir: string;

return {
name: "highlight-examples",

async configResolved(config: ResolvedConfig) {
rootDir = config.root;
examplesDir = path.join(rootDir, "src/examples");
try {
await generateAll(examplesDir, rootDir);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error(
`[highlight-examples] initial generation failed:\n ${msg}`,
);
}
},

configureServer(server) {
server.watcher.add(examplesDir);
server.watcher.on("change", async (changedPath) => {
const resolved = path.resolve(changedPath);
if (
!resolved.startsWith(path.resolve(examplesDir)) ||
resolved.includes(".gen.")
) {
return;
}
const file = path.basename(resolved);
try {
await generateOne(file, examplesDir, rootDir);
server.ws.send({ type: "full-reload" });
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error(
`[highlight-examples] failed to regenerate ${file}:\n ${msg}`,
);
}
});
},
};
}
Loading
Loading