@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.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,17 +377,31 @@ 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_ALLOWED_EXTENSIONS = ["js", "ts", "json"];
370
- const PROJECT_DEFAULT_PATTERNS = ["package.json"];
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 = [
384
+ // Source files
385
+ "js",
386
+ "mjs",
387
+ "ts",
388
+ // React files
389
+ "jsx",
390
+ "tsx"
391
+ ];
392
+ const PROJECT_JSON_EXTENSIONS = ["json"];
393
+ const PROJECT_ALLOWED_EXTENSIONS = [...PROJECT_CODE_EXTENSIONS, ...PROJECT_JSON_EXTENSIONS];
371
394
  const SCOPED_STRAPI_PACKAGE_PREFIX = "@strapi/";
372
395
  const STRAPI_DEPENDENCY_NAME = `${SCOPED_STRAPI_PACKAGE_PREFIX}strapi`;
373
396
  const constants$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
374
397
  __proto__: null,
375
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS,
376
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS,
377
- PROJECT_DEFAULT_PATTERNS,
398
+ PROJECT_ALLOWED_EXTENSIONS,
399
+ PROJECT_APP_ALLOWED_ROOT_PATHS,
400
+ PROJECT_CODE_EXTENSIONS,
401
+ PROJECT_JSON_EXTENSIONS,
378
402
  PROJECT_PACKAGE_JSON,
403
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS,
404
+ PROJECT_PLUGIN_ROOT_FILES,
379
405
  SCOPED_STRAPI_PACKAGE_PREFIX,
380
406
  STRAPI_DEPENDENCY_NAME
381
407
  }, Symbol.toStringTag, { value: "Module" }));
