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

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 (96) hide show
  1. package/LICENSE +19 -4
  2. package/README.md +1 -1
  3. package/dist/cli.js +1578 -5
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.js +739 -387
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +735 -384
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/modules/codemod/codemod.d.ts +4 -2
  10. package/dist/modules/codemod/codemod.d.ts.map +1 -1
  11. package/dist/modules/codemod/types.d.ts +8 -1
  12. package/dist/modules/codemod/types.d.ts.map +1 -1
  13. package/dist/modules/codemod-repository/constants.d.ts.map +1 -1
  14. package/dist/modules/codemod-repository/repository.d.ts +6 -5
  15. package/dist/modules/codemod-repository/repository.d.ts.map +1 -1
  16. package/dist/modules/codemod-repository/types.d.ts +7 -3
  17. package/dist/modules/codemod-repository/types.d.ts.map +1 -1
  18. package/dist/modules/codemod-runner/codemod-runner.d.ts +3 -0
  19. package/dist/modules/codemod-runner/codemod-runner.d.ts.map +1 -1
  20. package/dist/modules/codemod-runner/index.d.ts +1 -0
  21. package/dist/modules/codemod-runner/index.d.ts.map +1 -1
  22. package/dist/modules/codemod-runner/types.d.ts +1 -0
  23. package/dist/modules/codemod-runner/types.d.ts.map +1 -1
  24. package/dist/modules/error/utils.d.ts +8 -0
  25. package/dist/modules/error/utils.d.ts.map +1 -1
  26. package/dist/modules/file-scanner/scanner.d.ts.map +1 -1
  27. package/dist/modules/format/formats.d.ts +6 -0
  28. package/dist/modules/format/formats.d.ts.map +1 -1
  29. package/dist/modules/project/constants.d.ts +6 -3
  30. package/dist/modules/project/constants.d.ts.map +1 -1
  31. package/dist/modules/project/index.d.ts +2 -0
  32. package/dist/modules/project/index.d.ts.map +1 -1
  33. package/dist/modules/project/project.d.ts +27 -5
  34. package/dist/modules/project/project.d.ts.map +1 -1
  35. package/dist/modules/project/types.d.ts +3 -10
  36. package/dist/modules/project/types.d.ts.map +1 -1
  37. package/dist/modules/project/utils.d.ts +6 -0
  38. package/dist/modules/project/utils.d.ts.map +1 -0
  39. package/dist/modules/report/report.d.ts.map +1 -1
  40. package/dist/modules/requirement/types.d.ts +2 -2
  41. package/dist/modules/requirement/types.d.ts.map +1 -1
  42. package/dist/modules/runner/json/transform.d.ts.map +1 -1
  43. package/dist/modules/upgrader/types.d.ts +6 -0
  44. package/dist/modules/upgrader/types.d.ts.map +1 -1
  45. package/dist/modules/upgrader/upgrader.d.ts +7 -3
  46. package/dist/modules/upgrader/upgrader.d.ts.map +1 -1
  47. package/dist/modules/version/range.d.ts +2 -0
  48. package/dist/modules/version/range.d.ts.map +1 -1
  49. package/dist/modules/version/types.d.ts +2 -1
  50. package/dist/modules/version/types.d.ts.map +1 -1
  51. package/dist/tasks/codemods/index.d.ts +2 -1
  52. package/dist/tasks/codemods/index.d.ts.map +1 -1
  53. package/dist/tasks/codemods/list-codemods.d.ts +3 -0
  54. package/dist/tasks/codemods/list-codemods.d.ts.map +1 -0
  55. package/dist/tasks/codemods/run-codemods.d.ts +3 -0
  56. package/dist/tasks/codemods/run-codemods.d.ts.map +1 -0
  57. package/dist/tasks/codemods/types.d.ts +9 -3
  58. package/dist/tasks/codemods/types.d.ts.map +1 -1
  59. package/dist/tasks/codemods/utils.d.ts +6 -0
  60. package/dist/tasks/codemods/utils.d.ts.map +1 -0
  61. package/dist/tasks/index.d.ts +1 -1
  62. package/dist/tasks/index.d.ts.map +1 -1
  63. package/dist/tasks/upgrade/prompts/index.d.ts +2 -0
  64. package/dist/tasks/upgrade/prompts/index.d.ts.map +1 -0
  65. package/dist/tasks/upgrade/prompts/latest.d.ts +9 -0
  66. package/dist/tasks/upgrade/prompts/latest.d.ts.map +1 -0
  67. package/dist/tasks/upgrade/requirements/major.d.ts.map +1 -1
  68. package/dist/tasks/upgrade/upgrade.d.ts.map +1 -1
  69. package/package.json +11 -10
  70. package/resources/codemods/5.0.0/comment-out-lifecycle-files.code.ts +63 -0
  71. package/resources/codemods/5.0.0/dependency-remove-strapi-plugin-i18n.json.ts +31 -0
  72. package/resources/codemods/5.0.0/dependency-upgrade-react-and-react-dom.json.ts +67 -0
  73. package/resources/codemods/5.0.0/dependency-upgrade-react-router-dom.json.ts +59 -0
  74. package/resources/codemods/5.0.0/dependency-upgrade-styled-components.json.ts +49 -0
  75. package/resources/codemods/5.0.0/deprecate-helper-plugin.code.ts +192 -0
  76. package/resources/codemods/5.0.0/entity-service-document-service.code.ts +437 -0
  77. package/resources/codemods/5.0.0/s3-keys-wrapped-in-credentials.code.ts +1 -1
  78. package/resources/codemods/5.0.0/sqlite3-to-better-sqlite3.json.ts +5 -3
  79. package/resources/codemods/5.0.0/strapi-public-interface.code.ts +126 -0
  80. package/resources/codemods/5.0.0/use-uid-for-config-namespace.code.ts +1 -1
  81. package/resources/codemods/5.0.0/utils-public-interface.code.ts +320 -0
  82. package/resources/codemods/5.1.0/dependency-better-sqlite3.json.ts +48 -0
  83. package/resources/examples/console.log-to-console.info.code.ts +1 -1
  84. package/resources/examples/disable-jsx-buttons.code.ts +42 -0
  85. package/resources/utils/change-import.ts +118 -0
  86. package/resources/utils/replace-jsx.ts +49 -0
  87. package/dist/_chunks/codemod-runner-B5OeSMTQ.js +0 -730
  88. package/dist/_chunks/codemod-runner-B5OeSMTQ.js.map +0 -1
  89. package/dist/_chunks/codemods-10ZKewQx.js +0 -108
  90. package/dist/_chunks/codemods-10ZKewQx.js.map +0 -1
  91. package/dist/_chunks/index-uxCwtuH1.js +0 -103
  92. package/dist/_chunks/index-uxCwtuH1.js.map +0 -1
  93. package/dist/_chunks/upgrade-A4T1OWs5.js +0 -357
  94. package/dist/_chunks/upgrade-A4T1OWs5.js.map +0 -1
  95. package/dist/tasks/codemods/codemods.d.ts +0 -3
  96. package/dist/tasks/codemods/codemods.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,128 +1,15 @@
1
1
  import path$1 from "node:path";
2
- import simpleGit from "simple-git";
2
+ import CliTable3 from "cli-table3";
3
3
  import chalk from "chalk";
4
+ import assert from "node:assert";
4
5
  import semver from "semver";
5
- import { packageManager } from "@strapi/utils";
6
- import { cloneDeep, get, has, merge, set, omit, isEqual } from "lodash/fp";
7
6
  import fse from "fs-extra";
8
- import assert from "node:assert";
9
- import { glob } from "glob";
7
+ import fastglob from "fast-glob";
10
8
  import { run } from "jscodeshift/src/Runner";
9
+ import { cloneDeep, get, has, merge, set, omit, isEqual, groupBy, size } from "lodash/fp";
11
10
  import { register } from "esbuild-register/dist/node";
