@strapi/upgrade 0.0.0-experimental.fc1ac2acd58c8a5a858679956b6d102ac5ee4011 → 0.0.0-next.ce84fada19d58a7dfbdd553035e6558f8befcba4

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 (83) hide show
  1. package/LICENSE +19 -4
  2. package/dist/cli.js +1489 -5
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +375 -107
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +375 -108
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/modules/codemod/codemod.d.ts +4 -2
  9. package/dist/modules/codemod/codemod.d.ts.map +1 -1
  10. package/dist/modules/codemod/types.d.ts +8 -1
  11. package/dist/modules/codemod/types.d.ts.map +1 -1
  12. package/dist/modules/codemod-repository/constants.d.ts.map +1 -1
  13. package/dist/modules/codemod-repository/repository.d.ts +6 -5
  14. package/dist/modules/codemod-repository/repository.d.ts.map +1 -1
  15. package/dist/modules/codemod-repository/types.d.ts +7 -3
  16. package/dist/modules/codemod-repository/types.d.ts.map +1 -1
  17. package/dist/modules/codemod-runner/codemod-runner.d.ts +3 -0
  18. package/dist/modules/codemod-runner/codemod-runner.d.ts.map +1 -1
  19. package/dist/modules/codemod-runner/index.d.ts +1 -0
  20. package/dist/modules/codemod-runner/index.d.ts.map +1 -1
  21. package/dist/modules/codemod-runner/types.d.ts +1 -0
  22. package/dist/modules/codemod-runner/types.d.ts.map +1 -1
  23. package/dist/modules/file-scanner/scanner.d.ts.map +1 -1
  24. package/dist/modules/format/formats.d.ts +6 -0
  25. package/dist/modules/format/formats.d.ts.map +1 -1
  26. package/dist/modules/project/constants.d.ts +6 -3
  27. package/dist/modules/project/constants.d.ts.map +1 -1
  28. package/dist/modules/project/index.d.ts +2 -0
  29. package/dist/modules/project/index.d.ts.map +1 -1
  30. package/dist/modules/project/project.d.ts +27 -5
  31. package/dist/modules/project/project.d.ts.map +1 -1
  32. package/dist/modules/project/types.d.ts +3 -10
  33. package/dist/modules/project/types.d.ts.map +1 -1
  34. package/dist/modules/project/utils.d.ts +6 -0
  35. package/dist/modules/project/utils.d.ts.map +1 -0
  36. package/dist/modules/report/report.d.ts.map +1 -1
  37. package/dist/modules/requirement/types.d.ts +2 -2
  38. package/dist/modules/requirement/types.d.ts.map +1 -1
  39. package/dist/modules/runner/json/transform.d.ts.map +1 -1
  40. package/dist/modules/upgrader/upgrader.d.ts +3 -3
  41. package/dist/modules/upgrader/upgrader.d.ts.map +1 -1
  42. package/dist/modules/version/range.d.ts +2 -0
  43. package/dist/modules/version/range.d.ts.map +1 -1
  44. package/dist/tasks/codemods/index.d.ts +2 -1
  45. package/dist/tasks/codemods/index.d.ts.map +1 -1
  46. package/dist/tasks/codemods/list-codemods.d.ts +3 -0
  47. package/dist/tasks/codemods/list-codemods.d.ts.map +1 -0
  48. package/dist/tasks/codemods/run-codemods.d.ts +3 -0
  49. package/dist/tasks/codemods/run-codemods.d.ts.map +1 -0
  50. package/dist/tasks/codemods/types.d.ts +9 -3
  51. package/dist/tasks/codemods/types.d.ts.map +1 -1
  52. package/dist/tasks/codemods/utils.d.ts +6 -0
  53. package/dist/tasks/codemods/utils.d.ts.map +1 -0
  54. package/dist/tasks/index.d.ts +1 -1
  55. package/dist/tasks/index.d.ts.map +1 -1
  56. package/dist/tasks/upgrade/upgrade.d.ts.map +1 -1
  57. package/package.json +10 -9
  58. package/resources/codemods/5.0.0/comment-out-lifecycle-files.code.ts +63 -0
  59. package/resources/codemods/5.0.0/dependency-remove-strapi-plugin-i18n.json.ts +31 -0
  60. package/resources/codemods/5.0.0/dependency-upgrade-react-and-react-dom.json.ts +67 -0
  61. package/resources/codemods/5.0.0/dependency-upgrade-react-router-dom.json.ts +59 -0
  62. package/resources/codemods/5.0.0/dependency-upgrade-styled-components.json.ts +49 -0
  63. package/resources/codemods/5.0.0/deprecate-helper-plugin.code.ts +186 -0
  64. package/resources/codemods/5.0.0/entity-service-document-service.code.ts +437 -0
  65. package/resources/codemods/5.0.0/s3-keys-wrapped-in-credentials.code.ts +1 -1
  66. package/resources/codemods/5.0.0/sqlite3-to-better-sqlite3.json.ts +5 -2
  67. package/resources/codemods/5.0.0/strapi-public-interface.code.ts +126 -0
  68. package/resources/codemods/5.0.0/use-uid-for-config-namespace.code.ts +1 -1
  69. package/resources/codemods/5.0.0/utils-public-interface.code.ts +320 -0
  70. package/resources/examples/console.log-to-console.info.code.ts +1 -1
  71. package/resources/examples/disable-jsx-buttons.code.ts +42 -0
  72. package/resources/utils/change-import.ts +118 -0
  73. package/resources/utils/replace-jsx.ts +49 -0
  74. package/dist/_chunks/codemod-runner-oPpELfwO.js +0 -730
  75. package/dist/_chunks/codemod-runner-oPpELfwO.js.map +0 -1
  76. package/dist/_chunks/codemods-qc8_QMWD.js +0 -108
  77. package/dist/_chunks/codemods-qc8_QMWD.js.map +0 -1
  78. package/dist/_chunks/index-GHKaxdhJ.js +0 -103
  79. package/dist/_chunks/index-GHKaxdhJ.js.map +0 -1
  80. package/dist/_chunks/upgrade-4SxXG3Oe.js +0 -357
  81. package/dist/_chunks/upgrade-4SxXG3Oe.js.map +0 -1
  82. package/dist/tasks/codemods/codemods.d.ts +0 -3
  83. package/dist/tasks/codemods/codemods.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ const utils = require("@strapi/utils");
