Projects and workspaces
The .skmtc/<project>/ directory layout — what files exist, which are derived, which are user-edited. A SKMTC project is a generator configuration, not a consuming application: it specifies which…
A project is not the consuming app — it's the generator configuration the consuming app pulls code from. One repository can contain multiple SKMTC projects, each generating into a different part of the consuming app (or into different apps).
The one-paragraph essence
A SKMTC root is a directory containing a .skmtc/ folder. Inside
.skmtc/ is one directory per project. Each project directory holds
the project's deno.json (which generators are installed), its
.settings/client.json (which schema, which output path, which
enrichments), its worker.ts and bundle.js (derived from
deno.json, the things the Worker actually loads), and optionally
local generator source directories for any cloned generators.
Generated output lands wherever client.json#settings.basePath
points.
The SKMTC root
The SKMTC root is the nearest ancestor directory containing a
.skmtc/ folder. The CLI discovers it by walking up from the current
working directory.
my-app/ ← SKMTC root (nearest .skmtc/ ancestor)
├── .skmtc/ ← root marker
│ ├── my-api/ ← project "my-api"
│ ├── my-graphql/ ← project "my-graphql"
│ └── ...
├── src/ ← consuming app source
└── package.json ← consuming appCreated by skmtc init. After init, all skmtc commands run from
anywhere inside the root will find it.
Project directory layout
.skmtc/<project>/
├── deno.json ← user-edited (or via CLI)
├── deno.lock ← auto-managed by Deno
├── worker.ts ← derived from deno.json
├── bundle.js ← derived from worker.ts (present for every project)
├── .settings/
│ ├── client.json ← user-edited (schema, basePath, enrichments)
│ ├── manifest.json ← derived; overwritten every generate run
│ ├── logs.txt ← derived; bundle/generate stdout
│ └── error-logs.txt ← derived; bundle/generate stderr
└── <gen-name>/ ← only if cloned generators exist
├── deno.json
├── mod.ts
└── src/...Each file's role:
deno.json
The project's dependency manifest. JSR imports of installed generators plus core peer dependencies:
{
"imports": {
"@skmtc/core": "jsr:@skmtc/core@^0.3.7",
"@skmtc/worker": "jsr:@skmtc/worker@^0.2.0",
"@skmtc/gen-zod": "jsr:@skmtc/gen-zod@^0.0.55",
"@skmtc/gen-typescript": "jsr:@skmtc/gen-typescript@^0.0.55"
}
}User-edited via skmtc install, skmtc clone, or skmtc remove.
Hand-editing is supported but the CLI commands also do validation
(peer-pin checks) that hand-editing skips.
deno.lock
Auto-managed by Deno. Pins exact versions and content hashes. Ensures reproducible installs across machines.
worker.ts
A derived file — the CLI templates it from deno.json#imports:
import toWorker from '@skmtc/worker'
import gen1 from '@skmtc/gen-zod'
import gen2 from '@skmtc/gen-typescript'
// ... one import per generator
export default toWorker(() =>
Object.fromEntries([gen1, gen2].map(g => [g.id, g]))
)Regenerated by skmtc bundle and skmtc dev. Never hand-edited.
Hand-edits are lost on next bundle.
bundle.js
The compiled output of deno bundle worker.ts -o bundle.js. This is
what the SKMTC Worker actually loads at generate time.
Present for every project, including remote-only ones (all
generators installed from JSR). skmtc generate loads the
project-local bundle.js regardless of where the generator source
comes from, so a matching worker.ts/bundle.js must exist.
Bundle freshness — i.e., whether bundle.js matches the current
deno.json — is checked by skmtc doctor. The project-bundle/
check surfaces stale bundles.
.settings/client.json
The user-facing configuration. Schema source, output path, generator filters, enrichments:
{
"source": "./openapi.json",
"settings": {
"basePath": "src/generated",
"skip": [],
"include": [],
"enrichments": { /* per-generator */ }
}
}User-edited. The source, basePath, and enrichments are the
three knobs most users adjust.
.settings/manifest.json
A derived file — overwritten every skmtc generate run. Contains
the canonical record of the last run: parse issues, per-(generator,
operation) results, file metadata, timing.
See error handling philosophy
for the structure and manifest-format.md reference for the full
schema.
.settings/logs.txt and .settings/error-logs.txt
stdout and stderr captures from skmtc bundle runs. Useful for
debugging bundle failures.
<gen-name>/
Present only for cloned generators. Contains the generator's source
code — deno.json, mod.ts, src/. The directory is what
skmtc clone writes to.
After cloning, the deno.json#imports entry points to this local
directory (e.g., "@skmtc/gen-zod": "./gen-zod/mod.ts") instead of a
JSR specifier.
basePath alignment with the consuming app
The basePath field in client.json is load-bearing: it
controls both where on disk generated files land AND the @ alias
in the consuming app's bundler resolver.
Example: a Vite app with vite.config.ts setting @ → ./src would
configure basePath: "src/generated" (relative to the SKMTC root).
Generators produce paths like @/forms/CreateUserForm.generated.tsx,
which:
- On disk lands at
<root>/src/generated/forms/CreateUserForm.generated.tsx - In the consuming app's TS imports resolves to
<root>/src/forms/...via the bundler
These two paths must match. If they diverge, generated imports break at runtime.
For Vite/Next/Webpack/Rollup, the @ alias is typically configured
in their respective config files. SKMTC doesn't manage the bundler
config — you align them manually.
Global state at ~/.skmtc/
Not all project state is per-project. SKMTC also maintains a global state directory:
~/.skmtc/
├── auth-sb-api-auth-token.txt ← Supabase auth for the sandbox API
├── <project-name>/ ← shadow project state (per-project)
│ ├── schema.json ← cached schema
│ └── manifest.json ← shadow manifest
└── ...The global state is mostly used by the hosted sandbox API path. For
local-only setups, you can mostly ignore it. When debugging "why
isn't this working," check both <root>/.skmtc/<project>/ and
~/.skmtc/<project>/.
Multiple projects in one repo
A repo can have many projects:
my-app/
├── .skmtc/
│ ├── api-v1/ ← old API generator config
│ ├── api-v2/ ← new API generator config
│ ├── graphql/ ← GraphQL operations
│ └── mocks/ ← MSW handlers
├── src/
│ ├── generated/ ← api-v2 output
│ ├── generated-mocks/← mocks output (different basePath)
│ └── ...Each project has its own deno.json, client.json, generators, and
output location. The CLI subcommands take the project name as an
argument:
skmtc generate api-v2
skmtc generate mocksThis is the right pattern when you want different generator sets, different schemas, or different output locations. There's no performance benefit to combining multiple "logical" projects into one — generators run independently per project anyway.
Initialization
skmtc init <projectName> <basePath>:
- Creates
.skmtc/<projectName>/ - Writes
deno.jsonwith the core peer-dep pin and an empty imports list - Writes
.settings/client.jsonwithbasePathand no source - Returns
{ type: 'created', projectName, basePath }
If the project already exists, init returns { type: 'existed' }
and exits cleanly — it's idempotent.
The basePath argument:
- Must be relative to the SKMTC root. Absolute paths are rejected with exit code 2 and a recipe error.
- Must match the consumer app's bundler alias. The CLI doesn't
verify this — the user does — but
skmtc doctor'sproject-base-path/<project>check surfaces obvious mistakes (missing, absolute, etc.).
Common questions
Can multiple projects share generators?
The JSR imports in each project's deno.json are independent. If
both projects install @skmtc/gen-zod, each pins its own version.
Deno's content-addressed cache means the package only downloads once
per version, so the disk cost is low even with duplication.
If you clone a generator: the clone is project-local. It exists in
one project's directory only. To share a clone, you'd need to
publish your customized version to JSR and install it in both
projects.
How do I add or remove generators?
Use skmtc install <generator-id> <project> and
skmtc remove <project> <generator-id>. Hand-editing deno.json
works but skips the CLI's pre-flight checks (peer-pin compatibility,
auto-rebundle). Recommended: use the CLI for state changes; only
hand-edit for advanced pinning needs.
What happens to a project's bundle.js when I install a new generator?
skmtc install triggers a post-install rebundle unconditionally —
regardless of whether the project has any clones. The newly installed
generator is bundled into bundle.js so generate picks it up. (The
same holds for a project with only installed generators: it still has
a local bundle.js, and install refreshes it.)
Where do tests for cloned generators live?
There's no convention. Some teams put them adjacent to the cloned
source (.skmtc/<project>/<gen-name>/src/*.test.ts); some put them
in the consuming app's test directory; some skip them. Cloned
generators are project source, so the test location is the user's
call.
Can I version-control .skmtc/<project>/?
Yes — and you should. The directory contains:
- User-edited config (
deno.json,client.json) — definitely commit - Derived files (
worker.ts,bundle.js,manifest.json,logs.txt) — gitignore is reasonable - Cloned generator source (
<gen-name>/) — definitely commit (it's your code now)
A reasonable .gitignore:
.skmtc/**/bundle.js
.skmtc/**/worker.ts
.skmtc/**/.settings/manifest.json
.skmtc/**/.settings/*.txtCan SKMTC live outside a Node/pnpm project?
Yes. SKMTC requires Deno; the consuming app can be anything (Vite,
Next, Bun, even a non-JS project that doesn't import the generated
code). The CLI doesn't read package.json.
What if I have multiple SKMTC roots in nested directories?
The CLI discovers the nearest ancestor .skmtc/. If you have
nested roots, only the inner one is found. To work with the outer
root, run the CLI from a directory outside the inner root.
In practice, one SKMTC root per repository is the convention.
Further reading
- Clone vs install — what changes in the project directory when cloning
- Enrichments — the
client.json#settings.enrichmentsstructure - The Worker runtime — how
worker.tsandbundle.jsget used - Settings reference: client.json schema
skmtc initreferenceskmtc doctorreferenceskmtc-cliskill
Clone vs install
SKMTC's customization model: a graduated set of levers from "accept stock defaults" through "edit per-operation overrides" to "edit the generator source itself." Install for stock; configure for…
Multi-package output
By default SKMTC writes every generated file under a single root — client.json#settings.basePath — and every cross-file import renders through one @/… alias. Multi-package output lets a project route…