12
- import CliTable3 from "cli-table3";
13
- class Requirement {
14
- isRequired;
15
- name;
16
- testCallback;
17
- children;
18
- constructor(name, testCallback, isRequired) {
19
- this.name = name;
20
- this.testCallback = testCallback;
21
- this.isRequired = isRequired ?? true;
22
- this.children = [];
23
- }
24
- setChildren(children) {
25
- this.children = children;
26
- return this;
27
- }
28
- addChild(child) {
29
- this.children.push(child);
30
- return this;
31
- }
32
- asOptional() {
33
- const newInstance = requirementFactory(this.name, this.testCallback, false);
34
- newInstance.setChildren(this.children);
35
- return newInstance;
36
- }
37
- asRequired() {
38
- const newInstance = requirementFactory(this.name, this.testCallback, true);
39
- newInstance.setChildren(this.children);
40
- return newInstance;
41
- }
42
- async test(context) {
43
- try {
44
- await this.testCallback?.(context);
45
- return ok();
46
- } catch (e) {
47
- if (e instanceof Error) {
48
- return errored(e);
49
- }
50
- if (typeof e === "string") {
51
- return errored(new Error(e));
52
- }
53
- return errored(new Error("Unknown error"));
54
- }
55
- }
56
- }
57
- const ok = () => ({ pass: true, error: null });
58
- const errored = (error) => ({ pass: false, error });
59
- const requirementFactory = (name, testCallback, isRequired) => new Requirement(name, testCallback, isRequired);
60
- const index$g = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
61
- __proto__: null,
62
- requirementFactory
63
- }, Symbol.toStringTag, { value: "Module" }));
64
- const REQUIRE_AVAILABLE_NEXT_MAJOR = requirementFactory(
65
- "REQUIRE_AVAILABLE_NEXT_MAJOR",
66
- (context) => {
67
- const { project, target } = context;
68
- const currentMajor = project.strapiVersion.major;
69
- const targetedMajor = target.major;
70
- if (targetedMajor === currentMajor) {
71
- throw new Error(`You're already on the latest major version (v${currentMajor})`);
72
- }
73
- }
74
- );
75
- const REQUIRE_LATEST_FOR_CURRENT_MAJOR = requirementFactory(
76
- "REQUIRE_LATEST_FOR_CURRENT_MAJOR",
77
- (context) => {
78
- const { project, target, npmVersionsMatches } = context;
79
- if (npmVersionsMatches.length !== 1) {
80
- const invalidVersions = npmVersionsMatches.slice(0, -1);
81
- const invalidVersionsAsSemVer = invalidVersions.map((v) => v.version);
82
- const nbInvalidVersions = npmVersionsMatches.length;
83
- const currentMajor = project.strapiVersion.major;
84
- throw new Error(
85
- `Doing a major upgrade requires to be on the latest v${currentMajor} version, but found ${nbInvalidVersions} versions between the current one and ${target}: ${invalidVersionsAsSemVer}`
86
- );
87
- }
88
- }
89
- );
90
- const REQUIRE_GIT_CLEAN_REPOSITORY = requirementFactory(
91
- "REQUIRE_GIT_CLEAN_REPOSITORY",
92
- async (context) => {
93
- const git = simpleGit({ baseDir: context.project.cwd });
94
- const status = await git.status();
95
- if (!status.isClean()) {
96
- throw new Error(
97
- "Repository is not clean. Please commit or stash any changes before upgrading"
98
- );
99
- }
100
- }
101
- );
102
- const REQUIRE_GIT_REPOSITORY = requirementFactory(
103
- "REQUIRE_GIT_REPOSITORY",
104
- async (context) => {
105
- const git = simpleGit({ baseDir: context.project.cwd });
106
- const isRepo = await git.checkIsRepo();
107
- if (!isRepo) {
108
- throw new Error("Not a git repository (or any of the parent directories)");
109
- }
110
- }
111
- ).addChild(REQUIRE_GIT_CLEAN_REPOSITORY.asOptional());
112
- const REQUIRE_GIT_INSTALLED = requirementFactory(
113
- "REQUIRE_GIT_INSTALLED",
114
- async (context) => {
115
- const git = simpleGit({ baseDir: context.project.cwd });
116
- try {
117
- await git.version();
118
- } catch {
119
- throw new Error("Git is not installed");
120
- }
121
- }
122
- ).addChild(REQUIRE_GIT_REPOSITORY.asOptional());
123
- const REQUIRE_GIT = requirementFactory("REQUIRE_GIT", null).addChild(
124
- REQUIRE_GIT_INSTALLED.asOptional()
125
- );
11
+ import { packageManager } from "@strapi/utils";
12
+ import simpleGit from "simple-git";
126
13
  class Timer {
127
14
  interval;
128
15
  constructor() {
@@ -153,55 +40,101 @@ const constants$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP
153
40
  __proto__: null,
154
41
  ONE_SECOND_MS
155
42
  }, Symbol.toStringTag, { value: "Module" }));
156
- const index$f = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
43
+ const index$g = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
157
44
  __proto__: null,
158
45
  constants: constants$4,
159
46
  timerFactory
160
47
  }, Symbol.toStringTag, { value: "Module" }));
161
- class JSONTransformAPI {
162
- json;
163
- constructor(json) {
164
- this.json = cloneDeep(json);
165
- }
166
- get(path2, defaultValue) {
167
- if (!path2) {
168
- return this.root();
169
- }
170
- return cloneDeep(get(path2, this.json) ?? defaultValue);
171
- }
172
- has(path2) {
173
- return has(path2, this.json);
174
- }
175
- merge(other) {
176
- this.json = merge(other, this.json);
177
- return this;
178
- }
179
- root() {
180
- return cloneDeep(this.json);
181
- }
182
- set(path2, value) {
183
- this.json = set(path2, value, this.json);
184
- return this;
185
- }
186
- remove(path2) {
187
- this.json = omit(path2, this.json);
188
- return this;
189
- }
190
- }
191
- const createJSONTransformAPI = (object) => new JSONTransformAPI(object);
192
- const readJSON = async (path2) => {
193
- const buffer = await fse.readFile(path2);
194
- return JSON.parse(buffer.toString());
48
+ const path = (path2) => chalk.blue(path2);
49
+ const version = (version2) => {
50
+ return chalk.italic.yellow(`v${version2}`);
195
51
  };
196
- const saveJSON = async (path2, json) => {
197
- const jsonAsString = `${JSON.stringify(json, null, 2)}
198
- `;
199
- await fse.writeFile(path2, jsonAsString);
52
+ const codemodUID = (uid) => {
53
+ return chalk.bold.cyan(uid);
54
+ };
55
+ const projectDetails = (project) => {
56
+ return `Project: TYPE=${projectType(project.type)}; CWD=${path(project.cwd)}; PATHS=${project.paths.map(path)}`;
57
+ };
58
+ const projectType = (type) => chalk.cyan(type);
59
+ const versionRange = (range) => chalk.italic.yellow(range.raw);
60
+ const transform = (transformFilePath) => chalk.cyan(transformFilePath);
61
+ const highlight = (arg) => chalk.bold.underline(arg);
62
+ const upgradeStep = (text, step) => {
63
+ return chalk.bold(`(${step[0]}/${step[1]}) ${text}...`);
64
+ };
65
+ const reports = (reports2) => {
66
+ const rows = reports2.map(({ codemod, report }, i) => {
67
+ const fIndex = chalk.grey(i);
68
+ const fVersion = chalk.magenta(codemod.version);
69
+ const fKind = chalk.yellow(codemod.kind);
70
+ const fFormattedTransformPath = chalk.cyan(codemod.format());
71
+ const fTimeElapsed = i === 0 ? `${report.timeElapsed}s ${chalk.dim.italic("(cold start)")}` : `${report.timeElapsed}s`;
72
+ const fAffected = report.ok > 0 ? chalk.green(report.ok) : chalk.grey(0);
73
+ const fUnchanged = report.ok === 0 ? chalk.red(report.nochange) : chalk.grey(report.nochange);
74
+ return [fIndex, fVersion, fKind, fFormattedTransformPath, fAffected, fUnchanged, fTimeElapsed];
75
+ });
76
+ const table = new CliTable3({
77
+ style: { compact: true },
78
+ head: [
79
+ chalk.bold.grey("N°"),
80
+ chalk.bold.magenta("Version"),
81
+ chalk.bold.yellow("Kind"),
82
+ chalk.bold.cyan("Name"),
83
+ chalk.bold.green("Affected"),
84
+ chalk.bold.red("Unchanged"),
85
+ chalk.bold.blue("Duration")
86
+ ]
87
+ });
88
+ table.push(...rows);
89
+ return table.toString();
90
+ };
91
+ const codemodList = (codemods) => {
92
+ const rows = codemods.map((codemod, index2) => {
93
+ const fIndex = chalk.grey(index2);
94
+ const fVersion = chalk.magenta(codemod.version);
95
+ const fKind = chalk.yellow(codemod.kind);
96
+ const fName = chalk.blue(codemod.format());
97
+ const fUID = codemodUID(codemod.uid);
98
+ return [fIndex, fVersion, fKind, fName, fUID];
99
+ });
100
+ const table = new CliTable3({
101
+ style: { compact: true },
102
+ head: [
103
+ chalk.bold.grey("N°"),
104
+ chalk.bold.magenta("Version"),
105
+ chalk.bold.yellow("Kind"),
106
+ chalk.bold.blue("Name"),
107
+ chalk.bold.cyan("UID")
108
+ ]
109
+ });
110
+ table.push(...rows);
111
+ return table.toString();
200
112
  };
113
+ const durationMs = (elapsedMs) => {
114
+ const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
115
+ return `${elapsedSeconds}s`;
116
+ };
117
+ const index$f = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
118
+ __proto__: null,
119
+ codemodList,
120
+ codemodUID,
121
+ durationMs,
122
+ highlight,
123
+ path,
124
+ projectDetails,
125
+ projectType,
126
+ reports,
127
+ transform,
128
+ upgradeStep,
129
+ version,
130
+ versionRange
131
+ }, Symbol.toStringTag, { value: "Module" }));
132
+ const NPM_REGISTRY_URL = "https://registry.npmjs.org";
201
133
  var ReleaseType = /* @__PURE__ */ ((ReleaseType2) => {
202
134
  ReleaseType2["Major"] = "major";
203
135
  ReleaseType2["Minor"] = "minor";
204
136
  ReleaseType2["Patch"] = "patch";
137
+ ReleaseType2["Latest"] = "latest";
205
138
  return ReleaseType2;
206
139
  })(ReleaseType || {});
