@strapi/upgrade 0.0.0-experimental.d362bf200f5f9359a4bbd4a549603de5ee1f04ca → 0.0.0-experimental.d65615a2b9130dd742d3c396674457d7971da928

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 (71) 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 +305 -130
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +305 -131
  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 +5 -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 +6 -3
  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 -5
  27. package/dist/modules/project/constants.d.ts.map +1 -1
  28. package/dist/modules/project/project.d.ts +17 -3
  29. package/dist/modules/project/project.d.ts.map +1 -1
  30. package/dist/modules/project/types.d.ts +4 -0
  31. package/dist/modules/project/types.d.ts.map +1 -1
  32. package/dist/modules/project/utils.d.ts +1 -1
  33. package/dist/modules/project/utils.d.ts.map +1 -1
  34. package/dist/modules/report/report.d.ts.map +1 -1
  35. package/dist/modules/runner/json/transform.d.ts.map +1 -1
  36. package/dist/modules/upgrader/upgrader.d.ts.map +1 -1
  37. package/dist/modules/version/range.d.ts +2 -0
  38. package/dist/modules/version/range.d.ts.map +1 -1
  39. package/dist/tasks/codemods/index.d.ts +2 -1
  40. package/dist/tasks/codemods/index.d.ts.map +1 -1
  41. package/dist/tasks/codemods/list-codemods.d.ts +3 -0
  42. package/dist/tasks/codemods/list-codemods.d.ts.map +1 -0
  43. package/dist/tasks/codemods/run-codemods.d.ts +3 -0
  44. package/dist/tasks/codemods/run-codemods.d.ts.map +1 -0
  45. package/dist/tasks/codemods/types.d.ts +9 -3
  46. package/dist/tasks/codemods/types.d.ts.map +1 -1
  47. package/dist/tasks/codemods/utils.d.ts +6 -0
  48. package/dist/tasks/codemods/utils.d.ts.map +1 -0
  49. package/dist/tasks/index.d.ts +1 -1
  50. package/dist/tasks/index.d.ts.map +1 -1
  51. package/dist/tasks/upgrade/upgrade.d.ts.map +1 -1
  52. package/package.json +10 -9
  53. package/resources/codemods/5.0.0/comment-out-lifecycle-files.code.ts +63 -0
  54. package/resources/codemods/5.0.0/dependency-upgrade-react-and-react-dom.json.ts +67 -0
  55. package/resources/codemods/5.0.0/dependency-upgrade-styled-components.json.ts +49 -0
  56. package/resources/codemods/5.0.0/deprecate-helper-plugin.code.ts +192 -0
  57. package/resources/codemods/5.0.0/entity-service-document-service.code.ts +437 -0
  58. package/resources/codemods/5.0.0/strapi-public-interface.code.ts +126 -0
  59. package/resources/codemods/5.0.0/utils-public-interface.code.ts +320 -0
  60. package/resources/utils/change-import.ts +118 -0
  61. package/resources/utils/replace-jsx.ts +49 -0
  62. package/dist/_chunks/codemod-runner-mXNzVpHm.js +0 -798
  63. package/dist/_chunks/codemod-runner-mXNzVpHm.js.map +0 -1
  64. package/dist/_chunks/codemods-S4mNX9Qg.js +0 -105
  65. package/dist/_chunks/codemods-S4mNX9Qg.js.map +0 -1
  66. package/dist/_chunks/index-Pt-TU9MN.js +0 -103
  67. package/dist/_chunks/index-Pt-TU9MN.js.map +0 -1
  68. package/dist/_chunks/upgrade-aWNYibWB.js +0 -361
  69. package/dist/_chunks/upgrade-aWNYibWB.js.map +0 -1
  70. package/dist/tasks/codemods/codemods.d.ts +0 -3
  71. package/dist/tasks/codemods/codemods.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -3,10 +3,10 @@ import simpleGit from "simple-git";
3
3
  import chalk from "chalk";
4
4
  import semver from "semver";
5
5
  import { packageManager } from "@strapi/utils";
6
- import { cloneDeep, get, has, merge, set, omit, isEqual } from "lodash/fp";
6
+ import { cloneDeep, get, has, merge, set, omit, isEqual, groupBy, size } from "lodash/fp";
7
7
  import fse from "fs-extra";
8
8
  import assert from "node:assert";
9
- import { glob } from "glob";
9
+ import fastglob from "fast-glob";
10
10
  import { run } from "jscodeshift/src/Runner";
11
11
  import { register } from "esbuild-register/dist/node";
12
12
  import CliTable3 from "cli-table3";
@@ -253,13 +253,19 @@ const rangeFromVersions = (currentVersion, target) => {
253
253
  }
254
254
  throw new Error(`Invalid target set: ${target}`);
255
255
  };
256
+ const isValidStringifiedRange = (str) => semver.validRange(str) !== null;
257
+ const isRangeInstance = (range) => {
258
+ return range instanceof semver.Range;
259
+ };
256
260
  const index$e = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
257
261
  __proto__: null,
258
262
  Version: types,
259
263
  isLiteralSemVer,
264
+ isRangeInstance,
260
265
  isSemVerReleaseType,
