@strapi/upgrade 0.0.0-experimental.fed75ee8e64c57dbed0b670b25ef026b69baab10 → 0.0.0-next.3c5400321681b66eb35ab84c11113a78c1d9386e

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,51 +40,96 @@ const constants$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP
153
40
  __proto__: null,
154
41
  ONE_SECOND_MS
155
42
  }, Symbol.toStringTag, { value: "Module" }));
156
- const index$f = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
43
+ const index$g = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
157
44
  __proto__: null,
158
45
  constants: constants$4,
159
46
  timerFactory
160
47
  }, Symbol.toStringTag, { value: "Module" }));
161
- class JSONTransformAPI {
162
- json;
163
- constructor(json) {
164
- this.json = cloneDeep(json);
165
- }
166
- get(path2, defaultValue) {
167
- if (!path2) {
168
- return this.root();
169
- }
170
- return cloneDeep(get(path2, this.json) ?? defaultValue);
171
- }
172
- has(path2) {
173
- return has(path2, this.json);
174
- }
175
- merge(other) {
176
- this.json = merge(other, this.json);
177
- return this;
178
- }
179
- root() {
180
- return cloneDeep(this.json);
181
- }
182
- set(path2, value) {
183
- this.json = set(path2, value, this.json);
184
- return this;
185
- }
186
- remove(path2) {
187
- this.json = omit(path2, this.json);
188
- return this;
189
- }
190
- }
191
- const createJSONTransformAPI = (object) => new JSONTransformAPI(object);
192
- const readJSON = async (path2) => {
193
- const buffer = await fse.readFile(path2);
194
- return JSON.parse(buffer.toString());
48
+ const path = (path2) => chalk.blue(path2);
49
+ const version = (version2) => {
50
+ return chalk.italic.yellow(`v${version2}`);
195
51
  };
196
- const saveJSON = async (path2, json) => {
197
- const jsonAsString = `${JSON.stringify(json, null, 2)}
198
- `;
199
- await fse.writeFile(path2, jsonAsString);
52
+ const codemodUID = (uid) => {
53
+ return chalk.bold.cyan(uid);
54
+ };
55
+ const projectDetails = (project) => {
56
+ return `Project: TYPE=${projectType(project.type)}; CWD=${path(project.cwd)}; PATHS=${project.paths.map(path)}`;
57
+ };
58
+ const projectType = (type) => chalk.cyan(type);
59
+ const versionRange = (range) => chalk.italic.yellow(range.raw);
60
+ const transform = (transformFilePath) => chalk.cyan(transformFilePath);
61
+ const highlight = (arg) => chalk.bold.underline(arg);
62
+ const upgradeStep = (text, step) => {
63
+ return chalk.bold(`(${step[0]}/${step[1]}) ${text}...`);
64
+ };
65
+ const reports = (reports2) => {
66
+ const rows = reports2.map(({ codemod, report }, i) => {
67
+ const fIndex = chalk.grey(i);
68
+ const fVersion = chalk.magenta(codemod.version);
69
+ const fKind = chalk.yellow(codemod.kind);
70
+ const fFormattedTransformPath = chalk.cyan(codemod.format());
71
+ const fTimeElapsed = i === 0 ? `${report.timeElapsed}s ${chalk.dim.italic("(cold start)")}` : `${report.timeElapsed}s`;
72
+ const fAffected = report.ok > 0 ? chalk.green(report.ok) : chalk.grey(0);
73
+ const fUnchanged = report.ok === 0 ? chalk.red(report.nochange) : chalk.grey(report.nochange);
74
+ return [fIndex, fVersion, fKind, fFormattedTransformPath, fAffected, fUnchanged, fTimeElapsed];
75
+ });
76
+ const table = new CliTable3({
77
+ style: { compact: true },
78
+ head: [
79
+ chalk.bold.grey("N°"),
80
+ chalk.bold.magenta("Version"),
81
+ chalk.bold.yellow("Kind"),
82
+ chalk.bold.cyan("Name"),
83
+ chalk.bold.green("Affected"),
84
+ chalk.bold.red("Unchanged"),
85
+ chalk.bold.blue("Duration")
86
+ ]
87
+ });
88
+ table.push(...rows);
89
+ return table.toString();
90
+ };
91
+ const codemodList = (codemods) => {
92
+ const rows = codemods.map((codemod, index2) => {
93
+ const fIndex = chalk.grey(index2);
94
+ const fVersion = chalk.magenta(codemod.version);
95
+ const fKind = chalk.yellow(codemod.kind);
96
+ const fName = chalk.blue(codemod.format());
97
+ const fUID = codemodUID(codemod.uid);
98
+ return [fIndex, fVersion, fKind, fName, fUID];
99
+ });
100
+ const table = new CliTable3({
101
+ style: { compact: true },
102
+ head: [
103
+ chalk.bold.grey("N°"),
104
+ chalk.bold.magenta("Version"),
105
+ chalk.bold.yellow("Kind"),
106
+ chalk.bold.blue("Name"),
107
+ chalk.bold.cyan("UID")
108
+ ]
109
+ });
110
+ table.push(...rows);
111
+ return table.toString();
200
112
  };
113
+ const durationMs = (elapsedMs) => {
114
+ const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
115
+ return `${elapsedSeconds}s`;
116
+ };
117
+ const index$f = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
118
+ __proto__: null,
119
+ codemodList,
120
+ codemodUID,
121
+ durationMs,
122
+ highlight,
123
+ path,
124
+ projectDetails,
125
+ projectType,
126
+ reports,
127
+ transform,
128
+ upgradeStep,
129
+ version,
130
+ versionRange
131
+ }, Symbol.toStringTag, { value: "Module" }));
132
+ const NPM_REGISTRY_URL = "https://registry.npmjs.org";
201
133
  var ReleaseType = /* @__PURE__ */ ((ReleaseType2) => {
202
134
  ReleaseType2["Major"] = "major";
203
135
  ReleaseType2["Minor"] = "minor";
@@ -228,16 +160,16 @@ const rangeFactory = (range) => {
228
160
  const rangeFromReleaseType = (current, identifier) => {
229
161
  switch (identifier) {
230
162
  case ReleaseType.Major: {
231
- const nextMajor = semver.inc(current, "major");
232
- return rangeFactory(`>${current.raw} <=${nextMajor}`);
233
- }
234
- case ReleaseType.Patch: {
235
- const minor = semver.inc(current, "minor");
236
- return rangeFactory(`>${current.raw} <${minor}`);
163
+ const nextMajor = semVerFactory(current.raw).inc("major");
164
+ return rangeFactory(`>${current.raw} <=${nextMajor.major}`);
237
165
  }
238
166
  case ReleaseType.Minor: {
239
- const major = semver.inc(current, "major");
240
- return rangeFactory(`>${current.raw} <${major}`);
167
+ const nextMajor = semVerFactory(current.raw).inc("major");
168
+ return rangeFactory(`>${current.raw} <${nextMajor.raw}`);
169
+ }
170
+ case ReleaseType.Patch: {
171
+ const nextMinor = semVerFactory(current.raw).inc("minor");
172
+ return rangeFactory(`>${current.raw} <${nextMinor.raw}`);
241
173
  }
242
174
  default: {
243
175
  throw new Error("Not implemented");
@@ -271,6 +203,48 @@ const index$e = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
271
203
  rangeFromVersions,
272
204
  semVerFactory
273
205
  }, Symbol.toStringTag, { value: "Module" }));
206
+ class Package {
207
+ name;
208
+ packageURL;
209
+ npmPackage;
210
+ constructor(name) {
211
+ this.name = name;
212
+ this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
213
+ this.npmPackage = null;
214
+ }
215
+ get isLoaded() {
216
+ return this.npmPackage !== null;
217
+ }
218
+ assertPackageIsLoaded(npmPackage) {
219
+ assert(this.isLoaded, "The package is not loaded yet");
220
+ }
221
+ getVersionsDict() {
222
+ this.assertPackageIsLoaded(this.npmPackage);
223
+ return this.npmPackage.versions;
224
+ }
225
+ getVersionsAsList() {
226
+ this.assertPackageIsLoaded(this.npmPackage);
227
+ return Object.values(this.npmPackage.versions);
228
+ }
229
+ findVersionsInRange(range) {
230
+ const versions = this.getVersionsAsList();
231
+ return versions.filter((v) => range.test(v.version)).filter((v) => isLiteralSemVer(v.version)).sort((v1, v2) => semver.compare(v1.version, v2.version));
232
+ }
233
+ findVersion(version2) {
234
+ const versions = this.getVersionsAsList();
235
+ return versions.find((npmVersion) => semver.eq(npmVersion.version, version2));
236
+ }
237
+ async refresh() {
238
+ const response = await fetch(this.packageURL);
239
+ assert(response.ok, `Request failed for ${this.packageURL}`);
240
+ this.npmPackage = await response.json();
241
+ return this;
242
+ }
243
+ versionExists(version2) {
244
+ return this.findVersion(version2) !== void 0;
245
+ }
246
+ }
247
+ const npmPackageFactory = (name) => new Package(name);
274
248
  class FileScanner {
275
249
  cwd;
276
250
  constructor(cwd) {
@@ -303,20 +277,60 @@ class AbstractRunner {
303
277
  const runConfiguration = { ...this.configuration, ...configuration };
304
278
  return this.runner(codemod.path, this.paths, runConfiguration);
305
279
  }
306
- }
307
- class CodeRunner extends AbstractRunner {
308
- runner = run;
309
- valid(codemod) {
310
- return codemod.kind === "code";
280
+ }
281
+ class CodeRunner extends AbstractRunner {
282
+ runner = run;
283
+ valid(codemod) {
284
+ return codemod.kind === "code";
285
+ }
286
+ }
287
+ const codeRunnerFactory = (paths, configuration) => {
288
+ return new CodeRunner(paths, configuration);
289
+ };
290
+ const index$c = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
291
+ __proto__: null,
292
+ codeRunnerFactory
293
+ }, Symbol.toStringTag, { value: "Module" }));
294
+ class JSONTransformAPI {
295
+ json;
296
+ constructor(json) {
297
+ this.json = cloneDeep(json);
298
+ }
299
+ get(path2, defaultValue) {
300
+ if (!path2) {
301
+ return this.root();
302
+ }
303
+ return cloneDeep(get(path2, this.json) ?? defaultValue);
304
+ }
305
+ has(path2) {
306
+ return has(path2, this.json);
307
+ }
308
+ merge(other) {
309
+ this.json = merge(other, this.json);
310
+ return this;
311
+ }
312
+ root() {
313
+ return cloneDeep(this.json);
314
+ }
315
+ set(path2, value) {
316
+ this.json = set(path2, value, this.json);
317
+ return this;
318
+ }
319
+ remove(path2) {
320
+ this.json = omit(path2, this.json);
321
+ return this;
311
322
  }
312
323
  }
313
- const codeRunnerFactory = (paths, configuration) => {
314
- return new CodeRunner(paths, configuration);
324
+ const createJSONTransformAPI = (object) => new JSONTransformAPI(object);
325
+ const readJSON = async (path2) => {
326
+ const buffer = await fse.readFile(path2);
327
+ return JSON.parse(buffer.toString());
328
+ };
329
+ const saveJSON = async (path2, json) => {
330
+ const jsonAsString = `${JSON.stringify(json, null, 2)}
331
+ `;
332
+ await fse.writeFile(path2, jsonAsString);
315
333
  };
316
- const index$c = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
317
- __proto__: null,
318
- codeRunnerFactory
319
- }, Symbol.toStringTag, { value: "Module" }));
320
334
  const transformJSON = async (codemodPath, paths, config) => {
321
335
  const { dry } = config;
322
336
  const startTime = process.hrtime();
@@ -625,6 +639,13 @@ class UnexpectedError extends Error {
625
639
  super("Unexpected Error");
626
640
  }
627
641
  }
