@strapi/upgrade 0.0.0-experimental.e60ec1829240dae21c1e1d29076681c322288813 → 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 (43) hide show
  1. package/LICENSE +19 -4
  2. package/README.md +1 -1
  3. package/dist/cli.js +444 -318
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.js +467 -344
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +463 -341
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/modules/error/utils.d.ts +8 -0
  10. package/dist/modules/error/utils.d.ts.map +1 -1
  11. package/dist/modules/file-scanner/scanner.d.ts.map +1 -1
  12. package/dist/modules/format/formats.d.ts +2 -1
  13. package/dist/modules/format/formats.d.ts.map +1 -1
  14. package/dist/modules/project/constants.d.ts +6 -5
  15. package/dist/modules/project/constants.d.ts.map +1 -1
  16. package/dist/modules/project/project.d.ts +16 -2
  17. package/dist/modules/project/project.d.ts.map +1 -1
  18. package/dist/modules/project/types.d.ts +3 -0
  19. package/dist/modules/project/types.d.ts.map +1 -1
  20. package/dist/modules/runner/json/transform.d.ts.map +1 -1
  21. package/dist/modules/upgrader/types.d.ts +6 -0
  22. package/dist/modules/upgrader/types.d.ts.map +1 -1
  23. package/dist/modules/upgrader/upgrader.d.ts +4 -0
  24. package/dist/modules/upgrader/upgrader.d.ts.map +1 -1
  25. package/dist/modules/version/range.d.ts.map +1 -1
  26. package/dist/modules/version/types.d.ts +2 -1
  27. package/dist/modules/version/types.d.ts.map +1 -1
  28. package/dist/tasks/codemods/utils.d.ts.map +1 -1
  29. package/dist/tasks/upgrade/prompts/index.d.ts +2 -0
  30. package/dist/tasks/upgrade/prompts/index.d.ts.map +1 -0
  31. package/dist/tasks/upgrade/prompts/latest.d.ts +9 -0
  32. package/dist/tasks/upgrade/prompts/latest.d.ts.map +1 -0
  33. package/dist/tasks/upgrade/requirements/major.d.ts.map +1 -1
  34. package/dist/tasks/upgrade/upgrade.d.ts.map +1 -1
  35. package/package.json +8 -8
  36. package/resources/codemods/5.0.0/comment-out-lifecycle-files.code.ts +63 -0
  37. package/resources/codemods/5.0.0/dependency-upgrade-react-and-react-dom.json.ts +67 -0
  38. package/resources/codemods/5.0.0/dependency-upgrade-styled-components.json.ts +49 -0
  39. package/resources/codemods/5.0.0/deprecate-helper-plugin.code.ts +192 -0
  40. package/resources/codemods/5.0.0/sqlite3-to-better-sqlite3.json.ts +0 -1
  41. package/resources/codemods/5.1.0/dependency-better-sqlite3.json.ts +48 -0
  42. package/resources/utils/change-import.ts +118 -0
  43. 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);
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}...`);
210
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();
@@ -336,7 +357,11 @@ const transformJSON = async (codemodPath, paths, config) => {
336
357
  timeElapsed: "",
337
358
  stats: {}
338
359
  };
339
- const esbuildOptions = { extensions: [".js", ".mjs", ".ts"] };
360
+ const esbuildOptions = {
361
+ extensions: [".js", ".mjs", ".ts"],
362
+ hookIgnoreNodeModules: false,
363
+ hookMatcher: fp.isEqual(codemodPath)
364
+ };
340
365
  const { unregister } = node.register(esbuildOptions);
341
366
  const module2 = require(codemodPath);
342
367
  unregister();
@@ -381,8 +406,10 @@ const index$b = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
381
406
  jsonRunnerFactory
382
407
  }, Symbol.toStringTag, { value: "Module" }));
383
408
  const PROJECT_PACKAGE_JSON = "package.json";
384
- const PROJECT_DEFAULT_ALLOWED_ROOT_PATHS = ["src", "config", "public"];
385
- 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 = [
386
413
  // Source files
387
414
  "js",
388
415
  "mjs",
@@ -391,22 +418,19 @@ const PROJECT_DEFAULT_CODE_EXTENSIONS = [
391
418
  "jsx",
392
419
  "tsx"
393
420
  ];
394
- const PROJECT_DEFAULT_JSON_EXTENSIONS = ["json"];
395
- const PROJECT_DEFAULT_ALLOWED_EXTENSIONS = [
396
- ...PROJECT_DEFAULT_CODE_EXTENSIONS,
397
- ...PROJECT_DEFAULT_JSON_EXTENSIONS
398
- ];
399
- const PROJECT_DEFAULT_PATTERNS = ["package.json"];
421
+ const PROJECT_JSON_EXTENSIONS = ["json"];
422
+ const PROJECT_ALLOWED_EXTENSIONS = [...PROJECT_CODE_EXTENSIONS, ...PROJECT_JSON_EXTENSIONS];
400
423
  const SCOPED_STRAPI_PACKAGE_PREFIX = "@strapi/";
401
424
  const STRAPI_DEPENDENCY_NAME = `${SCOPED_STRAPI_PACKAGE_PREFIX}strapi`;
402
425
  const constants$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
403
426
  __proto__: null,
404
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS,
405
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS,
406
- PROJECT_DEFAULT_CODE_EXTENSIONS,
407
- PROJECT_DEFAULT_JSON_EXTENSIONS,
408
- PROJECT_DEFAULT_PATTERNS,
427
+ PROJECT_ALLOWED_EXTENSIONS,
428
+ PROJECT_APP_ALLOWED_ROOT_PATHS,
429
+ PROJECT_CODE_EXTENSIONS,
430
+ PROJECT_JSON_EXTENSIONS,
409
431
  PROJECT_PACKAGE_JSON,
432
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS,
433
+ PROJECT_PLUGIN_ROOT_FILES,
410
434
  SCOPED_STRAPI_PACKAGE_PREFIX,
411
435
  STRAPI_DEPENDENCY_NAME
412
436
  }, Symbol.toStringTag, { value: "Module" }));
@@ -416,11 +440,13 @@ class Project {
416
440
  files;
417
441
  packageJSONPath;
418
442
  packageJSON;
419
- constructor(cwd) {
443
+ paths;
444
+ constructor(cwd, config) {
420
445
  if (!fse__default.default.pathExistsSync(cwd)) {
421
446
  throw new Error(`ENOENT: no such file or directory, access '${cwd}'`);
422
447
  }
423
448
  this.cwd = cwd;
449
+ this.paths = config.paths;
424
450
  this.refresh();
425
451
  }
426
452
  getFilesByExtensions(extensions) {
@@ -448,12 +474,8 @@ class Project {
448
474
  return reports2;
449
475
  }
450
476
  createProjectCodemodsRunners(dry = false) {
451
- const jsonExtensions = PROJECT_DEFAULT_JSON_EXTENSIONS.map(
452
- (ext) => `.${ext}`
453
- );
454
- const codeExtensions = PROJECT_DEFAULT_CODE_EXTENSIONS.map(
455
- (ext) => `.${ext}`
456
- );
477
+ const jsonExtensions = PROJECT_JSON_EXTENSIONS.map((ext) => `.${ext}`);
478
+ const codeExtensions = PROJECT_CODE_EXTENSIONS.map((ext) => `.${ext}`);
457
479
  const jsonFiles = this.getFilesByExtensions(jsonExtensions);
458
480
  const codeFiles = this.getFilesByExtensions(codeExtensions);
459
481
  const codeRunner = codeRunnerFactory(codeFiles, {
@@ -461,7 +483,7 @@ class Project {
461
483
  parser: "ts",
462
484
  runInBand: true,
463
485
  babel: true,
464
- extensions: PROJECT_DEFAULT_CODE_EXTENSIONS.join(","),
486
+ extensions: PROJECT_CODE_EXTENSIONS.join(","),
465
487
  // Don't output any log coming from the runner
466
488
  print: false,
467
489
  silent: true,
@@ -482,23 +504,32 @@ class Project {
482
504
  this.packageJSON = JSON.parse(packageJSONBuffer.toString());
483
505
  }
484
506
  refreshProjectFiles() {
485
- const allowedRootPaths = formatGlobCollectionPattern(
486
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS
487
- );
488
- const allowedExtensions = formatGlobCollectionPattern(
489
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS
490
- );
491
- const projectFilesPattern = `./${allowedRootPaths}/**/*.${allowedExtensions}`;
492
- const patterns = [projectFilesPattern, ...PROJECT_DEFAULT_PATTERNS];
493
507
  const scanner = fileScannerFactory(this.cwd);
494
- this.files = scanner.scan(patterns);
508
+ this.files = scanner.scan(this.paths);
495
509
  }
496
510
  }
497
511
  class AppProject extends Project {
498
512
  strapiVersion;
499
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
+ }
500
531
  constructor(cwd) {
501
- super(cwd);
532
+ super(cwd, { paths: AppProject.paths });
502
533
  this.refreshStrapiVersion();
503
534
  }
504
535
  refresh() {
@@ -553,6 +584,30 @@ const formatGlobCollectionPattern = (collection) => {
553
584
  };
554
585
  class PluginProject extends Project {
555
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
+ }
556
611
  }
557
612
  const isPlugin = (cwd) => {
558
613
  const packageJSONPath = path__default.default.join(cwd, PROJECT_PACKAGE_JSON);
@@ -567,10 +622,7 @@ const isPlugin = (cwd) => {
567
622
  };
568
623
  const projectFactory = (cwd) => {
569
624
  fse__default.default.accessSync(cwd);
570
- if (isPlugin(cwd)) {
571
- return new PluginProject(cwd);
572
- }
573
- return new AppProject(cwd);
625
+ return isPlugin(cwd) ? new PluginProject(cwd) : new AppProject(cwd);
574
626
  };
575
627
  const isPluginProject = (project) => {
576
628
  return project instanceof PluginProject;
@@ -602,6 +654,18 @@ class UnexpectedError extends Error {
602
654
  super("Unexpected Error");
603
655
  }
604
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
+ }
605
669
  const unknownToError = (e) => {
606
670
  if (e instanceof Error) {
607
671
  return e;
@@ -613,89 +677,11 @@ const unknownToError = (e) => {
613
677
  };
614
678
  const index$9 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
615
679
  __proto__: null,
680
+ AbortedError,
681
+ NPMCandidateNotFoundError,
616
682
  UnexpectedError,
617
683
  unknownToError
618
684
  }, Symbol.toStringTag, { value: "Module" }));
619
- const path = (path2) => chalk__default.default.blue(path2);
620
- const version = (version2) => {
621
- return chalk__default.default.italic.yellow(`v${version2}`);
622
- };
623
- const codemodUID = (uid) => {
624
- return chalk__default.default.bold.cyan(uid);
625
- };
626
- const projectType = (type) => chalk__default.default.cyan(type);
627
- const versionRange = (range) => chalk__default.default.italic.yellow(range.raw);
628
- const transform = (transformFilePath) => chalk__default.default.cyan(transformFilePath);
629
- const highlight = (arg) => chalk__default.default.bold.underline(arg);
630
- const upgradeStep = (text, step) => {
631
- return chalk__default.default.bold(`(${step[0]}/${step[1]}) ${text}...`);
632
- };
633
- const reports = (reports2) => {
634
- const rows = reports2.map(({ codemod, report }, i) => {
635
- const fIndex = chalk__default.default.grey(i);
636
- const fVersion = chalk__default.default.magenta(codemod.version);
637
- const fKind = chalk__default.default.yellow(codemod.kind);
638
- const fFormattedTransformPath = chalk__default.default.cyan(codemod.format());
639
- const fTimeElapsed = i === 0 ? `${report.timeElapsed}s ${chalk__default.default.dim.italic("(cold start)")}` : `${report.timeElapsed}s`;
640
- const fAffected = report.ok > 0 ? chalk__default.default.green(report.ok) : chalk__default.default.grey(0);
641
- const fUnchanged = report.ok === 0 ? chalk__default.default.red(report.nochange) : chalk__default.default.grey(report.nochange);
642
- return [fIndex, fVersion, fKind, fFormattedTransformPath, fAffected, fUnchanged, fTimeElapsed];
643
- });
644
- const table = new CliTable3__default.default({
645
- style: { compact: true },
646
- head: [
647
- chalk__default.default.bold.grey("N°"),
648
- chalk__default.default.bold.magenta("Version"),
649
- chalk__default.default.bold.yellow("Kind"),
650
- chalk__default.default.bold.cyan("Name"),
651
- chalk__default.default.bold.green("Affected"),
652
- chalk__default.default.bold.red("Unchanged"),
653
- chalk__default.default.bold.blue("Duration")
654
- ]
655
- });
656
- table.push(...rows);
657
- return table.toString();
658
- };
659
- const codemodList = (codemods) => {
660
- const rows = codemods.map((codemod, index2) => {
661
- const fIndex = chalk__default.default.grey(index2);
662
- const fVersion = chalk__default.default.magenta(codemod.version);
663
- const fKind = chalk__default.default.yellow(codemod.kind);
664
- const fName = chalk__default.default.blue(codemod.format());
665
- const fUID = codemodUID(codemod.uid);
666
- return [fIndex, fVersion, fKind, fName, fUID];
667
- });
668
- const table = new CliTable3__default.default({
669
- style: { compact: true },
670
- head: [
671
- chalk__default.default.bold.grey("N°"),
672
- chalk__default.default.bold.magenta("Version"),
673
- chalk__default.default.bold.yellow("Kind"),
674
- chalk__default.default.bold.blue("Name"),
675
- chalk__default.default.bold.cyan("UID")
676
- ]
677
- });
678
- table.push(...rows);
679
- return table.toString();
680
- };
681
- const durationMs = (elapsedMs) => {
682
- const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
683
- return `${elapsedSeconds}s`;
684
- };
685
- const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
686
- __proto__: null,
687
- codemodList,
688
- codemodUID,
689
- durationMs,
690
- highlight,
691
- path,
692
- projectType,
693
- reports,
694
- transform,
695
- upgradeStep,
696
- version,
697
- versionRange
698
- }, Symbol.toStringTag, { value: "Module" }));
699
685
  const CODEMOD_CODE_SUFFIX = "code";
700
686
  const CODEMOD_JSON_SUFFIX = "json";
701
687
  const CODEMOD_ALLOWED_SUFFIXES = [CODEMOD_CODE_SUFFIX, CODEMOD_JSON_SUFFIX];
@@ -748,7 +734,7 @@ class Codemod {
748
734
  }
749
735
  }
750
736
  const codemodFactory = (options) => new Codemod(options);
751
- const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
737
+ const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
752
738
  __proto__: null,
753
739
  codemodFactory,
754
740
  constants: constants$2
@@ -860,7 +846,7 @@ const parseCodemodKindFromFilename = (filename) => {
860
846
  const codemodRepositoryFactory = (cwd = INTERNAL_CODEMODS_DIRECTORY) => {
861
847
  return new CodemodRepository(cwd);
862
848
  };
863
- const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
849
+ const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
864
850
  __proto__: null,
865
851
  codemodRepositoryFactory,
866
852
  constants: constants$1
@@ -973,6 +959,15 @@ class Upgrader {
973
959
  this.logger = null;
974
960
  this.confirmationCallback = null;
975
961
  }
962
+ getNPMPackage() {
963
+ return this.npmPackage;
964
+ }
965
+ getProject() {
966
+ return this.project;
967
+ }
968
+ getTarget() {
969
+ return semVerFactory(this.target.raw);
970
+ }
976
971
  setRequirements(requirements) {
977
972
  this.requirements = requirements;
978
973
  return this;
@@ -1054,6 +1049,12 @@ class Upgrader {
1054
1049
  }
1055
1050
  return successReport();
1056
1051
  }
1052
+ async confirm(message) {
1053
+ if (typeof this.confirmationCallback !== "function") {
1054
+ return true;
1055
+ }
1056
+ return this.confirmationCallback(message);
1057
+ }
1057
1058
  async checkRequirements(requirements, context) {
1058
1059
  for (const requirement of requirements) {
1059
1060
  const { pass, error } = await requirement.test(context);
@@ -1125,7 +1126,7 @@ class Upgrader {
1125
1126
  const packageManagerName = await utils.packageManager.getPreferred(projectPath);
1126
1127
  this.logger?.debug?.(`Using ${highlight(packageManagerName)} as package manager`);
1127
1128
  if (this.isDry) {
1128
- 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")})`);
1129
1130
  return;
