@strapi/upgrade 5.0.6 → 5.1.1

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.
package/dist/index.mjs CHANGED
@@ -1,128 +1,15 @@
1
1
  import path$1 from "node:path";
2
- import simpleGit from "simple-git";
2
+ import CliTable3 from "cli-table3";
3
3
  import chalk from "chalk";
4
+ import assert from "node:assert";
4
5
  import semver from "semver";
5
- import { packageManager } from "@strapi/utils";
6
- import { cloneDeep, get, has, merge, set, omit, isEqual, groupBy, size } from "lodash/fp";
7
6
  import fse from "fs-extra";
8
- import assert from "node:assert";
9
7
  import fastglob from "fast-glob";
10
8
  import { run } from "jscodeshift/src/Runner";
9
+ import { cloneDeep, get, has, merge, set, omit, isEqual, groupBy, size } from "lodash/fp";
11
10
  import { register } from "esbuild-register/dist/node";
12
- import CliTable3 from "cli-table3";
13
- class Requirement {
14
- isRequired;
15
- name;
16
- testCallback;
17
- children;
18
- constructor(name, testCallback, isRequired) {
19
- this.name = name;
20
- this.testCallback = testCallback;
21
- this.isRequired = isRequired ?? true;
22
- this.children = [];
23
- }
24
- setChildren(children) {
25
- this.children = children;
26
- return this;
27
- }
28
- addChild(child) {
29
- this.children.push(child);
30
- return this;
31
- }
32
- asOptional() {
33
- const newInstance = requirementFactory(this.name, this.testCallback, false);
34
- newInstance.setChildren(this.children);
35
- return newInstance;
36
- }
37
- asRequired() {
38
- const newInstance = requirementFactory(this.name, this.testCallback, true);
39
- newInstance.setChildren(this.children);
40
- return newInstance;
41
- }
42
- async test(context) {
43
- try {
44
- await this.testCallback?.(context);
45
- return ok();
46
- } catch (e) {
47
- if (e instanceof Error) {
48
- return errored(e);
49
- }
50
- if (typeof e === "string") {
51
- return errored(new Error(e));
52
- }
53
- return errored(new Error("Unknown error"));
54
- }
55
- }
56
- }
57
- const ok = () => ({ pass: true, error: null });
58
- const errored = (error) => ({ pass: false, error });
59
- const requirementFactory = (name, testCallback, isRequired) => new Requirement(name, testCallback, isRequired);
60
- const index$g = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
61
- __proto__: null,
62
- requirementFactory
63
- }, Symbol.toStringTag, { value: "Module" }));
64
- const REQUIRE_AVAILABLE_NEXT_MAJOR = requirementFactory(
65
- "REQUIRE_AVAILABLE_NEXT_MAJOR",
66
- (context) => {
67
- const { project, target } = context;
68
- const currentMajor = project.strapiVersion.major;
69
- const targetedMajor = target.major;
70
- if (targetedMajor === currentMajor) {
71
- throw new Error(`You're already on the latest major version (v${currentMajor})`);
72
- }
73
- }
74
- );
75
- const REQUIRE_LATEST_FOR_CURRENT_MAJOR = requirementFactory(
76
- "REQUIRE_LATEST_FOR_CURRENT_MAJOR",
77
- (context) => {
78
- const { project, target, npmVersionsMatches } = context;
79
- if (npmVersionsMatches.length !== 1) {
80
- const invalidVersions = npmVersionsMatches.slice(0, -1);
81
- const invalidVersionsAsSemVer = invalidVersions.map((v) => v.version);
82
- const nbInvalidVersions = npmVersionsMatches.length;
83
- const currentMajor = project.strapiVersion.major;
84
- throw new Error(
85
- `Doing a major upgrade requires to be on the latest v${currentMajor} version, but found ${nbInvalidVersions} versions between the current one and ${target}: ${invalidVersionsAsSemVer}`
86
- );
87
- }
88
- }
89
- );
90
- const REQUIRE_GIT_CLEAN_REPOSITORY = requirementFactory(
91
- "REQUIRE_GIT_CLEAN_REPOSITORY",
92
- async (context) => {
93
- const git = simpleGit({ baseDir: context.project.cwd });
94
- const status = await git.status();
95
- if (!status.isClean()) {
96
- throw new Error(
97
- "Repository is not clean. Please commit or stash any changes before upgrading"
98
- );
99
- }
100
- }
101
- );
102
- const REQUIRE_GIT_REPOSITORY = requirementFactory(
103
- "REQUIRE_GIT_REPOSITORY",
104
- async (context) => {
105
- const git = simpleGit({ baseDir: context.project.cwd });
106
- const isRepo = await git.checkIsRepo();
107
- if (!isRepo) {
108
- throw new Error("Not a git repository (or any of the parent directories)");
109
- }
110
- }
111
- ).addChild(REQUIRE_GIT_CLEAN_REPOSITORY.asOptional());
112
- const REQUIRE_GIT_INSTALLED = requirementFactory(
113
- "REQUIRE_GIT_INSTALLED",
114
- async (context) => {
115
- const git = simpleGit({ baseDir: context.project.cwd });
116
- try {
117
- await git.version();
118
- } catch {
119
- throw new Error("Git is not installed");
120
- }
121
- }
122
- ).addChild(REQUIRE_GIT_REPOSITORY.asOptional());
123
- const REQUIRE_GIT = requirementFactory("REQUIRE_GIT", null).addChild(
124
- REQUIRE_GIT_INSTALLED.asOptional()
125
- );
11
+ import { packageManager } from "@strapi/utils";
12
+ import simpleGit from "simple-git";
126
13
  class Timer {
127
14
  interval;
128
15
  constructor() {
@@ -153,55 +40,101 @@ const constants$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP
153
40
  __proto__: null,
154
41
  ONE_SECOND_MS
155
42
  }, Symbol.toStringTag, { value: "Module" }));
