tempest-express-sdk 0.1.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.
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/chunk-2NB7ZA7G.js +6 -0
- package/dist/chunk-2NB7ZA7G.js.map +1 -0
- package/dist/cli.cjs +546 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +18 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +542 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +7533 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1726 -0
- package/dist/index.d.ts +1726 -0
- package/dist/index.js +7266 -0
- package/dist/index.js.map +1 -0
- package/package.json +90 -0
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/template.ts","../src/version.ts","../src/cli/index.ts"],"names":["join","mkdir","dirname","writeFile","resolve","parseArgs","randomBytes"],"mappings":"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","file":"cli.cjs","sourcesContent":["/**\n * Project-template generator for the `new` command, mirroring `cli.new`.\n *\n * Produces a complete, runnable Express service that follows the SDK's layered\n * architecture (router → controller → service → repository → model) and is\n * pre-wired with `createApp`, native Swagger + Redoc, Zod validation, and a\n * `tempest-db-js` model. {@link projectFiles} returns a `path → contents` map\n * the CLI writes to disk.\n */\n\n/** SDK version the generated project depends on (kept in sync at release). */\nconst SDK_VERSION = \"^0.1.0\";\n\n/**\n * Build the file map for a new service named `name`.\n *\n * @param name - The project (and package) name.\n * @returns A map of relative file path to file contents.\n */\nexport function projectFiles(name: string): Record<string, string> {\n return {\n \"package.json\": `${JSON.stringify(\n {\n name,\n version: \"0.1.0\",\n private: true,\n type: \"module\",\n scripts: {\n dev: \"tsx watch main.ts\",\n start: \"tsx main.ts\",\n build: \"tsc -p tsconfig.json\",\n typecheck: \"tsc --noEmit\",\n },\n dependencies: {\n express: \"^5.1.0\",\n \"tempest-db-js\": \"^0.1.0\",\n \"tempest-express-sdk\": SDK_VERSION,\n zod: \"^3.24.1\",\n },\n devDependencies: {\n \"@types/express\": \"^5.0.0\",\n \"@types/node\": \"^22.10.0\",\n tsx: \"^4.19.2\",\n typescript: \"^5.7.0\",\n },\n },\n null,\n 2,\n )}\\n`,\n\n \"tsconfig.json\": `${JSON.stringify(\n {\n compilerOptions: {\n target: \"ES2022\",\n module: \"ESNext\",\n moduleResolution: \"Bundler\",\n lib: [\"ES2022\"],\n strict: true,\n noUncheckedIndexedAccess: true,\n verbatimModuleSyntax: true,\n esModuleInterop: true,\n skipLibCheck: true,\n forceConsistentCasingInFileNames: true,\n outDir: \"dist\",\n paths: { \"@/*\": [\"./src/*\"] },\n },\n include: [\"src\", \"main.ts\"],\n },\n null,\n 2,\n )}\\n`,\n\n \".gitignore\": \"node_modules\\ndist\\n.env\\n*.log\\n\",\n\n \".env.example\":\n \"HOST=127.0.0.1\\nPORT=8000\\nDEBUG=false\\nDATABASE_URL=sqlite://./app.db\\nCORS_ORIGINS=*\\n\",\n\n \"main.ts\": `import { run } from \"@/index\";\n\nrun();\n`,\n\n \"src/index.ts\": `import { run } from \"@/server\";\n\nexport { run };\n`,\n\n \"src/core/settings.ts\": `import { baseAppSettingsShape, loadSettings, z } from \"tempest-express-sdk\";\n\n/** Application settings — extend the SDK base shape with project fields. */\nexport const settingsSchema = z.object({\n ...baseAppSettingsShape,\n});\n\nexport const settings = loadSettings(settingsSchema);\n`,\n\n \"src/db/models/itemModel.ts\": `import { BaseModel, column, tableNameFor } from \"tempest-express-sdk\";\n\n/** A sample domain model. Replace with your own. */\nexport class ItemModel extends BaseModel {\n static tablename = tableNameFor(\"ItemModel\");\n name = column.text().notNull();\n price = column.integer().notNull();\n}\n`,\n\n \"src/schemas/item.ts\": `import { baseResponseSchema, z } from \"tempest-express-sdk\";\n\n/** Request payload to create an item. */\nexport const itemCreateSchema = z\n .object({\n name: z.string().min(1).openapi({ description: \"The item name.\" }),\n price: z.number().int().min(0).openapi({ description: \"Price in cents.\" }),\n })\n .openapi(\"ItemCreate\");\n\n/** Response payload for an item (base columns + domain fields). */\nexport const itemResponseSchema = baseResponseSchema\n .extend({\n name: z.string(),\n price: z.number().int(),\n })\n .openapi(\"Item\");\n\nexport type ItemCreate = z.infer<typeof itemCreateSchema>;\nexport type ItemResponse = z.infer<typeof itemResponseSchema>;\n`,\n\n \"src/db/repositories/itemRepository.ts\": `import { type AsyncSession, BaseRepository } from \"tempest-express-sdk\";\nimport { ItemModel } from \"@/db/models/itemModel\";\n\n/** Data-access layer for items. */\nexport class ItemRepository extends BaseRepository<typeof ItemModel> {\n constructor(session: AsyncSession) {\n super(ItemModel, session);\n }\n}\n`,\n\n \"src/services/itemService.ts\": `import { BaseService } from \"tempest-express-sdk\";\nimport type { ItemRepository } from \"@/db/repositories/itemRepository\";\nimport type { ItemModel } from \"@/db/models/itemModel\";\nimport type { ItemResponse } from \"@/schemas/item\";\n\n/** Business logic for items. Maps raw rows to the response shape. */\nexport class ItemService extends BaseService<typeof ItemModel, ItemResponse> {\n constructor(repository: ItemRepository) {\n super(repository, (row) => ({\n id: row.id,\n isActive: row.isActive,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n name: row.name,\n price: row.price,\n }));\n }\n}\n`,\n\n \"src/controllers/itemController.ts\": `import { BaseController } from \"tempest-express-sdk\";\nimport type { ItemModel } from \"@/db/models/itemModel\";\nimport type { ItemResponse } from \"@/schemas/item\";\nimport type { ItemService } from \"@/services/itemService\";\n\n/** Orchestration boundary between the router and the item service. */\nexport class ItemController extends BaseController<typeof ItemModel, ItemResponse> {\n constructor(service: ItemService) {\n super(service);\n }\n}\n`,\n\n \"src/api/routers/items.ts\": `import { Router } from \"express\";\nimport type { OpenAPIRegistry } from \"tempest-express-sdk\";\nimport { itemCreateSchema, itemResponseSchema } from \"@/schemas/item\";\n\n/**\n * Build the items router and register its OpenAPI paths.\n *\n * NOTE: wire a real controller (with a DB session) here. This stub returns\n * static data so a freshly generated project boots without a database.\n */\nexport function makeItemsRouter(registry: OpenAPIRegistry): Router {\n const router = Router();\n\n registry.registerPath({\n method: \"get\",\n path: \"/api/items\",\n summary: \"List items\",\n responses: {\n 200: {\n description: \"The items\",\n content: { \"application/json\": { schema: itemResponseSchema.array() } },\n },\n },\n });\n\n registry.registerPath({\n method: \"post\",\n path: \"/api/items\",\n summary: \"Create an item\",\n request: {\n body: { content: { \"application/json\": { schema: itemCreateSchema } } },\n },\n responses: {\n 201: {\n description: \"The created item\",\n content: { \"application/json\": { schema: itemResponseSchema } },\n },\n },\n });\n\n router.get(\"/api/items\", (_req, res) => {\n res.json([]);\n });\n\n router.post(\"/api/items\", (req, res) => {\n const data = itemCreateSchema.parse(req.body);\n res.status(201).json({\n id: \"00000000-0000-0000-0000-000000000000\",\n isActive: true,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n ...data,\n });\n });\n\n return router;\n}\n`,\n\n \"src/api/app.ts\": `import type { Express } from \"express\";\nimport { createApp, createOpenApiRegistry } from \"tempest-express-sdk\";\nimport { settings } from \"@/core/settings\";\nimport { makeItemsRouter } from \"@/api/routers/items\";\n\n/** Build the configured Express application. */\nexport async function makeApp(): Promise<Express> {\n const registry = createOpenApiRegistry();\n\n return createApp({\n corsOrigins: settings.CORS_ORIGINS,\n openapi: {\n registry,\n info: { title: \"${name}\", version: \"0.1.0\", description: \"Powered by tempest-express-sdk.\" },\n },\n configure: (app) => {\n app.use(makeItemsRouter(registry));\n },\n });\n}\n`,\n\n \"src/server.ts\": `import { runServer } from \"tempest-express-sdk\";\nimport { settings } from \"@/core/settings\";\nimport { makeApp } from \"@/api/app\";\n\n/** Build the app and start listening. */\nexport async function run(): Promise<void> {\n const app = await makeApp();\n await runServer(app, { host: settings.HOST, port: settings.PORT });\n}\n`,\n\n \"docker-compose.yml\": dockerComposeFile(name),\n\n \"README.md\": `# ${name}\n\nGenerated with \\`tempest-express new\\` — an Express + Zod + tempest-db-js service.\n\n## Develop\n\n\\`\\`\\`bash\nnpm install\ncp .env.example .env\nnpm run dev\n\\`\\`\\`\n\n- API: http://127.0.0.1:8000/api/items\n- Swagger UI: http://127.0.0.1:8000/docs\n- Redoc: http://127.0.0.1:8000/redoc\n- Health: http://127.0.0.1:8000/health\n\\`\\`\\`\n`,\n };\n}\n\n/** Lowercase the first letter (PascalCase → camelCase). */\nfunction toCamel(name: string): string {\n return name.charAt(0).toLowerCase() + name.slice(1);\n}\n\n/** Convert `CamelCase` to `snake_case`. */\nfunction toSnake(name: string): string {\n return name.replace(/(?<!^)(?=[A-Z])/g, \"_\").toLowerCase();\n}\n\n/**\n * Build a full CRUD resource file map for a PascalCase resource name.\n *\n * Generates model + schema + repository + service + controller + router under\n * `src/`, mirroring the layered slice the `new` template ships with.\n *\n * @param pascal - The resource name in PascalCase (e.g. `Product`).\n * @returns A map of relative file path to file contents.\n */\nexport function resourceFiles(pascal: string): Record<string, string> {\n const camel = toCamel(pascal);\n const table = toSnake(pascal);\n\n return {\n [`src/db/models/${camel}Model.ts`]: `import { BaseModel, column, tableNameFor } from \"tempest-express-sdk\";\n\n/** The ${pascal} domain model. */\nexport class ${pascal}Model extends BaseModel {\n static tablename = tableNameFor(\"${pascal}Model\"); // \"${table}\"\n name = column.text().notNull();\n}\n`,\n\n [`src/schemas/${camel}.ts`]: `import { baseResponseSchema, z } from \"tempest-express-sdk\";\n\n/** Request payload to create a ${pascal}. */\nexport const ${camel}CreateSchema = z\n .object({ name: z.string().min(1) })\n .openapi(\"${pascal}Create\");\n\n/** Response payload for a ${pascal}. */\nexport const ${camel}ResponseSchema = baseResponseSchema\n .extend({ name: z.string() })\n .openapi(\"${pascal}\");\n\nexport type ${pascal}Create = z.infer<typeof ${camel}CreateSchema>;\nexport type ${pascal}Response = z.infer<typeof ${camel}ResponseSchema>;\n`,\n\n [`src/db/repositories/${camel}Repository.ts`]: `import { type AsyncSession, BaseRepository } from \"tempest-express-sdk\";\nimport { ${pascal}Model } from \"@/db/models/${camel}Model\";\n\n/** Data-access layer for ${pascal}. */\nexport class ${pascal}Repository extends BaseRepository<typeof ${pascal}Model> {\n constructor(session: AsyncSession) {\n super(${pascal}Model, session);\n }\n}\n`,\n\n [`src/services/${camel}Service.ts`]: `import { BaseService } from \"tempest-express-sdk\";\nimport type { ${pascal}Model } from \"@/db/models/${camel}Model\";\nimport type { ${pascal}Repository } from \"@/db/repositories/${camel}Repository\";\nimport type { ${pascal}Response } from \"@/schemas/${camel}\";\n\n/** Business logic for ${pascal}. */\nexport class ${pascal}Service extends BaseService<typeof ${pascal}Model, ${pascal}Response> {\n constructor(repository: ${pascal}Repository) {\n super(repository, (row) => ({\n id: row.id,\n isActive: row.isActive,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n name: row.name,\n }));\n }\n}\n`,\n\n [`src/controllers/${camel}Controller.ts`]: `import { BaseController } from \"tempest-express-sdk\";\nimport type { ${pascal}Model } from \"@/db/models/${camel}Model\";\nimport type { ${pascal}Response } from \"@/schemas/${camel}\";\nimport type { ${pascal}Service } from \"@/services/${camel}Service\";\n\n/** Orchestration boundary for ${pascal}. */\nexport class ${pascal}Controller extends BaseController<typeof ${pascal}Model, ${pascal}Response> {\n constructor(service: ${pascal}Service) {\n super(service);\n }\n}\n`,\n\n [`src/api/routers/${camel}s.ts`]: `import { Router } from \"express\";\nimport type { OpenAPIRegistry } from \"tempest-express-sdk\";\nimport { ${camel}CreateSchema, ${camel}ResponseSchema } from \"@/schemas/${camel}\";\n\n/** Build the ${pascal} router and register its OpenAPI paths. */\nexport function make${pascal}sRouter(registry: OpenAPIRegistry): Router {\n const router = Router();\n\n registry.registerPath({\n method: \"get\",\n path: \"/api/${camel}s\",\n summary: \"List ${camel}s\",\n responses: {\n 200: {\n description: \"OK\",\n content: { \"application/json\": { schema: ${camel}ResponseSchema.array() } },\n },\n },\n });\n registry.registerPath({\n method: \"post\",\n path: \"/api/${camel}s\",\n summary: \"Create a ${camel}\",\n request: {\n body: { content: { \"application/json\": { schema: ${camel}CreateSchema } } },\n },\n responses: {\n 201: {\n description: \"Created\",\n content: { \"application/json\": { schema: ${camel}ResponseSchema } },\n },\n },\n });\n\n router.get(\"/api/${camel}s\", (_req, res) => res.json([]));\n router.post(\"/api/${camel}s\", (req, res) => {\n const data = ${camel}CreateSchema.parse(req.body);\n res.status(201).json({\n id: \"00000000-0000-0000-0000-000000000000\",\n isActive: true,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n ...data,\n });\n });\n\n return router;\n}\n`,\n };\n}\n\n/** Build a `docker-compose.yml` with Postgres + Redis for local dev. */\nexport function dockerComposeFile(name: string): string {\n return `services:\n postgres:\n image: postgres:16-alpine\n environment:\n POSTGRES_USER: ${name}\n POSTGRES_PASSWORD: ${name}\n POSTGRES_DB: ${name}\n ports:\n - \"5432:5432\"\n volumes:\n - pgdata:/var/lib/postgresql/data\n redis:\n image: redis:7-alpine\n ports:\n - \"6379:6379\"\n\nvolumes:\n pgdata:\n`;\n}\n","/** The installed SDK version. Single source of truth for the barrel + CLI. */\nexport const VERSION = \"0.1.0\";\n","#!/usr/bin/env node\n/**\n * `tempest-express` command-line interface, mirroring `cli.main`.\n *\n * Commands:\n * - `new <name>` — scaffold a runnable Express service from the SDK template.\n * - `--version` — print the CLI/SDK version.\n * - `--help` — print usage.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { dockerComposeFile, projectFiles, resourceFiles } from \"@/cli/template\";\nimport { VERSION } from \"@/version\";\n\nconst USAGE = `tempest-express — Express SDK CLI\n\nUsage:\n tempest-express new <name> [--dir <path>] Scaffold a new service\n tempest-express generate <Name> [--dir <path>] Scaffold a CRUD resource\n tempest-express secret [--bytes <n>] Print a random secret\n tempest-express docker-compose [--dir <path>] Write a docker-compose.yml\n tempest-express db Migration guidance\n tempest-express --version Print the version\n tempest-express --help Show this help\n`;\n\nconst DB_HELP = `Migrations are handled by tempest-db-js (programmatic API):\n\n import { ... } from \"tempest-db-js/migrations\";\n\nSee https://www.npmjs.com/package/tempest-db-js for the migration workflow.\n`;\n\n/** Write a project file map under `root`, creating directories as needed. */\nasync function writeFiles(root: string, files: Record<string, string>): Promise<void> {\n for (const [relative, contents] of Object.entries(files)) {\n const target = join(root, relative);\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, contents, \"utf8\");\n }\n}\n\n/** Scaffold a new project named `name` into `dir/name`. */\nasync function runNew(name: string, dir: string): Promise<void> {\n if (!/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {\n throw new Error(`Invalid project name: ${JSON.stringify(name)}`);\n }\n const root = resolve(dir, name);\n await writeFiles(root, projectFiles(name));\n process.stdout.write(\n `Created ${name} at ${root}\\n\\nNext steps:\\n cd ${name}\\n npm install\\n cp .env.example .env\\n npm run dev\\n`,\n );\n}\n\n/** Scaffold a CRUD resource into `dir` (PascalCase name required). */\nasync function runGenerate(name: string, dir: string): Promise<void> {\n if (!/^[A-Z][A-Za-z0-9]*$/.test(name)) {\n throw new Error(`Resource name must be PascalCase, got ${JSON.stringify(name)}`);\n }\n const files = resourceFiles(name);\n await writeFiles(resolve(dir), files);\n const written = Object.keys(files)\n .map((path) => ` ${path}`)\n .join(\"\\n\");\n process.stdout.write(`Generated ${name} resource:\\n${written}\\n`);\n}\n\n/**\n * Parse `argv` and dispatch to the matching command.\n *\n * @param argv - Arguments after `node script` (defaults to `process.argv`).\n * @returns The process exit code.\n */\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<number> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n version: { type: \"boolean\", short: \"v\" },\n help: { type: \"boolean\", short: \"h\" },\n dir: { type: \"string\", default: \".\" },\n bytes: { type: \"string\", default: \"32\" },\n },\n });\n\n if (values.version) {\n process.stdout.write(`${VERSION}\\n`);\n return 0;\n }\n\n const [command, ...rest] = positionals;\n\n if (values.help || command === undefined || command === \"help\") {\n process.stdout.write(USAGE);\n return 0;\n }\n\n const dir = values.dir ?? \".\";\n\n if (command === \"new\") {\n const name = rest[0];\n if (!name) {\n process.stderr.write(`error: \\`new\\` requires a project name\\n\\n${USAGE}`);\n return 1;\n }\n await runNew(name, dir);\n return 0;\n }\n\n if (command === \"generate\" || command === \"g\") {\n const name = rest[0];\n if (!name) {\n process.stderr.write(`error: \\`generate\\` requires a resource name\\n\\n${USAGE}`);\n return 1;\n }\n await runGenerate(name, dir);\n return 0;\n }\n\n if (command === \"secret\") {\n const nbytes = Number.parseInt(values.bytes ?? \"32\", 10);\n if (!Number.isInteger(nbytes) || nbytes < 16) {\n process.stderr.write(\"error: --bytes must be an integer >= 16\\n\");\n return 1;\n }\n process.stdout.write(`${randomBytes(nbytes).toString(\"base64url\")}\\n`);\n return 0;\n }\n\n if (command === \"docker-compose\") {\n await writeFiles(resolve(dir), { \"docker-compose.yml\": dockerComposeFile(\"app\") });\n process.stdout.write(`Wrote ${resolve(dir, \"docker-compose.yml\")}\\n`);\n return 0;\n }\n\n if (command === \"db\") {\n process.stdout.write(DB_HELP);\n return 0;\n }\n\n process.stderr.write(`error: unknown command ${JSON.stringify(command)}\\n\\n${USAGE}`);\n return 1;\n}\n\nmain()\n .then((code) => {\n process.exitCode = code;\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`error: ${message}\\n`);\n process.exitCode = 1;\n });\n"]}
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `tempest-express` command-line interface, mirroring `cli.main`.
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* - `new <name>` — scaffold a runnable Express service from the SDK template.
|
|
7
|
+
* - `--version` — print the CLI/SDK version.
|
|
8
|
+
* - `--help` — print usage.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Parse `argv` and dispatch to the matching command.
|
|
12
|
+
*
|
|
13
|
+
* @param argv - Arguments after `node script` (defaults to `process.argv`).
|
|
14
|
+
* @returns The process exit code.
|
|
15
|
+
*/
|
|
16
|
+
declare function main(argv?: string[]): Promise<number>;
|
|
17
|
+
|
|
18
|
+
export { main };
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `tempest-express` command-line interface, mirroring `cli.main`.
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* - `new <name>` — scaffold a runnable Express service from the SDK template.
|
|
7
|
+
* - `--version` — print the CLI/SDK version.
|
|
8
|
+
* - `--help` — print usage.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Parse `argv` and dispatch to the matching command.
|
|
12
|
+
*
|
|
13
|
+
* @param argv - Arguments after `node script` (defaults to `process.argv`).
|
|
14
|
+
* @returns The process exit code.
|
|
15
|
+
*/
|
|
16
|
+
declare function main(argv?: string[]): Promise<number>;
|
|
17
|
+
|
|
18
|
+
export { main };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { VERSION } from './chunk-2NB7ZA7G.js';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
5
|
+
import { resolve, join, dirname } from 'path';
|
|
6
|
+
import { parseArgs } from 'util';
|
|
7
|
+
|
|
8
|
+
// src/cli/template.ts
|
|
9
|
+
var SDK_VERSION = "^0.1.0";
|
|
10
|
+
function projectFiles(name) {
|
|
11
|
+
return {
|
|
12
|
+
"package.json": `${JSON.stringify(
|
|
13
|
+
{
|
|
14
|
+
name,
|
|
15
|
+
version: "0.1.0",
|
|
16
|
+
private: true,
|
|
17
|
+
type: "module",
|
|
18
|
+
scripts: {
|
|
19
|
+
dev: "tsx watch main.ts",
|
|
20
|
+
start: "tsx main.ts",
|
|
21
|
+
build: "tsc -p tsconfig.json",
|
|
22
|
+
typecheck: "tsc --noEmit"
|
|
23
|
+
},
|
|
24
|
+
dependencies: {
|
|
25
|
+
express: "^5.1.0",
|
|
26
|
+
"tempest-db-js": "^0.1.0",
|
|
27
|
+
"tempest-express-sdk": SDK_VERSION,
|
|
28
|
+
zod: "^3.24.1"
|
|
29
|
+
},
|
|
30
|
+
devDependencies: {
|
|
31
|
+
"@types/express": "^5.0.0",
|
|
32
|
+
"@types/node": "^22.10.0",
|
|
33
|
+
tsx: "^4.19.2",
|
|
34
|
+
typescript: "^5.7.0"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
null,
|
|
38
|
+
2
|
|
39
|
+
)}
|
|
40
|
+
`,
|
|
41
|
+
"tsconfig.json": `${JSON.stringify(
|
|
42
|
+
{
|
|
43
|
+
compilerOptions: {
|
|
44
|
+
target: "ES2022",
|
|
45
|
+
module: "ESNext",
|
|
46
|
+
moduleResolution: "Bundler",
|
|
47
|
+
lib: ["ES2022"],
|
|
48
|
+
strict: true,
|
|
49
|
+
noUncheckedIndexedAccess: true,
|
|
50
|
+
verbatimModuleSyntax: true,
|
|
51
|
+
esModuleInterop: true,
|
|
52
|
+
skipLibCheck: true,
|
|
53
|
+
forceConsistentCasingInFileNames: true,
|
|
54
|
+
outDir: "dist",
|
|
55
|
+
paths: { "@/*": ["./src/*"] }
|
|
56
|
+
},
|
|
57
|
+
include: ["src", "main.ts"]
|
|
58
|
+
},
|
|
59
|
+
null,
|
|
60
|
+
2
|
|
61
|
+
)}
|
|
62
|
+
`,
|
|
63
|
+
".gitignore": "node_modules\ndist\n.env\n*.log\n",
|
|
64
|
+
".env.example": "HOST=127.0.0.1\nPORT=8000\nDEBUG=false\nDATABASE_URL=sqlite://./app.db\nCORS_ORIGINS=*\n",
|
|
65
|
+
"main.ts": `import { run } from "@/index";
|
|
66
|
+
|
|
67
|
+
run();
|
|
68
|
+
`,
|
|
69
|
+
"src/index.ts": `import { run } from "@/server";
|
|
70
|
+
|
|
71
|
+
export { run };
|
|
72
|
+
`,
|
|
73
|
+
"src/core/settings.ts": `import { baseAppSettingsShape, loadSettings, z } from "tempest-express-sdk";
|
|
74
|
+
|
|
75
|
+
/** Application settings \u2014 extend the SDK base shape with project fields. */
|
|
76
|
+
export const settingsSchema = z.object({
|
|
77
|
+
...baseAppSettingsShape,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const settings = loadSettings(settingsSchema);
|
|
81
|
+
`,
|
|
82
|
+
"src/db/models/itemModel.ts": `import { BaseModel, column, tableNameFor } from "tempest-express-sdk";
|
|
83
|
+
|
|
84
|
+
/** A sample domain model. Replace with your own. */
|
|
85
|
+
export class ItemModel extends BaseModel {
|
|
86
|
+
static tablename = tableNameFor("ItemModel");
|
|
87
|
+
name = column.text().notNull();
|
|
88
|
+
price = column.integer().notNull();
|
|
89
|
+
}
|
|
90
|
+
`,
|
|
91
|
+
"src/schemas/item.ts": `import { baseResponseSchema, z } from "tempest-express-sdk";
|
|
92
|
+
|
|
93
|
+
/** Request payload to create an item. */
|
|
94
|
+
export const itemCreateSchema = z
|
|
95
|
+
.object({
|
|
96
|
+
name: z.string().min(1).openapi({ description: "The item name." }),
|
|
97
|
+
price: z.number().int().min(0).openapi({ description: "Price in cents." }),
|
|
98
|
+
})
|
|
99
|
+
.openapi("ItemCreate");
|
|
100
|
+
|
|
101
|
+
/** Response payload for an item (base columns + domain fields). */
|
|
102
|
+
export const itemResponseSchema = baseResponseSchema
|
|
103
|
+
.extend({
|
|
104
|
+
name: z.string(),
|
|
105
|
+
price: z.number().int(),
|
|
106
|
+
})
|
|
107
|
+
.openapi("Item");
|
|
108
|
+
|
|
109
|
+
export type ItemCreate = z.infer<typeof itemCreateSchema>;
|
|
110
|
+
export type ItemResponse = z.infer<typeof itemResponseSchema>;
|
|
111
|
+
`,
|
|
112
|
+
"src/db/repositories/itemRepository.ts": `import { type AsyncSession, BaseRepository } from "tempest-express-sdk";
|
|
113
|
+
import { ItemModel } from "@/db/models/itemModel";
|
|
114
|
+
|
|
115
|
+
/** Data-access layer for items. */
|
|
116
|
+
export class ItemRepository extends BaseRepository<typeof ItemModel> {
|
|
117
|
+
constructor(session: AsyncSession) {
|
|
118
|
+
super(ItemModel, session);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
`,
|
|
122
|
+
"src/services/itemService.ts": `import { BaseService } from "tempest-express-sdk";
|
|
123
|
+
import type { ItemRepository } from "@/db/repositories/itemRepository";
|
|
124
|
+
import type { ItemModel } from "@/db/models/itemModel";
|
|
125
|
+
import type { ItemResponse } from "@/schemas/item";
|
|
126
|
+
|
|
127
|
+
/** Business logic for items. Maps raw rows to the response shape. */
|
|
128
|
+
export class ItemService extends BaseService<typeof ItemModel, ItemResponse> {
|
|
129
|
+
constructor(repository: ItemRepository) {
|
|
130
|
+
super(repository, (row) => ({
|
|
131
|
+
id: row.id,
|
|
132
|
+
isActive: row.isActive,
|
|
133
|
+
createdAt: row.createdAt,
|
|
134
|
+
updatedAt: row.updatedAt,
|
|
135
|
+
name: row.name,
|
|
136
|
+
price: row.price,
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
`,
|
|
141
|
+
"src/controllers/itemController.ts": `import { BaseController } from "tempest-express-sdk";
|
|
142
|
+
import type { ItemModel } from "@/db/models/itemModel";
|
|
143
|
+
import type { ItemResponse } from "@/schemas/item";
|
|
144
|
+
import type { ItemService } from "@/services/itemService";
|
|
145
|
+
|
|
146
|
+
/** Orchestration boundary between the router and the item service. */
|
|
147
|
+
export class ItemController extends BaseController<typeof ItemModel, ItemResponse> {
|
|
148
|
+
constructor(service: ItemService) {
|
|
149
|
+
super(service);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
`,
|
|
153
|
+
"src/api/routers/items.ts": `import { Router } from "express";
|
|
154
|
+
import type { OpenAPIRegistry } from "tempest-express-sdk";
|
|
155
|
+
import { itemCreateSchema, itemResponseSchema } from "@/schemas/item";
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Build the items router and register its OpenAPI paths.
|
|
159
|
+
*
|
|
160
|
+
* NOTE: wire a real controller (with a DB session) here. This stub returns
|
|
161
|
+
* static data so a freshly generated project boots without a database.
|
|
162
|
+
*/
|
|
163
|
+
export function makeItemsRouter(registry: OpenAPIRegistry): Router {
|
|
164
|
+
const router = Router();
|
|
165
|
+
|
|
166
|
+
registry.registerPath({
|
|
167
|
+
method: "get",
|
|
168
|
+
path: "/api/items",
|
|
169
|
+
summary: "List items",
|
|
170
|
+
responses: {
|
|
171
|
+
200: {
|
|
172
|
+
description: "The items",
|
|
173
|
+
content: { "application/json": { schema: itemResponseSchema.array() } },
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
registry.registerPath({
|
|
179
|
+
method: "post",
|
|
180
|
+
path: "/api/items",
|
|
181
|
+
summary: "Create an item",
|
|
182
|
+
request: {
|
|
183
|
+
body: { content: { "application/json": { schema: itemCreateSchema } } },
|
|
184
|
+
},
|
|
185
|
+
responses: {
|
|
186
|
+
201: {
|
|
187
|
+
description: "The created item",
|
|
188
|
+
content: { "application/json": { schema: itemResponseSchema } },
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
router.get("/api/items", (_req, res) => {
|
|
194
|
+
res.json([]);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
router.post("/api/items", (req, res) => {
|
|
198
|
+
const data = itemCreateSchema.parse(req.body);
|
|
199
|
+
res.status(201).json({
|
|
200
|
+
id: "00000000-0000-0000-0000-000000000000",
|
|
201
|
+
isActive: true,
|
|
202
|
+
createdAt: new Date().toISOString(),
|
|
203
|
+
updatedAt: new Date().toISOString(),
|
|
204
|
+
...data,
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return router;
|
|
209
|
+
}
|
|
210
|
+
`,
|
|
211
|
+
"src/api/app.ts": `import type { Express } from "express";
|
|
212
|
+
import { createApp, createOpenApiRegistry } from "tempest-express-sdk";
|
|
213
|
+
import { settings } from "@/core/settings";
|
|
214
|
+
import { makeItemsRouter } from "@/api/routers/items";
|
|
215
|
+
|
|
216
|
+
/** Build the configured Express application. */
|
|
217
|
+
export async function makeApp(): Promise<Express> {
|
|
218
|
+
const registry = createOpenApiRegistry();
|
|
219
|
+
|
|
220
|
+
return createApp({
|
|
221
|
+
corsOrigins: settings.CORS_ORIGINS,
|
|
222
|
+
openapi: {
|
|
223
|
+
registry,
|
|
224
|
+
info: { title: "${name}", version: "0.1.0", description: "Powered by tempest-express-sdk." },
|
|
225
|
+
},
|
|
226
|
+
configure: (app) => {
|
|
227
|
+
app.use(makeItemsRouter(registry));
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
`,
|
|
232
|
+
"src/server.ts": `import { runServer } from "tempest-express-sdk";
|
|
233
|
+
import { settings } from "@/core/settings";
|
|
234
|
+
import { makeApp } from "@/api/app";
|
|
235
|
+
|
|
236
|
+
/** Build the app and start listening. */
|
|
237
|
+
export async function run(): Promise<void> {
|
|
238
|
+
const app = await makeApp();
|
|
239
|
+
await runServer(app, { host: settings.HOST, port: settings.PORT });
|
|
240
|
+
}
|
|
241
|
+
`,
|
|
242
|
+
"docker-compose.yml": dockerComposeFile(name),
|
|
243
|
+
"README.md": `# ${name}
|
|
244
|
+
|
|
245
|
+
Generated with \`tempest-express new\` \u2014 an Express + Zod + tempest-db-js service.
|
|
246
|
+
|
|
247
|
+
## Develop
|
|
248
|
+
|
|
249
|
+
\`\`\`bash
|
|
250
|
+
npm install
|
|
251
|
+
cp .env.example .env
|
|
252
|
+
npm run dev
|
|
253
|
+
\`\`\`
|
|
254
|
+
|
|
255
|
+
- API: http://127.0.0.1:8000/api/items
|
|
256
|
+
- Swagger UI: http://127.0.0.1:8000/docs
|
|
257
|
+
- Redoc: http://127.0.0.1:8000/redoc
|
|
258
|
+
- Health: http://127.0.0.1:8000/health
|
|
259
|
+
\`\`\`
|
|
260
|
+
`
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function toCamel(name) {
|
|
264
|
+
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
265
|
+
}
|
|
266
|
+
function toSnake(name) {
|
|
267
|
+
return name.replace(/(?<!^)(?=[A-Z])/g, "_").toLowerCase();
|
|
268
|
+
}
|
|
269
|
+
function resourceFiles(pascal) {
|
|
270
|
+
const camel = toCamel(pascal);
|
|
271
|
+
const table = toSnake(pascal);
|
|
272
|
+
return {
|
|
273
|
+
[`src/db/models/${camel}Model.ts`]: `import { BaseModel, column, tableNameFor } from "tempest-express-sdk";
|
|
274
|
+
|
|
275
|
+
/** The ${pascal} domain model. */
|
|
276
|
+
export class ${pascal}Model extends BaseModel {
|
|
277
|
+
static tablename = tableNameFor("${pascal}Model"); // "${table}"
|
|
278
|
+
name = column.text().notNull();
|
|
279
|
+
}
|
|
280
|
+
`,
|
|
281
|
+
[`src/schemas/${camel}.ts`]: `import { baseResponseSchema, z } from "tempest-express-sdk";
|
|
282
|
+
|
|
283
|
+
/** Request payload to create a ${pascal}. */
|
|
284
|
+
export const ${camel}CreateSchema = z
|
|
285
|
+
.object({ name: z.string().min(1) })
|
|
286
|
+
.openapi("${pascal}Create");
|
|
287
|
+
|
|
288
|
+
/** Response payload for a ${pascal}. */
|
|
289
|
+
export const ${camel}ResponseSchema = baseResponseSchema
|
|
290
|
+
.extend({ name: z.string() })
|
|
291
|
+
.openapi("${pascal}");
|
|
292
|
+
|
|
293
|
+
export type ${pascal}Create = z.infer<typeof ${camel}CreateSchema>;
|
|
294
|
+
export type ${pascal}Response = z.infer<typeof ${camel}ResponseSchema>;
|
|
295
|
+
`,
|
|
296
|
+
[`src/db/repositories/${camel}Repository.ts`]: `import { type AsyncSession, BaseRepository } from "tempest-express-sdk";
|
|
297
|
+
import { ${pascal}Model } from "@/db/models/${camel}Model";
|
|
298
|
+
|
|
299
|
+
/** Data-access layer for ${pascal}. */
|
|
300
|
+
export class ${pascal}Repository extends BaseRepository<typeof ${pascal}Model> {
|
|
301
|
+
constructor(session: AsyncSession) {
|
|
302
|
+
super(${pascal}Model, session);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
`,
|
|
306
|
+
[`src/services/${camel}Service.ts`]: `import { BaseService } from "tempest-express-sdk";
|
|
307
|
+
import type { ${pascal}Model } from "@/db/models/${camel}Model";
|
|
308
|
+
import type { ${pascal}Repository } from "@/db/repositories/${camel}Repository";
|
|
309
|
+
import type { ${pascal}Response } from "@/schemas/${camel}";
|
|
310
|
+
|
|
311
|
+
/** Business logic for ${pascal}. */
|
|
312
|
+
export class ${pascal}Service extends BaseService<typeof ${pascal}Model, ${pascal}Response> {
|
|
313
|
+
constructor(repository: ${pascal}Repository) {
|
|
314
|
+
super(repository, (row) => ({
|
|
315
|
+
id: row.id,
|
|
316
|
+
isActive: row.isActive,
|
|
317
|
+
createdAt: row.createdAt,
|
|
318
|
+
updatedAt: row.updatedAt,
|
|
319
|
+
name: row.name,
|
|
320
|
+
}));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
`,
|
|
324
|
+
[`src/controllers/${camel}Controller.ts`]: `import { BaseController } from "tempest-express-sdk";
|
|
325
|
+
import type { ${pascal}Model } from "@/db/models/${camel}Model";
|
|
326
|
+
import type { ${pascal}Response } from "@/schemas/${camel}";
|
|
327
|
+
import type { ${pascal}Service } from "@/services/${camel}Service";
|
|
328
|
+
|
|
329
|
+
/** Orchestration boundary for ${pascal}. */
|
|
330
|
+
export class ${pascal}Controller extends BaseController<typeof ${pascal}Model, ${pascal}Response> {
|
|
331
|
+
constructor(service: ${pascal}Service) {
|
|
332
|
+
super(service);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
`,
|
|
336
|
+
[`src/api/routers/${camel}s.ts`]: `import { Router } from "express";
|
|
337
|
+
import type { OpenAPIRegistry } from "tempest-express-sdk";
|
|
338
|
+
import { ${camel}CreateSchema, ${camel}ResponseSchema } from "@/schemas/${camel}";
|
|
339
|
+
|
|
340
|
+
/** Build the ${pascal} router and register its OpenAPI paths. */
|
|
341
|
+
export function make${pascal}sRouter(registry: OpenAPIRegistry): Router {
|
|
342
|
+
const router = Router();
|
|
343
|
+
|
|
344
|
+
registry.registerPath({
|
|
345
|
+
method: "get",
|
|
346
|
+
path: "/api/${camel}s",
|
|
347
|
+
summary: "List ${camel}s",
|
|
348
|
+
responses: {
|
|
349
|
+
200: {
|
|
350
|
+
description: "OK",
|
|
351
|
+
content: { "application/json": { schema: ${camel}ResponseSchema.array() } },
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
registry.registerPath({
|
|
356
|
+
method: "post",
|
|
357
|
+
path: "/api/${camel}s",
|
|
358
|
+
summary: "Create a ${camel}",
|
|
359
|
+
request: {
|
|
360
|
+
body: { content: { "application/json": { schema: ${camel}CreateSchema } } },
|
|
361
|
+
},
|
|
362
|
+
responses: {
|
|
363
|
+
201: {
|
|
364
|
+
description: "Created",
|
|
365
|
+
content: { "application/json": { schema: ${camel}ResponseSchema } },
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
router.get("/api/${camel}s", (_req, res) => res.json([]));
|
|
371
|
+
router.post("/api/${camel}s", (req, res) => {
|
|
372
|
+
const data = ${camel}CreateSchema.parse(req.body);
|
|
373
|
+
res.status(201).json({
|
|
374
|
+
id: "00000000-0000-0000-0000-000000000000",
|
|
375
|
+
isActive: true,
|
|
376
|
+
createdAt: new Date().toISOString(),
|
|
377
|
+
updatedAt: new Date().toISOString(),
|
|
378
|
+
...data,
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
return router;
|
|
383
|
+
}
|
|
384
|
+
`
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function dockerComposeFile(name) {
|
|
388
|
+
return `services:
|
|
389
|
+
postgres:
|
|
390
|
+
image: postgres:16-alpine
|
|
391
|
+
environment:
|
|
392
|
+
POSTGRES_USER: ${name}
|
|
393
|
+
POSTGRES_PASSWORD: ${name}
|
|
394
|
+
POSTGRES_DB: ${name}
|
|
395
|
+
ports:
|
|
396
|
+
- "5432:5432"
|
|
397
|
+
volumes:
|
|
398
|
+
- pgdata:/var/lib/postgresql/data
|
|
399
|
+
redis:
|
|
400
|
+
image: redis:7-alpine
|
|
401
|
+
ports:
|
|
402
|
+
- "6379:6379"
|
|
403
|
+
|
|
404
|
+
volumes:
|
|
405
|
+
pgdata:
|
|
406
|
+
`;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// src/cli/index.ts
|
|
410
|
+
var USAGE = `tempest-express \u2014 Express SDK CLI
|
|
411
|
+
|
|
412
|
+
Usage:
|
|
413
|
+
tempest-express new <name> [--dir <path>] Scaffold a new service
|
|
414
|
+
tempest-express generate <Name> [--dir <path>] Scaffold a CRUD resource
|
|
415
|
+
tempest-express secret [--bytes <n>] Print a random secret
|
|
416
|
+
tempest-express docker-compose [--dir <path>] Write a docker-compose.yml
|
|
417
|
+
tempest-express db Migration guidance
|
|
418
|
+
tempest-express --version Print the version
|
|
419
|
+
tempest-express --help Show this help
|
|
420
|
+
`;
|
|
421
|
+
var DB_HELP = `Migrations are handled by tempest-db-js (programmatic API):
|
|
422
|
+
|
|
423
|
+
import { ... } from "tempest-db-js/migrations";
|
|
424
|
+
|
|
425
|
+
See https://www.npmjs.com/package/tempest-db-js for the migration workflow.
|
|
426
|
+
`;
|
|
427
|
+
async function writeFiles(root, files) {
|
|
428
|
+
for (const [relative, contents] of Object.entries(files)) {
|
|
429
|
+
const target = join(root, relative);
|
|
430
|
+
await mkdir(dirname(target), { recursive: true });
|
|
431
|
+
await writeFile(target, contents, "utf8");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async function runNew(name, dir) {
|
|
435
|
+
if (!/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {
|
|
436
|
+
throw new Error(`Invalid project name: ${JSON.stringify(name)}`);
|
|
437
|
+
}
|
|
438
|
+
const root = resolve(dir, name);
|
|
439
|
+
await writeFiles(root, projectFiles(name));
|
|
440
|
+
process.stdout.write(
|
|
441
|
+
`Created ${name} at ${root}
|
|
442
|
+
|
|
443
|
+
Next steps:
|
|
444
|
+
cd ${name}
|
|
445
|
+
npm install
|
|
446
|
+
cp .env.example .env
|
|
447
|
+
npm run dev
|
|
448
|
+
`
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
async function runGenerate(name, dir) {
|
|
452
|
+
if (!/^[A-Z][A-Za-z0-9]*$/.test(name)) {
|
|
453
|
+
throw new Error(`Resource name must be PascalCase, got ${JSON.stringify(name)}`);
|
|
454
|
+
}
|
|
455
|
+
const files = resourceFiles(name);
|
|
456
|
+
await writeFiles(resolve(dir), files);
|
|
457
|
+
const written = Object.keys(files).map((path) => ` ${path}`).join("\n");
|
|
458
|
+
process.stdout.write(`Generated ${name} resource:
|
|
459
|
+
${written}
|
|
460
|
+
`);
|
|
461
|
+
}
|
|
462
|
+
async function main(argv = process.argv.slice(2)) {
|
|
463
|
+
const { values, positionals } = parseArgs({
|
|
464
|
+
args: argv,
|
|
465
|
+
allowPositionals: true,
|
|
466
|
+
options: {
|
|
467
|
+
version: { type: "boolean", short: "v" },
|
|
468
|
+
help: { type: "boolean", short: "h" },
|
|
469
|
+
dir: { type: "string", default: "." },
|
|
470
|
+
bytes: { type: "string", default: "32" }
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
if (values.version) {
|
|
474
|
+
process.stdout.write(`${VERSION}
|
|
475
|
+
`);
|
|
476
|
+
return 0;
|
|
477
|
+
}
|
|
478
|
+
const [command, ...rest] = positionals;
|
|
479
|
+
if (values.help || command === void 0 || command === "help") {
|
|
480
|
+
process.stdout.write(USAGE);
|
|
481
|
+
return 0;
|
|
482
|
+
}
|
|
483
|
+
const dir = values.dir ?? ".";
|
|
484
|
+
if (command === "new") {
|
|
485
|
+
const name = rest[0];
|
|
486
|
+
if (!name) {
|
|
487
|
+
process.stderr.write(`error: \`new\` requires a project name
|
|
488
|
+
|
|
489
|
+
${USAGE}`);
|
|
490
|
+
return 1;
|
|
491
|
+
}
|
|
492
|
+
await runNew(name, dir);
|
|
493
|
+
return 0;
|
|
494
|
+
}
|
|
495
|
+
if (command === "generate" || command === "g") {
|
|
496
|
+
const name = rest[0];
|
|
497
|
+
if (!name) {
|
|
498
|
+
process.stderr.write(`error: \`generate\` requires a resource name
|
|
499
|
+
|
|
500
|
+
${USAGE}`);
|
|
501
|
+
return 1;
|
|
502
|
+
}
|
|
503
|
+
await runGenerate(name, dir);
|
|
504
|
+
return 0;
|
|
505
|
+
}
|
|
506
|
+
if (command === "secret") {
|
|
507
|
+
const nbytes = Number.parseInt(values.bytes ?? "32", 10);
|
|
508
|
+
if (!Number.isInteger(nbytes) || nbytes < 16) {
|
|
509
|
+
process.stderr.write("error: --bytes must be an integer >= 16\n");
|
|
510
|
+
return 1;
|
|
511
|
+
}
|
|
512
|
+
process.stdout.write(`${randomBytes(nbytes).toString("base64url")}
|
|
513
|
+
`);
|
|
514
|
+
return 0;
|
|
515
|
+
}
|
|
516
|
+
if (command === "docker-compose") {
|
|
517
|
+
await writeFiles(resolve(dir), { "docker-compose.yml": dockerComposeFile("app") });
|
|
518
|
+
process.stdout.write(`Wrote ${resolve(dir, "docker-compose.yml")}
|
|
519
|
+
`);
|
|
520
|
+
return 0;
|
|
521
|
+
}
|
|
522
|
+
if (command === "db") {
|
|
523
|
+
process.stdout.write(DB_HELP);
|
|
524
|
+
return 0;
|
|
525
|
+
}
|
|
526
|
+
process.stderr.write(`error: unknown command ${JSON.stringify(command)}
|
|
527
|
+
|
|
528
|
+
${USAGE}`);
|
|
529
|
+
return 1;
|
|
530
|
+
}
|
|
531
|
+
main().then((code) => {
|
|
532
|
+
process.exitCode = code;
|
|
533
|
+
}).catch((error) => {
|
|
534
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
535
|
+
process.stderr.write(`error: ${message}
|
|
536
|
+
`);
|
|
537
|
+
process.exitCode = 1;
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
export { main };
|
|
541
|
+
//# sourceMappingURL=cli.js.map
|
|
542
|
+
//# sourceMappingURL=cli.js.map
|