@@ -385,12 +411,13 @@ class Project {
385
411
  files;
386
412
  packageJSONPath;
387
413
  packageJSON;
388
- strapiVersion;
389
- constructor(cwd) {
414
+ paths;
415
+ constructor(cwd, config) {
390
416
  if (!fse.pathExistsSync(cwd)) {
391
417
  throw new Error(`ENOENT: no such file or directory, access '${cwd}'`);
392
418
  }
393
419
  this.cwd = cwd;
420
+ this.paths = config.paths;
394
421
  this.refresh();
395
422
  }
396
423
  getFilesByExtensions(extensions) {
@@ -401,14 +428,13 @@ class Project {
401
428
  }
402
429
  refresh() {
403
430
  this.refreshPackageJSON();
404
- this.refreshStrapiVersion();
405
431
  this.refreshProjectFiles();
406
432
  return this;
407
433
  }
408
- async runCodemods(codemods2, options) {
434
+ async runCodemods(codemods, options) {
409
435
  const runners = this.createProjectCodemodsRunners(options.dry);
410
436
  const reports2 = [];
411
- for (const codemod of codemods2) {
437
+ for (const codemod of codemods) {
412
438
  for (const runner of runners) {
413
439
  if (runner.valid(codemod)) {
414
440
  const report = await runner.run(codemod);
@@ -419,17 +445,20 @@ class Project {
419
445
  return reports2;
420
446
  }
421
447
  createProjectCodemodsRunners(dry = false) {
422
- const jsonFiles = this.getFilesByExtensions([".json"]);
423
- const codeFiles = this.getFilesByExtensions([".js", ".ts", ".mjs"]);
448
+ const jsonExtensions = PROJECT_JSON_EXTENSIONS.map((ext) => `.${ext}`);
449
+ const codeExtensions = PROJECT_CODE_EXTENSIONS.map((ext) => `.${ext}`);
450
+ const jsonFiles = this.getFilesByExtensions(jsonExtensions);
451
+ const codeFiles = this.getFilesByExtensions(codeExtensions);
424
452
  const codeRunner = codeRunnerFactory(codeFiles, {
425
453
  dry,
426
- print: false,
427
- silent: true,
428
- extensions: "js,ts",
454
+ parser: "ts",
429
455
  runInBand: true,
430
- verbose: 0,
431
456
  babel: true,
432
- parser: "ts"
457
+ extensions: PROJECT_CODE_EXTENSIONS.join(","),
458
+ // Don't output any log coming from the runner
459
+ print: false,
460
+ silent: true,
461
+ verbose: 0
433
462
  });
434
463
  const jsonRunner = jsonRunnerFactory(jsonFiles, { dry, cwd: this.cwd });
435
464
  return [codeRunner, jsonRunner];
@@ -446,16 +475,38 @@ class Project {
446
475
  this.packageJSON = JSON.parse(packageJSONBuffer.toString());
447
476
  }
448
477
  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
478
  const scanner = fileScannerFactory(this.cwd);
458
- this.files = scanner.scan(patterns);
479
+ this.files = scanner.scan(this.paths);
480
+ }
481
+ }
482
+ class AppProject extends Project {
483
+ strapiVersion;
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
+ }
502
+ constructor(cwd) {
503
+ super(cwd, { paths: AppProject.paths });
504
+ this.refreshStrapiVersion();
505
+ }
506
+ refresh() {
507
+ super.refresh();
508
+ this.refreshStrapiVersion();
509
+ return this;
459
510
  }
460
511
  refreshStrapiVersion() {
461
512
  this.strapiVersion = // First try to get the strapi version from the package.json dependencies
@@ -502,10 +553,71 @@ const formatGlobCollectionPattern = (collection) => {
502
553
  );
503
554
  return collection.length === 1 ? collection[0] : `{${collection}}`;
504
555
  };
505
- const projectFactory = (cwd) => new Project(cwd);
556
+ class PluginProject extends Project {
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
+ }
582
+ }
583
+ const isPlugin = (cwd) => {
584
+ const packageJSONPath = path$1.join(cwd, PROJECT_PACKAGE_JSON);
585
+ try {
586
+ fse.accessSync(packageJSONPath);
587
+ } catch {
588
+ throw new Error(`Could not find a ${PROJECT_PACKAGE_JSON} file in ${cwd}`);
589
+ }
590
+ const packageJSONBuffer = fse.readFileSync(packageJSONPath);
591
+ const packageJSON = JSON.parse(packageJSONBuffer.toString());
592
+ return packageJSON?.strapi?.kind === "plugin";
593
+ };
594
+ const projectFactory = (cwd) => {
595
+ fse.accessSync(cwd);
596
+ return isPlugin(cwd) ? new PluginProject(cwd) : new AppProject(cwd);
597
+ };
598
+ const isPluginProject = (project) => {
599
+ return project instanceof PluginProject;
600
+ };
601
+ function assertPluginProject(project) {
602
+ if (!isPluginProject(project)) {
603
+ throw new Error("Project is not a plugin");
604
+ }
605
+ }
606
+ const isApplicationProject = (project) => {
607
+ return project instanceof AppProject;
608
+ };
609
+ function assertAppProject(project) {
610
+ if (!isApplicationProject(project)) {
611
+ throw new Error("Project is not an application");
612
+ }
613
+ }
506
614
  const index$a = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
507
615
  __proto__: null,
616
+ assertAppProject,
617
+ assertPluginProject,
508
618
  constants: constants$3,
619
+ isApplicationProject,
620
+ isPluginProject,
509
621
  projectFactory
510
622
  }, Symbol.toStringTag, { value: "Module" }));
511
623
  class UnexpectedError extends Error {
@@ -531,7 +643,14 @@ const path = (path2) => chalk.blue(path2);
531
643
  const version = (version2) => {
532
644
  return chalk.italic.yellow(`v${version2}`);
533
645
  };
534
- 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);
535
654
  const transform = (transformFilePath) => chalk.cyan(transformFilePath);
536
655
  const highlight = (arg) => chalk.bold.underline(arg);
537
656
  const upgradeStep = (text, step) => {
@@ -563,15 +682,41 @@ const reports = (reports2) => {
563
682
  table.push(...rows);
564
683
  return table.toString();
565
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
+ };
566
707
  const durationMs = (elapsedMs) => {
567
708
  const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
568
709
  return `${elapsedSeconds}s`;
569
710
  };
570
711
  const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
571
712
  __proto__: null,
713
+ codemodList,
714
+ codemodUID,
572
715
  durationMs,
573
716
  highlight,
574
717
  path,
718
+ projectDetails,
719
+ projectType,
575
720
  reports,
576
721
  transform,
577
722
  upgradeStep,
@@ -594,6 +739,7 @@ const constants$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP
594
739
  CODEMOD_JSON_SUFFIX
595
740
  }, Symbol.toStringTag, { value: "Module" }));
596
741
  class Codemod {
742
+ uid;
597
743
  kind;
598
744
  version;
599
745
  baseDirectory;
@@ -605,9 +751,27 @@ class Codemod {
605
751
  this.baseDirectory = options.baseDirectory;
606
752
  this.filename = options.filename;
607
753
  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("-", " ");
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;
611
775
  }
612
776
  }
613
777
  const codemodFactory = (options) => new Codemod(options);
@@ -616,6 +780,20 @@ const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
616
780
  codemodFactory,
617
781
  constants: constants$2
618
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" }));
619
797
  class CodemodRepository {
620
798
  groups;
621
799
  versions;
@@ -634,23 +812,48 @@ class CodemodRepository {
634
812
  count(version2) {
635
813
  return this.findByVersion(version2).length;
636
814
  }
637
- countRange(range) {
638
- return this.findByRange(range).length;
639
- }
640
- exists(version2) {
815
+ versionExists(version2) {
641
816
  return version2.raw in this.groups;
642
817
  }
643
- 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) {
644
827
  const entries = Object.entries(this.groups);
645
- return entries.filter(([version2]) => range.test(version2)).map(([version2, codemods2]) => ({
828
+ return entries.filter(maybeFilterByRange).map(([version2, codemods]) => ({
646
829
  version: semVerFactory(version2),
647
- codemods: codemods2
648
- }));
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
+ }
649
845
  }
650
846
  findByVersion(version2) {
651
847
  const literalVersion = version2.raw;
652
- const codemods2 = this.groups[literalVersion];
653
- return codemods2 ?? [];
848
+ const codemods = this.groups[literalVersion];
849
+ return codemods ?? [];
850
+ }
851
+ findAll() {
852
+ const entries = Object.entries(this.groups);
853
+ return entries.map(([version2, codemods]) => ({
854
+ version: semVerFactory(version2),
855
+ codemods
856
+ }));
654
857
  }
655
858
  refreshAvailableVersions() {
656
859
  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 +884,9 @@ const parseCodemodKindFromFilename = (filename) => {
681
884
  assert(CODEMOD_ALLOWED_SUFFIXES.includes(kind));
682
885
  return kind;
683
886
  };
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" }));
887
+ const codemodRepositoryFactory = (cwd = INTERNAL_CODEMODS_DIRECTORY) => {
888
+ return new CodemodRepository(cwd);
889
+ };
696
890
  const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
697
891
  __proto__: null,
698
892
  codemodRepositoryFactory,
@@ -727,34 +921,59 @@ class CodemodRunner {
727
921
  this.isDry = enabled;
728
922
  return this;
729
923
  }
730
- async run(codemodsDirectory) {
924
+ createRepository(codemodsDirectory) {
731
925
  const repository = codemodRepositoryFactory(
732
926
  codemodsDirectory ?? INTERNAL_CODEMODS_DIRECTORY
733
927
  );
734
928
  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();
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."
935
+ );
741
936
  }
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
937
  try {
752
- const reports$1 = await this.project.runCodemods(codemods2, { dry: this.isDry });
753
- 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();
754
947
  } catch (e) {
755
948
  return erroredReport$1(unknownToError(e));
756
949
  }
757
- 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);
758
977
  }
759
978
  }
760
979
  const codemodRunnerFactory = (project, range) => {
@@ -793,7 +1012,7 @@ class Upgrader {
793
1012
  this.codemodsTarget = semVerFactory(
794
1013
  `${this.target.major}.${this.target.minor}.${this.target.patch}`
795
1014
  );
796
- this.logger?.debug(
1015
+ this.logger?.debug?.(
797
1016
  `The codemods target has been synced with the upgrade target. The codemod runner will now look for ${version(
798
1017
  this.codemodsTarget
799
1018
  )}`
@@ -802,7 +1021,7 @@ class Upgrader {
802
1021
  }
803
1022
  overrideCodemodsTarget(target) {
804
1023
  this.codemodsTarget = target;
805
- this.logger?.debug(
1024
+ this.logger?.debug?.(
806
1025
  `Overriding the codemods target. The codemod runner will now look for ${version(target)}`
807
1026
  );
808
1027
  return this;
@@ -822,38 +1041,40 @@ class Upgrader {
822
1041
  addRequirement(requirement) {
823
1042
  this.requirements.push(requirement);
824
1043
  const fRequired = requirement.isRequired ? "(required)" : "(optional)";
825
- this.logger?.debug(
1044
+ this.logger?.debug?.(
826
1045
  `Added a new requirement to the upgrade: ${highlight(requirement.name)} ${fRequired}`
827
1046
  );
828
1047
  return this;
829
1048
  }
830
1049
  async upgrade() {
831
- this.logger?.info(
1050
+ this.logger?.info?.(
832
1051
  `Upgrading from ${version(this.project.strapiVersion)} to ${version(this.target)}`
833
1052
  );
834
1053
  if (this.isDry) {
835
- this.logger?.warn(
1054
+ this.logger?.warn?.(
836
1055
  "Running the upgrade in dry mode. No files will be modified during the process."
837
1056
  );
838
1057
  }
839
1058
  const range = rangeFromVersions(this.project.strapiVersion, this.target);
840
1059
  const codemodsRange = rangeFromVersions(this.project.strapiVersion, this.codemodsTarget);
841
1060
  const npmVersionsMatches = this.npmPackage?.findVersionsInRange(range) ?? [];
842
- this.logger?.debug(
1061
+ this.logger?.debug?.(
843
1062
  `Found ${highlight(npmVersionsMatches.length)} versions satisfying ${versionRange(range)}`
844
1063
  );
845
1064
  try {
846
- this.logger?.info(upgradeStep("Checking requirement", [1, 4]));
1065
+ this.logger?.info?.(upgradeStep("Checking requirement", [1, 4]));
847
1066
  await this.checkRequirements(this.requirements, {
848
1067
  npmVersionsMatches,
849
1068
  project: this.project,
850
1069
  target: this.target
851
1070
  });
852
- this.logger?.info(upgradeStep("Applying the latest code modifications", [2, 4]));
1071
+ this.logger?.info?.(upgradeStep("Applying the latest code modifications", [2, 4]));
853
1072
  await this.runCodemods(codemodsRange);
854
- this.logger?.info(upgradeStep("Upgrading Strapi dependencies", [3, 4]));
1073
+ this.logger?.debug?.("Refreshing project information...");
1074
+ this.project.refresh();
1075
+ this.logger?.info?.(upgradeStep("Upgrading Strapi dependencies", [3, 4]));
855
1076
  await this.updateDependencies();
856
- this.logger?.info(upgradeStep("Installing dependencies", [4, 4]));
1077
+ this.logger?.info?.(upgradeStep("Installing dependencies", [4, 4]));
857
1078
  await this.installDependencies();
858
1079
  } catch (e) {
859
1080
  return erroredReport(unknownToError(e));
@@ -886,7 +1107,7 @@ class Upgrader {
886
1107
  if (requirement.isRequired) {
887
1108
  throw error;
888
1109
  }
889
- this.logger?.warn(warningMessage);
1110
+ this.logger?.warn?.(warningMessage);
890
1111
  const response = await this.confirmationCallback?.(confirmationMessage);
891
1112
  if (!response) {
892
1113
  throw error;
@@ -897,9 +1118,11 @@ class Upgrader {
897
1118
  const json = createJSONTransformAPI(packageJSON);
898
1119
  const dependencies = json.get("dependencies", {});
899
1120
  const strapiDependencies = this.getScopedStrapiDependencies(dependencies);
900
- 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
+ );
901
1124
  strapiDependencies.forEach(
902
- (dependency) => this.logger?.debug(`- ${dependency[0]} (${dependency[1]} -> ${this.target})`)
1125
+ (dependency) => this.logger?.debug?.(`- ${dependency[0]} (${dependency[1]} -> ${this.target})`)
903
1126
  );
904
1127
  if (strapiDependencies.length === 0) {
905
1128
  return;
@@ -907,7 +1130,7 @@ class Upgrader {
907
1130
  strapiDependencies.forEach(([name]) => json.set(`dependencies.${name}`, this.target.raw));
908
1131
  const updatedPackageJSON = json.root();
909
1132
  if (this.isDry) {
910
- this.logger?.debug(`Skipping dependencies update (${chalk.italic("dry mode")})`);
1133
+ this.logger?.debug?.(`Skipping dependencies update (${chalk.italic("dry mode")})`);
911
1134
  return;
912
1135
  }
913
1136
  await saveJSON(packageJSONPath, updatedPackageJSON);
@@ -927,9 +1150,9 @@ class Upgrader {
927
1150
  async installDependencies() {
928
1151
  const projectPath = this.project.cwd;
929
1152
  const packageManagerName = await packageManager.getPreferred(projectPath);
930
- this.logger?.debug(`Using ${highlight(packageManagerName)} as package manager`);
1153
+ this.logger?.debug?.(`Using ${highlight(packageManagerName)} as package manager`);
931
1154
  if (this.isDry) {
932
- this.logger?.debug(`Skipping dependencies installation (${chalk.italic("dry mode")}`);
1155
+ this.logger?.debug?.(`Skipping dependencies installation (${chalk.italic("dry mode")}`);
933
1156
  return;
934
1157
  }
935
1158
  await packageManager.installDependencies(projectPath, packageManagerName, {
@@ -1028,6 +1251,12 @@ const upgrade = async (options) => {
1028
1251
  const { logger, codemodsTarget } = options;
1029
1252
  const cwd = path$1.resolve(options.cwd ?? process.cwd());
1030
1253
  const project = projectFactory(cwd);
1254
+ logger.debug(projectDetails(project));
1255
+ if (!isApplicationProject(project)) {
1256
+ throw new Error(
1257
+ `The "${options.target}" upgrade can only be run on a Strapi project; for plugins, please use "codemods".`
1258
+ );
1259
+ }
1031
1260
  const npmPackage = npmPackageFactory(STRAPI_PACKAGE_NAME);
1032
1261
  await npmPackage.refresh();
1033
1262
  const upgrader = upgraderFactory(project, options.target, npmPackage).dry(options.dry ?? false).onConfirm(options.confirm ?? null).setLogger(logger);
@@ -1045,20 +1274,7 @@ const upgrade = async (options) => {
1045
1274
  timer.stop();
1046
1275
  logger.info(`Completed in ${durationMs(timer.elapsedMs)}`);
1047
1276
  };
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
- };
1277
+ const resolvePath = (cwd) => path$1.resolve(cwd ?? process.cwd());
1062
1278
  const getRangeFromTarget = (currentVersion, target) => {
1063
1279
  if (isSemverInstance(target)) {
1064
1280
  return rangeFactory(target);
@@ -1075,9 +1291,60 @@ const getRangeFromTarget = (currentVersion, target) => {
1075
1291
  throw new Error(`Invalid target set: ${target}`);
1076
1292
  }
1077
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
+ };
1078
1344
  const index$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1079
1345
  __proto__: null,
1080
- codemods,
1346
+ listCodemods,
1347
+ runCodemods,
1081
1348
  upgrade
1082
1349
  }, Symbol.toStringTag, { value: "Module" }));
1083
1350
  class Logger {