261
266
  isSemverInstance,
262
267
  isValidSemVer,
268
+ isValidStringifiedRange,
263
269
  rangeFactory,
264
270
  rangeFromReleaseType,
265
271
  rangeFromVersions,
@@ -271,7 +277,9 @@ class FileScanner {
271
277
  this.cwd = cwd;
272
278
  }
273
279
  scan(patterns) {
274
- const filenames = glob.sync(patterns, { cwd: this.cwd });
280
+ const filenames = fastglob.sync(patterns, {
281
+ cwd: this.cwd
282
+ });
275
283
  return filenames.map((filename) => path$1.join(this.cwd, filename));
276
284
  }
277
285
  }
@@ -320,7 +328,11 @@ const transformJSON = async (codemodPath, paths, config) => {
320
328
  timeElapsed: "",
321
329
  stats: {}
322
330
  };
323
- const esbuildOptions = { extensions: [".js", ".mjs", ".ts"] };
331
+ const esbuildOptions = {
332
+ extensions: [".js", ".mjs", ".ts"],
333
+ hookIgnoreNodeModules: false,
334
+ hookMatcher: isEqual(codemodPath)
335
+ };
324
336
  const { unregister } = register(esbuildOptions);
325
337
  const module = require(codemodPath);
326
338
  unregister();
@@ -365,8 +377,10 @@ const index$b = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
365
377
  jsonRunnerFactory
366
378
  }, Symbol.toStringTag, { value: "Module" }));
367
379
  const PROJECT_PACKAGE_JSON = "package.json";
368
- const PROJECT_DEFAULT_ALLOWED_ROOT_PATHS = ["src", "config", "public"];
369
- const PROJECT_DEFAULT_CODE_EXTENSIONS = [
380
+ const PROJECT_APP_ALLOWED_ROOT_PATHS = ["src", "config", "public"];
381
+ const PROJECT_PLUGIN_ALLOWED_ROOT_PATHS = ["admin", "server"];
382
+ const PROJECT_PLUGIN_ROOT_FILES = ["strapi-admin.js", "strapi-server.js"];
383
+ const PROJECT_CODE_EXTENSIONS = [
370
384
  // Source files
371
385
  "js",
372
386
  "mjs",
@@ -375,22 +389,19 @@ const PROJECT_DEFAULT_CODE_EXTENSIONS = [
375
389
  "jsx",
376
390
  "tsx"
377
391
  ];
378
- const PROJECT_DEFAULT_JSON_EXTENSIONS = ["json"];
379
- const PROJECT_DEFAULT_ALLOWED_EXTENSIONS = [
380
- ...PROJECT_DEFAULT_CODE_EXTENSIONS,
381
- ...PROJECT_DEFAULT_JSON_EXTENSIONS
382
- ];
383
- const PROJECT_DEFAULT_PATTERNS = ["package.json"];
392
+ const PROJECT_JSON_EXTENSIONS = ["json"];
393
+ const PROJECT_ALLOWED_EXTENSIONS = [...PROJECT_CODE_EXTENSIONS, ...PROJECT_JSON_EXTENSIONS];
384
394
  const SCOPED_STRAPI_PACKAGE_PREFIX = "@strapi/";
385
395
  const STRAPI_DEPENDENCY_NAME = `${SCOPED_STRAPI_PACKAGE_PREFIX}strapi`;
386
396
  const constants$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
387
397
  __proto__: null,
388
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS,
389
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS,
390
- PROJECT_DEFAULT_CODE_EXTENSIONS,
391
- PROJECT_DEFAULT_JSON_EXTENSIONS,
392
- PROJECT_DEFAULT_PATTERNS,
398
+ PROJECT_ALLOWED_EXTENSIONS,
399
+ PROJECT_APP_ALLOWED_ROOT_PATHS,
400
+ PROJECT_CODE_EXTENSIONS,
401
+ PROJECT_JSON_EXTENSIONS,
393
402
  PROJECT_PACKAGE_JSON,
403
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS,
404
+ PROJECT_PLUGIN_ROOT_FILES,
394
405
  SCOPED_STRAPI_PACKAGE_PREFIX,
395
406
  STRAPI_DEPENDENCY_NAME
396
407
  }, Symbol.toStringTag, { value: "Module" }));
