@tatara/cli 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.mjs +590 -14
- package/package.json +1 -1
- package/templates/features/admin/files/src/apps/admin/server.ts +3 -2
- package/templates/features/admin/files/src/modules/example/routes/admin-greeting.routes.ts +1 -1
- package/templates/features/auth/files/src/core/auth/middleware.ts +1 -1
- package/templates/features/auth/files/src/core/auth/routes/me.routes.ts +1 -1
- package/templates/features/auth-keycloak/files/src/core/auth/commands/keycloak-token.ts +1 -1
- package/templates/features/authz/files/src/core/authz/commands/grant-role.ts +1 -1
- package/templates/features/authz/files/src/core/authz/commands/sync-permissions.ts +1 -1
- package/templates/features/authz/files/src/core/authz/middleware.ts +1 -1
- package/templates/features/authz/files/src/core/authz/routes/authorization.routes.ts +1 -1
- package/templates/features/authz/files/src/core/authz/services/grants.ts +1 -1
- package/templates/features/authz/files/src/core/authz/services/members.ts +1 -1
- package/templates/features/authz/files/src/core/authz/services/roles.ts +1 -1
- package/templates/features/base/files/AGENTS.md +1 -0
- package/templates/features/base/files/src/apps/api/server.ts +3 -2
- package/templates/features/base/files/src/apps/cli/index.ts +4 -4
- package/templates/features/base/files/src/core/http/app.ts +28 -41
- package/templates/features/base/files/src/core/http/routes.ts +7 -11
- package/templates/features/base/files/src/core/logging/logger.ts +4 -12
- package/templates/features/base/files/src/modules/example/routes/greeting.routes.ts +1 -1
- package/templates/features/db/files/src/core/db/commands/migrate.ts +1 -1
- package/templates/features/db/files/src/core/db/commands/reset.ts +1 -1
- package/templates/features/db/files/src/core/db/commands/seed.ts +1 -1
- package/templates/features/db/files/src/core/db/tests/reset.test.ts +1 -1
- package/templates/features/drizzle/files+mysql/src/core/db/client.ts +2 -2
- package/templates/features/drizzle/files+postgres/src/core/db/client.ts +2 -2
- package/templates/features/email/files/src/core/email/commands/send-test.ts +1 -1
- package/templates/features/jobs/files/src/apps/cron/index.ts +2 -2
- package/templates/features/jobs/files/src/apps/worker/index.ts +2 -2
- package/templates/features/jobs/files/src/core/jobs/commands/enqueue.ts +1 -1
- package/templates/features/jobs/files/src/core/jobs/queue.ts +1 -1
- package/templates/features/jobs/files/src/core/jobs/redis.ts +1 -1
- package/templates/features/prisma/files+mysql/src/core/db/client.ts +2 -2
- package/templates/features/prisma/files+postgres/src/core/db/client.ts +2 -2
- package/templates/features/storage/files/src/core/storage/commands/check.ts +1 -1
- package/templates/generators/command/files/src/modules/__moduleKebab__/commands/__nameKebab__.ts +1 -1
- package/templates/generators/module/files/src/modules/__moduleKebab__/routes/__moduleKebab__.routes.ts +1 -1
- package/templates/generators/resource/files/src/modules/__moduleKebab__/routes/__nameKebab__.routes.ts +1 -1
- package/templates/generators/resource/files+drizzle/src/modules/__moduleKebab__/services/__nameKebab__.service.ts +2 -2
- package/templates/generators/resource/files+prisma/src/modules/__moduleKebab__/services/__nameKebab__.service.ts +2 -2
- package/templates/generators/route/files/src/modules/__moduleKebab__/routes/__nameKebab__.routes.ts +1 -1
- package/templates/versions.json +3 -1
- package/templates/features/base/files/src/core/commands/define.ts +0 -33
- package/templates/features/base/files/src/core/commands/runner.ts +0 -54
- package/templates/features/base/files/src/core/commands/tests/flags.test.ts +0 -16
- package/templates/features/base/files/src/core/http/errors.ts +0 -79
- package/templates/features/base/files/src/core/http/health.ts +0 -25
- package/templates/features/base/files/src/core/http/middleware/error-handler.ts +0 -36
- package/templates/features/base/files/src/core/http/middleware/request-context.ts +0 -24
- package/templates/features/base/files/src/core/http/middleware/validation.ts +0 -17
- package/templates/features/base/files/src/core/http/response.ts +0 -72
- package/templates/features/base/files/src/core/http/server.ts +0 -15
- package/templates/features/base/files/src/core/process/lifecycle.ts +0 -34
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { startServer } from "@tatara/http";
|
|
1
2
|
import { app } from "#apps/admin/app";
|
|
2
3
|
import { env } from "#core/config/env";
|
|
3
|
-
import {
|
|
4
|
+
import { logger } from "#core/logging/logger";
|
|
4
5
|
|
|
5
|
-
startServer(app, env.ADMIN_PORT, "admin");
|
|
6
|
+
startServer(app, { port: env.ADMIN_PORT, name: "admin", logger });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRoute } from "@hono/zod-openapi";
|
|
2
2
|
import { currentPrincipal } from "#core/auth/middleware";
|
|
3
|
-
import { errorResponses, jsonContent, success, successSchema } from "
|
|
3
|
+
import { errorResponses, jsonContent, success, successSchema } from "@tatara/http";
|
|
4
4
|
import { createRouter, defineRoutes } from "#core/http/routes";
|
|
5
5
|
import { greetingSchema } from "#modules/example/validators/greeting.validators";
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createMiddleware } from "hono/factory";
|
|
2
2
|
import type { Context } from "hono";
|
|
3
3
|
import type { AppEnv, Principal } from "#core/http/context";
|
|
4
|
-
import { ForbiddenError, UnauthorizedError } from "
|
|
4
|
+
import { ForbiddenError, UnauthorizedError } from "@tatara/http";
|
|
5
5
|
|
|
6
6
|
/** 401 unless the request carries a valid session or token. */
|
|
7
7
|
export function requireAuth() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRoute, z } from "@hono/zod-openapi";
|
|
2
2
|
import { currentPrincipal, requireAuth } from "#core/auth/middleware";
|
|
3
|
-
import { errorResponses, jsonContent, success, successSchema } from "
|
|
3
|
+
import { errorResponses, jsonContent, success, successSchema } from "@tatara/http";
|
|
4
4
|
import { createRouter, defineRoutes } from "#core/http/routes";
|
|
5
5
|
|
|
6
6
|
const principalSchema = z
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { DEFAULT_ORG_ID, SYSTEM_ORG_ID } from "#core/authz/constants";
|
|
3
3
|
import { store } from "#core/authz/db/store";
|
|
4
4
|
import { assignRole } from "#core/authz/services/members";
|
|
5
|
-
import { defineCommand } from "
|
|
5
|
+
import { defineCommand } from "@tatara/core";
|
|
6
6
|
|
|
7
7
|
export default defineCommand({
|
|
8
8
|
name: "authz:grant-role",
|
|
@@ -2,7 +2,7 @@ import { createMiddleware } from "hono/factory";
|
|
|
2
2
|
import type { Scope } from "#core/authz/constants";
|
|
3
3
|
import { checkPermission } from "#core/authz/resolver";
|
|
4
4
|
import type { AppEnv } from "#core/http/context";
|
|
5
|
-
import { ForbiddenError, UnauthorizedError } from "
|
|
5
|
+
import { ForbiddenError, UnauthorizedError } from "@tatara/http";
|
|
6
6
|
import type { PermissionKey } from "#generated/permissions";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
userParams,
|
|
20
20
|
userRoleParams,
|
|
21
21
|
} from "#core/authz/validators";
|
|
22
|
-
import { errorResponses, jsonBody, jsonContent, success, successSchema } from "
|
|
22
|
+
import { errorResponses, jsonBody, jsonContent, success, successSchema } from "@tatara/http";
|
|
23
23
|
import { createRouter, defineRoutes } from "#core/http/routes";
|
|
24
24
|
import { catalog } from "#generated/permissions";
|
|
25
25
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assertGrantValid, InvalidGrantError, META_RESOURCES } from "#core/authz/catalog";
|
|
2
2
|
import type { Scope } from "#core/authz/constants";
|
|
3
3
|
import type { PermissionGrant } from "#core/authz/types";
|
|
4
|
-
import { BusinessRuleError } from "
|
|
4
|
+
import { BusinessRuleError } from "@tatara/http";
|
|
5
5
|
import { catalog } from "#generated/permissions";
|
|
6
6
|
|
|
7
7
|
/** Validates grants against the catalog; custom roles and overlays may not grant meta resources. */
|
|
@@ -3,7 +3,7 @@ import { type OverlayEffect, type Scope, SYSTEM_ACTOR } from "#core/authz/consta
|
|
|
3
3
|
import { store } from "#core/authz/db/store";
|
|
4
4
|
import { audit } from "#core/authz/services/audit";
|
|
5
5
|
import { validateGrants } from "#core/authz/services/grants";
|
|
6
|
-
import { NotFoundError } from "
|
|
6
|
+
import { NotFoundError } from "@tatara/http";
|
|
7
7
|
|
|
8
8
|
export async function getMember(orgId: string, userId: string) {
|
|
9
9
|
const [roles, overlays] = await Promise.all([
|
|
@@ -4,7 +4,7 @@ import { audit } from "#core/authz/services/audit";
|
|
|
4
4
|
import { validateGrants } from "#core/authz/services/grants";
|
|
5
5
|
import { SYSTEM_ROLE_KEYS } from "#core/authz/system-roles";
|
|
6
6
|
import type { RoleRow } from "#core/authz/types";
|
|
7
|
-
import { BusinessRuleError, ConflictError, NotFoundError } from "
|
|
7
|
+
import { BusinessRuleError, ConflictError, NotFoundError } from "@tatara/http";
|
|
8
8
|
|
|
9
9
|
export interface RoleInput {
|
|
10
10
|
key: string;
|
|
@@ -40,6 +40,7 @@ __PM_EXEC__ tatara make resource <name> --module <module> # CRUD slice
|
|
|
40
40
|
__PM_EXEC__ tatara make route|service|command <name> --module <module>
|
|
41
41
|
__PM_EXEC__ tatara run --list # app commands
|
|
42
42
|
__PM_EXEC__ tatara routes # every route and what protects it
|
|
43
|
+
__PM_EXEC__ tatara upgrade # after bumping @tatara/cli: run its codemods, sync, verify
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
## HTTP
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { startServer } from "@tatara/http";
|
|
1
2
|
import { app } from "#apps/api/app";
|
|
2
3
|
import { env } from "#core/config/env";
|
|
3
|
-
import {
|
|
4
|
+
import { logger } from "#core/logging/logger";
|
|
4
5
|
|
|
5
|
-
startServer(app, env.PORT, "api");
|
|
6
|
+
startServer(app, { port: env.PORT, name: "api", logger });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { runCommand } from "
|
|
2
|
-
import {
|
|
1
|
+
import { runCommand, shutdown } from "@tatara/core";
|
|
2
|
+
import { logger } from "#core/logging/logger";
|
|
3
3
|
import { commands } from "#generated/commands";
|
|
4
4
|
|
|
5
|
-
const code = await runCommand(commands, process.argv.slice(2));
|
|
6
|
-
await shutdown(code);
|
|
5
|
+
const code = await runCommand(commands, process.argv.slice(2), logger);
|
|
6
|
+
await shutdown(code, logger);
|
|
@@ -1,57 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Scalar } from "@scalar/hono-api-reference";
|
|
3
|
-
import { bodyLimit } from "hono/body-limit";
|
|
4
|
-
import { cors } from "hono/cors";
|
|
5
|
-
import { secureHeaders } from "hono/secure-headers";
|
|
1
|
+
import { createApp, handleErrors, mountRoutes, serveOpenApi } from "@tatara/http";
|
|
6
2
|
import { env } from "#core/config/env";
|
|
7
3
|
import type { AppEnv } from "#core/http/context";
|
|
8
|
-
import { runReadinessChecks } from "#core/http/health";
|
|
9
|
-
import { errorHandler, notFoundHandler } from "#core/http/middleware/error-handler";
|
|
10
|
-
import { requestContext } from "#core/http/middleware/request-context";
|
|
11
|
-
import { validationHook } from "#core/http/middleware/validation";
|
|
12
4
|
import type { AppName } from "#core/http/routes";
|
|
5
|
+
import { logger } from "#core/logging/logger";
|
|
13
6
|
import { routes } from "#generated/routes";
|
|
14
7
|
// tatara:slot imports
|
|
15
8
|
|
|
16
9
|
export const API_PREFIX = "/api";
|
|
17
10
|
|
|
11
|
+
/** The app's HTTP wiring: plain Hono, with tatara's defaults from @tatara/http. */
|
|
18
12
|
export function createHttpApp(name: AppName, options: { title: string }) {
|
|
19
|
-
const app =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
);
|
|
31
|
-
app.use(bodyLimit({ maxSize: env.BODY_LIMIT_BYTES }));
|
|
32
|
-
// tatara:slot middleware
|
|
33
|
-
|
|
34
|
-
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
35
|
-
app.get("/ready", async (c) => {
|
|
36
|
-
const result = await runReadinessChecks();
|
|
37
|
-
return c.json(result, result.ready ? 200 : 503);
|
|
13
|
+
const app = createApp<AppEnv>({
|
|
14
|
+
logger,
|
|
15
|
+
cors: {
|
|
16
|
+
origins: env.CORS_ORIGINS,
|
|
17
|
+
exposeHeaders: [
|
|
18
|
+
"X-Request-Id",
|
|
19
|
+
// tatara:slot expose-headers
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
bodyLimitBytes: env.BODY_LIMIT_BYTES,
|
|
23
|
+
defaults: { principal: null, language: "en" },
|
|
38
24
|
});
|
|
25
|
+
// tatara:slot middleware
|
|
39
26
|
|
|
40
27
|
// tatara:slot routes
|
|
41
28
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
29
|
+
mountRoutes(app, routes, { app: name, prefix: API_PREFIX });
|
|
30
|
+
serveOpenApi(app, {
|
|
31
|
+
title: options.title,
|
|
32
|
+
async extend(document) {
|
|
33
|
+
// tatara:slot openapi
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
handleErrors(app, {
|
|
37
|
+
message(err, c) {
|
|
38
|
+
let message = err.message;
|
|
39
|
+
// tatara:slot error-message
|
|
40
|
+
return message;
|
|
41
|
+
},
|
|
51
42
|
});
|
|
52
|
-
app.get("/docs", Scalar({ url: "/openapi.json", pageTitle: options.title }));
|
|
53
|
-
|
|
54
|
-
app.onError(errorHandler);
|
|
55
|
-
app.notFound(notFoundHandler);
|
|
56
43
|
return app;
|
|
57
44
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createRouter as createTataraRouter,
|
|
3
|
+
type RouteDefinition as TataraRouteDefinition,
|
|
4
|
+
} from "@tatara/http";
|
|
2
5
|
import type { AppEnv } from "#core/http/context";
|
|
3
|
-
import { validationHook } from "#core/http/middleware/validation";
|
|
4
6
|
|
|
5
7
|
export const APP_NAMES = [
|
|
6
8
|
"api",
|
|
@@ -9,13 +11,7 @@ export const APP_NAMES = [
|
|
|
9
11
|
|
|
10
12
|
export type AppName = (typeof APP_NAMES)[number];
|
|
11
13
|
|
|
12
|
-
export
|
|
13
|
-
/** which HTTP app serves these routes */
|
|
14
|
-
app: AppName;
|
|
15
|
-
/** mounted under `/api` + basePath, e.g. "/notes" */
|
|
16
|
-
basePath: string;
|
|
17
|
-
router: OpenAPIHono<AppEnv>;
|
|
18
|
-
}
|
|
14
|
+
export type RouteDefinition = TataraRouteDefinition<AppName, AppEnv>;
|
|
19
15
|
|
|
20
16
|
/** Default export of every `routes/*.routes.ts`; discovered by `tatara sync`. */
|
|
21
17
|
export function defineRoutes(definition: RouteDefinition): RouteDefinition {
|
|
@@ -23,6 +19,6 @@ export function defineRoutes(definition: RouteDefinition): RouteDefinition {
|
|
|
23
19
|
}
|
|
24
20
|
|
|
25
21
|
/** A router for one routes file. Attach middleware per route or per path, never `use("*")`. */
|
|
26
|
-
export function createRouter()
|
|
27
|
-
return
|
|
22
|
+
export function createRouter() {
|
|
23
|
+
return createTataraRouter<AppEnv>();
|
|
28
24
|
}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
+
import { loggerOptions } from "@tatara/core";
|
|
1
2
|
import { pino } from "pino";
|
|
2
3
|
import { env } from "#core/config/env";
|
|
3
4
|
|
|
4
|
-
export const logger = pino(
|
|
5
|
-
level: env.LOG_LEVEL,
|
|
6
|
-
|
|
7
|
-
...(env.NODE_ENV === "development"
|
|
8
|
-
? {
|
|
9
|
-
transport: {
|
|
10
|
-
target: "pino-pretty",
|
|
11
|
-
options: { colorize: true, ignore: "pid,hostname,service" },
|
|
12
|
-
},
|
|
13
|
-
}
|
|
14
|
-
: {}),
|
|
15
|
-
});
|
|
5
|
+
export const logger = pino(
|
|
6
|
+
loggerOptions({ env: env.NODE_ENV, level: env.LOG_LEVEL, service: "__APP_NAME__" }),
|
|
7
|
+
);
|
|
16
8
|
|
|
17
9
|
export type Logger = typeof logger;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRoute } from "@hono/zod-openapi";
|
|
2
|
-
import { errorResponses, jsonContent, success, successSchema } from "
|
|
2
|
+
import { errorResponses, jsonContent, success, successSchema } from "@tatara/http";
|
|
3
3
|
import { createRouter, defineRoutes } from "#core/http/routes";
|
|
4
4
|
import { greet } from "#modules/example/services/greeting.service";
|
|
5
5
|
import {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { defineCommand, flag } from "
|
|
2
|
+
import { defineCommand, flag } from "@tatara/core";
|
|
3
3
|
import { env } from "#core/config/env";
|
|
4
4
|
import { dropAllTables } from "#core/db/admin";
|
|
5
5
|
import { migrateDatabase } from "#core/db/migrate";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { drizzle } from "drizzle-orm/mysql2";
|
|
2
2
|
import mysql from "mysql2/promise";
|
|
3
3
|
import { env } from "#core/config/env";
|
|
4
|
-
import { registerReadinessCheck } from "
|
|
5
|
-
import { onShutdown } from "
|
|
4
|
+
import { registerReadinessCheck } from "@tatara/core";
|
|
5
|
+
import { onShutdown } from "@tatara/core";
|
|
6
6
|
import * as schema from "#generated/schema";
|
|
7
7
|
|
|
8
8
|
export const pool = mysql.createPool({ uri: env.DATABASE_URL, connectionLimit: env.DATABASE_POOL_MAX });
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { drizzle } from "drizzle-orm/node-postgres";
|
|
2
2
|
import pg from "pg";
|
|
3
3
|
import { env } from "#core/config/env";
|
|
4
|
-
import { registerReadinessCheck } from "
|
|
5
|
-
import { onShutdown } from "
|
|
4
|
+
import { registerReadinessCheck } from "@tatara/core";
|
|
5
|
+
import { onShutdown } from "@tatara/core";
|
|
6
6
|
import * as schema from "#generated/schema";
|
|
7
7
|
|
|
8
8
|
export const pool = new pg.Pool({ connectionString: env.DATABASE_URL, max: env.DATABASE_POOL_MAX });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Cron } from "croner";
|
|
2
2
|
import { withLock } from "#core/jobs/lock";
|
|
3
3
|
import { logger } from "#core/logging/logger";
|
|
4
|
-
import { handleSignals, onShutdown } from "
|
|
4
|
+
import { handleSignals, onShutdown } from "@tatara/core";
|
|
5
5
|
import { schedules } from "#generated/schedules";
|
|
6
6
|
|
|
7
7
|
const jobs = schedules.map((schedule) => {
|
|
@@ -21,5 +21,5 @@ const jobs = schedules.map((schedule) => {
|
|
|
21
21
|
onShutdown("cron", () => {
|
|
22
22
|
for (const job of jobs) job.stop();
|
|
23
23
|
});
|
|
24
|
-
handleSignals();
|
|
24
|
+
handleSignals(logger);
|
|
25
25
|
logger.info({ schedules: schedules.map((s) => `${s.name} (${s.cron})`) }, "cron started");
|
|
@@ -4,7 +4,7 @@ import { processJob } from "#core/jobs/process";
|
|
|
4
4
|
import { QUEUE_NAME } from "#core/jobs/queue";
|
|
5
5
|
import { redis } from "#core/jobs/redis";
|
|
6
6
|
import { logger } from "#core/logging/logger";
|
|
7
|
-
import { handleSignals, onShutdown } from "
|
|
7
|
+
import { handleSignals, onShutdown } from "@tatara/core";
|
|
8
8
|
import { consumers } from "#generated/consumers";
|
|
9
9
|
|
|
10
10
|
const worker = new Worker(
|
|
@@ -20,5 +20,5 @@ const worker = new Worker(
|
|
|
20
20
|
|
|
21
21
|
worker.on("failed", (job, err) => logger.error({ err, jobId: job?.id, topic: job?.name }, "job failed"));
|
|
22
22
|
onShutdown("worker", () => worker.close());
|
|
23
|
-
handleSignals();
|
|
23
|
+
handleSignals(logger);
|
|
24
24
|
logger.info({ topics: consumers.map((c) => c.topic) }, "worker started");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type JobsOptions, Queue } from "bullmq";
|
|
2
2
|
import { env } from "#core/config/env";
|
|
3
3
|
import { redis } from "#core/jobs/redis";
|
|
4
|
-
import { onShutdown } from "
|
|
4
|
+
import { onShutdown } from "@tatara/core";
|
|
5
5
|
|
|
6
6
|
export const QUEUE_NAME = "__APP_NAME__-jobs";
|
|
7
7
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
|
|
2
2
|
import { env } from "#core/config/env";
|
|
3
3
|
import { connectionConfig } from "#core/db/admin";
|
|
4
|
-
import { registerReadinessCheck } from "
|
|
5
|
-
import { onShutdown } from "
|
|
4
|
+
import { registerReadinessCheck } from "@tatara/core";
|
|
5
|
+
import { onShutdown } from "@tatara/core";
|
|
6
6
|
import { type Prisma, PrismaClient } from "#generated/prisma/client";
|
|
7
7
|
|
|
8
8
|
export const db = new PrismaClient({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PrismaPg } from "@prisma/adapter-pg";
|
|
2
2
|
import { env } from "#core/config/env";
|
|
3
|
-
import { registerReadinessCheck } from "
|
|
4
|
-
import { onShutdown } from "
|
|
3
|
+
import { registerReadinessCheck } from "@tatara/core";
|
|
4
|
+
import { onShutdown } from "@tatara/core";
|
|
5
5
|
import { type Prisma, PrismaClient } from "#generated/prisma/client";
|
|
6
6
|
|
|
7
7
|
export const db = new PrismaClient({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRoute } from "@hono/zod-openapi";
|
|
2
|
-
import { jsonContent, success, successSchema } from "
|
|
2
|
+
import { jsonContent, success, successSchema } from "@tatara/http";
|
|
3
3
|
import { createRouter, defineRoutes } from "#core/http/routes";
|
|
4
4
|
import { __moduleCamel__Status } from "#modules/__moduleKebab__/services/__moduleKebab__.service";
|
|
5
5
|
import { __moduleCamel__StatusSchema } from "#modules/__moduleKebab__/validators/__moduleKebab__.validators";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { count, desc, eq } from "drizzle-orm";
|
|
2
2
|
import { db, type Executor } from "#core/db/client";
|
|
3
3
|
import { newId } from "#core/db/id";
|
|
4
|
-
import { NotFoundError } from "
|
|
5
|
-
import type { PaginationQuery } from "
|
|
4
|
+
import { NotFoundError } from "@tatara/http";
|
|
5
|
+
import type { PaginationQuery } from "@tatara/http";
|
|
6
6
|
import { __namePluralCamel__ } from "#modules/__moduleKebab__/db/__nameKebab__.schema";
|
|
7
7
|
import type {
|
|
8
8
|
Create__namePascal__Input,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { db, type Executor } from "#core/db/client";
|
|
2
2
|
import { newId } from "#core/db/id";
|
|
3
|
-
import { NotFoundError } from "
|
|
4
|
-
import type { PaginationQuery } from "
|
|
3
|
+
import { NotFoundError } from "@tatara/http";
|
|
4
|
+
import type { PaginationQuery } from "@tatara/http";
|
|
5
5
|
import type {
|
|
6
6
|
Create__namePascal__Input,
|
|
7
7
|
__namePascal__,
|
package/templates/generators/route/files/src/modules/__moduleKebab__/routes/__nameKebab__.routes.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRoute } from "@hono/zod-openapi";
|
|
2
|
-
import { errorResponses, jsonContent, success, successSchema } from "
|
|
2
|
+
import { errorResponses, jsonContent, success, successSchema } from "@tatara/http";
|
|
3
3
|
import { createRouter, defineRoutes } from "#core/http/routes";
|
|
4
4
|
// tatara:slot imports
|
|
5
5
|
import {
|
package/templates/versions.json
CHANGED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import type { Logger } from "#core/logging/logger";
|
|
3
|
-
|
|
4
|
-
export interface CommandContext {
|
|
5
|
-
logger: Logger;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface CommandDefinition<S extends z.ZodType = z.ZodType> {
|
|
9
|
-
/** `module:action`, e.g. "billing:recalculate" */
|
|
10
|
-
name: string;
|
|
11
|
-
description: string;
|
|
12
|
-
/** flags parsed from `--key value`; use z.coerce for numbers and `flag()` for booleans */
|
|
13
|
-
args?: S;
|
|
14
|
-
run(args: z.infer<S>, ctx: CommandContext): Promise<void> | void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/** Default export of every `commands/*.ts`; discovered by `tatara sync`. */
|
|
19
|
-
export function defineCommand<S extends z.ZodType = typeof noArgs>(
|
|
20
|
-
definition: CommandDefinition<S>,
|
|
21
|
-
): CommandDefinition<S> {
|
|
22
|
-
return definition;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const noArgs = z.object({});
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* A boolean flag: bare `--force` is true, `--force false|0|no|off` is false.
|
|
29
|
-
* (`z.coerce.boolean()` would read the string "false" as true.)
|
|
30
|
-
*/
|
|
31
|
-
export function flag() {
|
|
32
|
-
return z.union([z.boolean(), z.stringbool()]).default(false);
|
|
33
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { CommandDefinition } from "#core/commands/define";
|
|
2
|
-
import { logger } from "#core/logging/logger";
|
|
3
|
-
|
|
4
|
-
/** Parses `--key value`, `--key=value` and bare `--flag` (true) into an object. */
|
|
5
|
-
export function parseFlags(argv: string[]): Record<string, string | boolean> {
|
|
6
|
-
const flags: Record<string, string | boolean> = {};
|
|
7
|
-
for (let i = 0; i < argv.length; i++) {
|
|
8
|
-
const arg = argv[i] ?? "";
|
|
9
|
-
if (!arg.startsWith("--")) continue;
|
|
10
|
-
const [rawKey, inline] = arg.slice(2).split("=", 2);
|
|
11
|
-
const key = (rawKey ?? "").replace(/-([a-z])/g, (_, ch: string) => ch.toUpperCase());
|
|
12
|
-
if (inline !== undefined) {
|
|
13
|
-
flags[key] = inline;
|
|
14
|
-
} else if (argv[i + 1] !== undefined && !argv[i + 1]?.startsWith("--")) {
|
|
15
|
-
flags[key] = argv[i + 1] ?? "";
|
|
16
|
-
i++;
|
|
17
|
-
} else {
|
|
18
|
-
flags[key] = true;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return flags;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function runCommand(commands: CommandDefinition[], argv: string[]): Promise<number> {
|
|
25
|
-
const [name, ...rest] = argv;
|
|
26
|
-
if (!name || name === "--list" || name === "list" || name === "help") {
|
|
27
|
-
const width = Math.max(0, ...commands.map((c) => c.name.length));
|
|
28
|
-
console.log("Commands:");
|
|
29
|
-
for (const c of [...commands].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
30
|
-
console.log(` ${c.name.padEnd(width)} ${c.description}`);
|
|
31
|
-
}
|
|
32
|
-
return 0;
|
|
33
|
-
}
|
|
34
|
-
const command = commands.find((c) => c.name === name);
|
|
35
|
-
if (!command) {
|
|
36
|
-
console.error(`Unknown command "${name}". Run with --list to see available commands.`);
|
|
37
|
-
return 1;
|
|
38
|
-
}
|
|
39
|
-
const parsed = (command.args ?? null)?.safeParse(parseFlags(rest));
|
|
40
|
-
if (parsed && !parsed.success) {
|
|
41
|
-
console.error(`Invalid arguments for ${name}:`);
|
|
42
|
-
for (const issue of parsed.error.issues) {
|
|
43
|
-
console.error(` --${issue.path.join(".")}: ${issue.message}`);
|
|
44
|
-
}
|
|
45
|
-
return 1;
|
|
46
|
-
}
|
|
47
|
-
try {
|
|
48
|
-
await command.run(parsed ? parsed.data : {}, { logger: logger.child({ command: name }) });
|
|
49
|
-
return 0;
|
|
50
|
-
} catch (error) {
|
|
51
|
-
logger.error({ err: error, command: name }, "command failed");
|
|
52
|
-
return 1;
|
|
53
|
-
}
|
|
54
|
-
}
|