@outfitter/presets 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +30 -0
  2. package/dist/index.js +1 -1
  3. package/package.json +25 -24
  4. package/presets/_base/AGENTS.md.template +3 -0
  5. package/presets/_base/CLAUDE.md.template +35 -0
  6. package/presets/_examples/cli-todo/src/program.ts.template +202 -0
  7. package/presets/_examples/mcp-files/src/mcp.ts.template +181 -0
  8. package/presets/basic/.lefthook.yml.template +2 -2
  9. package/presets/basic/README.md.template +22 -0
  10. package/presets/basic/package.json.template +41 -37
  11. package/presets/basic/src/index.test.ts.template +26 -0
  12. package/presets/basic/src/index.ts.template +32 -15
  13. package/presets/basic/tsconfig.json.template +28 -29
  14. package/presets/cli/.lefthook.yml.template +2 -2
  15. package/presets/cli/CLAUDE.md.template +60 -0
  16. package/presets/cli/README.md.template +5 -1
  17. package/presets/cli/package.json.template +47 -44
  18. package/presets/cli/src/commands/hello.ts.template +26 -0
  19. package/presets/cli/src/index.test.ts.template +38 -0
  20. package/presets/cli/src/index.ts.template +2 -0
  21. package/presets/cli/src/program.ts.template +17 -19
  22. package/presets/cli/src/types.ts.template +13 -0
  23. package/presets/cli/tsconfig.json.template +29 -29
  24. package/presets/daemon/.lefthook.yml.template +2 -2
  25. package/presets/daemon/CLAUDE.md.template +53 -0
  26. package/presets/daemon/README.md.template +6 -7
  27. package/presets/daemon/package.json.template +50 -47
  28. package/presets/daemon/src/cli.ts.template +73 -66
  29. package/presets/daemon/src/daemon-main.ts.template +56 -55
  30. package/presets/daemon/src/daemon.ts.template +7 -3
  31. package/presets/daemon/src/index.test.ts.template +9 -0
  32. package/presets/daemon/tsconfig.json.template +21 -21
  33. package/presets/full-stack/CLAUDE.md.template +66 -0
  34. package/presets/full-stack/apps/cli/package.json.template +34 -33
  35. package/presets/full-stack/apps/cli/src/cli.ts.template +16 -15
  36. package/presets/full-stack/apps/cli/src/index.test.ts.template +12 -11
  37. package/presets/full-stack/apps/cli/tsconfig.json.template +32 -32
  38. package/presets/full-stack/apps/mcp/package.json.template +35 -34
  39. package/presets/full-stack/apps/mcp/src/index.test.ts.template +12 -11
  40. package/presets/full-stack/apps/mcp/src/mcp.ts.template +10 -10
  41. package/presets/full-stack/apps/mcp/src/server.ts.template +3 -2
  42. package/presets/full-stack/apps/mcp/tsconfig.json.template +32 -32
  43. package/presets/full-stack/package.json.template +20 -14
  44. package/presets/full-stack/packages/core/package.json.template +31 -30
  45. package/presets/full-stack/packages/core/src/handlers.ts.template +29 -24
  46. package/presets/full-stack/packages/core/src/index.test.ts.template +23 -21
  47. package/presets/full-stack/packages/core/src/types.ts.template +11 -8
  48. package/presets/full-stack/packages/core/tsconfig.json.template +29 -29
  49. package/presets/library/CLAUDE.md.template +68 -0
  50. package/presets/library/bunup.config.ts.template +16 -16
  51. package/presets/library/package.json.template +51 -50
  52. package/presets/library/src/handlers.ts.template +51 -27
  53. package/presets/library/src/index.test.ts.template +40 -29
  54. package/presets/library/src/types.ts.template +14 -8
  55. package/presets/library/tsconfig.json.template +29 -29
  56. package/presets/mcp/.lefthook.yml.template +2 -2
  57. package/presets/mcp/CLAUDE.md.template +97 -0
  58. package/presets/mcp/README.md.template +12 -9
  59. package/presets/mcp/package.json.template +48 -44
  60. package/presets/mcp/src/index.test.ts.template +49 -0
  61. package/presets/mcp/src/index.ts.template +2 -0
  62. package/presets/mcp/src/mcp.ts.template +16 -16
  63. package/presets/mcp/src/server.ts.template +8 -1
  64. package/presets/mcp/src/tools/hello.ts.template +48 -0
  65. package/presets/mcp/tsconfig.json.template +21 -21
  66. package/presets/minimal/.lefthook.yml.template +2 -2
  67. package/presets/minimal/README.md.template +22 -0
  68. package/presets/minimal/package.json.template +47 -44
  69. package/presets/minimal/src/index.test.ts.template +19 -0
  70. package/presets/minimal/src/index.ts.template +10 -15
  71. package/presets/minimal/tsconfig.json.template +28 -29
  72. package/presets/cli/biome.json.template +0 -4
  73. package/presets/daemon/biome.json.template +0 -4
  74. package/presets/mcp/biome.json.template +0 -4
