@cedarjs/cli 8.0.0-canary.3395 → 8.0.0-canary.3397

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.
@@ -1,8 +1,7 @@
1
+ import { parse } from "@babel/parser";
1
2
  import { Listr } from "listr2";
2
- import pascalcase from "pascalcase";
3
3
  import { colors as c } from "@cedarjs/cli-helpers/colors";
4
4
  import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
5
- import { pluralize } from "@cedarjs/utils/cedarPluralize";
6
5
  import {
7
6
  deleteFilesTask,
8
7
  getPaths,
@@ -13,6 +12,7 @@ import {
13
12
  import { verifyModelName } from "../../../lib/schemaHelpers.js";
14
13
  import {
15
14
  files,
15
+ getScaffoldLayoutNames,
16
16
  routes as scaffoldRoutes,
17
17
  splitPathAndModel
18
18
  } from "../../generate/scaffold/scaffoldHandler.js";
@@ -23,45 +23,83 @@ const removeRoutesWithSet = async ({
23
23
  }) => {
24
24
  const routes = await scaffoldRoutes({ model, path, nestScaffoldByModel });
25
25
  const routeNames = routes.map(extractRouteName);
26
- const pluralPascalName = pascalcase(pluralize(model));
27
- const layoutName = `${pluralPascalName}Layout`;
28
- return removeRoutesFromRouterTask(routeNames, layoutName);
26
+ const { importName } = getScaffoldLayoutNames(model, path);
27
+ return removeRoutesFromRouterTask(routeNames, importName);
28
+ };
29
+ const removeNamedImport = (code, source, name) => {
30
+ const program = parse(code, {
31
+ sourceType: "module",
32
+ plugins: ["jsx", "typescript"]
33
+ }).program;
34
+ for (const statement of program.body) {
35
+ if (statement.type !== "ImportDeclaration" || statement.source.value !== source) {
36
+ continue;
37
+ }
38
+ const specifiers = statement.specifiers;
39
+ const index = specifiers.findIndex(
40
+ (specifier2) => specifier2.type === "ImportSpecifier" && specifier2.imported.type === "Identifier" && specifier2.imported.name === name && specifier2.local.name === name
41
+ );
42
+ if (index === -1) {
43
+ continue;
44
+ }
45
+ const specifier = specifiers[index];
46
+ let start;
47
+ let end;
48
+ if (specifiers.length === 1) {
49
+ start = statement.start ?? 0;
50
+ end = statement.end ?? 0;
51
+ if (code[end] === "\n") {
52
+ end++;
53
+ }
54
+ } else if (index === specifiers.length - 1) {
55
+ start = specifiers[index - 1].end ?? 0;
56
+ end = specifier.end ?? 0;
57
+ } else {
58
+ start = specifier.start ?? 0;
59
+ end = specifiers[index + 1].start ?? 0;
60
+ }
61
+ return code.slice(0, start) + code.slice(end);
62
+ }
63
+ return code;
29
64
  };
30
65
  const removeSetImport = () => {
31
66
  const routesPath = getPaths().web.routes;
32
67
  const routesContent = readFile(routesPath).toString();
33
- if (routesContent.match("<Set")) {
68
+ const unusedNames = ["Set", "PrivateSet"].filter(
69
+ (name) => !new RegExp(`<${name}\\b`).test(routesContent)
70
+ );
71
+ if (unusedNames.length === 0) {
34
72
  return "Skipping removal of Set import in Routes.{jsx,tsx}";
35
73
  }
36
- const cedarRouterImportMatch = routesContent.match(
37
- /import {[^]*} from '@cedarjs\/router'/
38
- );
39
- if (!cedarRouterImportMatch) {
40
- return "No @cedarjs/router import found in Routes.{jsx,tsx}";
74
+ let newRoutesContent = routesContent;
75
+ try {
76
+ for (const name of unusedNames) {
77
+ newRoutesContent = removeNamedImport(
78
+ newRoutesContent,
79
+ "@cedarjs/router",
80
+ name
81
+ );
82
+ }
83
+ } catch {
84
+ return "Skipping removal of Set import, Routes.{jsx,tsx} couldn't be parsed";
85
+ }
86
+ if (newRoutesContent === routesContent) {
87
+ return "No unused Set or PrivateSet import found in Routes.{jsx,tsx}";
41
88
  }
42
- const [cedarRouterImport] = cedarRouterImportMatch;
43
- const removedSetImport = cedarRouterImport.replace(/,*\s*Set,*/, "");
44
- const newRoutesContent = routesContent.replace(
45
- cedarRouterImport,
46
- removedSetImport
47
- );
48
89
  writeFile(routesPath, newRoutesContent, { overwriteExisting: true });
49
- return "Removed Set import in Routes.{jsx,tsx}";
90
+ return "Removed unused Set and PrivateSet imports in Routes.{jsx,tsx}";
50
91
  };
51
92
  const removeLayoutImport = ({
52
93
  model: name,
53
94
  path: scaffoldPath = ""
54
95
  }) => {
55
- const pluralPascalName = pascalcase(pluralize(name));
56
- const pascalScaffoldPath = scaffoldPath === "" ? scaffoldPath : scaffoldPath.split("/").map(pascalcase).join("/") + "/";
57
- const layoutName = `${pluralPascalName}Layout`;
58
- const importLayout = `import ${pluralPascalName}Layout from 'src/layouts/${pascalScaffoldPath}${layoutName}'`;
96
+ const { importName, importPath } = getScaffoldLayoutNames(name, scaffoldPath);
59
97
  const routesPath = getPaths().web.routes;
60
98
  const routesContent = readFile(routesPath).toString();
61
- const newRoutesContent = routesContent.replace(
62
- new RegExp(`\\s*${importLayout}`),
63
- ""
99
+ const importLayout = new RegExp(
100
+ `\\s*import ${importName} from (['"])${importPath}\\1;?`
64
101
  );
102
+ const newRoutesContent = routesContent.replace(importLayout, "");
65
103
  writeFile(routesPath, newRoutesContent, { overwriteExisting: true });
66
104
  return "Removed layout import from Routes.{jsx,tsx}";
67
105
  };
@@ -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));
@@ -160,7 +172,13 @@ const files = async ({
160
172
  }),
161
173
  ...await assetFiles(name, tailwind),
162
174
  ...await formatters(name, typescript),
163
- ...await layoutFiles(name, force, typescript, templateStrings),
175
+ ...await layoutFiles(
176
+ name,
177
+ scaffoldPath,
178
+ force,
179
+ typescript,
180
+ templateStrings
181
+ ),
164
182
  ...await pageFiles(
165
183
  name,
166
184
  pascalScaffoldPath,
@@ -358,41 +376,57 @@ const modelRelatedVariables = (model) => {
358
376
  parsedDateTime: isDateTimeParsed()
359
377
  };
360
378
  };
361
- const layoutFiles = async (name, force, generateTypescript, templateStrings) => {
379
+ const layoutFiles = async (name, scaffoldPath, force, generateTypescript, templateStrings) => {
362
380
  const fileList = {};
363
- 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(
364
399
  customOrDefaultTemplatePath({
365
400
  side: "web",
366
401
  generator: "scaffold",
367
402
  templatePath: "layouts"
368
403
  })
369
- );
370
- for (const layout of layouts) {
371
- const outputLayoutName = layout.replace(
372
- /\.tsx\.template/,
373
- generateTypescript ? ".tsx" : ".jsx"
374
- );
375
- const outputPath = path.join(
376
- getPaths().web.layouts,
377
- "ScaffoldLayout",
378
- outputLayoutName
379
- );
380
- if (!fs.existsSync(outputPath) || force) {
381
- const template = await generateTemplate(
382
- customOrDefaultTemplatePath({
383
- side: "web",
384
- generator: "scaffold",
385
- templatePath: path.join("layouts", layout)
386
- }),
387
- {
388
- name,
389
- pascalScaffoldPath: "",
390
- ...templateStrings
391
- }
392
- );
393
- 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));
394
415
  }
395
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
+ );
396
430
  return fileList;
397
431
  };
398
432
  const pageFiles = async (name, pascalScaffoldPath = "", generateTypescript, nestScaffoldByModel = true, templateStrings) => {
@@ -434,7 +468,7 @@ const pageFiles = async (name, pascalScaffoldPath = "", generateTypescript, nest
434
468
  ...modelRelatedVariables(model)
435
469
  }
436
470
  );
437
- fileList[outputPath] = generateTypescript ? template : await transformTSToJS(outputPath, template);
471
+ fileList[outputPath] = generateTypescript || !outputPath.endsWith(".jsx") ? template : await transformTSToJS(outputPath, template);
438
472
  }
439
473
  return fileList;
440
474
  };