1130
1131
  }
1131
1132
  await utils.packageManager.installDependencies(projectPath, packageManagerName, {
@@ -1144,23 +1145,28 @@ class Upgrader {
1144
1145
  }
1145
1146
  const resolveNPMTarget = (project, target, npmPackage) => {
1146
1147
  if (isSemverInstance(target)) {
1147
- return npmPackage.findVersion(target);
1148
+ const version2 = npmPackage.findVersion(target);
1149
+ if (!version2) {
1150
+ throw new NPMCandidateNotFoundError(target);
1151
+ }
1152
+ return version2;
1148
1153
  }
1149
1154
  if (isSemVerReleaseType(target)) {
1150
1155
  const range = rangeFromVersions(project.strapiVersion, target);
1151
1156
  const npmVersionsMatches = npmPackage.findVersionsInRange(range);
1152
- 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;
1153
1162
  }
1154
- return void 0;
1163
+ throw new NPMCandidateNotFoundError(target);
1155
1164
  };
1156
1165
  const upgraderFactory = (project, target, npmPackage) => {
1157
- const targetedNPMVersion = resolveNPMTarget(project, target, npmPackage);
1158
- if (!targetedNPMVersion) {
1159
- throw new Error(`Couldn't find a matching version in the NPM registry for "${target}"`);
1160
- }
1161
- const semverTarget = semVerFactory(targetedNPMVersion.version);
1166
+ const npmTarget = resolveNPMTarget(project, target, npmPackage);
1167
+ const semverTarget = semVerFactory(npmTarget.version);
1162
1168
  if (semver__default.default.eq(semverTarget, project.strapiVersion)) {
1163
- throw new Error(`The project is already on ${version(semverTarget)}`);
1169
+ throw new Error(`The project is already using v${semverTarget}`);
1164
1170
  }
1165
1171
  return new Upgrader(project, semverTarget, npmPackage);
1166
1172
  };
@@ -1171,80 +1177,195 @@ const constants = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
1171
1177
  __proto__: null,
1172
1178
  STRAPI_PACKAGE_NAME
1173
1179
  }, Symbol.toStringTag, { value: "Module" }));
