@strapi/upgrade 0.0.0-experimental.edc24aaa3bb5a90fa5fd4fee208167dd4e2e38d4 → 0.0.0-experimental.ee7402bacc4656d268ab76aa9c334a7b7a951201

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 (41) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +439 -317
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +462 -343
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +458 -340
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/modules/error/utils.d.ts +8 -0
  9. package/dist/modules/error/utils.d.ts.map +1 -1
  10. package/dist/modules/file-scanner/scanner.d.ts.map +1 -1
  11. package/dist/modules/format/formats.d.ts +2 -1
  12. package/dist/modules/format/formats.d.ts.map +1 -1
  13. package/dist/modules/project/constants.d.ts +6 -5
  14. package/dist/modules/project/constants.d.ts.map +1 -1
  15. package/dist/modules/project/project.d.ts +16 -2
  16. package/dist/modules/project/project.d.ts.map +1 -1
  17. package/dist/modules/project/types.d.ts +3 -0
  18. package/dist/modules/project/types.d.ts.map +1 -1
  19. package/dist/modules/upgrader/types.d.ts +6 -0
  20. package/dist/modules/upgrader/types.d.ts.map +1 -1
  21. package/dist/modules/upgrader/upgrader.d.ts +4 -0
  22. package/dist/modules/upgrader/upgrader.d.ts.map +1 -1
  23. package/dist/modules/version/range.d.ts.map +1 -1
  24. package/dist/modules/version/types.d.ts +2 -1
  25. package/dist/modules/version/types.d.ts.map +1 -1
  26. package/dist/tasks/codemods/utils.d.ts.map +1 -1
  27. package/dist/tasks/upgrade/prompts/index.d.ts +2 -0
  28. package/dist/tasks/upgrade/prompts/index.d.ts.map +1 -0
  29. package/dist/tasks/upgrade/prompts/latest.d.ts +9 -0
  30. package/dist/tasks/upgrade/prompts/latest.d.ts.map +1 -0
  31. package/dist/tasks/upgrade/requirements/major.d.ts.map +1 -1
  32. package/dist/tasks/upgrade/upgrade.d.ts.map +1 -1
  33. package/package.json +8 -8
  34. package/resources/codemods/5.0.0/comment-out-lifecycle-files.code.ts +63 -0
  35. package/resources/codemods/5.0.0/dependency-upgrade-react-and-react-dom.json.ts +67 -0
  36. package/resources/codemods/5.0.0/dependency-upgrade-styled-components.json.ts +49 -0
  37. package/resources/codemods/5.0.0/deprecate-helper-plugin.code.ts +192 -0
  38. package/resources/codemods/5.0.0/sqlite3-to-better-sqlite3.json.ts +0 -1
  39. package/resources/codemods/5.1.0/dependency-better-sqlite3.json.ts +48 -0
  40. package/resources/utils/change-import.ts +118 -0
  41. package/resources/utils/replace-jsx.ts +49 -0
package/dist/index.js CHANGED
@@ -1,138 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const path$1 = require("node:path");
4
- const simpleGit = require("simple-git");
4
+ const CliTable3 = require("cli-table3");
5
5
  const chalk = require("chalk");
6
+ const assert = require("node:assert");
6
7
  const semver = require("semver");
7
- const utils = require("@strapi/utils");
8
- const fp = require("lodash/fp");
9
8
  const fse = require("fs-extra");
10
- const assert = require("node:assert");
11
- const glob = require("glob");
9
+ const fastglob = require("fast-glob");
12
10
  const Runner = require("jscodeshift/src/Runner");
11
+ const fp = require("lodash/fp");
13
12
  const node = require("esbuild-register/dist/node");
14
- const CliTable3 = require("cli-table3");
13
+ const utils = require("@strapi/utils");
14
+ const simpleGit = require("simple-git");
15
15
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
16
16
  const path__default = /* @__PURE__ */ _interopDefault(path$1);
17
- const simpleGit__default = /* @__PURE__ */ _interopDefault(simpleGit);
17
+ const CliTable3__default = /* @__PURE__ */ _interopDefault(CliTable3);
18
18
  const chalk__default = /* @__PURE__ */ _interopDefault(chalk);
19
+ const assert__default = /* @__PURE__ */ _interopDefault(assert);
19
20
  const semver__default = /* @__PURE__ */ _interopDefault(semver);
20
21
  const fse__default = /* @__PURE__ */ _interopDefault(fse);
