create-m5kdev 0.30.0 → 0.31.1

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 (60) hide show
  1. package/dist/src/catalog.cjs +140 -0
  2. package/dist/src/catalog.cjs.map +1 -0
  3. package/dist/src/catalog.d.cts +31 -0
  4. package/dist/src/changes.cjs +70 -0
  5. package/dist/src/changes.cjs.map +1 -0
  6. package/dist/src/changes.d.cts +26 -0
  7. package/dist/src/create.cjs +37 -23
  8. package/dist/src/create.cjs.map +1 -1
  9. package/dist/src/diagnostics.cjs +33 -0
  10. package/dist/src/diagnostics.cjs.map +1 -0
  11. package/dist/src/diagnostics.d.cts +20 -0
  12. package/dist/src/doctor.cjs +246 -0
  13. package/dist/src/doctor.cjs.map +1 -0
  14. package/dist/src/doctor.d.cts +13 -0
  15. package/dist/src/fs.cjs +56 -26
  16. package/dist/src/fs.cjs.map +1 -1
  17. package/dist/src/fs.d.cts +11 -4
  18. package/dist/src/init.cjs +110 -0
  19. package/dist/src/init.cjs.map +1 -0
  20. package/dist/src/init.d.cts +36 -0
  21. package/dist/src/migrations/ast.cjs +49 -0
  22. package/dist/src/migrations/ast.cjs.map +1 -0
  23. package/dist/src/migrations/ast.d.cts +10 -0
  24. package/dist/src/migrations/registry.cjs +41 -0
  25. package/dist/src/migrations/registry.cjs.map +1 -0
  26. package/dist/src/migrations/registry.d.cts +12 -0
  27. package/dist/src/migrations/types.cjs +0 -0
  28. package/dist/src/migrations/types.d.cts +22 -0
  29. package/dist/src/prompts.cjs +1 -1
  30. package/dist/src/prompts.cjs.map +1 -1
  31. package/dist/src/reconcile.cjs +361 -0
  32. package/dist/src/reconcile.cjs.map +1 -0
  33. package/dist/src/reconcile.d.cts +26 -0
  34. package/dist/src/runCli.cjs +155 -27
  35. package/dist/src/runCli.cjs.map +1 -1
  36. package/dist/src/state.cjs +107 -0
  37. package/dist/src/state.cjs.map +1 -0
  38. package/dist/src/state.d.cts +50 -0
  39. package/dist/src/template.cjs +45 -0
  40. package/dist/src/template.cjs.map +1 -0
  41. package/dist/src/template.d.cts +10 -0
  42. package/dist/src/types.d.cts +20 -1
  43. package/dist/src/update.cjs +91 -0
  44. package/dist/src/update.cjs.map +1 -0
  45. package/dist/src/update.d.cts +33 -0
  46. package/package.json +12 -4
  47. package/templates/minimal-app/README.md.tpl +18 -3
  48. package/templates/minimal-app/apps/email/src/emails/accountDeletionEmail.tsx.tpl +36 -35
  49. package/templates/minimal-app/apps/email/src/emails/organizationInviteEmail.tsx.tpl +35 -34
  50. package/templates/minimal-app/apps/email/src/emails/passwordResetEmail.tsx.tpl +36 -35
  51. package/templates/minimal-app/apps/email/src/emails/verificationEmail.tsx.tpl +38 -37
  52. package/templates/minimal-app/apps/email/src/emails/waitlistEmails.tsx.tpl +88 -87
  53. package/templates/minimal-app/apps/email/src/index.ts.tpl +74 -72
  54. package/templates/minimal-app/apps/email/tsconfig.json.tpl +11 -16
  55. package/templates/minimal-app/apps/server/tsconfig.json.tpl +13 -20
  56. package/templates/minimal-app/apps/shared/tsconfig.json.tpl +12 -16
  57. package/templates/minimal-app/apps/webapp/src/modules/posts/hooks/usePostsList.ts.tpl +98 -98
  58. package/templates/minimal-app/biome.json.tpl +9 -2
  59. package/templates/minimal-app/pnpm-workspace.yaml.tpl +61 -63
  60. package/templates/minimal-app/template.manifest.json +30 -0
