@tatara/cli 0.2.0 → 0.4.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 +221 -19
- 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/tests/keycloak-tokens.test.ts +1 -1
- package/templates/features/auth-keycloak/files+auth-orgs/src/core/auth/tests/keycloak-organizations.test.ts +1 -1
- package/templates/features/authz/files/src/core/authz/db/seed.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/authz/files+drizzle/src/core/authz/db/store.ts +1 -1
- package/templates/features/authz/files+prisma/src/core/authz/db/store.ts +1 -1
- package/templates/features/base/files/src/apps/api/server.ts +3 -2
- package/templates/features/base/files/src/core/http/app.ts +22 -41
- package/templates/features/base/files/src/core/http/routes.ts +7 -11
- package/templates/features/base/files/src/modules/example/routes/greeting.routes.ts +1 -1
- package/templates/features/base/files/tests/helpers/api.ts +3 -47
- package/templates/features/better-auth/files/src/core/auth/auth.ts +1 -1
- package/templates/features/better-auth/files/src/core/auth/db/seed.ts +1 -1
- package/templates/features/better-auth/files/tests/helpers/auth.ts +1 -1
- package/templates/features/db/files/tests/global-setup.ts +4 -16
- package/templates/features/db/files+mysql+drizzle/src/core/db/admin.ts +2 -53
- package/templates/features/db/files+mysql+prisma/src/core/db/admin.ts +7 -78
- package/templates/features/db/files+postgres/src/core/db/admin.ts +2 -50
- package/templates/features/drizzle/files+mysql/src/core/db/migrate.ts +1 -13
- package/templates/features/drizzle/files+postgres/src/core/db/migrate.ts +1 -13
- package/templates/features/prisma/files/src/core/db/migrate.ts +1 -13
- 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 +3 -3
- package/templates/generators/resource/files+prisma/src/modules/__moduleKebab__/services/__nameKebab__.service.ts +3 -3
- package/templates/generators/route/files/src/modules/__moduleKebab__/routes/__nameKebab__.routes.ts +1 -1
- package/templates/versions.json +4 -1
- package/templates/features/base/files/src/core/http/errors.ts +0 -79
- 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/db/files/src/core/db/id.ts +0 -13
- package/templates/features/db/files/src/core/db/seed.ts +0 -14
- package/templates/features/db/files/tests/helpers/database-url.ts +0 -9
package/dist/bin.mjs
CHANGED
|
@@ -286,7 +286,7 @@ export type PermissionKey =
|
|
|
286
286
|
const seeds = await find(root, dirs.map((d) => `${d}/db/seed.ts`));
|
|
287
287
|
await writeIfChanged(join(out, "seeds.ts"), listModule(seeds, {
|
|
288
288
|
prefix: "seed",
|
|
289
|
-
typeImport: "import type { SeedDefinition } from \"
|
|
289
|
+
typeImport: "import type { SeedDefinition } from \"@tatara/db\";",
|
|
290
290
|
typeName: "SeedDefinition",
|
|
291
291
|
exportName: "seeds"
|
|
292
292
|
}), written);
|
|
@@ -1051,14 +1051,14 @@ const i18n = {
|
|
|
1051
1051
|
content: "app.use(detectLanguage);"
|
|
1052
1052
|
},
|
|
1053
1053
|
{
|
|
1054
|
-
file: "src/core/http/
|
|
1054
|
+
file: "src/core/http/app.ts",
|
|
1055
1055
|
slot: "imports",
|
|
1056
1056
|
content: "import { t } from \"#core/i18n/index\";"
|
|
1057
1057
|
},
|
|
1058
1058
|
{
|
|
1059
|
-
file: "src/core/http/
|
|
1060
|
-
slot: "error-
|
|
1061
|
-
content: "
|
|
1059
|
+
file: "src/core/http/app.ts",
|
|
1060
|
+
slot: "error-options",
|
|
1061
|
+
content: ["message: (err, c) =>", " err.translatable ? t(c.get(\"language\"), `errors.${err.code}`, err.message) : err.message,"].join("\n")
|
|
1062
1062
|
},
|
|
1063
1063
|
{
|
|
1064
1064
|
file: "AGENTS.md",
|
|
@@ -1170,8 +1170,17 @@ const betterAuth = {
|
|
|
1170
1170
|
},
|
|
1171
1171
|
{
|
|
1172
1172
|
file: "src/core/http/app.ts",
|
|
1173
|
-
slot: "
|
|
1174
|
-
content: "
|
|
1173
|
+
slot: "expose-headers",
|
|
1174
|
+
content: "\"set-auth-token\","
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
file: "src/core/http/app.ts",
|
|
1178
|
+
slot: "openapi-options",
|
|
1179
|
+
content: [
|
|
1180
|
+
"async extend(document) {",
|
|
1181
|
+
" if (name === \"api\") await mergeAuthOpenApi(document, `${API_PREFIX}/auth`);",
|
|
1182
|
+
"},"
|
|
1183
|
+
].join("\n")
|
|
1175
1184
|
},
|
|
1176
1185
|
{
|
|
1177
1186
|
file: "src/core/http/app.ts",
|
|
@@ -1640,6 +1649,7 @@ const base = {
|
|
|
1640
1649
|
label: "Hono API",
|
|
1641
1650
|
dependencies: [
|
|
1642
1651
|
"@tatara/core",
|
|
1652
|
+
"@tatara/http",
|
|
1643
1653
|
"hono",
|
|
1644
1654
|
"@hono/node-server",
|
|
1645
1655
|
"@hono/zod-openapi",
|
|
@@ -1649,6 +1659,7 @@ const base = {
|
|
|
1649
1659
|
"pino"
|
|
1650
1660
|
],
|
|
1651
1661
|
devDependencies: [
|
|
1662
|
+
"@tatara/testing",
|
|
1652
1663
|
"@tatara/cli",
|
|
1653
1664
|
"typescript",
|
|
1654
1665
|
"@types/node",
|
|
@@ -1846,6 +1857,7 @@ const db$1 = {
|
|
|
1846
1857
|
id: "db",
|
|
1847
1858
|
kind: "database",
|
|
1848
1859
|
label: "Database (shared)",
|
|
1860
|
+
dependencies: ["@tatara/db"],
|
|
1849
1861
|
scripts: {
|
|
1850
1862
|
"db:generate": "tatara db generate",
|
|
1851
1863
|
"db:migrate": "tatara db migrate",
|
|
@@ -1868,7 +1880,7 @@ const db$1 = {
|
|
|
1868
1880
|
file: "tests/setup.ts",
|
|
1869
1881
|
slot: "setup",
|
|
1870
1882
|
content: [
|
|
1871
|
-
"import { workerDatabaseUrl } from \"
|
|
1883
|
+
"import { workerDatabaseUrl } from \"@tatara/testing\";",
|
|
1872
1884
|
"",
|
|
1873
1885
|
"// One database per worker, created by tests/global-setup.ts.",
|
|
1874
1886
|
"process.env.DATABASE_URL = workerDatabaseUrl(process.env.DATABASE_URL ?? \"\", process.env.VITEST_POOL_ID ?? \"1\");"
|
|
@@ -3212,7 +3224,7 @@ function createBaseline(project, deps) {
|
|
|
3212
3224
|
//#region src/upgrade/codemods/core-package.ts
|
|
3213
3225
|
const CORE = "@tatara/core";
|
|
3214
3226
|
/** Files @tatara/core replaces, with the import specifier apps used for each. */
|
|
3215
|
-
const MOVED = {
|
|
3227
|
+
const MOVED$2 = {
|
|
3216
3228
|
"src/core/commands/define.ts": "#core/commands/define",
|
|
3217
3229
|
"src/core/commands/runner.ts": "#core/commands/runner",
|
|
3218
3230
|
"src/core/process/lifecycle.ts": "#core/process/lifecycle",
|
|
@@ -3247,8 +3259,8 @@ async function rewriteEnv(ctx) {
|
|
|
3247
3259
|
}
|
|
3248
3260
|
/** Only call sites of functions that now come from @tatara/core take the logger argument. */
|
|
3249
3261
|
async function passLogger(ctx, gone) {
|
|
3250
|
-
const runnerGone = gone.includes(MOVED["src/core/commands/runner.ts"]);
|
|
3251
|
-
const lifecycleGone = gone.includes(MOVED["src/core/process/lifecycle.ts"]);
|
|
3262
|
+
const runnerGone = gone.includes(MOVED$2["src/core/commands/runner.ts"]);
|
|
3263
|
+
const lifecycleGone = gone.includes(MOVED$2["src/core/process/lifecycle.ts"]);
|
|
3252
3264
|
const cli = await exists(ctx, CLI_ENTRY);
|
|
3253
3265
|
if (cli) {
|
|
3254
3266
|
let next = cli;
|
|
@@ -3294,17 +3306,14 @@ export const logger = pino(
|
|
|
3294
3306
|
export type Logger = typeof logger;
|
|
3295
3307
|
`);
|
|
3296
3308
|
}
|
|
3297
|
-
|
|
3298
|
-
//#region src/upgrade/codemods.ts
|
|
3299
|
-
/** Every codemod tatara ships, oldest first. Each step that moves code into a package adds one. */
|
|
3300
|
-
const CODEMODS = [{
|
|
3309
|
+
const corePackage = {
|
|
3301
3310
|
version: "0.2.0",
|
|
3302
3311
|
id: "core-package",
|
|
3303
3312
|
description: "Use @tatara/core for commands, flags, lifecycle, readiness, logger options and localDefault",
|
|
3304
3313
|
async run(ctx) {
|
|
3305
3314
|
await ctx.addDependency(CORE, versionOf(CORE));
|
|
3306
3315
|
const gone = [];
|
|
3307
|
-
for (const [path, specifier] of Object.entries(MOVED)) if (await ctx.removeIfUnchanged(path) === "kept") ctx.report.notes.push(`${path} is customised; its imports still point at it. Move the change into the app or eject.`);
|
|
3316
|
+
for (const [path, specifier] of Object.entries(MOVED$2)) if (await ctx.removeIfUnchanged(path) === "kept") ctx.report.notes.push(`${path} is customised; its imports still point at it. Move the change into the app or eject.`);
|
|
3308
3317
|
else gone.push(specifier);
|
|
3309
3318
|
for (const specifier of gone) await ctx.rewriteImports(specifier, CORE);
|
|
3310
3319
|
await rewriteEnv(ctx);
|
|
@@ -3312,7 +3321,181 @@ const CODEMODS = [{
|
|
|
3312
3321
|
await allowFreshReleases(ctx);
|
|
3313
3322
|
await useLoggerOptions(ctx);
|
|
3314
3323
|
}
|
|
3315
|
-
}
|
|
3324
|
+
};
|
|
3325
|
+
//#endregion
|
|
3326
|
+
//#region src/upgrade/codemods/db-testing.ts
|
|
3327
|
+
/** Copies replaced by a package export of the same name. */
|
|
3328
|
+
const MOVED$1 = [
|
|
3329
|
+
{
|
|
3330
|
+
path: "src/core/db/id.ts",
|
|
3331
|
+
specifier: "#core/db/id",
|
|
3332
|
+
to: "@tatara/db",
|
|
3333
|
+
needsDb: true
|
|
3334
|
+
},
|
|
3335
|
+
{
|
|
3336
|
+
path: "src/core/db/seed.ts",
|
|
3337
|
+
specifier: "#core/db/seed",
|
|
3338
|
+
to: "@tatara/db",
|
|
3339
|
+
needsDb: true
|
|
3340
|
+
},
|
|
3341
|
+
{
|
|
3342
|
+
path: "tests/helpers/database-url.ts",
|
|
3343
|
+
specifier: "#tests/helpers/database-url",
|
|
3344
|
+
to: "@tatara/testing",
|
|
3345
|
+
needsDb: false
|
|
3346
|
+
}
|
|
3347
|
+
];
|
|
3348
|
+
/** App files that stay, but become what this CLI generates (driver re-exports, thin helpers). */
|
|
3349
|
+
const REGENERATED = [
|
|
3350
|
+
"src/core/db/admin.ts",
|
|
3351
|
+
"src/core/db/migrate.ts",
|
|
3352
|
+
"tests/helpers/api.ts",
|
|
3353
|
+
"tests/global-setup.ts"
|
|
3354
|
+
];
|
|
3355
|
+
async function read$1(ctx, path) {
|
|
3356
|
+
return ctx.read(path).catch(() => void 0);
|
|
3357
|
+
}
|
|
3358
|
+
const dbTesting = {
|
|
3359
|
+
version: "0.4.0",
|
|
3360
|
+
id: "db-testing",
|
|
3361
|
+
description: "Use @tatara/db (ids, seeds, driver helpers, migrations) and @tatara/testing",
|
|
3362
|
+
async run(ctx) {
|
|
3363
|
+
const hasDb = ctx.project.config.database !== "none";
|
|
3364
|
+
if (hasDb) await ctx.addDependency("@tatara/db", versionOf("@tatara/db"));
|
|
3365
|
+
await ctx.addDependency("@tatara/testing", versionOf("@tatara/testing"), { dev: true });
|
|
3366
|
+
const regenerate = [];
|
|
3367
|
+
for (const path of REGENERATED) {
|
|
3368
|
+
const text = await read$1(ctx, path);
|
|
3369
|
+
if (text === void 0) continue;
|
|
3370
|
+
if (await ctx.baseline.isUnchanged(path, text)) regenerate.push(path);
|
|
3371
|
+
else ctx.report.notes.push(`${path} is customised and was kept; compare it with a newly generated app.`);
|
|
3372
|
+
}
|
|
3373
|
+
const moved = MOVED$1.filter((m) => hasDb || !m.needsDb);
|
|
3374
|
+
for (const path of regenerate) {
|
|
3375
|
+
const next = await ctx.renderCurrent(path);
|
|
3376
|
+
if (next !== void 0) await ctx.write(path, next);
|
|
3377
|
+
}
|
|
3378
|
+
for (const entry of moved) {
|
|
3379
|
+
if (await ctx.removeIfUnchanged(entry.path) === "kept") {
|
|
3380
|
+
ctx.report.notes.push(`${entry.path} is customised; its imports still point at it.`);
|
|
3381
|
+
continue;
|
|
3382
|
+
}
|
|
3383
|
+
await ctx.rewriteImports(entry.specifier, entry.to);
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
};
|
|
3387
|
+
//#endregion
|
|
3388
|
+
//#region src/upgrade/codemods/http-package.ts
|
|
3389
|
+
const HTTP = "@tatara/http";
|
|
3390
|
+
/** Wiring files the app keeps; replaced by what this CLI generates when the app never changed them. */
|
|
3391
|
+
const APP = "src/core/http/app.ts";
|
|
3392
|
+
const ROUTES = "src/core/http/routes.ts";
|
|
3393
|
+
const SERVERS = ["src/apps/api/server.ts", "src/apps/admin/server.ts"];
|
|
3394
|
+
/** Files @tatara/http replaces: import specifier, and the wiring files that import them. */
|
|
3395
|
+
const MOVED = [
|
|
3396
|
+
{
|
|
3397
|
+
path: "src/core/http/errors.ts",
|
|
3398
|
+
specifier: "#core/http/errors",
|
|
3399
|
+
usedBy: []
|
|
3400
|
+
},
|
|
3401
|
+
{
|
|
3402
|
+
path: "src/core/http/response.ts",
|
|
3403
|
+
specifier: "#core/http/response",
|
|
3404
|
+
usedBy: []
|
|
3405
|
+
},
|
|
3406
|
+
{
|
|
3407
|
+
path: "src/core/http/middleware/validation.ts",
|
|
3408
|
+
specifier: "#core/http/middleware/validation",
|
|
3409
|
+
usedBy: [APP, ROUTES]
|
|
3410
|
+
},
|
|
3411
|
+
{
|
|
3412
|
+
path: "src/core/http/middleware/error-handler.ts",
|
|
3413
|
+
specifier: "#core/http/middleware/error-handler",
|
|
3414
|
+
usedBy: [APP]
|
|
3415
|
+
},
|
|
3416
|
+
{
|
|
3417
|
+
path: "src/core/http/middleware/request-context.ts",
|
|
3418
|
+
specifier: "#core/http/middleware/request-context",
|
|
3419
|
+
usedBy: [APP]
|
|
3420
|
+
},
|
|
3421
|
+
{
|
|
3422
|
+
path: "src/core/http/server.ts",
|
|
3423
|
+
specifier: "#core/http/server",
|
|
3424
|
+
usedBy: SERVERS
|
|
3425
|
+
}
|
|
3426
|
+
];
|
|
3427
|
+
async function read(ctx, path) {
|
|
3428
|
+
return ctx.read(path).catch(() => void 0);
|
|
3429
|
+
}
|
|
3430
|
+
/** Files outside the group that import one of the moved files (they would break). */
|
|
3431
|
+
async function outsideImporters(ctx) {
|
|
3432
|
+
const group = /* @__PURE__ */ new Set([
|
|
3433
|
+
APP,
|
|
3434
|
+
ROUTES,
|
|
3435
|
+
...SERVERS,
|
|
3436
|
+
...MOVED.map((m) => m.path)
|
|
3437
|
+
]);
|
|
3438
|
+
const specifiers = MOVED.filter((m) => m.usedBy.length > 0).map((m) => m.specifier);
|
|
3439
|
+
const pattern = new RegExp(`["'](${specifiers.map((s) => s.replace(/[/.]/g, "\\$&")).join("|")})["']`);
|
|
3440
|
+
const out = [];
|
|
3441
|
+
for (const path of await glob(["src/**/*.ts", "tests/**/*.ts"], {
|
|
3442
|
+
cwd: ctx.project.root,
|
|
3443
|
+
ignore: ["src/generated/**"]
|
|
3444
|
+
})) {
|
|
3445
|
+
if (group.has(path)) continue;
|
|
3446
|
+
if (pattern.test(await ctx.read(path))) out.push(path);
|
|
3447
|
+
}
|
|
3448
|
+
return out;
|
|
3449
|
+
}
|
|
3450
|
+
//#endregion
|
|
3451
|
+
//#region src/upgrade/codemods.ts
|
|
3452
|
+
/** Every codemod tatara ships, oldest first. Each step that moves code into a package adds one. */
|
|
3453
|
+
const CODEMODS = [
|
|
3454
|
+
corePackage,
|
|
3455
|
+
{
|
|
3456
|
+
version: "0.3.0",
|
|
3457
|
+
id: "http-package",
|
|
3458
|
+
description: "Use @tatara/http for app setup, errors, the envelope, OpenAPI docs and the server",
|
|
3459
|
+
async run(ctx) {
|
|
3460
|
+
await ctx.addDependency(HTTP, versionOf(HTTP));
|
|
3461
|
+
const blockers = [];
|
|
3462
|
+
const replace = [];
|
|
3463
|
+
for (const path of [
|
|
3464
|
+
APP,
|
|
3465
|
+
ROUTES,
|
|
3466
|
+
...SERVERS
|
|
3467
|
+
]) {
|
|
3468
|
+
const text = await read(ctx, path);
|
|
3469
|
+
if (text === void 0) continue;
|
|
3470
|
+
if (await ctx.baseline.isUnchanged(path, text)) replace.push(path);
|
|
3471
|
+
else blockers.push(`${path} is customised`);
|
|
3472
|
+
}
|
|
3473
|
+
const present = [];
|
|
3474
|
+
for (const entry of MOVED) {
|
|
3475
|
+
const text = await read(ctx, entry.path);
|
|
3476
|
+
if (text === void 0) continue;
|
|
3477
|
+
present.push(entry);
|
|
3478
|
+
if (!await ctx.baseline.isUnchanged(entry.path, text)) blockers.push(`${entry.path} is customised`);
|
|
3479
|
+
}
|
|
3480
|
+
for (const path of await outsideImporters(ctx)) blockers.push(`${path} imports one of the moved files`);
|
|
3481
|
+
if (blockers.length > 0) {
|
|
3482
|
+
ctx.report.notes.push(`Nothing in src/core/http was changed: ${blockers.join("; ")}. Move these onto @tatara/http (createApp, mountRoutes, serveOpenApi, handleErrors, startServer and its error classes) by hand, then delete the old files.`);
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
for (const path of replace) {
|
|
3486
|
+
const next = await ctx.renderCurrent(path);
|
|
3487
|
+
if (next !== void 0) await ctx.write(path, next);
|
|
3488
|
+
}
|
|
3489
|
+
for (const entry of present) await ctx.removeIfUnchanged(entry.path);
|
|
3490
|
+
for (const specifier of [
|
|
3491
|
+
"#core/http/errors",
|
|
3492
|
+
"#core/http/response",
|
|
3493
|
+
"#core/http/middleware/validation"
|
|
3494
|
+
]) await ctx.rewriteImports(specifier, HTTP);
|
|
3495
|
+
}
|
|
3496
|
+
},
|
|
3497
|
+
dbTesting
|
|
3498
|
+
];
|
|
3316
3499
|
//#endregion
|
|
3317
3500
|
//#region src/upgrade/context.ts
|
|
3318
3501
|
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -3320,13 +3503,19 @@ function createContext(project, baseline, opts = {}) {
|
|
|
3320
3503
|
const report = {
|
|
3321
3504
|
ran: [],
|
|
3322
3505
|
written: [],
|
|
3506
|
+
regenerated: [],
|
|
3323
3507
|
removed: [],
|
|
3324
3508
|
kept: [],
|
|
3325
3509
|
notes: []
|
|
3326
3510
|
};
|
|
3327
3511
|
const abs = (path) => join(project.root, path);
|
|
3512
|
+
let rendered;
|
|
3328
3513
|
const write = async (path, text) => {
|
|
3329
|
-
if (!report.written.includes(path))
|
|
3514
|
+
if (!report.written.includes(path)) {
|
|
3515
|
+
report.written.push(path);
|
|
3516
|
+
const before = existsSync(abs(path)) ? await readFile(abs(path), "utf8") : void 0;
|
|
3517
|
+
if (before === void 0 || await baseline.isUnchanged(path, before)) report.regenerated.push(path);
|
|
3518
|
+
}
|
|
3330
3519
|
if (opts.dryRun) return;
|
|
3331
3520
|
await mkdir(dirname(abs(path)), { recursive: true });
|
|
3332
3521
|
await writeFile(abs(path), text);
|
|
@@ -3368,6 +3557,19 @@ function createContext(project, baseline, opts = {}) {
|
|
|
3368
3557
|
}
|
|
3369
3558
|
return changed;
|
|
3370
3559
|
},
|
|
3560
|
+
async renderCurrent(path) {
|
|
3561
|
+
const { name, orm, database, auth, authMethods, addons, packageManager } = project.config;
|
|
3562
|
+
rendered ??= renderApp({
|
|
3563
|
+
name,
|
|
3564
|
+
orm,
|
|
3565
|
+
database,
|
|
3566
|
+
auth,
|
|
3567
|
+
authMethods,
|
|
3568
|
+
addons,
|
|
3569
|
+
packageManager
|
|
3570
|
+
});
|
|
3571
|
+
return (await rendered).get(path);
|
|
3572
|
+
},
|
|
3371
3573
|
async addDependency(name, range, { dev = false } = {}) {
|
|
3372
3574
|
const pkg = JSON.parse(await readFile(abs("package.json"), "utf8"));
|
|
3373
3575
|
const key = dev ? "devDependencies" : "dependencies";
|
|
@@ -3401,7 +3603,7 @@ async function nextRecord(project, report) {
|
|
|
3401
3603
|
const record = { ...project.config.generated?.files ?? {} };
|
|
3402
3604
|
for (const path of report.removed) delete record[path];
|
|
3403
3605
|
const current = await hashTrackedFiles(project.root);
|
|
3404
|
-
for (const path of report.
|
|
3606
|
+
for (const path of report.regenerated) {
|
|
3405
3607
|
const hash = current[path];
|
|
3406
3608
|
if (hash) record[path] = hash;
|
|
3407
3609
|
}
|
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
|
|
@@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it } from "vitest";
|
|
|
3
3
|
import { auth } from "#core/auth/auth";
|
|
4
4
|
import { issuer } from "#core/auth/keycloak";
|
|
5
5
|
import { env } from "#core/config/env";
|
|
6
|
-
import { newId } from "
|
|
6
|
+
import { newId } from "@tatara/db";
|
|
7
7
|
import { api } from "#tests/helpers/api";
|
|
8
8
|
import { createTestUser, keycloakToken } from "#tests/helpers/auth";
|
|
9
9
|
import { resetDatabase } from "#tests/helpers/db";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
2
|
import { auth } from "#core/auth/auth";
|
|
3
|
-
import { newId } from "
|
|
3
|
+
import { newId } from "@tatara/db";
|
|
4
4
|
import { api } from "#tests/helpers/api";
|
|
5
5
|
import { keycloakToken } from "#tests/helpers/auth";
|
|
6
6
|
import { resetDatabase } from "#tests/helpers/db";
|
|
@@ -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;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "#core/authz/db/authz.schema";
|
|
11
11
|
import type { AuthzStore, OverlayRow, PermissionGrant, RoleRow } from "#core/authz/types";
|
|
12
12
|
import { db } from "#core/db/client";
|
|
13
|
-
import { newId } from "
|
|
13
|
+
import { newId } from "@tatara/db";
|
|
14
14
|
|
|
15
15
|
const toScope = (scope: string): Scope | null => (scope === "" ? null : (scope as Scope));
|
|
16
16
|
|
|
@@ -2,7 +2,7 @@ import type { OverlayEffect, Scope } from "#core/authz/constants";
|
|
|
2
2
|
import { SYSTEM_ORG_ID } from "#core/authz/constants";
|
|
3
3
|
import type { AuthzStore, OverlayRow, PermissionGrant, RoleRow } from "#core/authz/types";
|
|
4
4
|
import { db } from "#core/db/client";
|
|
5
|
-
import { newId } from "
|
|
5
|
+
import { newId } from "@tatara/db";
|
|
6
6
|
|
|
7
7
|
const toScope = (scope: string): Scope | null => (scope === "" ? null : (scope as Scope));
|
|
8
8
|
|
|
@@ -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,57 +1,38 @@
|
|
|
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 "@tatara/core";
|
|
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
|
-
app
|
|
48
|
-
|
|
49
|
-
// tatara:slot openapi
|
|
50
|
-
return c.json(document);
|
|
29
|
+
mountRoutes(app, routes, { app: name, prefix: API_PREFIX });
|
|
30
|
+
serveOpenApi(app, {
|
|
31
|
+
title: options.title,
|
|
32
|
+
// tatara:slot openapi-options
|
|
33
|
+
});
|
|
34
|
+
handleErrors(app, {
|
|
35
|
+
// tatara:slot error-options
|
|
51
36
|
});
|
|
52
|
-
app.get("/docs", Scalar({ url: "/openapi.json", pageTitle: options.title }));
|
|
53
|
-
|
|
54
|
-
app.onError(errorHandler);
|
|
55
|
-
app.notFound(notFoundHandler);
|
|
56
37
|
return app;
|
|
57
38
|
}
|
|
@@ -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,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 {
|