Gro's code generation system (.gen.* files) in @fuzdev/gro.
Gen files produce source code at build time. Discovered by the .gen.
pattern in filenames, executed by gro gen, output committed alongside
source. gro gen --check verifies no drift.
Output file is produced by dropping the .gen. segment:
| Gen file | Output file |
| ------------------------------------ | -------------------------- |
| library.gen.ts | library.ts |
| fuz.gen.css.ts | fuz.css |
| theme.gen.css.ts | theme.css |
| css_classes_fixture.gen.json.ts | css_classes_fixture.json |
| README.gen.md.ts | README.md |
| auth_attack_surface.gen.json.ts | auth_attack_surface.json |
The gen file always has a .ts (or .js) extension. An optional extension
between .gen. and .ts overrides the output extension.
- Exactly one .gen. segment per filename (duplicates are invalid)
- At most one additional extension after .gen. (e.g., .gen.css.ts is valid,
.gen.foo.bar.ts is not)
- Output filename cannot equal the gen filename
A gen file exports a gen value — either a function or a config object:
type Gen = GenFunction | GenConfig;Both importable from @fuzdev/gro or @fuzdev/gro/gen.js.
type GenFunction = (ctx: GenContext) => RawGenResult | Promise<RawGenResult>;// theme.gen.css.ts — simple form
import type {Gen} from '@fuzdev/gro';
export const gen: Gen = ({origin_path}) => {
const banner = `/* generated by ${origin_path} */`;
return `${banner}\n:root { --my-var: 1; }\n`;
};interface GenConfig {
generate: GenFunction;
dependencies?: GenDependencies;
}// highlight_priorities.gen.ts — config form with dependencies
import type {Gen} from '@fuzdev/gro';
export const gen: Gen = {
generate: ({origin_path}) => {
return `// generated by ${origin_path}\nexport const data = {};\n`;
},
dependencies: {files: ['src/lib/theme_highlight.css']},
};| Property | Type | Description |
| ----------------- | ----------------------- | --------------------------------------------------- |
| origin_id | PathId | absolute path of the gen file |
| origin_path | string | origin_id relative to the project root |
| config | GroConfig | the project's Gro configuration |
| svelte_config | ParsedSvelteConfig | parsed svelte.config.js |
| filer | Filer | filesystem tracker (file contents, dependency graph) |
| log | Logger | scoped logger |
| timings | Timings | performance measurement |
| invoke_task | InvokeTask | invoke other Gro tasks |
| changed_file_id | PathId \| undefined | set during dependency resolution; undefined during generation |
Most commonly used: origin_path (generated-by banners), log, and filer
(reading source files).
type RawGenResult = string | RawGenFile | null | Array<RawGenResult>;export const gen: Gen = () => {
return '// generated content\n';
};
// theme.gen.css.ts → writes theme.cssinterface RawGenFile {
content: string;
filename?: string; // override output name (can be relative or absolute path)
format?: boolean; // run Prettier (default: true)
}export const gen: Gen = () => {
return {content: '{"key": "value"}', filename: 'data.json', format: false};
};Relative filename resolves from the gen file's directory. Absolute paths
write to that exact location (e.g., blog.gen.ts writes static/blog/feed.xml).
export const gen: Gen = (ctx) => {
if (some_condition) return null; // produce no output
return 'content';
};Nested arrays are flattened:
export const gen: Gen = () => {
return [
{content: 'export const A = 1;', filename: 'a.ts'},
{content: 'export const B = 2;', filename: 'b.ts'},
];
};Duplicate output file IDs within a single gen file are invalid.
A single gen file can produce many output files — e.g., skill_docs.gen.ts
generates a manifest, per-skill data files, and per-page +page.svelte routes.
Control when a gen file re-runs during watch mode. Without dependencies,
re-runs only when the gen file or its imports change (tracked by filer).
Use GenConfig for broader triggers:
type GenDependencies = 'all' | GenDependenciesConfig | GenDependenciesResolver;Used by library_gen since it analyzes all source files:
export const gen: Gen = {
generate: async (ctx) => { /* ... */ },
dependencies: 'all',
};export const gen: Gen = {
generate: ({origin_path}) => { /* ... */ },
dependencies: {
patterns: [/\.svelte$/, /\.ts$/],
files: ['src/lib/theme_highlight.css'],
},
};patterns are tested against absolute paths. files can be relative
(resolved to absolute) or absolute.
Receives GenContext and returns a config, 'all', or null.
changed_file_id is set on context during dependency resolution:
type GenDependenciesResolver = (
ctx: GenContext,
) => GenDependenciesConfig | 'all' | null | Promise<GenDependenciesConfig | 'all' | null>;gro gen # run all gen files in src/
gro gen src/lib/ # run gen files in a specific directory
gro gen src/lib/foo.gen.ts # run a specific gen file
gro gen --check # verify no drift (used by gro check and CI)| Arg | Default | Description |
| ------------ | --------------- | ------------------------------------------------- |
| _ | ['src'] | input paths (files or directories to scan) |
| --root_dirs| [process.cwd()] | root directories to resolve input paths against |
| --check | false | exit nonzero if any generated files have changed |
gro gen --check compares generated output against existing files. If any
file is new or changed, it fails with a message to run gro gen. Called by
gro check as part of CI.
Every project with fuz_css has a fuz.gen.css.ts (typically in src/routes/):
import {gen_fuz_css} from '@fuzdev/fuz_css/gen_fuz_css.js';
export const gen = gen_fuz_css();Returns a GenConfig that scans source files, extracts CSS class usage via
AST, and generates a bundled fuz.css with only the classes, base styles,
and theme variables actually used. Accepts GenFuzCssOptions for customization.
fuz_css uses theme.gen.css.ts to generate the full base theme:
import type {Gen} from '@fuzdev/gro';
import {default_themes} from './themes.js';
import {render_theme_style} from './theme.js';
export const gen: Gen = ({origin_path}) => {
const banner = `/* generated by ${origin_path} */`;
const theme = default_themes[0]!;
const theme_style = render_theme_style(theme, {
comments: true,
empty_default_theme: false,
specificity: 1,
});
return `${banner}\n${theme_style}\n`;
};Every project uses library.gen.ts (typically in src/routes/) for API
documentation metadata. Analyzes TypeScript and Svelte source files and
produces library.ts and library.json:
import {library_gen} from '@fuzdev/fuz_ui/library_gen.js';
import {library_throw_on_duplicates} from '@fuzdev/fuz_ui/library_generate.js';
export const gen = library_gen({on_duplicates: library_throw_on_duplicates});Returns a GenConfig with dependencies: 'all' (re-runs on any source
change). library_throw_on_duplicates enforces the flat namespace convention
by throwing on duplicate export names across modules.
fuz_blog provides blog.gen.ts for Atom feeds, feed data, and slug routes:
export * from '@fuzdev/fuz_blog/blog.gen.js';Consumer projects re-export the gen. Returns an array with feed.xml (at an
absolute path in static/), feed.ts, and one +page.svelte per slug route.
Test fixtures can use gen files for snapshot data:
import type {Gen} from '@fuzdev/gro';
import {create_tx_app_surface_spec} from './auth_attack_surface_helpers.js';
export const gen: Gen = () => {
return JSON.stringify(create_tx_app_surface_spec().surface);
};
// auth_attack_surface.gen.json.ts → auth_attack_surface.jsonGen files can generate TypeScript types from runtime registries. zzz reads action specs and produces typed collections, metatypes, and handler interfaces:
import type {Gen} from '@fuzdev/gro/gen.js';
import * as action_specs from './action_specs.js';
import {is_action_spec} from './action_spec.js';
import {ActionRegistry} from './action_registry.js';
export const gen: Gen = ({origin_path}) => {
const registry = new ActionRegistry(
Object.values(action_specs).filter((s) => is_action_spec(s)),
);
return `
// generated by ${origin_path}
export const ActionMethods = [
${registry.methods.map((m) => `'${m}'`).join(',\n')}
] as const;
`;
};A single gen file can generate entire route trees. skill_docs.gen.ts
auto-discovers skills and generates manifests, data files, and +page.svelte
routes:
import type {Gen} from '@fuzdev/gro/gen.js';
export const gen: Gen = ({origin_path}) => {
// ... discover skills, read markdown ...
return [
{content: manifest_content, filename: 'skills_manifest.ts'},
{content: skill_data, filename: join(skill_route_dir, 'skill_data.ts')},
{content: page_content, filename: join(skill_route_dir, '+page.svelte')},
// ... more files
];
};| Export | Type | Source | Purpose |
| ---------------------- | --------- | --------------------- | ------------------------------------------------ |
| Gen | Type | @fuzdev/gro/gen.js | GenFunction or GenConfig |
| GenFunction | Type | @fuzdev/gro/gen.js | (ctx: GenContext) => RawGenResult |
| GenConfig | Interface | @fuzdev/gro/gen.js | generate + optional dependencies |
| GenContext | Interface | @fuzdev/gro/gen.js | context passed to gen functions |
| RawGenResult | Type | @fuzdev/gro/gen.js | string, RawGenFile, null, or nested array |
| RawGenFile | Interface | @fuzdev/gro/gen.js | output file with content, filename, format |
| GenDependencies | Type | @fuzdev/gro/gen.js | 'all', config object, or resolver function |
| GenDependenciesConfig| Interface | @fuzdev/gro/gen.js | patterns? (RegExp[]) and files? (PathId[]) |
Gen and GenContext are also re-exported from @fuzdev/gro (the package
index).