@@ -1,24 +1,25 @@
1
- import { createContext } from "@outfitter/contracts";
2
1
  import { output } from "@outfitter/cli";
2
+ import { createContext } from "@outfitter/contracts";
3
3
  import { createGreeting } from "{{packageName}}-core";
4
4
 
5
- export async function runCli(argv: readonly string[] = process.argv): Promise<void> {
6
- const args = argv.slice(2);
7
- const name =
8
- args.find((arg) => !arg.startsWith("-")) ?? "World";
9
- const excited = args.includes("--excited");
10
- const result = await createGreeting(
11
- { name, excited },
12
- createContext({ cwd: process.cwd(), env: process.env })
13
- );
5
+ export async function runCli(
6
+ argv: readonly string[] = process.argv
7
+ ): Promise<void> {
8
+ const args = argv.slice(2);
9
+ const name = args.find((arg) => !arg.startsWith("-")) ?? "World";
10
+ const excited = args.includes("--excited");
11
+ const result = await createGreeting(
12
+ { name, excited },
13
+ createContext({ cwd: process.cwd(), env: process.env })
14
+ );
14
15
 
15
- if (result.isErr()) {
16
- throw result.error;
17
- }
16
+ if (result.isErr()) {
17
+ throw result.error;
18
+ }
18
19
 
19
- await output(result.value.message, { mode: "human" });
20
+ await output(result.value.message, { mode: "human" });
20
21
  }
21
22
 
22
23
  if (import.meta.main) {
23
- await runCli();
24
+ await runCli();
24
25
  }
@@ -1,18 +1,19 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+
2
3
  import { createContext } from "@outfitter/contracts";
3
4
  import { createGreeting } from "{{packageName}}-core";
4
5
 
5
6
  describe("cli surface", () => {
6
- test("can use shared core handler", async () => {
7
- const result = await createGreeting(
8
- { name: "CLI", excited: true },
9
- createContext({ cwd: process.cwd(), env: process.env })
10
- );
7
+ test("can use shared core handler", async () => {
8
+ const result = await createGreeting(
9
+ { name: "CLI", excited: true },
10
+ createContext({ cwd: process.cwd(), env: process.env })
11
+ );
11
12
 
12
- expect(result.isOk()).toBe(true);
13
- if (result.isErr()) {
14
- return;
15
- }
16
- expect(result.value.message).toBe("Hello, CLI!");
17
- });
13
+ expect(result.isOk()).toBe(true);
14
+ if (result.isErr()) {
15
+ return;
16
+ }
17
+ expect(result.value.message).toBe("Hello, CLI!");
18
+ });
18
19
  });
@@ -1,37 +1,37 @@
1
1
  {
2
- "$schema": "https://json.schemastore.org/tsconfig",
3
- "compilerOptions": {
4
- "target": "ESNext",
5
- "module": "ESNext",
6
- "moduleResolution": "bundler",
7
- "lib": ["ESNext"],
8
- "types": ["@types/bun"],
9
- "baseUrl": ".",
10
- "paths": {
11
- "{{packageName}}-core": ["../../packages/core/src/index.ts"]
12
- },
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "lib": ["ESNext"],
8
+ "types": ["@types/bun"],
9
+ "baseUrl": ".",
10
+ "paths": {
11
+ "{{packageName}}-core": ["../../packages/core/src/index.ts"]
12
+ },
13
13
 
14
- "strict": true,
15
- "noImplicitAny": true,
16
- "strictNullChecks": true,
17
- "noUncheckedIndexedAccess": true,
18
- "exactOptionalPropertyTypes": true,
19
- "noPropertyAccessFromIndexSignature": true,
20
- "noImplicitReturns": true,
21
- "noFallthroughCasesInSwitch": true,
14
+ "strict": true,
15
+ "noImplicitAny": true,
16
+ "strictNullChecks": true,
17
+ "noUncheckedIndexedAccess": true,
18
+ "exactOptionalPropertyTypes": true,
19
+ "noPropertyAccessFromIndexSignature": true,
20
+ "noImplicitReturns": true,
21
+ "noFallthroughCasesInSwitch": true,
22
22
 
23
- "declaration": true,
24
- "declarationMap": true,
25
- "sourceMap": true,
26
- "outDir": "./dist",
23
+ "declaration": true,
24
+ "declarationMap": true,
25
+ "sourceMap": true,
26
+ "outDir": "./dist",
27
27
 
28
- "esModuleInterop": true,
29
- "forceConsistentCasingInFileNames": true,
30
- "isolatedModules": true,
31
- "verbatimModuleSyntax": true,
32
- "skipLibCheck": true,
33
- "resolveJsonModule": true
34
- },
35
- "include": ["src/**/*"],
36
- "exclude": ["node_modules", "dist"]
28
+ "esModuleInterop": true,
29
+ "forceConsistentCasingInFileNames": true,
30
+ "isolatedModules": true,
31
+ "verbatimModuleSyntax": true,
32
+ "skipLibCheck": true,
33
+ "resolveJsonModule": true
34
+ },
35
+ "include": ["src/**/*"],
36
+ "exclude": ["node_modules", "dist"]
37
37
  }
