@cedarjs/cli 7.0.1-next.314 → 7.0.1-next.466

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 (59) hide show
  1. package/dist/commands/deploy/baremetal/baremetalHandler.js +375 -76
  2. package/dist/commands/deploy/baremetal/monitors.js +66 -0
  3. package/dist/commands/deploy/baremetal.js +6 -0
  4. package/dist/commands/destroy/scaffold/scaffoldHandler.js +63 -25
  5. package/dist/commands/dev/devHandler.js +13 -1
  6. package/dist/commands/generate/dbAuth/dbAuth.js +6 -2
  7. package/dist/commands/generate/dbAuth/dbAuthHandler.js +57 -14
  8. package/dist/commands/generate/dbAuth/templates/changePassword.tsx.template +131 -0
  9. package/dist/commands/generate/dbAuth/templates/login.webAuthn.tsx.template +20 -12
  10. package/dist/commands/generate/directive/directive.js +15 -2
  11. package/dist/commands/generate/helpers.js +26 -0
  12. package/dist/commands/generate/ogImage/templates/ogImage.og.tsx.template +1 -1
  13. package/dist/commands/generate/package/templates/tsconfig.json.template +1 -0
  14. package/dist/commands/generate/realtime/templates/liveQueries/blank/blank.service.ts.template +12 -4
  15. package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.service.ts.template +2 -2
  16. package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.ts.template +3 -3
  17. package/dist/commands/generate/scaffold/scaffold.js +1 -1
  18. package/dist/commands/generate/scaffold/scaffoldHandler.js +83 -44
  19. package/dist/commands/generate/scaffold/templates/assets/scaffold.tailwind.css.template +12 -7
  20. package/dist/commands/generate/scaffold/templates/components/EditNameCell.tsx.template +7 -7
  21. package/dist/commands/generate/scaffold/templates/components/NameForm.tsx.template +7 -6
  22. package/dist/commands/generate/scaffold/templates/layouts/NamesScaffoldLayout.tsx.template +18 -0
  23. package/dist/commands/generate/scaffold/templates/lib/formatters.tsx.template +3 -3
  24. package/dist/commands/generate/script/templates/tsconfig.json.template +1 -0
  25. package/dist/commands/generate/sdl/sdlHandler.js +8 -3
  26. package/dist/commands/generate/service/serviceHandler.js +32 -11
  27. package/dist/commands/generate/service/templates/service.ts.template +21 -8
  28. package/dist/commands/generate/service/templates/test.ts.template +7 -5
  29. package/dist/commands/jobsHandler.js +5 -3
  30. package/dist/commands/setup/cache/templates/memcached.ts.template +14 -17
  31. package/dist/commands/setup/cache/templates/redis.ts.template +12 -20
  32. package/dist/commands/setup/deploy/helpers/index.js +3 -1
  33. package/dist/commands/setup/deploy/providers/baremetal.js +7 -0
  34. package/dist/commands/setup/deploy/providers/baremetalHandler.js +128 -20
  35. package/dist/commands/setup/deploy/providers/netlifyHandler.js +25 -3
  36. package/dist/commands/setup/deploy/templates/baremetal.js +119 -9
  37. package/dist/commands/setup/deploy/templates/netlifyUD.js +3 -0
  38. package/dist/commands/setup/jobs/jobsHandler.js +2 -0
  39. package/dist/commands/setup/monitoring/sentry/sentryHandler.js +5 -2
  40. package/dist/commands/setup/monitoring/sentry/templates/sentryApi.ts.template +1 -4
  41. package/dist/commands/setup/monitoring/sentry/templates/sentryWeb.ts.template +7 -4
  42. package/dist/commands/setup/realtime/templates/defer/fastAndSlowFields/fastAndSlowFields.ts.template +4 -1
  43. package/dist/commands/setup/realtime/templates/liveQueries/auctions/auctions.ts.template +12 -4
  44. package/dist/commands/setup/realtime/templates/subscriptions/newMessage/newMessage.ts.template +1 -1
  45. package/dist/commands/setup/tenancy/signupCodemod.js +2 -1
  46. package/dist/commands/setup/tenancy/templates/InvitePage.tsx.template +10 -1
  47. package/dist/commands/setup/tenancy/templates/organizations.scenarios.ts.template +0 -2
  48. package/dist/commands/setup/tenancy/templates/organizations.test.ts.template +0 -8
  49. package/dist/commands/setup/tenancy/templates/organizations.ts.template +1 -1
  50. package/dist/commands/setup/tenancy/templates/requireMembership.test.ts.template +1 -1
  51. package/dist/commands/setup/tenancy/tenancyHandler.js +1 -1
  52. package/dist/commands/setup/ui/libraries/tailwindcssHandler.js +61 -81
  53. package/dist/commands/setup/uploads/gitignore.js +17 -0
  54. package/dist/commands/setup/uploads/serverFile.js +93 -27
  55. package/dist/commands/setup/uploads/templates/uploads.ts.template +14 -5
  56. package/dist/commands/setup/uploads/uploadsHandler.js +22 -1
  57. package/dist/lib/index.js +10 -6
  58. package/package.json +24 -24
  59. package/dist/commands/setup/ui/templates/postcss.config.cjs.template +0 -9
