@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
@@ -5,14 +5,15 @@ import { paramCase } from "change-case";
5
5
  import humanize from "humanize-string";
6
6
  import { Listr } from "listr2";
7
7
  import pascalcase from "pascalcase";
8
- import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
8
+ import { colors as c } from "@cedarjs/cli-helpers/colors";
9
9
  import { formatCedarCommand } from "@cedarjs/cli-helpers/packageManager/display";
10
10
  import {
11
11
  addWorkspacePackages,
12
12
  removeWorkspacePackages
13
13
  } from "@cedarjs/cli-helpers/packageManager/packages";
14
+ import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
14
15
  import { generate as generateTypes } from "@cedarjs/internal/dist/generate/generate";
15
- import { getConfig } from "@cedarjs/project-config";
16
+ import { getConfig, getParsedScalars } from "@cedarjs/project-config";
16
17
  import { pluralize, singularize } from "@cedarjs/utils/cedarPluralize";
17
18
  import {
18
19
  generateTemplate,
@@ -85,6 +86,18 @@ const getImportComponentNames = (name, scaffoldPath, nestScaffoldByModel = true)
85
86
  importLayoutNames: `src/layouts/ScaffoldLayout`
86
87
  };
87
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
+ };
88
101
  const getTemplateStrings = (name, scaffoldPath, nestScaffoldByModel = true) => {
89
102
  const nameVars = nameVariants(name);
90
103
  const camelScaffoldPath = camelcase(pascalcase(scaffoldPath));
@@ -96,11 +109,21 @@ const getTemplateStrings = (name, scaffoldPath, nestScaffoldByModel = true) => {
96
109
  ...getImportComponentNames(name, scaffoldPath, nestScaffoldByModel)
97
110
  };
98
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
+ };
99
124
  const shouldUseTailwindCSS = (flag) => {
100
125
  if (flag === void 0) {
101
- return fs.existsSync(
102
- path.join(getPaths().web.config, "tailwind.config.cjs")
103
- );
126
+ return webDependsOnTailwindCSS();
104
127
  } else {
105
128
  return flag;
106
129
  }
@@ -149,7 +172,13 @@ const files = async ({
149
172
  }),
150
173
  ...await assetFiles(name, tailwind),
151
174
  ...await formatters(name, typescript),
152
- ...await layoutFiles(name, force, typescript, templateStrings),
175
+ ...await layoutFiles(
176
+ name,
177
+ scaffoldPath,
178
+ force,
179
+ typescript,
180
+ templateStrings
181
+ ),
153
182
  ...await pageFiles(
154
183
  name,
155
184
  pascalScaffoldPath,
@@ -189,6 +218,7 @@ const assetFiles = async (name, tailwind) => {
189
218
  }
190
219
  return fileList;
191
220
  };
221
+ const isDateTimeParsed = () => getParsedScalars().DateTime === "Date";
192
222
  const formatters = async (name, isTypescript) => {
193
223
  const outputPath = path.join(
194
224
  getPaths().web.src,
@@ -210,7 +240,8 @@ const formatters = async (name, isTypescript) => {
210
240
  templatePath: path.join("lib", "formatters.tsx.template")
211
241
  }),
212
242
  {
213
- name
243
+ name,
244
+ parsedDateTime: isDateTimeParsed()
214
245
  }
215
246
  );
216
247
  const templateTest = await generateTemplate(
@@ -220,7 +251,8 @@ const formatters = async (name, isTypescript) => {
220
251
  templatePath: path.join("lib", "formatters.test.tsx.template")
221
252
  }),
222
253
  {
223
- name
254
+ name,
255
+ parsedDateTime: isDateTimeParsed()
224
256
  }
225
257
  );
226
258
  return {
@@ -340,44 +372,61 @@ const modelRelatedVariables = (model) => {
340
372
  fieldsToImport,
341
373
  editableColumns,
342
374
  listFormattersImports,
343
- formattersImports
375
+ formattersImports,
376
+ parsedDateTime: isDateTimeParsed()
344
377
  };
345
378
  };
