@strapi/upgrade 0.0.0-experimental.e3e48deb89bd0a1b6cc69b698696566fa7854a95 → 0.0.0-experimental.e9122b401c96877b6707775c4f893660eab93ae3

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 (84) hide show
  1. package/LICENSE +19 -4
  2. package/dist/cli.js +1482 -5
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +366 -105
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +367 -106
  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/format/formats.d.ts +6 -0
  24. package/dist/modules/format/formats.d.ts.map +1 -1
  25. package/dist/modules/project/constants.d.ts +6 -3
  26. package/dist/modules/project/constants.d.ts.map +1 -1
  27. package/dist/modules/project/index.d.ts +2 -0
  28. package/dist/modules/project/index.d.ts.map +1 -1
  29. package/dist/modules/project/project.d.ts +27 -5
  30. package/dist/modules/project/project.d.ts.map +1 -1
  31. package/dist/modules/project/types.d.ts +3 -10
  32. package/dist/modules/project/types.d.ts.map +1 -1
  33. package/dist/modules/project/utils.d.ts +6 -0
  34. package/dist/modules/project/utils.d.ts.map +1 -0
  35. package/dist/modules/report/report.d.ts.map +1 -1
  36. package/dist/modules/requirement/types.d.ts +2 -2
  37. package/dist/modules/requirement/types.d.ts.map +1 -1
  38. package/dist/modules/runner/json/transform.d.ts.map +1 -1
  39. package/dist/modules/upgrader/upgrader.d.ts +3 -3
  40. package/dist/modules/upgrader/upgrader.d.ts.map +1 -1
  41. package/dist/modules/version/range.d.ts +2 -0
  42. package/dist/modules/version/range.d.ts.map +1 -1
  43. package/dist/tasks/codemods/index.d.ts +2 -1
  44. package/dist/tasks/codemods/index.d.ts.map +1 -1
  45. package/dist/tasks/codemods/list-codemods.d.ts +3 -0
  46. package/dist/tasks/codemods/list-codemods.d.ts.map +1 -0
  47. package/dist/tasks/codemods/run-codemods.d.ts +3 -0
  48. package/dist/tasks/codemods/run-codemods.d.ts.map +1 -0
  49. package/dist/tasks/codemods/types.d.ts +9 -3
  50. package/dist/tasks/codemods/types.d.ts.map +1 -1
  51. package/dist/tasks/codemods/utils.d.ts +6 -0
  52. package/dist/tasks/codemods/utils.d.ts.map +1 -0
  53. package/dist/tasks/index.d.ts +1 -1
  54. package/dist/tasks/index.d.ts.map +1 -1
  55. package/dist/tasks/upgrade/upgrade.d.ts.map +1 -1
  56. package/package.json +9 -8
  57. package/resources/codemods/5.0.0/change-useAPIErrorHandler-import.code.ts +21 -0
  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/entity-service-document-service.code.ts +437 -0
  64. package/resources/codemods/5.0.0/nocontent-migrate-to-emptystatelayout.code.ts +30 -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/useRBAC-hook-import-change.code.ts +21 -0
  70. package/resources/codemods/5.0.0/utils-public-interface.code.ts +320 -0
  71. package/resources/examples/console.log-to-console.info.code.ts +1 -1
  72. package/resources/examples/disable-jsx-buttons.code.ts +42 -0
  73. package/resources/utils/change-import.ts +105 -0
  74. package/resources/utils/replace-jsx.ts +49 -0
  75. package/dist/_chunks/codemod-runner-B5OeSMTQ.js +0 -730
  76. package/dist/_chunks/codemod-runner-B5OeSMTQ.js.map +0 -1
  77. package/dist/_chunks/codemods-10ZKewQx.js +0 -108
  78. package/dist/_chunks/codemods-10ZKewQx.js.map +0 -1
  79. package/dist/_chunks/index-uxCwtuH1.js +0 -103
  80. package/dist/_chunks/index-uxCwtuH1.js.map +0 -1
  81. package/dist/_chunks/upgrade-A4T1OWs5.js +0 -357
  82. package/dist/_chunks/upgrade-A4T1OWs5.js.map +0 -1
  83. package/dist/tasks/codemods/codemods.d.ts +0 -3
  84. package/dist/tasks/codemods/codemods.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ 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
9
  import { glob } from "glob";
@@ -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,
@@ -320,7 +326,11 @@ const transformJSON = async (codemodPath, paths, config) => {
320
326
  timeElapsed: "",
321
327
  stats: {}
322
328
  };
323
- const esbuildOptions = { extensions: [".js", ".mjs", ".ts"] };
329
+ const esbuildOptions = {
330
+ extensions: [".js", ".mjs", ".ts"],
331
+ hookIgnoreNodeModules: false,
332
+ hookMatcher: isEqual(codemodPath)
333
+ };
324
334
  const { unregister } = register(esbuildOptions);
325
335
  const module = require(codemodPath);
326
336
  unregister();
