@cedarjs/cli 7.1.0-rc.34 → 7.1.1-next.468

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 (194) hide show
  1. package/dist/commands/build/buildHandler.js +2 -1
  2. package/dist/commands/build.js +1 -1
  3. package/dist/commands/check.js +2 -1
  4. package/dist/commands/deploy/baremetal/baremetalHandler.js +364 -73
  5. package/dist/commands/deploy/baremetal/monitors.js +66 -0
  6. package/dist/commands/deploy/baremetal.js +7 -1
  7. package/dist/commands/deploy/flightcontrolHandler.js +1 -1
  8. package/dist/commands/deploy/helpers/deployHandler.js +1 -1
  9. package/dist/commands/deploy/netlify.js +1 -1
  10. package/dist/commands/deploy/renderHandler.js +1 -1
  11. package/dist/commands/deploy/serverlessHandler.js +2 -1
  12. package/dist/commands/deploy/vercel.js +1 -1
  13. package/dist/commands/destroy/handlerHelpers.js +1 -1
  14. package/dist/commands/destroy/page/pageHandler.js +2 -1
  15. package/dist/commands/destroy/scaffold/scaffoldHandler.js +65 -26
  16. package/dist/commands/destroy/sdl/sdlHandler.js +2 -1
  17. package/dist/commands/dev/devHandler.js +3 -4
  18. package/dist/commands/dev/packageWatchCommands.js +8 -3
  19. package/dist/commands/dev.js +1 -1
  20. package/dist/commands/execHandler.js +2 -1
  21. package/dist/commands/experimental/live-queries/liveQueries.js +1 -1
  22. package/dist/commands/experimental/live-queries/liveQueriesHandler.js +3 -3
  23. package/dist/commands/experimental/setupInngest.js +1 -1
  24. package/dist/commands/experimental/setupInngestHandler.js +1 -1
  25. package/dist/commands/experimental/setupOpentelemetry.js +1 -1
  26. package/dist/commands/experimental/setupOpentelemetryHandler.js +2 -1
  27. package/dist/commands/experimental/setupReactCompiler.js +1 -1
  28. package/dist/commands/experimental/setupReactCompilerHandler.js +4 -4
  29. package/dist/commands/experimental/setupRsc.js +1 -1
  30. package/dist/commands/experimental/setupRscHandler.js +2 -1
  31. package/dist/commands/experimental/setupStreamingSsr.js +1 -1
  32. package/dist/commands/experimental/setupStreamingSsrHandler.js +3 -2
  33. package/dist/commands/generate/cell/cell.js +20 -0
  34. package/dist/commands/generate/cell/cellHandler.js +68 -20
  35. package/dist/commands/generate/cell/templates/cell.tsx.template +21 -2
  36. package/dist/commands/generate/cell/templates/cellFragment.tsx.template +43 -0
  37. package/dist/commands/generate/cell/templates/cellList.tsx.template +21 -2
  38. package/dist/commands/generate/cell/templates/storiesFragment.tsx.template +23 -0
  39. package/dist/commands/generate/cell/templates/test.js.template +1 -1
  40. package/dist/commands/generate/cell/templates/testFragment.js.template +30 -0
  41. package/dist/commands/generate/dataMigration/dataMigration.js +2 -1
  42. package/dist/commands/generate/dbAuth/dbAuth.js +6 -2
  43. package/dist/commands/generate/dbAuth/dbAuthHandler.js +80 -17
  44. package/dist/commands/generate/dbAuth/templates/changePassword.tsx.template +131 -0
  45. package/dist/commands/generate/dbAuth/templates/login.oauth.tsx.template +164 -0
  46. package/dist/commands/generate/dbAuth/templates/login.webAuthn.tsx.template +20 -12
  47. package/dist/commands/generate/dbAuth/templates/signup.oauth.tsx.template +147 -0
  48. package/dist/commands/generate/directive/directiveHandler.js +2 -1
  49. package/dist/commands/generate/function/functionHandler.js +2 -1
  50. package/dist/commands/generate/helpers.js +26 -0
  51. package/dist/commands/generate/job/jobHandler.js +2 -1
  52. package/dist/commands/generate/model/modelHandler.js +2 -1
  53. package/dist/commands/generate/ogImage/ogImageHandler.js +2 -1
  54. package/dist/commands/generate/ogImage/templates/ogImage.og.tsx.template +1 -1
  55. package/dist/commands/generate/package/filesTask.js +9 -12
  56. package/dist/commands/generate/package/packageHandler.js +3 -4
  57. package/dist/commands/generate/package/templates/tsconfig.json.template +1 -0
  58. package/dist/commands/generate/page/pageHandler.js +2 -1
  59. package/dist/commands/generate/realtime/realtime.js +1 -1
  60. package/dist/commands/generate/realtime/realtimeHandler.js +2 -9
  61. package/dist/commands/generate/realtime/templates/liveQueries/blank/blank.service.ts.template +12 -4
  62. package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.service.ts.template +2 -2
  63. package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.ts.template +4 -4
  64. package/dist/commands/generate/scaffold/scaffold.js +1 -1
  65. package/dist/commands/generate/scaffold/scaffoldHandler.js +93 -49
  66. package/dist/commands/generate/scaffold/templates/assets/scaffold.tailwind.css.template +12 -7
  67. package/dist/commands/generate/scaffold/templates/components/EditNameCell.tsx.template +5 -7
  68. package/dist/commands/generate/scaffold/templates/components/NameForm.tsx.template +10 -9
  69. package/dist/commands/generate/scaffold/templates/layouts/NamesScaffoldLayout.tsx.template +18 -0
  70. package/dist/commands/generate/scaffold/templates/lib/formatters.test.tsx.template +11 -0
  71. package/dist/commands/generate/scaffold/templates/lib/formatters.tsx.template +16 -4
  72. package/dist/commands/generate/script/scriptHandler.js +2 -1
  73. package/dist/commands/generate/script/templates/tsconfig.json.template +1 -0
  74. package/dist/commands/generate/sdl/sdlHandler.js +10 -4
  75. package/dist/commands/generate/secret/secret.js +1 -1
  76. package/dist/commands/generate/service/serviceHandler.js +79 -49
  77. package/dist/commands/generate/service/templates/service.ts.template +40 -13
  78. package/dist/commands/generate/service/templates/test.ts.template +8 -6
  79. package/dist/commands/generate/yargsCommandHelpers.js +1 -1
  80. package/dist/commands/generate/yargsHandlerHelpers.js +2 -1
  81. package/dist/commands/generate.js +1 -1
  82. package/dist/commands/info.js +1 -1
  83. package/dist/commands/jobsHandler.js +5 -3
  84. package/dist/commands/lint.js +1 -1
  85. package/dist/commands/prerenderHandler.js +11 -4
  86. package/dist/commands/prismaHandler.js +7 -2
  87. package/dist/commands/record/init.js +1 -1
  88. package/dist/commands/serve.js +2 -1
  89. package/dist/commands/setup/auth/auth.js +2 -4
  90. package/dist/commands/setup/cache/cache.js +1 -1
  91. package/dist/commands/setup/cache/cacheHandler.js +2 -1
  92. package/dist/commands/setup/cache/templates/memcached.ts.template +14 -17
  93. package/dist/commands/setup/cache/templates/redis.ts.template +12 -20
  94. package/dist/commands/setup/database/postgres.js +1 -1
  95. package/dist/commands/setup/database/postgresHandler.js +3 -1
  96. package/dist/commands/setup/deploy/providers/baremetal.js +7 -0
  97. package/dist/commands/setup/deploy/providers/baremetalHandler.js +130 -21
  98. package/dist/commands/setup/deploy/providers/coherence.js +1 -1
  99. package/dist/commands/setup/deploy/providers/coherenceHandler.js +7 -6
  100. package/dist/commands/setup/deploy/providers/flightcontrolHandler.js +2 -1
  101. package/dist/commands/setup/deploy/providers/netlifyHandler.js +2 -1
  102. package/dist/commands/setup/deploy/providers/renderHandler.js +2 -1
  103. package/dist/commands/setup/deploy/providers/serverlessHandler.js +2 -1
  104. package/dist/commands/setup/deploy/providers/universalDeployHandler.js +4 -6
  105. package/dist/commands/setup/deploy/providers/vercelHandler.js +2 -1
  106. package/dist/commands/setup/deploy/templates/baremetal.js +119 -9
  107. package/dist/commands/setup/deploy/templates/netlifyUD.js +3 -0
  108. package/dist/commands/setup/docker/docker.js +1 -1
  109. package/dist/commands/setup/docker/dockerHandler.js +2 -1
  110. package/dist/commands/setup/generator/generator.js +1 -1
  111. package/dist/commands/setup/generator/generatorHandler.js +1 -1
  112. package/dist/commands/setup/graphql/features/fragments/fragments.js +1 -1
  113. package/dist/commands/setup/graphql/features/fragments/fragmentsHandler.js +3 -5
  114. package/dist/commands/setup/graphql/features/trustedDocuments/trustedDocuments.js +1 -1
  115. package/dist/commands/setup/graphql/features/trustedDocuments/trustedDocumentsHandler.js +2 -1
  116. package/dist/commands/setup/i18n/i18n.js +1 -1
  117. package/dist/commands/setup/i18n/i18nHandler.js +1 -1
  118. package/dist/commands/setup/jobs/jobs.js +1 -1
  119. package/dist/commands/setup/jobs/jobsHandler.js +4 -1
  120. package/dist/commands/setup/mailer/mailer.js +1 -1
  121. package/dist/commands/setup/mailer/mailerHandler.js +2 -1
  122. package/dist/commands/setup/middleware/ogImage/ogImage.js +1 -1
  123. package/dist/commands/setup/middleware/ogImage/ogImageHandler.js +2 -1
  124. package/dist/commands/setup/monitoring/sentry/sentry.js +1 -1
  125. package/dist/commands/setup/monitoring/sentry/sentryHandler.js +13 -9
  126. package/dist/commands/setup/monitoring/sentry/templates/sentryApi.ts.template +1 -4
  127. package/dist/commands/setup/monitoring/sentry/templates/sentryWeb.ts.template +7 -4
  128. package/dist/commands/setup/neon/neon.js +1 -1
  129. package/dist/commands/setup/neon/neonHandler.js +5 -9
  130. package/dist/commands/setup/package/package.js +1 -1
  131. package/dist/commands/setup/package/packageHandler.js +4 -4
  132. package/dist/commands/setup/realtime/addRealtimeToGraphql.js +1 -1
  133. package/dist/commands/setup/realtime/realtime.js +1 -1
  134. package/dist/commands/setup/realtime/realtimeHandler.js +4 -16
  135. package/dist/commands/setup/realtime/templates/defer/fastAndSlowFields/fastAndSlowFields.ts.template +4 -1
  136. package/dist/commands/setup/realtime/templates/liveQueries/auctions/auctions.ts.template +12 -4
  137. package/dist/commands/setup/realtime/templates/subscriptions/countdown/countdown.ts.template +1 -1
  138. package/dist/commands/setup/realtime/templates/subscriptions/newMessage/newMessage.ts.template +2 -2
  139. package/dist/commands/setup/server-file/serverFile.js +1 -1
  140. package/dist/commands/setup/server-file/serverFileHandler.js +2 -1
  141. package/dist/commands/setup/tenancy/authCodemod.js +66 -0
  142. package/dist/commands/setup/tenancy/dbCodemod.js +64 -0
  143. package/dist/commands/setup/tenancy/graphqlCodemod.js +88 -0
  144. package/dist/commands/setup/tenancy/preflight.js +49 -0
  145. package/dist/commands/setup/tenancy/schemaPrisma.js +81 -0
  146. package/dist/commands/setup/tenancy/signupCodemod.js +154 -0
  147. package/dist/commands/setup/tenancy/templates/InvitePage.tsx.template +72 -0
  148. package/dist/commands/setup/tenancy/templates/OrgScope.tsx.template +40 -0
  149. package/dist/commands/setup/tenancy/templates/ensureDefaultOrganizations.dataMigration.ts.template +39 -0
  150. package/dist/commands/setup/tenancy/templates/organizations.scenarios.ts.template +57 -0
  151. package/dist/commands/setup/tenancy/templates/organizations.sdl.ts.template +89 -0
  152. package/dist/commands/setup/tenancy/templates/organizations.test.ts.template +381 -0
  153. package/dist/commands/setup/tenancy/templates/organizations.ts.template +394 -0
  154. package/dist/commands/setup/tenancy/templates/requireMembership.test.ts.template +35 -0
  155. package/dist/commands/setup/tenancy/templates/requireMembership.ts.template +25 -0
  156. package/dist/commands/setup/tenancy/tenancy.js +30 -0
  157. package/dist/commands/setup/tenancy/tenancyHandler.js +425 -0
  158. package/dist/commands/setup/ui/libraries/chakra-uiHandler.js +2 -1
  159. package/dist/commands/setup/ui/libraries/mantineHandler.js +2 -1
  160. package/dist/commands/setup/ui/libraries/tailwindcssHandler.js +63 -82
  161. package/dist/commands/setup/uploads/gitignore.js +17 -0
  162. package/dist/commands/setup/uploads/schemaPrisma.js +41 -0
  163. package/dist/commands/setup/uploads/serverFile.js +217 -0
  164. package/dist/commands/setup/uploads/templates/requireUploadToken.ts.template +15 -0
  165. package/dist/commands/setup/uploads/templates/srcLibUploads.ts.template +23 -24
  166. package/dist/commands/setup/uploads/templates/uploads.sdl.ts.template +79 -0
  167. package/dist/commands/setup/uploads/templates/uploads.ts.template +98 -0
  168. package/dist/commands/setup/uploads/templates/withDataUri.ts.template +14 -0
  169. package/dist/commands/setup/uploads/templates/withSignedUrl.ts.template +16 -0
  170. package/dist/commands/setup/uploads/uploads.js +49 -7
  171. package/dist/commands/setup/uploads/uploadsHandler.js +277 -69
  172. package/dist/commands/setup.js +2 -1
  173. package/dist/commands/studio.js +1 -1
  174. package/dist/commands/test/testHandler.js +25 -71
  175. package/dist/commands/test/viteVersionCheck.js +75 -0
  176. package/dist/commands/test.js +10 -18
  177. package/dist/commands/type-checkHandler.js +1 -1
  178. package/dist/commands/upgrade/preUpgradeScripts.js +3 -3
  179. package/dist/commands/upgrade/upgrade.js +1 -1
  180. package/dist/commands/upgrade/upgradeHandler.js +2 -1
  181. package/dist/index.js +5 -8
  182. package/dist/lib/exit.js +1 -1
  183. package/dist/lib/index.js +16 -31
  184. package/dist/lib/loadEnvFiles.js +1 -1
  185. package/dist/lib/plugin.js +1 -1
  186. package/dist/lib/test.js +1 -1
  187. package/dist/lib/updateCheck.js +4 -4
  188. package/dist/middleware/checkNodeVersion.js +4 -4
  189. package/package.json +27 -27
  190. package/dist/commands/setup/ui/templates/postcss.config.cjs.template +0 -9
  191. package/dist/commands/setup/uploads/dbCodemod.js +0 -28
  192. package/dist/commands/setup/uploads/templates/signedUrl.ts.template +0 -21
  193. package/dist/commands/test/testHandlerEsm.js +0 -109
  194. package/dist/commands/testEsm.js +0 -41