346
- const layoutFiles = async (name, force, generateTypescript, templateStrings) => {
379
+ const layoutFiles = async (name, scaffoldPath, force, generateTypescript, templateStrings) => {
347
380
  const fileList = {};
348
- 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(
349
399
  customOrDefaultTemplatePath({
350
400
  side: "web",
351
401
  generator: "scaffold",
352
402
  templatePath: "layouts"
353
403
  })
354
- );
355
- for (const layout of layouts) {
356
- const outputLayoutName = layout.replace(
357
- /\.tsx\.template/,
358
- generateTypescript ? ".tsx" : ".jsx"
359
- );
360
- const outputPath = path.join(
361
- getPaths().web.layouts,
362
- "ScaffoldLayout",
363
- outputLayoutName
364
- );
365
- if (!fs.existsSync(outputPath) || force) {
366
- const template = await generateTemplate(
367
- customOrDefaultTemplatePath({
368
- side: "web",
369
- generator: "scaffold",
370
- templatePath: path.join("layouts", layout)
371
- }),
372
- {
373
- name,
374
- pascalScaffoldPath: "",
375
- ...templateStrings
376
- }
377
- );
378
- 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));
379
415
  }
380
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
+ );
381
430
  return fileList;
382
431
  };
383
432
  const pageFiles = async (name, pascalScaffoldPath = "", generateTypescript, nestScaffoldByModel = true, templateStrings) => {
@@ -419,7 +468,7 @@ const pageFiles = async (name, pascalScaffoldPath = "", generateTypescript, nest
419
468
  ...modelRelatedVariables(model)
420
469
  }
421
470
  );
422
- fileList[outputPath] = generateTypescript ? template : await transformTSToJS(outputPath, template);
471
+ fileList[outputPath] = generateTypescript || !outputPath.endsWith(".jsx") ? template : await transformTSToJS(outputPath, template);
423
472
  }
424
473
  return fileList;
425
474
  };
@@ -467,7 +516,7 @@ const componentFiles = async (name, pascalScaffoldPath = "", generateTypescript,
467
516
  ...modelRelatedVariables(model)
468
517
  }
469
518
  );
470
- fileList[outputPath] = generateTypescript ? template : await transformTSToJS(outputPath, template);
519
+ fileList[outputPath] = generateTypescript || !outputPath.endsWith(".jsx") ? template : await transformTSToJS(outputPath, template);
471
520
  }
472
521
  return fileList;
473
522
  };
@@ -498,8 +547,9 @@ const routes = async ({
498
547
  `<Route path="/${paramScaffoldPath}${nameVars.pluralParamName}" page={${pageRoot}${nameVars.pluralPascalName}Page} name="${templateNames.pluralRouteName}" />`
499
548
  ];
500
549
  };
501
- const addLayoutImport = () => {
502
- 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}'`;
503
553
  const routesPath = getPaths().web.routes;
504
554
  const routesContent = readFile(routesPath).toString();