156
- const index$f = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
43
+ const index$g = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
157
44
  __proto__: null,
158
45
  constants: constants$4,
159
46
  timerFactory
160
47
  }, Symbol.toStringTag, { value: "Module" }));
161
- class JSONTransformAPI {
162
- json;
163
- constructor(json) {
164
- this.json = cloneDeep(json);
165
- }
166
- get(path2, defaultValue) {
167
- if (!path2) {
168
- return this.root();
169
- }
170
- return cloneDeep(get(path2, this.json) ?? defaultValue);
171
- }
172
- has(path2) {
173
- return has(path2, this.json);
174
- }
175
- merge(other) {
176
- this.json = merge(other, this.json);
177
- return this;
178
- }
179
- root() {
180
- return cloneDeep(this.json);
181
- }
182
- set(path2, value) {
183
- this.json = set(path2, value, this.json);
184
- return this;
185
- }
186
- remove(path2) {
187
- this.json = omit(path2, this.json);
188
- return this;
189
- }
190
- }
191
- const createJSONTransformAPI = (object) => new JSONTransformAPI(object);
192
- const readJSON = async (path2) => {
193
- const buffer = await fse.readFile(path2);
194
- return JSON.parse(buffer.toString());
48
+ const path = (path2) => chalk.blue(path2);
49
+ const version = (version2) => {
50
+ return chalk.italic.yellow(`v${version2}`);
195
51
  };
196
- const saveJSON = async (path2, json) => {
197
- const jsonAsString = `${JSON.stringify(json, null, 2)}
198
- `;
199
- await fse.writeFile(path2, jsonAsString);
52
+ const codemodUID = (uid) => {
53
+ return chalk.bold.cyan(uid);
200
54
  };
55
+ const projectDetails = (project) => {
56
+ return `Project: TYPE=${projectType(project.type)}; CWD=${path(project.cwd)}; PATHS=${project.paths.map(path)}`;
57
+ };
58
+ const projectType = (type) => chalk.cyan(type);
59
+ const versionRange = (range) => chalk.italic.yellow(range.raw);
60
+ const transform = (transformFilePath) => chalk.cyan(transformFilePath);
61
+ const highlight = (arg) => chalk.bold.underline(arg);
62
+ const upgradeStep = (text, step) => {
63
+ return chalk.bold(`(${step[0]}/${step[1]}) ${text}...`);
64
+ };
65
+ const reports = (reports2) => {
66
+ const rows = reports2.map(({ codemod, report }, i) => {
67
+ const fIndex = chalk.grey(i);
68
+ const fVersion = chalk.magenta(codemod.version);
69
+ const fKind = chalk.yellow(codemod.kind);
70
+ const fFormattedTransformPath = chalk.cyan(codemod.format());
71
+ const fTimeElapsed = i === 0 ? `${report.timeElapsed}s ${chalk.dim.italic("(cold start)")}` : `${report.timeElapsed}s`;
72
+ const fAffected = report.ok > 0 ? chalk.green(report.ok) : chalk.grey(0);
73
+ const fUnchanged = report.ok === 0 ? chalk.red(report.nochange) : chalk.grey(report.nochange);
74
+ return [fIndex, fVersion, fKind, fFormattedTransformPath, fAffected, fUnchanged, fTimeElapsed];
75
+ });
76
+ const table = new CliTable3({
77
+ style: { compact: true },
78
+ head: [
79
+ chalk.bold.grey("N°"),
80
+ chalk.bold.magenta("Version"),
81
+ chalk.bold.yellow("Kind"),
82
+ chalk.bold.cyan("Name"),
83
+ chalk.bold.green("Affected"),
84
+ chalk.bold.red("Unchanged"),
85
+ chalk.bold.blue("Duration")
86
+ ]
87
+ });
88
+ table.push(...rows);
89
+ return table.toString();
90
+ };
91
+ const codemodList = (codemods) => {
92
+ const rows = codemods.map((codemod, index2) => {
93
+ const fIndex = chalk.grey(index2);
94
+ const fVersion = chalk.magenta(codemod.version);
95
+ const fKind = chalk.yellow(codemod.kind);
96
+ const fName = chalk.blue(codemod.format());
97
+ const fUID = codemodUID(codemod.uid);
98
+ return [fIndex, fVersion, fKind, fName, fUID];
99
+ });
100
+ const table = new CliTable3({
101
+ style: { compact: true },
102
+ head: [
103
+ chalk.bold.grey("N°"),
104
+ chalk.bold.magenta("Version"),
105
+ chalk.bold.yellow("Kind"),
106
+ chalk.bold.blue("Name"),
107
+ chalk.bold.cyan("UID")
108
+ ]
109
+ });
110
+ table.push(...rows);
111
+ return table.toString();
112
+ };
113
+ const durationMs = (elapsedMs) => {
114
+ const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
115
+ return `${elapsedSeconds}s`;
116
+ };
117
+ const index$f = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
118
+ __proto__: null,
119
+ codemodList,
120
+ codemodUID,
121
+ durationMs,
122
+ highlight,
123
+ path,
124
+ projectDetails,
125
+ projectType,
126
+ reports,
127
+ transform,
128
+ upgradeStep,
129
+ version,
130
+ versionRange
131
+ }, Symbol.toStringTag, { value: "Module" }));
132
+ const NPM_REGISTRY_URL = "https://registry.npmjs.org";
201
133
  var ReleaseType = /* @__PURE__ */ ((ReleaseType2) => {
202
134
  ReleaseType2["Major"] = "major";
203
135
  ReleaseType2["Minor"] = "minor";
204
136
  ReleaseType2["Patch"] = "patch";
137
+ ReleaseType2["Latest"] = "latest";
205
138
  return ReleaseType2;
206
139
  })(ReleaseType || {});