642
+ class NPMCandidateNotFoundError extends Error {
643
+ target;
644
+ constructor(target, message = `Couldn't find a valid NPM candidate for "${target}"`) {
645
+ super(message);
646
+ this.target = target;
647
+ }
648
+ }
628
649
  const unknownToError = (e) => {
629
650
  if (e instanceof Error) {
630
651
  return e;
@@ -636,93 +657,10 @@ const unknownToError = (e) => {
636
657
  };
637
658
  const index$9 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
638
659
  __proto__: null,
660
+ NPMCandidateNotFoundError,
639
661
  UnexpectedError,
640
662
  unknownToError
641
663
  }, 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
664
  const CODEMOD_CODE_SUFFIX = "code";
727
665
  const CODEMOD_JSON_SUFFIX = "json";
728
666
  const CODEMOD_ALLOWED_SUFFIXES = [CODEMOD_CODE_SUFFIX, CODEMOD_JSON_SUFFIX];
@@ -775,7 +713,7 @@ class Codemod {
775
713
  }
776
714
  }
777
715
  const codemodFactory = (options) => new Codemod(options);
778
- const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
716
+ const index$8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
779
717
  __proto__: null,
780
718
  codemodFactory,
781
719
  constants: constants$2
@@ -887,7 +825,7 @@ const parseCodemodKindFromFilename = (filename) => {
887
825
  const codemodRepositoryFactory = (cwd = INTERNAL_CODEMODS_DIRECTORY) => {
888
826
  return new CodemodRepository(cwd);
889
827
  };
890
- const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
828
+ const index$7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
891
829
  __proto__: null,
892
830
  codemodRepositoryFactory,
893
831
  constants: constants$1
@@ -1152,7 +1090,7 @@ class Upgrader {
1152
1090
  const packageManagerName = await packageManager.getPreferred(projectPath);
1153
1091
  this.logger?.debug?.(`Using ${highlight(packageManagerName)} as package manager`);
1154
1092
  if (this.isDry) {
1155
- this.logger?.debug?.(`Skipping dependencies installation (${chalk.italic("dry mode")}`);
1093
+ this.logger?.debug?.(`Skipping dependencies installation (${chalk.italic("dry mode")})`);
1156
1094
  return;
1157
1095
  }
1158
1096
  await packageManager.installDependencies(projectPath, packageManagerName, {
@@ -1171,23 +1109,28 @@ class Upgrader {
1171
1109
  }
1172
1110
  const resolveNPMTarget = (project, target, npmPackage) => {
1173
1111
  if (isSemverInstance(target)) {
1174
- return npmPackage.findVersion(target);
1112
+ const version2 = npmPackage.findVersion(target);
1113
+ if (!version2) {
1114
+ throw new NPMCandidateNotFoundError(target);
1115
+ }
1116
+ return version2;
1175
1117
  }
1176
1118
  if (isSemVerReleaseType(target)) {
1177
1119
  const range = rangeFromVersions(project.strapiVersion, target);
1178
1120
  const npmVersionsMatches = npmPackage.findVersionsInRange(range);
1179
- return npmVersionsMatches.at(-1);
1121
+ const version2 = npmVersionsMatches.at(-1);
1122
+ if (!version2) {
1123
+ throw new NPMCandidateNotFoundError(range, `The project is already up-to-date (${target})`);
1124
+ }
1125
+ return version2;
1180
1126
  }
1181
- return void 0;
1127
+ throw new NPMCandidateNotFoundError(target);
1182
1128
  };
1183
1129
  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);
1130
+ const npmTarget = resolveNPMTarget(project, target, npmPackage);
1131
+ const semverTarget = semVerFactory(npmTarget.version);
1189
1132
  if (semver.eq(semverTarget, project.strapiVersion)) {
1190
- throw new Error(`The project is already on ${version(semverTarget)}`);
1133
+ throw new Error(`The project is already using v${semverTarget}`);
1191
1134
  }
1192
1135
  return new Upgrader(project, semverTarget, npmPackage);
1193
1136
  };
@@ -1198,54 +1141,126 @@ const constants = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
1198
1141
  __proto__: null,
1199
1142
  STRAPI_PACKAGE_NAME
1200
1143
  }, Symbol.toStringTag, { value: "Module" }));