@@ -0,0 +1,43 @@
1
+ import type { ${fragmentName} } from 'types/graphql'
2
+
3
+ import type {
4
+ CellSuccessProps,<% if (afterQuery || isEmpty) { %>
5
+ DataObject,<% } %>
6
+ } from '@cedarjs/web'
7
+
8
+ // A parent Cell spreads this fragment by name in its own QUERY, and passes
9
+ // the matching field down as the `${camelName}` prop - no import needed,
10
+ // just render <${pascalName}Cell />:
11
+ //
12
+ // query FindSomething($id: Int!) {
13
+ // something(id: $id) {
14
+ // id
15
+ // ...${fragmentName}
16
+ // }
17
+ // }
18
+ //
19
+ // <${pascalName}Cell ${camelName}={something} />
20
+ export const FRAGMENT = gql`
21
+ fragment ${fragmentName} on ${fragmentOnType} {
22
+ id
23
+ }
24
+ `
25
+ <% if (isEmpty) { %>
26
+ export const isEmpty = (
27
+ data: DataObject,
28
+ { isDataEmpty }: { isDataEmpty: (data: DataObject) => boolean },
29
+ ) => {
30
+ return isDataEmpty(data)
31
+ }
32
+ <% } %><% if (afterQuery) { %>
33
+ export const afterQuery = (data: DataObject): DataObject => {
34
+ return data
35
+ }
36
+ <% } %>
37
+ export const Empty = () => <div>Empty</div>
38
+
39
+ export const Success = ({
40
+ ${camelName},
41
+ }: CellSuccessProps<{ ${camelName}: ${fragmentName} }>) => {
42
+ return <div>{JSON.stringify(${camelName})}</div>
43
+ }
@@ -3,7 +3,9 @@ import type { ${operationName}, ${operationName}Variables } from 'types/graphql
3
3
  import type {
4
4
  CellSuccessProps,
5
5
  CellFailureProps,
6
- TypedDocumentNode,
6
+ TypedDocumentNode,<% if (beforeQuery) { %>
7
+ CellBeforeQueryResult,<% } %><% if (afterQuery || isEmpty) { %>
8
+ DataObject,<% } %>
7
9
  } from '@cedarjs/web'