207
140
  const types = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
@@ -227,17 +160,20 @@ const rangeFactory = (range) => {
227
160
  };
228
161
  const rangeFromReleaseType = (current, identifier) => {
229
162
  switch (identifier) {
230
- case ReleaseType.Major: {
231
- const nextMajor = semver.inc(current, "major");
232
- return rangeFactory(`>${current.raw} <=${nextMajor}`);
163
+ case ReleaseType.Latest: {
164
+ return rangeFactory(`>${current.raw}`);
233
165
  }
234
- case ReleaseType.Patch: {
235
- const minor = semver.inc(current, "minor");
236
- return rangeFactory(`>${current.raw} <${minor}`);
166
+ case ReleaseType.Major: {
167
+ const nextMajor = semVerFactory(current.raw).inc("major");
168
+ return rangeFactory(`>${current.raw} <=${nextMajor.major}`);
237
169
  }
238
170
  case ReleaseType.Minor: {
239
- const major = semver.inc(current, "major");
240
- return rangeFactory(`>${current.raw} <${major}`);
171
+ const nextMajor = semVerFactory(current.raw).inc("major");
172
+ return rangeFactory(`>${current.raw} <${nextMajor.raw}`);
173
+ }
174
+ case ReleaseType.Patch: {
175
+ const nextMinor = semVerFactory(current.raw).inc("minor");
176
+ return rangeFactory(`>${current.raw} <${nextMinor.raw}`);
241
177
  }
242
178
  default: {
243
179
  throw new Error("Not implemented");
@@ -271,6 +207,48 @@ const index$e = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
271
207
  rangeFromVersions,
272
208
  semVerFactory
273
209
  }, Symbol.toStringTag, { value: "Module" }));
210
+ class Package {
211
+ name;
212
+ packageURL;
213
+ npmPackage;
214
+ constructor(name) {
215
+ this.name = name;
216
+ this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
217
+ this.npmPackage = null;
218
+ }
219
+ get isLoaded() {
220
+ return this.npmPackage !== null;
221
+ }
222
+ assertPackageIsLoaded(npmPackage) {
223
+ assert(this.isLoaded, "The package is not loaded yet");
224
+ }
225
+ getVersionsDict() {
226
+ this.assertPackageIsLoaded(this.npmPackage);
227
+ return this.npmPackage.versions;
228
+ }
229
+ getVersionsAsList() {
230
+ this.assertPackageIsLoaded(this.npmPackage);
231
+ return Object.values(this.npmPackage.versions);
232
+ }
233
+ findVersionsInRange(range) {
234
+ const versions = this.getVersionsAsList();
235
+ return versions.filter((v) => range.test(v.version)).filter((v) => isLiteralSemVer(v.version)).sort((v1, v2) => semver.compare(v1.version, v2.version));
236
+ }
237
+ findVersion(version2) {
238
+ const versions = this.getVersionsAsList();
239
+ return versions.find((npmVersion) => semver.eq(npmVersion.version, version2));
240
+ }
241
+ async refresh() {
242
+ const response = await fetch(this.packageURL);
243
+ assert(response.ok, `Request failed for ${this.packageURL}`);
244
+ this.npmPackage = await response.json();
245
+ return this;
246
+ }
247
+ versionExists(version2) {
248
+ return this.findVersion(version2) !== void 0;
249
+ }
250
+ }
251
+ const npmPackageFactory = (name) => new Package(name);
274
252
  class FileScanner {
275
253
  cwd;
276
254
  constructor(cwd) {
@@ -317,6 +295,46 @@ const index$c = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
317
295
  __proto__: null,
318
296
  codeRunnerFactory
319
297
  }, Symbol.toStringTag, { value: "Module" }));
298
+ class JSONTransformAPI {
299
+ json;
300
+ constructor(json) {
301
+ this.json = cloneDeep(json);
302
+ }
303
+ get(path2, defaultValue) {
304
+ if (!path2) {
305
+ return this.root();
306
+ }
307
+ return cloneDeep(get(path2, this.json) ?? defaultValue);
308
+ }
309
+ has(path2) {
310
+ return has(path2, this.json);
311
+ }
312
+ merge(other) {
313
+ this.json = merge(other, this.json);
314
+ return this;
315
+ }
316
+ root() {
317
+ return cloneDeep(this.json);
318
+ }
319
+ set(path2, value) {
320
+ this.json = set(path2, value, this.json);
321
+ return this;
322
+ }
323
+ remove(path2) {
324
+ this.json = omit(path2, this.json);
325
+ return this;
326
+ }
327
+ }
328
+ const createJSONTransformAPI = (object) => new JSONTransformAPI(object);
329
+ const readJSON = async (path2) => {
330
+ const buffer = await fse.readFile(path2);
331
+ return JSON.parse(buffer.toString());
332
+ };
333
+ const saveJSON = async (path2, json) => {
334
+ const jsonAsString = `${JSON.stringify(json, null, 2)}
335
+ `;
336
+ await fse.writeFile(path2, jsonAsString);
337
+ };
320
338
  const transformJSON = async (codemodPath, paths, config) => {
321
339
  const { dry } = config;
322
340
  const startTime = process.hrtime();
@@ -625,6 +643,18 @@ class UnexpectedError extends Error {
625
643
  super("Unexpected Error");
626
644
  }
627
645
  }
646
+ class NPMCandidateNotFoundError extends Error {
647
+ target;
648
+ constructor(target, message = `Couldn't find a valid NPM candidate for "${target}"`) {
649
+ super(message);
650
+ this.target = target;
651
+ }
652
+ }
653
+ class AbortedError extends Error {
654
+ constructor(message = "Upgrade aborted") {
655
+ super(message);
656
+ }
657
+ }
628
658
  const unknownToError = (e) => {
629
659
  if (e instanceof Error) {
630
660
  return e;
@@ -636,93 +666,11 @@ const unknownToError = (e) => {
636
666
  };
637
667
  const index$9 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
638
668
  __proto__: null,
669
+ AbortedError,
670
+ NPMCandidateNotFoundError,
639
671
  UnexpectedError,
640
672
  unknownToError
641
673
  }, Symbol.toStringTag, { value: "Module" }));
