Back to skills
SkillHub ClubShip Full StackFull Stack

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.

Stars
1
Hot score
77
Updated
March 20, 2026
Overall rating
C0.4
Composite score
0.4
Best-practice grade
B80.4

Install command

npx @skill-hub/cli install khalic-lab-schmock-package-generator

Repository

khalic-lab/schmock

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 repository

Best 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

Claude CodeCodex CLIGemini CLIOpenCode

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!");

```

package-generator | SkillHub