@@ -482,7 +516,7 @@ const componentFiles = async (name, pascalScaffoldPath = "", generateTypescript,
482
516
  ...modelRelatedVariables(model)
483
517
  }
484
518
  );
485
- fileList[outputPath] = generateTypescript ? template : await transformTSToJS(outputPath, template);
519
+ fileList[outputPath] = generateTypescript || !outputPath.endsWith(".jsx") ? template : await transformTSToJS(outputPath, template);
486
520
  }
487
521
  return fileList;
488
522
  };
@@ -513,8 +547,9 @@ const routes = async ({
513
547
  `<Route path="/${paramScaffoldPath}${nameVars.pluralParamName}" page={${pageRoot}${nameVars.pluralPascalName}Page} name="${templateNames.pluralRouteName}" />`
514
548
  ];
515
549
  };
516
- const addLayoutImport = () => {
517
- 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}'`;
518
553
  const routesPath = getPaths().web.routes;
519
554
  const routesContent = readFile(routesPath).toString();
520
555
  if (!routesContent.match(importLayout)) {
@@ -628,17 +663,10 @@ const addSetImport = (task) => {
628
663
  return `Added ${namesToImport.join(", ")} import to Routes.{jsx,tsx}`;
629
664
  };
630
665
  const addScaffoldSetToRouter = async (model, scaffoldPath) => {
631
- const templateNames = getTemplateStrings(model, scaffoldPath);
632
- const nameVars = nameVariants(model);
633
- const title = nameVars.pluralPascalName;
634
- const titleTo = templateNames.pluralRouteName;
635
- const buttonLabel = `New ${nameVars.singularPascalName}`;
636
- const buttonTo = templateNames.newRouteName;
637
666
  const unauthenticatedRoute = getUnauthenticatedRedirectRoute();
638
667
  return addRoutesToRouterTask(
639
668
  await routes({ model, path: scaffoldPath }),
640
- "ScaffoldLayout",
641
- { title, titleTo, buttonLabel, buttonTo },
669
+ getScaffoldLayoutNames(model, scaffoldPath).importName,
642
670
  unauthenticatedRoute ? { unauthenticated: unauthenticatedRoute } : void 0
643
671
  );
644
672
  };
@@ -679,7 +707,7 @@ const tasks = ({
679
707
  },
680
708
  {
681
709
  title: "Adding layout import...",
682
- task: async () => addLayoutImport()
710
+ task: async () => addLayoutImport(model, path2)
683
711
  },
684
712
  {
685
713
  title: "Adding set import...",
@@ -795,6 +823,7 @@ const splitPathAndModel = (pathSlashModel) => {
795
823
  };
796
824
  export {
797
825
  files,
826
+ getScaffoldLayoutNames,
798
827
  getUnauthenticatedRedirectRoute,
799
828
  handler,
800
829
  hasLoginRoute,
@@ -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
package/dist/lib/index.js CHANGED
@@ -286,18 +286,17 @@ const cleanupEmptyDirsTask = (files) => {
286
286
  })
287
287
  );
288
288
  };
289
- function wrapWithSet(routesContent, layout, routes, newLineAndIndent, props = {}, privateSetProps) {
289
+ function wrapWithSet(routesContent, layout, routes, newLineAndIndent, privateSetProps) {
290
290
  const [_, indentOne, indentTwo] = routesContent.match(
291
291
  /([ \t]*)<Router.*?>[^<]*[\r\n]+([ \t]+)/
292
292
  ) || ["", "", ""];
293
293
  const oneLevelIndent = indentTwo.slice(0, indentTwo.length - indentOne.length);
294
- const propsString = Object.entries(props).map((values) => `${values[0]}="${values[1]}"`).join(" ");
295
294
  if (!privateSetProps) {
296
295
  const newRoutesWithExtraIndent2 = routes.map(
297
296
  (route) => oneLevelIndent + route
298
297
  );
299
298
  return [
300
- `<Set wrap={${layout}}${propsString && " " + propsString}>`,
299
+ `<Set wrap={${layout}}>`,
301
300
  ...newRoutesWithExtraIndent2,
302
301
  `</Set>`
303
302
  ].join(newLineAndIndent);
@@ -308,13 +307,13 @@ function wrapWithSet(routesContent, layout, routes, newLineAndIndent, props = {}
308
307
  );
309
308
  return [
310
309
  `<PrivateSet${privateSetPropsString && " " + privateSetPropsString}>`,
311
- `${oneLevelIndent}<Set wrap={${layout}}${propsString && " " + propsString}>`,
310
+ `${oneLevelIndent}<Set wrap={${layout}}>`,
312
311
  ...newRoutesWithExtraIndent,
313
312
  `${oneLevelIndent}</Set>`,
314
313
  `</PrivateSet>`
315
314
  ].join(newLineAndIndent);
316
315
  }
317
- function addRoutesToRouterTask(routes, layout, setProps = {}, privateSetProps) {
316
+ function addRoutesToRouterTask(routes, layout, privateSetProps) {
318
317
  const cedarPaths = getPaths();
319
318
  const routesContent = readFile(cedarPaths.web.routes).toString();
320
319
  let newRoutes = routes.filter((route) => !routesContent.match(route));
@@ -338,7 +337,6 @@ ${route}`);
338
337
  layout,
339
338
  newRoutes,
340
339
  newLineAndIndent,
341
- setProps,
342
340
  privateSetProps
343
341
  ) : newRoutes.join(newLineAndIndent);
344
342
  const newRoutesContent = routesContent.replace(
@@ -377,6 +375,12 @@ function removeEmtpySet(routesContent, layout) {
377
375
  if (child.length > 0) {
378
376
  return routesContent;
379
377
  }
378
+ const privateSetWithLayoutReg = new RegExp(
379
+ `\\s*<PrivateSet[^>]*>\\s*<Set[^>]*wrap={${layout}}[^<]*>\\s*</Set>\\s*</PrivateSet>`
380
+ );
381
+ if (privateSetWithLayoutReg.test(routesContent)) {
382
+ return routesContent.replace(privateSetWithLayoutReg, "");
383
+ }
380
384
  return routesContent.replace(setWithLayoutReg, "");
381
385
  }
382
386
  function removeRoutesFromRouterTask(routes, layout) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/cli",
3
- "version": "8.0.0-canary.3395",
3
+ "version": "8.0.0-canary.3397",
4
4
  "description": "The CedarJS Command Line",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,17 +36,17 @@
36
36
  "@babel/preset-typescript": "7.29.7",
37
37
  "@babel/traverse": "7.29.10",
38
38
  "@babel/types": "7.29.8",
39
- "@cedarjs/api-server": "8.0.0-canary.3395",
40
- "@cedarjs/babel-config": "8.0.0-canary.3395",
41
- "@cedarjs/cli-helpers": "8.0.0-canary.3395",
42
- "@cedarjs/internal": "8.0.0-canary.3395",
43
- "@cedarjs/prerender": "8.0.0-canary.3395",
44
- "@cedarjs/project-config": "8.0.0-canary.3395",
45
- "@cedarjs/structure": "8.0.0-canary.3395",
46
- "@cedarjs/telemetry": "8.0.0-canary.3395",
47
- "@cedarjs/utils": "8.0.0-canary.3395",
48
- "@cedarjs/vite": "8.0.0-canary.3395",
49
- "@cedarjs/web-server": "8.0.0-canary.3395",
39
+ "@cedarjs/api-server": "8.0.0-canary.3397",
40
+ "@cedarjs/babel-config": "8.0.0-canary.3397",
41
+ "@cedarjs/cli-helpers": "8.0.0-canary.3397",
42
+ "@cedarjs/internal": "8.0.0-canary.3397",
43
+ "@cedarjs/prerender": "8.0.0-canary.3397",
44
+ "@cedarjs/project-config": "8.0.0-canary.3397",
45
+ "@cedarjs/structure": "8.0.0-canary.3397",
46
+ "@cedarjs/telemetry": "8.0.0-canary.3397",
47
+ "@cedarjs/utils": "8.0.0-canary.3397",
48
+ "@cedarjs/vite": "8.0.0-canary.3397",
49
+ "@cedarjs/web-server": "8.0.0-canary.3397",
50
50
  "@listr2/prompt-adapter-enquirer": "4.3.3",
51
51
  "@opentelemetry/api": "1.9.1",
52
52
  "@opentelemetry/core": "2.12.0",
@@ -94,7 +94,7 @@
94
94
  "yargs": "17.7.3"
95
95
  },
96
96
  "devDependencies": {
97
- "@cedarjs/framework-tools": "8.0.0-canary.3395",
97
+ "@cedarjs/framework-tools": "8.0.0-canary.3397",
98
98
  "@prisma/dmmf": "7.10.0",
99
99
  "@types/archiver": "^7.0.0",
100
100
  "memfs": "4.80.0",