505
555
  if (!routesContent.match(importLayout)) {
@@ -613,17 +663,10 @@ const addSetImport = (task) => {
613
663
  return `Added ${namesToImport.join(", ")} import to Routes.{jsx,tsx}`;
614
664
  };
615
665
  const addScaffoldSetToRouter = async (model, scaffoldPath) => {
616
- const templateNames = getTemplateStrings(model, scaffoldPath);
617
- const nameVars = nameVariants(model);
618
- const title = nameVars.pluralPascalName;
619
- const titleTo = templateNames.pluralRouteName;
620
- const buttonLabel = `New ${nameVars.singularPascalName}`;
621
- const buttonTo = templateNames.newRouteName;
622
666
  const unauthenticatedRoute = getUnauthenticatedRedirectRoute();
623
667
  return addRoutesToRouterTask(
624
668
  await routes({ model, path: scaffoldPath }),
625
- "ScaffoldLayout",
626
- { title, titleTo, buttonLabel, buttonTo },
669
+ getScaffoldLayoutNames(model, scaffoldPath).importName,
627
670
  unauthenticatedRoute ? { unauthenticated: unauthenticatedRoute } : void 0
628
671
  );
629
672
  };
@@ -664,7 +707,7 @@ const tasks = ({
664
707
  },
665
708
  {
666
709
  title: "Adding layout import...",
667
- task: async () => addLayoutImport()
710
+ task: async () => addLayoutImport(model, path2)
668
711
  },
669
712
  {
670
713
  title: "Adding set import...",
@@ -780,6 +823,7 @@ const splitPathAndModel = (pathSlashModel) => {
780
823
  };
781
824
  export {
782
825
  files,
826
+ getScaffoldLayoutNames,
783
827
  getUnauthenticatedRedirectRoute,
784
828
  handler,
785
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!) {
@@ -56,17 +57,14 @@ export const Success = ({ ${singularCamelName} }: CellSuccessProps<Edit${singula
56
57
  }
57
58
  )
58
59
 
59
- const onSave = (
60
- input: Update${singularPascalName}Input,
61
- id: Edit${singularPascalName}By${pascalIdName}['${singularCamelName}']['id']
62
- ) => {
63
- update${singularPascalName}({ variables: { id, input } })
60
+ const onSave = (input: Update${singularPascalName}Input) => {
61
+ update${singularPascalName}({ variables: { ${idName}: ${singularCamelName}.${idName}, input } })
64
62
  }
65
63
 
66
64
  return (
67
65
  <div className="rw-segment">
68
66
  <header className="rw-segment-header">
69
- <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>
70
68
  </header>
71
69
  <div className="rw-segment-main">
72
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 {
@@ -12,17 +12,17 @@ import {
12
12
 
13
13
  <% if (fieldsToImport.includes('DatetimeLocalField')) { %>
14
14
  /**
15
- * Formats a DateTime string from the API, which is a UTC timestamp, for use as
15
+ * Formats a DateTime <%= parsedDateTime ? 'Date object' : 'string' %> from the API, which is a UTC timestamp, for use as
16
16
  * the value of a `datetime-local` input. The input takes `YYYY-MM-DDTHH:mm` in
17
17
  * the user's local time, with no seconds, milliseconds or timezone, and the
18
18
  * form reads its value back as local time when it is submitted.
19
19
  */
20
- function formatDatetime(value: string | undefined | null) {
20
+ function formatDatetime(value: <%= parsedDateTime ? 'Date' : 'string' %> | undefined | null) {
21
21
  if (!value) {
22
22
  return undefined
23
23
  }
24
24
 
25
- const date = new Date(value)
25
+ const date = <%= parsedDateTime ? 'value' : 'new Date(value)' %>
26
26
 
27
27
  if (Number.isNaN(date.getTime())) {
28
28
  return undefined
@@ -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
@@ -83,14 +83,25 @@ describe('jsonTruncate', () => {
83
83
 
84
84
  describe('timeTag', () => {
85
85
  it('renders a date', async () => {
86
+ <% if (parsedDateTime) { %>
87
+ render(<div>{timeTag(new Date('1970-08-20'))}</div>)
88
+ <% } else { %>
86
89
  render(<div>{timeTag(new Date('1970-08-20').toUTCString())}</div>)
90
+ <% } %>
87
91
 
88
92
  await waitFor(() => screen.getByText(/1970.*00:00:00/))
89
93
  })
90
94
 
95
+ <% if (parsedDateTime) { %>
96
+ it('can take an empty input', async () => {
97
+ expect(timeTag(null)).toEqual('')
98
+ expect(timeTag(undefined)).toEqual('')
99
+ })
100
+ <% } else { %>
91
101
  it('can take an empty input string', async () => {
92
102
  expect(timeTag('')).toEqual('')
93
103
  })
104
+ <% } %>
94
105
  })
95
106
 
96
107
  describe('jsonDisplay', () => {
@@ -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) {
@@ -39,20 +39,32 @@ export const jsonTruncate = (obj: unknown) => {
39
39
  return truncate(JSON.stringify(obj, null, 2))
40
40
  }
41
41
 
42
- export function timeTag(dateTime: string | undefined | null) {
42
+ export function timeTag(dateTime: <%= parsedDateTime ? 'Date' : 'string' %> | undefined | null) {
43
43
  let output: string | React.JSX.Element = ''
44
44
 
45
45
  if (dateTime) {
46
+ <% if (parsedDateTime) { %>
47
+ if (!Number.isNaN(dateTime.getTime())) {
48
+ const isoString = dateTime.toISOString()
49
+
50
+ output = (
51
+ <time dateTime={isoString} title={isoString}>
52
+ {dateTime.toUTCString()}
53
+ </time>
54
+ )
55
+ }
56
+ <% } else { %>
46
57
  output = (
47
58
  <time dateTime={dateTime} title={dateTime}>
48
59
  {new Date(dateTime).toUTCString()}
49
60
  </time>
50
61
  )
62
+ <% } %>
51
63
  }
52
64
 
53
65
  return output
54
66
  }
55
67
 
56
- export const checkboxInputTag = (checked: boolean) => {
57
- return <input type="checkbox" checked={checked} disabled />
68
+ export const checkboxInputTag = (checked: boolean | null | undefined) => {
69
+ return <input type="checkbox" checked={!!checked} disabled />
58
70
  }
@@ -1,7 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { Listr } from "listr2";
4
- import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
4
+ import { colors as c } from "@cedarjs/cli-helpers/colors";
5
+ import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
5
6
  import { errorTelemetry } from "@cedarjs/telemetry";
6
7
  import {
7
8
  getPaths,
@@ -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": [
@@ -2,8 +2,9 @@ import ansis from "ansis";
2
2
  import boxen from "boxen";
3
3
  import camelcase from "camelcase";
4
4
  import { Listr } from "listr2";
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 { generate as generateTypes } from "@cedarjs/internal/dist/generate/generate";
8
9
  import { getConfig } from "@cedarjs/project-config";
9
10
  import { errorTelemetry } from "@cedarjs/telemetry";
@@ -18,7 +19,11 @@ import {
18
19
  getEnum,
19
20
  verifyModelName
20
21
  } from "../../../lib/schemaHelpers.js";
21
- import { redactedModelFields, relationsForModel } from "../helpers.js";
22
+ import {
23
+ DEFAULT_IGNORE_FIELDS_FOR_INPUT,
24
+ redactedModelFields,
25
+ relationsForModel
26
+ } from "../helpers.js";
22
27
  import { files as serviceFiles } from "../service/serviceHandler.js";
23
28
  import { templateForFile } from "../yargsHandlerHelpers.js";
24
29
  import {
@@ -26,7 +31,6 @@ import {
26
31
  missingRelatedModels,
27
32
  writeFilesWithStubsTask
28
33
  } from "./stubFiles.js";
29
- const DEFAULT_IGNORE_FIELDS_FOR_INPUT = ["createdAt", "updatedAt"];
30
34
  const missingIdConsoleMessage = () => {
31
35
  const line1 = ansis.bold.yellow("WARNING") + ": Cannot generate CRUD SDL without an `@id` database column.";
32
36
  const line2 = "If you are trying to generate for a many-to-many join table ";
@@ -60,7 +64,8 @@ const modelFieldToSDL = ({
60
64
  Bytes: "Byte"
61
65
  };
62
66
  const gqlType = prismaTypeToGraphqlType[field.type] || field.type;
63
- const type = field.isList ? `[${gqlType}]` : gqlType;
67
+ const listItemType = field.kind === "object" ? gqlType : `${gqlType}!`;
68
+ const type = field.isList ? `[${listItemType}]` : gqlType;
64
69
  const isRequired = field.isRequired && required || field.isList || field.isId;
65
70
  const fieldContent = `${field.name}: ${type}${isRequired ? "!" : ""}`;
66
71
  if (docs) {
@@ -331,6 +336,7 @@ const handler = async ({
331
336
  export {
332
337
  files,
333
338
  handler,
339
+ modelFieldToSDL,
334
340
  printRedactedFieldsNote,
335
341
  redactedSensitiveFields,
336
342
  stubFiles
@@ -1,6 +1,6 @@
1
1
  import crypto from "node:crypto";
2
2
  import { terminalLink } from "termi-link";
3
- import { recordTelemetryAttributes } from "@cedarjs/cli-helpers";
3
+ import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
4
4
  const DEFAULT_LENGTH = 32;
5
5
  const generateSecret = (length = DEFAULT_LENGTH) => {
6
6
  return crypto.randomBytes(Math.trunc(length)).toString("base64");