@@ -0,0 +1,91 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
3
+ const require_src_paths = require("./paths.cjs");
4
+ const require_src_state = require("./state.cjs");
5
+ const require_src_migrations_registry = require("./migrations/registry.cjs");
6
+ const require_src_reconcile = require("./reconcile.cjs");
7
+ let node_path = require("node:path");
8
+ node_path = require_runtime.__toESM(node_path);
9
+ let node_child_process = require("node:child_process");
10
+ let node_util = require("node:util");
11
+ let semver = require("semver");
12
+ semver = require_runtime.__toESM(semver);
13
+ //#region src/update.ts
14
+ const execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
15
+ async function assertCleanGit(repoRoot) {
16
+ if ((await execFileAsync("git", ["status", "--porcelain"], {
17
+ cwd: repoRoot,
18
+ encoding: "utf8"
19
+ })).stdout.trim()) throw new Error("Write-mode update requires a clean Git worktree. Commit or stash changes first.");
20
+ }
21
+ async function updateManagedRepo(options) {
22
+ const state = await require_src_state.readManagedState(options.repoRoot);
23
+ const targetVersion = options.targetVersion ?? require_src_state.getCliVersion();
24
+ if (semver.default.gt(state.template.version, targetVersion)) throw new Error(`The running CLI ${targetVersion} is older than managed state ${state.template.version}.`);
25
+ if (!options.dryRun) await (options.assertClean ?? assertCleanGit)(options.repoRoot);
26
+ const reconciliation = await require_src_reconcile.reconcileTemplates({
27
+ repoRoot: options.repoRoot,
28
+ state,
29
+ targetTemplateRoot: options.targetTemplateRoot ?? require_src_paths.getTemplateRoot(),
30
+ targetVersion,
31
+ baseProvider: options.baseProvider
32
+ });
33
+ const registry = options.registry ?? require_src_migrations_registry.getMigrationRegistry();
34
+ const pending = require_src_migrations_registry.getPendingMigrations(state, targetVersion, registry);
35
+ const appliedMigrations = [];
36
+ for (const migration of pending) {
37
+ const context = {
38
+ repoRoot: options.repoRoot,
39
+ state,
40
+ changes: reconciliation.changes
41
+ };
42
+ if (migration.applies && !await migration.applies(context)) continue;
43
+ await migration.plan(context);
44
+ appliedMigrations.push(migration.id);
45
+ }
46
+ reconciliation.targetState.appliedMigrations = [...new Set([...state.appliedMigrations, ...appliedMigrations])];
47
+ const validationDiagnostics = await require_src_migrations_registry.runMigrationValidators({
48
+ repoRoot: options.repoRoot,
49
+ state: reconciliation.targetState,
50
+ changes: reconciliation.changes
51
+ }, registry);
52
+ for (const diagnostic of validationDiagnostics) {
53
+ if (diagnostic.severity !== "error") continue;
54
+ reconciliation.changes.addConflict({
55
+ path: diagnostic.path ?? ".m5kdev.json",
56
+ reason: `[${diagnostic.code}] ${diagnostic.message}`
57
+ });
58
+ }
59
+ const stateContent = Buffer.from(`${JSON.stringify(reconciliation.targetState, null, 2)}\n`);
60
+ const currentState = await reconciliation.changes.read(require_src_state.STATE_FILE_NAME);
61
+ if (!currentState || !currentState.equals(stateContent)) reconciliation.changes.addChange({
62
+ kind: currentState ? "modify" : "add",
63
+ path: require_src_state.STATE_FILE_NAME,
64
+ content: stateContent,
65
+ reason: "Advance managed template state"
66
+ });
67
+ const result = {
68
+ fromVersion: state.template.version,
69
+ targetVersion,
70
+ dryRun: options.dryRun,
71
+ applied: false,
72
+ changes: [...reconciliation.changes.changes.values()].map(({ content: _content, ...change }) => change),
73
+ conflicts: [...reconciliation.changes.conflicts],
74
+ migrations: appliedMigrations,
75
+ dependenciesChanged: reconciliation.dependenciesChanged,
76
+ installRequired: reconciliation.dependenciesChanged && options.skipInstall
77
+ };
78
+ if (options.dryRun || result.conflicts.length > 0) return result;
79
+ await reconciliation.changes.apply();
80
+ result.applied = true;
81
+ if (reconciliation.dependenciesChanged && !options.skipInstall) if (options.install) await options.install(node_path.default.resolve(options.repoRoot));
82
+ else await execFileAsync("pnpm", ["install"], {
83
+ cwd: node_path.default.resolve(options.repoRoot),
84
+ env: process.env
85
+ });
86
+ return result;
87
+ }
88
+ //#endregion
89
+ exports.updateManagedRepo = updateManagedRepo;
90
+
91
+ //# sourceMappingURL=update.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.cjs","names":["execFile","readManagedState","getCliVersion","reconcileTemplates","getTemplateRoot","getMigrationRegistry","getPendingMigrations","runMigrationValidators","STATE_FILE_NAME","path"],"sources":["../../src/update.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport semver from \"semver\";\r\nimport type { PlannedChange, PlannedConflict } from \"./changes\";\r\nimport {\r\n getMigrationRegistry,\r\n getPendingMigrations,\r\n runMigrationValidators,\r\n} from \"./migrations/registry\";\r\nimport type { MigrationDefinition } from \"./migrations/types\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { type BaseTemplateProvider, reconcileTemplates } from \"./reconcile\";\r\nimport { getCliVersion, readManagedState, STATE_FILE_NAME } from \"./state\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\n\r\nexport interface UpdateCommandOptions {\r\n repoRoot: string;\r\n dryRun: boolean;\r\n skipInstall: boolean;\r\n baseProvider?: BaseTemplateProvider;\r\n /** Test seam; published commands always use the embedded production registry. */\r\n registry?: readonly MigrationDefinition[];\r\n /** Test seams for fixture-version updates. */\r\n targetTemplateRoot?: string;\r\n targetVersion?: string;\r\n assertClean?: (repoRoot: string) => Promise<void>;\r\n install?: (repoRoot: string) => Promise<void>;\r\n}\r\n\r\nexport interface UpdateResult {\r\n fromVersion: string;\r\n targetVersion: string;\r\n dryRun: boolean;\r\n applied: boolean;\r\n changes: Array<Omit<PlannedChange, \"content\">>;\r\n conflicts: PlannedConflict[];\r\n migrations: string[];\r\n dependenciesChanged: boolean;\r\n installRequired: boolean;\r\n}\r\n\r\nasync function assertCleanGit(repoRoot: string): Promise<void> {\r\n const result = await execFileAsync(\"git\", [\"status\", \"--porcelain\"], {\r\n cwd: repoRoot,\r\n encoding: \"utf8\",\r\n });\r\n if (result.stdout.trim()) {\r\n throw new Error(\r\n \"Write-mode update requires a clean Git worktree. Commit or stash changes first.\"\r\n );\r\n }\r\n}\r\n\r\nexport async function updateManagedRepo(options: UpdateCommandOptions): Promise<UpdateResult> {\r\n const state = await readManagedState(options.repoRoot);\r\n const targetVersion = options.targetVersion ?? getCliVersion();\r\n if (semver.gt(state.template.version, targetVersion)) {\r\n throw new Error(\r\n `The running CLI ${targetVersion} is older than managed state ${state.template.version}.`\r\n );\r\n }\r\n if (!options.dryRun) await (options.assertClean ?? assertCleanGit)(options.repoRoot);\r\n\r\n const reconciliation = await reconcileTemplates({\r\n repoRoot: options.repoRoot,\r\n state,\r\n targetTemplateRoot: options.targetTemplateRoot ?? getTemplateRoot(),\r\n targetVersion,\r\n baseProvider: options.baseProvider,\r\n });\r\n const registry = options.registry ?? getMigrationRegistry();\r\n const pending = getPendingMigrations(state, targetVersion, registry);\r\n const appliedMigrations: string[] = [];\r\n for (const migration of pending) {\r\n const context = { repoRoot: options.repoRoot, state, changes: reconciliation.changes };\r\n if (migration.applies && !(await migration.applies(context))) continue;\r\n await migration.plan(context);\r\n appliedMigrations.push(migration.id);\r\n }\r\n reconciliation.targetState.appliedMigrations = [\r\n ...new Set([...state.appliedMigrations, ...appliedMigrations]),\r\n ];\r\n\r\n const validationDiagnostics = await runMigrationValidators(\r\n {\r\n repoRoot: options.repoRoot,\r\n state: reconciliation.targetState,\r\n changes: reconciliation.changes,\r\n },\r\n registry\r\n );\r\n for (const diagnostic of validationDiagnostics) {\r\n if (diagnostic.severity !== \"error\") continue;\r\n reconciliation.changes.addConflict({\r\n path: diagnostic.path ?? \".m5kdev.json\",\r\n reason: `[${diagnostic.code}] ${diagnostic.message}`,\r\n });\r\n }\r\n\r\n const stateContent = Buffer.from(`${JSON.stringify(reconciliation.targetState, null, 2)}\\n`);\r\n const currentState = await reconciliation.changes.read(STATE_FILE_NAME);\r\n if (!currentState || !currentState.equals(stateContent)) {\r\n reconciliation.changes.addChange({\r\n kind: currentState ? \"modify\" : \"add\",\r\n path: STATE_FILE_NAME,\r\n content: stateContent,\r\n reason: \"Advance managed template state\",\r\n });\r\n }\r\n\r\n const result: UpdateResult = {\r\n fromVersion: state.template.version,\r\n targetVersion,\r\n dryRun: options.dryRun,\r\n applied: false,\r\n changes: [...reconciliation.changes.changes.values()].map(\r\n ({ content: _content, ...change }) => change\r\n ),\r\n conflicts: [...reconciliation.changes.conflicts],\r\n migrations: appliedMigrations,\r\n dependenciesChanged: reconciliation.dependenciesChanged,\r\n installRequired: reconciliation.dependenciesChanged && options.skipInstall,\r\n };\r\n if (options.dryRun || result.conflicts.length > 0) return result;\r\n\r\n await reconciliation.changes.apply();\r\n result.applied = true;\r\n if (reconciliation.dependenciesChanged && !options.skipInstall) {\r\n if (options.install) await options.install(path.resolve(options.repoRoot));\r\n else\r\n await execFileAsync(\"pnpm\", [\"install\"], {\r\n cwd: path.resolve(options.repoRoot),\r\n env: process.env,\r\n });\r\n }\r\n return result;\r\n}\r\n"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { PlannedChange, PlannedConflict } from "./changes.cjs";
2
+ import { MigrationDefinition } from "./migrations/types.cjs";
3
+ import { BaseTemplateProvider } from "./reconcile.cjs";
4
+
5
+ //#region src/update.d.ts
6
+ interface UpdateCommandOptions {
7
+ repoRoot: string;
8
+ dryRun: boolean;
9
+ skipInstall: boolean;
10
+ baseProvider?: BaseTemplateProvider;
11
+ /** Test seam; published commands always use the embedded production registry. */
12
+ registry?: readonly MigrationDefinition[];
13
+ /** Test seams for fixture-version updates. */
14
+ targetTemplateRoot?: string;
15
+ targetVersion?: string;
16
+ assertClean?: (repoRoot: string) => Promise<void>;
17
+ install?: (repoRoot: string) => Promise<void>;
18
+ }
19
+ interface UpdateResult {
20
+ fromVersion: string;
21
+ targetVersion: string;
22
+ dryRun: boolean;
23
+ applied: boolean;
24
+ changes: Array<Omit<PlannedChange, "content">>;
25
+ conflicts: PlannedConflict[];
26
+ migrations: string[];
27
+ dependenciesChanged: boolean;
28
+ installRequired: boolean;
29
+ }
30
+ declare function updateManagedRepo(options: UpdateCommandOptions): Promise<UpdateResult>;
31
+ //#endregion
32
+ export { UpdateCommandOptions, UpdateResult, updateManagedRepo };
33
+ //# sourceMappingURL=update.d.cts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-m5kdev",
3
- "version": "0.30.0",
3
+ "version": "0.31.1",
4
4
  "license": "GPL-3.0-only",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,9 +17,17 @@
