@tatara/cli 0.11.0 → 0.12.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 +3122 -2946
- package/package.json +1 -1
- package/templates/features/authz/files+auth-orgs/tests/tatara/authz-org-creator.test.ts +26 -0
- package/templates/features/base/files/AGENTS.md +3 -0
- package/templates/features/email/files/src/core/email/send.ts +3 -2
- package/templates/features/notifications/files/src/modules/notifications/services/notifier.ts +3 -0
- package/templates/features/observability/files/src/apps/instrument.ts +2 -3
- package/templates/versions.json +6 -5
package/package.json
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { authz } from "#core/authz/index";
|
|
3
|
+
import { api } from "#tests/helpers/api";
|
|
4
|
+
import { authenticate } from "#tests/helpers/auth";
|
|
5
|
+
import { resetDatabase } from "#tests/helpers/db";
|
|
6
|
+
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
await resetDatabase();
|
|
9
|
+
await authz.syncSystemRoles();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("an organization's creator", () => {
|
|
13
|
+
it("is its admin", async () => {
|
|
14
|
+
const user = await authenticate();
|
|
15
|
+
const created = await api.post<{ id: string }>(
|
|
16
|
+
"/api/auth/organization/create",
|
|
17
|
+
{ name: "New School", slug: `new-${crypto.randomUUID()}` },
|
|
18
|
+
{ headers: user.headers },
|
|
19
|
+
);
|
|
20
|
+
expect(created.status).toBe(200);
|
|
21
|
+
const me = await api.get<{ data: { roles: string[] } }>("/api/authorization/me", {
|
|
22
|
+
headers: { ...user.headers, "x-org-id": created.body.id },
|
|
23
|
+
});
|
|
24
|
+
expect(me.body.data.roles).toContain("admin");
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -41,6 +41,7 @@ __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
43
|
__PM_EXEC__ tatara upgrade # after bumping @tatara/cli: run its codemods, sync, verify
|
|
44
|
+
__PM_EXEC__ tatara add <add-on…> # add jobs, cache, realtime… to this app later
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
## HTTP
|
|
@@ -73,6 +74,8 @@ __PM_EXEC__ tatara upgrade # after bumping @tatara/cli: run its co
|
|
|
73
74
|
Never `Math.random()` for anything a user could guess.
|
|
74
75
|
- User text: `text({ max })` and `url()` from `@tatara/core` in validators clean and check it (control,
|
|
75
76
|
invisible and bidi characters, whitespace; only http(s) links). Escape when you render HTML.
|
|
77
|
+
Rich text users write: `sanitizeHtml(html)` (or `"email"` for templates) from `@tatara/core/html`
|
|
78
|
+
once `sanitize-html` is a dependency.
|
|
76
79
|
|
|
77
80
|
## Deploying
|
|
78
81
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createMailer, renderEmail } from "@tatara/email";
|
|
1
|
+
import { createMailer, type EmailDefinition, renderEmail } from "@tatara/email";
|
|
2
2
|
import { env } from "#core/config/env";
|
|
3
3
|
import { logger } from "#core/logging/logger";
|
|
4
4
|
import type { emails } from "#generated/emails";
|
|
@@ -27,7 +27,8 @@ export async function sendTemplate<N extends keyof Templates & string>(
|
|
|
27
27
|
): Promise<void> {
|
|
28
28
|
// loaded on first use: core doesn't import module code until it's needed
|
|
29
29
|
const { emails: all } = await import("#generated/emails");
|
|
30
|
-
|
|
30
|
+
// read as any template: an app without templates has an empty list
|
|
31
|
+
const template = (all as readonly EmailDefinition[]).find((e) => e.name === name);
|
|
31
32
|
if (!template) throw new Error(`No email template named "${name}"`);
|
|
32
33
|
await sendEmail({ to, ...renderEmail(template, data, language) });
|
|
33
34
|
}
|
package/templates/features/notifications/files/src/modules/notifications/services/notifier.ts
CHANGED
|
@@ -52,6 +52,9 @@ export const { notify, consumers } = createNotifier<NotificationsOf<typeof notif
|
|
|
52
52
|
? { email: user.email ?? null, phone: user.phoneNumber ?? null, language: user.language ?? null }
|
|
53
53
|
: null;
|
|
54
54
|
},
|
|
55
|
+
// who gets what is yours to decide: preferences, quiet hours… ({ userId, notification, channel,
|
|
56
|
+
// data }); false skips that channel for that user
|
|
57
|
+
allow: () => true,
|
|
55
58
|
sms: sms(),
|
|
56
59
|
push: push(),
|
|
57
60
|
// tatara:slot senders
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
// Loaded before the app (`node --import ./dist/apps/instrument.mjs …`, set in the Dockerfile):
|
|
2
2
|
// instrumentation can only patch libraries imported after it, so app code comes in dynamically.
|
|
3
|
-
import { register } from "node:module";
|
|
4
|
-
|
|
5
3
|
const { env } = await import("#core/config/env");
|
|
6
4
|
|
|
7
5
|
if (env.OTEL_EXPORTER_OTLP_ENDPOINT) {
|
|
8
|
-
|
|
6
|
+
const { registerInstrumentationHooks } = await import("@tatara/observability/hooks");
|
|
7
|
+
await registerInstrumentationHooks();
|
|
9
8
|
const { startTelemetry } = await import("@tatara/observability");
|
|
10
9
|
startTelemetry({
|
|
11
10
|
serviceName: env.OTEL_SERVICE_NAME,
|
package/templates/versions.json
CHANGED
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
"@aws-sdk/s3-request-presigner": "3.1152.0",
|
|
32
32
|
"nodemailer": "10.1.1",
|
|
33
33
|
"@types/nodemailer": "8.0.2",
|
|
34
|
-
"@tatara/core": "0.1.
|
|
34
|
+
"@tatara/core": "0.1.3",
|
|
35
35
|
"@tatara/http": "0.1.2",
|
|
36
36
|
"@tatara/db": "0.2.2",
|
|
37
37
|
"@tatara/testing": "0.1.1",
|
|
38
38
|
"@tatara/auth": "0.2.0",
|
|
39
39
|
"@tatara/keycloak": "0.1.2",
|
|
40
|
-
"@tatara/authz": "0.
|
|
40
|
+
"@tatara/authz": "0.4.0",
|
|
41
41
|
"@tatara/storage": "0.2.0",
|
|
42
42
|
"@tatara/email": "0.3.0",
|
|
43
43
|
"@tatara/i18n": "0.1.0",
|
|
@@ -45,13 +45,13 @@
|
|
|
45
45
|
"@tatara/cache": "0.1.0",
|
|
46
46
|
"openapi-typescript": "7.13.0",
|
|
47
47
|
"kafkajs": "2.2.4",
|
|
48
|
-
"@tatara/notifications": "0.
|
|
48
|
+
"@tatara/notifications": "0.2.0",
|
|
49
49
|
"@tatara/realtime": "0.1.0",
|
|
50
50
|
"socket.io": "4.8.4",
|
|
51
51
|
"socket.io-client": "4.8.4",
|
|
52
52
|
"@socket.io/redis-adapter": "8.3.0",
|
|
53
53
|
"@socket.io/redis-emitter": "5.1.0",
|
|
54
|
-
"@tatara/observability": "0.
|
|
54
|
+
"@tatara/observability": "0.2.0",
|
|
55
55
|
"@opentelemetry/api": "1.10.0",
|
|
56
56
|
"@opentelemetry/sdk-node": "0.300.0",
|
|
57
57
|
"@opentelemetry/sdk-trace": "3.0.0",
|
|
@@ -61,5 +61,6 @@
|
|
|
61
61
|
"@opentelemetry/exporter-metrics-otlp-http": "0.300.0",
|
|
62
62
|
"@opentelemetry/auto-instrumentations-node": "0.82.0",
|
|
63
63
|
"@opentelemetry/instrumentation": "0.300.0",
|
|
64
|
-
"@hono/otel": "1.3.0"
|
|
64
|
+
"@hono/otel": "1.3.0",
|
|
65
|
+
"import-in-the-middle": "3.5.3"
|
|
65
66
|
}
|