8
10
 
9
11
  export const QUERY: TypedDocumentNode<
@@ -16,7 +18,24 @@ export const QUERY: TypedDocumentNode<
16
18
  }
17
19
  }
18
20
  `
19
-
21
+ <% if (beforeQuery) { %>
22
+ export const beforeQuery = (
23
+ props: ${operationName}Variables,
24
+ ): CellBeforeQueryResult<${operationName}Variables> => {
25
+ return { variables: props, fetchPolicy: 'cache-and-network' }
26
+ }
27
+ <% } %><% if (isEmpty) { %>
28
+ export const isEmpty = (
29
+ data: DataObject,
30
+ { isDataEmpty }: { isDataEmpty: (data: DataObject) => boolean },
31
+ ) => {
32
+ return isDataEmpty(data)
33
+ }
34
+ <% } %><% if (afterQuery) { %>
35
+ export const afterQuery = (data: DataObject): DataObject => {
36
+ return data
37
+ }
38
+ <% } %>
20
39
  export const Loading = () => <div>Loading...</div>
21
40
 
22
41
  export const Empty = () => <div>Empty</div>
@@ -0,0 +1,23 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+
3
+ import { Empty, Success } from './${pascalName}Cell'
4
+ import { standard } from './${pascalName}Cell.mock'
5
+
6
+ const meta: Meta = {
7
+ title: 'Cells/${pascalName}Cell',
8
+ tags: ['autodocs']
9
+ }
10
+
11
+ export default meta
12
+
13
+ export const empty: StoryObj<typeof Empty> = {
14
+ render: () => {
15
+ return Empty ? <Empty /> : <></>
16
+ }
17
+ }
18
+
19
+ export const success: StoryObj<typeof Success> = {
20
+ render: (args) => {
21
+ return Success ? <Success {...standard()} {...args} /> : <></>
22
+ }
23
+ }
@@ -7,7 +7,7 @@ import { standard } from './${pascalName}Cell.mock'
7
7
  // and can fail without adjustments, e.g. Float and DateTime types.
8
8
  // Please refer to the RedwoodJS Testing Docs:
9
9
  // https://cedarjs.com/docs/testing#testing-cells
10
- // https://cedarjs.com/docs/testing#jest-expect-type-considerations
10
+ // https://cedarjs.com/docs/testing#expect-type-considerations
11
11
 
12
12
  describe('${pascalName}Cell', () => {
13
13
  it('renders Loading successfully', () => {
@@ -0,0 +1,30 @@
1
+ import { render } from '@cedarjs/testing/web'
2
+
3
+ import { Empty, Success } from './${pascalName}Cell'
4
+ import { standard } from './${pascalName}Cell.mock'
5
+
6
+ // Generated boilerplate tests do not account for all circumstances
7
+ // and can fail without adjustments, e.g. Float and DateTime types.
8
+ // Please refer to the RedwoodJS Testing Docs:
9
+ // https://cedarjs.com/docs/testing#testing-cells
10
+ // https://cedarjs.com/docs/testing#expect-type-considerations
11
+
12
+ describe('${pascalName}Cell', () => {
13
+ it('renders Empty successfully', async () => {
14
+ expect(() => {
15
+ render(<Empty />)
16
+ }).not.toThrow()
17
+ })
18
+
19
+ // When you're ready to test the actual output of your component render
20
+ // you could test that, for example, certain text is present:
21
+ //
22
+ // 1. import { screen } from '@cedarjs/testing/web'
23
+ // 2. Add test: expect(screen.getByText('Hello, world')).toBeInTheDocument()
24
+
25
+ it('renders Success successfully', async () => {
26
+ expect(() => {
27
+ render(<Success ${camelName}={standard().${camelName}} />)
28
+ }).not.toThrow()
29
+ })
30
+ })
@@ -2,8 +2,9 @@ import path from "node:path";
2
2
  import { paramCase } from "change-case";
3
3
  import { Listr } from "listr2";
4
4
  import { terminalLink } from "termi-link";
5
- import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
5
+ import { colors as c } from "@cedarjs/cli-helpers/colors";
6
6
  import { formatCedarCommand } from "@cedarjs/cli-helpers/packageManager/display";
7
+ import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
7
8
  import { getDataMigrationsPath } from "@cedarjs/project-config";
8
9
  import {
9
10
  generateTemplate,
@@ -1,9 +1,13 @@
1
1
  import { terminalLink } from "termi-link";
2
2
  import { getYargsDefaults, createHandler } from "../yargsCommandHelpers.js";
3
3
  const command = "dbAuth";
4
- const description = "Generate Login, Signup and Forgot Password pages for dbAuth";
4
+ const description = "Generate Login, Signup, Forgot Password, Reset Password and Change Password pages for dbAuth";
5
5
  const builder = (yargs) => {
6
- yargs.option("skip-forgot", {
6
+ yargs.option("skip-change", {
7
+ description: "Skip generating the Change Password page",
8
+ type: "boolean",
9
+ default: false
10
+ }).option("skip-forgot", {
7
11
  description: "Skip generating the Forgot Password page",
8
12
  type: "boolean",
9
13
  default: false
@@ -4,9 +4,10 @@ import { ListrEnquirerPromptAdapter } from "@listr2/prompt-adapter-enquirer";
4
4
  import { camelCase } from "camel-case";
5
5
  import { Listr } from "listr2";
6
6
  import { titleCase } from "title-case";
7
- import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
7
+ import { colors as c } from "@cedarjs/cli-helpers/colors";
8
8
  import { formatCedarCommand } from "@cedarjs/cli-helpers/packageManager/display";
9
9
  import { runBinSync } from "@cedarjs/cli-helpers/packageManager/exec";
10
+ import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
10
11
  import {
11
12
  addRoutesToRouterTask,
12
13
  addScaffoldImport,
@@ -17,12 +18,25 @@ import {
17
18
  } from "../../../lib/index.js";
18
19
  import { prepareForRollback } from "../../../lib/rollback.js";
19
20
  import { templateForComponentFile } from "../yargsHandlerHelpers.js";
20
- const ROUTES = [
21
- `<Route path="/login" page={LoginPage} name="login" />`,
22
- `<Route path="/signup" page={SignupPage} name="signup" />`,
23
- `<Route path="/forgot-password" page={ForgotPasswordPage} name="forgotPassword" />`,
24
- `<Route path="/reset-password" page={ResetPasswordPage} name="resetPassword" />`
25
- ];
21
+ function routesFor({
22
+ skipChange,
23
+ skipForgot,
24
+ skipLogin,
25
+ skipReset,
26
+ skipSignup
27
+ }) {
28
+ return [
29
+ !skipLogin && `<Route path="/login" page={LoginPage} name="login" />`,
30
+ !skipSignup && `<Route path="/signup" page={SignupPage} name="signup" />`,
31
+ !skipForgot && `<Route path="/forgot-password" page={ForgotPasswordPage} name="forgotPassword" />`,
32
+ !skipReset && `<Route path="/reset-password" page={ResetPasswordPage} name="resetPassword" />`,
33
+ !skipChange && `<Route path="/change-password" page={ChangePasswordPage} name="changePassword" />`
34
+ ].filter((route) => typeof route === "string");
35
+ }
36
+ function pascalCase(str) {
37
+ const camelCased = camelCase(str);
38
+ return camelCased.charAt(0).toUpperCase() + camelCased.slice(1);
39
+ }
26
40
  function getPostInstallMessage(isDbAuthSetup2) {
27
41
  return [
28
42
  ` ${c.warning("Pages created! But you're not done yet:")}