21
- const assert__default = /* @__PURE__ */ _interopDefault(assert);
22
- const CliTable3__default = /* @__PURE__ */ _interopDefault(CliTable3);
23
- class Requirement {
24
- isRequired;
25
- name;
26
- testCallback;
27
- children;
28
- constructor(name, testCallback, isRequired) {
29
- this.name = name;
30
- this.testCallback = testCallback;
31
- this.isRequired = isRequired ?? true;
32
- this.children = [];
33
- }
34
- setChildren(children) {
35
- this.children = children;
36
- return this;
37
- }
38
- addChild(child) {
39
- this.children.push(child);
40
- return this;
41
- }
42
- asOptional() {
43
- const newInstance = requirementFactory(this.name, this.testCallback, false);
44
- newInstance.setChildren(this.children);
45
- return newInstance;
46
- }
47
- asRequired() {
48
- const newInstance = requirementFactory(this.name, this.testCallback, true);
49
- newInstance.setChildren(this.children);
50
- return newInstance;
51
- }
52
- async test(context) {
53
- try {
54
- await this.testCallback?.(context);
55
- return ok();
56
- } catch (e) {
57
- if (e instanceof Error) {
58
- return errored(e);
59
- }
60
- if (typeof e === "string") {
61
- return errored(new Error(e));
62
- }
63
- return errored(new Error("Unknown error"));
64
- }
65
- }
66
- }
67
- const ok = () => ({ pass: true, error: null });
68
- const errored = (error) => ({ pass: false, error });
69
- const requirementFactory = (name, testCallback, isRequired) => new Requirement(name, testCallback, isRequired);
70
- const index$g = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
71
- __proto__: null,
72
- requirementFactory
73
- }, Symbol.toStringTag, { value: "Module" }));
74
- const REQUIRE_AVAILABLE_NEXT_MAJOR = requirementFactory(
75
- "REQUIRE_AVAILABLE_NEXT_MAJOR",
76
- (context) => {
77
- const { project, target } = context;
78
- const currentMajor = project.strapiVersion.major;
79
- const targetedMajor = target.major;
80
- if (targetedMajor === currentMajor) {
81
- throw new Error(`You're already on the latest major version (v${currentMajor})`);
82
- }
83
- }
84
- );
85
- const REQUIRE_LATEST_FOR_CURRENT_MAJOR = requirementFactory(
86
- "REQUIRE_LATEST_FOR_CURRENT_MAJOR",
87
- (context) => {
88
- const { project, target, npmVersionsMatches } = context;
89
- if (npmVersionsMatches.length !== 1) {
90
- const invalidVersions = npmVersionsMatches.slice(0, -1);
91
- const invalidVersionsAsSemVer = invalidVersions.map((v) => v.version);
92
- const nbInvalidVersions = npmVersionsMatches.length;
93
- const currentMajor = project.strapiVersion.major;
94
- throw new Error(
95
- `Doing a major upgrade requires to be on the latest v${currentMajor} version, but found ${nbInvalidVersions} versions between the current one and ${target}: ${invalidVersionsAsSemVer}`
96
- );
97
- }
98
- }
99
- );
100
- const REQUIRE_GIT_CLEAN_REPOSITORY = requirementFactory(
101
- "REQUIRE_GIT_CLEAN_REPOSITORY",
102
- async (context) => {
103
- const git = simpleGit__default.default({ baseDir: context.project.cwd });
104
- const status = await git.status();
105
- if (!status.isClean()) {
106
- throw new Error(
107
- "Repository is not clean. Please commit or stash any changes before upgrading"
108
- );
109
- }
110
- }
111
- );
112
- const REQUIRE_GIT_REPOSITORY = requirementFactory(
113
- "REQUIRE_GIT_REPOSITORY",
114
- async (context) => {
115
- const git = simpleGit__default.default({ baseDir: context.project.cwd });
116
- const isRepo = await git.checkIsRepo();
117
- if (!isRepo) {
118
- throw new Error("Not a git repository (or any of the parent directories)");
119
- }
120
- }
121
- ).addChild(REQUIRE_GIT_CLEAN_REPOSITORY.asOptional());
122
- const REQUIRE_GIT_INSTALLED = requirementFactory(
123
- "REQUIRE_GIT_INSTALLED",
124
- async (context) => {
125
- const git = simpleGit__default.default({ baseDir: context.project.cwd });
126
- try {
127
- await git.version();
128
- } catch {
129
- throw new Error("Git is not installed");
130
- }
131
- }
132
- ).addChild(REQUIRE_GIT_REPOSITORY.asOptional());
133
- const REQUIRE_GIT = requirementFactory("REQUIRE_GIT", null).addChild(
134
- REQUIRE_GIT_INSTALLED.asOptional()
135
- );
22
+ const fastglob__default = /* @__PURE__ */ _interopDefault(fastglob);
23
+ const simpleGit__default = /* @__PURE__ */ _interopDefault(simpleGit);
136
24
  class Timer {
137
25
  interval;
138
26
  constructor() {
@@ -163,55 +51,101 @@ const constants$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP
163
51
  __proto__: null,
164
52
  ONE_SECOND_MS
165
53
  }, Symbol.toStringTag, { value: "Module" }));
166
- const index$f = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
54
+ const index$g = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
167
55
  __proto__: null,
168
56
  constants: constants$4,
169
57
  timerFactory
170
58
  }, Symbol.toStringTag, { value: "Module" }));
171
- class JSONTransformAPI {
172
- json;
173
- constructor(json) {
174
- this.json = fp.cloneDeep(json);
175
- }
176
- get(path2, defaultValue) {
177
- if (!path2) {
178
- return this.root();
179
- }
180
- return fp.cloneDeep(fp.get(path2, this.json) ?? defaultValue);
181
- }
182
- has(path2) {
183
- return fp.has(path2, this.json);
184
- }
185
- merge(other) {
186
- this.json = fp.merge(other, this.json);
187
- return this;
188
- }
189
- root() {
190
- return fp.cloneDeep(this.json);
191
- }
192
- set(path2, value) {
193
- this.json = fp.set(path2, value, this.json);
194
- return this;
195
- }
196
- remove(path2) {
197
- this.json = fp.omit(path2, this.json);
198
- return this;
199
- }
200
- }
201
- const createJSONTransformAPI = (object) => new JSONTransformAPI(object);
202
- const readJSON = async (path2) => {
203
- const buffer = await fse__default.default.readFile(path2);
204
- return JSON.parse(buffer.toString());
59
+ const path = (path2) => chalk__default.default.blue(path2);
60
+ const version = (version2) => {
61
+ return chalk__default.default.italic.yellow(`v${version2}`);
205
62
  };
206
- const saveJSON = async (path2, json) => {
207
- const jsonAsString = `${JSON.stringify(json, null, 2)}
208
- `;
209
- await fse__default.default.writeFile(path2, jsonAsString);
63
+ const codemodUID = (uid) => {
64
+ return chalk__default.default.bold.cyan(uid);
210
65
  };
66
+ const projectDetails = (project) => {
67
+ return `Project: TYPE=${projectType(project.type)}; CWD=${path(project.cwd)}; PATHS=${project.paths.map(path)}`;
68
+ };
69
+ const projectType = (type) => chalk__default.default.cyan(type);
70
+ const versionRange = (range) => chalk__default.default.italic.yellow(range.raw);
71
+ const transform = (transformFilePath) => chalk__default.default.cyan(transformFilePath);
72
+ const highlight = (arg) => chalk__default.default.bold.underline(arg);
73
+ const upgradeStep = (text, step) => {
74
+ return chalk__default.default.bold(`(${step[0]}/${step[1]}) ${text}...`);
75
+ };
76
+ const reports = (reports2) => {
77
+ const rows = reports2.map(({ codemod, report }, i) => {
78
+ const fIndex = chalk__default.default.grey(i);
79
+ const fVersion = chalk__default.default.magenta(codemod.version);
80
+ const fKind = chalk__default.default.yellow(codemod.kind);
81
+ const fFormattedTransformPath = chalk__default.default.cyan(codemod.format());
82
+ const fTimeElapsed = i === 0 ? `${report.timeElapsed}s ${chalk__default.default.dim.italic("(cold start)")}` : `${report.timeElapsed}s`;
83
+ const fAffected = report.ok > 0 ? chalk__default.default.green(report.ok) : chalk__default.default.grey(0);
84
+ const fUnchanged = report.ok === 0 ? chalk__default.default.red(report.nochange) : chalk__default.default.grey(report.nochange);
85
+ return [fIndex, fVersion, fKind, fFormattedTransformPath, fAffected, fUnchanged, fTimeElapsed];
86
+ });
87
+ const table = new CliTable3__default.default({
88
+ style: { compact: true },
89
+ head: [
90
+ chalk__default.default.bold.grey("N°"),
91
+ chalk__default.default.bold.magenta("Version"),
92
+ chalk__default.default.bold.yellow("Kind"),
93
+ chalk__default.default.bold.cyan("Name"),
94
+ chalk__default.default.bold.green("Affected"),
95
+ chalk__default.default.bold.red("Unchanged"),
96
+ chalk__default.default.bold.blue("Duration")
97
+ ]
98
+ });
99
+ table.push(...rows);
100
+ return table.toString();
101
+ };
102
+ const codemodList = (codemods) => {
103
+ const rows = codemods.map((codemod, index2) => {
104
+ const fIndex = chalk__default.default.grey(index2);
105
+ const fVersion = chalk__default.default.magenta(codemod.version);
106
+ const fKind = chalk__default.default.yellow(codemod.kind);
107
+ const fName = chalk__default.default.blue(codemod.format());
108
+ const fUID = codemodUID(codemod.uid);
109
+ return [fIndex, fVersion, fKind, fName, fUID];
110
+ });
111
+ const table = new CliTable3__default.default({
112
+ style: { compact: true },
113
+ head: [
114
+ chalk__default.default.bold.grey("N°"),
115
+ chalk__default.default.bold.magenta("Version"),
116
+ chalk__default.default.bold.yellow("Kind"),
117
+ chalk__default.default.bold.blue("Name"),
118
+ chalk__default.default.bold.cyan("UID")
119
+ ]
120
+ });
121
+ table.push(...rows);
122
+ return table.toString();
123
+ };
124
+ const durationMs = (elapsedMs) => {
125
+ const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
126
+ return `${elapsedSeconds}s`;
127
+ };
128
+ const index$f = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
129
+ __proto__: null,
130
+ codemodList,
131
+ codemodUID,
132
+ durationMs,
133
+ highlight,
134
+ path,
135
+ projectDetails,
136
+ projectType,
137
+ reports,
138
+ transform,
139
+ upgradeStep,
140
+ version,
141
+ versionRange
142
+ }, Symbol.toStringTag, { value: "Module" }));
143
+ const NPM_REGISTRY_URL = "https://registry.npmjs.org";
211
144
  var ReleaseType = /* @__PURE__ */ ((ReleaseType2) => {
212
145
  ReleaseType2["Major"] = "major";
213
146
  ReleaseType2["Minor"] = "minor";
214
147
  ReleaseType2["Patch"] = "patch";
148
+ ReleaseType2["Latest"] = "latest";
215
149
  return ReleaseType2;
216
150
  })(ReleaseType || {});