@@ -1,36 +1,37 @@
1
1
  {
2
- "name": "{{packageName}}-mcp",
3
- "version": "{{version}}",
4
- "description": "MCP surface for {{projectName}}",
5
- "type": "module",
6
- "bin": {
7
- "{{projectName}}-mcp": "./dist/server.js"
8
- },
9
- "main": "./dist/index.js",
10
- "types": "./dist/index.d.ts",
11
- "exports": {
12
- ".": {
13
- "types": "./dist/index.d.ts",
14
- "import": "./dist/index.js"
15
- }
16
- },
17
- "scripts": {
18
- "build": "bun build src/server.ts --outdir dist --target bun && bun build src/index.ts --outdir dist --target bun --sourcemap",
19
- "dev": "bun --watch src/server.ts",
20
- "test": "bun test",
21
- "test:watch": "bun test --watch",
22
- "typecheck": "tsc --noEmit",
23
- "lint": "biome check .",
24
- "lint:fix": "biome check . --write",
25
- "format": "biome format --write .",
26
- "clean:artifacts": "rm -rf dist .turbo"
27
- },
28
- "dependencies": {
29
- "{{packageName}}-core": "workspace:*",
30
- "@outfitter/contracts": "workspace:*",
31
- "@outfitter/mcp": "workspace:*",
32
- "zod": "^4.3.5"
33
- },
34
- "devDependencies": {},
35
- "license": "MIT"
2
+ "name": "{{packageName}}-mcp",
3
+ "version": "{{version}}",
4
+ "description": "MCP surface for {{projectName}}",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "{{projectName}}-mcp": "./dist/server.js"
8
+ },
9
+ "type": "module",
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "bun build src/server.ts --outdir dist --target bun && bun build src/index.ts --outdir dist --target bun --sourcemap",
20
+ "dev": "bun --watch src/server.ts",
21
+ "test": "bun test",
22
+ "test:watch": "bun test --watch",
23
+ "typecheck": "tsc --noEmit",
24
+ "lint": "oxlint .",
25
+ "lint:fix": "oxlint --fix .",
26
+ "format": "oxfmt --write .",
27
+ "check": "ultracite check",
28
+ "clean:artifacts": "rm -rf dist .turbo"
29
+ },
30
+ "dependencies": {
31
+ "@outfitter/contracts": "workspace:*",
32
+ "@outfitter/mcp": "workspace:*",
33
+ "zod": "catalog:",
34
+ "{{packageName}}-core": "workspace:*"
35
+ },
36
+ "devDependencies": {}
36
37
  }
@@ -1,18 +1,19 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+
2
3
  import { createContext } from "@outfitter/contracts";
3
4
  import { createGreeting } from "{{packageName}}-core";
4
5
 