642
- const path = (path2) => chalk.blue(path2);
643
- const version = (version2) => {
644
- return chalk.italic.yellow(`v${version2}`);
645
- };
646
- const codemodUID = (uid) => {
647
- return chalk.bold.cyan(uid);
648
- };
649
- const projectDetails = (project) => {
650
- return `Project: TYPE=${projectType(project.type)}; CWD=${path(project.cwd)}; PATHS=${project.paths.map(path)}`;
651
- };
652
- const projectType = (type) => chalk.cyan(type);
653
- const versionRange = (range) => chalk.italic.yellow(range.raw);
654
- const transform = (transformFilePath) => chalk.cyan(transformFilePath);
655
- const highlight = (arg) => chalk.bold.underline(arg);
656
- const upgradeStep = (text, step) => {
657
- return chalk.bold(`(${step[0]}/${step[1]}) ${text}...`);
658
- };
659
- const reports = (reports2) => {
660
- const rows = reports2.map(({ codemod, report }, i) => {
661
- const fIndex = chalk.grey(i);
662
- const fVersion = chalk.magenta(codemod.version);
663
- const fKind = chalk.yellow(codemod.kind);
664
- const fFormattedTransformPath = chalk.cyan(codemod.format());
665
- const fTimeElapsed = i === 0 ? `${report.timeElapsed}s ${chalk.dim.italic("(cold start)")}` : `${report.timeElapsed}s`;
666
- const fAffected = report.ok > 0 ? chalk.green(report.ok) : chalk.grey(0);
667
- const fUnchanged = report.ok === 0 ? chalk.red(report.nochange) : chalk.grey(report.nochange);
668
- return [fIndex, fVersion, fKind, fFormattedTransformPath, fAffected, fUnchanged, fTimeElapsed];
669
- });
670
- const table = new CliTable3({
671
- style: { compact: true },
672
- head: [
673
- chalk.bold.grey("N°"),
674
- chalk.bold.magenta("Version"),
675
- chalk.bold.yellow("Kind"),
676
- chalk.bold.cyan("Name"),
677
- chalk.bold.green("Affected"),
678
- chalk.bold.red("Unchanged"),
679
- chalk.bold.blue("Duration")
680
- ]
681
- });
682
- table.push(...rows);
683
- return table.toString();
684
- };
685
- const codemodList = (codemods) => {
686
- const rows = codemods.map((codemod, index2) => {
687
- const fIndex = chalk.grey(index2);
688
- const fVersion = chalk.magenta(codemod.version);
689
- const fKind = chalk.yellow(codemod.kind);
690
- const fName = chalk.blue(codemod.format());
691
- const fUID = codemodUID(codemod.uid);
692
- return [fIndex, fVersion, fKind, fName, fUID];
693
- });
694
- const table = new CliTable3({
695
- style: { compact: true },
696
- head: [
697
- chalk.bold.grey("N°"),
698
- chalk.bold.magenta("Version"),
699
- chalk.bold.yellow("Kind"),
700
- chalk.bold.blue("Name"),
701
- chalk.bold.cyan("UID")
702
- ]
703
- });
704
- table.push(...rows);
705
- return table.toString();
706
- };
707
- const durationMs = (elapsedMs) => {
708
- const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
709
- return `${elapsedSeconds}s`;
710
- };
711
- const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
712
- __proto__: null,
713
- codemodList,
714
- codemodUID,
715
- durationMs,
716
- highlight,
717
- path,
718
- projectDetails,
719
- projectType,
720
- reports,
721
- transform,
722
- upgradeStep,
723
- version,
724
- versionRange
725
- }, Symbol.toStringTag, { value: "Module" }));
726
674
  const CODEMOD_CODE_SUFFIX = "code";