@@ -1,9 +1,17 @@
1
1
  // api/src/services/${name}s/${name}s.ts
2
+ import type { ${typeName}ItemInput } from 'types/graphql'
3
+
2
4
  import type { LiveQueryStorageMechanism } from '@cedarjs/realtime'
3
5
 
4
6
  import { logger } from 'src/lib/logger'
5
7
 
6
- const ${collectionName} = [
8
+ interface ${typeName}Data {
9
+ id: string
10
+ title: string
11
+ items: { amount: number }[]
12
+ }
13
+
14
+ const ${collectionName}: ${typeName}Data[] = [
7
15
  { id: '1', title: 'First ${modelName}', items: [{ amount: 11 }] },
8
16
  { id: '2', title: '2nd ${modelName}', items: [{ amount: 22 }] },
9
17
  { id: '3', title: 'Third ${modelName}', items: [{ amount: 33 }] },
@@ -35,14 +43,14 @@ const ${collectionName} = [
35
43
  * }
36
44
  * }
37
45
  */
38
- export const ${liveQueryName} = async ({ id }) => {
46
+ export const ${liveQueryName} = async ({ id }: { id: string }) => {
39
47
  const found${modelName} = ${collectionName}.find((a) => a.id === id)
40
48
  logger.debug({ id, ${name}: found${modelName} }, `${name} details`)
41
49
  return found${modelName}
42
50
  }
43
51
 
44
52
  export const create${typeName}Item = async (
45
- { input },
53
+ { input }: { input: ${typeName}ItemInput },
46
54
  { context }: { context: { liveQueryStore: LiveQueryStorageMechanism } }
47
55
  ) => {
48
56
  const { ${camelName}Id, amount } = input
@@ -63,7 +71,7 @@ export const create${typeName}Item = async (
63
71
  }
64
72
 
65
73
  export const ${modelName} = {
66
- largest${typeName}Item: (obj, { root }) => {
74
+ largest${typeName}Item: (obj: unknown, { root }: { root: ${typeName}Data }) => {
67
75
  const [largest] = root.items.sort((a, b) => b.amount - a.amount)
68
76
 
69
77
  logger.debug({ obj, root }, 'largest ${typeName}Item')
@@ -1,9 +1,9 @@
1
1
  import type { ${subscriptionInputType} } from 'types/graphql'
2
2
 
3
3
  import { logger } from 'src/lib/logger'
4
- import type { Publish${typeName}ChannelType } from 'src/subscriptions/${name}/${name}.service.js'
4
+ import type { Publish${typeName}ChannelType } from 'src/subscriptions/${name}/${name}.js'
5
5
 
6
- export const ${subscriptionQueryName} = ({ id }) => [id]
6
+ export const ${subscriptionQueryName} = ({ id }: { id: string }) => [id]
7
7
 
8
8
  export const ${subscriptionServiceResolver} = async (
9
9
  { input }: { input: ${subscriptionInputType} },
@@ -39,15 +39,15 @@ export type Publish${typeName}ChannelType = PubSub<Publish${typeName}Channel>
39
39
  const ${subscriptionName}Subscription = {
40
40
  ${subscriptionName}: {
41
41
  subscribe: (
42
- _,
43
- { id },
42
+ _: unknown,
43
+ { id }: { id: string },
44
44
  { pubSub }: { pubSub: Publish${typeName}ChannelType }
45
45
  ) => {
46
46
  logger.debug({ id }, '${name} subscription')
47
47
 
48
48
  return pubSub.subscribe('${typeName}', id)
49
49
  },
50
- resolve: (payload) => {
50
+ resolve: (payload: Publish${typeName}Channel['${typeName}'][1]) => {
51
51
  logger.debug({ payload }, '${name} subscription resolve')
52
52
 
53
53
  return payload
@@ -13,7 +13,7 @@ const builder = (yargs) => {
13
13
  description: "Generate test files",
14
14
  type: "boolean"
15
15
  }).option("tailwind", {
16
- description: "Generate TailwindCSS version of scaffold.css (automatically set to `true` if TailwindCSS config exists)",
16
+ description: "Generate TailwindCSS version of scaffold.css (automatically set to `true` if the web side depends on `tailwindcss`)",
17
17
  type: "boolean"
18
18
  }).option("rollback", {
19
19
  description: "Revert all generator actions if an error occurs",
@@ -86,6 +86,18 @@ const getImportComponentNames = (name, scaffoldPath, nestScaffoldByModel = true)
86
86
  importLayoutNames: `src/layouts/ScaffoldLayout`
87
87
  };
88
88
  };
89
+ const getScaffoldLayoutNames = (name, scaffoldPath = "") => {
90
+ const pluralPascalName = pascalcase(pluralize(name));
91
+ const directoryName = `${pluralPascalName}ScaffoldLayout`;
92
+ const pathParts = scaffoldPath === "" ? [] : scaffoldPath.split("/").map(pascalcase);
93
+ return {
94
+ directoryName,
95
+ pathParts,
96
+ // The name to import the layout as in Routes.{jsx,tsx}
97
+ importName: pascalcase(scaffoldPath) + directoryName,
98
+ importPath: ["src/layouts", ...pathParts, directoryName].join("/")
99
+ };
100
+ };
89
101
  const getTemplateStrings = (name, scaffoldPath, nestScaffoldByModel = true) => {
90
102
  const nameVars = nameVariants(name);
91
103
  const camelScaffoldPath = camelcase(pascalcase(scaffoldPath));
@@ -97,11 +109,21 @@ const getTemplateStrings = (name, scaffoldPath, nestScaffoldByModel = true) => {
97
109
  ...getImportComponentNames(name, scaffoldPath, nestScaffoldByModel)
98
110
  };
99
111
  };
112
+ const webDependsOnTailwindCSS = () => {
113
+ const webPackageJsonPath = path.join(getPaths().web.base, "package.json");
114
+ if (!fs.existsSync(webPackageJsonPath)) {
115
+ return false;
116
+ }
117
+ const { dependencies, devDependencies } = JSON.parse(
118
+ fs.readFileSync(webPackageJsonPath, "utf-8")
119
+ );
120
+ return Boolean(
121
+ dependencies?.["tailwindcss"] || devDependencies?.["tailwindcss"]
122
+ );
123
+ };
100
124
  const shouldUseTailwindCSS = (flag) => {
101
125
  if (flag === void 0) {
102
- return fs.existsSync(
103
- path.join(getPaths().web.config, "tailwind.config.cjs")
104
- );
126
+ return webDependsOnTailwindCSS();
105
127
  } else {
106
128
  return flag;
107
129
  }
@@ -150,7 +172,13 @@ const files = async ({
150
172
  }),
151
173
  ...await assetFiles(name, tailwind),
152
174
  ...await formatters(name, typescript),
153
- ...await layoutFiles(name, force, typescript, templateStrings),
175
+ ...await layoutFiles(
176
+ name,
177
+ scaffoldPath,
178
+ force,
179
+ typescript,
180
+ templateStrings
181
+ ),
154
182
  ...await pageFiles(
155
183
  name,
156
184
  pascalScaffoldPath,
@@ -348,41 +376,57 @@ const modelRelatedVariables = (model) => {
348
376
  parsedDateTime: isDateTimeParsed()
349
377
  };
350
378
  };
351
- const layoutFiles = async (name, force, generateTypescript, templateStrings) => {
379
+ const layoutFiles = async (name, scaffoldPath, force, generateTypescript, templateStrings) => {
352
380
  const fileList = {};
353
- const layouts = fs.readdirSync(
381
+ const extension = generateTypescript ? ".tsx" : ".jsx";
382
+ const renderLayout = async (templateName, outputPath) => {
383
+ const template = await generateTemplate(
384
+ customOrDefaultTemplatePath({
385
+ side: "web",
386
+ generator: "scaffold",
387
+ templatePath: path.join("layouts", templateName)
388
+ }),
389
+ {
390
+ name,
391
+ pascalScaffoldPath: "",
392
+ ...templateStrings
393
+ }
394
+ );
395
+ fileList[outputPath] = generateTypescript || !outputPath.endsWith(".jsx") ? template : await transformTSToJS(outputPath, template);
396
+ };
397
+ const sharedLayoutDir = path.join(getPaths().web.layouts, "ScaffoldLayout");
398
+ const sharedLayoutTemplates = fs.readdirSync(
354
399
  customOrDefaultTemplatePath({
355
400
  side: "web",
356
401
  generator: "scaffold",
357
402
  templatePath: "layouts"
358
403
  })
359
- );
360
- for (const layout of layouts) {
361
- const outputLayoutName = layout.replace(
362
- /\.tsx\.template/,
363
- generateTypescript ? ".tsx" : ".jsx"
364
- );
365
- const outputPath = path.join(
366
- getPaths().web.layouts,
367
- "ScaffoldLayout",
368
- outputLayoutName
369
- );
370
- if (!fs.existsSync(outputPath) || force) {
371
- const template = await generateTemplate(
372
- customOrDefaultTemplatePath({
373
- side: "web",
374
- generator: "scaffold",
375
- templatePath: path.join("layouts", layout)
376
- }),
377
- {
378
- name,
379
- pascalScaffoldPath: "",
380
- ...templateStrings
381
- }
382
- );
383
- fileList[outputPath] = generateTypescript ? template : await transformTSToJS(outputPath, template);
404
+ ).filter((template) => template !== "NamesScaffoldLayout.tsx.template");
405
+ for (const template of sharedLayoutTemplates) {
406
+ const isComponent = template.endsWith(".tsx.template");
407
+ const outputName = isComponent ? template.replace(/\.tsx\.template$/, extension) : template.replace(/\.template$/, "");
408
+ const exists = isComponent ? [".tsx", ".jsx", ".ts", ".js"].some(
409
+ (ext) => fs.existsSync(
410
+ path.join(sharedLayoutDir, outputName.replace(extension, ext))
411
+ )
412
+ ) : fs.existsSync(path.join(sharedLayoutDir, outputName));
413
+ if (!exists || force) {
414
+ await renderLayout(template, path.join(sharedLayoutDir, outputName));
384
415
  }
385
416
  }
417
+ const { directoryName, pathParts } = getScaffoldLayoutNames(
418
+ name,
419
+ scaffoldPath
420
+ );
421
+ await renderLayout(
422
+ "NamesScaffoldLayout.tsx.template",
423
+ path.join(
424
+ getPaths().web.layouts,
425
+ ...pathParts,
426
+ directoryName,
427
+ directoryName + extension
428
+ )
429
+ );
386
430
  return fileList;
387
431
  };
388
432
  const pageFiles = async (name, pascalScaffoldPath = "", generateTypescript, nestScaffoldByModel = true, templateStrings) => {
@@ -424,7 +468,7 @@ const pageFiles = async (name, pascalScaffoldPath = "", generateTypescript, nest
424
468
  ...modelRelatedVariables(model)
425
469
  }
426
470
  );
427
- fileList[outputPath] = generateTypescript ? template : await transformTSToJS(outputPath, template);
471
+ fileList[outputPath] = generateTypescript || !outputPath.endsWith(".jsx") ? template : await transformTSToJS(outputPath, template);
428
472
  }
429
473
  return fileList;
430
474
  };
@@ -472,7 +516,7 @@ const componentFiles = async (name, pascalScaffoldPath = "", generateTypescript,
472
516
  ...modelRelatedVariables(model)
473
517
  }
474
518
  );
475
- fileList[outputPath] = generateTypescript ? template : await transformTSToJS(outputPath, template);
519
+ fileList[outputPath] = generateTypescript || !outputPath.endsWith(".jsx") ? template : await transformTSToJS(outputPath, template);
476
520
  }
477
521
  return fileList;
478
522
  };
@@ -503,8 +547,9 @@ const routes = async ({
503
547
  `<Route path="/${paramScaffoldPath}${nameVars.pluralParamName}" page={${pageRoot}${nameVars.pluralPascalName}Page} name="${templateNames.pluralRouteName}" />`
504
548
  ];
505
549
  };
506
- const addLayoutImport = () => {
507
- const importLayout = `import ScaffoldLayout from 'src/layouts/ScaffoldLayout'`;
550
+ const addLayoutImport = (model, scaffoldPath) => {
551
+ const { importName, importPath } = getScaffoldLayoutNames(model, scaffoldPath);
552
+ const importLayout = `import ${importName} from '${importPath}'`;
508
553
  const routesPath = getPaths().web.routes;
509
554
  const routesContent = readFile(routesPath).toString();
510
555
  if (!routesContent.match(importLayout)) {
@@ -618,17 +663,10 @@ const addSetImport = (task) => {
618
663
  return `Added ${namesToImport.join(", ")} import to Routes.{jsx,tsx}`;
619
664
  };
620
665
  const addScaffoldSetToRouter = async (model, scaffoldPath) => {
621
- const templateNames = getTemplateStrings(model, scaffoldPath);
622
- const nameVars = nameVariants(model);
623
- const title = nameVars.pluralPascalName;
624
- const titleTo = templateNames.pluralRouteName;
625
- const buttonLabel = `New ${nameVars.singularPascalName}`;
626
- const buttonTo = templateNames.newRouteName;
627
666
  const unauthenticatedRoute = getUnauthenticatedRedirectRoute();
628
667
  return addRoutesToRouterTask(
629
668
  await routes({ model, path: scaffoldPath }),
630
- "ScaffoldLayout",
631
- { title, titleTo, buttonLabel, buttonTo },
669
+ getScaffoldLayoutNames(model, scaffoldPath).importName,
632
670
  unauthenticatedRoute ? { unauthenticated: unauthenticatedRoute } : void 0
633
671
  );
634
672
  };
@@ -669,7 +707,7 @@ const tasks = ({
669
707
  },
670
708
  {
671
709
  title: "Adding layout import...",
672
- task: async () => addLayoutImport()
710
+ task: async () => addLayoutImport(model, path2)
673
711
  },
674
712
  {
675
713
  title: "Adding set import...",
@@ -785,6 +823,7 @@ const splitPathAndModel = (pathSlashModel) => {
785
823
  };
786
824
  export {
787
825
  files,
826
+ getScaffoldLayoutNames,
788
827
  getUnauthenticatedRedirectRoute,
789
828
  handler,
790
829
  hasLoginRoute,
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Makes your app's Tailwind theme and utilities available to `@apply`
3
+ * without emitting any of the styles in index.css a second time
4
+ */
5
+ @reference './index.css';
6
+
1
7
  .rw-scaffold {
2
8
  @apply bg-white text-gray-600;
3
9
  }
@@ -27,8 +33,7 @@
27
33
  @apply mx-4 pb-4;
28
34
  }
29
35
  .rw-segment {
30
- @apply rounded-lg overflow-hidden w-full border border-gray-200;
31
- scrollbar-color: theme('colors.zinc.400') transparent;
36
+ @apply rounded-lg overflow-hidden w-full border border-gray-200 scrollbar-thumb-zinc-400 scrollbar-track-transparent;
32
37
  }
33
38
  .rw-segment::-webkit-scrollbar {
34
39
  height: initial;
@@ -80,7 +85,7 @@
80
85
  }
81
86
  .rw-cell-error,
82
87
  .rw-form-error-wrapper {
83
- @apply p-4 bg-red-50 text-red-600 border border-red-100 rounded my-4;
88
+ @apply p-4 bg-red-50 text-red-600 border border-red-100 rounded-sm my-4;
84
89
  }
85
90
  .rw-form-error-title {
86
91
  @apply m-0 font-semibold;
@@ -89,13 +94,13 @@
89
94
  @apply mt-2 list-disc list-inside;
90
95
  }
91
96
  .rw-button {
92
- @apply flex justify-center py-1 px-4 border-0 rounded bg-gray-200 text-gray-500 text-xs font-semibold uppercase tracking-wide leading-loose no-underline cursor-pointer transition duration-100;
97
+ @apply flex justify-center py-1 px-4 border-0 rounded-sm bg-gray-200 text-gray-500 text-xs font-semibold uppercase tracking-wide leading-loose no-underline cursor-pointer transition duration-100;
93
98
  }
94
99
  .rw-button:hover {
95
100
  @apply bg-gray-500 text-white;
96
101
  }
97
102
  .rw-button.rw-button-small {
98
- @apply text-xs rounded-sm py-1 px-2;
103
+ @apply text-xs rounded-xs py-1 px-2;
99
104
  }
100
105
  .rw-button.rw-button-green {
101
106
  @apply bg-green-500 text-white;
@@ -134,7 +139,7 @@
134
139
  @apply text-red-600;
135
140
  }
136
141
  .rw-input {
137
- @apply block mt-2 w-full p-2 bg-white border border-gray-200 rounded outline-none;
142
+ @apply block mt-2 w-full p-2 bg-white border border-gray-200 rounded-sm outline-hidden;
138
143
  }
139
144
  .rw-check-radio-items {
140
145
  @apply flex justify-items-center;
@@ -153,7 +158,7 @@
153
158
  @apply border-red-600 text-red-600;
154
159
  }
155
160
  .rw-input-error:focus {
156
- @apply border-red-600 outline-none;
161
+ @apply border-red-600 outline-hidden;
157
162
  box-shadow: 0 0 5px #c53030;
158
163
  }
159
164
  .rw-field-error {
@@ -1,6 +1,7 @@
1
1
  ${useClientDirective}import type {
2
2
  Edit${singularPascalName}By${pascalIdName},
3
3
  Update${singularPascalName}Input,
4
+ Update${singularPascalName}Mutation,
4
5
  Update${singularPascalName}MutationVariables
5
6
  } from 'types/graphql'
6
7
 
@@ -24,7 +25,7 @@ export const QUERY: TypedDocumentNode<Edit${singularPascalName}By${pascalIdName}
24
25
  `
25
26
 
26
27
  const UPDATE_${singularConstantName}_MUTATION: TypedDocumentNode<
27
- Edit${singularPascalName}ById,
28
+ Update${singularPascalName}Mutation,
28
29
  Update${singularPascalName}MutationVariables
29
30
  > = gql`
30
31
  mutation Update${singularPascalName}Mutation($${idName}: ${idType}!, $input: Update${singularPascalName}Input!) {
@@ -36,6 +37,8 @@ const UPDATE_${singularConstantName}_MUTATION: TypedDocumentNode<
36
37
 
37
38
  export const Loading = () => <div>Loading...</div>
38
39
 
40
+ export const Empty = () => <div>${singularPascalName} not found</div>
41
+
39
42
  export const Failure = ({ error }: CellFailureProps) => (
40
43
  <div className="rw-cell-error">{error?.message}</div>
41
44
  )
@@ -54,17 +57,14 @@ export const Success = ({ ${singularCamelName} }: CellSuccessProps<Edit${singula
54
57
  }
55
58
  )
56
59
 
57
- const onSave = (
58
- input: Update${singularPascalName}Input,
59
- id: Edit${singularPascalName}By${pascalIdName}['${singularCamelName}']['id']
60
- ) => {
61
- update${singularPascalName}({ variables: { id, input } })
60
+ const onSave = (input: Update${singularPascalName}Input) => {
61
+ update${singularPascalName}({ variables: { ${idName}: ${singularCamelName}.${idName}, input } })
62
62
  }
63
63
 
64
64
  return (
65
65
  <div className="rw-segment">
66
66
  <header className="rw-segment-header">
67
- <h2 className="rw-heading rw-heading-secondary">Edit ${singularPascalName} {${singularCamelName}?.id}</h2>
67
+ <h2 className="rw-heading rw-heading-secondary">Edit ${singularPascalName} {${singularCamelName}.${idName}}</h2>
68
68
  </header>
69
69
  <div className="rw-segment-main">
70
70
  <${singularPascalName}Form ${singularCamelName}={${singularCamelName}} onSave={onSave} error={error} loading={loading} />
@@ -1,4 +1,4 @@
1
- import type { Edit${singularPascalName}By${pascalIdName}, Update${singularPascalName}Input } from 'types/graphql'
1
+ import type { Edit${singularPascalName}By${pascalIdName}, Create${singularPascalName}Input } from 'types/graphql'
2
2
 
3
3
  import type { RWGqlError } from '@cedarjs/forms'
4
4
  import {
@@ -48,8 +48,8 @@ type Form${singularPascalName} = NonNullable<Edit${singularPascalName}By${pascal
48
48
 
49
49
  interface ${singularPascalName}FormProps {
50
50
  ${singularCamelName}?: Edit${singularPascalName}By${pascalIdName}['${singularCamelName}']
51
- onSave: (data: Update${singularPascalName}Input, ${idName}?: Form${singularPascalName}['${idName}']) => void
52
- error: RWGqlError
51
+ onSave: (data: Create${singularPascalName}Input) => void
52
+ error?: RWGqlError
53
53
  loading: boolean
54
54
  }
55
55
 
@@ -70,7 +70,8 @@ const ${singularPascalName}Form = (props: ${singularPascalName}FormProps) => {
70
70
  }
71
71
  <% } %>
72
72
  <% if (column.isEnum && !column.isList && !column.isRequired) { %>
73
- if (data.${column.name} === '') {
73
+ // The "None" option has an empty value
74
+ if (!data.${column.name}) {
74
75
  data.${column.name} = null
75
76
  }
76
77
  <% } %>
@@ -80,7 +81,7 @@ const ${singularPascalName}Form = (props: ${singularPascalName}FormProps) => {
80
81
  }
81
82
  <% } %>
82
83
  <% }) %>
83
- props.onSave(data, props?.${singularCamelName}?.${idName})
84
+ props.onSave(data)
84
85
  }
85
86
 
86
87
  return (
@@ -136,7 +137,7 @@ const ${singularPascalName}Form = (props: ${singularPascalName}FormProps) => {
136
137
  <% } else { %>
137
138
  <${column.component}
138
139
  name="${column.name}"
139
- ${column.defaultProp}={${column.deserializeFunction && column.deserializeFunction + '('}props.${singularCamelName}?.<%= column.name %>${column.deserializeFunction && ')'}}
140
+ ${column.defaultProp}={${column.deserializeFunction && column.deserializeFunction + '('}props.${singularCamelName}?.<%= column.name %>${column.deserializeFunction && ')'}<%= !column.deserializeFunction && !column.isRequired ? ' ?? undefined' : '' %>}
140
141
  className="rw-input"
141
142
  errorClassName="rw-input rw-input-error"
142
143
  <%= !column.validation ? '' : `validation=${column.validation}` %>
@@ -0,0 +1,18 @@
1
+ import type { WrapperProps } from '@cedarjs/router'
2
+
3
+ import ScaffoldLayout from '${importLayoutNames}'
4
+
5
+ function ${pluralPascalName}ScaffoldLayout({ children }: WrapperProps) {
6
+ return (
7
+ <ScaffoldLayout
8
+ title="${pluralPascalName}"
9
+ titleTo="${pluralRouteName}"
10
+ buttonLabel="New ${singularPascalName}"
11
+ buttonTo="${newRouteName}"
12
+ >
13
+ {children}
14
+ </ScaffoldLayout>
15
+ )
16
+ }
17
+
18
+ export default ${pluralPascalName}ScaffoldLayout
@@ -25,7 +25,7 @@ export const jsonDisplay = (obj: unknown) => {
25
25
  )
26
26
  }
27
27
 
28
- export const truncate = (value: string | number) => {
28
+ export const truncate = (value: string | number | null | undefined) => {
29
29
  let output = value?.toString() ?? ''
30
30
 
31
31
  if (output.length > MAX_STRING_LENGTH) {
@@ -65,6 +65,6 @@ export function timeTag(dateTime: <%= parsedDateTime ? 'Date' : 'string' %> | un
65
65
  return output
66
66
  }
67
67
 
68
- export const checkboxInputTag = (checked: boolean) => {
69
- return <input type="checkbox" checked={checked} disabled />
68
+ export const checkboxInputTag = (checked: boolean | null | undefined) => {
69
+ return <input type="checkbox" checked={!!checked} disabled />
70
70
  }
@@ -2,6 +2,7 @@
2
2
  "compilerOptions": {
3
3
  "noEmit": true,
4
4
  "allowJs": true,
5
+ "strict": true,
5
6
  "esModuleInterop": true,
6
7
  "baseUrl": "./",
7
8
  "rootDirs": [
@@ -19,7 +19,11 @@ import {
19
19
  getEnum,
20
20
  verifyModelName
21
21
  } from "../../../lib/schemaHelpers.js";
22
- import { redactedModelFields, relationsForModel } from "../helpers.js";
22
+ import {
23
+ DEFAULT_IGNORE_FIELDS_FOR_INPUT,
24
+ redactedModelFields,
25
+ relationsForModel
26
+ } from "../helpers.js";
23
27
  import { files as serviceFiles } from "../service/serviceHandler.js";
24
28
  import { templateForFile } from "../yargsHandlerHelpers.js";
25
29
  import {
@@ -27,7 +31,6 @@ import {
27
31
  missingRelatedModels,
28
32
  writeFilesWithStubsTask
29
33
  } from "./stubFiles.js";
30
- const DEFAULT_IGNORE_FIELDS_FOR_INPUT = ["createdAt", "updatedAt"];
31
34
  const missingIdConsoleMessage = () => {
32
35
  const line1 = ansis.bold.yellow("WARNING") + ": Cannot generate CRUD SDL without an `@id` database column.";
33
36
  const line2 = "If you are trying to generate for a many-to-many join table ";
@@ -61,7 +64,8 @@ const modelFieldToSDL = ({
61
64
  Bytes: "Byte"
62
65
  };
63
66
  const gqlType = prismaTypeToGraphqlType[field.type] || field.type;
64
- const type = field.isList ? `[${gqlType}]` : gqlType;
67
+ const listItemType = field.kind === "object" ? gqlType : `${gqlType}!`;
68
+ const type = field.isList ? `[${listItemType}]` : gqlType;
65
69
  const isRequired = field.isRequired && required || field.isList || field.isId;
66
70
  const fieldContent = `${field.name}: ${type}${isRequired ? "!" : ""}`;
67
71
  if (docs) {
@@ -332,6 +336,7 @@ const handler = async ({
332
336
  export {
333
337
  files,
334
338
  handler,
339
+ modelFieldToSDL,
335
340
  printRedactedFieldsNote,
336
341
  redactedSensitiveFields,
337
342
  stubFiles
@@ -3,7 +3,14 @@ import camelcase from "camelcase";
3
3
  import { pluralize, singularize } from "@cedarjs/utils/cedarPluralize";
4
4
  import { transformTSToJS } from "../../../lib/index.js";
5
5
  import { getSchema, verifyModelName } from "../../../lib/schemaHelpers.js";
6
- import { redactedModelFields, relationsForModel } from "../helpers.js";
6
+ import {
7
+ DEFAULT_IGNORE_FIELDS_FOR_INPUT,
8
+ nullableJsonFieldsInInput,
9
+ nullableRelationsForModel,
10
+ redactedModelFields,
11
+ relationsForModel,
12
+ requiredFieldsInUpdateInput
13
+ } from "../helpers.js";
7
14
  import { createHandler, templateForFile } from "../yargsHandlerHelpers.js";
8
15
  function isServiceModel(schema) {
9
16
  return typeof schema === "object" && schema !== null && "fields" in schema && "name" in schema;
@@ -59,11 +66,13 @@ function scenarioFieldValue(field) {
59
66
  return randInt;
60
67
  case "Json":
61
68
  return { foo: "bar" };
62
- case "String":
69
+ case "String": {
70
+ const isUnique = field.isUnique || field.isId;
63
71
  if (field.name?.toLowerCase().includes("email")) {
64
- return field.isUnique ? `foo${randInt}@bar.com` : "foo@bar.com";
72
+ return isUnique ? `foo${randInt}@bar.com` : "foo@bar.com";
65
73
  }
66
- return field.isUnique ? `String${randInt}` : "String";
74
+ return isUnique ? `String${randInt}` : "String";
75
+ }
67
76
  case "Bytes":
68
77
  return `new Uint8Array([${randIntArray}])`;
69
78
  default: {
@@ -152,11 +161,14 @@ const fieldsToInput = async (model) => {
152
161
  const modelName = camelcase(singularize(model));
153
162
  const inputObj = {};
154
163
  if (scalarFields.some(
155
- (field) => redactedFields.includes(field.name)
164
+ (field) => redactedFields.includes(field.name) || DEFAULT_IGNORE_FIELDS_FOR_INPUT.includes(field.name) && !field.isUpdatedAt
156
165
  )) {
157
166
  return false;
158
167
  }
159
168
  scalarFields.forEach((field) => {
169
+ if (DEFAULT_IGNORE_FIELDS_FOR_INPUT.includes(field.name)) {
170
+ return;
171
+ }
160
172
  if (foreignKeys.includes(field.name)) {
161
173
  inputObj[field.name] = `scenario.${modelName}.two.${field.name}`;
162
174
  } else {
@@ -174,7 +186,7 @@ const fieldsToUpdate = async (model) => {
174
186
  const modelName = camelcase(singularize(model));
175
187
  let field, newValue, fieldName;
176
188
  const updatableFields = scalarFields.filter(
177
- (scalar) => !redactedFields.includes(scalar.name)
189
+ (scalar) => !redactedFields.includes(scalar.name) && !DEFAULT_IGNORE_FIELDS_FOR_INPUT.includes(scalar.name)
178
190
  );
179
191
  field = updatableFields.find(
180
192
  (scalar) => !foreignKeys.includes(scalar.name)
@@ -273,13 +285,19 @@ const files = async ({
273
285
  }
274
286
  const prismaImportSource = "src/lib/db";
275
287
  let modelRelations;
288
+ let updateInputRequiredFields = [];
289
+ let nullableJsonFields = [];
290
+ let nullableRelations = [];
291
+ const schemaResult = await getSchema(model);
292
+ if (isServiceModel(schemaResult)) {
293
+ updateInputRequiredFields = requiredFieldsInUpdateInput(schemaResult);
294
+ nullableJsonFields = nullableJsonFieldsInInput(schemaResult);
295
+ nullableRelations = nullableRelationsForModel(schemaResult);
296
+ }
276
297
  if (relations) {
277
298
  modelRelations = relations;
278
- } else {
279
- const schemaResult = await getSchema(model);
280
- if (isServiceModel(schemaResult)) {
281
- modelRelations = relationsForModel(schemaResult);
282
- }
299
+ } else if (isServiceModel(schemaResult)) {
300
+ modelRelations = relationsForModel(schemaResult);
283
301
  }
284
302
  const serviceFile = await templateForFile({
285
303
  name,
@@ -292,6 +310,9 @@ const files = async ({
292
310
  relations: modelRelations,
293
311
  idName,
294
312
  prismaImportSource,
313
+ updateInputRequiredFields,
314
+ nullableJsonFields,
315
+ nullableRelations,
295
316
  ...rest
296
317
  }
297
318
  });