5
6
  describe("mcp surface", () => {
6
- test("can use shared core handler", async () => {
7
- const result = await createGreeting(
8
- { name: "MCP" },
9
- createContext({ cwd: process.cwd(), env: process.env })
10
- );
7
+ test("can use shared core handler", async () => {
8
+ const result = await createGreeting(
9
+ { name: "MCP" },
10
+ createContext({ cwd: process.cwd(), env: process.env })
11
+ );
11
12
 
12
- expect(result.isOk()).toBe(true);
13
- if (result.isErr()) {
14
- return;
15
- }
16
- expect(result.value.message).toBe("Hello, MCP.");
17
- });
13
+ expect(result.isOk()).toBe(true);
14
+ if (result.isErr()) {
15
+ return;
16
+ }
17
+ expect(result.value.message).toBe("Hello, MCP.");
18
+ });
18
19
  });
@@ -1,20 +1,20 @@
1
- import { createGreeting } from "{{packageName}}-core";
2
1
  import { createMcpServer, defineTool } from "@outfitter/mcp";
3
2
  import { z } from "zod";
3
+ import { createGreeting } from "{{packageName}}-core";
4
4
 
5
5
  const server = createMcpServer({
6
- name: "{{projectName}}-mcp",
7
- version: "{{version}}",
6
+ name: "{{projectName}}-mcp",
7
+ version: "{{version}}",
8
8
  });
9
9
 
10
10
  const greetTool = defineTool({
11
- name: "greet",
12
- description: "Generate greeting via shared core handler",
13
- inputSchema: z.object({
14
- name: z.string().default("World").describe("Name to greet"),
15
- excited: z.boolean().default(false),
16
- }),
17
- handler: async (input, ctx) => createGreeting(input, ctx),
11
+ name: "greet",
12
+ description: "Generate greeting via shared core handler",
13
+ inputSchema: z.object({
14
+ name: z.string().default("World").describe("Name to greet"),
15
+ excited: z.boolean().default(false),
16
+ }),
17
+ handler: async (input, ctx) => createGreeting(input, ctx),
18
18
  });
19
19
 
20
20
  server.registerTool(greetTool);
@@ -1,10 +1,11 @@
1
1
  import { connectStdio } from "@outfitter/mcp";
2
+
2
3
  import { server } from "./mcp.js";
3
4
 
4
5
  export async function startServer(): Promise<void> {
5
- await connectStdio(server);
6
+ await connectStdio(server);
6
7
  }
7
8
 
8
9
  if (import.meta.main) {
9
- await startServer();
10
+ await startServer();
10
11
  }
@@ -1,37 +1,37 @@
1
1
  {
2
- "$schema": "https://json.schemastore.org/tsconfig",
3
- "compilerOptions": {
4
- "target": "ESNext",
5
- "module": "ESNext",
6
- "moduleResolution": "bundler",
7
- "lib": ["ESNext"],
8
- "types": ["@types/bun"],
9
- "baseUrl": ".",
10
- "paths": {
11
- "{{packageName}}-core": ["../../packages/core/src/index.ts"]
12
- },
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "lib": ["ESNext"],
8
+ "types": ["@types/bun"],
9
+ "baseUrl": ".",
10
+ "paths": {
11
+ "{{packageName}}-core": ["../../packages/core/src/index.ts"]
12
+ },
13
13
 
14
- "strict": true,
15
- "noImplicitAny": true,
16
- "strictNullChecks": true,
17
- "noUncheckedIndexedAccess": true,
18
- "exactOptionalPropertyTypes": true,
19
- "noPropertyAccessFromIndexSignature": true,
20
- "noImplicitReturns": true,
21
- "noFallthroughCasesInSwitch": true,
14
+ "strict": true,
15
+ "noImplicitAny": true,
16
+ "strictNullChecks": true,
17
+ "noUncheckedIndexedAccess": true,
18
+ "exactOptionalPropertyTypes": true,
19
+ "noPropertyAccessFromIndexSignature": true,
20
+ "noImplicitReturns": true,
21
+ "noFallthroughCasesInSwitch": true,
22
22
 
23
- "declaration": true,
24
- "declarationMap": true,
25
- "sourceMap": true,
26
- "outDir": "./dist",
23
+ "declaration": true,
24
+ "declarationMap": true,
25
+ "sourceMap": true,
26
+ "outDir": "./dist",
27
27
 
28
- "esModuleInterop": true,
29
- "forceConsistentCasingInFileNames": true,
30
- "isolatedModules": true,
31
- "verbatimModuleSyntax": true,
32
- "skipLibCheck": true,
33
- "resolveJsonModule": true
34
- },
35
- "include": ["src/**/*"],
36
- "exclude": ["node_modules", "dist"]
28
+ "esModuleInterop": true,
29
+ "forceConsistentCasingInFileNames": true,
30
+ "isolatedModules": true,
31
+ "verbatimModuleSyntax": true,
32
+ "skipLibCheck": true,
33
+ "resolveJsonModule": true
34
+ },
35
+ "include": ["src/**/*"],
36
+ "exclude": ["node_modules", "dist"]
37
37
  }