207
140
  const types = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
@@ -227,17 +160,20 @@ const rangeFactory = (range) => {
227
160
  };
228
161
  const rangeFromReleaseType = (current, identifier) => {
229
162
  switch (identifier) {
230
- case ReleaseType.Major: {
231
- const nextMajor = semver.inc(current, "major");
232
- return rangeFactory(`>${current.raw} <=${nextMajor}`);
163
+ case ReleaseType.Latest: {
164
+ return rangeFactory(`>${current.raw}`);
233
165
  }
234
- case ReleaseType.Patch: {
235
- const minor = semver.inc(current, "minor");
236
- return rangeFactory(`>${current.raw} <${minor}`);
166
+ case ReleaseType.Major: {
167
+ const nextMajor = semVerFactory(current.raw).inc("major");
168
+ return rangeFactory(`>${current.raw} <=${nextMajor.major}`);
237
169
  }
238
170
  case ReleaseType.Minor: {
239
- const major = semver.inc(current, "major");
240
- return rangeFactory(`>${current.raw} <${major}`);
171
+ const nextMajor = semVerFactory(current.raw).inc("major");
172
+ return rangeFactory(`>${current.raw} <${nextMajor.raw}`);
173
+ }
174
+ case ReleaseType.Patch: {
175
+ const nextMinor = semVerFactory(current.raw).inc("minor");
176
+ return rangeFactory(`>${current.raw} <${nextMinor.raw}`);
241
177
  }
242
178
  default: {
243
179
  throw new Error("Not implemented");
@@ -253,25 +189,75 @@ const rangeFromVersions = (currentVersion, target) => {
253
189
  }
254
190
  throw new Error(`Invalid target set: ${target}`);
255
191
  };
192
+ const isValidStringifiedRange = (str) => semver.validRange(str) !== null;
193
+ const isRangeInstance = (range) => {
194
+ return range instanceof semver.Range;
195
+ };
256
196
  const index$e = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
257
197
  __proto__: null,
258
198
  Version: types,
259
199
  isLiteralSemVer,
200
+ isRangeInstance,
260
201
  isSemVerReleaseType,
261
202
  isSemverInstance,
262
203
  isValidSemVer,
204
+ isValidStringifiedRange,
263
205
  rangeFactory,
264
206
  rangeFromReleaseType,
265
207
  rangeFromVersions,
266
208
  semVerFactory
267
209
  }, Symbol.toStringTag, { value: "Module" }));
210
+ class Package {
211
+ name;
212
+ packageURL;
213
+ npmPackage;
214
+ constructor(name) {
215
+ this.name = name;
216
+ this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
217
+ this.npmPackage = null;
218
+ }
219
+ get isLoaded() {
220
+ return this.npmPackage !== null;
221
+ }
222
+ assertPackageIsLoaded(npmPackage) {
223
+ assert(this.isLoaded, "The package is not loaded yet");
224
+ }
225
+ getVersionsDict() {
226
+ this.assertPackageIsLoaded(this.npmPackage);
227
+ return this.npmPackage.versions;
228
+ }
229
+ getVersionsAsList() {
230
+ this.assertPackageIsLoaded(this.npmPackage);
231
+ return Object.values(this.npmPackage.versions);
232
+ }
233
+ findVersionsInRange(range) {
234
+ const versions = this.getVersionsAsList();
235
+ return versions.filter((v) => range.test(v.version)).filter((v) => isLiteralSemVer(v.version)).sort((v1, v2) => semver.compare(v1.version, v2.version));
236
+ }
237
+ findVersion(version2) {
238
+ const versions = this.getVersionsAsList();
239
+ return versions.find((npmVersion) => semver.eq(npmVersion.version, version2));
240
+ }
241
+ async refresh() {
242
+ const response = await fetch(this.packageURL);
243
+ assert(response.ok, `Request failed for ${this.packageURL}`);
244
+ this.npmPackage = await response.json();
245
+ return this;
246
+ }
247
+ versionExists(version2) {
248
+ return this.findVersion(version2) !== void 0;
249
+ }
250
+ }
251
+ const npmPackageFactory = (name) => new Package(name);
268
252
  class FileScanner {
269
253
  cwd;
270
254
  constructor(cwd) {
271
255
  this.cwd = cwd;
272
256
  }
273
257
  scan(patterns) {
274
- const filenames = glob.sync(patterns, { cwd: this.cwd });
258
+ const filenames = fastglob.sync(patterns, {
259
+ cwd: this.cwd
260
+ });
275
261
  return filenames.map((filename) => path$1.join(this.cwd, filename));
276
262
  }
277
263
  }
@@ -309,6 +295,46 @@ const index$c = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
309
295
  __proto__: null,
310
296
  codeRunnerFactory
311
297
  }, Symbol.toStringTag, { value: "Module" }));
298
+ class JSONTransformAPI {
299
+ json;
300
+ constructor(json) {
301
+ this.json = cloneDeep(json);
302
+ }
303
+ get(path2, defaultValue) {
304
+ if (!path2) {
305
+ return this.root();
306
+ }
307
+ return cloneDeep(get(path2, this.json) ?? defaultValue);
308
+ }
309
+ has(path2) {
310
+ return has(path2, this.json);
311
+ }
312
+ merge(other) {
313
+ this.json = merge(other, this.json);
314
+ return this;
315
+ }
316
+ root() {
317
+ return cloneDeep(this.json);
318
+ }
319
+ set(path2, value) {
320
+ this.json = set(path2, value, this.json);
321
+ return this;
322
+ }
323
+ remove(path2) {
324
+ this.json = omit(path2, this.json);
325
+ return this;
326
+ }
327
+ }
328
+ const createJSONTransformAPI = (object) => new JSONTransformAPI(object);
329
+ const readJSON = async (path2) => {
330
+ const buffer = await fse.readFile(path2);
331
+ return JSON.parse(buffer.toString());
332
+ };
333
+ const saveJSON = async (path2, json) => {
334
+ const jsonAsString = `${JSON.stringify(json, null, 2)}
335
+ `;
336
+ await fse.writeFile(path2, jsonAsString);
337
+ };
312
338
  const transformJSON = async (codemodPath, paths, config) => {
313
339
  const { dry } = config;
314
340
  const startTime = process.hrtime();
@@ -320,7 +346,11 @@ const transformJSON = async (codemodPath, paths, config) => {
320
346
  timeElapsed: "",
321
347
  stats: {}
322
348
  };
323
- const esbuildOptions = { extensions: [".js", ".mjs", ".ts"] };
349
+ const esbuildOptions = {
350
+ extensions: [".js", ".mjs", ".ts"],
351
+ hookIgnoreNodeModules: false,
352
+ hookMatcher: isEqual(codemodPath)
353
+ };
324
354
  const { unregister } = register(esbuildOptions);
325
355
  const module = require(codemodPath);
326
356
  unregister();
@@ -365,17 +395,31 @@ const index$b = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
365
395
  jsonRunnerFactory
366
396
  }, Symbol.toStringTag, { value: "Module" }));
367
397
  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"];
398
+ const PROJECT_APP_ALLOWED_ROOT_PATHS = ["src", "config", "public"];
399
+ const PROJECT_PLUGIN_ALLOWED_ROOT_PATHS = ["admin", "server"];
400
+ const PROJECT_PLUGIN_ROOT_FILES = ["strapi-admin.js", "strapi-server.js"];
401
+ const PROJECT_CODE_EXTENSIONS = [
402
+ // Source files
403
+ "js",
404
+ "mjs",
405
+ "ts",
406
+ // React files
407
+ "jsx",
408
+ "tsx"
409
+ ];
410
+ const PROJECT_JSON_EXTENSIONS = ["json"];
411
+ const PROJECT_ALLOWED_EXTENSIONS = [...PROJECT_CODE_EXTENSIONS, ...PROJECT_JSON_EXTENSIONS];
371
412
  const SCOPED_STRAPI_PACKAGE_PREFIX = "@strapi/";
372
413
  const STRAPI_DEPENDENCY_NAME = `${SCOPED_STRAPI_PACKAGE_PREFIX}strapi`;
373
414
  const constants$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
374
415
  __proto__: null,
375
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS,
376
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS,
377
- PROJECT_DEFAULT_PATTERNS,
416
+ PROJECT_ALLOWED_EXTENSIONS,
417
+ PROJECT_APP_ALLOWED_ROOT_PATHS,
418
+ PROJECT_CODE_EXTENSIONS,
419
+ PROJECT_JSON_EXTENSIONS,
378
420
  PROJECT_PACKAGE_JSON,
