create-m5kdev 0.32.8 → 0.34.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.
Files changed (152) hide show
  1. package/dist/src/catalog.cjs +76 -11
  2. package/dist/src/catalog.cjs.map +1 -1
  3. package/dist/src/catalog.d.cts +13 -1
  4. package/dist/src/create.cjs +6 -2
  5. package/dist/src/create.cjs.map +1 -1
  6. package/dist/src/doctor.cjs +13 -7
  7. package/dist/src/doctor.cjs.map +1 -1
  8. package/dist/src/fs.cjs +10 -5
  9. package/dist/src/fs.cjs.map +1 -1
  10. package/dist/src/prompts.cjs +32 -0
  11. package/dist/src/prompts.cjs.map +1 -1
  12. package/dist/src/prompts.d.cts +7 -1
  13. package/dist/src/template.cjs +23 -4
  14. package/dist/src/template.cjs.map +1 -1
  15. package/dist/src/template.d.cts +17 -3
  16. package/dist/src/types.d.cts +11 -4
  17. package/package.json +4 -1
  18. package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +1 -1
  19. package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
  20. package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
  21. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
  22. package/templates/minimal-app/.dockerignore.tpl +31 -0
  23. package/templates/minimal-app/.gitignore.tpl +1 -0
  24. package/templates/minimal-app/AGENTS.md.tpl +13 -10
  25. package/templates/minimal-app/CLAUDE.md.tpl +132 -131
  26. package/templates/minimal-app/README.md.tpl +50 -19
  27. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
  28. package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
  29. package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
  30. package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
  31. package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
  32. package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
  33. package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
  34. package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
  35. package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
  36. package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
  37. package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
  38. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
  39. package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
  40. package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
  41. package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
  42. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  43. package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
  44. package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
  45. package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
  46. package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
  47. package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
  48. package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
  49. package/templates/minimal-app/apps/expo/package.json.tpl +59 -59
  50. package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
  51. package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
  52. package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
  53. package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
  54. package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
  55. package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
  56. package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
  57. package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
  58. package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
  59. package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
  60. package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
  61. package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
  62. package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
  63. package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
  64. package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
  65. package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
  66. package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
  67. package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
  68. package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
  69. package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
  70. package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
  71. package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
  72. package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
  73. package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
  74. package/templates/minimal-app/apps/landing/scripts/fly-deploy.mjs.tpl +75 -0
  75. package/templates/minimal-app/apps/landing/scripts/fly-secrets.mjs.tpl +31 -0
  76. package/templates/minimal-app/apps/landing/src/App.tsx.tpl +12 -0
  77. package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +37 -0
  78. package/templates/minimal-app/apps/landing/src/index.css.tpl +24 -0
  79. package/templates/minimal-app/apps/landing/src/main.tsx.tpl +16 -0
  80. package/templates/minimal-app/apps/landing/src/vite-env.d.ts.tpl +11 -0
  81. package/templates/minimal-app/apps/landing/tsconfig.json.tpl +12 -0
  82. package/templates/minimal-app/apps/landing/vite.config.ts.tpl +25 -0
  83. package/templates/minimal-app/apps/server/AGENTS.md.tpl +54 -46
  84. package/templates/minimal-app/apps/server/{drizzle/seed.e2e.ts.tpl → db.e2e.ts.tpl} +57 -40
  85. package/templates/minimal-app/apps/server/{drizzle/seed.ts.tpl → db.ts.tpl} +27 -20
  86. package/templates/minimal-app/apps/server/drizzle/0002_concerned_proudstar.sql +3 -0
  87. package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +27 -0
  88. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +56 -166
  89. package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +1796 -1910
  90. package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1818 -0
  91. package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2137 -0
  92. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +15 -1
  93. package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
  94. package/templates/minimal-app/apps/server/package.json.tpl +57 -56
  95. package/templates/minimal-app/apps/server/src/app.ts.tpl +149 -141
  96. package/templates/minimal-app/apps/server/src/index.ts.tpl +12 -65
  97. package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +26 -0
  98. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl +47 -0
  99. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +43 -0
  100. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.trpc.ts.tpl +16 -0
  101. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +28 -28
  102. package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +28 -30
  103. package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +54 -54
  104. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -67
  105. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +116 -115
  106. package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -37
  107. package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -279
  108. package/templates/minimal-app/apps/server/src/schema.ts.tpl +31 -20
  109. package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -1
  110. package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -1
  111. package/templates/minimal-app/apps/server/tsup.config.ts.tpl +23 -9
  112. package/templates/minimal-app/apps/shared/.env.example.tpl +5 -0
  113. package/templates/minimal-app/apps/shared/.env.production.example.tpl +23 -0
  114. package/templates/minimal-app/apps/shared/.env.tpl +5 -0
  115. package/templates/minimal-app/apps/shared/Dockerfile.tpl +48 -0
  116. package/templates/minimal-app/apps/shared/fly.toml.tpl +24 -0
  117. package/templates/minimal-app/apps/shared/package.json.tpl +25 -24
  118. package/templates/minimal-app/apps/shared/scripts/fly-deploy.mjs.tpl +75 -0
  119. package/templates/minimal-app/apps/shared/scripts/fly-secrets.mjs.tpl +31 -0
  120. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
  121. package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
  122. package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
  123. package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
  124. package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
  125. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
  126. package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
  127. package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +46 -44
  128. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
  129. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
  130. package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -15
  131. package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
  132. package/templates/minimal-app/apps/webapp/src/modules/conversation/ConversationRoute.tsx.tpl +36 -0
  133. package/templates/minimal-app/apps/webapp/src/modules/files/FilesRoute.tsx.tpl +88 -0
  134. package/templates/minimal-app/apps/webapp/src/modules/files/hooks/useFilesRoute.ts.tpl +50 -0
  135. package/templates/minimal-app/apps/webapp/src/modules/workflows/WorkflowsRoute.tsx.tpl +97 -0
  136. package/templates/minimal-app/apps/webapp/src/modules/workflows/hooks/useWorkflowsRoute.ts.tpl +64 -0
  137. package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
  138. package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -10
  139. package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
  140. package/templates/minimal-app/apps/webapp/translations/en/app.json.tpl +7 -7
  141. package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +193 -135
  142. package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
  143. package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
  144. package/templates/minimal-app/biome.json.tpl +17 -12
  145. package/templates/minimal-app/package.json.tpl +9 -5
  146. package/templates/minimal-app/pnpm-workspace.yaml.tpl +67 -58
  147. package/templates/minimal-app/template.manifest.json +92 -2
  148. package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +0 -26
  149. package/templates/minimal-app/apps/server/drizzle/guard.ts.tpl +0 -36
  150. package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +0 -40
  151. package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +0 -16
  152. package/templates/minimal-app/apps/server/src/lib/auth.ts.tpl +0 -12