8
8
  const fp = require("lodash/fp");
9
9
  const fse = require("fs-extra");
10
10
  const assert = require("node:assert");
11
- const glob = require("glob");
11
+ const fastglob = require("fast-glob");
12
12
  const Runner = require("jscodeshift/src/Runner");
13
13
  const node = require("esbuild-register/dist/node");
14
14
  const CliTable3 = require("cli-table3");
@@ -19,6 +19,7 @@ const chalk__default = /* @__PURE__ */ _interopDefault(chalk);
19
19
  const semver__default = /* @__PURE__ */ _interopDefault(semver);
20
20
  const fse__default = /* @__PURE__ */ _interopDefault(fse);
21
21
  const assert__default = /* @__PURE__ */ _interopDefault(assert);
22
+ const fastglob__default = /* @__PURE__ */ _interopDefault(fastglob);
22
23
  const CliTable3__default = /* @__PURE__ */ _interopDefault(CliTable3);
23
24
  class Requirement {
24
25
  isRequired;
@@ -263,13 +264,19 @@ const rangeFromVersions = (currentVersion, target) => {
263
264
  }
264
265
  throw new Error(`Invalid target set: ${target}`);
265
266
  };
267
+ const isValidStringifiedRange = (str) => semver__default.default.validRange(str) !== null;
268
+ const isRangeInstance = (range) => {
269
+ return range instanceof semver__default.default.Range;
270
+ };
266
271
  const index$e = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
267
272
  __proto__: null,
268
273
  Version: types,
269
274
  isLiteralSemVer,
275
+ isRangeInstance,
270
276
  isSemVerReleaseType,
271
277
  isSemverInstance,
272
278
  isValidSemVer,
279
+ isValidStringifiedRange,
273
280
  rangeFactory,
274
281
  rangeFromReleaseType,
275
282
  rangeFromVersions,
@@ -281,7 +288,9 @@ class FileScanner {
281
288
  this.cwd = cwd;
282
289
  }
283
290
  scan(patterns) {
284
- const filenames = glob.glob.sync(patterns, { cwd: this.cwd });
291
+ const filenames = fastglob__default.default.sync(patterns, {
292
+ cwd: this.cwd
293
+ });
285
294
  return filenames.map((filename) => path__default.default.join(this.cwd, filename));
286
295
  }
287
296
  }
@@ -330,7 +339,11 @@ const transformJSON = async (codemodPath, paths, config) => {
330
339
  timeElapsed: "",
331
340
  stats: {}
332
341
  };
333
- const esbuildOptions = { extensions: [".js", ".mjs", ".ts"] };
342
+ const esbuildOptions = {
343
+ extensions: [".js", ".mjs", ".ts"],
344
+ hookIgnoreNodeModules: false,
345
+ hookMatcher: fp.isEqual(codemodPath)
346
+ };
334
347
  const { unregister } = node.register(esbuildOptions);
335
348
  const module2 = require(codemodPath);
336
349
  unregister();
@@ -375,17 +388,31 @@ const index$b = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
375
388
  jsonRunnerFactory
376
389
  }, Symbol.toStringTag, { value: "Module" }));
377
390
  const PROJECT_PACKAGE_JSON = "package.json";
378
- const PROJECT_DEFAULT_ALLOWED_ROOT_PATHS = ["src", "config", "public"];
379
- const PROJECT_DEFAULT_ALLOWED_EXTENSIONS = ["js", "ts", "json"];
380
- const PROJECT_DEFAULT_PATTERNS = ["package.json"];
391
+ const PROJECT_APP_ALLOWED_ROOT_PATHS = ["src", "config", "public"];
392
+ const PROJECT_PLUGIN_ALLOWED_ROOT_PATHS = ["admin", "server"];
393
+ const PROJECT_PLUGIN_ROOT_FILES = ["strapi-admin.js", "strapi-server.js"];
394
+ const PROJECT_CODE_EXTENSIONS = [
395
+ // Source files
396
+ "js",
397
+ "mjs",
398
+ "ts",
399
+ // React files
400
+ "jsx",
401
+ "tsx"
402
+ ];
403
+ const PROJECT_JSON_EXTENSIONS = ["json"];
404
+ const PROJECT_ALLOWED_EXTENSIONS = [...PROJECT_CODE_EXTENSIONS, ...PROJECT_JSON_EXTENSIONS];
381
405
  const SCOPED_STRAPI_PACKAGE_PREFIX = "@strapi/";
382
406
  const STRAPI_DEPENDENCY_NAME = `${SCOPED_STRAPI_PACKAGE_PREFIX}strapi`;
383
407
  const constants$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
384
408
  __proto__: null,