@@ -365,17 +375,31 @@ const index$b = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
365
375
  jsonRunnerFactory
366
376
  }, Symbol.toStringTag, { value: "Module" }));
367
377
  const PROJECT_PACKAGE_JSON = "package.json";
368
- const PROJECT_DEFAULT_ALLOWED_ROOT_PATHS = ["src", "config", "public"];
369
- const PROJECT_DEFAULT_ALLOWED_EXTENSIONS = ["js", "ts", "json"];
370
- const PROJECT_DEFAULT_PATTERNS = ["package.json"];
378
+ const PROJECT_APP_ALLOWED_ROOT_PATHS = ["src", "config", "public"];
379
+ const PROJECT_PLUGIN_ALLOWED_ROOT_PATHS = ["admin", "server"];
380
+ const PROJECT_PLUGIN_ROOT_FILES = ["strapi-admin.js", "strapi-server.js"];
381
+ const PROJECT_CODE_EXTENSIONS = [
382
+ // Source files
383
+ "js",
384
+ "mjs",
385
+ "ts",
386
+ // React files
387
+ "jsx",
388
+ "tsx"
389
+ ];
390
+ const PROJECT_JSON_EXTENSIONS = ["json"];
391
+ const PROJECT_ALLOWED_EXTENSIONS = [...PROJECT_CODE_EXTENSIONS, ...PROJECT_JSON_EXTENSIONS];
371
392
  const SCOPED_STRAPI_PACKAGE_PREFIX = "@strapi/";
372
393
  const STRAPI_DEPENDENCY_NAME = `${SCOPED_STRAPI_PACKAGE_PREFIX}strapi`;
373
394
  const constants$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
374
395
  __proto__: null,
375
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS,
376
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS,
377
- PROJECT_DEFAULT_PATTERNS,
396
+ PROJECT_ALLOWED_EXTENSIONS,
397
+ PROJECT_APP_ALLOWED_ROOT_PATHS,
398
+ PROJECT_CODE_EXTENSIONS,
399
+ PROJECT_JSON_EXTENSIONS,
378
400
  PROJECT_PACKAGE_JSON,
401
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS,
402
+ PROJECT_PLUGIN_ROOT_FILES,
379
403
  SCOPED_STRAPI_PACKAGE_PREFIX,
380
404
  STRAPI_DEPENDENCY_NAME
381
405
  }, Symbol.toStringTag, { value: "Module" }));