421
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS,
422
+ PROJECT_PLUGIN_ROOT_FILES,
379
423
  SCOPED_STRAPI_PACKAGE_PREFIX,
380
424
  STRAPI_DEPENDENCY_NAME
381
425
  }, Symbol.toStringTag, { value: "Module" }));
@@ -385,12 +429,13 @@ class Project {
385
429
  files;
386
430
  packageJSONPath;
387
431
  packageJSON;
388
- strapiVersion;
389
- constructor(cwd) {
432
+ paths;
433
+ constructor(cwd, config) {
390
434
  if (!fse.pathExistsSync(cwd)) {
391
435
  throw new Error(`ENOENT: no such file or directory, access '${cwd}'`);
392
436
  }
393
437
  this.cwd = cwd;
438
+ this.paths = config.paths;
394
439
  this.refresh();
395
440
  }
396
441
  getFilesByExtensions(extensions) {
@@ -401,14 +446,13 @@ class Project {
401
446
  }
402
447
  refresh() {
403
448
  this.refreshPackageJSON();
404
- this.refreshStrapiVersion();
405
449
  this.refreshProjectFiles();
406
450
  return this;
407
451
  }
408
- async runCodemods(codemods2, options) {
452
+ async runCodemods(codemods, options) {
409
453
  const runners = this.createProjectCodemodsRunners(options.dry);
410
454
  const reports2 = [];
411
- for (const codemod of codemods2) {
455
+ for (const codemod of codemods) {
412
456
  for (const runner of runners) {
413
457
  if (runner.valid(codemod)) {
414
458
  const report = await runner.run(codemod);
@@ -419,17 +463,20 @@ class Project {
419
463
  return reports2;
420
464
  }
421
465
  createProjectCodemodsRunners(dry = false) {
422
- const jsonFiles = this.getFilesByExtensions([".json"]);
423
- const codeFiles = this.getFilesByExtensions([".js", ".ts", ".mjs"]);
466
+ const jsonExtensions = PROJECT_JSON_EXTENSIONS.map((ext) => `.${ext}`);
467
+ const codeExtensions = PROJECT_CODE_EXTENSIONS.map((ext) => `.${ext}`);
468
+ const jsonFiles = this.getFilesByExtensions(jsonExtensions);
469
+ const codeFiles = this.getFilesByExtensions(codeExtensions);
424
470
  const codeRunner = codeRunnerFactory(codeFiles, {
425
471
  dry,
426
- print: false,
427
- silent: true,
428
- extensions: "js,ts",
472
+ parser: "ts",
429
473
  runInBand: true,
430
- verbose: 0,
431
474
  babel: true,
432
- parser: "ts"
475
+ extensions: PROJECT_CODE_EXTENSIONS.join(","),
476
+ // Don't output any log coming from the runner
477
+ print: false,
478
+ silent: true,
479
+ verbose: 0
433
480
  });
434
481
  const jsonRunner = jsonRunnerFactory(jsonFiles, { dry, cwd: this.cwd });
435
482
  return [codeRunner, jsonRunner];
@@ -446,16 +493,38 @@ class Project {
446
493
  this.packageJSON = JSON.parse(packageJSONBuffer.toString());
447
494
  }
448
495
  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
496
  const scanner = fileScannerFactory(this.cwd);
458
- this.files = scanner.scan(patterns);
497
+ this.files = scanner.scan(this.paths);
498
+ }
499
+ }
500
+ class AppProject extends Project {
501
+ strapiVersion;
502
+ type = "application";
503
+ /**
504
+ * Returns an array of allowed file paths for a Strapi application
505
+ *
506
+ * The resulting paths include app default files and the root package.json file.
507
+ */
508
+ static get paths() {
509
+ const allowedRootPaths = formatGlobCollectionPattern(PROJECT_APP_ALLOWED_ROOT_PATHS);
510
+ const allowedExtensions = formatGlobCollectionPattern(PROJECT_ALLOWED_EXTENSIONS);
511
+ return [
512
+ // App default files
513
+ `./${allowedRootPaths}/**/*.${allowedExtensions}`,
514
+ `!./**/node_modules/**/*`,
515
+ `!./**/dist/**/*`,
516
+ // Root package.json file
517
+ PROJECT_PACKAGE_JSON
518
+ ];
519
+ }
520
+ constructor(cwd) {
521
+ super(cwd, { paths: AppProject.paths });
522
+ this.refreshStrapiVersion();
523
+ }
524
+ refresh() {
525
+ super.refresh();
526
+ this.refreshStrapiVersion();
527
+ return this;
459
528
  }
460
529
  refreshStrapiVersion() {
461
530
  this.strapiVersion = // First try to get the strapi version from the package.json dependencies
@@ -502,10 +571,71 @@ const formatGlobCollectionPattern = (collection) => {
502
571
  );
503
572
  return collection.length === 1 ? collection[0] : `{${collection}}`;
504
573
  };
505
- const projectFactory = (cwd) => new Project(cwd);
574
+ class PluginProject extends Project {
575
+ type = "plugin";
576
+ /**
577
+ * Returns an array of allowed file paths for a Strapi plugin
578
+ *
579
+ * The resulting paths include plugin default files, the root package.json file, and plugin-specific files.
580
+ */
581
+ static get paths() {
582
+ const allowedRootPaths = formatGlobCollectionPattern(
583
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS
584
+ );
585
+ const allowedExtensions = formatGlobCollectionPattern(PROJECT_ALLOWED_EXTENSIONS);
586
+ return [
587
+ // Plugin default files
588
+ `./${allowedRootPaths}/**/*.${allowedExtensions}`,
589
+ `!./**/node_modules/**/*`,
590
+ `!./**/dist/**/*`,
591
+ // Root package.json file
592
+ PROJECT_PACKAGE_JSON,
593
+ // Plugin root files
594
+ ...PROJECT_PLUGIN_ROOT_FILES
595
+ ];
596
+ }
597
+ constructor(cwd) {
598
+ super(cwd, { paths: PluginProject.paths });
599
+ }
600
+ }
601
+ const isPlugin = (cwd) => {
602
+ const packageJSONPath = path$1.join(cwd, PROJECT_PACKAGE_JSON);
603
+ try {
604
+ fse.accessSync(packageJSONPath);
605
+ } catch {
606
+ throw new Error(`Could not find a ${PROJECT_PACKAGE_JSON} file in ${cwd}`);
607
+ }
608
+ const packageJSONBuffer = fse.readFileSync(packageJSONPath);
609
+ const packageJSON = JSON.parse(packageJSONBuffer.toString());
610
+ return packageJSON?.strapi?.kind === "plugin";
611
+ };
612
+ const projectFactory = (cwd) => {
613
+ fse.accessSync(cwd);
614
+ return isPlugin(cwd) ? new PluginProject(cwd) : new AppProject(cwd);
615
+ };
616
+ const isPluginProject = (project) => {
617
+ return project instanceof PluginProject;
618
+ };
619
+ function assertPluginProject(project) {
620
+ if (!isPluginProject(project)) {
621
+ throw new Error("Project is not a plugin");
622
+ }
623
+ }
624
+ const isApplicationProject = (project) => {
625
+ return project instanceof AppProject;
626
+ };
627
+ function assertAppProject(project) {
628
+ if (!isApplicationProject(project)) {
629
+ throw new Error("Project is not an application");
630
+ }
631
+ }
506
632
  const index$a = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
507
633
  __proto__: null,
634
+ assertAppProject,
635
+ assertPluginProject,
508
636
  constants: constants$3,
637
+ isApplicationProject,
638
+ isPluginProject,
509
639
  projectFactory
510
640
  }, Symbol.toStringTag, { value: "Module" }));
511
641
  class UnexpectedError extends Error {
@@ -513,70 +643,33 @@ class UnexpectedError extends Error {
513
643
  super("Unexpected Error");
514
644
  }
515
645
  }
516
- const unknownToError = (e) => {
517
- if (e instanceof Error) {
518
- return e;
646
+ class NPMCandidateNotFoundError extends Error {
647
+ target;
648
+ constructor(target, message = `Couldn't find a valid NPM candidate for "${target}"`) {
649
+ super(message);
650
+ this.target = target;
519
651
  }
520
- if (typeof e === "string") {
521
- return new Error(e);
652
+ }
653
+ class AbortedError extends Error {
654
+ constructor(message = "Upgrade aborted") {
655
+ super(message);
522
656
  }
523
- return new UnexpectedError();
524
- };
525
- const index$9 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
526
- __proto__: null,
527
- UnexpectedError,
528
- unknownToError
529
- }, Symbol.toStringTag, { value: "Module" }));
530
- const path = (path2) => chalk.blue(path2);
531
- const version = (version2) => {
532
- return chalk.italic.yellow(`v${version2}`);
533
- };
534
- const versionRange = (range) => chalk.italic.yellow(range);
535
- const transform = (transformFilePath) => chalk.cyan(transformFilePath);
536
- const highlight = (arg) => chalk.bold.underline(arg);
537
- const upgradeStep = (text, step) => {
538
- return chalk.bold(`(${step[0]}/${step[1]}) ${text}...`);
539
- };
540
- const reports = (reports2) => {
541
- const rows = reports2.map(({ codemod, report }, i) => {
542
- const fIndex = chalk.grey(i);
543
- const fVersion = chalk.magenta(codemod.version);
544
- const fKind = chalk.yellow(codemod.kind);
545
- const fFormattedTransformPath = chalk.cyan(codemod.format());
546
- const fTimeElapsed = i === 0 ? `${report.timeElapsed}s ${chalk.dim.italic("(cold start)")}` : `${report.timeElapsed}s`;
547
- const fAffected = report.ok > 0 ? chalk.green(report.ok) : chalk.grey(0);
548
- const fUnchanged = report.ok === 0 ? chalk.red(report.nochange) : chalk.grey(report.nochange);
549
- return [fIndex, fVersion, fKind, fFormattedTransformPath, fAffected, fUnchanged, fTimeElapsed];
550
- });
551
- const table = new CliTable3({
552
- style: { compact: true },
553
- head: [
554
- chalk.bold.grey("N°"),
555
- chalk.bold.magenta("Version"),
556
- chalk.bold.yellow("Kind"),
557
- chalk.bold.cyan("Name"),
558
- chalk.bold.green("Affected"),
559
- chalk.bold.red("Unchanged"),
560
- chalk.bold.blue("Duration")
561
- ]
562
- });
563
- table.push(...rows);
564
- return table.toString();
565
- };
566
- const durationMs = (elapsedMs) => {
567
- const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
568
- return `${elapsedSeconds}s`;
657
+ }
658
+ const unknownToError = (e) => {
659
+ if (e instanceof Error) {
660
+ return e;
661
+ }
662
+ if (typeof e === "string") {
663
+ return new Error(e);
664
+ }
665
+ return new UnexpectedError();
569
666
  };
570
- const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
667
+ const index$9 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
571
668
  __proto__: null,
572
- durationMs,
573
- highlight,
574
- path,
575
- reports,
576
- transform,
577
- upgradeStep,
578
- version,
579
- versionRange
669
+ AbortedError,
670
+ NPMCandidateNotFoundError,
671
+ UnexpectedError,
672
+ unknownToError
580
673
  }, Symbol.toStringTag, { value: "Module" }));
581
674
  const CODEMOD_CODE_SUFFIX = "code";
582
675
  const CODEMOD_JSON_SUFFIX = "json";
@@ -594,6 +687,7 @@ const constants$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP
594
687
  CODEMOD_JSON_SUFFIX
595
688
  }, Symbol.toStringTag, { value: "Module" }));