1201
- const index$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1144
+ const index$6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1202
1145
  __proto__: null,
1203
1146
  constants,
1204
1147
  upgraderFactory
1205
1148
  }, Symbol.toStringTag, { value: "Module" }));
1206
- const NPM_REGISTRY_URL = "https://registry.npmjs.org";
1207
- class Package {
1149
+ class Requirement {
1150
+ isRequired;
1208
1151
  name;
1209
- packageURL;
1210
- npmPackage;
1211
- constructor(name) {
1152
+ testCallback;
1153
+ children;
1154
+ constructor(name, testCallback, isRequired) {
1212
1155
  this.name = name;
1213
- this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
1214
- this.npmPackage = null;
1156
+ this.testCallback = testCallback;
1157
+ this.isRequired = isRequired ?? true;
1158
+ this.children = [];
1215
1159
  }
1216
- get isLoaded() {
1217
- return this.npmPackage !== null;
1160
+ setChildren(children) {
1161
+ this.children = children;
1162
+ return this;
1218
1163
  }
1219
- assertPackageIsLoaded(npmPackage) {
1220
- assert(this.isLoaded, "The package is not loaded yet");
1164
+ addChild(child) {
1165
+ this.children.push(child);
1166
+ return this;
1221
1167
  }
1222
- getVersionsDict() {
1223
- this.assertPackageIsLoaded(this.npmPackage);
1224
- return this.npmPackage.versions;
1168
+ asOptional() {
1169
+ const newInstance = requirementFactory(this.name, this.testCallback, false);
1170
+ newInstance.setChildren(this.children);
1171
+ return newInstance;
1225
1172
  }
1226
- getVersionsAsList() {
1227
- this.assertPackageIsLoaded(this.npmPackage);
1228
- return Object.values(this.npmPackage.versions);
1173
+ asRequired() {
1174
+ const newInstance = requirementFactory(this.name, this.testCallback, true);
1175
+ newInstance.setChildren(this.children);
1176
+ return newInstance;
1229
1177
  }
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));
1178
+ async test(context) {
1179
+ try {
1180
+ await this.testCallback?.(context);
1181
+ return ok();
1182
+ } catch (e) {
1183
+ if (e instanceof Error) {
1184
+ return errored(e);
1185
+ }
1186
+ if (typeof e === "string") {
1187
+ return errored(new Error(e));
1188
+ }
1189
+ return errored(new Error("Unknown error"));
1190
+ }
1233
1191
  }
1234
- findVersion(version2) {
1235
- const versions = this.getVersionsAsList();
1236
- return versions.find((npmVersion) => semver.eq(npmVersion.version, version2));
1192
+ }
1193
+ const ok = () => ({ pass: true, error: null });
1194
+ const errored = (error) => ({ pass: false, error });
1195
+ const requirementFactory = (name, testCallback, isRequired) => new Requirement(name, testCallback, isRequired);
1196
+ const index$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1197
+ __proto__: null,
1198
+ requirementFactory
1199
+ }, Symbol.toStringTag, { value: "Module" }));
1200
+ const REQUIRE_AVAILABLE_NEXT_MAJOR = requirementFactory(
1201
+ "REQUIRE_AVAILABLE_NEXT_MAJOR",
1202
+ (context) => {
1203
+ const { project, target } = context;
1204
+ const currentMajor = project.strapiVersion.major;
1205
+ const targetedMajor = target.major;
1206
+ if (targetedMajor === currentMajor) {
1207
+ throw new Error(`You're already on the latest major version (v${currentMajor})`);
1208
+ }
1237
1209
  }
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;
1210
+ );
1211
+ const REQUIRE_LATEST_FOR_CURRENT_MAJOR = requirementFactory(
1212
+ "REQUIRE_LATEST_FOR_CURRENT_MAJOR",
1213
+ (context) => {
1214
+ const { project, target, npmVersionsMatches } = context;
1215
+ const { major: currentMajor } = project.strapiVersion;
1216
+ const invalidMatches = npmVersionsMatches.filter(
1217
+ (match) => semVerFactory(match.version).major === currentMajor
1218
+ );
1219
+ if (invalidMatches.length > 0) {
1220
+ const invalidVersions = invalidMatches.map((match) => match.version);
1221
+ const invalidVersionsCount = invalidVersions.length;
1222
+ throw new Error(
1223
+ `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.`
1224
+ );
1225
+ }
1243
1226
  }
1244
- versionExists(version2) {
1245
- return this.findVersion(version2) !== void 0;
1227
+ );
1228
+ const REQUIRE_GIT_CLEAN_REPOSITORY = requirementFactory(
1229
+ "REQUIRE_GIT_CLEAN_REPOSITORY",
1230
+ async (context) => {
1231
+ const git = simpleGit({ baseDir: context.project.cwd });
1232
+ const status = await git.status();
1233
+ if (!status.isClean()) {
1234
+ throw new Error(
1235
+ "Repository is not clean. Please commit or stash any changes before upgrading"
1236
+ );
1237
+ }
1246
1238
  }
1247
- }
1248
- const npmPackageFactory = (name) => new Package(name);
1239
+ );
1240
+ const REQUIRE_GIT_REPOSITORY = requirementFactory(
1241
+ "REQUIRE_GIT_REPOSITORY",
1242
+ async (context) => {
1243
+ const git = simpleGit({ baseDir: context.project.cwd });
1244
+ const isRepo = await git.checkIsRepo();
1245
+ if (!isRepo) {
1246
+ throw new Error("Not a git repository (or any of the parent directories)");
1247
+ }
1248
+ }
1249
+ ).addChild(REQUIRE_GIT_CLEAN_REPOSITORY.asOptional());
1250
+ const REQUIRE_GIT_INSTALLED = requirementFactory(
1251
+ "REQUIRE_GIT_INSTALLED",
1252
+ async (context) => {
1253
+ const git = simpleGit({ baseDir: context.project.cwd });
1254
+ try {
1255
+ await git.version();
1256
+ } catch {
1257
+ throw new Error("Git is not installed");
1258
+ }
1259
+ }
1260
+ ).addChild(REQUIRE_GIT_REPOSITORY.asOptional());
1261
+ const REQUIRE_GIT = requirementFactory("REQUIRE_GIT", null).addChild(
1262
+ REQUIRE_GIT_INSTALLED.asOptional()
1263
+ );
1249
1264
  const upgrade = async (options) => {
1250
1265
  const timer = timerFactory();
1251
1266
  const { logger, codemodsTarget } = options;
@@ -1257,6 +1272,9 @@ const upgrade = async (options) => {
1257
1272
  `The "${options.target}" upgrade can only be run on a Strapi project; for plugins, please use "codemods".`
1258
1273
  );
1259
1274
  }
1275
+ logger.debug(
1276
+ `Application: VERSION=${version(project.packageJSON.version)}; STRAPI_VERSION=${version(project.strapiVersion)}`
1277
+ );
1260
1278
  const npmPackage = npmPackageFactory(STRAPI_PACKAGE_NAME);
1261
1279
  await npmPackage.refresh();
1262
1280
  const upgrader = upgraderFactory(project, options.target, npmPackage).dry(options.dry ?? false).onConfirm(options.confirm ?? null).setLogger(logger);
@@ -1272,7 +1290,7 @@ const upgrade = async (options) => {
1272
1290
  throw upgradeReport.error;
1273
1291
  }
1274
1292
  timer.stop();
1275
- logger.info(`Completed in ${durationMs(timer.elapsedMs)}`);
1293
+ logger.info(`Completed in ${durationMs(timer.elapsedMs)}ms`);
1276
1294
  };
1277
1295
  const resolvePath = (cwd) => path$1.resolve(cwd ?? process.cwd());
1278
1296
  const getRangeFromTarget = (currentVersion, target) => {
@@ -1438,18 +1456,18 @@ const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
1438
1456
  }, Symbol.toStringTag, { value: "Module" }));
1439
1457
  const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1440
1458
  __proto__: null,
1441
- codemod: index$7,
1442
- codemodRepository: index$6,
1459
+ codemod: index$8,
1460
+ codemodRepository: index$7,
1443
1461
  error: index$9,
1444
- f: index$8,
1462
+ f: index$f,
1445
1463
  fileScanner: index$d,
1446
1464
  logger: index$3,
1447
1465
  project: index$a,
1448
1466
  report: index$2,
1449
- requirement: index$g,
1467
+ requirement: index$5,
1450
1468
  runner: index$1,
1451
- timer: index$f,
1452
- upgrader: index$5,
1469
+ timer: index$g,
1470
+ upgrader: index$6,
1453
1471
  version: index$e
1454
1472
  }, Symbol.toStringTag, { value: "Module" }));
1455
1473
  export {