@@ -385,12 +409,13 @@ class Project {
385
409
  files;
386
410
  packageJSONPath;
387
411
  packageJSON;
388
- strapiVersion;
389
- constructor(cwd) {
412
+ paths;
413
+ constructor(cwd, config) {
390
414
  if (!fse.pathExistsSync(cwd)) {
391
415
  throw new Error(`ENOENT: no such file or directory, access '${cwd}'`);
392
416
  }
393
417
  this.cwd = cwd;
418
+ this.paths = config.paths;
394
419
  this.refresh();
395
420
  }
396
421
  getFilesByExtensions(extensions) {
@@ -401,14 +426,13 @@ class Project {
401
426
  }
402
427
  refresh() {
403
428
  this.refreshPackageJSON();
404
- this.refreshStrapiVersion();
405
429
  this.refreshProjectFiles();
406
430
  return this;
407
431
  }
408
- async runCodemods(codemods2, options) {
432
+ async runCodemods(codemods, options) {
409
433
  const runners = this.createProjectCodemodsRunners(options.dry);
410
434
  const reports2 = [];
411
- for (const codemod of codemods2) {
435
+ for (const codemod of codemods) {
412
436
  for (const runner of runners) {
413
437
  if (runner.valid(codemod)) {
414
438
  const report = await runner.run(codemod);
@@ -419,17 +443,20 @@ class Project {
419
443
  return reports2;
420
444
  }
421
445
  createProjectCodemodsRunners(dry = false) {
422
- const jsonFiles = this.getFilesByExtensions([".json"]);
423
- const codeFiles = this.getFilesByExtensions([".js", ".ts", ".mjs"]);
446
+ const jsonExtensions = PROJECT_JSON_EXTENSIONS.map((ext) => `.${ext}`);
447
+ const codeExtensions = PROJECT_CODE_EXTENSIONS.map((ext) => `.${ext}`);
448
+ const jsonFiles = this.getFilesByExtensions(jsonExtensions);
449
+ const codeFiles = this.getFilesByExtensions(codeExtensions);
424
450
  const codeRunner = codeRunnerFactory(codeFiles, {
425
451
  dry,
426
- print: false,
427
- silent: true,
428
- extensions: "js,ts",
452
+ parser: "ts",
429
453
  runInBand: true,
430
- verbose: 0,
431
454
  babel: true,
432
- parser: "ts"
455
+ extensions: PROJECT_CODE_EXTENSIONS.join(","),
456
+ // Don't output any log coming from the runner
457
+ print: false,
458
+ silent: true,
459
+ verbose: 0
433
460
  });
434
461
  const jsonRunner = jsonRunnerFactory(jsonFiles, { dry, cwd: this.cwd });
435
462
  return [codeRunner, jsonRunner];
@@ -446,16 +473,36 @@ class Project {
446
473
  this.packageJSON = JSON.parse(packageJSONBuffer.toString());
447
474
  }
448
475
  refreshProjectFiles() {
449
- const allowedRootPaths = formatGlobCollectionPattern(
450
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS
451
- );
452
- const allowedExtensions = formatGlobCollectionPattern(
453
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS
454
- );
455
- const projectFilesPattern = `./${allowedRootPaths}/**/*.${allowedExtensions}`;
456
- const patterns = [projectFilesPattern, ...PROJECT_DEFAULT_PATTERNS];
457
476
  const scanner = fileScannerFactory(this.cwd);
458
- this.files = scanner.scan(patterns);
477
+ this.files = scanner.scan(this.paths);
478
+ }
479
+ }
480
+ class AppProject extends Project {
481
+ strapiVersion;
482
+ type = "application";
483
+ /**
484
+ * Returns an array of allowed file paths for a Strapi application
485
+ *
486
+ * The resulting paths include app default files and the root package.json file.
487
+ */
488
+ static get paths() {
489
+ const allowedRootPaths = formatGlobCollectionPattern(PROJECT_APP_ALLOWED_ROOT_PATHS);
490
+ const allowedExtensions = formatGlobCollectionPattern(PROJECT_ALLOWED_EXTENSIONS);
491
+ return [
492
+ // App default files
493
+ `./${allowedRootPaths}/**/*.${allowedExtensions}`,
494
+ // Root package.json file
495
+ PROJECT_PACKAGE_JSON
496
+ ];
497
+ }
498
+ constructor(cwd) {
499
+ super(cwd, { paths: AppProject.paths });
500
+ this.refreshStrapiVersion();
501
+ }
502
+ refresh() {
503
+ super.refresh();
504
+ this.refreshStrapiVersion();
505
+ return this;
459
506
  }
460
507
  refreshStrapiVersion() {
461
508
  this.strapiVersion = // First try to get the strapi version from the package.json dependencies
@@ -502,10 +549,69 @@ const formatGlobCollectionPattern = (collection) => {
502
549
  );
503
550
  return collection.length === 1 ? collection[0] : `{${collection}}`;
504
551
  };
505
- const projectFactory = (cwd) => new Project(cwd);
552
+ class PluginProject extends Project {
553
+ type = "plugin";
554
+ /**
555
+ * Returns an array of allowed file paths for a Strapi plugin
556
+ *
557
+ * The resulting paths include plugin default files, the root package.json file, and plugin-specific files.
558
+ */
559
+ static get paths() {
560
+ const allowedRootPaths = formatGlobCollectionPattern(
561
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS
562
+ );
563
+ const allowedExtensions = formatGlobCollectionPattern(PROJECT_ALLOWED_EXTENSIONS);
564
+ return [
565
+ // Plugin default files
566
+ `./${allowedRootPaths}/**/*.${allowedExtensions}`,
567
+ // Root package.json file
568
+ PROJECT_PACKAGE_JSON,
569
+ // Plugin root files
570
+ ...PROJECT_PLUGIN_ROOT_FILES
571
+ ];
572
+ }
573
+ constructor(cwd) {
574
+ super(cwd, { paths: PluginProject.paths });
575
+ }
576
+ }
577
+ const isPlugin = (cwd) => {
578
+ const packageJSONPath = path$1.join(cwd, PROJECT_PACKAGE_JSON);
579
+ try {
580
+ fse.accessSync(packageJSONPath);
581
+ } catch {
582
+ throw new Error(`Could not find a ${PROJECT_PACKAGE_JSON} file in ${cwd}`);
583
+ }
584
+ const packageJSONBuffer = fse.readFileSync(packageJSONPath);
585
+ const packageJSON = JSON.parse(packageJSONBuffer.toString());
586
+ return packageJSON?.strapi?.kind === "plugin";
587
+ };
588
+ const projectFactory = (cwd) => {
589
+ fse.accessSync(cwd);
590
+ return isPlugin(cwd) ? new PluginProject(cwd) : new AppProject(cwd);
591
+ };
592
+ const isPluginProject = (project) => {
593
+ return project instanceof PluginProject;
594
+ };
595
+ function assertPluginProject(project) {
596
+ if (!isPluginProject(project)) {
597
+ throw new Error("Project is not a plugin");
598
+ }
599
+ }
600
+ const isApplicationProject = (project) => {
601
+ return project instanceof AppProject;
602
+ };
603
+ function assertAppProject(project) {
604
+ if (!isApplicationProject(project)) {
605
+ throw new Error("Project is not an application");
606
+ }
607
+ }
506
608
  const index$a = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
507
609
  __proto__: null,
610
+ assertAppProject,
611
+ assertPluginProject,
508
612
  constants: constants$3,
613
+ isApplicationProject,
614
+ isPluginProject,
509
615
  projectFactory
510
616
  }, Symbol.toStringTag, { value: "Module" }));
511
617
  class UnexpectedError extends Error {
@@ -531,7 +637,14 @@ const path = (path2) => chalk.blue(path2);
531
637
  const version = (version2) => {
532
638
  return chalk.italic.yellow(`v${version2}`);
533
639
  };
534
- const versionRange = (range) => chalk.italic.yellow(range);
640
+ const codemodUID = (uid) => {
641
+ return chalk.bold.cyan(uid);
642
+ };
643
+ const projectDetails = (project) => {
644
+ return `Project: TYPE=${projectType(project.type)}; CWD=${path(project.cwd)}; PATHS=${project.paths.map(path)}`;
645
+ };
646
+ const projectType = (type) => chalk.cyan(type);
647
+ const versionRange = (range) => chalk.italic.yellow(range.raw);
535
648
  const transform = (transformFilePath) => chalk.cyan(transformFilePath);
536
649
  const highlight = (arg) => chalk.bold.underline(arg);
537
650
  const upgradeStep = (text, step) => {
@@ -563,15 +676,41 @@ const reports = (reports2) => {
563
676
  table.push(...rows);
564
677
  return table.toString();
565
678
  };
679
+ const codemodList = (codemods) => {
680
+ const rows = codemods.map((codemod, index2) => {
681
+ const fIndex = chalk.grey(index2);
682
+ const fVersion = chalk.magenta(codemod.version);
683
+ const fKind = chalk.yellow(codemod.kind);
684
+ const fName = chalk.blue(codemod.format());
685
+ const fUID = codemodUID(codemod.uid);
686
+ return [fIndex, fVersion, fKind, fName, fUID];
687
+ });
688
+ const table = new CliTable3({
689
+ style: { compact: true },
690
+ head: [
691
+ chalk.bold.grey("N°"),
692
+ chalk.bold.magenta("Version"),
693
+ chalk.bold.yellow("Kind"),
694
+ chalk.bold.blue("Name"),
695
+ chalk.bold.cyan("UID")
696
+ ]
697
+ });
698
+ table.push(...rows);
699
+ return table.toString();
700
+ };
566
701
  const durationMs = (elapsedMs) => {
567
702
  const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
568
703
  return `${elapsedSeconds}s`;
569
704
  };
570
705
  const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
571
706
  __proto__: null,
707
+ codemodList,
708
+ codemodUID,
572
709
  durationMs,
573
710
  highlight,
574
711
  path,
712
+ projectDetails,
713
+ projectType,
575
714
  reports,
576
715
  transform,
577
716
  upgradeStep,
@@ -594,6 +733,7 @@ const constants$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP
594
733
  CODEMOD_JSON_SUFFIX
595
734
  }, Symbol.toStringTag, { value: "Module" }));
596
735
  class Codemod {
736
+ uid;
597
737
  kind;
598
738
  version;
599
739
  baseDirectory;
@@ -605,9 +745,27 @@ class Codemod {
605
745
  this.baseDirectory = options.baseDirectory;
606
746
  this.filename = options.filename;
607
747
  this.path = path$1.join(this.baseDirectory, this.version.raw, this.filename);
608
- }
609
- format() {
610
- return this.filename.replace(`.${CODEMOD_CODE_SUFFIX}.${CODEMOD_EXTENSION}`, "").replace(`.${CODEMOD_JSON_SUFFIX}.${CODEMOD_EXTENSION}`, "").replaceAll("-", " ");
748
+ this.uid = this.createUID();
749
+ }
750
+ createUID() {
751
+ const name = this.format({ stripExtension: true, stripKind: true, stripHyphens: false });
752
+ const kind = this.kind;
753
+ const version2 = this.version.raw;
754
+ return `${version2}-${name}-${kind}`;
755
+ }
756
+ format(options) {
757
+ const { stripExtension = true, stripKind = true, stripHyphens = true } = options ?? {};
758
+ let formatted = this.filename;
759
+ if (stripExtension) {
760
+ formatted = formatted.replace(new RegExp(`\\.${CODEMOD_EXTENSION}$`, "i"), "");
761
+ }
762
+ if (stripKind) {
763
+ formatted = formatted.replace(`.${CODEMOD_CODE_SUFFIX}`, "").replace(`.${CODEMOD_JSON_SUFFIX}`, "");
764
+ }
765
+ if (stripHyphens) {
766
+ formatted = formatted.replaceAll("-", " ");
767
+ }
768
+ return formatted;
611
769
  }
612
770
  }
613
771
  const codemodFactory = (options) => new Codemod(options);
@@ -616,6 +774,20 @@ const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
616
774
  codemodFactory,
617
775
  constants: constants$2
618
776
  }, Symbol.toStringTag, { value: "Module" }));
777
+ const INTERNAL_CODEMODS_DIRECTORY = path$1.join(
778
+ __dirname,
779
+ // upgrade/dist
780
+ "..",
781
+ // upgrade
782
+ "resources",
783
+ // upgrade/resources
784
+ "codemods"
785
+ // upgrade/resources/codemods
786
+ );
787
+ const constants$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
788
+ __proto__: null,
789
+ INTERNAL_CODEMODS_DIRECTORY
790
+ }, Symbol.toStringTag, { value: "Module" }));
619
791
  class CodemodRepository {
620
792
  groups;
621
793
  versions;
@@ -634,23 +806,48 @@ class CodemodRepository {
634
806
  count(version2) {
635
807
  return this.findByVersion(version2).length;
636
808
  }
637
- countRange(range) {
638
- return this.findByRange(range).length;
639
- }
640
- exists(version2) {
809
+ versionExists(version2) {
641
810
  return version2.raw in this.groups;
642
811
  }
643
- findByRange(range) {
812
+ has(uid) {
813
+ const result = this.find({ uids: [uid] });
814
+ if (result.length !== 1) {
815
+ return false;
816
+ }
817
+ const { codemods } = result[0];
818
+ return codemods.length === 1 && codemods[0].uid === uid;
819
+ }
820
+ find(q) {
644
821
  const entries = Object.entries(this.groups);
645
- return entries.filter(([version2]) => range.test(version2)).map(([version2, codemods2]) => ({
822
+ return entries.filter(maybeFilterByRange).map(([version2, codemods]) => ({
646
823
  version: semVerFactory(version2),
647
- codemods: codemods2
648
- }));
824
+ // Filter by UID if provided in the query
825
+ codemods: codemods.filter(maybeFilterByUIDs)
826
+ })).filter(({ codemods }) => codemods.length > 0);
827
+ function maybeFilterByRange([version2]) {
828
+ if (!isRangeInstance(q.range)) {
829
+ return true;
830
+ }
831
+ return q.range.test(version2);
832
+ }
833
+ function maybeFilterByUIDs(codemod) {
834
+ if (q.uids === void 0) {
835
+ return true;
836
+ }
837
+ return q.uids.includes(codemod.uid);
838
+ }
649
839
  }
650
840
  findByVersion(version2) {
651
841
  const literalVersion = version2.raw;
652
- const codemods2 = this.groups[literalVersion];
653
- return codemods2 ?? [];
842
+ const codemods = this.groups[literalVersion];
843
+ return codemods ?? [];
844
+ }
845
+ findAll() {
846
+ const entries = Object.entries(this.groups);
847
+ return entries.map(([version2, codemods]) => ({
848
+ version: semVerFactory(version2),
849
+ codemods
850
+ }));
654
851
  }
655
852
  refreshAvailableVersions() {
656
853
  this.versions = fse.readdirSync(this.cwd).filter((filename) => fse.statSync(path$1.join(this.cwd, filename)).isDirectory()).filter((filename) => semver.valid(filename) !== null).map((version2) => semVerFactory(version2)).sort(semver.compare);
@@ -681,18 +878,9 @@ const parseCodemodKindFromFilename = (filename) => {
681
878
  assert(CODEMOD_ALLOWED_SUFFIXES.includes(kind));
682
879
  return kind;
683
880
  };
684
- const codemodRepositoryFactory = (cwd) => new CodemodRepository(cwd);
685
- const INTERNAL_CODEMODS_DIRECTORY = path$1.join(
686
- __dirname,
687
- "..",
688
- "..",
689
- "resources",
690
- "codemods"
691
- );
692
- const constants$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
693
- __proto__: null,
694
- INTERNAL_CODEMODS_DIRECTORY
695
- }, Symbol.toStringTag, { value: "Module" }));
881
+ const codemodRepositoryFactory = (cwd = INTERNAL_CODEMODS_DIRECTORY) => {
882
+ return new CodemodRepository(cwd);
883
+ };
696
884
  const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
697
885
  __proto__: null,
698
886
  codemodRepositoryFactory,
@@ -727,34 +915,59 @@ class CodemodRunner {
727
915
  this.isDry = enabled;
728
916
  return this;
729
917
  }
730
- async run(codemodsDirectory) {
918
+ createRepository(codemodsDirectory) {
731
919
  const repository = codemodRepositoryFactory(
732
920
  codemodsDirectory ?? INTERNAL_CODEMODS_DIRECTORY
733
921
  );
734
922
  repository.refresh();
735
- const allVersionedCodemods = repository.findByRange(this.range);
736
- const versionedCodemods = this.selectCodemodsCallback ? await this.selectCodemodsCallback(allVersionedCodemods) : allVersionedCodemods;
737
- const hasCodemodsToRun = versionedCodemods.length > 0;
738
- if (!hasCodemodsToRun) {
739
- this.logger?.debug(`Found no codemods to run for ${versionRange(this.range)}`);
740
- return successReport$1();
923
+ return repository;
924
+ }
925
+ async safeRunAndReport(codemods) {
926
+ if (this.isDry) {
927
+ this.logger?.warn?.(
928
+ "Running the codemods in dry mode. No files will be modified during the process."
929
+ );
741
930
  }
742
- this.logger?.debug(
743
- `Found codemods for ${highlight(
744
- versionedCodemods.length
745
- )} version(s) using ${versionRange(this.range)}`
746
- );
747
- versionedCodemods.forEach(
748
- ({ version: version$1, codemods: codemods22 }) => this.logger?.debug(`- ${version(version$1)} (${codemods22.length})`)
749
- );
750
- const codemods2 = versionedCodemods.map(({ codemods: codemods22 }) => codemods22).flat();
751
931
  try {
752
- const reports$1 = await this.project.runCodemods(codemods2, { dry: this.isDry });
753
- this.logger?.raw(reports(reports$1));
932
+ const reports$1 = await this.project.runCodemods(codemods, { dry: this.isDry });
933
+ this.logger?.raw?.(reports(reports$1));
934
+ if (!this.isDry) {
935
+ const nbAffectedTotal = reports$1.flatMap((report) => report.report.ok).reduce((acc, nb) => acc + nb, 0);
936
+ this.logger?.debug?.(
937
+ `Successfully ran ${highlight(codemods.length)} codemod(s), ${highlight(nbAffectedTotal)} change(s) have been detected`
938
+ );
939
+ }
940
+ return successReport$1();
754
941
  } catch (e) {
755
942
  return erroredReport$1(unknownToError(e));
756
943
  }
757
- return successReport$1();
944
+ }
945
+ async runByUID(uid, codemodsDirectory) {
946
+ const repository = this.createRepository(codemodsDirectory);
947
+ if (!repository.has(uid)) {
948
+ throw new Error(`Unknown codemod UID provided: ${uid}`);
949
+ }
950
+ const codemods = repository.find({ uids: [uid] }).flatMap(({ codemods: codemods2 }) => codemods2);
951
+ return this.safeRunAndReport(codemods);
952
+ }
953
+ async run(codemodsDirectory) {
954
+ const repository = this.createRepository(codemodsDirectory);
955
+ const codemodsInRange = repository.find({ range: this.range });
956
+ const selectedCodemods = this.selectCodemodsCallback ? await this.selectCodemodsCallback(codemodsInRange) : codemodsInRange;
957
+ if (selectedCodemods.length === 0) {
958
+ this.logger?.debug?.(`Found no codemods to run for ${versionRange(this.range)}`);
959
+ return successReport$1();
960
+ }
961
+ const codemods = selectedCodemods.flatMap(({ codemods: codemods2 }) => codemods2);
962
+ const codemodsByVersion = groupBy("version", codemods);
963
+ const fRange = versionRange(this.range);
964
+ this.logger?.debug?.(
965
+ `Found ${highlight(codemods.length)} codemods for ${highlight(size(codemodsByVersion))} version(s) using ${fRange}`
966
+ );
967
+ for (const [version$1, codemods2] of Object.entries(codemodsByVersion)) {
968
+ this.logger?.debug?.(`- ${version(semVerFactory(version$1))} (${codemods2.length})`);
969
+ }
970
+ return this.safeRunAndReport(codemods);
758
971
  }
759
972
  }
760
973
  const codemodRunnerFactory = (project, range) => {
@@ -793,7 +1006,7 @@ class Upgrader {
793
1006
  this.codemodsTarget = semVerFactory(
794
1007
  `${this.target.major}.${this.target.minor}.${this.target.patch}`
795
1008
  );
796
- this.logger?.debug(
1009
+ this.logger?.debug?.(
797
1010
  `The codemods target has been synced with the upgrade target. The codemod runner will now look for ${version(
798
1011
  this.codemodsTarget
799
1012
  )}`
@@ -802,7 +1015,7 @@ class Upgrader {
802
1015
  }
803
1016
  overrideCodemodsTarget(target) {
804
1017
  this.codemodsTarget = target;
805
- this.logger?.debug(
1018
+ this.logger?.debug?.(
806
1019
  `Overriding the codemods target. The codemod runner will now look for ${version(target)}`
807
1020
  );
808
1021
  return this;
@@ -822,38 +1035,40 @@ class Upgrader {
822
1035
  addRequirement(requirement) {
823
1036
  this.requirements.push(requirement);
824
1037
  const fRequired = requirement.isRequired ? "(required)" : "(optional)";
825
- this.logger?.debug(
1038
+ this.logger?.debug?.(
826
1039
  `Added a new requirement to the upgrade: ${highlight(requirement.name)} ${fRequired}`
827
1040
  );
828
1041
  return this;
829
1042
  }
830
1043
  async upgrade() {
831
- this.logger?.info(
1044
+ this.logger?.info?.(
832
1045
  `Upgrading from ${version(this.project.strapiVersion)} to ${version(this.target)}`
833
1046
  );
834
1047
  if (this.isDry) {
835
- this.logger?.warn(
1048
+ this.logger?.warn?.(
836
1049
  "Running the upgrade in dry mode. No files will be modified during the process."
837
1050
  );
838
1051
  }
839
1052
  const range = rangeFromVersions(this.project.strapiVersion, this.target);
840
1053
  const codemodsRange = rangeFromVersions(this.project.strapiVersion, this.codemodsTarget);
841
1054
  const npmVersionsMatches = this.npmPackage?.findVersionsInRange(range) ?? [];
842
- this.logger?.debug(
1055
+ this.logger?.debug?.(
843
1056
  `Found ${highlight(npmVersionsMatches.length)} versions satisfying ${versionRange(range)}`
844
1057
  );
845
1058
  try {
846
- this.logger?.info(upgradeStep("Checking requirement", [1, 4]));
1059
+ this.logger?.info?.(upgradeStep("Checking requirement", [1, 4]));
847
1060
  await this.checkRequirements(this.requirements, {
848
1061
  npmVersionsMatches,
849
1062
  project: this.project,
850
1063
  target: this.target
851
1064
  });
852
- this.logger?.info(upgradeStep("Applying the latest code modifications", [2, 4]));
1065
+ this.logger?.info?.(upgradeStep("Applying the latest code modifications", [2, 4]));
853
1066
  await this.runCodemods(codemodsRange);
854
- this.logger?.info(upgradeStep("Upgrading Strapi dependencies", [3, 4]));
1067
+ this.logger?.debug?.("Refreshing project information...");
1068
+ this.project.refresh();
1069
+ this.logger?.info?.(upgradeStep("Upgrading Strapi dependencies", [3, 4]));
855
1070
  await this.updateDependencies();
856
- this.logger?.info(upgradeStep("Installing dependencies", [4, 4]));
1071
+ this.logger?.info?.(upgradeStep("Installing dependencies", [4, 4]));
857
1072
  await this.installDependencies();
858
1073
  } catch (e) {
859
1074
  return erroredReport(unknownToError(e));
@@ -886,7 +1101,7 @@ class Upgrader {
886
1101
  if (requirement.isRequired) {
887
1102
  throw error;
888
1103
  }
889
- this.logger?.warn(warningMessage);
1104
+ this.logger?.warn?.(warningMessage);
890
1105
  const response = await this.confirmationCallback?.(confirmationMessage);
891
1106
  if (!response) {
892
1107
  throw error;
@@ -897,9 +1112,11 @@ class Upgrader {
897
1112
  const json = createJSONTransformAPI(packageJSON);
898
1113
  const dependencies = json.get("dependencies", {});
899
1114
  const strapiDependencies = this.getScopedStrapiDependencies(dependencies);
900
- this.logger?.debug(`Found ${highlight(strapiDependencies.length)} dependency(ies) to update`);
1115
+ this.logger?.debug?.(
1116
+ `Found ${highlight(strapiDependencies.length)} dependency(ies) to update`
1117
+ );
901
1118
  strapiDependencies.forEach(
902
- (dependency) => this.logger?.debug(`- ${dependency[0]} (${dependency[1]} -> ${this.target})`)
1119
+ (dependency) => this.logger?.debug?.(`- ${dependency[0]} (${dependency[1]} -> ${this.target})`)
903
1120
  );
904
1121
  if (strapiDependencies.length === 0) {
905
1122
  return;
@@ -907,7 +1124,7 @@ class Upgrader {
907
1124
  strapiDependencies.forEach(([name]) => json.set(`dependencies.${name}`, this.target.raw));
908
1125
  const updatedPackageJSON = json.root();
909
1126
  if (this.isDry) {
910
- this.logger?.debug(`Skipping dependencies update (${chalk.italic("dry mode")})`);
1127
+ this.logger?.debug?.(`Skipping dependencies update (${chalk.italic("dry mode")})`);
911
1128
  return;
912
1129
  }
913
1130
  await saveJSON(packageJSONPath, updatedPackageJSON);
@@ -927,9 +1144,9 @@ class Upgrader {
927
1144
  async installDependencies() {
928
1145
  const projectPath = this.project.cwd;
929
1146
  const packageManagerName = await packageManager.getPreferred(projectPath);
930
- this.logger?.debug(`Using ${highlight(packageManagerName)} as package manager`);
1147
+ this.logger?.debug?.(`Using ${highlight(packageManagerName)} as package manager`);
931
1148
  if (this.isDry) {
932
- this.logger?.debug(`Skipping dependencies installation (${chalk.italic("dry mode")}`);
1149
+ this.logger?.debug?.(`Skipping dependencies installation (${chalk.italic("dry mode")}`);
933
1150
  return;
934
1151
  }
935
1152
  await packageManager.installDependencies(projectPath, packageManagerName, {
@@ -1028,6 +1245,12 @@ const upgrade = async (options) => {
1028
1245
  const { logger, codemodsTarget } = options;
1029
1246
  const cwd = path$1.resolve(options.cwd ?? process.cwd());
1030
1247
  const project = projectFactory(cwd);
1248
+ logger.debug(projectDetails(project));
1249
+ if (!isApplicationProject(project)) {
1250
+ throw new Error(
1251
+ `The "${options.target}" upgrade can only be run on a Strapi project; for plugins, please use "codemods".`
1252
+ );
1253
+ }
1031
1254
  const npmPackage = npmPackageFactory(STRAPI_PACKAGE_NAME);
1032
1255
  await npmPackage.refresh();
1033
1256
  const upgrader = upgraderFactory(project, options.target, npmPackage).dry(options.dry ?? false).onConfirm(options.confirm ?? null).setLogger(logger);
@@ -1045,20 +1268,7 @@ const upgrade = async (options) => {
1045
1268
  timer.stop();
1046
1269
  logger.info(`Completed in ${durationMs(timer.elapsedMs)}`);
1047
1270
  };
1048
- const codemods = async (options) => {
1049
- const timer = timerFactory();
1050
- const { logger } = options;
1051
- const cwd = path$1.resolve(options.cwd ?? process.cwd());
1052
- const project = projectFactory(cwd);
1053
- const range = getRangeFromTarget(project.strapiVersion, options.target);
1054
- const codemodRunner = codemodRunnerFactory(project, range).dry(options.dry ?? false).onSelectCodemods(options.selectCodemods ?? null).setLogger(logger);
1055
- const executionReport = await codemodRunner.run();
1056
- if (!executionReport.success) {
1057
- throw executionReport.error;
1058
- }
1059
- timer.stop();
1060
- logger.info(`Completed in ${timer.elapsedMs}`);
1061
- };
1271
+ const resolvePath = (cwd) => path$1.resolve(cwd ?? process.cwd());
1062
1272
  const getRangeFromTarget = (currentVersion, target) => {
1063
1273
  if (isSemverInstance(target)) {
1064
1274
  return rangeFactory(target);
@@ -1075,9 +1285,60 @@ const getRangeFromTarget = (currentVersion, target) => {
1075
1285
  throw new Error(`Invalid target set: ${target}`);
1076
1286
  }
1077
1287
  };
1288
+ const findRangeFromTarget = (project, target) => {
1289
+ if (isRangeInstance(target)) {
1290
+ return target;
1291
+ }
1292
+ if (isApplicationProject(project)) {
1293
+ return getRangeFromTarget(project.strapiVersion, target);
1294
+ }
1295
+ return rangeFactory("*");
1296
+ };
1297
+ const runCodemods = async (options) => {
1298
+ const timer = timerFactory();
1299
+ const { logger, uid } = options;
1300
+ const cwd = resolvePath(options.cwd);
1301
+ const project = projectFactory(cwd);
1302
+ const range = findRangeFromTarget(project, options.target);
1303
+ logger.debug(projectDetails(project));
1304
+ logger.debug(`Range: set to ${versionRange(range)}`);
1305
+ const codemodRunner = codemodRunnerFactory(project, range).dry(options.dry ?? false).onSelectCodemods(options.selectCodemods ?? null).setLogger(logger);
1306
+ let report;
1307
+ if (uid !== void 0) {
1308
+ logger.debug(`Running a single codemod: ${codemodUID(uid)}`);
1309
+ report = await codemodRunner.runByUID(uid);
1310
+ } else {
1311
+ report = await codemodRunner.run();
1312
+ }
1313
+ if (!report.success) {
1314
+ throw report.error;
1315
+ }
1316
+ timer.stop();
1317
+ logger.info(`Completed in ${timer.elapsedMs}`);
1318
+ };
1319
+ const listCodemods = async (options) => {
1320
+ const { logger, target } = options;
1321
+ const cwd = resolvePath(options.cwd);
1322
+ const project = projectFactory(cwd);
1323
+ const range = findRangeFromTarget(project, target);
1324
+ logger.debug(projectDetails(project));
1325
+ logger.debug(`Range: set to ${versionRange(range)}`);
1326
+ const repo = codemodRepositoryFactory();
1327
+ repo.refresh();
1328
+ const groups = repo.find({ range });
1329
+ const codemods = groups.flatMap((collection) => collection.codemods);
1330
+ logger.debug(`Found ${highlight(codemods.length)} codemods`);
1331
+ if (codemods.length === 0) {
1332
+ logger.info(`Found no codemods matching ${versionRange(range)}`);
1333
+ return;
1334
+ }
1335
+ const fCodemods = codemodList(codemods);
1336
+ logger.raw(fCodemods);
1337
+ };
1078
1338
  const index$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1079
1339
  __proto__: null,
1080
- codemods,
1340
+ listCodemods,
1341
+ runCodemods,
1081
1342
  upgrade
1082
1343
  }, Symbol.toStringTag, { value: "Module" }));
1083
1344
  class Logger {