727
675
  const CODEMOD_JSON_SUFFIX = "json";
728
676
  const CODEMOD_ALLOWED_SUFFIXES = [CODEMOD_CODE_SUFFIX, CODEMOD_JSON_SUFFIX];
@@ -775,7 +723,7 @@ class Codemod {
775
723
  }
776
724
  }
777
725
  const codemodFactory = (options) => new Codemod(options);
778
- const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
726
+ const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
779
727
  __proto__: null,
780
728
  codemodFactory,
781
729
  constants: constants$2
@@ -887,7 +835,7 @@ const parseCodemodKindFromFilename = (filename) => {
887
835
  const codemodRepositoryFactory = (cwd = INTERNAL_CODEMODS_DIRECTORY) => {
888
836
  return new CodemodRepository(cwd);
889
837
  };
890
- const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
838
+ const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
891
839
  __proto__: null,
892
840
  codemodRepositoryFactory,
893
841
  constants: constants$1
@@ -1000,6 +948,15 @@ class Upgrader {
1000
948
  this.logger = null;
1001
949
  this.confirmationCallback = null;
1002
950
  }
951
+ getNPMPackage() {
952
+ return this.npmPackage;
953
+ }
954
+ getProject() {
955
+ return this.project;
956
+ }
957
+ getTarget() {
958
+ return semVerFactory(this.target.raw);
959
+ }
1003
960
  setRequirements(requirements) {
1004
961
  this.requirements = requirements;
1005
962
  return this;
@@ -1081,6 +1038,12 @@ class Upgrader {
1081
1038
  }
1082
1039
  return successReport();
1083
1040
  }
1041
+ async confirm(message) {
1042
+ if (typeof this.confirmationCallback !== "function") {
1043
+ return true;
1044
+ }
1045
+ return this.confirmationCallback(message);
1046
+ }
1084
1047
  async checkRequirements(requirements, context) {
1085
1048
  for (const requirement of requirements) {
1086
1049
  const { pass, error } = await requirement.test(context);
@@ -1152,7 +1115,7 @@ class Upgrader {
1152
1115
  const packageManagerName = await packageManager.getPreferred(projectPath);
1153
1116
  this.logger?.debug?.(`Using ${highlight(packageManagerName)} as package manager`);
1154
1117
  if (this.isDry) {
1155
- this.logger?.debug?.(`Skipping dependencies installation (${chalk.italic("dry mode")}`);
1118
+ this.logger?.debug?.(`Skipping dependencies installation (${chalk.italic("dry mode")})`);
1156
1119
  return;
1157
1120
  }
1158
1121
  await packageManager.installDependencies(projectPath, packageManagerName, {
@@ -1171,23 +1134,28 @@ class Upgrader {
1171
1134
  }
1172
1135
  const resolveNPMTarget = (project, target, npmPackage) => {
1173
1136
  if (isSemverInstance(target)) {
1174
- return npmPackage.findVersion(target);
1137
+ const version2 = npmPackage.findVersion(target);
1138
+ if (!version2) {
1139
+ throw new NPMCandidateNotFoundError(target);
1140
+ }
1141
+ return version2;
1175
1142
  }
1176
1143
  if (isSemVerReleaseType(target)) {
1177
1144
  const range = rangeFromVersions(project.strapiVersion, target);
1178
1145
  const npmVersionsMatches = npmPackage.findVersionsInRange(range);
1179
- return npmVersionsMatches.at(-1);
1146
+ const version2 = npmVersionsMatches.at(-1);
1147
+ if (!version2) {
1148
+ throw new NPMCandidateNotFoundError(range, `The project is already up-to-date (${target})`);
1149
+ }
1150
+ return version2;
1180
1151
  }
1181
- return void 0;
1152
+ throw new NPMCandidateNotFoundError(target);
1182
1153
  };
1183
1154
  const upgraderFactory = (project, target, npmPackage) => {
1184
- const targetedNPMVersion = resolveNPMTarget(project, target, npmPackage);
1185
- if (!targetedNPMVersion) {
1186
- throw new Error(`Couldn't find a matching version in the NPM registry for "${target}"`);
1187
- }
1188
- const semverTarget = semVerFactory(targetedNPMVersion.version);
1155
+ const npmTarget = resolveNPMTarget(project, target, npmPackage);
1156
+ const semverTarget = semVerFactory(npmTarget.version);
1189
1157
  if (semver.eq(semverTarget, project.strapiVersion)) {
1190
- throw new Error(`The project is already on ${version(semverTarget)}`);
1158
+ throw new Error(`The project is already using v${semverTarget}`);
1191
1159
  }
1192
1160
  return new Upgrader(project, semverTarget, npmPackage);
1193
1161
  };
@@ -1198,54 +1166,156 @@ const constants = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
1198
1166
  __proto__: null,
1199
1167
  STRAPI_PACKAGE_NAME
1200
1168
  }, Symbol.toStringTag, { value: "Module" }));
1201
- const index$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1169
+ const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1202
1170
  __proto__: null,
1203
1171
  constants,
1204
1172
  upgraderFactory
1205
1173
  }, Symbol.toStringTag, { value: "Module" }));