@@ -1,47 +1,47 @@
1
- import { expect, test } from "@playwright/test";
2
- import { clearEmails, latestEmail, profileFrom, profiles } from "./helpers";
3
-
4
- test("stored email inbox renders and exposes JSON helpers", async ({ page, request }, testInfo) => {
5
- const profile = profileFrom(testInfo);
6
- await clearEmails(request, profile);
7
-
8
- await page.goto("/forgot-password");
9
- await page.locator('[name="forgot-password-email"]').fill(profiles[profile].adminEmail);
10
- const resetRequestPromise = page.waitForResponse(
11
- (response) =>
12
- response.url().includes("/api/auth/request-password-reset") &&
13
- response.request().method() === "POST"
14
- );
15
- await page.getByRole("button", { name: /reset password/i }).click();
16
- const resetRequest = await resetRequestPromise;
17
- expect(
18
- resetRequest.ok(),
19
- `${resetRequest.status()} ${resetRequest.statusText()}: ${await resetRequest.text()}`
20
- ).toBe(true);
21
-
22
- const email = await latestEmail(request, profile, {
23
- to: profiles[profile].adminEmail,
24
- templateId: "password-reset",
25
- });
26
- expect(email.subject).toContain("Reset");
27
- expect(email.html).toContain("Reset your password");
28
-
29
- await page.goto(`${profiles[profile].serverUrl}/__emails`);
30
- await expect(page.getByRole("heading", { name: "Stored emails" })).toBeVisible();
31
- await page.goto(`${profiles[profile].serverUrl}/__emails/${email.id}`);
32
- await expect(page.getByRole("heading", { name: email.subject })).toBeVisible();
33
-
34
- const rawResponse = await request.get(
35
- `${profiles[profile].serverUrl}/__emails/${email.id}/raw.html`
36
- );
37
- expect(rawResponse.ok()).toBe(true);
38
- expect(await rawResponse.text()).toContain("Reset your password");
39
-
40
- const jsonResponse = await request.get(
41
- `${profiles[profile].serverUrl}/__emails/${email.id}.json`
42
- );
43
- expect(jsonResponse.ok()).toBe(true);
44
- const payload = await jsonResponse.json();
45
- expect(payload.props).toEqual(expect.objectContaining({ url: expect.any(String) }));
46
- expect(payload.html).toContain("Reset your password");
47
- });
1
+ import { expect, test } from "@playwright/test";
2
+ import { clearEmails, latestEmail, profileFrom, profiles } from "./helpers";
3
+
4
+ test("stored email inbox renders and exposes JSON helpers", async ({ page, request }, testInfo) => {
5
+ const profile = profileFrom(testInfo);
6
+ await clearEmails(request, profile);
7
+
8
+ await page.goto("/forgot-password");
9
+ await page.locator('[name="forgot-password-email"]').fill(profiles[profile].adminEmail);
10
+ const resetRequestPromise = page.waitForResponse(
11
+ (response) =>
12
+ response.url().includes("/api/auth/request-password-reset") &&
13
+ response.request().method() === "POST"
14
+ );
15
+ await page.getByRole("button", { name: /reset password/i }).click();
16
+ const resetRequest = await resetRequestPromise;
17
+ expect(
18
+ resetRequest.ok(),
19
+ `${resetRequest.status()} ${resetRequest.statusText()}: ${await resetRequest.text()}`
20
+ ).toBe(true);
21
+
22
+ const email = await latestEmail(request, profile, {
23
+ to: profiles[profile].adminEmail,
24
+ templateId: "password-reset",
25
+ });
26
+ expect(email.subject).toContain("Reset");
27
+ expect(email.html).toContain("Reset your password");
28
+
29
+ await page.goto(`${profiles[profile].serverUrl}/__emails`);
30
+ await expect(page.getByRole("heading", { name: "Stored emails" })).toBeVisible();
31
+ await page.goto(`${profiles[profile].serverUrl}/__emails/${email.id}`);
32
+ await expect(page.getByRole("heading", { name: email.subject })).toBeVisible();
33
+
34
+ const rawResponse = await request.get(
35
+ `${profiles[profile].serverUrl}/__emails/${email.id}/raw.html`
36
+ );
37
+ expect(rawResponse.ok()).toBe(true);
38
+ expect(await rawResponse.text()).toContain("Reset your password");
39
+
40
+ const jsonResponse = await request.get(
41
+ `${profiles[profile].serverUrl}/__emails/${email.id}.json`
42
+ );
43
+ expect(jsonResponse.ok()).toBe(true);
44
+ const payload = await jsonResponse.json();
45
+ expect(payload.props).toEqual(expect.objectContaining({ url: expect.any(String) }));
46
+ expect(payload.html).toContain("Reset your password");
47
+ });
@@ -1,73 +1,73 @@
1
- import type { BackendTRPCRouter } from "@m5kdev/backend/types";
2
- import { transformer } from "@m5kdev/commons/utils/trpc";
3
- import type { Page } from "@playwright/test";
4
- import { expect } from "@playwright/test";
5
- import { createTRPCClient, httpBatchLink } from "@trpc/client";
6
- import { type Profile, profiles } from "./helpers";
7
-
8
- type FetchInit = NonNullable<Parameters<typeof fetch>[1]>;
9
-
10
- async function cookieHeader(page: Page, profile: Profile) {
11
- const cookies = await page.context().cookies(profiles[profile].serverUrl);
12
- return cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
13
- }
14
-
15
- export async function createExpoTrpcClient(page: Page, profile: Profile) {
16
- const cookie = await cookieHeader(page, profile);
17
-
18
- return createTRPCClient<BackendTRPCRouter>({
19
- links: [
20
- httpBatchLink({
21
- url: `${profiles[profile].serverUrl}/trpc`,
22
- fetch(url, options) {
23
- const headers = new Headers(options?.headers);
24
- if (cookie) {
25
- headers.set("cookie", cookie);
26
- }
27
- return fetch(url, {
28
- ...options,
29
- body: options?.body ? (options.body as FetchInit["body"]) : undefined,
30
- headers,
31
- });
32
- },
33
- transformer,
34
- }),
35
- ],
36
- });
37
- }
38
-
39
- export async function expoSignUp(page: Page, email: string, password: string) {
40
- await page.goto("/signup");
41
- await page.getByTestId("signup-email").fill(email);
42
- await page.getByTestId("signup-password").fill(password);
43
- await page.getByTestId("signup-submit").click();
44
- }
45
-
46
- export async function expoLogin(page: Page, email: string, password: string) {
47
- await page.goto("/login");
48
- await page.getByTestId("login-email").fill(email);
49
- await page.getByTestId("login-password").fill(password);
50
- await page.getByTestId("login-submit").click();
51
- await expect(page.getByTestId("session-email")).toContainText(email);
52
- }
53
-
54
- export async function expoLogout(page: Page) {
55
- await page.goto("/logout");
56
- await expect(page).toHaveURL(/\/login/);
57
- }
58
-
59
- export async function expectExpoLoginRejected(page: Page, email: string, password: string) {
60
- await page.goto("/login");
61
- await page.getByTestId("login-email").fill(email);
62
- await page.getByTestId("login-password").fill(password);
63
- const responsePromise = page.waitForResponse(
64
- (response) =>
65
- response.url().includes("/api/auth/sign-in/email") && response.request().method() === "POST"
66
- );
67
- await page.getByTestId("login-submit").click();
68
- const response = await responsePromise;
69
- expect(
70
- response.ok(),
71
- `${response.status()} ${response.statusText()}: ${await response.text()}`
72
- ).toBe(false);
73
- }
1
+ import type { BackendTRPCRouter } from "@m5kdev/backend/types";
2
+ import { transformer } from "@m5kdev/commons/utils/trpc";
3
+ import type { Page } from "@playwright/test";
4
+ import { expect } from "@playwright/test";
5
+ import { createTRPCClient, httpBatchLink } from "@trpc/client";
6
+ import { type Profile, profiles } from "./helpers";
7
+
8
+ type FetchInit = NonNullable<Parameters<typeof fetch>[1]>;
9
+
10
+ async function cookieHeader(page: Page, profile: Profile) {
11
+ const cookies = await page.context().cookies(profiles[profile].serverUrl);
12
+ return cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
13
+ }
14
+
15
+ export async function createExpoTrpcClient(page: Page, profile: Profile) {
16
+ const cookie = await cookieHeader(page, profile);
17
+
18
+ return createTRPCClient<BackendTRPCRouter>({
19
+ links: [
20
+ httpBatchLink({
21
+ url: `${profiles[profile].serverUrl}/trpc`,
22
+ fetch(url, options) {
23
+ const headers = new Headers(options?.headers);
24
+ if (cookie) {
25
+ headers.set("cookie", cookie);
26
+ }
27
+ return fetch(url, {
28
+ ...options,
29
+ body: options?.body ? (options.body as FetchInit["body"]) : undefined,
30
+ headers,
31
+ });
32
+ },
33
+ transformer,
34
+ }),
35
+ ],
36
+ });
37
+ }
38
+
39
+ export async function expoSignUp(page: Page, email: string, password: string) {
40
+ await page.goto("/signup");
41
+ await page.getByTestId("signup-email").fill(email);
42
+ await page.getByTestId("signup-password").fill(password);
43
+ await page.getByTestId("signup-submit").click();
44
+ }
45
+
46
+ export async function expoLogin(page: Page, email: string, password: string) {
47
+ await page.goto("/login");
48
+ await page.getByTestId("login-email").fill(email);
49
+ await page.getByTestId("login-password").fill(password);
50
+ await page.getByTestId("login-submit").click();
51
+ await expect(page.getByTestId("session-email")).toContainText(email);
52
+ }
53
+
54
+ export async function expoLogout(page: Page) {
55
+ await page.goto("/logout");
56
+ await expect(page).toHaveURL(/\/login/);
57
+ }
58
+
59
+ export async function expectExpoLoginRejected(page: Page, email: string, password: string) {
60
+ await page.goto("/login");
61
+ await page.getByTestId("login-email").fill(email);
62
+ await page.getByTestId("login-password").fill(password);
63
+ const responsePromise = page.waitForResponse(
64
+ (response) =>
65
+ response.url().includes("/api/auth/sign-in/email") && response.request().method() === "POST"
66
+ );
67
+ await page.getByTestId("login-submit").click();
68
+ const response = await responsePromise;
69
+ expect(
70
+ response.ok(),
71
+ `${response.status()} ${response.statusText()}: ${await response.text()}`
72
+ ).toBe(false);
73
+ }