@@ -1,16 +1,22 @@
1
1
  {
2
- "name": "{{packageName}}",
3
- "private": true,
4
- "version": "{{version}}",
5
- "type": "module",
6
- "workspaces": ["apps/*", "packages/*"],
7
- "scripts": {
8
- "build": "bun run --filter '*' build",
9
- "dev": "bun run --filter '*' dev",
10
- "test": "bun run --filter '*' test",
11
- "typecheck": "bun run --filter '*' typecheck",
12
- "lint": "bun run --filter '*' lint",
13
- "lint:fix": "bun run --filter '*' lint:fix",
14
- "format": "bun run --filter '*' format"
15
- }
2
+ "name": "{{packageName}}",
3
+ "version": "{{version}}",
4
+ "private": true,
5
+ "workspaces": [
6
+ "apps/*",
7
+ "packages/*"
8
+ ],
9
+ "type": "module",
10
+ "scripts": {
11
+ "build": "bun run --filter '*' build",
12
+ "dev": "bun run --filter '*' dev",
13
+ "test": "bun run --filter '*' test",
14
+ "typecheck": "bun run --filter '*' typecheck",
15
+ "lint": "bun run --filter '*' lint",
16
+ "lint:fix": "bun run --filter '*' lint:fix",
17
+ "format": "bun run --filter '*' format",
18
+ "check": "bun run --filter '*' check",
19
+ "clean": "bun run --filter '*' clean:artifacts",
20
+ "verify:ci": "bun run clean && bun run typecheck && bun run check && bun run build && bun run test"
21
+ }
16
22
  }
@@ -1,32 +1,33 @@
1
1
  {
2
- "name": "{{packageName}}-core",
3
- "version": "{{version}}",
4
- "description": "Shared handlers for {{projectName}}",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
12
- }
13
- },
14
- "scripts": {
15
- "build": "bun build src/index.ts --outdir dist --format=esm && tsc --emitDeclarationOnly --outDir dist",
16
- "dev": "bun --watch src/index.ts",
17
- "test": "bun test",
18
- "test:watch": "bun test --watch",
19
- "typecheck": "tsc --noEmit",
20
- "lint": "biome check .",
21
- "lint:fix": "biome check . --write",
22
- "format": "biome format --write .",
23
- "clean:artifacts": "rm -rf dist .turbo"
24
- },
25
- "dependencies": {
26
- "@outfitter/contracts": "workspace:*",
27
- "@outfitter/logging": "workspace:*",
28
- "zod": "^4.3.5"
29
- },
30
- "devDependencies": {},
31
- "license": "MIT"
2
+ "name": "{{packageName}}-core",
3
+ "version": "{{version}}",
4
+ "description": "Shared handlers for {{projectName}}",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "bun build src/index.ts --outdir dist --format=esm && tsc --emitDeclarationOnly --outDir dist",
17
+ "dev": "bun --watch src/index.ts",
18
+ "test": "bun test",
19
+ "test:watch": "bun test --watch",
20
+ "typecheck": "tsc --noEmit",
21
+ "lint": "oxlint .",
22
+ "lint:fix": "oxlint --fix .",
23
+ "format": "oxfmt --write .",
24
+ "check": "ultracite check",
25
+ "clean:artifacts": "rm -rf dist .turbo"
26
+ },
27
+ "dependencies": {
28
+ "@outfitter/contracts": "workspace:*",
29
+ "@outfitter/logging": "workspace:*",
30
+ "zod": "catalog:"
31
+ },
32
+ "devDependencies": {}
32
33
  }
@@ -1,31 +1,36 @@
1
- import { Result, ValidationError, type Handler } from "@outfitter/contracts";
1
+ import {
2
+ Result,
3
+ ValidationError,
4
+ type HandlerContext,
5
+ } from "@outfitter/contracts";
2
6
  import { createLogger } from "@outfitter/logging";
7
+
3
8
  import { greetingInputSchema, type Greeting } from "./types.js";
4
9
 
5
10
  const logger = createLogger({ name: "{{projectName}}-core" });
6
11
 