385
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS,
386
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS,
387
- PROJECT_DEFAULT_PATTERNS,
409
+ PROJECT_ALLOWED_EXTENSIONS,
410
+ PROJECT_APP_ALLOWED_ROOT_PATHS,
411
+ PROJECT_CODE_EXTENSIONS,
412
+ PROJECT_JSON_EXTENSIONS,
388
413
  PROJECT_PACKAGE_JSON,
414
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS,
415
+ PROJECT_PLUGIN_ROOT_FILES,
389
416
  SCOPED_STRAPI_PACKAGE_PREFIX,
390
417
  STRAPI_DEPENDENCY_NAME
391
418
  }, Symbol.toStringTag, { value: "Module" }));
@@ -395,12 +422,13 @@ class Project {
395
422
  files;
396
423
  packageJSONPath;
397
424
  packageJSON;
398
- strapiVersion;
399
- constructor(cwd) {
425
+ paths;
426
+ constructor(cwd, config) {
400
427
  if (!fse__default.default.pathExistsSync(cwd)) {
401
428
  throw new Error(`ENOENT: no such file or directory, access '${cwd}'`);
402
429
  }
403
430
  this.cwd = cwd;
431
+ this.paths = config.paths;
404
432
  this.refresh();
405
433
  }
406
434
  getFilesByExtensions(extensions) {
@@ -411,14 +439,13 @@ class Project {
411
439
  }
412
440
  refresh() {
413
441
  this.refreshPackageJSON();
414
- this.refreshStrapiVersion();
415
442
  this.refreshProjectFiles();
416
443
  return this;
417
444
  }
418
- async runCodemods(codemods2, options) {
445
+ async runCodemods(codemods, options) {
419
446
  const runners = this.createProjectCodemodsRunners(options.dry);
420
447
  const reports2 = [];
421
- for (const codemod of codemods2) {
448
+ for (const codemod of codemods) {
422
449
  for (const runner of runners) {
423
450
  if (runner.valid(codemod)) {
424
451
  const report = await runner.run(codemod);
@@ -429,17 +456,20 @@ class Project {
429
456
  return reports2;
430
457
  }
431
458
  createProjectCodemodsRunners(dry = false) {
432
- const jsonFiles = this.getFilesByExtensions([".json"]);
433
- const codeFiles = this.getFilesByExtensions([".js", ".ts", ".mjs"]);
459
+ const jsonExtensions = PROJECT_JSON_EXTENSIONS.map((ext) => `.${ext}`);
460
+ const codeExtensions = PROJECT_CODE_EXTENSIONS.map((ext) => `.${ext}`);
461
+ const jsonFiles = this.getFilesByExtensions(jsonExtensions);
462
+ const codeFiles = this.getFilesByExtensions(codeExtensions);
434
463
  const codeRunner = codeRunnerFactory(codeFiles, {
435
464
  dry,
436
- print: false,
437
- silent: true,
438
- extensions: "js,ts",
465
+ parser: "ts",
439
466
  runInBand: true,
440
- verbose: 0,
441
467
  babel: true,
442
- parser: "ts"
468
+ extensions: PROJECT_CODE_EXTENSIONS.join(","),
469
+ // Don't output any log coming from the runner
470
+ print: false,
471
+ silent: true,
472
+ verbose: 0
443
473
  });
444
474
  const jsonRunner = jsonRunnerFactory(jsonFiles, { dry, cwd: this.cwd });
445
475
  return [codeRunner, jsonRunner];
@@ -456,16 +486,38 @@ class Project {
456
486
  this.packageJSON = JSON.parse(packageJSONBuffer.toString());
457
487
  }
458
488
  refreshProjectFiles() {
459
- const allowedRootPaths = formatGlobCollectionPattern(
460
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS
461
- );
462
- const allowedExtensions = formatGlobCollectionPattern(
463
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS
464
- );
465
- const projectFilesPattern = `./${allowedRootPaths}/**/*.${allowedExtensions}`;
466
- const patterns = [projectFilesPattern, ...PROJECT_DEFAULT_PATTERNS];
467
489
  const scanner = fileScannerFactory(this.cwd);
468
- this.files = scanner.scan(patterns);
490
+ this.files = scanner.scan(this.paths);
491
+ }
492
+ }
493
+ class AppProject extends Project {
494
+ strapiVersion;
495
+ type = "application";
496
+ /**
497
+ * Returns an array of allowed file paths for a Strapi application
498
+ *
499
+ * The resulting paths include app default files and the root package.json file.
500
+ */
501
+ static get paths() {
502
+ const allowedRootPaths = formatGlobCollectionPattern(PROJECT_APP_ALLOWED_ROOT_PATHS);
503
+ const allowedExtensions = formatGlobCollectionPattern(PROJECT_ALLOWED_EXTENSIONS);
504
+ return [
505
+ // App default files
506
+ `./${allowedRootPaths}/**/*.${allowedExtensions}`,
507
+ `!./**/node_modules/**/*`,
508
+ `!./**/dist/**/*`,
509
+ // Root package.json file
510
+ PROJECT_PACKAGE_JSON
511
+ ];
512
+ }
513
+ constructor(cwd) {
514
+ super(cwd, { paths: AppProject.paths });
515
+ this.refreshStrapiVersion();
516
+ }
517
+ refresh() {
518
+ super.refresh();
519
+ this.refreshStrapiVersion();
520
+ return this;
469
521
  }
470
522
  refreshStrapiVersion() {
471
523
  this.strapiVersion = // First try to get the strapi version from the package.json dependencies
@@ -512,10 +564,71 @@ const formatGlobCollectionPattern = (collection) => {
512
564
  );
513
565
  return collection.length === 1 ? collection[0] : `{${collection}}`;
514
566
  };
515
- const projectFactory = (cwd) => new Project(cwd);
567
+ class PluginProject extends Project {
568
+ type = "plugin";
569
+ /**
570
+ * Returns an array of allowed file paths for a Strapi plugin
571
+ *
572
+ * The resulting paths include plugin default files, the root package.json file, and plugin-specific files.
573
+ */
574
+ static get paths() {
575
+ const allowedRootPaths = formatGlobCollectionPattern(
576
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS
577
+ );
578
+ const allowedExtensions = formatGlobCollectionPattern(PROJECT_ALLOWED_EXTENSIONS);
579
+ return [
580
+ // Plugin default files
581
+ `./${allowedRootPaths}/**/*.${allowedExtensions}`,
582
+ `!./**/node_modules/**/*`,
583
+ `!./**/dist/**/*`,
584
+ // Root package.json file
585
+ PROJECT_PACKAGE_JSON,
586
+ // Plugin root files
587
+ ...PROJECT_PLUGIN_ROOT_FILES
588
+ ];
589
+ }
590
+ constructor(cwd) {
591
+ super(cwd, { paths: PluginProject.paths });
592
+ }
593
+ }
594
+ const isPlugin = (cwd) => {
595
+ const packageJSONPath = path__default.default.join(cwd, PROJECT_PACKAGE_JSON);
596
+ try {
597
+ fse__default.default.accessSync(packageJSONPath);
598
+ } catch {
599
+ throw new Error(`Could not find a ${PROJECT_PACKAGE_JSON} file in ${cwd}`);
600
+ }
601
+ const packageJSONBuffer = fse__default.default.readFileSync(packageJSONPath);
602
+ const packageJSON = JSON.parse(packageJSONBuffer.toString());
603
+ return packageJSON?.strapi?.kind === "plugin";
604
+ };
605
+ const projectFactory = (cwd) => {
606
+ fse__default.default.accessSync(cwd);
607
+ return isPlugin(cwd) ? new PluginProject(cwd) : new AppProject(cwd);
608
+ };
609
+ const isPluginProject = (project) => {
610
+ return project instanceof PluginProject;
611
+ };
612
+ function assertPluginProject(project) {
613
+ if (!isPluginProject(project)) {
614
+ throw new Error("Project is not a plugin");
615
+ }
616
+ }
617
+ const isApplicationProject = (project) => {
618
+ return project instanceof AppProject;
619
+ };
620
+ function assertAppProject(project) {
621
+ if (!isApplicationProject(project)) {
622
+ throw new Error("Project is not an application");
623
+ }
624
+ }
516
625
  const index$a = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
517
626
  __proto__: null,
627
+ assertAppProject,
628
+ assertPluginProject,
518
629
  constants: constants$3,
630
+ isApplicationProject,
631
+ isPluginProject,
519
632
  projectFactory
520
633
  }, Symbol.toStringTag, { value: "Module" }));
521
634
  class UnexpectedError extends Error {
@@ -541,7 +654,14 @@ const path = (path2) => chalk__default.default.blue(path2);
541
654
  const version = (version2) => {
542
655
  return chalk__default.default.italic.yellow(`v${version2}`);
543
656
  };
544
- const versionRange = (range) => chalk__default.default.italic.yellow(range);
657
+ const codemodUID = (uid) => {
658
+ return chalk__default.default.bold.cyan(uid);
659
+ };
660
+ const projectDetails = (project) => {
661
+ return `Project: TYPE=${projectType(project.type)}; CWD=${path(project.cwd)}; PATHS=${project.paths.map(path)}`;
662
+ };
663
+ const projectType = (type) => chalk__default.default.cyan(type);
664
+ const versionRange = (range) => chalk__default.default.italic.yellow(range.raw);
545
665
  const transform = (transformFilePath) => chalk__default.default.cyan(transformFilePath);
546
666
  const highlight = (arg) => chalk__default.default.bold.underline(arg);
547
667
  const upgradeStep = (text, step) => {
@@ -573,15 +693,41 @@ const reports = (reports2) => {
573
693
  table.push(...rows);
574
694
  return table.toString();
575
695
  };
696
+ const codemodList = (codemods) => {
697
+ const rows = codemods.map((codemod, index2) => {
698
+ const fIndex = chalk__default.default.grey(index2);
699
+ const fVersion = chalk__default.default.magenta(codemod.version);
700
+ const fKind = chalk__default.default.yellow(codemod.kind);
701
+ const fName = chalk__default.default.blue(codemod.format());
702
+ const fUID = codemodUID(codemod.uid);
703
+ return [fIndex, fVersion, fKind, fName, fUID];
704
+ });
705
+ const table = new CliTable3__default.default({
706
+ style: { compact: true },
707
+ head: [
708
+ chalk__default.default.bold.grey("N°"),
709
+ chalk__default.default.bold.magenta("Version"),
710
+ chalk__default.default.bold.yellow("Kind"),
711
+ chalk__default.default.bold.blue("Name"),
712
+ chalk__default.default.bold.cyan("UID")
713
+ ]
714
+ });
715
+ table.push(...rows);
716
+ return table.toString();
717
+ };
576
718
  const durationMs = (elapsedMs) => {
577
719
  const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
578
720
  return `${elapsedSeconds}s`;
579
721
  };
580
722
  const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
581
723
  __proto__: null,
724
+ codemodList,
725
+ codemodUID,
582
726
  durationMs,
583
727
  highlight,
584
728
  path,
729
+ projectDetails,
730
+ projectType,
585
731
  reports,
586
732
  transform,
587
733
  upgradeStep,
@@ -604,6 +750,7 @@ const constants$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP
604
750
  CODEMOD_JSON_SUFFIX
605
751
  }, Symbol.toStringTag, { value: "Module" }));
606
752
  class Codemod {
753
+ uid;
607
754
  kind;
608
755
  version;
609
756
  baseDirectory;
@@ -615,9 +762,27 @@ class Codemod {
615
762
  this.baseDirectory = options.baseDirectory;
616
763
  this.filename = options.filename;
617
764
  this.path = path__default.default.join(this.baseDirectory, this.version.raw, this.filename);
618
- }
619
- format() {
620
- return this.filename.replace(`.${CODEMOD_CODE_SUFFIX}.${CODEMOD_EXTENSION}`, "").replace(`.${CODEMOD_JSON_SUFFIX}.${CODEMOD_EXTENSION}`, "").replaceAll("-", " ");
765
+ this.uid = this.createUID();
766
+ }
767
+ createUID() {
768
+ const name = this.format({ stripExtension: true, stripKind: true, stripHyphens: false });
769
+ const kind = this.kind;
770
+ const version2 = this.version.raw;
771
+ return `${version2}-${name}-${kind}`;
772
+ }
773
+ format(options) {
774
+ const { stripExtension = true, stripKind = true, stripHyphens = true } = options ?? {};
775
+ let formatted = this.filename;
776
+ if (stripExtension) {
777
+ formatted = formatted.replace(new RegExp(`\\.${CODEMOD_EXTENSION}$`, "i"), "");
778
+ }
779
+ if (stripKind) {
780
+ formatted = formatted.replace(`.${CODEMOD_CODE_SUFFIX}`, "").replace(`.${CODEMOD_JSON_SUFFIX}`, "");
781
+ }
782
+ if (stripHyphens) {
783
+ formatted = formatted.replaceAll("-", " ");
784
+ }
785
+ return formatted;
621
786
  }
622
787
  }
623
788
  const codemodFactory = (options) => new Codemod(options);
@@ -626,6 +791,20 @@ const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
626
791
  codemodFactory,
627
792
  constants: constants$2
628
793
  }, Symbol.toStringTag, { value: "Module" }));
794
+ const INTERNAL_CODEMODS_DIRECTORY = path__default.default.join(
795
+ __dirname,
796
+ // upgrade/dist
797
+ "..",
798
+ // upgrade
799
+ "resources",
800
+ // upgrade/resources
801
+ "codemods"
802
+ // upgrade/resources/codemods
803
+ );
804
+ const constants$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
805
+ __proto__: null,
806
+ INTERNAL_CODEMODS_DIRECTORY
807
+ }, Symbol.toStringTag, { value: "Module" }));
629
808
  class CodemodRepository {
630
809
  groups;
631
810
  versions;
@@ -644,23 +823,48 @@ class CodemodRepository {
644
823
  count(version2) {
645
824
  return this.findByVersion(version2).length;
646
825
  }
647
- countRange(range) {
648
- return this.findByRange(range).length;
649
- }
650
- exists(version2) {
826
+ versionExists(version2) {
651
827
  return version2.raw in this.groups;
652
828
  }
653
- findByRange(range) {
829
+ has(uid) {
830
+ const result = this.find({ uids: [uid] });
831
+ if (result.length !== 1) {
832
+ return false;
833
+ }
834
+ const { codemods } = result[0];
835
+ return codemods.length === 1 && codemods[0].uid === uid;
836
+ }
837
+ find(q) {
654
838
  const entries = Object.entries(this.groups);
655
- return entries.filter(([version2]) => range.test(version2)).map(([version2, codemods2]) => ({
839
+ return entries.filter(maybeFilterByRange).map(([version2, codemods]) => ({
656
840
  version: semVerFactory(version2),
657
- codemods: codemods2
658
- }));
841
+ // Filter by UID if provided in the query
842
+ codemods: codemods.filter(maybeFilterByUIDs)
843
+ })).filter(({ codemods }) => codemods.length > 0);
844
+ function maybeFilterByRange([version2]) {
845
+ if (!isRangeInstance(q.range)) {
846
+ return true;
847
+ }
848
+ return q.range.test(version2);
849
+ }
850
+ function maybeFilterByUIDs(codemod) {
851
+ if (q.uids === void 0) {
852
+ return true;
853
+ }
854
+ return q.uids.includes(codemod.uid);
855
+ }
659
856
  }
660
857
  findByVersion(version2) {
661
858
  const literalVersion = version2.raw;
662
- const codemods2 = this.groups[literalVersion];
663
- return codemods2 ?? [];
859
+ const codemods = this.groups[literalVersion];
860
+ return codemods ?? [];
861
+ }
862
+ findAll() {
863
+ const entries = Object.entries(this.groups);
864
+ return entries.map(([version2, codemods]) => ({
865
+ version: semVerFactory(version2),
866
+ codemods
867
+ }));
664
868
  }
665
869
  refreshAvailableVersions() {
666
870
  this.versions = fse__default.default.readdirSync(this.cwd).filter((filename) => fse__default.default.statSync(path__default.default.join(this.cwd, filename)).isDirectory()).filter((filename) => semver__default.default.valid(filename) !== null).map((version2) => semVerFactory(version2)).sort(semver__default.default.compare);
@@ -691,18 +895,9 @@ const parseCodemodKindFromFilename = (filename) => {
691
895
  assert__default.default(CODEMOD_ALLOWED_SUFFIXES.includes(kind));
692
896
  return kind;
693
897
  };
694
- const codemodRepositoryFactory = (cwd) => new CodemodRepository(cwd);
695
- const INTERNAL_CODEMODS_DIRECTORY = path__default.default.join(
696
- __dirname,
697
- "..",
698
- "..",
699
- "resources",
700
- "codemods"
701
- );
702
- const constants$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
703
- __proto__: null,
704
- INTERNAL_CODEMODS_DIRECTORY
705
- }, Symbol.toStringTag, { value: "Module" }));
898
+ const codemodRepositoryFactory = (cwd = INTERNAL_CODEMODS_DIRECTORY) => {
899
+ return new CodemodRepository(cwd);
900
+ };
706
901
  const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
707
902
  __proto__: null,
708
903
  codemodRepositoryFactory,
@@ -737,34 +932,59 @@ class CodemodRunner {
737
932
  this.isDry = enabled;
738
933
  return this;
739
934
  }
740
- async run(codemodsDirectory) {
935
+ createRepository(codemodsDirectory) {
741
936
  const repository = codemodRepositoryFactory(
742
937
  codemodsDirectory ?? INTERNAL_CODEMODS_DIRECTORY
743
938
  );
744
939
  repository.refresh();
745
- const allVersionedCodemods = repository.findByRange(this.range);
746
- const versionedCodemods = this.selectCodemodsCallback ? await this.selectCodemodsCallback(allVersionedCodemods) : allVersionedCodemods;
747
- const hasCodemodsToRun = versionedCodemods.length > 0;
748
- if (!hasCodemodsToRun) {
749
- this.logger?.debug(`Found no codemods to run for ${versionRange(this.range)}`);
750
- return successReport$1();
940
+ return repository;
941
+ }
942
+ async safeRunAndReport(codemods) {
943
+ if (this.isDry) {
944
+ this.logger?.warn?.(
945
+ "Running the codemods in dry mode. No files will be modified during the process."
946
+ );
751
947
  }
752
- this.logger?.debug(
753
- `Found codemods for ${highlight(
754
- versionedCodemods.length
755
- )} version(s) using ${versionRange(this.range)}`
756
- );
757
- versionedCodemods.forEach(
758
- ({ version: version$1, codemods: codemods22 }) => this.logger?.debug(`- ${version(version$1)} (${codemods22.length})`)
759
- );
760
- const codemods2 = versionedCodemods.map(({ codemods: codemods22 }) => codemods22).flat();
761
948
  try {
762
- const reports$1 = await this.project.runCodemods(codemods2, { dry: this.isDry });
763
- this.logger?.raw(reports(reports$1));
949
+ const reports$1 = await this.project.runCodemods(codemods, { dry: this.isDry });
950
+ this.logger?.raw?.(reports(reports$1));
951
+ if (!this.isDry) {
952
+ const nbAffectedTotal = reports$1.flatMap((report) => report.report.ok).reduce((acc, nb) => acc + nb, 0);
953
+ this.logger?.debug?.(
954
+ `Successfully ran ${highlight(codemods.length)} codemod(s), ${highlight(nbAffectedTotal)} change(s) have been detected`
955
+ );
956
+ }
957
+ return successReport$1();
764
958
  } catch (e) {
765
959
  return erroredReport$1(unknownToError(e));
766
960
  }
767
- return successReport$1();
961
+ }
962
+ async runByUID(uid, codemodsDirectory) {
963
+ const repository = this.createRepository(codemodsDirectory);
964
+ if (!repository.has(uid)) {
965
+ throw new Error(`Unknown codemod UID provided: ${uid}`);
966
+ }
967
+ const codemods = repository.find({ uids: [uid] }).flatMap(({ codemods: codemods2 }) => codemods2);
968
+ return this.safeRunAndReport(codemods);
969
+ }
970
+ async run(codemodsDirectory) {
971
+ const repository = this.createRepository(codemodsDirectory);
972
+ const codemodsInRange = repository.find({ range: this.range });
973
+ const selectedCodemods = this.selectCodemodsCallback ? await this.selectCodemodsCallback(codemodsInRange) : codemodsInRange;
974
+ if (selectedCodemods.length === 0) {
975
+ this.logger?.debug?.(`Found no codemods to run for ${versionRange(this.range)}`);
976
+ return successReport$1();
977
+ }
978
+ const codemods = selectedCodemods.flatMap(({ codemods: codemods2 }) => codemods2);
979
+ const codemodsByVersion = fp.groupBy("version", codemods);
980
+ const fRange = versionRange(this.range);
981
+ this.logger?.debug?.(
982
+ `Found ${highlight(codemods.length)} codemods for ${highlight(fp.size(codemodsByVersion))} version(s) using ${fRange}`
983
+ );
984
+ for (const [version$1, codemods2] of Object.entries(codemodsByVersion)) {
985
+ this.logger?.debug?.(`- ${version(semVerFactory(version$1))} (${codemods2.length})`);
986
+ }
987
+ return this.safeRunAndReport(codemods);
768
988
  }
769
989
  }
770
990
  const codemodRunnerFactory = (project, range) => {
@@ -803,7 +1023,7 @@ class Upgrader {
803
1023
  this.codemodsTarget = semVerFactory(
804
1024
  `${this.target.major}.${this.target.minor}.${this.target.patch}`
805
1025
  );
806
- this.logger?.debug(
1026
+ this.logger?.debug?.(
807
1027
  `The codemods target has been synced with the upgrade target. The codemod runner will now look for ${version(
808
1028
  this.codemodsTarget
809
1029
  )}`
@@ -812,7 +1032,7 @@ class Upgrader {
812
1032
  }
813
1033
  overrideCodemodsTarget(target) {
814
1034
  this.codemodsTarget = target;
815
- this.logger?.debug(
1035
+ this.logger?.debug?.(
816
1036
  `Overriding the codemods target. The codemod runner will now look for ${version(target)}`
817
1037
  );
818
1038
  return this;
@@ -832,38 +1052,40 @@ class Upgrader {
832
1052
  addRequirement(requirement) {
833
1053
  this.requirements.push(requirement);
834
1054
  const fRequired = requirement.isRequired ? "(required)" : "(optional)";
835
- this.logger?.debug(
1055
+ this.logger?.debug?.(
836
1056
  `Added a new requirement to the upgrade: ${highlight(requirement.name)} ${fRequired}`
837
1057
  );
838
1058
  return this;
839
1059
  }
840
1060
  async upgrade() {
841
- this.logger?.info(
1061
+ this.logger?.info?.(
842
1062
  `Upgrading from ${version(this.project.strapiVersion)} to ${version(this.target)}`
843
1063
  );
844
1064
  if (this.isDry) {
845
- this.logger?.warn(
1065
+ this.logger?.warn?.(
846
1066
  "Running the upgrade in dry mode. No files will be modified during the process."
847
1067
  );
848
1068
  }
849
1069
  const range = rangeFromVersions(this.project.strapiVersion, this.target);
850
1070
  const codemodsRange = rangeFromVersions(this.project.strapiVersion, this.codemodsTarget);
851
1071
  const npmVersionsMatches = this.npmPackage?.findVersionsInRange(range) ?? [];
852
- this.logger?.debug(
1072
+ this.logger?.debug?.(
853
1073
  `Found ${highlight(npmVersionsMatches.length)} versions satisfying ${versionRange(range)}`
854
1074
  );
855
1075
  try {
856
- this.logger?.info(upgradeStep("Checking requirement", [1, 4]));
1076
+ this.logger?.info?.(upgradeStep("Checking requirement", [1, 4]));
857
1077
  await this.checkRequirements(this.requirements, {
858
1078
  npmVersionsMatches,
859
1079
  project: this.project,
860
1080
  target: this.target
861
1081
  });
862
- this.logger?.info(upgradeStep("Applying the latest code modifications", [2, 4]));
1082
+ this.logger?.info?.(upgradeStep("Applying the latest code modifications", [2, 4]));
863
1083
  await this.runCodemods(codemodsRange);
864
- this.logger?.info(upgradeStep("Upgrading Strapi dependencies", [3, 4]));
1084
+ this.logger?.debug?.("Refreshing project information...");
1085
+ this.project.refresh();
1086
+ this.logger?.info?.(upgradeStep("Upgrading Strapi dependencies", [3, 4]));
865
1087
  await this.updateDependencies();
866
- this.logger?.info(upgradeStep("Installing dependencies", [4, 4]));
1088
+ this.logger?.info?.(upgradeStep("Installing dependencies", [4, 4]));
867
1089
  await this.installDependencies();
868
1090
  } catch (e) {
869
1091
  return erroredReport(unknownToError(e));
@@ -896,7 +1118,7 @@ class Upgrader {
896
1118
  if (requirement.isRequired) {
897
1119
  throw error;
898
1120
  }
899
- this.logger?.warn(warningMessage);
1121
+ this.logger?.warn?.(warningMessage);
900
1122
  const response = await this.confirmationCallback?.(confirmationMessage);
901
1123
  if (!response) {
902
1124
  throw error;
@@ -907,9 +1129,11 @@ class Upgrader {
907
1129
  const json = createJSONTransformAPI(packageJSON);
908
1130
  const dependencies = json.get("dependencies", {});
909
1131
  const strapiDependencies = this.getScopedStrapiDependencies(dependencies);
910
- this.logger?.debug(`Found ${highlight(strapiDependencies.length)} dependency(ies) to update`);
1132
+ this.logger?.debug?.(
1133
+ `Found ${highlight(strapiDependencies.length)} dependency(ies) to update`
1134
+ );
911
1135
  strapiDependencies.forEach(
912
- (dependency) => this.logger?.debug(`- ${dependency[0]} (${dependency[1]} -> ${this.target})`)
1136
+ (dependency) => this.logger?.debug?.(`- ${dependency[0]} (${dependency[1]} -> ${this.target})`)
913
1137
  );
914
1138
  if (strapiDependencies.length === 0) {
915
1139
  return;
@@ -917,7 +1141,7 @@ class Upgrader {
917
1141
  strapiDependencies.forEach(([name]) => json.set(`dependencies.${name}`, this.target.raw));
918
1142
  const updatedPackageJSON = json.root();
919
1143
  if (this.isDry) {
920
- this.logger?.debug(`Skipping dependencies update (${chalk__default.default.italic("dry mode")})`);
1144
+ this.logger?.debug?.(`Skipping dependencies update (${chalk__default.default.italic("dry mode")})`);
921
1145
  return;
922
1146
  }
923
1147
  await saveJSON(packageJSONPath, updatedPackageJSON);
@@ -937,9 +1161,9 @@ class Upgrader {
937
1161
  async installDependencies() {
938
1162
  const projectPath = this.project.cwd;
939
1163
  const packageManagerName = await utils.packageManager.getPreferred(projectPath);
940
- this.logger?.debug(`Using ${highlight(packageManagerName)} as package manager`);
1164
+ this.logger?.debug?.(`Using ${highlight(packageManagerName)} as package manager`);
941
1165
  if (this.isDry) {
942
- this.logger?.debug(`Skipping dependencies installation (${chalk__default.default.italic("dry mode")}`);
1166
+ this.logger?.debug?.(`Skipping dependencies installation (${chalk__default.default.italic("dry mode")}`);
943
1167
  return;
944
1168
  }
945
1169
  await utils.packageManager.installDependencies(projectPath, packageManagerName, {
@@ -1038,6 +1262,12 @@ const upgrade = async (options) => {
1038
1262
  const { logger, codemodsTarget } = options;
1039
1263
  const cwd = path__default.default.resolve(options.cwd ?? process.cwd());
1040
1264
  const project = projectFactory(cwd);
1265
+ logger.debug(projectDetails(project));
1266
+ if (!isApplicationProject(project)) {
1267
+ throw new Error(
1268
+ `The "${options.target}" upgrade can only be run on a Strapi project; for plugins, please use "codemods".`
1269
+ );
1270
+ }
1041
1271
  const npmPackage = npmPackageFactory(STRAPI_PACKAGE_NAME);
1042
1272
  await npmPackage.refresh();
1043
1273
  const upgrader = upgraderFactory(project, options.target, npmPackage).dry(options.dry ?? false).onConfirm(options.confirm ?? null).setLogger(logger);
@@ -1055,20 +1285,7 @@ const upgrade = async (options) => {
1055
1285
  timer.stop();
1056
1286
  logger.info(`Completed in ${durationMs(timer.elapsedMs)}`);
1057
1287
  };
1058
- const codemods = async (options) => {
1059
- const timer = timerFactory();
1060
- const { logger } = options;
1061
- const cwd = path__default.default.resolve(options.cwd ?? process.cwd());
1062
- const project = projectFactory(cwd);
1063
- const range = getRangeFromTarget(project.strapiVersion, options.target);
1064
- const codemodRunner = codemodRunnerFactory(project, range).dry(options.dry ?? false).onSelectCodemods(options.selectCodemods ?? null).setLogger(logger);
1065
- const executionReport = await codemodRunner.run();
1066
- if (!executionReport.success) {
1067
- throw executionReport.error;
1068
- }
1069
- timer.stop();
1070
- logger.info(`Completed in ${timer.elapsedMs}`);
1071
- };
1288
+ const resolvePath = (cwd) => path__default.default.resolve(cwd ?? process.cwd());
1072
1289
  const getRangeFromTarget = (currentVersion, target) => {
1073
1290
  if (isSemverInstance(target)) {
1074
1291
  return rangeFactory(target);
@@ -1085,9 +1302,60 @@ const getRangeFromTarget = (currentVersion, target) => {
1085
1302
  throw new Error(`Invalid target set: ${target}`);
1086
1303
  }
1087
1304
  };
1305
+ const findRangeFromTarget = (project, target) => {
1306
+ if (isRangeInstance(target)) {
1307
+ return target;
1308
+ }
1309
+ if (isApplicationProject(project)) {
1310
+ return getRangeFromTarget(project.strapiVersion, target);
1311
+ }
1312
+ return rangeFactory("*");
1313
+ };
1314
+ const runCodemods = async (options) => {
1315
+ const timer = timerFactory();
1316
+ const { logger, uid } = options;
1317
+ const cwd = resolvePath(options.cwd);
1318
+ const project = projectFactory(cwd);
1319
+ const range = findRangeFromTarget(project, options.target);
1320
+ logger.debug(projectDetails(project));
1321
+ logger.debug(`Range: set to ${versionRange(range)}`);
1322
+ const codemodRunner = codemodRunnerFactory(project, range).dry(options.dry ?? false).onSelectCodemods(options.selectCodemods ?? null).setLogger(logger);
1323
+ let report;
1324
+ if (uid !== void 0) {
1325
+ logger.debug(`Running a single codemod: ${codemodUID(uid)}`);
1326
+ report = await codemodRunner.runByUID(uid);
1327
+ } else {
1328
+ report = await codemodRunner.run();
1329
+ }
1330
+ if (!report.success) {
1331
+ throw report.error;
1332
+ }
1333
+ timer.stop();
1334
+ logger.info(`Completed in ${timer.elapsedMs}`);
1335
+ };
1336
+ const listCodemods = async (options) => {
1337
+ const { logger, target } = options;
1338
+ const cwd = resolvePath(options.cwd);
1339
+ const project = projectFactory(cwd);
1340
+ const range = findRangeFromTarget(project, target);
1341
+ logger.debug(projectDetails(project));
1342
+ logger.debug(`Range: set to ${versionRange(range)}`);
1343
+ const repo = codemodRepositoryFactory();
1344
+ repo.refresh();
1345
+ const groups = repo.find({ range });
1346
+ const codemods = groups.flatMap((collection) => collection.codemods);
1347
+ logger.debug(`Found ${highlight(codemods.length)} codemods`);
1348
+ if (codemods.length === 0) {
1349
+ logger.info(`Found no codemods matching ${versionRange(range)}`);
1350
+ return;
1351
+ }
1352
+ const fCodemods = codemodList(codemods);
1353
+ logger.raw(fCodemods);
1354
+ };
1088
1355
  const index$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1089
1356
  __proto__: null,
1090
- codemods,
1357
+ listCodemods,
1358
+ runCodemods,
1091
1359
  upgrade
1092
1360
  }, Symbol.toStringTag, { value: "Module" }));
1093
1361
  class Logger {