217
151
  const types = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
@@ -237,17 +171,20 @@ const rangeFactory = (range) => {
237
171
  };
238
172
  const rangeFromReleaseType = (current, identifier) => {
239
173
  switch (identifier) {
240
- case ReleaseType.Major: {
241
- const nextMajor = semver__default.default.inc(current, "major");
242
- return rangeFactory(`>${current.raw} <=${nextMajor}`);
174
+ case ReleaseType.Latest: {
175
+ return rangeFactory(`>${current.raw}`);
243
176
  }
244
- case ReleaseType.Patch: {
245
- const minor = semver__default.default.inc(current, "minor");
246
- return rangeFactory(`>${current.raw} <${minor}`);
177
+ case ReleaseType.Major: {
178
+ const nextMajor = semVerFactory(current.raw).inc("major");
179
+ return rangeFactory(`>${current.raw} <=${nextMajor.major}`);
247
180
  }
248
181
  case ReleaseType.Minor: {
249
- const major = semver__default.default.inc(current, "major");
250
- return rangeFactory(`>${current.raw} <${major}`);
182
+ const nextMajor = semVerFactory(current.raw).inc("major");
183
+ return rangeFactory(`>${current.raw} <${nextMajor.raw}`);
184
+ }
185
+ case ReleaseType.Patch: {
186
+ const nextMinor = semVerFactory(current.raw).inc("minor");
187
+ return rangeFactory(`>${current.raw} <${nextMinor.raw}`);
251
188
  }
252
189
  default: {
253
190
  throw new Error("Not implemented");
@@ -281,13 +218,57 @@ const index$e = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
281
218
  rangeFromVersions,
282
219
  semVerFactory
283
220
  }, Symbol.toStringTag, { value: "Module" }));
221
+ class Package {
222
+ name;
223
+ packageURL;
224
+ npmPackage;
225
+ constructor(name) {
226
+ this.name = name;
227
+ this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
228
+ this.npmPackage = null;
229
+ }
230
+ get isLoaded() {
231
+ return this.npmPackage !== null;
232
+ }
233
+ assertPackageIsLoaded(npmPackage) {
234
+ assert__default.default(this.isLoaded, "The package is not loaded yet");
235
+ }
236
+ getVersionsDict() {
237
+ this.assertPackageIsLoaded(this.npmPackage);
238
+ return this.npmPackage.versions;
239
+ }
240
+ getVersionsAsList() {
241
+ this.assertPackageIsLoaded(this.npmPackage);
242
+ return Object.values(this.npmPackage.versions);
243
+ }
244
+ findVersionsInRange(range) {
245
+ const versions = this.getVersionsAsList();
246
+ return versions.filter((v) => range.test(v.version)).filter((v) => isLiteralSemVer(v.version)).sort((v1, v2) => semver__default.default.compare(v1.version, v2.version));
247
+ }
248
+ findVersion(version2) {
249
+ const versions = this.getVersionsAsList();
250
+ return versions.find((npmVersion) => semver__default.default.eq(npmVersion.version, version2));
251
+ }
252
+ async refresh() {
253
+ const response = await fetch(this.packageURL);
254
+ assert__default.default(response.ok, `Request failed for ${this.packageURL}`);
255
+ this.npmPackage = await response.json();
256
+ return this;
257
+ }
258
+ versionExists(version2) {
259
+ return this.findVersion(version2) !== void 0;
260
+ }
261
+ }
262
+ const npmPackageFactory = (name) => new Package(name);
284
263
  class FileScanner {
285
264
  cwd;
286
265
  constructor(cwd) {
287
266
  this.cwd = cwd;
288
267
  }
289
268
  scan(patterns) {
290
- const filenames = glob.glob.sync(patterns, { cwd: this.cwd });
269
+ const filenames = fastglob__default.default.sync(patterns, {
270
+ cwd: this.cwd
271
+ });
291
272
  return filenames.map((filename) => path__default.default.join(this.cwd, filename));
292
273
  }
293
274
  }
@@ -325,6 +306,46 @@ const index$c = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
325
306
  __proto__: null,
326
307
  codeRunnerFactory
327
308
  }, Symbol.toStringTag, { value: "Module" }));