596
689
  class Codemod {
690
+ uid;
597
691
  kind;
598
692
  version;
599
693
  baseDirectory;
@@ -605,17 +699,49 @@ class Codemod {
605
699
  this.baseDirectory = options.baseDirectory;
606
700
  this.filename = options.filename;
607
701
  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("-", " ");
702
+ this.uid = this.createUID();
703
+ }
704
+ createUID() {
705
+ const name = this.format({ stripExtension: true, stripKind: true, stripHyphens: false });
706
+ const kind = this.kind;
707
+ const version2 = this.version.raw;
708
+ return `${version2}-${name}-${kind}`;
709
+ }
710
+ format(options) {
711
+ const { stripExtension = true, stripKind = true, stripHyphens = true } = options ?? {};
712
+ let formatted = this.filename;
713
+ if (stripExtension) {
714
+ formatted = formatted.replace(new RegExp(`\\.${CODEMOD_EXTENSION}$`, "i"), "");
715
+ }
716
+ if (stripKind) {
717
+ formatted = formatted.replace(`.${CODEMOD_CODE_SUFFIX}`, "").replace(`.${CODEMOD_JSON_SUFFIX}`, "");
718
+ }
719
+ if (stripHyphens) {
720
+ formatted = formatted.replaceAll("-", " ");
721
+ }
722
+ return formatted;
611
723
  }
612
724
  }
613
725
  const codemodFactory = (options) => new Codemod(options);
614
- const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
726
+ const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
615
727
  __proto__: null,
616
728
  codemodFactory,
617
729
  constants: constants$2
618
730
  }, Symbol.toStringTag, { value: "Module" }));