7
- export const createGreeting: Handler<unknown, Greeting, ValidationError> = async (
8
- input,
9
- ctx
10
- ) => {
11
- const parsed = greetingInputSchema.safeParse(input);
12
- if (!parsed.success) {
13
- return Result.err(
14
- new ValidationError({
15
- message: "Invalid greeting input",
16
- field: "name",
17
- })
18
- );
19
- }
12
+ export async function createGreeting(
13
+ input: unknown,
14
+ ctx: HandlerContext
15
+ ): Promise<Result<Greeting, ValidationError>> {
16
+ const parsed = greetingInputSchema.safeParse(input);
17
+ if (!parsed.success) {
18
+ return Result.err(
19
+ new ValidationError({
20
+ message: "Invalid greeting input",
21
+ field: "name",
22
+ })
23
+ );
24
+ }
20
25
 
21
- const suffix = parsed.data.excited ? "!" : ".";
22
- const greeting: Greeting = {
23
- message: `Hello, ${parsed.data.name}${suffix}`,
24
- issuedAt: new Date().toISOString(),
25
- };
26
+ const suffix = parsed.data.excited ? "!" : ".";
27
+ const greeting: Greeting = {
28
+ message: `Hello, ${parsed.data.name}${suffix}`,
29
+ issuedAt: new Date().toISOString(),
30
+ };
26
31
 
27
- logger.info(`Created greeting for ${parsed.data.name}`, {
28
- requestId: ctx.requestId,
29
- });
30
- return Result.ok(greeting);
31
- };
32
+ logger.info(`Created greeting for ${parsed.data.name}`, {
33
+ requestId: ctx.requestId,
34
+ });
35
+ return Result.ok(greeting);
36
+ }
@@ -1,30 +1,32 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+
2
3
  import { createContext } from "@outfitter/contracts";
4
+
3
5
  import { createGreeting } from "./handlers.js";
4
6
 
5
7
  describe("createGreeting", () => {
6
- test("returns greeting for valid input", async () => {
7
- const result = await createGreeting(
8
- { name: "Outfitter", excited: true },
9
- createContext({ cwd: process.cwd(), env: process.env })
10
- );
8
+ test("returns greeting for valid input", async () => {
9
+ const result = await createGreeting(
10
+ { name: "Outfitter", excited: true },
11
+ createContext({ cwd: process.cwd(), env: process.env })
12
+ );
11
13
 
12
- expect(result.isOk()).toBe(true);
13
- if (result.isErr()) {
14
- return;
15
- }
16
- expect(result.value.message).toBe("Hello, Outfitter!");
17
- });
14
+ expect(result.isOk()).toBe(true);
15
+ if (result.isErr()) {
16
+ return;
17
+ }
18
+ expect(result.value.message).toBe("Hello, Outfitter!");
19
+ });
18
20
 
19
- test("returns validation error for invalid input", async () => {
20
- const result = await createGreeting(
21
- { name: "" },
22
- createContext({ cwd: process.cwd(), env: process.env })
23
- );
21
+ test("returns validation error for invalid input", async () => {
22
+ const result = await createGreeting(
23
+ { name: "" },
24
+ createContext({ cwd: process.cwd(), env: process.env })
25
+ );
24
26
 
25
- expect(result.isErr()).toBe(true);
26
- if (result.isErr()) {
27
- expect(result.error.name).toBe("ValidationError");
28
- }
29
- });
27
+ expect(result.isErr()).toBe(true);
28
+ if (result.isErr()) {
29
+ expect(result.error.name).toBe("ValidationError");
30
+ }
31
+ });
30
32
  });
@@ -1,13 +1,16 @@
1
- import { z } from "zod";
1
+ import { type ZodType, z } from "zod";
2
2
 
3
- export const greetingInputSchema = z.object({
4
- name: z.string().min(1, "name is required"),
5
- excited: z.boolean().default(false),
6
- });
3
+ export interface GreetingInput {
4
+ readonly name: string;
5
+ readonly excited: boolean;
6
+ }
7
7
 
8
- export type GreetingInput = z.infer<typeof greetingInputSchema>;
8
+ export const greetingInputSchema: ZodType<GreetingInput> = z.object({
9
+ name: z.string().min(1, "name is required"),
10
+ excited: z.boolean().default(false),
11
+ });
9
12
 
10
13
  export interface Greeting {
11
- readonly message: string;
12
- readonly issuedAt: string;
14
+ readonly message: string;
15
+ readonly issuedAt: string;
13
16
  }