309
+ class JSONTransformAPI {
310
+ json;
311
+ constructor(json) {
312
+ this.json = fp.cloneDeep(json);
313
+ }
314
+ get(path2, defaultValue) {
315
+ if (!path2) {
316
+ return this.root();
317
+ }
318
+ return fp.cloneDeep(fp.get(path2, this.json) ?? defaultValue);
319
+ }
320
+ has(path2) {
321
+ return fp.has(path2, this.json);
322
+ }
323
+ merge(other) {
324
+ this.json = fp.merge(other, this.json);
325
+ return this;
326
+ }
327
+ root() {
328
+ return fp.cloneDeep(this.json);
329
+ }
330
+ set(path2, value) {
331
+ this.json = fp.set(path2, value, this.json);
332
+ return this;
333
+ }
334
+ remove(path2) {
335
+ this.json = fp.omit(path2, this.json);
336
+ return this;
337
+ }
338
+ }
339
+ const createJSONTransformAPI = (object) => new JSONTransformAPI(object);
340
+ const readJSON = async (path2) => {
341
+ const buffer = await fse__default.default.readFile(path2);
342
+ return JSON.parse(buffer.toString());
343
+ };
344
+ const saveJSON = async (path2, json) => {
345
+ const jsonAsString = `${JSON.stringify(json, null, 2)}
346
+ `;
347
+ await fse__default.default.writeFile(path2, jsonAsString);
348
+ };
328
349
  const transformJSON = async (codemodPath, paths, config) => {
329
350
  const { dry } = config;
330
351
  const startTime = process.hrtime();
@@ -385,8 +406,10 @@ const index$b = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
385
406
  jsonRunnerFactory
386
407
  }, Symbol.toStringTag, { value: "Module" }));
387
408
  const PROJECT_PACKAGE_JSON = "package.json";
388
- const PROJECT_DEFAULT_ALLOWED_ROOT_PATHS = ["src", "config", "public"];
389
- const PROJECT_DEFAULT_CODE_EXTENSIONS = [
409
+ const PROJECT_APP_ALLOWED_ROOT_PATHS = ["src", "config", "public"];
410
+ const PROJECT_PLUGIN_ALLOWED_ROOT_PATHS = ["admin", "server"];
411
+ const PROJECT_PLUGIN_ROOT_FILES = ["strapi-admin.js", "strapi-server.js"];
412
+ const PROJECT_CODE_EXTENSIONS = [
390
413
  // Source files
391
414
  "js",
392
415
  "mjs",
@@ -395,22 +418,19 @@ const PROJECT_DEFAULT_CODE_EXTENSIONS = [
395
418
  "jsx",
396
419
  "tsx"
397
420
  ];
398
- const PROJECT_DEFAULT_JSON_EXTENSIONS = ["json"];
399
- const PROJECT_DEFAULT_ALLOWED_EXTENSIONS = [
400
- ...PROJECT_DEFAULT_CODE_EXTENSIONS,
401
- ...PROJECT_DEFAULT_JSON_EXTENSIONS
402
- ];
403
- const PROJECT_DEFAULT_PATTERNS = ["package.json"];
421
+ const PROJECT_JSON_EXTENSIONS = ["json"];
422
+ const PROJECT_ALLOWED_EXTENSIONS = [...PROJECT_CODE_EXTENSIONS, ...PROJECT_JSON_EXTENSIONS];
404
423
  const SCOPED_STRAPI_PACKAGE_PREFIX = "@strapi/";
405
424
  const STRAPI_DEPENDENCY_NAME = `${SCOPED_STRAPI_PACKAGE_PREFIX}strapi`;
406
425
  const constants$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
407
426
  __proto__: null,
408
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS,
409
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS,
410
- PROJECT_DEFAULT_CODE_EXTENSIONS,
411
- PROJECT_DEFAULT_JSON_EXTENSIONS,
412
- PROJECT_DEFAULT_PATTERNS,
427
+ PROJECT_ALLOWED_EXTENSIONS,
428
+ PROJECT_APP_ALLOWED_ROOT_PATHS,
429
+ PROJECT_CODE_EXTENSIONS,
430
+ PROJECT_JSON_EXTENSIONS,
413
431
  PROJECT_PACKAGE_JSON,
432
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS,
433
+ PROJECT_PLUGIN_ROOT_FILES,
414
434
  SCOPED_STRAPI_PACKAGE_PREFIX,
415
435
  STRAPI_DEPENDENCY_NAME
416
436
  }, Symbol.toStringTag, { value: "Module" }));