1206
- const NPM_REGISTRY_URL = "https://registry.npmjs.org";
1207
- class Package {
1174
+ class Requirement {
1175
+ isRequired;
1208
1176
  name;
1209
- packageURL;
1210
- npmPackage;
1211
- constructor(name) {
1177
+ testCallback;
1178
+ children;
1179
+ constructor(name, testCallback, isRequired) {
1212
1180
  this.name = name;
1213
- this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
1214
- this.npmPackage = null;
1181
+ this.testCallback = testCallback;
1182
+ this.isRequired = isRequired ?? true;
1183
+ this.children = [];
1215
1184
  }
1216
- get isLoaded() {
1217
- return this.npmPackage !== null;
1185
+ setChildren(children) {
1186
+ this.children = children;
1187
+ return this;
1218
1188
  }
1219
- assertPackageIsLoaded(npmPackage) {
1220
- assert(this.isLoaded, "The package is not loaded yet");
1189
+ addChild(child) {
1190
+ this.children.push(child);
1191
+ return this;
1221
1192
  }
1222
- getVersionsDict() {
1223
- this.assertPackageIsLoaded(this.npmPackage);
1224
- return this.npmPackage.versions;
1193
+ asOptional() {
1194
+ const newInstance = requirementFactory(this.name, this.testCallback, false);
1195
+ newInstance.setChildren(this.children);
1196
+ return newInstance;
1225
1197
  }
1226
- getVersionsAsList() {
1227
- this.assertPackageIsLoaded(this.npmPackage);
1228
- return Object.values(this.npmPackage.versions);
1198
+ asRequired() {
1199
+ const newInstance = requirementFactory(this.name, this.testCallback, true);
1200
+ newInstance.setChildren(this.children);
1201
+ return newInstance;
1229
1202
  }
1230
- findVersionsInRange(range) {
1231
- const versions = this.getVersionsAsList();
1232
- return versions.filter((v) => range.test(v.version)).filter((v) => isLiteralSemVer(v.version)).sort((v1, v2) => semver.compare(v1.version, v2.version));
1203
+ async test(context) {
1204
+ try {
1205
+ await this.testCallback?.(context);
1206
+ return ok();
1207
+ } catch (e) {
1208
+ if (e instanceof Error) {
1209
+ return errored(e);
1210
+ }
1211
+ if (typeof e === "string") {
1212
+ return errored(new Error(e));
1213
+ }
1214
+ return errored(new Error("Unknown error"));
1215
+ }
1233
1216
  }
1234
- findVersion(version2) {
1235
- const versions = this.getVersionsAsList();
1236
- return versions.find((npmVersion) => semver.eq(npmVersion.version, version2));
1217
+ }
1218
+ const ok = () => ({ pass: true, error: null });
1219
+ const errored = (error) => ({ pass: false, error });
1220
+ const requirementFactory = (name, testCallback, isRequired) => new Requirement(name, testCallback, isRequired);
1221
+ const index$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1222
+ __proto__: null,
1223
+ requirementFactory
1224
+ }, Symbol.toStringTag, { value: "Module" }));
1225
+ const REQUIRE_AVAILABLE_NEXT_MAJOR = requirementFactory(
1226
+ "REQUIRE_AVAILABLE_NEXT_MAJOR",
1227
+ (context) => {
1228
+ const { project, target } = context;
1229
+ const currentMajor = project.strapiVersion.major;
1230
+ const targetedMajor = target.major;
1231
+ if (targetedMajor === currentMajor) {
1232
+ throw new Error(`You're already on the latest major version (v${currentMajor})`);
1233
+ }
1237
1234
  }
1238
- async refresh() {
1239
- const response = await fetch(this.packageURL);
1240
- assert(response.ok, `Request failed for ${this.packageURL}`);
1241
- this.npmPackage = await response.json();
1242
- return this;
1235
+ );
1236
+ const REQUIRE_LATEST_FOR_CURRENT_MAJOR = requirementFactory(
1237
+ "REQUIRE_LATEST_FOR_CURRENT_MAJOR",
1238
+ (context) => {
1239
+ const { project, target, npmVersionsMatches } = context;
1240
+ const { major: currentMajor } = project.strapiVersion;
1241
+ const invalidMatches = npmVersionsMatches.filter(
1242
+ (match) => semVerFactory(match.version).major === currentMajor
1243
+ );
1244
+ if (invalidMatches.length > 0) {
1245
+ const invalidVersions = invalidMatches.map((match) => match.version);
1246
+ const invalidVersionsCount = invalidVersions.length;
1247
+ throw new Error(
1248
+ `Doing a major upgrade requires to be on the latest v${currentMajor} version, but found ${invalidVersionsCount} versions between the current one and ${target}. Please upgrade to ${invalidVersions.at(-1)} and try again.`
1249
+ );
1250
+ }
1243
1251
  }
1244
- versionExists(version2) {
1245
- return this.findVersion(version2) !== void 0;
1252
+ );
1253
+ const REQUIRE_GIT_CLEAN_REPOSITORY = requirementFactory(
1254
+ "REQUIRE_GIT_CLEAN_REPOSITORY",
1255
+ async (context) => {
1256
+ const git = simpleGit({ baseDir: context.project.cwd });
1257
+ const status = await git.status();
1258
+ if (!status.isClean()) {
1259
+ throw new Error(
1260
+ "Repository is not clean. Please commit or stash any changes before upgrading"
1261
+ );
1262
+ }
1246
1263
  }
1247
- }
1248
- const npmPackageFactory = (name) => new Package(name);
1264
+ );
1265
+ const REQUIRE_GIT_REPOSITORY = requirementFactory(
1266
+ "REQUIRE_GIT_REPOSITORY",
1267
+ async (context) => {
1268
+ const git = simpleGit({ baseDir: context.project.cwd });
1269
+ const isRepo = await git.checkIsRepo();
1270
+ if (!isRepo) {
1271
+ throw new Error("Not a git repository (or any of the parent directories)");
1272
+ }
1273
+ }
1274
+ ).addChild(REQUIRE_GIT_CLEAN_REPOSITORY.asOptional());
1275
+ const REQUIRE_GIT_INSTALLED = requirementFactory(
1276
+ "REQUIRE_GIT_INSTALLED",
1277
+ async (context) => {
1278
+ const git = simpleGit({ baseDir: context.project.cwd });
1279
+ try {
1280
+ await git.version();
1281
+ } catch {
1282
+ throw new Error("Git is not installed");
1283
+ }
1284
+ }
1285
+ ).addChild(REQUIRE_GIT_REPOSITORY.asOptional());
1286
+ const REQUIRE_GIT = requirementFactory("REQUIRE_GIT", null).addChild(
1287
+ REQUIRE_GIT_INSTALLED.asOptional()
1288
+ );
1289
+ const latest = async (upgrader, options) => {
1290
+ if (options.target !== ReleaseType.Latest) {
1291
+ return;
1292
+ }
1293
+ const npmPackage = upgrader.getNPMPackage();
1294
+ const target = upgrader.getTarget();
1295
+ const project = upgrader.getProject();
1296
+ const { strapiVersion: current } = project;
1297
+ const fTargetMajor = highlight(`v${target.major}`);
1298
+ const fCurrentMajor = highlight(`v${current.major}`);
1299
+ const fTarget = version(target);
1300
+ const fCurrent = version(current);
1301
+ const isMajorUpgrade = target.major > current.major;
1302
+ if (isMajorUpgrade) {
1303
+ options.logger.warn(
1304
+ `Detected a major upgrade for the "${highlight(ReleaseType.Latest)}" tag: ${fCurrent} > ${fTarget}`
1305
+ );
1306
+ const newerPackageRelease = npmPackage.findVersionsInRange(rangeFactory(`>${current.raw} <${target.major}`)).at(-1);
1307
+ if (newerPackageRelease) {
1308
+ const fLatest = version(semVerFactory(newerPackageRelease.version));
1309
+ options.logger.warn(
1310
+ `It's recommended to first upgrade to the latest version of ${fCurrentMajor} (${fLatest}) before upgrading to ${fTargetMajor}.`
1311
+ );
1312
+ }
1313
+ const proceedAnyway = await upgrader.confirm(`I know what I'm doing. Proceed anyway!`);
1314
+ if (!proceedAnyway) {
1315
+ throw new AbortedError();
1316
+ }
1317
+ }
1318
+ };
1249
1319
  const upgrade = async (options) => {
1250
1320
  const timer = timerFactory();
1251
1321
  const { logger, codemodsTarget } = options;
@@ -1257,22 +1327,34 @@ const upgrade = async (options) => {
1257
1327
  `The "${options.target}" upgrade can only be run on a Strapi project; for plugins, please use "codemods".`
1258
1328
  );
1259
1329
  }
1330
+ logger.debug(
1331
+ `Application: VERSION=${version(project.packageJSON.version)}; STRAPI_VERSION=${version(project.strapiVersion)}`
1332
+ );
1260
1333
  const npmPackage = npmPackageFactory(STRAPI_PACKAGE_NAME);
1261
1334
  await npmPackage.refresh();
1262
1335
  const upgrader = upgraderFactory(project, options.target, npmPackage).dry(options.dry ?? false).onConfirm(options.confirm ?? null).setLogger(logger);
1263
1336
  if (codemodsTarget !== void 0) {
1264
1337
  upgrader.overrideCodemodsTarget(codemodsTarget);
1265
1338
  }
1266
- if (options.target === ReleaseType.Major) {
1267
- upgrader.addRequirement(REQUIRE_AVAILABLE_NEXT_MAJOR).addRequirement(REQUIRE_LATEST_FOR_CURRENT_MAJOR);
1268
- }
1269
- upgrader.addRequirement(REQUIRE_GIT.asOptional());
1339
+ await runUpgradePrompts(upgrader, options);
1340
+ addUpgradeRequirements(upgrader, options);
1270
1341
  const upgradeReport = await upgrader.upgrade();
1271
1342
  if (!upgradeReport.success) {
1272
1343
  throw upgradeReport.error;
1273
1344
  }
1274
1345
  timer.stop();
1275
- logger.info(`Completed in ${durationMs(timer.elapsedMs)}`);
1346
+ logger.info(`Completed in ${durationMs(timer.elapsedMs)}ms`);
1347
+ };
1348
+ const runUpgradePrompts = async (upgrader, options) => {
1349
+ if (options.target === ReleaseType.Latest) {
1350
+ await latest(upgrader, options);
1351
+ }
1352
+ };
1353
+ const addUpgradeRequirements = (upgrader, options) => {
1354
+ if (options.target === ReleaseType.Major) {
1355
+ upgrader.addRequirement(REQUIRE_AVAILABLE_NEXT_MAJOR).addRequirement(REQUIRE_LATEST_FOR_CURRENT_MAJOR);
1356
+ }
1357
+ upgrader.addRequirement(REQUIRE_GIT.asOptional());
1276
1358
  };
1277
1359
  const resolvePath = (cwd) => path$1.resolve(cwd ?? process.cwd());
1278
1360
  const getRangeFromTarget = (currentVersion, target) => {
@@ -1281,6 +1363,8 @@ const getRangeFromTarget = (currentVersion, target) => {
1281
1363
  }
1282
1364
  const { major, minor, patch } = currentVersion;
1283
1365
  switch (target) {
1366
+ case ReleaseType.Latest:
1367
+ throw new Error("Can't use <latest> to create a codemods range: not implemented");
1284
1368
  case ReleaseType.Major:
1285
1369
  return rangeFactory(`${major}`);
1286
1370
  case ReleaseType.Minor:
@@ -1438,18 +1522,18 @@ const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
1438
1522
  }, Symbol.toStringTag, { value: "Module" }));
1439
1523
  const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1440
1524
  __proto__: null,
1441
- codemod: index$7,
1442
- codemodRepository: index$6,
1525
+ codemod: index$8,
1526
+ codemodRepository: index$7,
1443
1527
  error: index$9,
1444
- f: index$8,
1528
+ f: index$f,
1445
1529
  fileScanner: index$d,
1446
1530
  logger: index$3,
1447
1531
  project: index$a,
1448
1532
  report: index$2,
1449
- requirement: index$g,
1533
+ requirement: index$5,
1450
1534
  runner: index$1,
1451
- timer: index$f,
1452
- upgrader: index$5,
1535
+ timer: index$g,
1536
+ upgrader: index$6,
1453
1537
  version: index$e
1454
1538
  }, Symbol.toStringTag, { value: "Module" }));
1455
1539
  export {