731
+ const INTERNAL_CODEMODS_DIRECTORY = path$1.join(
732
+ __dirname,
733
+ // upgrade/dist
734
+ "..",
735
+ // upgrade
736
+ "resources",
737
+ // upgrade/resources
738
+ "codemods"
739
+ // upgrade/resources/codemods
740
+ );
741
+ const constants$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
742
+ __proto__: null,
743
+ INTERNAL_CODEMODS_DIRECTORY
744
+ }, Symbol.toStringTag, { value: "Module" }));
619
745
  class CodemodRepository {
620
746
  groups;
621
747
  versions;
@@ -634,23 +760,48 @@ class CodemodRepository {
634
760
  count(version2) {
635
761
  return this.findByVersion(version2).length;
636
762
  }
637
- countRange(range) {
638
- return this.findByRange(range).length;
639
- }
640
- exists(version2) {
763
+ versionExists(version2) {
641
764
  return version2.raw in this.groups;
642
765
  }
643
- findByRange(range) {
766
+ has(uid) {
767
+ const result = this.find({ uids: [uid] });
768
+ if (result.length !== 1) {
769
+ return false;
770
+ }
771
+ const { codemods } = result[0];
772
+ return codemods.length === 1 && codemods[0].uid === uid;
773
+ }
774
+ find(q) {
644
775
  const entries = Object.entries(this.groups);
645
- return entries.filter(([version2]) => range.test(version2)).map(([version2, codemods2]) => ({
776
+ return entries.filter(maybeFilterByRange).map(([version2, codemods]) => ({
646
777
  version: semVerFactory(version2),
647
- codemods: codemods2
648
- }));
778
+ // Filter by UID if provided in the query
779
+ codemods: codemods.filter(maybeFilterByUIDs)
780
+ })).filter(({ codemods }) => codemods.length > 0);
781
+ function maybeFilterByRange([version2]) {
782
+ if (!isRangeInstance(q.range)) {
783
+ return true;
784
+ }
785
+ return q.range.test(version2);
786
+ }
787
+ function maybeFilterByUIDs(codemod) {
788
+ if (q.uids === void 0) {
789
+ return true;
790
+ }
791
+ return q.uids.includes(codemod.uid);
792
+ }
649
793
  }
650
794
  findByVersion(version2) {
651
795
  const literalVersion = version2.raw;
652
- const codemods2 = this.groups[literalVersion];
653
- return codemods2 ?? [];
796
+ const codemods = this.groups[literalVersion];
797
+ return codemods ?? [];
798
+ }
799
+ findAll() {
800
+ const entries = Object.entries(this.groups);
801
+ return entries.map(([version2, codemods]) => ({
802
+ version: semVerFactory(version2),
803
+ codemods
804
+ }));
654
805
  }
655
806
  refreshAvailableVersions() {
656
807
  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,19 +832,10 @@ const parseCodemodKindFromFilename = (filename) => {
681
832
  assert(CODEMOD_ALLOWED_SUFFIXES.includes(kind));
682
833
  return kind;
683
834
  };
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" }));
696
- const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
835
+ const codemodRepositoryFactory = (cwd = INTERNAL_CODEMODS_DIRECTORY) => {
836
+ return new CodemodRepository(cwd);
837
+ };
838
+ const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
697
839
  __proto__: null,
698
840
  codemodRepositoryFactory,
699
841
  constants: constants$1
@@ -727,34 +869,59 @@ class CodemodRunner {
727
869
  this.isDry = enabled;
728
870
  return this;
729
871
  }
730
- async run(codemodsDirectory) {
872
+ createRepository(codemodsDirectory) {
731
873
  const repository = codemodRepositoryFactory(
732
874
  codemodsDirectory ?? INTERNAL_CODEMODS_DIRECTORY
733
875
  );
734
876
  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();
877
+ return repository;
878
+ }
879
+ async safeRunAndReport(codemods) {
880
+ if (this.isDry) {
881
+ this.logger?.warn?.(
882
+ "Running the codemods in dry mode. No files will be modified during the process."
883
+ );
741
884
  }
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
885
  try {
752
- const reports$1 = await this.project.runCodemods(codemods2, { dry: this.isDry });
753
- this.logger?.raw(reports(reports$1));
886
+ const reports$1 = await this.project.runCodemods(codemods, { dry: this.isDry });
887
+ this.logger?.raw?.(reports(reports$1));
888
+ if (!this.isDry) {
889
+ const nbAffectedTotal = reports$1.flatMap((report) => report.report.ok).reduce((acc, nb) => acc + nb, 0);
890
+ this.logger?.debug?.(
891
+ `Successfully ran ${highlight(codemods.length)} codemod(s), ${highlight(nbAffectedTotal)} change(s) have been detected`
892
+ );
893
+ }
894
+ return successReport$1();
754
895
  } catch (e) {
755
896
  return erroredReport$1(unknownToError(e));
756
897
  }
757
- return successReport$1();
898
+ }
899
+ async runByUID(uid, codemodsDirectory) {
900
+ const repository = this.createRepository(codemodsDirectory);
901
+ if (!repository.has(uid)) {
902
+ throw new Error(`Unknown codemod UID provided: ${uid}`);
903
+ }
904
+ const codemods = repository.find({ uids: [uid] }).flatMap(({ codemods: codemods2 }) => codemods2);
905
+ return this.safeRunAndReport(codemods);
906
+ }
907
+ async run(codemodsDirectory) {
908
+ const repository = this.createRepository(codemodsDirectory);
909
+ const codemodsInRange = repository.find({ range: this.range });
910
+ const selectedCodemods = this.selectCodemodsCallback ? await this.selectCodemodsCallback(codemodsInRange) : codemodsInRange;
911
+ if (selectedCodemods.length === 0) {
912
+ this.logger?.debug?.(`Found no codemods to run for ${versionRange(this.range)}`);
913
+ return successReport$1();
914
+ }
915
+ const codemods = selectedCodemods.flatMap(({ codemods: codemods2 }) => codemods2);
916
+ const codemodsByVersion = groupBy("version", codemods);
917
+ const fRange = versionRange(this.range);
918
+ this.logger?.debug?.(
919
+ `Found ${highlight(codemods.length)} codemods for ${highlight(size(codemodsByVersion))} version(s) using ${fRange}`
920
+ );
921
+ for (const [version$1, codemods2] of Object.entries(codemodsByVersion)) {
922
+ this.logger?.debug?.(`- ${version(semVerFactory(version$1))} (${codemods2.length})`);
923
+ }
924
+ return this.safeRunAndReport(codemods);
758
925
  }
759
926
  }
760
927
  const codemodRunnerFactory = (project, range) => {
@@ -781,6 +948,15 @@ class Upgrader {
781
948
  this.logger = null;
782
949
  this.confirmationCallback = null;
783
950
  }
951
+ getNPMPackage() {
952
+ return this.npmPackage;
953
+ }
954
+ getProject() {
955
+ return this.project;
956
+ }
957
+ getTarget() {
958
+ return semVerFactory(this.target.raw);
959
+ }
784
960
  setRequirements(requirements) {
785
961
  this.requirements = requirements;
786
962
  return this;
@@ -793,7 +969,7 @@ class Upgrader {
793
969
  this.codemodsTarget = semVerFactory(
794
970
  `${this.target.major}.${this.target.minor}.${this.target.patch}`
795
971
  );
796
- this.logger?.debug(
972
+ this.logger?.debug?.(
797
973
  `The codemods target has been synced with the upgrade target. The codemod runner will now look for ${version(
798
974
  this.codemodsTarget
799
975
  )}`
@@ -802,7 +978,7 @@ class Upgrader {
802
978
  }
803
979
  overrideCodemodsTarget(target) {
804
980
  this.codemodsTarget = target;
805
- this.logger?.debug(
981
+ this.logger?.debug?.(
806
982
  `Overriding the codemods target. The codemod runner will now look for ${version(target)}`
807
983
  );
808
984
  return this;
@@ -822,44 +998,52 @@ class Upgrader {
822
998
  addRequirement(requirement) {
823
999
  this.requirements.push(requirement);
824
1000
  const fRequired = requirement.isRequired ? "(required)" : "(optional)";
825
- this.logger?.debug(
1001
+ this.logger?.debug?.(
826
1002
  `Added a new requirement to the upgrade: ${highlight(requirement.name)} ${fRequired}`
827
1003
  );
828
1004
  return this;
829
1005
  }
830
1006
  async upgrade() {
831
- this.logger?.info(
1007
+ this.logger?.info?.(
832
1008
  `Upgrading from ${version(this.project.strapiVersion)} to ${version(this.target)}`
833
1009
  );
834
1010
  if (this.isDry) {
835
- this.logger?.warn(
1011
+ this.logger?.warn?.(
836
1012
  "Running the upgrade in dry mode. No files will be modified during the process."
837
1013
  );
838
1014
  }
839
1015
  const range = rangeFromVersions(this.project.strapiVersion, this.target);
840
1016
  const codemodsRange = rangeFromVersions(this.project.strapiVersion, this.codemodsTarget);
841
1017
  const npmVersionsMatches = this.npmPackage?.findVersionsInRange(range) ?? [];
842
- this.logger?.debug(
1018
+ this.logger?.debug?.(
843
1019
  `Found ${highlight(npmVersionsMatches.length)} versions satisfying ${versionRange(range)}`
844
1020
  );
845
1021
  try {
846
- this.logger?.info(upgradeStep("Checking requirement", [1, 4]));
1022
+ this.logger?.info?.(upgradeStep("Checking requirement", [1, 4]));
847
1023
  await this.checkRequirements(this.requirements, {
848
1024
  npmVersionsMatches,
849
1025
  project: this.project,
850
1026
  target: this.target
851
1027
  });
852
- this.logger?.info(upgradeStep("Applying the latest code modifications", [2, 4]));
1028
+ this.logger?.info?.(upgradeStep("Applying the latest code modifications", [2, 4]));
853
1029
  await this.runCodemods(codemodsRange);
854
- this.logger?.info(upgradeStep("Upgrading Strapi dependencies", [3, 4]));
1030
+ this.logger?.debug?.("Refreshing project information...");
1031
+ this.project.refresh();
1032
+ this.logger?.info?.(upgradeStep("Upgrading Strapi dependencies", [3, 4]));
855
1033
  await this.updateDependencies();
856
- this.logger?.info(upgradeStep("Installing dependencies", [4, 4]));
1034
+ this.logger?.info?.(upgradeStep("Installing dependencies", [4, 4]));
857
1035
  await this.installDependencies();
858
1036
  } catch (e) {
859
1037
  return erroredReport(unknownToError(e));
860
1038
  }
861
1039
  return successReport();
862
1040
  }
1041
+ async confirm(message) {
1042
+ if (typeof this.confirmationCallback !== "function") {
1043
+ return true;
1044
+ }
1045
+ return this.confirmationCallback(message);
1046
+ }
863
1047
  async checkRequirements(requirements, context) {
864
1048
  for (const requirement of requirements) {
865
1049
  const { pass, error } = await requirement.test(context);
@@ -886,7 +1070,7 @@ class Upgrader {
886
1070
  if (requirement.isRequired) {
887
1071
  throw error;
888
1072
  }
889
- this.logger?.warn(warningMessage);
1073
+ this.logger?.warn?.(warningMessage);
890
1074
  const response = await this.confirmationCallback?.(confirmationMessage);
891
1075
  if (!response) {
892
1076
  throw error;
@@ -897,9 +1081,11 @@ class Upgrader {
897
1081
  const json = createJSONTransformAPI(packageJSON);
898
1082
  const dependencies = json.get("dependencies", {});
899
1083
  const strapiDependencies = this.getScopedStrapiDependencies(dependencies);
900
- this.logger?.debug(`Found ${highlight(strapiDependencies.length)} dependency(ies) to update`);
1084
+ this.logger?.debug?.(
1085
+ `Found ${highlight(strapiDependencies.length)} dependency(ies) to update`
1086
+ );
901
1087
  strapiDependencies.forEach(
902
- (dependency) => this.logger?.debug(`- ${dependency[0]} (${dependency[1]} -> ${this.target})`)
1088
+ (dependency) => this.logger?.debug?.(`- ${dependency[0]} (${dependency[1]} -> ${this.target})`)
903
1089
  );
904
1090
  if (strapiDependencies.length === 0) {
905
1091
  return;
@@ -907,7 +1093,7 @@ class Upgrader {
907
1093
  strapiDependencies.forEach(([name]) => json.set(`dependencies.${name}`, this.target.raw));
908
1094
  const updatedPackageJSON = json.root();
909
1095
  if (this.isDry) {
910
- this.logger?.debug(`Skipping dependencies update (${chalk.italic("dry mode")})`);
1096
+ this.logger?.debug?.(`Skipping dependencies update (${chalk.italic("dry mode")})`);
911
1097
  return;
912
1098
  }
913
1099
  await saveJSON(packageJSONPath, updatedPackageJSON);
@@ -927,9 +1113,9 @@ class Upgrader {
927
1113
  async installDependencies() {
928
1114
  const projectPath = this.project.cwd;
929
1115
  const packageManagerName = await packageManager.getPreferred(projectPath);
930
- this.logger?.debug(`Using ${highlight(packageManagerName)} as package manager`);
1116
+ this.logger?.debug?.(`Using ${highlight(packageManagerName)} as package manager`);
931
1117
  if (this.isDry) {
932
- this.logger?.debug(`Skipping dependencies installation (${chalk.italic("dry mode")}`);
1118
+ this.logger?.debug?.(`Skipping dependencies installation (${chalk.italic("dry mode")})`);
933
1119
  return;
934
1120
  }
935
1121
  await packageManager.installDependencies(projectPath, packageManagerName, {
@@ -948,23 +1134,28 @@ class Upgrader {
948
1134
  }
949
1135
  const resolveNPMTarget = (project, target, npmPackage) => {
950
1136
  if (isSemverInstance(target)) {
951
- return npmPackage.findVersion(target);
1137
+ const version2 = npmPackage.findVersion(target);
1138
+ if (!version2) {
1139
+ throw new NPMCandidateNotFoundError(target);
1140
+ }
1141
+ return version2;
952
1142
  }
953
1143
  if (isSemVerReleaseType(target)) {
954
1144
  const range = rangeFromVersions(project.strapiVersion, target);
955
1145
  const npmVersionsMatches = npmPackage.findVersionsInRange(range);
956
- return npmVersionsMatches.at(-1);
1146
+ const version2 = npmVersionsMatches.at(-1);
1147
+ if (!version2) {
1148
+ throw new NPMCandidateNotFoundError(range, `The project is already up-to-date (${target})`);
1149
+ }
1150
+ return version2;
957
1151
  }
958
- return void 0;
1152
+ throw new NPMCandidateNotFoundError(target);
959
1153
  };
960
1154
  const upgraderFactory = (project, target, npmPackage) => {
961
- const targetedNPMVersion = resolveNPMTarget(project, target, npmPackage);
962
- if (!targetedNPMVersion) {
963
- throw new Error(`Couldn't find a matching version in the NPM registry for "${target}"`);
964
- }
965
- const semverTarget = semVerFactory(targetedNPMVersion.version);
1155
+ const npmTarget = resolveNPMTarget(project, target, npmPackage);
1156
+ const semverTarget = semVerFactory(npmTarget.version);
966
1157
  if (semver.eq(semverTarget, project.strapiVersion)) {
967
- throw new Error(`The project is already on ${version(semverTarget)}`);
1158
+ throw new Error(`The project is already using v${semverTarget}`);
968
1159
  }
969
1160
  return new Upgrader(project, semverTarget, npmPackage);
970
1161
  };
@@ -975,96 +1166,205 @@ const constants = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
975
1166
  __proto__: null,
976
1167
  STRAPI_PACKAGE_NAME
977
1168
  }, Symbol.toStringTag, { value: "Module" }));
978
- const index$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1169
+ const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
979
1170
  __proto__: null,
980
1171
  constants,
981
1172
  upgraderFactory
982
1173
  }, Symbol.toStringTag, { value: "Module" }));
983
- const NPM_REGISTRY_URL = "https://registry.npmjs.org";
984
- class Package {
1174
+ class Requirement {
1175
+ isRequired;
985
1176
  name;
986
- packageURL;
987
- npmPackage;
988
- constructor(name) {
1177
+ testCallback;
1178
+ children;
1179
+ constructor(name, testCallback, isRequired) {
989
1180
  this.name = name;
990
- this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
991
- this.npmPackage = null;
1181
+ this.testCallback = testCallback;
1182
+ this.isRequired = isRequired ?? true;
1183
+ this.children = [];
992
1184
  }
993
- get isLoaded() {
994
- return this.npmPackage !== null;
1185
+ setChildren(children) {
1186
+ this.children = children;
1187
+ return this;
995
1188
  }
996
- assertPackageIsLoaded(npmPackage) {
997
- assert(this.isLoaded, "The package is not loaded yet");
1189
+ addChild(child) {
1190
+ this.children.push(child);
1191
+ return this;
998
1192
  }
999
- getVersionsDict() {
1000
- this.assertPackageIsLoaded(this.npmPackage);
1001
- return this.npmPackage.versions;
1193
+ asOptional() {
1194
+ const newInstance = requirementFactory(this.name, this.testCallback, false);
1195
+ newInstance.setChildren(this.children);
1196
+ return newInstance;
1002
1197
  }
1003
- getVersionsAsList() {
1004
- this.assertPackageIsLoaded(this.npmPackage);
1005
- return Object.values(this.npmPackage.versions);
1198
+ asRequired() {
1199
+ const newInstance = requirementFactory(this.name, this.testCallback, true);
1200
+ newInstance.setChildren(this.children);
1201
+ return newInstance;
1006
1202
  }
1007
- findVersionsInRange(range) {
1008
- const versions = this.getVersionsAsList();
1009
- return versions.filter((v) => range.test(v.version)).filter((v) => isLiteralSemVer(v.version)).sort((v1, v2) => semver.compare(v1.version, v2.version));
1203
+ async test(context) {
1204
+ try {
1205
+ await this.testCallback?.(context);
1206
+ return ok();
1207
+ } catch (e) {
1208
+ if (e instanceof Error) {
1209
+ return errored(e);
1210
+ }
1211
+ if (typeof e === "string") {
1212
+ return errored(new Error(e));
1213
+ }
1214
+ return errored(new Error("Unknown error"));
1215
+ }
1010
1216
  }
1011
- findVersion(version2) {
1012
- const versions = this.getVersionsAsList();
1013
- return versions.find((npmVersion) => semver.eq(npmVersion.version, version2));
1217
+ }
1218
+ const ok = () => ({ pass: true, error: null });
1219
+ const errored = (error) => ({ pass: false, error });
1220
+ const requirementFactory = (name, testCallback, isRequired) => new Requirement(name, testCallback, isRequired);
1221
+ const index$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1222
+ __proto__: null,
1223
+ requirementFactory
1224
+ }, Symbol.toStringTag, { value: "Module" }));
1225
+ const REQUIRE_AVAILABLE_NEXT_MAJOR = requirementFactory(
1226
+ "REQUIRE_AVAILABLE_NEXT_MAJOR",
1227
+ (context) => {
1228
+ const { project, target } = context;
1229
+ const currentMajor = project.strapiVersion.major;
1230
+ const targetedMajor = target.major;
1231
+ if (targetedMajor === currentMajor) {
1232
+ throw new Error(`You're already on the latest major version (v${currentMajor})`);
1233
+ }
1014
1234
  }
1015
- async refresh() {
1016
- const response = await fetch(this.packageURL);
1017
- assert(response.ok, `Request failed for ${this.packageURL}`);
1018
- this.npmPackage = await response.json();
1019
- return this;
1235
+ );
1236
+ const REQUIRE_LATEST_FOR_CURRENT_MAJOR = requirementFactory(
1237
+ "REQUIRE_LATEST_FOR_CURRENT_MAJOR",
1238
+ (context) => {
1239
+ const { project, target, npmVersionsMatches } = context;
1240
+ const { major: currentMajor } = project.strapiVersion;
1241
+ const invalidMatches = npmVersionsMatches.filter(
1242
+ (match) => semVerFactory(match.version).major === currentMajor
1243
+ );
1244
+ if (invalidMatches.length > 0) {
1245
+ const invalidVersions = invalidMatches.map((match) => match.version);
1246
+ const invalidVersionsCount = invalidVersions.length;
1247
+ throw new Error(
1248
+ `Doing a major upgrade requires to be on the latest v${currentMajor} version, but found ${invalidVersionsCount} versions between the current one and ${target}. Please upgrade to ${invalidVersions.at(-1)} and try again.`
1249
+ );
1250
+ }
1020
1251
  }
1021
- versionExists(version2) {
1022
- return this.findVersion(version2) !== void 0;
1252
+ );
1253
+ const REQUIRE_GIT_CLEAN_REPOSITORY = requirementFactory(
1254
+ "REQUIRE_GIT_CLEAN_REPOSITORY",
1255
+ async (context) => {
1256
+ const git = simpleGit({ baseDir: context.project.cwd });
1257
+ const status = await git.status();
1258
+ if (!status.isClean()) {
1259
+ throw new Error(
1260
+ "Repository is not clean. Please commit or stash any changes before upgrading"
1261
+ );
1262
+ }
1023
1263
  }
1024
- }
1025
- const npmPackageFactory = (name) => new Package(name);
1264
+ );
1265
+ const REQUIRE_GIT_REPOSITORY = requirementFactory(
1266
+ "REQUIRE_GIT_REPOSITORY",
1267
+ async (context) => {
1268
+ const git = simpleGit({ baseDir: context.project.cwd });
1269
+ const isRepo = await git.checkIsRepo();
1270
+ if (!isRepo) {
1271
+ throw new Error("Not a git repository (or any of the parent directories)");
1272
+ }
1273
+ }
1274
+ ).addChild(REQUIRE_GIT_CLEAN_REPOSITORY.asOptional());
1275
+ const REQUIRE_GIT_INSTALLED = requirementFactory(
1276
+ "REQUIRE_GIT_INSTALLED",
1277
+ async (context) => {
1278
+ const git = simpleGit({ baseDir: context.project.cwd });
1279
+ try {
1280
+ await git.version();
1281
+ } catch {
1282
+ throw new Error("Git is not installed");
1283
+ }
1284
+ }
1285
+ ).addChild(REQUIRE_GIT_REPOSITORY.asOptional());
1286
+ const REQUIRE_GIT = requirementFactory("REQUIRE_GIT", null).addChild(
1287
+ REQUIRE_GIT_INSTALLED.asOptional()
1288
+ );
1289
+ const latest = async (upgrader, options) => {
1290
+ if (options.target !== ReleaseType.Latest) {
1291
+ return;
1292
+ }
1293
+ const npmPackage = upgrader.getNPMPackage();
1294
+ const target = upgrader.getTarget();
1295
+ const project = upgrader.getProject();
1296
+ const { strapiVersion: current } = project;
1297
+ const fTargetMajor = highlight(`v${target.major}`);
1298
+ const fCurrentMajor = highlight(`v${current.major}`);
1299
+ const fTarget = version(target);
1300
+ const fCurrent = version(current);
1301
+ const isMajorUpgrade = target.major > current.major;
1302
+ if (isMajorUpgrade) {
1303
+ options.logger.warn(
1304
+ `Detected a major upgrade for the "${highlight(ReleaseType.Latest)}" tag: ${fCurrent} > ${fTarget}`
1305
+ );
1306
+ const newerPackageRelease = npmPackage.findVersionsInRange(rangeFactory(`>${current.raw} <${target.major}`)).at(-1);
1307
+ if (newerPackageRelease) {
1308
+ const fLatest = version(semVerFactory(newerPackageRelease.version));
1309
+ options.logger.warn(
1310
+ `It's recommended to first upgrade to the latest version of ${fCurrentMajor} (${fLatest}) before upgrading to ${fTargetMajor}.`
1311
+ );
1312
+ }
1313
+ const proceedAnyway = await upgrader.confirm(`I know what I'm doing. Proceed anyway!`);
1314
+ if (!proceedAnyway) {
1315
+ throw new AbortedError();
1316
+ }
1317
+ }
1318
+ };
1026
1319
  const upgrade = async (options) => {
1027
1320
  const timer = timerFactory();
1028
1321
  const { logger, codemodsTarget } = options;
1029
1322
  const cwd = path$1.resolve(options.cwd ?? process.cwd());
1030
1323
  const project = projectFactory(cwd);
1324
+ logger.debug(projectDetails(project));
1325
+ if (!isApplicationProject(project)) {
1326
+ throw new Error(
1327
+ `The "${options.target}" upgrade can only be run on a Strapi project; for plugins, please use "codemods".`
1328
+ );
1329
+ }
1330
+ logger.debug(
1331
+ `Application: VERSION=${version(project.packageJSON.version)}; STRAPI_VERSION=${version(project.strapiVersion)}`
1332
+ );
1031
1333
  const npmPackage = npmPackageFactory(STRAPI_PACKAGE_NAME);
1032
1334
  await npmPackage.refresh();
1033
1335
  const upgrader = upgraderFactory(project, options.target, npmPackage).dry(options.dry ?? false).onConfirm(options.confirm ?? null).setLogger(logger);
1034
1336
  if (codemodsTarget !== void 0) {
1035
1337
  upgrader.overrideCodemodsTarget(codemodsTarget);
1036
1338
  }
1037
- if (options.target === ReleaseType.Major) {
1038
- upgrader.addRequirement(REQUIRE_AVAILABLE_NEXT_MAJOR).addRequirement(REQUIRE_LATEST_FOR_CURRENT_MAJOR);
1039
- }
1040
- upgrader.addRequirement(REQUIRE_GIT.asOptional());
1339
+ await runUpgradePrompts(upgrader, options);
1340
+ addUpgradeRequirements(upgrader, options);
1041
1341
  const upgradeReport = await upgrader.upgrade();
1042
1342
  if (!upgradeReport.success) {
1043
1343
  throw upgradeReport.error;
1044
1344
  }
1045
1345
  timer.stop();
1046
- logger.info(`Completed in ${durationMs(timer.elapsedMs)}`);
1346
+ logger.info(`Completed in ${durationMs(timer.elapsedMs)}ms`);
1047
1347
  };
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;
1348
+ const runUpgradePrompts = async (upgrader, options) => {
1349
+ if (options.target === ReleaseType.Latest) {
1350
+ await latest(upgrader, options);
1058
1351
  }
1059
- timer.stop();
1060
- logger.info(`Completed in ${timer.elapsedMs}`);
1061
1352
  };
1353
+ const addUpgradeRequirements = (upgrader, options) => {
1354
+ if (options.target === ReleaseType.Major) {
1355
+ upgrader.addRequirement(REQUIRE_AVAILABLE_NEXT_MAJOR).addRequirement(REQUIRE_LATEST_FOR_CURRENT_MAJOR);
1356
+ }
1357
+ upgrader.addRequirement(REQUIRE_GIT.asOptional());
1358
+ };
1359
+ const resolvePath = (cwd) => path$1.resolve(cwd ?? process.cwd());
1062
1360
  const getRangeFromTarget = (currentVersion, target) => {
1063
1361
  if (isSemverInstance(target)) {
1064
1362
  return rangeFactory(target);
1065
1363
  }
1066
1364
  const { major, minor, patch } = currentVersion;
1067
1365
  switch (target) {
1366
+ case ReleaseType.Latest:
1367
+ throw new Error("Can't use <latest> to create a codemods range: not implemented");
1068
1368
  case ReleaseType.Major:
1069
1369
  return rangeFactory(`${major}`);
1070
1370
  case ReleaseType.Minor:
@@ -1075,9 +1375,60 @@ const getRangeFromTarget = (currentVersion, target) => {
1075
1375
  throw new Error(`Invalid target set: ${target}`);
1076
1376
  }
1077
1377
  };
1378
+ const findRangeFromTarget = (project, target) => {
1379
+ if (isRangeInstance(target)) {
1380
+ return target;
1381
+ }
1382
+ if (isApplicationProject(project)) {
1383
+ return getRangeFromTarget(project.strapiVersion, target);
1384
+ }
1385
+ return rangeFactory("*");
1386
+ };
1387
+ const runCodemods = async (options) => {
1388
+ const timer = timerFactory();
1389
+ const { logger, uid } = options;
1390
+ const cwd = resolvePath(options.cwd);
1391
+ const project = projectFactory(cwd);
1392
+ const range = findRangeFromTarget(project, options.target);
1393
+ logger.debug(projectDetails(project));
1394
+ logger.debug(`Range: set to ${versionRange(range)}`);
1395
+ const codemodRunner = codemodRunnerFactory(project, range).dry(options.dry ?? false).onSelectCodemods(options.selectCodemods ?? null).setLogger(logger);
1396
+ let report;
1397
+ if (uid !== void 0) {
1398
+ logger.debug(`Running a single codemod: ${codemodUID(uid)}`);
1399
+ report = await codemodRunner.runByUID(uid);
1400
+ } else {
1401
+ report = await codemodRunner.run();
1402
+ }
1403
+ if (!report.success) {
1404
+ throw report.error;
1405
+ }
1406
+ timer.stop();
1407
+ logger.info(`Completed in ${timer.elapsedMs}`);
1408
+ };
1409
+ const listCodemods = async (options) => {
1410
+ const { logger, target } = options;
1411
+ const cwd = resolvePath(options.cwd);
1412
+ const project = projectFactory(cwd);
1413
+ const range = findRangeFromTarget(project, target);
1414
+ logger.debug(projectDetails(project));
1415
+ logger.debug(`Range: set to ${versionRange(range)}`);
1416
+ const repo = codemodRepositoryFactory();
1417
+ repo.refresh();
1418
+ const groups = repo.find({ range });
1419
+ const codemods = groups.flatMap((collection) => collection.codemods);
1420
+ logger.debug(`Found ${highlight(codemods.length)} codemods`);
1421
+ if (codemods.length === 0) {
1422
+ logger.info(`Found no codemods matching ${versionRange(range)}`);
1423
+ return;
1424
+ }
1425
+ const fCodemods = codemodList(codemods);
1426
+ logger.raw(fCodemods);
1427
+ };
1078
1428
  const index$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1079
1429
  __proto__: null,
1080
- codemods,
1430
+ listCodemods,
1431
+ runCodemods,
1081
1432
  upgrade
1082
1433
  }, Symbol.toStringTag, { value: "Module" }));
1083
1434
  class Logger {
@@ -1171,18 +1522,18 @@ const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
1171
1522
  }, Symbol.toStringTag, { value: "Module" }));
1172
1523
  const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1173
1524
  __proto__: null,
1174
- codemod: index$7,
1175
- codemodRepository: index$6,
1525
+ codemod: index$8,
1526
+ codemodRepository: index$7,
1176
1527
  error: index$9,
1177
- f: index$8,
1528
+ f: index$f,
1178
1529
  fileScanner: index$d,
1179
1530
  logger: index$3,
1180
1531
  project: index$a,
1181
1532
  report: index$2,
1182
- requirement: index$g,
1533
+ requirement: index$5,
1183
1534
  runner: index$1,
1184
- timer: index$f,
1185
- upgrader: index$5,
1535
+ timer: index$g,
1536
+ upgrader: index$6,
1186
1537
  version: index$e
1187
1538
  }, Symbol.toStringTag, { value: "Module" }));
1188
1539
  export {