tempest-express-sdk 0.20.1 → 0.22.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/README.md +11 -4
- package/dist/{chunk-XGKAZZR2.js → chunk-7Q5NBMSP.js} +36 -6
- package/dist/chunk-7Q5NBMSP.js.map +1 -0
- package/dist/cli.cjs +37 -7
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +64 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +171 -409
- package/dist/index.d.ts +171 -409
- package/dist/index.js +32 -55
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
- package/dist/chunk-XGKAZZR2.js.map +0 -1
package/README.md
CHANGED
|
@@ -16,10 +16,17 @@ router → controller → service → repository → model stack built on
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
npm install tempest-express-sdk tempest-db-js express zod
|
|
19
|
+
npm install tempest-express-sdk tempest-db-js express zod@^4
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
`tempest-db-js`
|
|
22
|
+
`tempest-db-js` and `zod@^4` are required peer dependencies. `zod` is a **peer**
|
|
23
|
+
so the SDK and your project share **one** instance — that is what makes
|
|
24
|
+
`.openapi()` and `instanceof ZodType` work across the package boundary. Check it
|
|
25
|
+
with `npm ls zod`: a single line, marked `deduped`.
|
|
26
|
+
|
|
27
|
+
> ⚠️ **v0.21.0 requires zod 4.** Coming from 0.20.x on zod 3? See the
|
|
28
|
+
> [zod 4 migration guide](https://mauriciobenjamin700.github.io/tempest-express-sdk/migration/zod-4/)
|
|
29
|
+
> ([EN](https://mauriciobenjamin700.github.io/tempest-express-sdk/en/migration/zod-4/)).
|
|
23
30
|
|
|
24
31
|
## What's inside
|
|
25
32
|
|
|
@@ -27,7 +34,7 @@ npm install tempest-express-sdk tempest-db-js express zod
|
|
|
27
34
|
| --- | --- |
|
|
28
35
|
| **core** | `JSONLogger`, `configureLogging`, request-id context (`getRequestId`, `runWithRequestContext`), `defineEnum` |
|
|
29
36
|
| **exceptions** | `AppException` + `ConflictException` / `NotFoundException` / `UnauthorizedException` / `ForbiddenException` / `ValidationException` / `TooManyRequestsException` / `InvalidTokenException` / `ExpiredTokenException`, `MessageCatalog` (i18n) |
|
|
30
|
-
| **schemas** | `z` (OpenAPI-augmented), `baseResponseSchema`, `toDict`, `paginationFilterSchema` / `paginationSchema`, cursor + delta-sync pagination, `buildPaginationLinkHeader`, validated field types (`centsField`/`priceField`/`slugField`/…), `logEntrySchema` |
|
|
37
|
+
| **schemas** | `z` (OpenAPI-augmented), `baseResponseSchema`, `toDict`, `paginationFilterSchema` / `paginationSchema`, cursor + delta-sync pagination, `buildPaginationLinkHeader`, validated field types (`centsField`/`priceField`/`slugField`/…), `looseBoolean` (textual booleans from query strings and env), `logEntrySchema` |
|
|
31
38
|
| **settings** | `loadSettings`, `baseAppSettingsShape` (server / database / CORS) + domain fragments (`jwtSettingsShape`, `authSettingsShape`, `emailSettingsShape`, `redisSettingsShape`, `sessionSettingsShape`, `uploadSettingsShape`, `minioSettingsShape`, …), `envBoolean` / `envList` |
|
|
32
39
|
| **db** | re-exports `tempest-db-js` + `BaseModel`, `tableNameFor`, soft-delete / audit column helpers; `TenantScopedRepository`, `BaseOutboxModel` + `OutboxRelay`, `BaseAuditLogModel` + `snapshot`/`diffSnapshots`, `BaseUserModel` / `BaseUserTokenModel` / `BaseUserRefreshTokenModel`, `wrapWithSlowQueryLog`, `backupDatabase` |
|
|
33
40
|
| **services / controllers** | `BaseService`, `BaseController` over a typed repository |
|
|
@@ -51,7 +58,7 @@ npm install tempest-express-sdk tempest-db-js express zod
|
|
|
51
58
|
import { createApp, createOpenApiRegistry, runServer, z } from "tempest-express-sdk";
|
|
52
59
|
|
|
53
60
|
const registry = createOpenApiRegistry();
|
|
54
|
-
const Item = registry.register("Item", z.object({ id: z.
|
|
61
|
+
const Item = registry.register("Item", z.object({ id: z.uuid(), name: z.string() }));
|
|
55
62
|
|
|
56
63
|
const app = await createApp({
|
|
57
64
|
corsOrigins: "*",
|
|
@@ -15,17 +15,47 @@ function toDict(data, options = {}) {
|
|
|
15
15
|
return { ...out, ...options.include ?? {} };
|
|
16
16
|
}
|
|
17
17
|
var baseResponseSchema = z.object({
|
|
18
|
-
id: z.
|
|
18
|
+
id: z.uuid().openapi({ description: "The unique identifier of the record." }),
|
|
19
19
|
isActive: z.boolean().openapi({ description: "Whether the record is active (soft-delete flag)." }),
|
|
20
20
|
createdAt: z.coerce.date().openapi({ description: "The creation timestamp of the record (UTC)." }),
|
|
21
21
|
updatedAt: z.coerce.date().openapi({ description: "The last update timestamp of the record (UTC)." })
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
// src/schemas/fields.ts
|
|
25
|
+
var positiveIntField = z.number().int().positive();
|
|
26
|
+
var nonNegativeIntField = z.number().int().min(0);
|
|
27
|
+
var centsField = z.number().int().min(0);
|
|
28
|
+
var portField = z.number().int().min(1).max(65535);
|
|
29
|
+
var ratingField = z.number().int().min(0).max(5);
|
|
30
|
+
var positiveFloatField = z.number().positive();
|
|
31
|
+
var nonNegativeFloatField = z.number().min(0);
|
|
32
|
+
var percentField = z.number().min(0).max(100);
|
|
33
|
+
var ratioField = z.number().min(0).max(1);
|
|
34
|
+
var latitudeField = z.number().min(-90).max(90);
|
|
35
|
+
var longitudeField = z.number().min(-180).max(180);
|
|
36
|
+
var nonEmptyStrField = z.string().transform((value) => value.trim()).pipe(z.string().min(1));
|
|
37
|
+
var slugField = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {
|
|
38
|
+
message: "Must be a lowercase hyphen-separated slug."
|
|
39
|
+
});
|
|
40
|
+
var hexColorField = z.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, {
|
|
41
|
+
message: "Must be a #rgb or #rrggbb hex color."
|
|
42
|
+
});
|
|
43
|
+
var priceField = z.string().regex(/^\d+(?:\.\d{1,2})?$/, {
|
|
44
|
+
message: 'Must be a decimal money string, e.g. "19.90".'
|
|
45
|
+
});
|
|
46
|
+
var LOOSE_BOOLEAN_ERROR = 'Must be a boolean value such as "true", "false", "1", "0", "yes" or "no".';
|
|
47
|
+
function looseBoolean(defaultValue) {
|
|
48
|
+
return z.preprocess(
|
|
49
|
+
(value) => typeof value === "string" ? value.trim() || void 0 : value,
|
|
50
|
+
z.union([z.boolean(), z.stringbool()], { error: LOOSE_BOOLEAN_ERROR }).default(defaultValue)
|
|
51
|
+
).openapi({ type: "boolean", default: defaultValue });
|
|
52
|
+
}
|
|
53
|
+
|
|
24
54
|
// src/settings/base.ts
|
|
25
55
|
var serverSettingsShape = {
|
|
26
56
|
HOST: z.string().default("127.0.0.1"),
|
|
27
57
|
PORT: z.coerce.number().int().min(0).max(65535).default(8e3),
|
|
28
|
-
DEBUG:
|
|
58
|
+
DEBUG: looseBoolean(false)
|
|
29
59
|
};
|
|
30
60
|
var databaseSettingsShape = {
|
|
31
61
|
DATABASE_URL: z.string().default("sqlite://./app.db")
|
|
@@ -99,8 +129,8 @@ var PasswordUtils = class {
|
|
|
99
129
|
};
|
|
100
130
|
|
|
101
131
|
// src/version.ts
|
|
102
|
-
var VERSION = "0.
|
|
132
|
+
var VERSION = "0.22.0";
|
|
103
133
|
|
|
104
|
-
export { PasswordUtils, VERSION, baseAppSettingsSchema, baseAppSettingsShape, baseResponseSchema, corsSettingsShape, databaseSettingsShape, loadSettings, serverSettingsShape, toDict };
|
|
105
|
-
//# sourceMappingURL=chunk-
|
|
106
|
-
//# sourceMappingURL=chunk-
|
|
134
|
+
export { PasswordUtils, VERSION, baseAppSettingsSchema, baseAppSettingsShape, baseResponseSchema, centsField, corsSettingsShape, databaseSettingsShape, hexColorField, latitudeField, loadSettings, longitudeField, looseBoolean, nonEmptyStrField, nonNegativeFloatField, nonNegativeIntField, percentField, portField, positiveFloatField, positiveIntField, priceField, ratingField, ratioField, serverSettingsShape, slugField, toDict };
|
|
135
|
+
//# sourceMappingURL=chunk-7Q5NBMSP.js.map
|
|
136
|
+
//# sourceMappingURL=chunk-7Q5NBMSP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schemas/base.ts","../src/schemas/fields.ts","../src/settings/base.ts","../src/utils/password.ts","../src/version.ts"],"names":[],"mappings":";;;;;AAcA,oBAAA,CAAqB,CAAC,CAAA;AAoBf,SAAS,MAAA,CACd,IAAA,EACA,OAAA,GAAyB,EAAC,EACD;AACzB,EAAA,MAAM,UAAU,IAAI,GAAA,CAAI,OAAA,CAAQ,OAAA,IAAW,EAAE,CAAA;AAC7C,EAAA,MAAM,MAA+B,EAAC;AACtC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA,EAAG;AAC/C,IAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,EAAG;AACtB,IAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAW;AAC3C,IAAA,GAAA,CAAI,GAAG,CAAA,GAAI,KAAA;AAAA,EACb;AACA,EAAA,OAAO,EAAE,GAAG,GAAA,EAAK,GAAI,OAAA,CAAQ,OAAA,IAAW,EAAC,EAAG;AAC9C;AAOO,IAAM,kBAAA,GAAqB,EAAE,MAAA,CAAO;AAAA,EACzC,EAAA,EAAI,EAAE,IAAA,EAAK,CAAE,QAAQ,EAAE,WAAA,EAAa,wCAAwC,CAAA;AAAA,EAC5E,QAAA,EAAU,EACP,OAAA,EAAQ,CACR,QAAQ,EAAE,WAAA,EAAa,oDAAoD,CAAA;AAAA,EAC9E,SAAA,EAAW,EAAE,MAAA,CACV,IAAA,GACA,OAAA,CAAQ,EAAE,WAAA,EAAa,6CAAA,EAA+C,CAAA;AAAA,EACzE,SAAA,EAAW,EAAE,MAAA,CACV,IAAA,GACA,OAAA,CAAQ,EAAE,WAAA,EAAa,gDAAA,EAAkD;AAC9E,CAAC;;;ACnDM,IAAM,mBAAmB,CAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,QAAA;AAE1C,IAAM,sBAAsB,CAAA,CAAE,MAAA,GAAS,GAAA,EAAI,CAAE,IAAI,CAAC;AAElD,IAAM,aAAa,CAAA,CAAE,MAAA,GAAS,GAAA,EAAI,CAAE,IAAI,CAAC;AAEzC,IAAM,SAAA,GAAY,CAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,GAAA,CAAI,CAAC,CAAA,CAAE,GAAA,CAAI,KAAK;AAEnD,IAAM,WAAA,GAAc,CAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,GAAA,CAAI,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC;AAKjD,IAAM,kBAAA,GAAqB,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AAEtC,IAAM,qBAAA,GAAwB,CAAA,CAAE,MAAA,EAAO,CAAE,IAAI,CAAC;AAE9C,IAAM,YAAA,GAAe,EAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA,CAAE,IAAI,GAAG;AAE9C,IAAM,UAAA,GAAa,EAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA,CAAE,IAAI,CAAC;AAE1C,IAAM,aAAA,GAAgB,EAAE,MAAA,EAAO,CAAE,IAAI,GAAG,CAAA,CAAE,IAAI,EAAE;AAEhD,IAAM,cAAA,GAAiB,EAAE,MAAA,EAAO,CAAE,IAAI,IAAI,CAAA,CAAE,IAAI,GAAG;AAKnD,IAAM,mBAAmB,CAAA,CAC7B,MAAA,EAAO,CACP,SAAA,CAAU,CAAC,KAAA,KAAU,KAAA,CAAM,IAAA,EAAM,EACjC,IAAA,CAAK,CAAA,CAAE,QAAO,CAAE,GAAA,CAAI,CAAC,CAAC;AAGlB,IAAM,SAAA,GAAY,CAAA,CAAE,MAAA,EAAO,CAAE,MAAM,4BAAA,EAA8B;AAAA,EACtE,OAAA,EAAS;AACX,CAAC;AAGM,IAAM,aAAA,GAAgB,CAAA,CAAE,MAAA,EAAO,CAAE,MAAM,sCAAA,EAAwC;AAAA,EACpF,OAAA,EAAS;AACX,CAAC;AAOM,IAAM,UAAA,GAAa,CAAA,CACvB,MAAA,EAAO,CACP,MAAM,qBAAA,EAAuB;AAAA,EAC5B,OAAA,EAAS;AACX,CAAC;AAKH,IAAM,mBAAA,GACJ,2EAAA;AA0BK,SAAS,aAAa,YAAA,EAAuB;AAClD,EAAA,OAAO,CAAA,CACJ,UAAA;AAAA,IACC,CAAC,UAAW,OAAO,KAAA,KAAU,WAAW,KAAA,CAAM,IAAA,MAAU,MAAA,GAAY,KAAA;AAAA,IACpE,EACG,KAAA,CAAM,CAAC,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,UAAA,EAAY,CAAA,EAAG,EAAE,KAAA,EAAO,mBAAA,EAAqB,CAAA,CACnE,QAAQ,YAAY;AAAA,IAExB,OAAA,CAAQ,EAAE,MAAM,SAAA,EAAW,OAAA,EAAS,cAAc,CAAA;AACvD;;;AC5FO,IAAM,mBAAA,GAAsB;AAAA,EACjC,IAAA,EAAM,CAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,WAAW,CAAA;AAAA,EACpC,IAAA,EAAM,CAAA,CAAE,MAAA,CAAO,MAAA,GAAS,GAAA,EAAI,CAAE,GAAA,CAAI,CAAC,CAAA,CAAE,GAAA,CAAI,KAAK,CAAA,CAAE,QAAQ,GAAI,CAAA;AAAA,EAC5D,KAAA,EAAO,aAAa,KAAK;AAC3B;AAGO,IAAM,qBAAA,GAAwB;AAAA,EACnC,YAAA,EAAc,CAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,mBAAmB;AACtD;AAGO,IAAM,iBAAA,GAAoB;AAAA,EAC/B,cAAc,CAAA,CACX,MAAA,EAAO,CACP,OAAA,CAAQ,GAAG,CAAA,CACX,SAAA;AAAA,IAAU,CAAC,KAAA,KACV,KAAA,CACG,MAAM,GAAG,CAAA,CACT,IAAI,CAAC,MAAA,KAAW,MAAA,CAAO,IAAA,EAAM,CAAA,CAC7B,MAAA,CAAO,CAAC,MAAA,KAAW,MAAA,CAAO,SAAS,CAAC;AAAA;AAE7C;AAGO,IAAM,oBAAA,GAAuB;AAAA,EAClC,GAAG,mBAAA;AAAA,EACH,GAAG,qBAAA;AAAA,EACH,GAAG;AACL;AAGO,IAAM,qBAAA,GAAwB,CAAA,CAAE,MAAA,CAAO,oBAAoB;AAqB3D,SAAS,YAAA,CACd,MAAA,EACA,GAAA,GAAyB,OAAA,CAAQ,GAAA,EACX;AACtB,EAAA,OAAO,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,GAAG,CAAC,CAAA;AACxC;;;AC7DA,IAAI,MAAA,GAA8B,IAAA;AAGlC,eAAe,UAAA,GAAoC;AACjD,EAAA,IAAI,QAAQ,OAAO,MAAA;AACnB,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAO,MAAM,OAAO,UAAU,CAAA;AACpC,IAAA,MAAA,GAAS,IAAI,OAAA,IAAW,GAAA;AAAA,EAC1B,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,uFAAA;AAAA,MACA,EAAE,KAAA;AAAM,KACV;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAGO,IAAM,gBAAN,MAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKzB,WAAA,CAA6B,SAAiB,EAAA,EAAI;AAArB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAsB;AAAA,EAAtB,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7B,MAAM,KAAK,KAAA,EAAgC;AACzC,IAAA,MAAM,MAAA,GAAS,MAAM,UAAA,EAAW;AAChC,IAAA,MAAM,IAAA,GAAO,MAAM,MAAA,CAAO,OAAA,CAAQ,KAAK,MAAM,CAAA;AAC7C,IAAA,OAAO,MAAA,CAAO,IAAA,CAAK,KAAA,EAAO,IAAI,CAAA;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,MAAA,CAAO,KAAA,EAAe,MAAA,EAAkC;AAC5D,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,UAAA,EAAW;AAChC,MAAA,OAAO,MAAM,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,MAAM,CAAA;AAAA,IAC3C,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACF;;;AC/DO,IAAM,OAAA,GAAU","file":"chunk-7Q5NBMSP.js","sourcesContent":["/**\n * Zod foundation shared by every DTO, mirroring `schemas.base.BaseSchema`.\n *\n * Pydantic uses class inheritance for shared config; Zod composes instead. This\n * module re-exports a `z` already augmented with `.openapi()` (so every schema\n * can carry OpenAPI metadata) and a {@link toDict} helper matching\n * `BaseSchema.to_dict` (drop nullish, exclude keys, merge extras).\n */\n\nimport { extendZodWithOpenApi } from \"@asteasolutions/zod-to-openapi\";\nimport { z } from \"zod\";\n\n// Augment the shared `z` instance with `.openapi(...)`. Idempotent — calling it\n// more than once across modules is safe.\nextendZodWithOpenApi(z);\n\nexport { z };\n\n/** Options for {@link toDict}. */\nexport interface ToDictOptions {\n /** Field names to drop from the output. */\n exclude?: string[];\n /** Extra entries merged on top (override existing keys). */\n include?: Record<string, unknown>;\n}\n\n/**\n * Serialize a validated object to a plain record, dropping `null`/`undefined`,\n * removing `exclude`d keys and merging `include` on top.\n *\n * @param data - The source object (typically a parsed schema).\n * @param options - Keys to exclude and entries to merge in.\n * @returns The cleaned record.\n */\nexport function toDict(\n data: Record<string, unknown>,\n options: ToDictOptions = {},\n): Record<string, unknown> {\n const exclude = new Set(options.exclude ?? []);\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (exclude.has(key)) continue;\n if (value === null || value === undefined) continue;\n out[key] = value;\n }\n return { ...out, ...(options.include ?? {}) };\n}\n\n/**\n * Response schema fields every ORM record carries (`id`, `isActive`,\n * `createdAt`, `updatedAt`). Mirrors `BaseResponseSchema`. Extend it with\n * `baseResponseSchema.extend({ ... })` to build concrete `*ResponseSchema`s.\n */\nexport const baseResponseSchema = z.object({\n id: z.uuid().openapi({ description: \"The unique identifier of the record.\" }),\n isActive: z\n .boolean()\n .openapi({ description: \"Whether the record is active (soft-delete flag).\" }),\n createdAt: z.coerce\n .date()\n .openapi({ description: \"The creation timestamp of the record (UTC).\" }),\n updatedAt: z.coerce\n .date()\n .openapi({ description: \"The last update timestamp of the record (UTC).\" }),\n});\n\n/** The inferred TS type of a {@link baseResponseSchema} payload. */\nexport type BaseResponse = z.infer<typeof baseResponseSchema>;\n","/**\n * Ready-made, validated Zod field types, mirroring `utils.fields`.\n *\n * Reusable building blocks for DTOs so you don't re-derive the same constraint\n * everywhere: money in cents, a price string, a percentage, a latitude, a slug,\n * a hex color. Compose them into schemas with `z.object({ price: priceField })`.\n */\n\nimport { z } from \"@/schemas/base\";\n\n// --- Integers ---------------------------------------------------------------\n\n/** A strictly positive integer (`> 0`). */\nexport const positiveIntField = z.number().int().positive();\n/** A non-negative integer (`>= 0`). */\nexport const nonNegativeIntField = z.number().int().min(0);\n/** A monetary amount in the smallest unit (cents), `>= 0`. Avoids float drift. */\nexport const centsField = z.number().int().min(0);\n/** A TCP port (`1..65535`). */\nexport const portField = z.number().int().min(1).max(65535);\n/** A 0–5 star rating. */\nexport const ratingField = z.number().int().min(0).max(5);\n\n// --- Floats -----------------------------------------------------------------\n\n/** A strictly positive float (`> 0`). */\nexport const positiveFloatField = z.number().positive();\n/** A non-negative float (`>= 0`). */\nexport const nonNegativeFloatField = z.number().min(0);\n/** A percentage (`0..100`). */\nexport const percentField = z.number().min(0).max(100);\n/** A ratio (`0..1`). */\nexport const ratioField = z.number().min(0).max(1);\n/** A WGS-84 latitude (`-90..90`). */\nexport const latitudeField = z.number().min(-90).max(90);\n/** A WGS-84 longitude (`-180..180`). */\nexport const longitudeField = z.number().min(-180).max(180);\n\n// --- Strings ----------------------------------------------------------------\n\n/** A non-empty string; whitespace is trimmed before the length check. */\nexport const nonEmptyStrField = z\n .string()\n .transform((value) => value.trim())\n .pipe(z.string().min(1));\n\n/** A URL slug: lowercase alphanumerics separated by single hyphens. */\nexport const slugField = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {\n message: \"Must be a lowercase hyphen-separated slug.\",\n});\n\n/** A hex color: `#rgb` or `#rrggbb`. */\nexport const hexColorField = z.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, {\n message: \"Must be a #rgb or #rrggbb hex color.\",\n});\n\n/**\n * A money amount as an exact decimal **string** with up to two decimal places\n * (e.g. `\"19.90\"`). Mirrors `PriceField` — `tempest-db-js` `numeric` columns map\n * to strings, so money stays exact instead of drifting through a float.\n */\nexport const priceField = z\n .string()\n .regex(/^\\d+(?:\\.\\d{1,2})?$/, {\n message: 'Must be a decimal money string, e.g. \"19.90\".',\n });\n\n// --- Booleans ---------------------------------------------------------------\n\n/** Message shown when a value is neither a boolean nor a boolean-ish token. */\nconst LOOSE_BOOLEAN_ERROR =\n 'Must be a boolean value such as \"true\", \"false\", \"1\", \"0\", \"yes\" or \"no\".';\n\n/**\n * A boolean read from a **textual** source — a query string or an environment\n * variable, where every value arrives as a string.\n *\n * `z.coerce.boolean()` is the wrong tool for those: it is `Boolean(input)`, so\n * every non-empty string is `true` — `\"false\"` and `\"0\"` included — and there is\n * no way to ask for `false` over the wire. This reads the usual textual tokens\n * in both directions (`true`/`1`/`yes`/`on`/`y`/`enabled` and\n * `false`/`0`/`no`/`off`/`n`/`disabled`, case-insensitive, surrounding\n * whitespace trimmed) and **rejects** anything else, so a typo surfaces as a\n * validation error instead of silently becoming `false`.\n *\n * An absent value — and an empty or whitespace-only string, which is how an\n * unset `.env` entry (`DEBUG=`) reaches the schema — falls back to\n * `defaultValue`. Real booleans pass through untouched, so a schema built for\n * `process.env` still parses a synthetic object in tests.\n *\n * The OpenAPI metadata is pinned to `type: boolean` (with the default) so the\n * document describes the field the client actually sends, not the union used to\n * parse it.\n *\n * @param defaultValue - The value used when the field is absent or empty.\n * @returns A zod schema producing a `boolean`.\n */\nexport function looseBoolean(defaultValue: boolean) {\n return z\n .preprocess(\n (value) => (typeof value === \"string\" ? value.trim() || undefined : value),\n z\n .union([z.boolean(), z.stringbool()], { error: LOOSE_BOOLEAN_ERROR })\n .default(defaultValue),\n )\n .openapi({ type: \"boolean\", default: defaultValue });\n}\n","/**\n * Environment-driven settings, mirroring `settings.base.BaseAppSettings`.\n *\n * Pydantic-settings reads env vars into a frozen, validated model. Here a zod\n * schema plays the same role: {@link loadSettings} parses `process.env` (env\n * names are matched case-sensitively, like the FastAPI SDK) and returns a\n * frozen, fully-typed object. Composable fragments ({@link serverSettingsShape}\n * etc.) cover the common server/database/CORS knobs.\n */\n\nimport { z } from \"@/schemas/base\";\nimport { looseBoolean } from \"@/schemas/fields\";\n\n/** Server bind/runtime settings. Defaults bind to localhost. */\nexport const serverSettingsShape = {\n HOST: z.string().default(\"127.0.0.1\"),\n PORT: z.coerce.number().int().min(0).max(65535).default(8000),\n DEBUG: looseBoolean(false),\n} as const;\n\n/** Database connection settings. */\nexport const databaseSettingsShape = {\n DATABASE_URL: z.string().default(\"sqlite://./app.db\"),\n} as const;\n\n/** CORS settings. `CORS_ORIGINS` is a comma-separated list. */\nexport const corsSettingsShape = {\n CORS_ORIGINS: z\n .string()\n .default(\"*\")\n .transform((value) =>\n value\n .split(\",\")\n .map((origin) => origin.trim())\n .filter((origin) => origin.length > 0),\n ),\n} as const;\n\n/** The combined base settings shape (server + database + CORS). */\nexport const baseAppSettingsShape = {\n ...serverSettingsShape,\n ...databaseSettingsShape,\n ...corsSettingsShape,\n} as const;\n\n/** A zod object built from {@link baseAppSettingsShape}. */\nexport const baseAppSettingsSchema = z.object(baseAppSettingsShape);\n\n/** The parsed shape of {@link baseAppSettingsSchema}. */\nexport type BaseAppSettings = z.infer<typeof baseAppSettingsSchema>;\n\n/**\n * Parse and freeze settings from an environment source.\n *\n * Extend the base shape with project fields:\n *\n * ```ts\n * const settings = loadSettings(\n * z.object({ ...baseAppSettingsShape, JWT_SECRET: z.string() }),\n * );\n * ```\n *\n * @param schema - The settings zod schema.\n * @param env - The environment source (defaults to `process.env`).\n * @returns The validated, frozen settings object.\n * @throws {z.ZodError} When required env vars are missing or malformed.\n */\nexport function loadSettings<S extends z.ZodType>(\n schema: S,\n env: NodeJS.ProcessEnv = process.env,\n): Readonly<z.infer<S>> {\n return Object.freeze(schema.parse(env));\n}\n","/**\n * Password hashing backed by bcrypt, mirroring `utils.password.PasswordUtils`.\n *\n * `bcryptjs` is an optional peer dependency, imported lazily so that\n * `import \"tempest-express-sdk\"` keeps working when it is not installed — the\n * clear error is deferred to first use. Methods are async (the idiomatic Node\n * bcrypt surface).\n */\n\ntype BcryptModule = typeof import(\"bcryptjs\");\n\nlet cached: BcryptModule | null = null;\n\n/** Lazily load `bcryptjs`, with a clear install hint when missing. */\nasync function loadBcrypt(): Promise<BcryptModule> {\n if (cached) return cached;\n try {\n const mod = (await import(\"bcryptjs\")) as BcryptModule & { default?: BcryptModule };\n cached = mod.default ?? mod;\n } catch (cause) {\n throw new Error(\n \"PasswordUtils requires the 'bcryptjs' peer dependency. Install with `npm i bcryptjs`.\",\n { cause },\n );\n }\n return cached;\n}\n\n/** Hash and verify passwords using bcrypt. Stateless — construct once, reuse. */\nexport class PasswordUtils {\n /**\n * @param rounds - The bcrypt cost factor. Higher is slower and harder to\n * brute-force. Defaults to 12.\n */\n constructor(private readonly rounds: number = 12) {}\n\n /**\n * Hash a plaintext password.\n *\n * @param plain - The plaintext password.\n * @returns The bcrypt hash, ready to persist.\n */\n async hash(plain: string): Promise<string> {\n const bcrypt = await loadBcrypt();\n const salt = await bcrypt.genSalt(this.rounds);\n return bcrypt.hash(plain, salt);\n }\n\n /**\n * Verify a plaintext password against a stored hash. Returns `false` for\n * malformed hashes rather than throwing.\n *\n * @param plain - The plaintext password to verify.\n * @param hashed - The previously stored bcrypt hash.\n * @returns `true` when the password matches.\n */\n async verify(plain: string, hashed: string): Promise<boolean> {\n try {\n const bcrypt = await loadBcrypt();\n return await bcrypt.compare(plain, hashed);\n } catch {\n return false;\n }\n }\n}\n","/** The installed SDK version. Single source of truth for the barrel + CLI. */\nexport const VERSION = \"0.22.0\";\n"]}
|
package/dist/cli.cjs
CHANGED
|
@@ -10,8 +10,11 @@ var util = require('util');
|
|
|
10
10
|
var zodToOpenapi = require('@asteasolutions/zod-to-openapi');
|
|
11
11
|
var zod = require('zod');
|
|
12
12
|
|
|
13
|
+
// src/version.ts
|
|
14
|
+
var VERSION = "0.22.0";
|
|
15
|
+
|
|
13
16
|
// src/cli/template.ts
|
|
14
|
-
var SDK_VERSION =
|
|
17
|
+
var SDK_VERSION = `^${VERSION}`;
|
|
15
18
|
function projectFiles(name) {
|
|
16
19
|
return {
|
|
17
20
|
"package.json": `${JSON.stringify(
|
|
@@ -30,7 +33,7 @@ function projectFiles(name) {
|
|
|
30
33
|
express: "^5.1.0",
|
|
31
34
|
"tempest-db-js": "^0.4.0",
|
|
32
35
|
"tempest-express-sdk": SDK_VERSION,
|
|
33
|
-
zod: "^
|
|
36
|
+
zod: "^4.0.0"
|
|
34
37
|
},
|
|
35
38
|
devDependencies: {
|
|
36
39
|
"@types/express": "^5.0.0",
|
|
@@ -412,17 +415,47 @@ volumes:
|
|
|
412
415
|
}
|
|
413
416
|
zodToOpenapi.extendZodWithOpenApi(zod.z);
|
|
414
417
|
zod.z.object({
|
|
415
|
-
id: zod.z.
|
|
418
|
+
id: zod.z.uuid().openapi({ description: "The unique identifier of the record." }),
|
|
416
419
|
isActive: zod.z.boolean().openapi({ description: "Whether the record is active (soft-delete flag)." }),
|
|
417
420
|
createdAt: zod.z.coerce.date().openapi({ description: "The creation timestamp of the record (UTC)." }),
|
|
418
421
|
updatedAt: zod.z.coerce.date().openapi({ description: "The last update timestamp of the record (UTC)." })
|
|
419
422
|
});
|
|
420
423
|
|
|
424
|
+
// src/schemas/fields.ts
|
|
425
|
+
zod.z.number().int().positive();
|
|
426
|
+
zod.z.number().int().min(0);
|
|
427
|
+
zod.z.number().int().min(0);
|
|
428
|
+
zod.z.number().int().min(1).max(65535);
|
|
429
|
+
zod.z.number().int().min(0).max(5);
|
|
430
|
+
zod.z.number().positive();
|
|
431
|
+
zod.z.number().min(0);
|
|
432
|
+
zod.z.number().min(0).max(100);
|
|
433
|
+
zod.z.number().min(0).max(1);
|
|
434
|
+
zod.z.number().min(-90).max(90);
|
|
435
|
+
zod.z.number().min(-180).max(180);
|
|
436
|
+
zod.z.string().transform((value) => value.trim()).pipe(zod.z.string().min(1));
|
|
437
|
+
zod.z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {
|
|
438
|
+
message: "Must be a lowercase hyphen-separated slug."
|
|
439
|
+
});
|
|
440
|
+
zod.z.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, {
|
|
441
|
+
message: "Must be a #rgb or #rrggbb hex color."
|
|
442
|
+
});
|
|
443
|
+
zod.z.string().regex(/^\d+(?:\.\d{1,2})?$/, {
|
|
444
|
+
message: 'Must be a decimal money string, e.g. "19.90".'
|
|
445
|
+
});
|
|
446
|
+
var LOOSE_BOOLEAN_ERROR = 'Must be a boolean value such as "true", "false", "1", "0", "yes" or "no".';
|
|
447
|
+
function looseBoolean(defaultValue) {
|
|
448
|
+
return zod.z.preprocess(
|
|
449
|
+
(value) => typeof value === "string" ? value.trim() || void 0 : value,
|
|
450
|
+
zod.z.union([zod.z.boolean(), zod.z.stringbool()], { error: LOOSE_BOOLEAN_ERROR }).default(defaultValue)
|
|
451
|
+
).openapi({ type: "boolean", default: defaultValue });
|
|
452
|
+
}
|
|
453
|
+
|
|
421
454
|
// src/settings/base.ts
|
|
422
455
|
var serverSettingsShape = {
|
|
423
456
|
HOST: zod.z.string().default("127.0.0.1"),
|
|
424
457
|
PORT: zod.z.coerce.number().int().min(0).max(65535).default(8e3),
|
|
425
|
-
DEBUG:
|
|
458
|
+
DEBUG: looseBoolean(false)
|
|
426
459
|
};
|
|
427
460
|
var databaseSettingsShape = {
|
|
428
461
|
DATABASE_URL: zod.z.string().default("sqlite://./app.db")
|
|
@@ -495,9 +528,6 @@ var PasswordUtils = class {
|
|
|
495
528
|
}
|
|
496
529
|
};
|
|
497
530
|
|
|
498
|
-
// src/version.ts
|
|
499
|
-
var VERSION = "0.20.1";
|
|
500
|
-
|
|
501
531
|
// src/cli/index.ts
|
|
502
532
|
var USAGE = `tempest-express \u2014 Express SDK CLI
|
|
503
533
|
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/template.ts","../src/schemas/base.ts","../src/settings/base.ts","../src/utils/password.ts","../src/version.ts","../src/cli/index.ts"],"names":["extendZodWithOpenApi","z","existsSync","readFileSync","join","mkdir","dirname","writeFile","resolve","parseArgs","randomBytes","spawnSync","randomUUID"],"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.20.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.4.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","/**\n * Zod foundation shared by every DTO, mirroring `schemas.base.BaseSchema`.\n *\n * Pydantic uses class inheritance for shared config; Zod composes instead. This\n * module re-exports a `z` already augmented with `.openapi()` (so every schema\n * can carry OpenAPI metadata) and a {@link toDict} helper matching\n * `BaseSchema.to_dict` (drop nullish, exclude keys, merge extras).\n */\n\nimport { extendZodWithOpenApi } from \"@asteasolutions/zod-to-openapi\";\nimport { z } from \"zod\";\n\n// Augment the shared `z` instance with `.openapi(...)`. Idempotent — calling it\n// more than once across modules is safe.\nextendZodWithOpenApi(z);\n\nexport { z };\n\n/** Options for {@link toDict}. */\nexport interface ToDictOptions {\n /** Field names to drop from the output. */\n exclude?: string[];\n /** Extra entries merged on top (override existing keys). */\n include?: Record<string, unknown>;\n}\n\n/**\n * Serialize a validated object to a plain record, dropping `null`/`undefined`,\n * removing `exclude`d keys and merging `include` on top.\n *\n * @param data - The source object (typically a parsed schema).\n * @param options - Keys to exclude and entries to merge in.\n * @returns The cleaned record.\n */\nexport function toDict(\n data: Record<string, unknown>,\n options: ToDictOptions = {},\n): Record<string, unknown> {\n const exclude = new Set(options.exclude ?? []);\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (exclude.has(key)) continue;\n if (value === null || value === undefined) continue;\n out[key] = value;\n }\n return { ...out, ...(options.include ?? {}) };\n}\n\n/**\n * Response schema fields every ORM record carries (`id`, `isActive`,\n * `createdAt`, `updatedAt`). Mirrors `BaseResponseSchema`. Extend it with\n * `baseResponseSchema.extend({ ... })` to build concrete `*ResponseSchema`s.\n */\nexport const baseResponseSchema = z.object({\n id: z.string().uuid().openapi({ description: \"The unique identifier of the record.\" }),\n isActive: z\n .boolean()\n .openapi({ description: \"Whether the record is active (soft-delete flag).\" }),\n createdAt: z.coerce\n .date()\n .openapi({ description: \"The creation timestamp of the record (UTC).\" }),\n updatedAt: z.coerce\n .date()\n .openapi({ description: \"The last update timestamp of the record (UTC).\" }),\n});\n\n/** The inferred TS type of a {@link baseResponseSchema} payload. */\nexport type BaseResponse = z.infer<typeof baseResponseSchema>;\n","/**\n * Environment-driven settings, mirroring `settings.base.BaseAppSettings`.\n *\n * Pydantic-settings reads env vars into a frozen, validated model. Here a zod\n * schema plays the same role: {@link loadSettings} parses `process.env` (env\n * names are matched case-sensitively, like the FastAPI SDK) and returns a\n * frozen, fully-typed object. Composable fragments ({@link serverSettingsShape}\n * etc.) cover the common server/database/CORS knobs.\n */\n\nimport { z } from \"@/schemas/base\";\n\n/** Server bind/runtime settings. Defaults bind to localhost. */\nexport const serverSettingsShape = {\n HOST: z.string().default(\"127.0.0.1\"),\n PORT: z.coerce.number().int().min(0).max(65535).default(8000),\n DEBUG: z.coerce.boolean().default(false),\n} as const;\n\n/** Database connection settings. */\nexport const databaseSettingsShape = {\n DATABASE_URL: z.string().default(\"sqlite://./app.db\"),\n} as const;\n\n/** CORS settings. `CORS_ORIGINS` is a comma-separated list. */\nexport const corsSettingsShape = {\n CORS_ORIGINS: z\n .string()\n .default(\"*\")\n .transform((value) =>\n value\n .split(\",\")\n .map((origin) => origin.trim())\n .filter((origin) => origin.length > 0),\n ),\n} as const;\n\n/** The combined base settings shape (server + database + CORS). */\nexport const baseAppSettingsShape = {\n ...serverSettingsShape,\n ...databaseSettingsShape,\n ...corsSettingsShape,\n} as const;\n\n/** A zod object built from {@link baseAppSettingsShape}. */\nexport const baseAppSettingsSchema = z.object(baseAppSettingsShape);\n\n/** The parsed shape of {@link baseAppSettingsSchema}. */\nexport type BaseAppSettings = z.infer<typeof baseAppSettingsSchema>;\n\n/**\n * Parse and freeze settings from an environment source.\n *\n * Extend the base shape with project fields:\n *\n * ```ts\n * const settings = loadSettings(\n * z.object({ ...baseAppSettingsShape, JWT_SECRET: z.string() }),\n * );\n * ```\n *\n * @param schema - The settings zod schema.\n * @param env - The environment source (defaults to `process.env`).\n * @returns The validated, frozen settings object.\n * @throws {z.ZodError} When required env vars are missing or malformed.\n */\nexport function loadSettings<S extends z.ZodTypeAny>(\n schema: S,\n env: NodeJS.ProcessEnv = process.env,\n): Readonly<z.infer<S>> {\n return Object.freeze(schema.parse(env));\n}\n","/**\n * Password hashing backed by bcrypt, mirroring `utils.password.PasswordUtils`.\n *\n * `bcryptjs` is an optional peer dependency, imported lazily so that\n * `import \"tempest-express-sdk\"` keeps working when it is not installed — the\n * clear error is deferred to first use. Methods are async (the idiomatic Node\n * bcrypt surface).\n */\n\ntype BcryptModule = typeof import(\"bcryptjs\");\n\nlet cached: BcryptModule | null = null;\n\n/** Lazily load `bcryptjs`, with a clear install hint when missing. */\nasync function loadBcrypt(): Promise<BcryptModule> {\n if (cached) return cached;\n try {\n const mod = (await import(\"bcryptjs\")) as BcryptModule & { default?: BcryptModule };\n cached = mod.default ?? mod;\n } catch (cause) {\n throw new Error(\n \"PasswordUtils requires the 'bcryptjs' peer dependency. Install with `npm i bcryptjs`.\",\n { cause },\n );\n }\n return cached;\n}\n\n/** Hash and verify passwords using bcrypt. Stateless — construct once, reuse. */\nexport class PasswordUtils {\n /**\n * @param rounds - The bcrypt cost factor. Higher is slower and harder to\n * brute-force. Defaults to 12.\n */\n constructor(private readonly rounds: number = 12) {}\n\n /**\n * Hash a plaintext password.\n *\n * @param plain - The plaintext password.\n * @returns The bcrypt hash, ready to persist.\n */\n async hash(plain: string): Promise<string> {\n const bcrypt = await loadBcrypt();\n const salt = await bcrypt.genSalt(this.rounds);\n return bcrypt.hash(plain, salt);\n }\n\n /**\n * Verify a plaintext password against a stored hash. Returns `false` for\n * malformed hashes rather than throwing.\n *\n * @param plain - The plaintext password to verify.\n * @param hashed - The previously stored bcrypt hash.\n * @returns `true` when the password matches.\n */\n async verify(plain: string, hashed: string): Promise<boolean> {\n try {\n const bcrypt = await loadBcrypt();\n return await bcrypt.compare(plain, hashed);\n } catch {\n return false;\n }\n }\n}\n","/** The installed SDK version. Single source of truth for the barrel + CLI. */\nexport const VERSION = \"0.20.1\";\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 { spawnSync } from \"node:child_process\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\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 { baseAppSettingsSchema, loadSettings } from \"@/settings/base\";\nimport { PasswordUtils } from \"@/utils/password\";\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 lint [--dir <path>] Run Biome check in a project\n tempest-express config [--dir <path>] Print the resolved base settings\n tempest-express user --email <e> --password <p> [--admin]\n Print a ready-to-insert user record\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/** Parse a `.env` file's `KEY=VALUE` lines into a record (ignores comments). */\nfunction parseDotEnv(path: string): Record<string, string> {\n const out: Record<string, string> = {};\n if (!existsSync(path)) return out;\n for (const raw of readFileSync(path, \"utf8\").split(\"\\n\")) {\n const line = raw.trim();\n if (!line || line.startsWith(\"#\")) continue;\n const eq = line.indexOf(\"=\");\n if (eq === -1) continue;\n const key = line.slice(0, eq).trim();\n let value = line.slice(eq + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n out[key] = value;\n }\n return out;\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 email: { type: \"string\" },\n password: { type: \"string\" },\n admin: { type: \"boolean\", default: false },\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 if (command === \"lint\") {\n const target = resolve(dir);\n const result = spawnSync(\"npx\", [\"biome\", \"check\", target], {\n stdio: \"inherit\",\n shell: process.platform === \"win32\",\n });\n return result.status ?? 1;\n }\n\n if (command === \"config\") {\n const env = { ...process.env, ...parseDotEnv(join(resolve(dir), \".env\")) };\n const settings = loadSettings(baseAppSettingsSchema, env);\n process.stdout.write(`${JSON.stringify(settings, null, 2)}\\n`);\n return 0;\n }\n\n if (command === \"user\") {\n const { email, password } = values;\n if (!email || !password) {\n process.stderr.write(\"error: `user` requires --email and --password\\n\");\n return 1;\n }\n const hashedPassword = await new PasswordUtils().hash(password);\n const record = {\n id: randomUUID(),\n email,\n hashedPassword,\n isAdmin: Boolean(values.admin),\n isActive: true,\n };\n process.stdout.write(`${JSON.stringify(record, null, 2)}\\n`);\n return 0;\n }\n\n process.stderr.write(`error: unknown command ${JSON.stringify(command)}\\n\\n${USAGE}`);\n return 1;\n}\n\n// Auto-run as a binary, but stay importable from tests without executing.\nif (!process.env.VITEST) {\n main()\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}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/version.ts","../src/cli/template.ts","../src/schemas/base.ts","../src/schemas/fields.ts","../src/settings/base.ts","../src/utils/password.ts","../src/cli/index.ts"],"names":["extendZodWithOpenApi","z","existsSync","readFileSync","join","mkdir","dirname","writeFile","resolve","parseArgs","randomBytes","spawnSync","randomUUID"],"mappings":"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","file":"cli.cjs","sourcesContent":["/** The installed SDK version. Single source of truth for the barrel + CLI. */\nexport const VERSION = \"0.22.0\";\n","/**\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\nimport { VERSION } from \"@/version\";\n\n/**\n * Caret range for the SDK the generated project depends on. Derived from\n * {@link VERSION} so a release bump can never leave the scaffold pinned to an\n * older major/minor — a stale pin here would install a different SDK (and, with\n * it, a different `zod` peer range) than the CLI that wrote the project.\n */\nconst SDK_VERSION = `^${VERSION}`;\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.4.0\",\n \"tempest-express-sdk\": SDK_VERSION,\n zod: \"^4.0.0\",\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","/**\n * Zod foundation shared by every DTO, mirroring `schemas.base.BaseSchema`.\n *\n * Pydantic uses class inheritance for shared config; Zod composes instead. This\n * module re-exports a `z` already augmented with `.openapi()` (so every schema\n * can carry OpenAPI metadata) and a {@link toDict} helper matching\n * `BaseSchema.to_dict` (drop nullish, exclude keys, merge extras).\n */\n\nimport { extendZodWithOpenApi } from \"@asteasolutions/zod-to-openapi\";\nimport { z } from \"zod\";\n\n// Augment the shared `z` instance with `.openapi(...)`. Idempotent — calling it\n// more than once across modules is safe.\nextendZodWithOpenApi(z);\n\nexport { z };\n\n/** Options for {@link toDict}. */\nexport interface ToDictOptions {\n /** Field names to drop from the output. */\n exclude?: string[];\n /** Extra entries merged on top (override existing keys). */\n include?: Record<string, unknown>;\n}\n\n/**\n * Serialize a validated object to a plain record, dropping `null`/`undefined`,\n * removing `exclude`d keys and merging `include` on top.\n *\n * @param data - The source object (typically a parsed schema).\n * @param options - Keys to exclude and entries to merge in.\n * @returns The cleaned record.\n */\nexport function toDict(\n data: Record<string, unknown>,\n options: ToDictOptions = {},\n): Record<string, unknown> {\n const exclude = new Set(options.exclude ?? []);\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (exclude.has(key)) continue;\n if (value === null || value === undefined) continue;\n out[key] = value;\n }\n return { ...out, ...(options.include ?? {}) };\n}\n\n/**\n * Response schema fields every ORM record carries (`id`, `isActive`,\n * `createdAt`, `updatedAt`). Mirrors `BaseResponseSchema`. Extend it with\n * `baseResponseSchema.extend({ ... })` to build concrete `*ResponseSchema`s.\n */\nexport const baseResponseSchema = z.object({\n id: z.uuid().openapi({ description: \"The unique identifier of the record.\" }),\n isActive: z\n .boolean()\n .openapi({ description: \"Whether the record is active (soft-delete flag).\" }),\n createdAt: z.coerce\n .date()\n .openapi({ description: \"The creation timestamp of the record (UTC).\" }),\n updatedAt: z.coerce\n .date()\n .openapi({ description: \"The last update timestamp of the record (UTC).\" }),\n});\n\n/** The inferred TS type of a {@link baseResponseSchema} payload. */\nexport type BaseResponse = z.infer<typeof baseResponseSchema>;\n","/**\n * Ready-made, validated Zod field types, mirroring `utils.fields`.\n *\n * Reusable building blocks for DTOs so you don't re-derive the same constraint\n * everywhere: money in cents, a price string, a percentage, a latitude, a slug,\n * a hex color. Compose them into schemas with `z.object({ price: priceField })`.\n */\n\nimport { z } from \"@/schemas/base\";\n\n// --- Integers ---------------------------------------------------------------\n\n/** A strictly positive integer (`> 0`). */\nexport const positiveIntField = z.number().int().positive();\n/** A non-negative integer (`>= 0`). */\nexport const nonNegativeIntField = z.number().int().min(0);\n/** A monetary amount in the smallest unit (cents), `>= 0`. Avoids float drift. */\nexport const centsField = z.number().int().min(0);\n/** A TCP port (`1..65535`). */\nexport const portField = z.number().int().min(1).max(65535);\n/** A 0–5 star rating. */\nexport const ratingField = z.number().int().min(0).max(5);\n\n// --- Floats -----------------------------------------------------------------\n\n/** A strictly positive float (`> 0`). */\nexport const positiveFloatField = z.number().positive();\n/** A non-negative float (`>= 0`). */\nexport const nonNegativeFloatField = z.number().min(0);\n/** A percentage (`0..100`). */\nexport const percentField = z.number().min(0).max(100);\n/** A ratio (`0..1`). */\nexport const ratioField = z.number().min(0).max(1);\n/** A WGS-84 latitude (`-90..90`). */\nexport const latitudeField = z.number().min(-90).max(90);\n/** A WGS-84 longitude (`-180..180`). */\nexport const longitudeField = z.number().min(-180).max(180);\n\n// --- Strings ----------------------------------------------------------------\n\n/** A non-empty string; whitespace is trimmed before the length check. */\nexport const nonEmptyStrField = z\n .string()\n .transform((value) => value.trim())\n .pipe(z.string().min(1));\n\n/** A URL slug: lowercase alphanumerics separated by single hyphens. */\nexport const slugField = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {\n message: \"Must be a lowercase hyphen-separated slug.\",\n});\n\n/** A hex color: `#rgb` or `#rrggbb`. */\nexport const hexColorField = z.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, {\n message: \"Must be a #rgb or #rrggbb hex color.\",\n});\n\n/**\n * A money amount as an exact decimal **string** with up to two decimal places\n * (e.g. `\"19.90\"`). Mirrors `PriceField` — `tempest-db-js` `numeric` columns map\n * to strings, so money stays exact instead of drifting through a float.\n */\nexport const priceField = z\n .string()\n .regex(/^\\d+(?:\\.\\d{1,2})?$/, {\n message: 'Must be a decimal money string, e.g. \"19.90\".',\n });\n\n// --- Booleans ---------------------------------------------------------------\n\n/** Message shown when a value is neither a boolean nor a boolean-ish token. */\nconst LOOSE_BOOLEAN_ERROR =\n 'Must be a boolean value such as \"true\", \"false\", \"1\", \"0\", \"yes\" or \"no\".';\n\n/**\n * A boolean read from a **textual** source — a query string or an environment\n * variable, where every value arrives as a string.\n *\n * `z.coerce.boolean()` is the wrong tool for those: it is `Boolean(input)`, so\n * every non-empty string is `true` — `\"false\"` and `\"0\"` included — and there is\n * no way to ask for `false` over the wire. This reads the usual textual tokens\n * in both directions (`true`/`1`/`yes`/`on`/`y`/`enabled` and\n * `false`/`0`/`no`/`off`/`n`/`disabled`, case-insensitive, surrounding\n * whitespace trimmed) and **rejects** anything else, so a typo surfaces as a\n * validation error instead of silently becoming `false`.\n *\n * An absent value — and an empty or whitespace-only string, which is how an\n * unset `.env` entry (`DEBUG=`) reaches the schema — falls back to\n * `defaultValue`. Real booleans pass through untouched, so a schema built for\n * `process.env` still parses a synthetic object in tests.\n *\n * The OpenAPI metadata is pinned to `type: boolean` (with the default) so the\n * document describes the field the client actually sends, not the union used to\n * parse it.\n *\n * @param defaultValue - The value used when the field is absent or empty.\n * @returns A zod schema producing a `boolean`.\n */\nexport function looseBoolean(defaultValue: boolean) {\n return z\n .preprocess(\n (value) => (typeof value === \"string\" ? value.trim() || undefined : value),\n z\n .union([z.boolean(), z.stringbool()], { error: LOOSE_BOOLEAN_ERROR })\n .default(defaultValue),\n )\n .openapi({ type: \"boolean\", default: defaultValue });\n}\n","/**\n * Environment-driven settings, mirroring `settings.base.BaseAppSettings`.\n *\n * Pydantic-settings reads env vars into a frozen, validated model. Here a zod\n * schema plays the same role: {@link loadSettings} parses `process.env` (env\n * names are matched case-sensitively, like the FastAPI SDK) and returns a\n * frozen, fully-typed object. Composable fragments ({@link serverSettingsShape}\n * etc.) cover the common server/database/CORS knobs.\n */\n\nimport { z } from \"@/schemas/base\";\nimport { looseBoolean } from \"@/schemas/fields\";\n\n/** Server bind/runtime settings. Defaults bind to localhost. */\nexport const serverSettingsShape = {\n HOST: z.string().default(\"127.0.0.1\"),\n PORT: z.coerce.number().int().min(0).max(65535).default(8000),\n DEBUG: looseBoolean(false),\n} as const;\n\n/** Database connection settings. */\nexport const databaseSettingsShape = {\n DATABASE_URL: z.string().default(\"sqlite://./app.db\"),\n} as const;\n\n/** CORS settings. `CORS_ORIGINS` is a comma-separated list. */\nexport const corsSettingsShape = {\n CORS_ORIGINS: z\n .string()\n .default(\"*\")\n .transform((value) =>\n value\n .split(\",\")\n .map((origin) => origin.trim())\n .filter((origin) => origin.length > 0),\n ),\n} as const;\n\n/** The combined base settings shape (server + database + CORS). */\nexport const baseAppSettingsShape = {\n ...serverSettingsShape,\n ...databaseSettingsShape,\n ...corsSettingsShape,\n} as const;\n\n/** A zod object built from {@link baseAppSettingsShape}. */\nexport const baseAppSettingsSchema = z.object(baseAppSettingsShape);\n\n/** The parsed shape of {@link baseAppSettingsSchema}. */\nexport type BaseAppSettings = z.infer<typeof baseAppSettingsSchema>;\n\n/**\n * Parse and freeze settings from an environment source.\n *\n * Extend the base shape with project fields:\n *\n * ```ts\n * const settings = loadSettings(\n * z.object({ ...baseAppSettingsShape, JWT_SECRET: z.string() }),\n * );\n * ```\n *\n * @param schema - The settings zod schema.\n * @param env - The environment source (defaults to `process.env`).\n * @returns The validated, frozen settings object.\n * @throws {z.ZodError} When required env vars are missing or malformed.\n */\nexport function loadSettings<S extends z.ZodType>(\n schema: S,\n env: NodeJS.ProcessEnv = process.env,\n): Readonly<z.infer<S>> {\n return Object.freeze(schema.parse(env));\n}\n","/**\n * Password hashing backed by bcrypt, mirroring `utils.password.PasswordUtils`.\n *\n * `bcryptjs` is an optional peer dependency, imported lazily so that\n * `import \"tempest-express-sdk\"` keeps working when it is not installed — the\n * clear error is deferred to first use. Methods are async (the idiomatic Node\n * bcrypt surface).\n */\n\ntype BcryptModule = typeof import(\"bcryptjs\");\n\nlet cached: BcryptModule | null = null;\n\n/** Lazily load `bcryptjs`, with a clear install hint when missing. */\nasync function loadBcrypt(): Promise<BcryptModule> {\n if (cached) return cached;\n try {\n const mod = (await import(\"bcryptjs\")) as BcryptModule & { default?: BcryptModule };\n cached = mod.default ?? mod;\n } catch (cause) {\n throw new Error(\n \"PasswordUtils requires the 'bcryptjs' peer dependency. Install with `npm i bcryptjs`.\",\n { cause },\n );\n }\n return cached;\n}\n\n/** Hash and verify passwords using bcrypt. Stateless — construct once, reuse. */\nexport class PasswordUtils {\n /**\n * @param rounds - The bcrypt cost factor. Higher is slower and harder to\n * brute-force. Defaults to 12.\n */\n constructor(private readonly rounds: number = 12) {}\n\n /**\n * Hash a plaintext password.\n *\n * @param plain - The plaintext password.\n * @returns The bcrypt hash, ready to persist.\n */\n async hash(plain: string): Promise<string> {\n const bcrypt = await loadBcrypt();\n const salt = await bcrypt.genSalt(this.rounds);\n return bcrypt.hash(plain, salt);\n }\n\n /**\n * Verify a plaintext password against a stored hash. Returns `false` for\n * malformed hashes rather than throwing.\n *\n * @param plain - The plaintext password to verify.\n * @param hashed - The previously stored bcrypt hash.\n * @returns `true` when the password matches.\n */\n async verify(plain: string, hashed: string): Promise<boolean> {\n try {\n const bcrypt = await loadBcrypt();\n return await bcrypt.compare(plain, hashed);\n } catch {\n return false;\n }\n }\n}\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 { spawnSync } from \"node:child_process\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\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 { baseAppSettingsSchema, loadSettings } from \"@/settings/base\";\nimport { PasswordUtils } from \"@/utils/password\";\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 lint [--dir <path>] Run Biome check in a project\n tempest-express config [--dir <path>] Print the resolved base settings\n tempest-express user --email <e> --password <p> [--admin]\n Print a ready-to-insert user record\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/** Parse a `.env` file's `KEY=VALUE` lines into a record (ignores comments). */\nfunction parseDotEnv(path: string): Record<string, string> {\n const out: Record<string, string> = {};\n if (!existsSync(path)) return out;\n for (const raw of readFileSync(path, \"utf8\").split(\"\\n\")) {\n const line = raw.trim();\n if (!line || line.startsWith(\"#\")) continue;\n const eq = line.indexOf(\"=\");\n if (eq === -1) continue;\n const key = line.slice(0, eq).trim();\n let value = line.slice(eq + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n out[key] = value;\n }\n return out;\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 email: { type: \"string\" },\n password: { type: \"string\" },\n admin: { type: \"boolean\", default: false },\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 if (command === \"lint\") {\n const target = resolve(dir);\n const result = spawnSync(\"npx\", [\"biome\", \"check\", target], {\n stdio: \"inherit\",\n shell: process.platform === \"win32\",\n });\n return result.status ?? 1;\n }\n\n if (command === \"config\") {\n const env = { ...process.env, ...parseDotEnv(join(resolve(dir), \".env\")) };\n const settings = loadSettings(baseAppSettingsSchema, env);\n process.stdout.write(`${JSON.stringify(settings, null, 2)}\\n`);\n return 0;\n }\n\n if (command === \"user\") {\n const { email, password } = values;\n if (!email || !password) {\n process.stderr.write(\"error: `user` requires --email and --password\\n\");\n return 1;\n }\n const hashedPassword = await new PasswordUtils().hash(password);\n const record = {\n id: randomUUID(),\n email,\n hashedPassword,\n isAdmin: Boolean(values.admin),\n isActive: true,\n };\n process.stdout.write(`${JSON.stringify(record, null, 2)}\\n`);\n return 0;\n }\n\n process.stderr.write(`error: unknown command ${JSON.stringify(command)}\\n\\n${USAGE}`);\n return 1;\n}\n\n// Auto-run as a binary, but stay importable from tests without executing.\nif (!process.env.VITEST) {\n main()\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}\n"]}
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION, loadSettings, baseAppSettingsSchema, PasswordUtils } from './chunk-
|
|
2
|
+
import { VERSION, loadSettings, baseAppSettingsSchema, PasswordUtils } from './chunk-7Q5NBMSP.js';
|
|
3
3
|
import { spawnSync } from 'child_process';
|
|
4
4
|
import { randomBytes, randomUUID } from 'crypto';
|
|
5
5
|
import { existsSync, readFileSync } from 'fs';
|
|
@@ -8,7 +8,7 @@ import { resolve, join, dirname } from 'path';
|
|
|
8
8
|
import { parseArgs } from 'util';
|
|
9
9
|
|
|
10
10
|
// src/cli/template.ts
|
|
11
|
-
var SDK_VERSION =
|
|
11
|
+
var SDK_VERSION = `^${VERSION}`;
|
|
12
12
|
function projectFiles(name) {
|
|
13
13
|
return {
|
|
14
14
|
"package.json": `${JSON.stringify(
|
|
@@ -27,7 +27,7 @@ function projectFiles(name) {
|
|
|
27
27
|
express: "^5.1.0",
|
|
28
28
|
"tempest-db-js": "^0.4.0",
|
|
29
29
|
"tempest-express-sdk": SDK_VERSION,
|
|
30
|
-
zod: "^
|
|
30
|
+
zod: "^4.0.0"
|
|
31
31
|
},
|
|
32
32
|
devDependencies: {
|
|
33
33
|
"@types/express": "^5.0.0",
|