@@ -420,11 +440,13 @@ class Project {
420
440
  files;
421
441
  packageJSONPath;
422
442
  packageJSON;
423
- constructor(cwd) {
443
+ paths;
444
+ constructor(cwd, config) {
424
445
  if (!fse__default.default.pathExistsSync(cwd)) {
425
446
  throw new Error(`ENOENT: no such file or directory, access '${cwd}'`);
426
447
  }
427
448
  this.cwd = cwd;
449
+ this.paths = config.paths;
428
450
  this.refresh();
429
451
  }
430
452
  getFilesByExtensions(extensions) {
@@ -452,12 +474,8 @@ class Project {
452
474
  return reports2;
453
475
  }
454
476
  createProjectCodemodsRunners(dry = false) {
455
- const jsonExtensions = PROJECT_DEFAULT_JSON_EXTENSIONS.map(
456
- (ext) => `.${ext}`
457
- );
458
- const codeExtensions = PROJECT_DEFAULT_CODE_EXTENSIONS.map(
459
- (ext) => `.${ext}`
460
- );
477
+ const jsonExtensions = PROJECT_JSON_EXTENSIONS.map((ext) => `.${ext}`);
478
+ const codeExtensions = PROJECT_CODE_EXTENSIONS.map((ext) => `.${ext}`);
461
479
  const jsonFiles = this.getFilesByExtensions(jsonExtensions);
462
480
  const codeFiles = this.getFilesByExtensions(codeExtensions);
463
481
  const codeRunner = codeRunnerFactory(codeFiles, {
@@ -465,7 +483,7 @@ class Project {
465
483
  parser: "ts",
466
484
  runInBand: true,
467
485
  babel: true,
468
- extensions: PROJECT_DEFAULT_CODE_EXTENSIONS.join(","),
486
+ extensions: PROJECT_CODE_EXTENSIONS.join(","),
469
487
  // Don't output any log coming from the runner
470
488
  print: false,
471
489
  silent: true,
@@ -486,23 +504,32 @@ class Project {
486
504
  this.packageJSON = JSON.parse(packageJSONBuffer.toString());
487
505
  }
488
506
  refreshProjectFiles() {
489
- const allowedRootPaths = formatGlobCollectionPattern(
490
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS
491
- );
492
- const allowedExtensions = formatGlobCollectionPattern(
493
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS
494
- );
495
- const projectFilesPattern = `./${allowedRootPaths}/**/*.${allowedExtensions}`;
496
- const patterns = [projectFilesPattern, ...PROJECT_DEFAULT_PATTERNS];
497
507
  const scanner = fileScannerFactory(this.cwd);
498
- this.files = scanner.scan(patterns);
508
+ this.files = scanner.scan(this.paths);
499
509
  }
500
510
  }
501
511
  class AppProject extends Project {
502
512
  strapiVersion;
503
513
  type = "application";
514
+ /**
515
+ * Returns an array of allowed file paths for a Strapi application
516
+ *
517
+ * The resulting paths include app default files and the root package.json file.
518
+ */
519
+ static get paths() {
520
+ const allowedRootPaths = formatGlobCollectionPattern(PROJECT_APP_ALLOWED_ROOT_PATHS);
521
+ const allowedExtensions = formatGlobCollectionPattern(PROJECT_ALLOWED_EXTENSIONS);
522
+ return [
523
+ // App default files
524
+ `./${allowedRootPaths}/**/*.${allowedExtensions}`,
525
+ `!./**/node_modules/**/*`,
526
+ `!./**/dist/**/*`,
527
+ // Root package.json file
528
+ PROJECT_PACKAGE_JSON
529
+ ];
530
+ }
504
531
  constructor(cwd) {
505
- super(cwd);
532
+ super(cwd, { paths: AppProject.paths });
506
533
  this.refreshStrapiVersion();
507
534
  }
508
535
  refresh() {
@@ -557,6 +584,30 @@ const formatGlobCollectionPattern = (collection) => {
557
584
  };
558
585
  class PluginProject extends Project {
559
586
  type = "plugin";
587
+ /**
588
+ * Returns an array of allowed file paths for a Strapi plugin
589
+ *
590
+ * The resulting paths include plugin default files, the root package.json file, and plugin-specific files.
591
+ */
592
+ static get paths() {
593
+ const allowedRootPaths = formatGlobCollectionPattern(
594
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS
595
+ );
596
+ const allowedExtensions = formatGlobCollectionPattern(PROJECT_ALLOWED_EXTENSIONS);
597
+ return [
598
+ // Plugin default files
599
+ `./${allowedRootPaths}/**/*.${allowedExtensions}`,
600
+ `!./**/node_modules/**/*`,
601
+ `!./**/dist/**/*`,
602
+ // Root package.json file
603
+ PROJECT_PACKAGE_JSON,
604
+ // Plugin root files
605
+ ...PROJECT_PLUGIN_ROOT_FILES
606
+ ];
607
+ }
608
+ constructor(cwd) {
609
+ super(cwd, { paths: PluginProject.paths });
610
+ }
560
611
  }
561
612
  const isPlugin = (cwd) => {
562
613
  const packageJSONPath = path__default.default.join(cwd, PROJECT_PACKAGE_JSON);
@@ -571,10 +622,7 @@ const isPlugin = (cwd) => {
571
622
  };
572
623
  const projectFactory = (cwd) => {
573
624
  fse__default.default.accessSync(cwd);
574
- if (isPlugin(cwd)) {
575
- return new PluginProject(cwd);
576
- }
577
- return new AppProject(cwd);
625
+ return isPlugin(cwd) ? new PluginProject(cwd) : new AppProject(cwd);
578
626
  };
579
627
  const isPluginProject = (project) => {
580
628
  return project instanceof PluginProject;
@@ -606,6 +654,18 @@ class UnexpectedError extends Error {
606
654
  super("Unexpected Error");
607
655
  }
608
656
  }
657
+ class NPMCandidateNotFoundError extends Error {
658
+ target;
659
+ constructor(target, message = `Couldn't find a valid NPM candidate for "${target}"`) {
660
+ super(message);
661
+ this.target = target;
662
+ }
663
+ }
664
+ class AbortedError extends Error {
665
+ constructor(message = "Upgrade aborted") {
666
+ super(message);
667
+ }
668
+ }
609
669
  const unknownToError = (e) => {
610
670
  if (e instanceof Error) {
611
671
  return e;
@@ -617,89 +677,11 @@ const unknownToError = (e) => {
617
677
  };
618
678
  const index$9 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
619
679
  __proto__: null,
680
+ AbortedError,
681
+ NPMCandidateNotFoundError,
620
682
  UnexpectedError,
621
683
  unknownToError
622
684
  }, Symbol.toStringTag, { value: "Module" }));
623
- const path = (path2) => chalk__default.default.blue(path2);
624
- const version = (version2) => {
625
- return chalk__default.default.italic.yellow(`v${version2}`);
626
- };
627
- const codemodUID = (uid) => {
628
- return chalk__default.default.bold.cyan(uid);
629
- };
630
- const projectType = (type) => chalk__default.default.cyan(type);
631
- const versionRange = (range) => chalk__default.default.italic.yellow(range.raw);
632
- const transform = (transformFilePath) => chalk__default.default.cyan(transformFilePath);
633
- const highlight = (arg) => chalk__default.default.bold.underline(arg);
634
- const upgradeStep = (text, step) => {
635
- return chalk__default.default.bold(`(${step[0]}/${step[1]}) ${text}...`);
636
- };
637
- const reports = (reports2) => {
638
- const rows = reports2.map(({ codemod, report }, i) => {
639
- const fIndex = chalk__default.default.grey(i);
640
- const fVersion = chalk__default.default.magenta(codemod.version);
641
- const fKind = chalk__default.default.yellow(codemod.kind);
642
- const fFormattedTransformPath = chalk__default.default.cyan(codemod.format());
643
- const fTimeElapsed = i === 0 ? `${report.timeElapsed}s ${chalk__default.default.dim.italic("(cold start)")}` : `${report.timeElapsed}s`;
644
- const fAffected = report.ok > 0 ? chalk__default.default.green(report.ok) : chalk__default.default.grey(0);
645
- const fUnchanged = report.ok === 0 ? chalk__default.default.red(report.nochange) : chalk__default.default.grey(report.nochange);
646
- return [fIndex, fVersion, fKind, fFormattedTransformPath, fAffected, fUnchanged, fTimeElapsed];
647
- });
648
- const table = new CliTable3__default.default({
649
- style: { compact: true },
650
- head: [
651
- chalk__default.default.bold.grey("N°"),
652
- chalk__default.default.bold.magenta("Version"),
653
- chalk__default.default.bold.yellow("Kind"),
654
- chalk__default.default.bold.cyan("Name"),
655
- chalk__default.default.bold.green("Affected"),
656
- chalk__default.default.bold.red("Unchanged"),
657
- chalk__default.default.bold.blue("Duration")
658
- ]
659
- });
660
- table.push(...rows);
661
- return table.toString();
662
- };
663
- const codemodList = (codemods) => {
664
- const rows = codemods.map((codemod, index2) => {
665
- const fIndex = chalk__default.default.grey(index2);
666
- const fVersion = chalk__default.default.magenta(codemod.version);
667
- const fKind = chalk__default.default.yellow(codemod.kind);
668
- const fName = chalk__default.default.blue(codemod.format());
669
- const fUID = codemodUID(codemod.uid);
670
- return [fIndex, fVersion, fKind, fName, fUID];
671
- });
672
- const table = new CliTable3__default.default({
673
- style: { compact: true },
674
- head: [
675
- chalk__default.default.bold.grey("N°"),
676
- chalk__default.default.bold.magenta("Version"),
677
- chalk__default.default.bold.yellow("Kind"),
678
- chalk__default.default.bold.blue("Name"),
679
- chalk__default.default.bold.cyan("UID")
680
- ]
681
- });
682
- table.push(...rows);
683
- return table.toString();
684
- };
685
- const durationMs = (elapsedMs) => {
686
- const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
687
- return `${elapsedSeconds}s`;
688
- };
689
- const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
690
- __proto__: null,
691
- codemodList,
692
- codemodUID,
693
- durationMs,
694
- highlight,
695
- path,
696
- projectType,
697
- reports,
698
- transform,
699
- upgradeStep,
700
- version,
701
- versionRange
702
- }, Symbol.toStringTag, { value: "Module" }));
703
685
  const CODEMOD_CODE_SUFFIX = "code";
704
686
  const CODEMOD_JSON_SUFFIX = "json";
705
687
  const CODEMOD_ALLOWED_SUFFIXES = [CODEMOD_CODE_SUFFIX, CODEMOD_JSON_SUFFIX];
@@ -752,7 +734,7 @@ class Codemod {
752
734
  }
753
735
  }
754
736
  const codemodFactory = (options) => new Codemod(options);
755
- const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
737
+ const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
756
738
  __proto__: null,
757
739
  codemodFactory,
758
740
  constants: constants$2
@@ -864,7 +846,7 @@ const parseCodemodKindFromFilename = (filename) => {
864
846
  const codemodRepositoryFactory = (cwd = INTERNAL_CODEMODS_DIRECTORY) => {
865
847
  return new CodemodRepository(cwd);
866
848
  };
867
- const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
849
+ const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
868
850
  __proto__: null,
869
851
  codemodRepositoryFactory,
870
852
  constants: constants$1
@@ -977,6 +959,15 @@ class Upgrader {
977
959
  this.logger = null;
978
960
  this.confirmationCallback = null;
979
961
  }
962
+ getNPMPackage() {
963
+ return this.npmPackage;
964
+ }
965
+ getProject() {
966
+ return this.project;
967
+ }
968
+ getTarget() {
969
+ return semVerFactory(this.target.raw);
970
+ }
980
971
  setRequirements(requirements) {
981
972
  this.requirements = requirements;
982
973
  return this;
@@ -1058,6 +1049,12 @@ class Upgrader {
1058
1049
  }
1059
1050
  return successReport();
1060
1051
  }
1052
+ async confirm(message) {
1053
+ if (typeof this.confirmationCallback !== "function") {
1054
+ return true;
1055
+ }
1056
+ return this.confirmationCallback(message);
1057
+ }
1061
1058
  async checkRequirements(requirements, context) {
1062
1059
  for (const requirement of requirements) {
1063
1060
  const { pass, error } = await requirement.test(context);
@@ -1129,7 +1126,7 @@ class Upgrader {
1129
1126
  const packageManagerName = await utils.packageManager.getPreferred(projectPath);
1130
1127
  this.logger?.debug?.(`Using ${highlight(packageManagerName)} as package manager`);
1131
1128
  if (this.isDry) {
1132
- this.logger?.debug?.(`Skipping dependencies installation (${chalk__default.default.italic("dry mode")}`);
1129
+ this.logger?.debug?.(`Skipping dependencies installation (${chalk__default.default.italic("dry mode")})`);
1133
1130
  return;
1134
1131
  }
1135
1132
  await utils.packageManager.installDependencies(projectPath, packageManagerName, {
@@ -1148,23 +1145,28 @@ class Upgrader {
1148
1145
  }
1149
1146
  const resolveNPMTarget = (project, target, npmPackage) => {
1150
1147
  if (isSemverInstance(target)) {
1151
- return npmPackage.findVersion(target);
1148
+ const version2 = npmPackage.findVersion(target);
1149
+ if (!version2) {
1150
+ throw new NPMCandidateNotFoundError(target);
1151
+ }
1152
+ return version2;
1152
1153
  }
1153
1154
  if (isSemVerReleaseType(target)) {
1154
1155
  const range = rangeFromVersions(project.strapiVersion, target);
1155
1156
  const npmVersionsMatches = npmPackage.findVersionsInRange(range);
1156
- return npmVersionsMatches.at(-1);
1157
+ const version2 = npmVersionsMatches.at(-1);
1158
+ if (!version2) {
1159
+ throw new NPMCandidateNotFoundError(range, `The project is already up-to-date (${target})`);
1160
+ }
1161
+ return version2;
1157
1162
  }
1158
- return void 0;
1163
+ throw new NPMCandidateNotFoundError(target);
1159
1164
  };
1160
1165
  const upgraderFactory = (project, target, npmPackage) => {
1161
- const targetedNPMVersion = resolveNPMTarget(project, target, npmPackage);
1162
- if (!targetedNPMVersion) {
1163
- throw new Error(`Couldn't find a matching version in the NPM registry for "${target}"`);
1164
- }
1165
- const semverTarget = semVerFactory(targetedNPMVersion.version);
1166
+ const npmTarget = resolveNPMTarget(project, target, npmPackage);
1167
+ const semverTarget = semVerFactory(npmTarget.version);
1166
1168
  if (semver__default.default.eq(semverTarget, project.strapiVersion)) {
1167
- throw new Error(`The project is already on ${version(semverTarget)}`);
1169
+ throw new Error(`The project is already using v${semverTarget}`);
1168
1170
  }
1169
1171
  return new Upgrader(project, semverTarget, npmPackage);
1170
1172
  };
@@ -1175,80 +1177,195 @@ const constants = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
1175
1177
  __proto__: null,
1176
1178
  STRAPI_PACKAGE_NAME
1177
1179
  }, Symbol.toStringTag, { value: "Module" }));
1178
- const index$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1180
+ const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1179
1181
  __proto__: null,
1180
1182
  constants,
1181
1183
  upgraderFactory
1182
1184
  }, Symbol.toStringTag, { value: "Module" }));
1183
- const NPM_REGISTRY_URL = "https://registry.npmjs.org";
1184
- class Package {
1185
+ class Requirement {
1186
+ isRequired;
1185
1187
  name;
1186
- packageURL;
1187
- npmPackage;
1188
- constructor(name) {
1188
+ testCallback;
1189
+ children;
1190
+ constructor(name, testCallback, isRequired) {
1189
1191
  this.name = name;
1190
- this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
1191
- this.npmPackage = null;
1192
+ this.testCallback = testCallback;
1193
+ this.isRequired = isRequired ?? true;
1194
+ this.children = [];
1192
1195
  }
1193
- get isLoaded() {
1194
- return this.npmPackage !== null;
1196
+ setChildren(children) {
1197
+ this.children = children;
1198
+ return this;
1195
1199
  }
1196
- assertPackageIsLoaded(npmPackage) {
1197
- assert__default.default(this.isLoaded, "The package is not loaded yet");
1200
+ addChild(child) {
1201
+ this.children.push(child);
1202
+ return this;
1198
1203
  }
1199
- getVersionsDict() {
1200
- this.assertPackageIsLoaded(this.npmPackage);
1201
- return this.npmPackage.versions;
1204
+ asOptional() {
1205
+ const newInstance = requirementFactory(this.name, this.testCallback, false);
1206
+ newInstance.setChildren(this.children);
1207
+ return newInstance;
1202
1208
  }
1203
- getVersionsAsList() {
1204
- this.assertPackageIsLoaded(this.npmPackage);
1205
- return Object.values(this.npmPackage.versions);
1209
+ asRequired() {
1210
+ const newInstance = requirementFactory(this.name, this.testCallback, true);
1211
+ newInstance.setChildren(this.children);
1212
+ return newInstance;
1206
1213
  }
1207
- findVersionsInRange(range) {
1208
- const versions = this.getVersionsAsList();
1209
- return versions.filter((v) => range.test(v.version)).filter((v) => isLiteralSemVer(v.version)).sort((v1, v2) => semver__default.default.compare(v1.version, v2.version));
1214
+ async test(context) {
1215
+ try {
1216
+ await this.testCallback?.(context);
1217
+ return ok();
1218
+ } catch (e) {
1219
+ if (e instanceof Error) {
1220
+ return errored(e);
1221
+ }
1222
+ if (typeof e === "string") {
1223
+ return errored(new Error(e));
1224
+ }
1225
+ return errored(new Error("Unknown error"));
1226
+ }
1210
1227
  }
1211
- findVersion(version2) {
1212
- const versions = this.getVersionsAsList();
1213
- return versions.find((npmVersion) => semver__default.default.eq(npmVersion.version, version2));
1228
+ }
1229
+ const ok = () => ({ pass: true, error: null });
1230
+ const errored = (error) => ({ pass: false, error });
1231
+ const requirementFactory = (name, testCallback, isRequired) => new Requirement(name, testCallback, isRequired);
1232
+ const index$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1233
+ __proto__: null,
1234
+ requirementFactory
1235
+ }, Symbol.toStringTag, { value: "Module" }));
1236
+ const REQUIRE_AVAILABLE_NEXT_MAJOR = requirementFactory(
1237
+ "REQUIRE_AVAILABLE_NEXT_MAJOR",
1238
+ (context) => {
1239
+ const { project, target } = context;
1240
+ const currentMajor = project.strapiVersion.major;
1241
+ const targetedMajor = target.major;
1242
+ if (targetedMajor === currentMajor) {
1243
+ throw new Error(`You're already on the latest major version (v${currentMajor})`);
1244
+ }
1214
1245
  }
1215
- async refresh() {
1216
- const response = await fetch(this.packageURL);
1217
- assert__default.default(response.ok, `Request failed for ${this.packageURL}`);
1218
- this.npmPackage = await response.json();
1219
- return this;
1246
+ );
1247
+ const REQUIRE_LATEST_FOR_CURRENT_MAJOR = requirementFactory(
1248
+ "REQUIRE_LATEST_FOR_CURRENT_MAJOR",
1249
+ (context) => {
1250
+ const { project, target, npmVersionsMatches } = context;
1251
+ const { major: currentMajor } = project.strapiVersion;
1252
+ const invalidMatches = npmVersionsMatches.filter(
1253
+ (match) => semVerFactory(match.version).major === currentMajor
1254
+ );
1255
+ if (invalidMatches.length > 0) {
1256
+ const invalidVersions = invalidMatches.map((match) => match.version);
1257
+ const invalidVersionsCount = invalidVersions.length;
1258
+ throw new Error(
1259
+ `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.`
1260
+ );
1261
+ }
1220
1262
  }
1221
- versionExists(version2) {
1222
- return this.findVersion(version2) !== void 0;
1263
+ );
1264
+ const REQUIRE_GIT_CLEAN_REPOSITORY = requirementFactory(
1265
+ "REQUIRE_GIT_CLEAN_REPOSITORY",
1266
+ async (context) => {
1267
+ const git = simpleGit__default.default({ baseDir: context.project.cwd });
1268
+ const status = await git.status();
1269
+ if (!status.isClean()) {
1270
+ throw new Error(
1271
+ "Repository is not clean. Please commit or stash any changes before upgrading"
1272
+ );
1273
+ }
1223
1274
  }
1224
- }
1225
- const npmPackageFactory = (name) => new Package(name);
1275
+ );
1276
+ const REQUIRE_GIT_REPOSITORY = requirementFactory(
1277
+ "REQUIRE_GIT_REPOSITORY",
1278
+ async (context) => {
1279
+ const git = simpleGit__default.default({ baseDir: context.project.cwd });
1280
+ const isRepo = await git.checkIsRepo();
1281
+ if (!isRepo) {
1282
+ throw new Error("Not a git repository (or any of the parent directories)");
1283
+ }
1284
+ }
1285
+ ).addChild(REQUIRE_GIT_CLEAN_REPOSITORY.asOptional());
1286
+ const REQUIRE_GIT_INSTALLED = requirementFactory(
1287
+ "REQUIRE_GIT_INSTALLED",
1288
+ async (context) => {
1289
+ const git = simpleGit__default.default({ baseDir: context.project.cwd });
1290
+ try {
1291
+ await git.version();
1292
+ } catch {
1293
+ throw new Error("Git is not installed");
1294
+ }
1295
+ }
1296
+ ).addChild(REQUIRE_GIT_REPOSITORY.asOptional());
1297
+ const REQUIRE_GIT = requirementFactory("REQUIRE_GIT", null).addChild(
1298
+ REQUIRE_GIT_INSTALLED.asOptional()
1299
+ );
1300
+ const latest = async (upgrader, options) => {
1301
+ if (options.target !== ReleaseType.Latest) {
1302
+ return;
1303
+ }
1304
+ const npmPackage = upgrader.getNPMPackage();
1305
+ const target = upgrader.getTarget();
1306
+ const project = upgrader.getProject();
1307
+ const { strapiVersion: current } = project;
1308
+ const fTargetMajor = highlight(`v${target.major}`);
1309
+ const fCurrentMajor = highlight(`v${current.major}`);
1310
+ const fTarget = version(target);
1311
+ const fCurrent = version(current);
1312
+ const isMajorUpgrade = target.major > current.major;
1313
+ if (isMajorUpgrade) {
1314
+ options.logger.warn(
1315
+ `Detected a major upgrade for the "${highlight(ReleaseType.Latest)}" tag: ${fCurrent} > ${fTarget}`
1316
+ );
1317
+ const newerPackageRelease = npmPackage.findVersionsInRange(rangeFactory(`>${current.raw} <${target.major}`)).at(-1);
1318
+ if (newerPackageRelease) {
1319
+ const fLatest = version(semVerFactory(newerPackageRelease.version));
1320
+ options.logger.warn(
1321
+ `It's recommended to first upgrade to the latest version of ${fCurrentMajor} (${fLatest}) before upgrading to ${fTargetMajor}.`
1322
+ );
1323
+ }
1324
+ const proceedAnyway = await upgrader.confirm(`I know what I'm doing. Proceed anyway!`);
1325
+ if (!proceedAnyway) {
1326
+ throw new AbortedError();
1327
+ }
1328
+ }
1329
+ };
1226
1330
  const upgrade = async (options) => {
1227
1331
  const timer = timerFactory();
1228
1332
  const { logger, codemodsTarget } = options;
1229
1333
  const cwd = path__default.default.resolve(options.cwd ?? process.cwd());
1230
1334
  const project = projectFactory(cwd);
1335
+ logger.debug(projectDetails(project));
1231
1336
  if (!isApplicationProject(project)) {
1232
1337
  throw new Error(
1233
1338
  `The "${options.target}" upgrade can only be run on a Strapi project; for plugins, please use "codemods".`
1234
1339
  );
1235
1340
  }
1341
+ logger.debug(
1342
+ `Application: VERSION=${version(project.packageJSON.version)}; STRAPI_VERSION=${version(project.strapiVersion)}`
1343
+ );
1236
1344
  const npmPackage = npmPackageFactory(STRAPI_PACKAGE_NAME);
1237
1345
  await npmPackage.refresh();
1238
1346
  const upgrader = upgraderFactory(project, options.target, npmPackage).dry(options.dry ?? false).onConfirm(options.confirm ?? null).setLogger(logger);
1239
1347
  if (codemodsTarget !== void 0) {
1240
1348
  upgrader.overrideCodemodsTarget(codemodsTarget);
1241
1349
  }
1242
- if (options.target === ReleaseType.Major) {
1243
- upgrader.addRequirement(REQUIRE_AVAILABLE_NEXT_MAJOR).addRequirement(REQUIRE_LATEST_FOR_CURRENT_MAJOR);
1244
- }
1245
- upgrader.addRequirement(REQUIRE_GIT.asOptional());
1350
+ await runUpgradePrompts(upgrader, options);
1351
+ addUpgradeRequirements(upgrader, options);
1246
1352
  const upgradeReport = await upgrader.upgrade();
1247
1353
  if (!upgradeReport.success) {
1248
1354
  throw upgradeReport.error;
1249
1355
  }
1250
1356
  timer.stop();
1251
- logger.info(`Completed in ${durationMs(timer.elapsedMs)}`);
1357
+ logger.info(`Completed in ${durationMs(timer.elapsedMs)}ms`);
1358
+ };
1359
+ const runUpgradePrompts = async (upgrader, options) => {
1360
+ if (options.target === ReleaseType.Latest) {
1361
+ await latest(upgrader, options);
1362
+ }
1363
+ };
1364
+ const addUpgradeRequirements = (upgrader, options) => {
1365
+ if (options.target === ReleaseType.Major) {
1366
+ upgrader.addRequirement(REQUIRE_AVAILABLE_NEXT_MAJOR).addRequirement(REQUIRE_LATEST_FOR_CURRENT_MAJOR);
1367
+ }
1368
+ upgrader.addRequirement(REQUIRE_GIT.asOptional());
1252
1369
  };
1253
1370
  const resolvePath = (cwd) => path__default.default.resolve(cwd ?? process.cwd());
1254
1371
  const getRangeFromTarget = (currentVersion, target) => {
@@ -1257,6 +1374,8 @@ const getRangeFromTarget = (currentVersion, target) => {
1257
1374
  }
1258
1375
  const { major, minor, patch } = currentVersion;
1259
1376
  switch (target) {
1377
+ case ReleaseType.Latest:
1378
+ throw new Error("Can't use <latest> to create a codemods range: not implemented");
1260
1379
  case ReleaseType.Major:
1261
1380
  return rangeFactory(`${major}`);
1262
1381
  case ReleaseType.Minor:
@@ -1282,7 +1401,7 @@ const runCodemods = async (options) => {
1282
1401
  const cwd = resolvePath(options.cwd);
1283
1402
  const project = projectFactory(cwd);
1284
1403
  const range = findRangeFromTarget(project, options.target);
1285
- logger.debug(`Project: ${projectType(project.type)} found in ${path(cwd)}`);
1404
+ logger.debug(projectDetails(project));
1286
1405
  logger.debug(`Range: set to ${versionRange(range)}`);
1287
1406
  const codemodRunner = codemodRunnerFactory(project, range).dry(options.dry ?? false).onSelectCodemods(options.selectCodemods ?? null).setLogger(logger);
1288
1407
  let report;
@@ -1303,7 +1422,7 @@ const listCodemods = async (options) => {
1303
1422
  const cwd = resolvePath(options.cwd);
1304
1423
  const project = projectFactory(cwd);
1305
1424
  const range = findRangeFromTarget(project, target);
1306
- logger.debug(`Project: ${projectType(project.type)} found in ${path(cwd)}`);
1425
+ logger.debug(projectDetails(project));
1307
1426
  logger.debug(`Range: set to ${versionRange(range)}`);
1308
1427
  const repo = codemodRepositoryFactory();
1309
1428
  repo.refresh();
@@ -1414,18 +1533,18 @@ const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
1414
1533
  }, Symbol.toStringTag, { value: "Module" }));
1415
1534
  const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1416
1535
  __proto__: null,
1417
- codemod: index$7,
1418
- codemodRepository: index$6,
1536
+ codemod: index$8,
1537
+ codemodRepository: index$7,
1419
1538
  error: index$9,
1420
- f: index$8,
1539
+ f: index$f,
1421
1540
  fileScanner: index$d,
1422
1541
  logger: index$3,
1423
1542
  project: index$a,
1424
1543
  report: index$2,
1425
- requirement: index$g,
1544
+ requirement: index$5,
1426
1545
  runner: index$1,
1427
- timer: index$f,
1428
- upgrader: index$5,
1546
+ timer: index$g,
1547
+ upgrader: index$6,
1429
1548
  version: index$e
1430
1549
  }, Symbol.toStringTag, { value: "Module" }));
1431
1550
  exports.modules = index;