17
17
  "m5kdev": "dist/src/index.cjs"
18
18
  },
19
19
  "dependencies": {
20
- "tslib": "2.8.1"
20
+ "jscodeshift": "17.3.0",
21
+ "pacote": "22.0.0",
22
+ "semver": "7.8.5",
23
+ "tslib": "2.8.1",
24
+ "yaml": "2.9.0",
25
+ "zod": "4.2.1"
21
26
  },
22
27
  "devDependencies": {
28
+ "@types/jscodeshift": "17.3.0",
29
+ "@types/pacote": "11.1.8",
30
+ "@types/semver": "7.7.1",
23
31
  "@types/jest": "30.0.0",
24
32
  "@types/node": "20.19.11",
25
33
  "jest": "30.1.3",
@@ -30,8 +38,8 @@
30
38
  },
31
39
  "scripts": {
32
40
  "build": "tsx scripts/prepare-templates.ts && tsdown",
33
- "lint": "biome check .",
34
- "lint:fix": "biome check . --write",
41
+ "lint": "biome check src scripts root-templates package.json tsconfig.json jest.config.ts tsdown.config.ts",
42
+ "lint:fix": "biome check src scripts root-templates package.json tsconfig.json jest.config.ts tsdown.config.ts --write",
35
43
  "check-types": "tsc --noEmit",
36
44
  "test": "jest -c jest.config.ts",
37
45
  "test:smoke": "CLI_SMOKE=1 jest -c jest.config.ts src/__tests__/create.smoke.test.ts",
@@ -26,11 +26,26 @@ Database tables are registered by hand in `apps/server/src/schema.ts`; after cha
26
26
  - Email: `admin@{{APP_SLUG}}.local`
27
27
  - Password: `password1234`
28
28
 
29
- ## Typical Commands
29
+ ## Typical Commands
30
30
 
31
31
  ```sh
32
32
  pnpm dev
33
33
  pnpm build
34
34
  pnpm check-types
35
- pnpm lint
36
- ```
35
+ pnpm lint
36
+ ```
37
+
38
+ ## Managed framework updates
39
+
40
+ This app records its generated baseline in `.m5kdev.json`. The updater is run
41
+ explicitly from the package registry and is not installed as an app dependency:
42
+
43
+ ```sh
44
+ pnpm dlx create-m5kdev@<version> doctor
45
+ pnpm dlx create-m5kdev@<version> update --dry-run
46
+ pnpm dlx create-m5kdev@<version> update
47
+ ```
48
+
49
+ Use an exact target version in CI and when coordinating an upgrade. Existing
50
+ projects without `.m5kdev.json` can enroll at their current compatible baseline
51
+ with `pnpm dlx create-m5kdev@<version> init`.
@@ -1,35 +1,36 @@
1
- /** @jsxRuntime automatic */
2
- import { Heading, Text } from "@react-email/components";
3
- import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
- import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
- import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
6
-
7
- function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
8
- return t ?? ((key: string) => key);
9
- }
10
-
11
- export default function AccountDeletionEmail({
12
- previewText,
13
- url,
14
- brand,
15
- t,
16
- htmlLang,
17
- }: {
18
- previewText: string;
19
- url: string;
20
- brand: Brand;
21
- t?: EmailTranslateFn;
22
- htmlLang?: string;
23
- }) {
24
- const translate = resolveT(t);
25
-
26
- return (
27
- <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
28
- <Heading className="mb-4 text-2xl font-bold text-black">
29
- {translate("accountDeletion.title")}
30
- </Heading>
31
- <Text className="mb-6 text-base text-gray-700">{translate("accountDeletion.body")}</Text>
32
- <CtaButton href={url}>{translate("accountDeletion.action")}</CtaButton>
33
- </EmailLayout>
34
- );
35
- }
1
+ /** @jsxRuntime automatic */
2
+
3
+ import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
+ import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
+ import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
6
+ import { Heading, Text } from "@react-email/components";
7
+
8
+ function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
9
+ return t ?? ((key: string) => key);
10
+ }
11
+
12
+ export default function AccountDeletionEmail({
13
+ previewText,
14
+ url,
15
+ brand,
16
+ t,
17
+ htmlLang,
18
+ }: {
19
+ previewText: string;
20
+ url: string;
21
+ brand: Brand;
22
+ t?: EmailTranslateFn;
23
+ htmlLang?: string;
24
+ }) {
25
+ const translate = resolveT(t);
26
+
27
+ return (
28
+ <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
29
+ <Heading className="mb-4 text-2xl font-bold text-black">
30
+ {translate("accountDeletion.title")}
31
+ </Heading>
32
+ <Text className="mb-6 text-base text-gray-700">{translate("accountDeletion.body")}</Text>
33
+ <CtaButton href={url}>{translate("accountDeletion.action")}</CtaButton>
34
+ </EmailLayout>
35
+ );
36
+ }
@@ -1,34 +1,35 @@
1
- /** @jsxRuntime automatic */
2
- import { Heading, Text } from "@react-email/components";
3
- import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
- import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
- import type { EmailTranslateFn, OrganizationInviteTemplateProps } from "@m5kdev/email/types";
6
-
7
- function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
8
- return t ?? ((key: string) => key);
9
- }
10
-
11
- export default function OrganizationInviteEmail({
12
- previewText,
13
- inviterName,
14
- organizationName,
15
- role,
16
- url,
17
- brand,
18
- t,
19
- htmlLang,
20
- }: OrganizationInviteTemplateProps) {
21
- const translate = resolveT(t);
22
-
23
- return (
24
- <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
25
- <Heading className="mb-4 text-2xl font-bold text-black">
26
- {translate("organizationInvite.title", { organizationName })}
27
- </Heading>
28
- <Text className="mb-6 text-base text-gray-700">
29
- {translate("organizationInvite.body", { inviterName, organizationName, role })}
30
- </Text>
31
- <CtaButton href={url}>{translate("organizationInvite.action")}</CtaButton>
32
- </EmailLayout>
33
- );
34
- }
1
+ /** @jsxRuntime automatic */
2
+
3
+ import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
+ import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
+ import type { EmailTranslateFn, OrganizationInviteTemplateProps } from "@m5kdev/email/types";
6
+ import { Heading, Text } from "@react-email/components";
7
+
8
+ function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
9
+ return t ?? ((key: string) => key);
10
+ }
11
+
12
+ export default function OrganizationInviteEmail({
13
+ previewText,
14
+ inviterName,
15
+ organizationName,
16
+ role,
17
+ url,
18
+ brand,
19
+ t,
20
+ htmlLang,
21
+ }: OrganizationInviteTemplateProps) {
22
+ const translate = resolveT(t);
23
+
24
+ return (
25
+ <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
26
+ <Heading className="mb-4 text-2xl font-bold text-black">
27
+ {translate("organizationInvite.title", { organizationName })}
28
+ </Heading>
29
+ <Text className="mb-6 text-base text-gray-700">
30
+ {translate("organizationInvite.body", { inviterName, organizationName, role })}
31
+ </Text>
32
+ <CtaButton href={url}>{translate("organizationInvite.action")}</CtaButton>
33
+ </EmailLayout>
34
+ );
35
+ }
@@ -1,35 +1,36 @@
1
- /** @jsxRuntime automatic */
2
- import { Heading, Text } from "@react-email/components";
3
- import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
- import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
- import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
6
-
7
- function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
8
- return t ?? ((key: string) => key);
9
- }
10
-
11
- export default function PasswordResetEmail({
12
- previewText,
13
- url,
14
- brand,
15
- t,
16
- htmlLang,
17
- }: {
18
- previewText: string;
19
- url: string;
20
- brand: Brand;
21
- t?: EmailTranslateFn;
22
- htmlLang?: string;
23
- }) {
24
- const translate = resolveT(t);
25
-
26
- return (
27
- <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
28
- <Heading className="mb-4 text-2xl font-bold text-black">
29
- {translate("passwordReset.title")}
30
- </Heading>
31
- <Text className="mb-6 text-base text-gray-700">{translate("passwordReset.body")}</Text>
32
- <CtaButton href={url}>{translate("passwordReset.action")}</CtaButton>
33
- </EmailLayout>
34
- );
35
- }
1
+ /** @jsxRuntime automatic */
2
+
3
+ import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
+ import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
+ import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
6
+ import { Heading, Text } from "@react-email/components";
7
+
8
+ function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
9
+ return t ?? ((key: string) => key);
10
+ }
11
+
12
+ export default function PasswordResetEmail({
13
+ previewText,
14
+ url,
15
+ brand,
16
+ t,
17
+ htmlLang,
18
+ }: {
19
+ previewText: string;
20
+ url: string;
21
+ brand: Brand;
22
+ t?: EmailTranslateFn;
23
+ htmlLang?: string;
24
+ }) {
25
+ const translate = resolveT(t);
26
+
27
+ return (
28
+ <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
29
+ <Heading className="mb-4 text-2xl font-bold text-black">
30
+ {translate("passwordReset.title")}
31
+ </Heading>
32
+ <Text className="mb-6 text-base text-gray-700">{translate("passwordReset.body")}</Text>
33
+ <CtaButton href={url}>{translate("passwordReset.action")}</CtaButton>
34
+ </EmailLayout>
35
+ );
36
+ }
@@ -1,37 +1,38 @@
1
- /** @jsxRuntime automatic */
2
- import { Heading, Text } from "@react-email/components";
3
- import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
- import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
- import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
6
-
7
- function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
8
- return t ?? ((key: string) => key);
9
- }
10
-
11
- export default function VerificationEmail({
12
- previewText,
13
- url,
14
- brand,
15
- t,
16
- htmlLang,
17
- }: {
18
- previewText: string;
19
- url: string;
20
- brand: Brand;
21
- t?: EmailTranslateFn;
22
- htmlLang?: string;
23
- }) {
24
- const translate = resolveT(t);
25
-
26
- return (
27
- <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
28
- <Heading className="mb-4 text-2xl font-bold text-black">
29
- {translate("verification.title")}
30
- </Heading>
31
- <Text className="mb-6 text-base text-gray-700">
32
- {translate("verification.body", { appName: "{{APP_NAME}}" })}
33
- </Text>
34
- <CtaButton href={url}>{translate("verification.action")}</CtaButton>
35
- </EmailLayout>
36
- );
37
- }
1
+ /** @jsxRuntime automatic */
2
+
3
+ import { CtaButton } from "@m5kdev/email/components/CtaButton";
4
+ import { EmailLayout } from "@m5kdev/email/components/EmailLayout";
5
+ import type { Brand, EmailTranslateFn } from "@m5kdev/email/types";
6
+ import { Heading, Text } from "@react-email/components";
7
+
8
+ function resolveT(t?: EmailTranslateFn): EmailTranslateFn {
9
+ return t ?? ((key: string) => key);
10
+ }
11
+
12
+ export default function VerificationEmail({
13
+ previewText,
14
+ url,
15
+ brand,
16
+ t,
17
+ htmlLang,
18
+ }: {
19
+ previewText: string;
20
+ url: string;
21
+ brand: Brand;
22
+ t?: EmailTranslateFn;
23
+ htmlLang?: string;
24
+ }) {
25
+ const translate = resolveT(t);
26
+
27
+ return (
28
+ <EmailLayout previewText={previewText} brand={brand} htmlLang={htmlLang}>
29
+ <Heading className="mb-4 text-2xl font-bold text-black">
30
+ {translate("verification.title")}
31
+ </Heading>
32
+ <Text className="mb-6 text-base text-gray-700">
33
+ {translate("verification.body", { appName: "{{APP_NAME}}" })}
34
+ </Text>
35
+ <CtaButton href={url}>{translate("verification.action")}</CtaButton>
36
+ </EmailLayout>
37
+ );
38
+ }