@@ -400,11 +411,13 @@ class Project {
400
411
  files;
401
412
  packageJSONPath;
402
413
  packageJSON;
403
- constructor(cwd) {
414
+ paths;
415
+ constructor(cwd, config) {
404
416
  if (!fse.pathExistsSync(cwd)) {
405
417
  throw new Error(`ENOENT: no such file or directory, access '${cwd}'`);
406
418
  }
407
419
  this.cwd = cwd;
420
+ this.paths = config.paths;
408
421
  this.refresh();
409
422
  }
410
423
  getFilesByExtensions(extensions) {
@@ -418,10 +431,10 @@ class Project {
418
431
  this.refreshProjectFiles();
419
432
  return this;
420
433
  }
421
- async runCodemods(codemods2, options) {
434
+ async runCodemods(codemods, options) {
422
435
  const runners = this.createProjectCodemodsRunners(options.dry);
423
436
  const reports2 = [];
424
- for (const codemod of codemods2) {
437
+ for (const codemod of codemods) {
425
438
  for (const runner of runners) {
426
439
  if (runner.valid(codemod)) {
427
440
  const report = await runner.run(codemod);
@@ -432,12 +445,8 @@ class Project {
432
445
  return reports2;
433
446
  }
434
447
  createProjectCodemodsRunners(dry = false) {
435
- const jsonExtensions = PROJECT_DEFAULT_JSON_EXTENSIONS.map(
436
- (ext) => `.${ext}`
437
- );
438
- const codeExtensions = PROJECT_DEFAULT_CODE_EXTENSIONS.map(
439
- (ext) => `.${ext}`
440
- );
448
+ const jsonExtensions = PROJECT_JSON_EXTENSIONS.map((ext) => `.${ext}`);
449
+ const codeExtensions = PROJECT_CODE_EXTENSIONS.map((ext) => `.${ext}`);
441
450
  const jsonFiles = this.getFilesByExtensions(jsonExtensions);
442
451
  const codeFiles = this.getFilesByExtensions(codeExtensions);
443
452
  const codeRunner = codeRunnerFactory(codeFiles, {
@@ -445,7 +454,7 @@ class Project {
445
454
  parser: "ts",
446
455
  runInBand: true,
447
456
  babel: true,
448
- extensions: PROJECT_DEFAULT_CODE_EXTENSIONS.join(","),
457
+ extensions: PROJECT_CODE_EXTENSIONS.join(","),
449
458
  // Don't output any log coming from the runner
450
459
  print: false,
451
460
  silent: true,
@@ -466,23 +475,32 @@ class Project {
466
475
  this.packageJSON = JSON.parse(packageJSONBuffer.toString());
467
476
  }
468
477
  refreshProjectFiles() {
469
- const allowedRootPaths = formatGlobCollectionPattern(
470
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS
471
- );
472
- const allowedExtensions = formatGlobCollectionPattern(
473
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS
474
- );
475
- const projectFilesPattern = `./${allowedRootPaths}/**/*.${allowedExtensions}`;
476
- const patterns = [projectFilesPattern, ...PROJECT_DEFAULT_PATTERNS];
477
478
  const scanner = fileScannerFactory(this.cwd);
478
- this.files = scanner.scan(patterns);
479
+ this.files = scanner.scan(this.paths);
479
480
  }
480
481
  }
481
482
  class AppProject extends Project {
482
483
  strapiVersion;
483
- type = "app";
484
+ type = "application";
485
+ /**
486
+ * Returns an array of allowed file paths for a Strapi application
487
+ *
488
+ * The resulting paths include app default files and the root package.json file.
489
+ */
490
+ static get paths() {
491
+ const allowedRootPaths = formatGlobCollectionPattern(PROJECT_APP_ALLOWED_ROOT_PATHS);
492
+ const allowedExtensions = formatGlobCollectionPattern(PROJECT_ALLOWED_EXTENSIONS);
493
+ return [
494
+ // App default files
495
+ `./${allowedRootPaths}/**/*.${allowedExtensions}`,
496
+ `!./**/node_modules/**/*`,
497
+ `!./**/dist/**/*`,
498
+ // Root package.json file
499
+ PROJECT_PACKAGE_JSON
500
+ ];
501
+ }
484
502
  constructor(cwd) {
485
- super(cwd);
503
+ super(cwd, { paths: AppProject.paths });
486
504
  this.refreshStrapiVersion();
487
505
  }
488
506
  refresh() {
@@ -537,6 +555,30 @@ const formatGlobCollectionPattern = (collection) => {
537
555
  };
538
556
  class PluginProject extends Project {
539
557
  type = "plugin";
558
+ /**
559
+ * Returns an array of allowed file paths for a Strapi plugin
560
+ *
561
+ * The resulting paths include plugin default files, the root package.json file, and plugin-specific files.
562
+ */
563
+ static get paths() {
564
+ const allowedRootPaths = formatGlobCollectionPattern(
565
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS
566
+ );
567
+ const allowedExtensions = formatGlobCollectionPattern(PROJECT_ALLOWED_EXTENSIONS);
568
+ return [
569
+ // Plugin default files
570
+ `./${allowedRootPaths}/**/*.${allowedExtensions}`,
571
+ `!./**/node_modules/**/*`,
572
+ `!./**/dist/**/*`,
573
+ // Root package.json file
574
+ PROJECT_PACKAGE_JSON,
575
+ // Plugin root files
576
+ ...PROJECT_PLUGIN_ROOT_FILES
577
+ ];
578
+ }
579
+ constructor(cwd) {
580
+ super(cwd, { paths: PluginProject.paths });
581
+ }
540
582
  }
541
583
  const isPlugin = (cwd) => {
542
584
  const packageJSONPath = path$1.join(cwd, PROJECT_PACKAGE_JSON);
@@ -551,10 +593,7 @@ const isPlugin = (cwd) => {
551
593
  };
552
594
  const projectFactory = (cwd) => {
553
595
  fse.accessSync(cwd);
554
- if (isPlugin(cwd)) {
555
- return new PluginProject(cwd);
556
- }
557
- return new AppProject(cwd);
596
+ return isPlugin(cwd) ? new PluginProject(cwd) : new AppProject(cwd);
558
597
  };
559
598
  const isPluginProject = (project) => {
560
599
  return project instanceof PluginProject;
@@ -564,12 +603,12 @@ function assertPluginProject(project) {
564
603
  throw new Error("Project is not a plugin");
565
604
  }
566
605
  }
567
- const isAppProject = (project) => {
606
+ const isApplicationProject = (project) => {
568
607
  return project instanceof AppProject;
569
608
  };
570
609
  function assertAppProject(project) {
571
- if (!isAppProject(project)) {
572
- throw new Error("Project is not an app");
610
+ if (!isApplicationProject(project)) {
611
+ throw new Error("Project is not an application");
573
612
  }
574
613
  }
575
614
  const index$a = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
@@ -577,7 +616,7 @@ const index$a = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
577
616
  assertAppProject,
578
617
  assertPluginProject,
579
618
  constants: constants$3,
580
- isAppProject,
619
+ isApplicationProject,
581
620
  isPluginProject,
582
621
  projectFactory
583
622
  }, Symbol.toStringTag, { value: "Module" }));
@@ -604,7 +643,14 @@ const path = (path2) => chalk.blue(path2);
604
643
  const version = (version2) => {
605
644
  return chalk.italic.yellow(`v${version2}`);
606
645
  };
607
- const versionRange = (range) => chalk.italic.yellow(range);
646
+ const codemodUID = (uid) => {
647
+ return chalk.bold.cyan(uid);
648
+ };
649
+ const projectDetails = (project) => {
650
+ return `Project: TYPE=${projectType(project.type)}; CWD=${path(project.cwd)}; PATHS=${project.paths.map(path)}`;
651
+ };
652
+ const projectType = (type) => chalk.cyan(type);
653
+ const versionRange = (range) => chalk.italic.yellow(range.raw);
608
654
  const transform = (transformFilePath) => chalk.cyan(transformFilePath);
609
655
  const highlight = (arg) => chalk.bold.underline(arg);
610
656
  const upgradeStep = (text, step) => {
@@ -636,15 +682,41 @@ const reports = (reports2) => {
636
682
  table.push(...rows);
637
683
  return table.toString();
638
684
  };
685
+ const codemodList = (codemods) => {
686
+ const rows = codemods.map((codemod, index2) => {
687
+ const fIndex = chalk.grey(index2);
688
+ const fVersion = chalk.magenta(codemod.version);
689
+ const fKind = chalk.yellow(codemod.kind);
690
+ const fName = chalk.blue(codemod.format());
691
+ const fUID = codemodUID(codemod.uid);
692
+ return [fIndex, fVersion, fKind, fName, fUID];
693
+ });
694
+ const table = new CliTable3({
695
+ style: { compact: true },
696
+ head: [
697
+ chalk.bold.grey("N°"),
698
+ chalk.bold.magenta("Version"),
699
+ chalk.bold.yellow("Kind"),
700
+ chalk.bold.blue("Name"),
701
+ chalk.bold.cyan("UID")
702
+ ]
703
+ });
704
+ table.push(...rows);
705
+ return table.toString();
706
+ };
639
707
  const durationMs = (elapsedMs) => {
640
708
  const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
641
709
  return `${elapsedSeconds}s`;
642
710
  };
643
711
  const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
644
712
  __proto__: null,
713
+ codemodList,
714
+ codemodUID,
645
715
  durationMs,
646
716
  highlight,
647
717
  path,
718
+ projectDetails,
719
+ projectType,
648
720
  reports,
649
721
  transform,
650
722
  upgradeStep,
@@ -667,6 +739,7 @@ const constants$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP
667
739
  CODEMOD_JSON_SUFFIX
668
740
  }, Symbol.toStringTag, { value: "Module" }));
669
741
  class Codemod {
742
+ uid;
670
743
  kind;
671
744
  version;
672
745
  baseDirectory;
@@ -678,9 +751,27 @@ class Codemod {
678
751
  this.baseDirectory = options.baseDirectory;
679
752
  this.filename = options.filename;
680
753
  this.path = path$1.join(this.baseDirectory, this.version.raw, this.filename);
681
- }
682
- format() {
683
- return this.filename.replace(`.${CODEMOD_CODE_SUFFIX}.${CODEMOD_EXTENSION}`, "").replace(`.${CODEMOD_JSON_SUFFIX}.${CODEMOD_EXTENSION}`, "").replaceAll("-", " ");
754
+ this.uid = this.createUID();
755
+ }
756
+ createUID() {
757
+ const name = this.format({ stripExtension: true, stripKind: true, stripHyphens: false });
758
+ const kind = this.kind;
759
+ const version2 = this.version.raw;
760
+ return `${version2}-${name}-${kind}`;
761
+ }
762
+ format(options) {
763
+ const { stripExtension = true, stripKind = true, stripHyphens = true } = options ?? {};
764
+ let formatted = this.filename;
765
+ if (stripExtension) {
766
+ formatted = formatted.replace(new RegExp(`\\.${CODEMOD_EXTENSION}$`, "i"), "");
767
+ }
768
+ if (stripKind) {
769
+ formatted = formatted.replace(`.${CODEMOD_CODE_SUFFIX}`, "").replace(`.${CODEMOD_JSON_SUFFIX}`, "");
770
+ }
771
+ if (stripHyphens) {
772
+ formatted = formatted.replaceAll("-", " ");
773
+ }
774
+ return formatted;
684
775
  }
685
776
  }
686
777
  const codemodFactory = (options) => new Codemod(options);
@@ -689,6 +780,20 @@ const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
689
780
  codemodFactory,
690
781
  constants: constants$2
691
782
  }, Symbol.toStringTag, { value: "Module" }));
783
+ const INTERNAL_CODEMODS_DIRECTORY = path$1.join(
784
+ __dirname,
785
+ // upgrade/dist
786
+ "..",
787
+ // upgrade
788
+ "resources",
789
+ // upgrade/resources
790
+ "codemods"
791
+ // upgrade/resources/codemods
792
+ );
793
+ const constants$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
794
+ __proto__: null,
795
+ INTERNAL_CODEMODS_DIRECTORY
796
+ }, Symbol.toStringTag, { value: "Module" }));
692
797
  class CodemodRepository {
693
798
  groups;
694
799
  versions;
@@ -707,29 +812,47 @@ class CodemodRepository {
707
812
  count(version2) {
708
813
  return this.findByVersion(version2).length;
709
814
  }
710
- countRange(range) {
711
- return this.findByRange(range).length;
712
- }
713
- exists(version2) {
815
+ versionExists(version2) {
714
816
  return version2.raw in this.groups;
715
817
  }
716
- findByRange(range) {
818
+ has(uid) {
819
+ const result = this.find({ uids: [uid] });
820
+ if (result.length !== 1) {
821
+ return false;
822
+ }
823
+ const { codemods } = result[0];
824
+ return codemods.length === 1 && codemods[0].uid === uid;
825
+ }
826
+ find(q) {
717
827
  const entries = Object.entries(this.groups);
718
- return entries.filter(([version2]) => range.test(version2)).map(([version2, codemods2]) => ({
828
+ return entries.filter(maybeFilterByRange).map(([version2, codemods]) => ({
719
829
  version: semVerFactory(version2),
720
- codemods: codemods2
721
- }));
830
+ // Filter by UID if provided in the query
831
+ codemods: codemods.filter(maybeFilterByUIDs)
832
+ })).filter(({ codemods }) => codemods.length > 0);
833
+ function maybeFilterByRange([version2]) {
834
+ if (!isRangeInstance(q.range)) {
835
+ return true;
836
+ }
837
+ return q.range.test(version2);
838
+ }
839
+ function maybeFilterByUIDs(codemod) {
840
+ if (q.uids === void 0) {
841
+ return true;
842
+ }
843
+ return q.uids.includes(codemod.uid);
844
+ }
722
845
  }
723
846
  findByVersion(version2) {
724
847
  const literalVersion = version2.raw;
725
- const codemods2 = this.groups[literalVersion];
726
- return codemods2 ?? [];
848
+ const codemods = this.groups[literalVersion];
849
+ return codemods ?? [];
727
850
  }
728
851
  findAll() {
729
852
  const entries = Object.entries(this.groups);
730
- return entries.map(([version2, codemods2]) => ({
853
+ return entries.map(([version2, codemods]) => ({
731
854
  version: semVerFactory(version2),
732
- codemods: codemods2
855
+ codemods
733
856
  }));
734
857
  }
735
858
  refreshAvailableVersions() {
@@ -761,18 +884,9 @@ const parseCodemodKindFromFilename = (filename) => {
761
884
  assert(CODEMOD_ALLOWED_SUFFIXES.includes(kind));
762
885
  return kind;
763
886
  };
764
- const codemodRepositoryFactory = (cwd) => new CodemodRepository(cwd);
765
- const INTERNAL_CODEMODS_DIRECTORY = path$1.join(
766
- __dirname,
767
- "..",
768
- "..",
769
- "resources",
770
- "codemods"
771
- );
772
- const constants$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
773
- __proto__: null,
774
- INTERNAL_CODEMODS_DIRECTORY
775
- }, Symbol.toStringTag, { value: "Module" }));
887
+ const codemodRepositoryFactory = (cwd = INTERNAL_CODEMODS_DIRECTORY) => {
888
+ return new CodemodRepository(cwd);
889
+ };
776
890
  const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
777
891
  __proto__: null,
778
892
  codemodRepositoryFactory,
@@ -807,40 +921,59 @@ class CodemodRunner {
807
921
  this.isDry = enabled;
808
922
  return this;
809
923
  }
810
- async run(codemodsDirectory) {
924
+ createRepository(codemodsDirectory) {
811
925
  const repository = codemodRepositoryFactory(
812
926
  codemodsDirectory ?? INTERNAL_CODEMODS_DIRECTORY
813
927
  );
814
928
  repository.refresh();
815
- const allVersionedCodemods = this.range ? repository.findByRange(this.range) : repository.findAll();
816
- const versionedCodemods = this.selectCodemodsCallback ? await this.selectCodemodsCallback(allVersionedCodemods) : allVersionedCodemods;
817
- const hasCodemodsToRun = versionedCodemods.length > 0;
818
- if (!hasCodemodsToRun) {
819
- if (this.range) {
820
- this.logger?.debug(`Found no codemods to run for ${versionRange(this.range)}`);
821
- } else {
822
- this.logger?.debug(`Found no codemods to run`);
823
- }
824
- return successReport$1();
825
- }
826
- if (this.range) {
827
- this.logger?.debug(
828
- `Found codemods for ${highlight(versionedCodemods.length)} version(s) using ${this.range}`
929
+ return repository;
930
+ }
931
+ async safeRunAndReport(codemods) {
932
+ if (this.isDry) {
933
+ this.logger?.warn?.(
934
+ "Running the codemods in dry mode. No files will be modified during the process."
829
935
  );
830
- } else {
831
- this.logger?.debug(`Found codemods for ${highlight(versionedCodemods.length)} version(s)`);
832
936
  }
833
- versionedCodemods.forEach(
834
- ({ version: version$1, codemods: codemods22 }) => this.logger?.debug(`- ${version(version$1)} (${codemods22.length})`)
835
- );
836
- const codemods2 = versionedCodemods.map(({ codemods: codemods22 }) => codemods22).flat();
837
937
  try {
838
- const reports$1 = await this.project.runCodemods(codemods2, { dry: this.isDry });
839
- this.logger?.raw(reports(reports$1));
938
+ const reports$1 = await this.project.runCodemods(codemods, { dry: this.isDry });
939
+ this.logger?.raw?.(reports(reports$1));
940
+ if (!this.isDry) {
941
+ const nbAffectedTotal = reports$1.flatMap((report) => report.report.ok).reduce((acc, nb) => acc + nb, 0);
942
+ this.logger?.debug?.(
943
+ `Successfully ran ${highlight(codemods.length)} codemod(s), ${highlight(nbAffectedTotal)} change(s) have been detected`
944
+ );
945
+ }
946
+ return successReport$1();
840
947
  } catch (e) {
841
948
  return erroredReport$1(unknownToError(e));
842
949
  }
843
- return successReport$1();
950
+ }
951
+ async runByUID(uid, codemodsDirectory) {
952
+ const repository = this.createRepository(codemodsDirectory);
953
+ if (!repository.has(uid)) {
954
+ throw new Error(`Unknown codemod UID provided: ${uid}`);
955
+ }
956
+ const codemods = repository.find({ uids: [uid] }).flatMap(({ codemods: codemods2 }) => codemods2);
957
+ return this.safeRunAndReport(codemods);
958
+ }
959
+ async run(codemodsDirectory) {
960
+ const repository = this.createRepository(codemodsDirectory);
961
+ const codemodsInRange = repository.find({ range: this.range });
962
+ const selectedCodemods = this.selectCodemodsCallback ? await this.selectCodemodsCallback(codemodsInRange) : codemodsInRange;
963
+ if (selectedCodemods.length === 0) {
964
+ this.logger?.debug?.(`Found no codemods to run for ${versionRange(this.range)}`);
965
+ return successReport$1();
966
+ }
967
+ const codemods = selectedCodemods.flatMap(({ codemods: codemods2 }) => codemods2);
968
+ const codemodsByVersion = groupBy("version", codemods);
969
+ const fRange = versionRange(this.range);
970
+ this.logger?.debug?.(
971
+ `Found ${highlight(codemods.length)} codemods for ${highlight(size(codemodsByVersion))} version(s) using ${fRange}`
972
+ );
973
+ for (const [version$1, codemods2] of Object.entries(codemodsByVersion)) {
974
+ this.logger?.debug?.(`- ${version(semVerFactory(version$1))} (${codemods2.length})`);
975
+ }
976
+ return this.safeRunAndReport(codemods);
844
977
  }
845
978
  }
846
979
  const codemodRunnerFactory = (project, range) => {
@@ -879,7 +1012,7 @@ class Upgrader {
879
1012
  this.codemodsTarget = semVerFactory(
880
1013
  `${this.target.major}.${this.target.minor}.${this.target.patch}`
881
1014
  );
882
- this.logger?.debug(
1015
+ this.logger?.debug?.(
883
1016
  `The codemods target has been synced with the upgrade target. The codemod runner will now look for ${version(
884
1017
  this.codemodsTarget
885
1018
  )}`
@@ -888,7 +1021,7 @@ class Upgrader {
888
1021
  }
889
1022
  overrideCodemodsTarget(target) {
890
1023
  this.codemodsTarget = target;
891
- this.logger?.debug(
1024
+ this.logger?.debug?.(
892
1025
  `Overriding the codemods target. The codemod runner will now look for ${version(target)}`
893
1026
  );
894
1027
  return this;
@@ -908,40 +1041,40 @@ class Upgrader {
908
1041
  addRequirement(requirement) {
909
1042
  this.requirements.push(requirement);
910
1043
  const fRequired = requirement.isRequired ? "(required)" : "(optional)";
911
- this.logger?.debug(
1044
+ this.logger?.debug?.(
912
1045
  `Added a new requirement to the upgrade: ${highlight(requirement.name)} ${fRequired}`
913
1046
  );
914
1047
  return this;
915
1048
  }
916
1049
  async upgrade() {
917
- this.logger?.info(
1050
+ this.logger?.info?.(
918
1051
  `Upgrading from ${version(this.project.strapiVersion)} to ${version(this.target)}`
919
1052
  );
920
1053
  if (this.isDry) {
921
- this.logger?.warn(
1054
+ this.logger?.warn?.(
922
1055
  "Running the upgrade in dry mode. No files will be modified during the process."
923
1056
  );
924
1057
  }
925
1058
  const range = rangeFromVersions(this.project.strapiVersion, this.target);
926
1059
  const codemodsRange = rangeFromVersions(this.project.strapiVersion, this.codemodsTarget);
927
1060
  const npmVersionsMatches = this.npmPackage?.findVersionsInRange(range) ?? [];
928
- this.logger?.debug(
1061
+ this.logger?.debug?.(
929
1062
  `Found ${highlight(npmVersionsMatches.length)} versions satisfying ${versionRange(range)}`
930
1063
  );
931
1064
  try {
932
- this.logger?.info(upgradeStep("Checking requirement", [1, 4]));
1065
+ this.logger?.info?.(upgradeStep("Checking requirement", [1, 4]));
933
1066
  await this.checkRequirements(this.requirements, {
934
1067
  npmVersionsMatches,
935
1068
  project: this.project,
936
1069
  target: this.target
937
1070
  });
938
- this.logger?.info(upgradeStep("Applying the latest code modifications", [2, 4]));
1071
+ this.logger?.info?.(upgradeStep("Applying the latest code modifications", [2, 4]));
939
1072
  await this.runCodemods(codemodsRange);
940
- this.logger?.debug("Refreshing project information...");
1073
+ this.logger?.debug?.("Refreshing project information...");
941
1074
  this.project.refresh();
942
- this.logger?.info(upgradeStep("Upgrading Strapi dependencies", [3, 4]));
1075
+ this.logger?.info?.(upgradeStep("Upgrading Strapi dependencies", [3, 4]));
943
1076
  await this.updateDependencies();
944
- this.logger?.info(upgradeStep("Installing dependencies", [4, 4]));
1077
+ this.logger?.info?.(upgradeStep("Installing dependencies", [4, 4]));
945
1078
  await this.installDependencies();
946
1079
  } catch (e) {
947
1080
  return erroredReport(unknownToError(e));
@@ -974,7 +1107,7 @@ class Upgrader {
974
1107
  if (requirement.isRequired) {
975
1108
  throw error;
976
1109
  }
977
- this.logger?.warn(warningMessage);
1110
+ this.logger?.warn?.(warningMessage);
978
1111
  const response = await this.confirmationCallback?.(confirmationMessage);
979
1112
  if (!response) {
980
1113
  throw error;
@@ -985,9 +1118,11 @@ class Upgrader {
985
1118
  const json = createJSONTransformAPI(packageJSON);
986
1119
  const dependencies = json.get("dependencies", {});
987
1120
  const strapiDependencies = this.getScopedStrapiDependencies(dependencies);
988
- this.logger?.debug(`Found ${highlight(strapiDependencies.length)} dependency(ies) to update`);
1121
+ this.logger?.debug?.(
1122
+ `Found ${highlight(strapiDependencies.length)} dependency(ies) to update`
1123
+ );
989
1124
  strapiDependencies.forEach(
990
- (dependency) => this.logger?.debug(`- ${dependency[0]} (${dependency[1]} -> ${this.target})`)
1125
+ (dependency) => this.logger?.debug?.(`- ${dependency[0]} (${dependency[1]} -> ${this.target})`)
991
1126
  );
992
1127
  if (strapiDependencies.length === 0) {
993
1128
  return;
@@ -995,7 +1130,7 @@ class Upgrader {
995
1130
  strapiDependencies.forEach(([name]) => json.set(`dependencies.${name}`, this.target.raw));
996
1131
  const updatedPackageJSON = json.root();
997
1132
  if (this.isDry) {
998
- this.logger?.debug(`Skipping dependencies update (${chalk.italic("dry mode")})`);
1133
+ this.logger?.debug?.(`Skipping dependencies update (${chalk.italic("dry mode")})`);
999
1134
  return;
1000
1135
  }
1001
1136
  await saveJSON(packageJSONPath, updatedPackageJSON);
@@ -1015,9 +1150,9 @@ class Upgrader {
1015
1150
  async installDependencies() {
1016
1151
  const projectPath = this.project.cwd;
1017
1152
  const packageManagerName = await packageManager.getPreferred(projectPath);
1018
- this.logger?.debug(`Using ${highlight(packageManagerName)} as package manager`);
1153
+ this.logger?.debug?.(`Using ${highlight(packageManagerName)} as package manager`);
1019
1154
  if (this.isDry) {
1020
- this.logger?.debug(`Skipping dependencies installation (${chalk.italic("dry mode")}`);
1155
+ this.logger?.debug?.(`Skipping dependencies installation (${chalk.italic("dry mode")}`);
1021
1156
  return;
1022
1157
  }
1023
1158
  await packageManager.installDependencies(projectPath, packageManagerName, {
@@ -1116,7 +1251,8 @@ const upgrade = async (options) => {
1116
1251
  const { logger, codemodsTarget } = options;
1117
1252
  const cwd = path$1.resolve(options.cwd ?? process.cwd());
1118
1253
  const project = projectFactory(cwd);
1119
- if (!isAppProject(project)) {
1254
+ logger.debug(projectDetails(project));
1255
+ if (!isApplicationProject(project)) {
1120
1256
  throw new Error(
1121
1257
  `The "${options.target}" upgrade can only be run on a Strapi project; for plugins, please use "codemods".`
1122
1258
  );
@@ -1138,20 +1274,7 @@ const upgrade = async (options) => {
1138
1274
  timer.stop();
1139
1275
  logger.info(`Completed in ${durationMs(timer.elapsedMs)}`);
1140
1276
  };
1141
- const codemods = async (options) => {
1142
- const timer = timerFactory();
1143
- const { logger } = options;
1144
- const cwd = path$1.resolve(options.cwd ?? process.cwd());
1145
- const project = projectFactory(cwd);
1146
- const range = isAppProject(project) ? getRangeFromTarget(project.strapiVersion, options.target) : void 0;
1147
- const codemodRunner = codemodRunnerFactory(project, range).dry(options.dry ?? false).onSelectCodemods(options.selectCodemods ?? null).setLogger(logger);
1148
- const executionReport = await codemodRunner.run();
1149
- if (!executionReport.success) {
1150
- throw executionReport.error;
1151
- }
1152
- timer.stop();
1153
- logger.info(`Completed in ${timer.elapsedMs}`);
1154
- };
1277
+ const resolvePath = (cwd) => path$1.resolve(cwd ?? process.cwd());
1155
1278
  const getRangeFromTarget = (currentVersion, target) => {
1156
1279
  if (isSemverInstance(target)) {
1157
1280
  return rangeFactory(target);
@@ -1168,9 +1291,60 @@ const getRangeFromTarget = (currentVersion, target) => {
1168
1291
  throw new Error(`Invalid target set: ${target}`);
1169
1292
  }
1170
1293
  };
1294
+ const findRangeFromTarget = (project, target) => {
1295
+ if (isRangeInstance(target)) {
1296
+ return target;
1297
+ }
1298
+ if (isApplicationProject(project)) {
1299
+ return getRangeFromTarget(project.strapiVersion, target);
1300
+ }
1301
+ return rangeFactory("*");
1302
+ };
1303
+ const runCodemods = async (options) => {
1304
+ const timer = timerFactory();
1305
+ const { logger, uid } = options;
1306
+ const cwd = resolvePath(options.cwd);
1307
+ const project = projectFactory(cwd);
1308
+ const range = findRangeFromTarget(project, options.target);
1309
+ logger.debug(projectDetails(project));
1310
+ logger.debug(`Range: set to ${versionRange(range)}`);
1311
+ const codemodRunner = codemodRunnerFactory(project, range).dry(options.dry ?? false).onSelectCodemods(options.selectCodemods ?? null).setLogger(logger);
1312
+ let report;
1313
+ if (uid !== void 0) {
1314
+ logger.debug(`Running a single codemod: ${codemodUID(uid)}`);
1315
+ report = await codemodRunner.runByUID(uid);
1316
+ } else {
1317
+ report = await codemodRunner.run();
1318
+ }
1319
+ if (!report.success) {
1320
+ throw report.error;
1321
+ }
1322
+ timer.stop();
1323
+ logger.info(`Completed in ${timer.elapsedMs}`);
1324
+ };
1325
+ const listCodemods = async (options) => {
1326
+ const { logger, target } = options;
1327
+ const cwd = resolvePath(options.cwd);
1328
+ const project = projectFactory(cwd);
1329
+ const range = findRangeFromTarget(project, target);
1330
+ logger.debug(projectDetails(project));
1331
+ logger.debug(`Range: set to ${versionRange(range)}`);
1332
+ const repo = codemodRepositoryFactory();
1333
+ repo.refresh();
1334
+ const groups = repo.find({ range });
1335
+ const codemods = groups.flatMap((collection) => collection.codemods);
1336
+ logger.debug(`Found ${highlight(codemods.length)} codemods`);
1337
+ if (codemods.length === 0) {
1338
+ logger.info(`Found no codemods matching ${versionRange(range)}`);
1339
+ return;
1340
+ }
1341
+ const fCodemods = codemodList(codemods);
1342
+ logger.raw(fCodemods);
1343
+ };
1171
1344
  const index$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1172
1345
  __proto__: null,
1173
- codemods,
1346
+ listCodemods,
1347
+ runCodemods,
1174
1348
  upgrade
1175
1349
  }, Symbol.toStringTag, { value: "Module" }));
1176
1350
  class Logger {