1174
- const index$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1180
+ const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1175
1181
  __proto__: null,
1176
1182
  constants,
1177
1183
  upgraderFactory
1178
1184
  }, Symbol.toStringTag, { value: "Module" }));
1179
- const NPM_REGISTRY_URL = "https://registry.npmjs.org";
1180
- class Package {
1185
+ class Requirement {
1186
+ isRequired;
1181
1187
  name;
1182
- packageURL;
1183
- npmPackage;
1184
- constructor(name) {
1188
+ testCallback;
1189
+ children;
1190
+ constructor(name, testCallback, isRequired) {
1185
1191
  this.name = name;
1186
- this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
1187
- this.npmPackage = null;
1192
+ this.testCallback = testCallback;
1193
+ this.isRequired = isRequired ?? true;
1194
+ this.children = [];
1188
1195
  }
1189
- get isLoaded() {
1190
- return this.npmPackage !== null;
1196
+ setChildren(children) {
1197
+ this.children = children;
1198
+ return this;
1191
1199
  }
1192
- assertPackageIsLoaded(npmPackage) {
1193
- assert__default.default(this.isLoaded, "The package is not loaded yet");
1200
+ addChild(child) {
1201
+ this.children.push(child);
1202
+ return this;
1194
1203
  }
1195
- getVersionsDict() {
1196
- this.assertPackageIsLoaded(this.npmPackage);
1197
- return this.npmPackage.versions;
1204
+ asOptional() {
1205
+ const newInstance = requirementFactory(this.name, this.testCallback, false);
1206
+ newInstance.setChildren(this.children);
1207
+ return newInstance;
1198
1208
  }
1199
- getVersionsAsList() {
1200
- this.assertPackageIsLoaded(this.npmPackage);
1201
- 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;
1202
1213
  }
1203
- findVersionsInRange(range) {
1204
- const versions = this.getVersionsAsList();
1205
- 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
+ }
1206
1227
  }
1207
- findVersion(version2) {
1208
- const versions = this.getVersionsAsList();
1209
- 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
+ }
1210
1245
  }
1211
- async refresh() {
1212
- const response = await fetch(this.packageURL);
1213
- assert__default.default(response.ok, `Request failed for ${this.packageURL}`);
1214
- this.npmPackage = await response.json();
1215
- 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
+ }
1216
1262
  }
1217
- versionExists(version2) {
1218
- 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
+ }
1219
1274
  }
1220
- }
1221
- 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
+ };
1222
1330
  const upgrade = async (options) => {
1223
1331
  const timer = timerFactory();
1224
1332
  const { logger, codemodsTarget } = options;
1225
1333
  const cwd = path__default.default.resolve(options.cwd ?? process.cwd());
1226
1334
  const project = projectFactory(cwd);
1335
+ logger.debug(projectDetails(project));
1227
1336
  if (!isApplicationProject(project)) {
1228
1337
  throw new Error(
1229
1338
  `The "${options.target}" upgrade can only be run on a Strapi project; for plugins, please use "codemods".`
1230
1339
  );
1231
1340
  }
1341
+ logger.debug(
1342
+ `Application: VERSION=${version(project.packageJSON.version)}; STRAPI_VERSION=${version(project.strapiVersion)}`
1343
+ );
1232
1344
  const npmPackage = npmPackageFactory(STRAPI_PACKAGE_NAME);
1233
1345
  await npmPackage.refresh();
1234
1346
  const upgrader = upgraderFactory(project, options.target, npmPackage).dry(options.dry ?? false).onConfirm(options.confirm ?? null).setLogger(logger);
1235
1347
  if (codemodsTarget !== void 0) {
1236
1348
  upgrader.overrideCodemodsTarget(codemodsTarget);
1237
1349
  }
1238
- if (options.target === ReleaseType.Major) {
1239
- upgrader.addRequirement(REQUIRE_AVAILABLE_NEXT_MAJOR).addRequirement(REQUIRE_LATEST_FOR_CURRENT_MAJOR);
1240
- }
1241
- upgrader.addRequirement(REQUIRE_GIT.asOptional());
1350
+ await runUpgradePrompts(upgrader, options);
1351
+ addUpgradeRequirements(upgrader, options);
1242
1352
  const upgradeReport = await upgrader.upgrade();
1243
1353
  if (!upgradeReport.success) {
1244
1354
  throw upgradeReport.error;
1245
1355
  }
1246
1356
  timer.stop();
1247
- 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());
1248
1369
  };
