package-generator
Scaffold a new @schmock/* package with full monorepo structure. Creates package.json, tsconfig, vitest configs, entry point, and registers in workspace.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install khalic-lab-schmock-package-generator
Repository
Skill path: .claude/skills/package-generator
Scaffold a new @schmock/* package with full monorepo structure. Creates package.json, tsconfig, vitest configs, entry point, and registers in workspace.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: khalic-lab.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install package-generator into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/khalic-lab/schmock before adding package-generator to shared team environments
- Use package-generator for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: package-generator
description: >
Scaffold a new @schmock/* package with full monorepo structure. Creates
package.json, tsconfig, vitest configs, entry point, and registers in workspace.
argument-hint: "<package-name>"
disable-model-invocation: true
allowed-tools:
- Bash(bun .claude/skills/package-generator/scripts/generate.ts *)
---
# Schmock Package Generator Skill
## Package Anatomy
Every `@schmock/*` package needs these files:
```
packages/<name>/
package.json # Name, version, exports, scripts, peer deps
tsconfig.json # Extends root, NodeNext module resolution
vitest.config.ts # Unit test config
vitest.config.bdd.ts # BDD test config
src/
index.ts # Entry point with exports
```
### package.json
Based on the express/angular adapter pattern:
- `"type": "module"` — ESM only
- Exports: `"."` with `types` and `import` conditions
- Scripts: `build`, `build:lib`, `build:types`, `test`, `test:bdd`, `lint`, `check:publish`
- Build: `bun build --minify` for JS, `tsc` for declarations
- Peer dep on `@schmock/core: ^1.0.0`
- Dev deps: `vitest`, `@amiceli/vitest-cucumber`, `typescript`
### tsconfig.json
Extends root config with package-specific overrides:
- `module: "NodeNext"`, `moduleResolution: "NodeNext"`
- `rootDir: "./src"`, `outDir: "./dist"`
- Excludes: `*.test.ts`, `*.steps.ts`
- References `../core`
### vitest.config.ts (unit)
```typescript
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "node",
include: ["src/**/*.test.ts"],
exclude: ["**/*.steps.ts"],
},
});
```
### vitest.config.bdd.ts
```typescript
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "node",
include: ["src/**/*.steps.ts"],
reporters: [['default', { summary: false }]],
},
});
```
## Registration
After generating the package, these files need updating:
1. **Root `tsconfig.json`** — Add path alias: `"@schmock/<name>": ["packages/<name>/src"]`
2. **Root `package.json`** — Add to `build` and `typecheck` scripts
The generate script handles tsconfig registration automatically.
## Conventions
- Build: `bun build --minify` for JS output + `tsc` for type declarations
- ESM-only output (no CJS)
- Strict TypeScript (inherited from root config)
- Author: `Khalic Lab`, License: `MIT`
## Post-Generation Steps
After scaffolding a new package:
1. Create a `.feature` file for the package behavior (`/development scaffold <name> <pkg>`)
2. Add to CI workflow matrix if needed (`.github/workflows/`)
3. Install any specific dependencies: `bun add -D <dep> --cwd packages/<name>`
4. Start implementing!
## Command
```
/package-generator <name>
```
Example:
```
/package-generator fastify
```
Creates `packages/fastify/` with full structure and registers it in the workspace.
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### scripts/__tests__/generate.test.ts
```typescript
import { execSync } from "node:child_process";
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
const SCRIPT = join(__dirname, "..", "generate.ts");
const ROOT = join(__dirname, "../../../../..");
function run(args: string): { output: string; exitCode: number } {
try {
const stdout = execSync(`bun ${SCRIPT} ${args}`, {
cwd: ROOT,
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"],
});
return { output: stdout, exitCode: 0 };
} catch (e: any) {
const output = [e.stdout, e.stderr].filter(Boolean).join("\n");
return { output, exitCode: e.status ?? 1 };
}
}
function readJson(path: string): any {
return JSON.parse(readFileSync(path, "utf-8"));
}
describe("generate.ts", () => {
// Use a valid package name (lowercase, alphanumeric + hyphens only)
const testPkg = `test-gen-${Date.now()}`;
const pkgDir = join(ROOT, "packages", testPkg);
const tsconfigPath = join(ROOT, "tsconfig.json");
let tsconfigBackup: string;
beforeEach(() => {
tsconfigBackup = readFileSync(tsconfigPath, "utf-8");
});
afterEach(() => {
if (existsSync(pkgDir)) {
rmSync(pkgDir, { recursive: true, force: true });
}
writeFileSync(tsconfigPath, tsconfigBackup);
});
describe("argument validation", () => {
it("should fail without arguments", () => {
const result = run("");
expect(result.exitCode).not.toBe(0);
expect(result.output).toContain("Usage:");
});
it("should fail with invalid package name (uppercase)", () => {
const result = run("InvalidName");
expect(result.exitCode).not.toBe(0);
expect(result.output).toContain("Invalid package name");
});
it("should fail with invalid package name (starts with number)", () => {
const result = run("123invalid");
expect(result.exitCode).not.toBe(0);
expect(result.output).toContain("Invalid package name");
});
});
describe("package generation", () => {
it("should create all required files", () => {
const result = run(testPkg);
expect(result.exitCode).toBe(0);
expect(existsSync(join(pkgDir, "package.json"))).toBe(true);
expect(existsSync(join(pkgDir, "tsconfig.json"))).toBe(true);
expect(existsSync(join(pkgDir, "vitest.config.ts"))).toBe(true);
expect(existsSync(join(pkgDir, "vitest.config.bdd.ts"))).toBe(true);
expect(existsSync(join(pkgDir, "src", "index.ts"))).toBe(true);
});
it("should generate correct package.json", () => {
run(testPkg);
const pkg = readJson(join(pkgDir, "package.json"));
expect(pkg.name).toBe(`@schmock/${testPkg}`);
expect(pkg.version).toBe("1.0.0");
expect(pkg.type).toBe("module");
expect(pkg.peerDependencies["@schmock/core"]).toBe("^1.0.0");
});
it("should generate tsconfig.json extending root", () => {
run(testPkg);
const tsconfig = readJson(join(pkgDir, "tsconfig.json"));
expect(tsconfig.extends).toBe("../../tsconfig.json");
expect(tsconfig.compilerOptions.module).toBe("NodeNext");
expect(tsconfig.exclude).toContain("**/*.test.ts");
expect(tsconfig.exclude).toContain("**/*.steps.ts");
});
it("should generate vitest configs", () => {
run(testPkg);
const unitConfig = readFileSync(
join(pkgDir, "vitest.config.ts"),
"utf-8",
);
expect(unitConfig).toContain("src/**/*.test.ts");
expect(unitConfig).toContain("**/*.steps.ts");
const bddConfig = readFileSync(
join(pkgDir, "vitest.config.bdd.ts"),
"utf-8",
);
expect(bddConfig).toContain("src/**/*.steps.ts");
});
it("should register path alias in root tsconfig.json", () => {
run(testPkg);
const tsconfig = readJson(tsconfigPath);
expect(tsconfig.compilerOptions.paths[`@schmock/${testPkg}`]).toEqual([
`packages/${testPkg}/src`,
]);
});
it("should fail if package directory already exists", () => {
run(testPkg);
const result = run(testPkg);
expect(result.exitCode).not.toBe(0);
expect(result.output).toContain("already exists");
});
});
});
```
### scripts/generate.ts
```typescript
#!/usr/bin/env bun
/**
* Generate a new @schmock/* package with full monorepo structure.
*
* Usage:
* bun generate.ts <package-name>
*
* Example:
* bun generate.ts fastify
*
* Creates:
* packages/<name>/package.json
* packages/<name>/tsconfig.json
* packages/<name>/vitest.config.ts
* packages/<name>/vitest.config.bdd.ts
* packages/<name>/src/index.ts
*
* Registers in:
* tsconfig.json (path alias)
*/
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
const name = process.argv[2];
if (!name) {
console.error("Usage: bun generate.ts <package-name>");
console.error("Example: bun generate.ts fastify");
process.exit(1);
}
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
console.error(
`Invalid package name: ${name} (must be lowercase, alphanumeric + hyphens)`,
);
process.exit(1);
}
const root = join(import.meta.dir, "../../../..");
const pkgDir = join(root, "packages", name);
if (existsSync(pkgDir)) {
console.error(`Package directory already exists: packages/${name}`);
process.exit(1);
}
const templateDir = join(import.meta.dir, "..", "templates");
function readTemplate(filename: string): string {
return readFileSync(join(templateDir, filename), "utf-8");
}
function render(template: string, vars: Record<string, string>): string {
let result = template;
for (const [key, value] of Object.entries(vars)) {
result = result.replaceAll(`{{${key}}}`, value);
}
return result;
}
const vars = {
PACKAGE_NAME: name,
DESCRIPTION: `${name.charAt(0).toUpperCase() + name.slice(1)} adapter for Schmock mock API generator`,
};
// Create directory structure
mkdirSync(join(pkgDir, "src"), { recursive: true });
// Render and write templates
const files: Array<[string, string]> = [
["package.json", render(readTemplate("package.json.tmpl"), vars)],
["tsconfig.json", readTemplate("tsconfig.json.tmpl")],
["vitest.config.ts", readTemplate("vitest.config.ts.tmpl")],
["vitest.config.bdd.ts", readTemplate("vitest.config.bdd.ts.tmpl")],
["src/index.ts", render(readTemplate("index.ts.tmpl"), vars)],
];
for (const [path, content] of files) {
writeFileSync(join(pkgDir, path), content);
}
// Register in root tsconfig.json
const tsconfigPath = join(root, "tsconfig.json");
const tsconfig = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
const alias = `@schmock/${name}`;
if (!tsconfig.compilerOptions.paths[alias]) {
tsconfig.compilerOptions.paths[alias] = [`packages/${name}/src`];
writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`);
console.log(`Registered path alias: ${alias} → packages/${name}/src`);
}
console.log("");
console.log(`Generated @schmock/${name}:`);
for (const [path] of files) {
console.log(` packages/${name}/${path}`);
}
console.log("");
console.log("Next steps:");
console.log(` 1. cd packages/${name} && bun install`);
console.log(` 2. Add to root package.json build/typecheck scripts`);
console.log(` 3. Create a feature file: /development scaffold ${name}-adapter ${name}`);
console.log(" 4. Start implementing!");
```