@@ -34,9 +48,11 @@ function getPostInstallMessage(isDbAuthSetup2) {
34
48
  " navigate(routes.home())",
35
49
  " }\n",
36
50
  " and change the route to where you want them to go if the user is already",
37
- " logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage",
38
- " and ResetPasswordPage to change where the user redirects to after submitting",
39
- " those forms.\n",
51
+ " logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage,",
52
+ " ResetPasswordPage and ChangePasswordPage to change where the user redirects",
53
+ " to after submitting those forms.\n",
54
+ " ChangePasswordPage is for users who are already logged in. Link to it from",
55
+ " wherever your app lets users manage their account.\n",
40
56
  !isDbAuthSetup2 && ` Oh, and if you haven't already, add the necessary dbAuth functions and
41
57
  app setup by running:
42
58
 
@@ -57,9 +73,11 @@ function getPostInstallWebauthnMessage(isDbAuthSetup2) {
57
73
  " navigate(routes.home())",
58
74
  " }\n",
59
75
  " and change the route to where you want them to go if the user is already",
60
- " logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage",
61
- " and ResetPasswordPage to change where the user redirects to after submitting",
62
- " those forms.\n",
76
+ " logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage,",
77
+ " ResetPasswordPage and ChangePasswordPage to change where the user redirects",
78
+ " to after submitting those forms.\n",
79
+ " ChangePasswordPage is for users who are already logged in. Link to it from",
80
+ " wherever your app lets users manage their account.\n",
63
81
  !isDbAuthSetup2 && ` Oh, and if you haven't already, add the necessary dbAuth functions and
64
82
  app setup by running:
65
83
 
@@ -70,24 +88,28 @@ function getPostInstallWebauthnMessage(isDbAuthSetup2) {
70
88
  }
71
89
  const files = async ({
72
90
  typescript,
91
+ skipChange,
73
92
  skipForgot,
74
93
  skipLogin,
75
94
  skipReset,
76
95
  skipSignup,
77
96
  webauthn,
97
+ oauth,
78
98
  usernameLabel,
79
99
  passwordLabel
80
100
  }) => {
81
101
  const filesList = [];
82
102
  usernameLabel = usernameLabel || "username";
83
103
  passwordLabel = passwordLabel || "password";
104
+ const oauthEnabled = oauth ?? isOAuthEnabled();
84
105
  const templateVars = {
85
106
  usernameLowerCase: usernameLabel.toLowerCase(),
86
107
  usernameCamelCase: camelCase(usernameLabel),
87
108
  usernameTitleCase: titleCase(usernameLabel),
88
109
  passwordLowerCase: passwordLabel.toLowerCase(),
89
110
  passwordCamelCase: camelCase(passwordLabel),
90
- passwordTitleCase: titleCase(passwordLabel)
111
+ passwordTitleCase: titleCase(passwordLabel),
112
+ passwordPascalCase: pascalCase(passwordLabel)
91
113
  };
92
114
  if (!skipForgot) {
93
115
  filesList.push(
@@ -110,7 +132,7 @@ const files = async ({
110
132
  extension: typescript ? ".tsx" : ".jsx",
111
133
  webPathSection: "pages",
112
134
  generator: "dbAuth",
113
- templatePath: webauthn ? "login.webAuthn.tsx.template" : "login.tsx.template",
135
+ templatePath: loginTemplatePath(webauthn, oauthEnabled),
114
136
  templateVars
115
137
  })
116
138
  );
@@ -128,6 +150,19 @@ const files = async ({
128
150
  })
129
151
  );
130
152
  }
153
+ if (!skipChange) {
154
+ filesList.push(
155
+ await templateForComponentFile({
156
+ name: "ChangePassword",
157
+ suffix: "Page",
158
+ extension: typescript ? ".tsx" : ".jsx",
159
+ webPathSection: "pages",
160
+ generator: "dbAuth",
161
+ templatePath: "changePassword.tsx.template",
162
+ templateVars
163
+ })
164
+ );
165
+ }
131
166
  if (!skipSignup) {
132
167
  filesList.push(
133
168
  await templateForComponentFile({
@@ -136,7 +171,7 @@ const files = async ({
136
171
  extension: typescript ? ".tsx" : ".jsx",
137
172
  webPathSection: "pages",
138
173
  generator: "dbAuth",
139
- templatePath: "signup.tsx.template",
174
+ templatePath: oauthEnabled ? "signup.oauth.tsx.template" : "signup.tsx.template",
140
175
  templateVars
141
176
  })
142
177
  );
@@ -176,6 +211,7 @@ const tasks = ({
176
211
  listr2,
177
212
  force,
178
213
  typescript,
214
+ skipChange,
179
215
  skipForgot,
180
216
  skipLogin,
181
217
  skipReset,
@@ -286,6 +322,7 @@ const tasks = ({
286
322
  task: async () => {
287
323
  const filesObj = await files({
288
324
  typescript,
325
+ skipChange,
289
326
  skipForgot,
290
327
  skipLogin,
291
328
  skipReset,
@@ -302,7 +339,15 @@ const tasks = ({
302
339
  {
303
340
  title: "Adding routes...",
304
341
  task: async () => {
305
- addRoutesToRouterTask(ROUTES);
342
+ addRoutesToRouterTask(
343
+ routesFor({
344
+ skipChange,
345
+ skipForgot,
346
+ skipLogin,
347
+ skipReset,
348
+ skipSignup
349
+ })
350
+ );
306
351
  }
307
352
  },
308
353
  {
@@ -332,6 +377,7 @@ const tasks = ({
332
377
  const handler = async (yargs) => {
333
378
  recordTelemetryAttributes({
334
379
  command: "generate dbAuth",
380
+ skipChange: yargs.skipChange,
335
381
  skipForgot: yargs.skipForgot,
336
382
  skipLogin: yargs.skipLogin,
337
383
  skipReset: yargs.skipReset,
@@ -378,6 +424,23 @@ function isWebAuthnEnabled() {
378
424
  );
379
425
  return webPackageJson.includes('"@simplewebauthn/browser": ');
380
426
  }
427
+ function isOAuthEnabled() {
428
+ const apiPackageJsonPath = path.join(getPaths().base, "api", "package.json");
429
+ if (!fs.existsSync(apiPackageJsonPath)) {
430
+ return false;
431
+ }
432
+ const apiPackageJson = fs.readFileSync(apiPackageJsonPath, "utf-8");
433
+ return apiPackageJson.includes('"@cedarjs/auth-dbauth-oauth": ');
434
+ }
435
+ function loginTemplatePath(webauthn, oauth) {
436
+ if (webauthn) {
437
+ return "login.webAuthn.tsx.template";
438
+ }
439
+ if (oauth) {
440
+ return "login.oauth.tsx.template";
441
+ }
442
+ return "login.tsx.template";
443
+ }
381
444
  export {
382
445
  files,
383
446
  handler
@@ -0,0 +1,131 @@
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ import {
4
+ Form,
5
+ Label,
6
+ PasswordField,
7
+ Submit,
8
+ FieldError,
9
+ } from '@cedarjs/forms'
10
+ import { navigate, routes } from '@cedarjs/router'
11
+ import { Metadata } from '@cedarjs/web'
12
+ import { toast, Toaster } from '@cedarjs/web/toast'
13
+
14
+ import { useAuth } from 'src/auth'
15
+
16
+ const ChangePasswordPage = () => {
17
+ const { loading, isAuthenticated, reauthenticate, changePassword } =
18
+ useAuth()
19
+
20
+ // Only a logged in user can change their password
21
+ useEffect(() => {
22
+ if (!loading && !isAuthenticated) {
23
+ navigate(routes.login())
24
+ }
25
+ }, [loading, isAuthenticated])
26
+
27
+ const current${passwordPascalCase}Ref = useRef<HTMLInputElement>(null)
28
+ useEffect(() => {
29
+ current${passwordPascalCase}Ref.current?.focus()
30
+ }, [])
31
+
32
+ const onSubmit = async (data: Record<string, string>) => {
33
+ const response = await changePassword({
34
+ currentPassword: data.current${passwordPascalCase},
35
+ newPassword: data.new${passwordPascalCase},
36
+ })
37
+
38
+ if (response.error) {
39
+ toast.error(response.error)
40
+ } else {
41
+ // The function `changePassword.handler` in api/src/functions/auth.js
42
+ // decides if the user stays logged in. `reauthenticate()` picks up
43
+ // whichever it was
44
+ toast.success('${passwordTitleCase} changed!')
45
+ await reauthenticate()
46
+ navigate(routes.home())
47
+ }
48
+ }
49
+
50
+ return (
51
+ <>
52
+ <Metadata title="Change ${passwordTitleCase}" />
53
+
54
+ <main className="rw-main">
55
+ <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} />
56
+ <div className="rw-scaffold rw-login-container">
57
+ <div className="rw-segment">
58
+ <header className="rw-segment-header">
59
+ <h2 className="rw-heading rw-heading-secondary">
60
+ Change ${passwordTitleCase}
61
+ </h2>
62
+ </header>
63
+
64
+ <div className="rw-segment-main">
65
+ <div className="rw-form-wrapper">
66
+ <Form onSubmit={onSubmit} className="rw-form-wrapper">
67
+ <Label
68
+ name="current${passwordPascalCase}"
69
+ className="rw-label"
70
+ errorClassName="rw-label rw-label-error"
71
+ >
72
+ Current ${passwordTitleCase}
73
+ </Label>
74
+ <PasswordField
75
+ name="current${passwordPascalCase}"
76
+ autoComplete="current-password"
77
+ className="rw-input"
78
+ errorClassName="rw-input rw-input-error"
79
+ ref={current${passwordPascalCase}Ref}
80
+ validation={{
81
+ required: {
82
+ value: true,
83
+ message: 'Current ${passwordTitleCase} is required',
84
+ },
85
+ }}
86
+ />
87
+
88
+ <FieldError
89
+ name="current${passwordPascalCase}"
90
+ className="rw-field-error"
91
+ />
92
+
93
+ <Label
94
+ name="new${passwordPascalCase}"
95
+ className="rw-label"
96
+ errorClassName="rw-label rw-label-error"
97
+ >
98
+ New ${passwordTitleCase}
99
+ </Label>
100
+ <PasswordField
101
+ name="new${passwordPascalCase}"
102
+ autoComplete="new-password"
103
+ className="rw-input"
104
+ errorClassName="rw-input rw-input-error"
105
+ validation={{
106
+ required: {
107
+ value: true,
108
+ message: 'New ${passwordTitleCase} is required',
109
+ },
110
+ }}
111
+ />
112
+
113
+ <FieldError
114
+ name="new${passwordPascalCase}"
115
+ className="rw-field-error"
116
+ />
117
+
118
+ <div className="rw-button-group">
119
+ <Submit className="rw-button rw-button-blue">Submit</Submit>
120
+ </div>
121
+ </Form>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </main>
127
+ </>
128
+ )
129
+ }
130
+
131
+ export default ChangePasswordPage
@@ -0,0 +1,164 @@
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ import {
4
+ Form,
5
+ Label,
6
+ TextField,
7
+ PasswordField,
8
+ Submit,
9
+ FieldError,
10
+ } from '@cedarjs/forms'
11
+ import { Link, navigate, routes } from '@cedarjs/router'
12
+ import { Metadata } from '@cedarjs/web'
13
+ import { toast, Toaster } from '@cedarjs/web/toast'
14
+
15
+ import { getOAuthUrl, getOAuthError } from '@cedarjs/auth-dbauth-web/oauth'
16
+
17
+ import { useAuth } from 'src/auth'
18
+
19
+ const LoginPage = () => {
20
+ const { isAuthenticated, logIn } = useAuth()
21
+
22
+ useEffect(() => {
23
+ if (isAuthenticated) {
24
+ navigate(routes.home())
25
+ }
26
+ }, [isAuthenticated])
27
+
28
+ // Surface an error that the OAuth provider redirect sent back, e.g.
29
+ // after the user cancels the provider's consent screen.
30
+ useEffect(() => {
31
+ const oauthError = getOAuthError(window.location.search)
32
+
33
+ if (oauthError) {
34
+ toast.error('Could not log in: ' + oauthError)
35
+ }
36
+ }, [])
37
+
38
+ const ${usernameCamelCase}Ref = useRef<HTMLInputElement>(null)
39
+ useEffect(() => {
40
+ ${usernameCamelCase}Ref.current?.focus()
41
+ }, [])
42
+
43
+ const onSubmit = async (data: Record<string, string>) => {
44
+ const response = await logIn({ username: data.${usernameCamelCase}, password: data.${passwordCamelCase} })
45
+
46
+ if (response.message) {
47
+ toast(response.message)
48
+ } else if (response.error) {
49
+ toast.error(response.error)
50
+ } else {
51
+ toast.success('Welcome back!')
52
+ }
53
+ }
54
+
55
+ return (
56
+ <>
57
+ <Metadata title="Login" />
58
+
59
+ <main className="rw-main">
60
+ <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} />
61
+ <div className="rw-scaffold rw-login-container">
62
+ <div className="rw-segment">
63
+ <header className="rw-segment-header">
64
+ <h2 className="rw-heading rw-heading-secondary">Login</h2>
65
+ </header>
66
+
67
+ <div className="rw-segment-main">
68
+ <div className="rw-form-wrapper">
69
+ <Form onSubmit={onSubmit} className="rw-form-wrapper">
70
+ <Label
71
+ name="${usernameCamelCase}"
72
+ className="rw-label"
73
+ errorClassName="rw-label rw-label-error"
74
+ >
75
+ ${usernameTitleCase}
76
+ </Label>
77
+ <TextField
78
+ name="${usernameCamelCase}"
79
+ className="rw-input"
80
+ errorClassName="rw-input rw-input-error"
81
+ ref={${usernameCamelCase}Ref}
82
+ validation={{
83
+ required: {
84
+ value: true,
85
+ message: '${usernameTitleCase} is required',
86
+ },
87
+ }}
88
+ />
89
+
90
+ <FieldError name="${usernameCamelCase}" className="rw-field-error" />
91
+
92
+ <Label
93
+ name="${passwordCamelCase}"
94
+ className="rw-label"
95
+ errorClassName="rw-label rw-label-error"
96
+ >
97
+ ${passwordTitleCase}
98
+ </Label>
99
+ <PasswordField
100
+ name="${passwordCamelCase}"
101
+ className="rw-input"
102
+ errorClassName="rw-input rw-input-error"
103
+ autoComplete="current-password"
104
+ validation={{
105
+ required: {
106
+ value: true,
107
+ message: '${passwordTitleCase} is required',
108
+ },
109
+ }}
110
+ />
111
+
112
+ <div className="rw-forgot-link">
113
+ <Link
114
+ to={routes.forgotPassword()}
115
+ className="rw-forgot-link"
116
+ >
117
+ Forgot ${passwordTitleCase}?
118
+ </Link>
119
+ </div>
120
+
121
+ <FieldError name="${passwordCamelCase}" className="rw-field-error" />
122
+
123
+ <div className="rw-button-group">
124
+ <Submit className="rw-button rw-button-blue">Login</Submit>
125
+ </div>
126
+ </Form>
127
+
128
+ {/*
129
+ * OAuth login buttons. Delete the ones for providers you
130
+ * haven't configured in `cedar setup auth dbAuth --oauth`.
131
+ * Each link is a full-page navigation to the provider's
132
+ * authorize route — it must not be turned into a fetch/XHR
133
+ * call.
134
+ */}
135
+ <div className="rw-button-group">
136
+ <a
137
+ href={getOAuthUrl('google', { flow: 'login' })}
138
+ className="rw-button rw-button-blue"
139
+ >
140
+ Continue with Google
141
+ </a>
142
+ <a
143
+ href={getOAuthUrl('github', { flow: 'login' })}
144
+ className="rw-button rw-button-blue"
145
+ >
146
+ Continue with GitHub
147
+ </a>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ <div className="rw-login-link">
153
+ <span>Don&apos;t have an account?</span>{' '}
154
+ <Link to={routes.signup()} className="rw-link">
155
+ Sign up!
156
+ </Link>
157
+ </div>
158
+ </div>
159
+ </main>
160
+ </>
161
+ )
162
+ }
163
+
164
+ export default LoginPage