1249
1370
  const resolvePath = (cwd) => path__default.default.resolve(cwd ?? process.cwd());
1250
1371
  const getRangeFromTarget = (currentVersion, target) => {
@@ -1253,6 +1374,8 @@ const getRangeFromTarget = (currentVersion, target) => {
1253
1374
  }
1254
1375
  const { major, minor, patch } = currentVersion;
1255
1376
  switch (target) {
1377
+ case ReleaseType.Latest:
1378
+ throw new Error("Can't use <latest> to create a codemods range: not implemented");
1256
1379
  case ReleaseType.Major:
1257
1380
  return rangeFactory(`${major}`);
1258
1381
  case ReleaseType.Minor:
@@ -1278,7 +1401,7 @@ const runCodemods = async (options) => {
1278
1401
  const cwd = resolvePath(options.cwd);
1279
1402
  const project = projectFactory(cwd);
1280
1403
  const range = findRangeFromTarget(project, options.target);
1281
- logger.debug(`Project: ${projectType(project.type)} found in ${path(cwd)}`);
1404
+ logger.debug(projectDetails(project));
1282
1405
  logger.debug(`Range: set to ${versionRange(range)}`);
1283
1406
  const codemodRunner = codemodRunnerFactory(project, range).dry(options.dry ?? false).onSelectCodemods(options.selectCodemods ?? null).setLogger(logger);
1284
1407
  let report;
@@ -1299,7 +1422,7 @@ const listCodemods = async (options) => {
1299
1422
  const cwd = resolvePath(options.cwd);
1300
1423
  const project = projectFactory(cwd);
1301
1424
  const range = findRangeFromTarget(project, target);
1302
- logger.debug(`Project: ${projectType(project.type)} found in ${path(cwd)}`);
1425
+ logger.debug(projectDetails(project));
1303
1426
  logger.debug(`Range: set to ${versionRange(range)}`);
1304
1427
  const repo = codemodRepositoryFactory();
1305
1428
  repo.refresh();
@@ -1410,18 +1533,18 @@ const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
1410
1533
  }, Symbol.toStringTag, { value: "Module" }));
1411
1534
  const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1412
1535
  __proto__: null,
1413
- codemod: index$7,
1414
- codemodRepository: index$6,
1536
+ codemod: index$8,
1537
+ codemodRepository: index$7,
1415
1538
  error: index$9,
1416
- f: index$8,
1539
+ f: index$f,
1417
1540
  fileScanner: index$d,
1418
1541
  logger: index$3,
1419
1542
  project: index$a,
1420
1543
  report: index$2,
1421
- requirement: index$g,
1544
+ requirement: index$5,
1422
1545
  runner: index$1,
1423
- timer: index$f,
1424
- upgrader: index$5,
1546
+ timer: index$g,
1547
+ upgrader: index$6,
1425
1548
  version: index$e
1426
1549
  }, Symbol.toStringTag, { value: "Module" }));
1427
1550
  exports.modules = index;