@strapi/upgrade 0.0.0-experimental.fdacf4285d1cada9d94ab4dcd756c5362cba1b54 → 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/cli.js CHANGED
@@ -5,26 +5,26 @@ const commander = require("commander");
5
5
  const prompts = require("prompts");
6
6
  const semver = require("semver");
7
7
  const path$1 = require("node:path");
8
- const simpleGit = require("simple-git");
9
- const utils = require("@strapi/utils");
10
- const fp = require("lodash/fp");
11
- const fse = require("fs-extra");
8
+ const CliTable3 = require("cli-table3");
12
9
  const assert = require("node:assert");
10
+ const fse = require("fs-extra");
13
11
  const fastglob = require("fast-glob");
14
12
  const Runner = require("jscodeshift/src/Runner");
13
+ const fp = require("lodash/fp");
15
14
  const node = require("esbuild-register/dist/node");
16
- const CliTable3 = require("cli-table3");
15
+ const utils = require("@strapi/utils");
16
+ const simpleGit = require("simple-git");
17
17
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
18
18
  const os__default = /* @__PURE__ */ _interopDefault(os);
19
19
  const chalk__default = /* @__PURE__ */ _interopDefault(chalk);
20
20
  const prompts__default = /* @__PURE__ */ _interopDefault(prompts);
21
21
  const semver__default = /* @__PURE__ */ _interopDefault(semver);
22
22
  const path__default = /* @__PURE__ */ _interopDefault(path$1);
23
- const simpleGit__default = /* @__PURE__ */ _interopDefault(simpleGit);
24
- const fse__default = /* @__PURE__ */ _interopDefault(fse);
23
+ const CliTable3__default = /* @__PURE__ */ _interopDefault(CliTable3);
25
24
  const assert__default = /* @__PURE__ */ _interopDefault(assert);
25
+ const fse__default = /* @__PURE__ */ _interopDefault(fse);
26
26
  const fastglob__default = /* @__PURE__ */ _interopDefault(fastglob);
27
- const CliTable3__default = /* @__PURE__ */ _interopDefault(CliTable3);
27
+ const simpleGit__default = /* @__PURE__ */ _interopDefault(simpleGit);
28
28
  class Logger {
29
29
  isDebug;
30
30
  isSilent;
@@ -121,16 +121,16 @@ const rangeFactory = (range) => {
121
121
  const rangeFromReleaseType = (current, identifier) => {
122
122
  switch (identifier) {
123
123
  case ReleaseType.Major: {
124
- const nextMajor = semver__default.default.inc(current, "major");
125
- return rangeFactory(`>${current.raw} <=${nextMajor}`);
126
- }
127
- case ReleaseType.Patch: {
128
- const minor = semver__default.default.inc(current, "minor");
129
- return rangeFactory(`>${current.raw} <${minor}`);
124
+ const nextMajor = semVerFactory(current.raw).inc("major");
125
+ return rangeFactory(`>${current.raw} <=${nextMajor.major}`);
130
126
  }
131
127
  case ReleaseType.Minor: {
132
- const major = semver__default.default.inc(current, "major");
133
- return rangeFactory(`>${current.raw} <${major}`);
128
+ const nextMajor = semVerFactory(current.raw).inc("major");
129
+ return rangeFactory(`>${current.raw} <${nextMajor.raw}`);
130
+ }
131
+ case ReleaseType.Patch: {
132
+ const nextMinor = semVerFactory(current.raw).inc("minor");
133
+ return rangeFactory(`>${current.raw} <${nextMinor.raw}`);
134
134
  }
135
135
  default: {
136
136
  throw new Error("Not implemented");
@@ -159,115 +159,6 @@ const handleError = (err, isSilent) => {
159
159
  }
160
160
  process.exit(1);
161
161
  };
162
- class Requirement {
163
- isRequired;
164
- name;
165
- testCallback;
166
- children;
167
- constructor(name, testCallback, isRequired) {
168
- this.name = name;
169
- this.testCallback = testCallback;
170
- this.isRequired = isRequired ?? true;
171
- this.children = [];
172
- }
173
- setChildren(children) {
174
- this.children = children;
175
- return this;
176
- }
177
- addChild(child) {
178
- this.children.push(child);
179
- return this;
180
- }
181
- asOptional() {
182
- const newInstance = requirementFactory(this.name, this.testCallback, false);
183
- newInstance.setChildren(this.children);
184
- return newInstance;
185
- }
186
- asRequired() {
187
- const newInstance = requirementFactory(this.name, this.testCallback, true);
188
- newInstance.setChildren(this.children);
189
- return newInstance;
190
- }
191
- async test(context) {
192
- try {
193
- await this.testCallback?.(context);
194
- return ok();
195
- } catch (e) {
196
- if (e instanceof Error) {
197
- return errored(e);
198
- }
199
- if (typeof e === "string") {
200
- return errored(new Error(e));
201
- }
202
- return errored(new Error("Unknown error"));
203
- }
204
- }
205
- }
206
- const ok = () => ({ pass: true, error: null });
207
- const errored = (error) => ({ pass: false, error });
208
- const requirementFactory = (name, testCallback, isRequired) => new Requirement(name, testCallback, isRequired);
209
- const REQUIRE_AVAILABLE_NEXT_MAJOR = requirementFactory(
210
- "REQUIRE_AVAILABLE_NEXT_MAJOR",
211
- (context) => {
212
- const { project, target } = context;
213
- const currentMajor = project.strapiVersion.major;
214
- const targetedMajor = target.major;
215
- if (targetedMajor === currentMajor) {
216
- throw new Error(`You're already on the latest major version (v${currentMajor})`);
217
- }
218
- }
219
- );
220
- const REQUIRE_LATEST_FOR_CURRENT_MAJOR = requirementFactory(
221
- "REQUIRE_LATEST_FOR_CURRENT_MAJOR",
222
- (context) => {
223
- const { project, target, npmVersionsMatches } = context;
224
- if (npmVersionsMatches.length !== 1) {
225
- const invalidVersions = npmVersionsMatches.slice(0, -1);
226
- const invalidVersionsAsSemVer = invalidVersions.map((v) => v.version);
227
- const nbInvalidVersions = npmVersionsMatches.length;
228
- const currentMajor = project.strapiVersion.major;
229
- throw new Error(
230
- `Doing a major upgrade requires to be on the latest v${currentMajor} version, but found ${nbInvalidVersions} versions between the current one and ${target}: ${invalidVersionsAsSemVer}`
231
- );
232
- }
233
- }
234
- );
235
- const REQUIRE_GIT_CLEAN_REPOSITORY = requirementFactory(
236
- "REQUIRE_GIT_CLEAN_REPOSITORY",
237
- async (context) => {
238
- const git = simpleGit__default.default({ baseDir: context.project.cwd });
239
- const status = await git.status();
240
- if (!status.isClean()) {
241
- throw new Error(
242
- "Repository is not clean. Please commit or stash any changes before upgrading"
243
- );
244
- }
245
- }
246
- );
247
- const REQUIRE_GIT_REPOSITORY = requirementFactory(
248
- "REQUIRE_GIT_REPOSITORY",
249
- async (context) => {
250
- const git = simpleGit__default.default({ baseDir: context.project.cwd });
251
- const isRepo = await git.checkIsRepo();
252
- if (!isRepo) {
253
- throw new Error("Not a git repository (or any of the parent directories)");
254
- }
255
- }
256
- ).addChild(REQUIRE_GIT_CLEAN_REPOSITORY.asOptional());
257
- const REQUIRE_GIT_INSTALLED = requirementFactory(
258
- "REQUIRE_GIT_INSTALLED",
259
- async (context) => {
260
- const git = simpleGit__default.default({ baseDir: context.project.cwd });
261
- try {
262
- await git.version();
263
- } catch {
264
- throw new Error("Git is not installed");
265
- }
266
- }
267
- ).addChild(REQUIRE_GIT_REPOSITORY.asOptional());
268
- const REQUIRE_GIT = requirementFactory("REQUIRE_GIT", null).addChild(
269
- REQUIRE_GIT_INSTALLED.asOptional()
270
- );
271
162
  class Timer {
272
163
  interval;
273
164
  constructor() {
@@ -294,46 +185,117 @@ class Timer {
294
185
  }
295
186
  const timerFactory = () => new Timer();
296
187
  const ONE_SECOND_MS = 1e3;
297
- class JSONTransformAPI {
298
- json;
299
- constructor(json) {
300
- this.json = fp.cloneDeep(json);
188
+ const path = (path2) => chalk__default.default.blue(path2);
189
+ const version$1 = (version2) => {
190
+ return chalk__default.default.italic.yellow(`v${version2}`);
191
+ };
192
+ const codemodUID = (uid) => {
193
+ return chalk__default.default.bold.cyan(uid);
194
+ };
195
+ const projectDetails = (project) => {
196
+ return `Project: TYPE=${projectType(project.type)}; CWD=${path(project.cwd)}; PATHS=${project.paths.map(path)}`;
197
+ };
198
+ const projectType = (type) => chalk__default.default.cyan(type);
199
+ const versionRange = (range) => chalk__default.default.italic.yellow(range.raw);
200
+ const highlight = (arg) => chalk__default.default.bold.underline(arg);
201
+ const upgradeStep = (text, step) => {
202
+ return chalk__default.default.bold(`(${step[0]}/${step[1]}) ${text}...`);
203
+ };
204
+ const reports = (reports2) => {
205
+ const rows = reports2.map(({ codemod, report }, i) => {
206
+ const fIndex = chalk__default.default.grey(i);
207
+ const fVersion = chalk__default.default.magenta(codemod.version);
208
+ const fKind = chalk__default.default.yellow(codemod.kind);
209
+ const fFormattedTransformPath = chalk__default.default.cyan(codemod.format());
210
+ const fTimeElapsed = i === 0 ? `${report.timeElapsed}s ${chalk__default.default.dim.italic("(cold start)")}` : `${report.timeElapsed}s`;
211
+ const fAffected = report.ok > 0 ? chalk__default.default.green(report.ok) : chalk__default.default.grey(0);
212
+ const fUnchanged = report.ok === 0 ? chalk__default.default.red(report.nochange) : chalk__default.default.grey(report.nochange);
213
+ return [fIndex, fVersion, fKind, fFormattedTransformPath, fAffected, fUnchanged, fTimeElapsed];
214
+ });
215
+ const table = new CliTable3__default.default({
216
+ style: { compact: true },
217
+ head: [
218
+ chalk__default.default.bold.grey("N°"),
219
+ chalk__default.default.bold.magenta("Version"),
220
+ chalk__default.default.bold.yellow("Kind"),
221
+ chalk__default.default.bold.cyan("Name"),
222
+ chalk__default.default.bold.green("Affected"),
223
+ chalk__default.default.bold.red("Unchanged"),
224
+ chalk__default.default.bold.blue("Duration")
225
+ ]
226
+ });
227
+ table.push(...rows);
228
+ return table.toString();
229
+ };
230
+ const codemodList = (codemods) => {
231
+ const rows = codemods.map((codemod, index) => {
232
+ const fIndex = chalk__default.default.grey(index);
233
+ const fVersion = chalk__default.default.magenta(codemod.version);
234
+ const fKind = chalk__default.default.yellow(codemod.kind);
235
+ const fName = chalk__default.default.blue(codemod.format());
236
+ const fUID = codemodUID(codemod.uid);
237
+ return [fIndex, fVersion, fKind, fName, fUID];
238
+ });
239
+ const table = new CliTable3__default.default({
240
+ style: { compact: true },
241
+ head: [
242
+ chalk__default.default.bold.grey("N°"),
243
+ chalk__default.default.bold.magenta("Version"),
244
+ chalk__default.default.bold.yellow("Kind"),
245
+ chalk__default.default.bold.blue("Name"),
246
+ chalk__default.default.bold.cyan("UID")
247
+ ]
248
+ });
249
+ table.push(...rows);
250
+ return table.toString();
251
+ };
252
+ const durationMs = (elapsedMs) => {
253
+ const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
254
+ return `${elapsedSeconds}s`;
255
+ };
256
+ const NPM_REGISTRY_URL = "https://registry.npmjs.org";
257
+ class Package {
258
+ name;
259
+ packageURL;
260
+ npmPackage;
261
+ constructor(name) {
262
+ this.name = name;
263
+ this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
264
+ this.npmPackage = null;
301
265
  }
302
- get(path2, defaultValue) {
303
- if (!path2) {
304
- return this.root();
305
- }
306
- return fp.cloneDeep(fp.get(path2, this.json) ?? defaultValue);
266
+ get isLoaded() {
267
+ return this.npmPackage !== null;
307
268
  }
308
- has(path2) {
309
- return fp.has(path2, this.json);
269
+ assertPackageIsLoaded(npmPackage) {
270
+ assert__default.default(this.isLoaded, "The package is not loaded yet");
310
271
  }
311
- merge(other) {
312
- this.json = fp.merge(other, this.json);
313
- return this;
272
+ getVersionsDict() {
273
+ this.assertPackageIsLoaded(this.npmPackage);
274
+ return this.npmPackage.versions;
314
275
  }
315
- root() {
316
- return fp.cloneDeep(this.json);
276
+ getVersionsAsList() {
277
+ this.assertPackageIsLoaded(this.npmPackage);
278
+ return Object.values(this.npmPackage.versions);
317
279
  }
318
- set(path2, value) {
319
- this.json = fp.set(path2, value, this.json);
320
- return this;
280
+ findVersionsInRange(range) {
281
+ const versions = this.getVersionsAsList();
282
+ return versions.filter((v) => range.test(v.version)).filter((v) => isLiteralSemVer(v.version)).sort((v1, v2) => semver__default.default.compare(v1.version, v2.version));
321
283
  }
322
- remove(path2) {
323
- this.json = fp.omit(path2, this.json);
284
+ findVersion(version2) {
285
+ const versions = this.getVersionsAsList();
286
+ return versions.find((npmVersion) => semver__default.default.eq(npmVersion.version, version2));
287
+ }
288
+ async refresh() {
289
+ const response = await fetch(this.packageURL);
290
+ assert__default.default(response.ok, `Request failed for ${this.packageURL}`);
291
+ this.npmPackage = await response.json();
324
292
  return this;
325
293
  }
294
+ versionExists(version2) {
295
+ return this.findVersion(version2) !== void 0;
296
+ }
326
297
  }
327
- const createJSONTransformAPI = (object) => new JSONTransformAPI(object);
328
- const readJSON = async (path2) => {
329
- const buffer = await fse__default.default.readFile(path2);
330
- return JSON.parse(buffer.toString());
331
- };
332
- const saveJSON = async (path2, json) => {
333
- const jsonAsString = `${JSON.stringify(json, null, 2)}
334
- `;
335
- await fse__default.default.writeFile(path2, jsonAsString);
336
- };
298
+ const npmPackageFactory = (name) => new Package(name);
337
299
  class FileScanner {
338
300
  cwd;
339
301
  constructor(cwd) {
@@ -372,6 +334,46 @@ class CodeRunner extends AbstractRunner {
372
334
  const codeRunnerFactory = (paths, configuration) => {
373
335
  return new CodeRunner(paths, configuration);
374
336
  };
337
+ class JSONTransformAPI {
338
+ json;
339
+ constructor(json) {
340
+ this.json = fp.cloneDeep(json);
341
+ }
342
+ get(path2, defaultValue) {
343
+ if (!path2) {
344
+ return this.root();
345
+ }
346
+ return fp.cloneDeep(fp.get(path2, this.json) ?? defaultValue);
347
+ }
348
+ has(path2) {
349
+ return fp.has(path2, this.json);
350
+ }
351
+ merge(other) {
352
+ this.json = fp.merge(other, this.json);
353
+ return this;
354
+ }
355
+ root() {
356
+ return fp.cloneDeep(this.json);
357
+ }
358
+ set(path2, value) {
359
+ this.json = fp.set(path2, value, this.json);
360
+ return this;
361
+ }
362
+ remove(path2) {
363
+ this.json = fp.omit(path2, this.json);
364
+ return this;
365
+ }
366
+ }
367
+ const createJSONTransformAPI = (object) => new JSONTransformAPI(object);
368
+ const readJSON = async (path2) => {
369
+ const buffer = await fse__default.default.readFile(path2);
370
+ return JSON.parse(buffer.toString());
371
+ };
372
+ const saveJSON = async (path2, json) => {
373
+ const jsonAsString = `${JSON.stringify(json, null, 2)}
374
+ `;
375
+ await fse__default.default.writeFile(path2, jsonAsString);
376
+ };
375
377
  const transformJSON = async (codemodPath, paths, config) => {
376
378
  const { dry } = config;
377
379
  const startTime = process.hrtime();
@@ -642,6 +644,13 @@ class UnexpectedError extends Error {
642
644
  super("Unexpected Error");
643
645
  }
644
646
  }
647
+ class NPMCandidateNotFoundError extends Error {
648
+ target;
649
+ constructor(target, message = `Couldn't find a valid NPM candidate for "${target}"`) {
650
+ super(message);
651
+ this.target = target;
652
+ }
653
+ }
645
654
  const unknownToError = (e) => {
646
655
  if (e instanceof Error) {
647
656
  return e;
@@ -651,74 +660,6 @@ const unknownToError = (e) => {
651
660
  }
652
661
  return new UnexpectedError();
653
662
  };
654
- const path = (path2) => chalk__default.default.blue(path2);
655
- const version$1 = (version2) => {
656
- return chalk__default.default.italic.yellow(`v${version2}`);
657
- };
658
- const codemodUID = (uid) => {
659
- return chalk__default.default.bold.cyan(uid);
660
- };
661
- const projectDetails = (project) => {
662
- return `Project: TYPE=${projectType(project.type)}; CWD=${path(project.cwd)}; PATHS=${project.paths.map(path)}`;
663
- };
664
- const projectType = (type) => chalk__default.default.cyan(type);
665
- const versionRange = (range) => chalk__default.default.italic.yellow(range.raw);
666
- const highlight = (arg) => chalk__default.default.bold.underline(arg);
667
- const upgradeStep = (text, step) => {
668
- return chalk__default.default.bold(`(${step[0]}/${step[1]}) ${text}...`);
669
- };
670
- const reports = (reports2) => {
671
- const rows = reports2.map(({ codemod, report }, i) => {
672
- const fIndex = chalk__default.default.grey(i);
673
- const fVersion = chalk__default.default.magenta(codemod.version);
674
- const fKind = chalk__default.default.yellow(codemod.kind);
675
- const fFormattedTransformPath = chalk__default.default.cyan(codemod.format());
676
- const fTimeElapsed = i === 0 ? `${report.timeElapsed}s ${chalk__default.default.dim.italic("(cold start)")}` : `${report.timeElapsed}s`;
677
- const fAffected = report.ok > 0 ? chalk__default.default.green(report.ok) : chalk__default.default.grey(0);
678
- const fUnchanged = report.ok === 0 ? chalk__default.default.red(report.nochange) : chalk__default.default.grey(report.nochange);
679
- return [fIndex, fVersion, fKind, fFormattedTransformPath, fAffected, fUnchanged, fTimeElapsed];
680
- });
681
- const table = new CliTable3__default.default({
682
- style: { compact: true },
683
- head: [
684
- chalk__default.default.bold.grey("N°"),
685
- chalk__default.default.bold.magenta("Version"),
686
- chalk__default.default.bold.yellow("Kind"),
687
- chalk__default.default.bold.cyan("Name"),
688
- chalk__default.default.bold.green("Affected"),
689
- chalk__default.default.bold.red("Unchanged"),
690
- chalk__default.default.bold.blue("Duration")
691
- ]
692
- });
693
- table.push(...rows);
694
- return table.toString();
695
- };
696
- const codemodList = (codemods) => {
697
- const rows = codemods.map((codemod, index) => {
698
- const fIndex = chalk__default.default.grey(index);
699
- const fVersion = chalk__default.default.magenta(codemod.version);
700
- const fKind = chalk__default.default.yellow(codemod.kind);
701
- const fName = chalk__default.default.blue(codemod.format());
702
- const fUID = codemodUID(codemod.uid);
703
- return [fIndex, fVersion, fKind, fName, fUID];
704
- });
705
- const table = new CliTable3__default.default({
706
- style: { compact: true },
707
- head: [
708
- chalk__default.default.bold.grey("N°"),
709
- chalk__default.default.bold.magenta("Version"),
710
- chalk__default.default.bold.yellow("Kind"),
711
- chalk__default.default.bold.blue("Name"),
712
- chalk__default.default.bold.cyan("UID")
713
- ]
714
- });
715
- table.push(...rows);
716
- return table.toString();
717
- };
718
- const durationMs = (elapsedMs) => {
719
- const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
720
- return `${elapsedSeconds}s`;
721
- };
722
663
  const CODEMOD_CODE_SUFFIX = "code";
723
664
  const CODEMOD_JSON_SUFFIX = "json";
724
665
  const CODEMOD_ALLOWED_SUFFIXES = [CODEMOD_CODE_SUFFIX, CODEMOD_JSON_SUFFIX];
@@ -1126,7 +1067,7 @@ class Upgrader {
1126
1067
  const packageManagerName = await utils.packageManager.getPreferred(projectPath);
1127
1068
  this.logger?.debug?.(`Using ${highlight(packageManagerName)} as package manager`);
1128
1069
  if (this.isDry) {
1129
- this.logger?.debug?.(`Skipping dependencies installation (${chalk__default.default.italic("dry mode")}`);
1070
+ this.logger?.debug?.(`Skipping dependencies installation (${chalk__default.default.italic("dry mode")})`);
1130
1071
  return;
1131
1072
  }
1132
1073
  await utils.packageManager.installDependencies(projectPath, packageManagerName, {
@@ -1145,72 +1086,145 @@ class Upgrader {
1145
1086
  }
1146
1087
  const resolveNPMTarget = (project, target, npmPackage) => {
1147
1088
  if (isSemverInstance(target)) {
1148
- return npmPackage.findVersion(target);
1089
+ const version2 = npmPackage.findVersion(target);
1090
+ if (!version2) {
1091
+ throw new NPMCandidateNotFoundError(target);
1092
+ }
1093
+ return version2;
1149
1094
  }
1150
1095
  if (isSemVerReleaseType(target)) {
1151
1096
  const range = rangeFromVersions(project.strapiVersion, target);
1152
1097
  const npmVersionsMatches = npmPackage.findVersionsInRange(range);
1153
- return npmVersionsMatches.at(-1);
1098
+ const version2 = npmVersionsMatches.at(-1);
1099
+ if (!version2) {
1100
+ throw new NPMCandidateNotFoundError(range, `The project is already up-to-date (${target})`);
1101
+ }
1102
+ return version2;
1154
1103
  }
1155
- return void 0;
1104
+ throw new NPMCandidateNotFoundError(target);
1156
1105
  };
1157
1106
  const upgraderFactory = (project, target, npmPackage) => {
1158
- const targetedNPMVersion = resolveNPMTarget(project, target, npmPackage);
1159
- if (!targetedNPMVersion) {
1160
- throw new Error(`Couldn't find a matching version in the NPM registry for "${target}"`);
1161
- }
1162
- const semverTarget = semVerFactory(targetedNPMVersion.version);
1107
+ const npmTarget = resolveNPMTarget(project, target, npmPackage);
1108
+ const semverTarget = semVerFactory(npmTarget.version);
1163
1109
  if (semver__default.default.eq(semverTarget, project.strapiVersion)) {
1164
- throw new Error(`The project is already on ${version$1(semverTarget)}`);
1110
+ throw new Error(`The project is already using v${semverTarget}`);
1165
1111
  }
1166
1112
  return new Upgrader(project, semverTarget, npmPackage);
1167
1113
  };
1168
1114
  const successReport = () => ({ success: true, error: null });
1169
1115
  const erroredReport = (error) => ({ success: false, error });
1170
1116
  const STRAPI_PACKAGE_NAME = "@strapi/strapi";
1171
- const NPM_REGISTRY_URL = "https://registry.npmjs.org";
1172
- class Package {
1117
+ class Requirement {
1118
+ isRequired;
1173
1119
  name;
1174
- packageURL;
1175
- npmPackage;
1176
- constructor(name) {
1120
+ testCallback;
1121
+ children;
1122
+ constructor(name, testCallback, isRequired) {
1177
1123
  this.name = name;
1178
- this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
1179
- this.npmPackage = null;
1124
+ this.testCallback = testCallback;
1125
+ this.isRequired = isRequired ?? true;
1126
+ this.children = [];
1180
1127
  }
1181
- get isLoaded() {
1182
- return this.npmPackage !== null;
1128
+ setChildren(children) {
1129
+ this.children = children;
1130
+ return this;
1183
1131
  }
1184
- assertPackageIsLoaded(npmPackage) {
1185
- assert__default.default(this.isLoaded, "The package is not loaded yet");
1132
+ addChild(child) {
1133
+ this.children.push(child);
1134
+ return this;
1186
1135
  }
1187
- getVersionsDict() {
1188
- this.assertPackageIsLoaded(this.npmPackage);
1189
- return this.npmPackage.versions;
1136
+ asOptional() {
1137
+ const newInstance = requirementFactory(this.name, this.testCallback, false);
1138
+ newInstance.setChildren(this.children);
1139
+ return newInstance;
1190
1140
  }
1191
- getVersionsAsList() {
1192
- this.assertPackageIsLoaded(this.npmPackage);
1193
- return Object.values(this.npmPackage.versions);
1141
+ asRequired() {
1142
+ const newInstance = requirementFactory(this.name, this.testCallback, true);
1143
+ newInstance.setChildren(this.children);
1144
+ return newInstance;
1194
1145
  }
1195
- findVersionsInRange(range) {
1196
- const versions = this.getVersionsAsList();
1197
- return versions.filter((v) => range.test(v.version)).filter((v) => isLiteralSemVer(v.version)).sort((v1, v2) => semver__default.default.compare(v1.version, v2.version));
1146
+ async test(context) {
1147
+ try {
1148
+ await this.testCallback?.(context);
1149
+ return ok();
1150
+ } catch (e) {
1151
+ if (e instanceof Error) {
1152
+ return errored(e);
1153
+ }
1154
+ if (typeof e === "string") {
1155
+ return errored(new Error(e));
1156
+ }
1157
+ return errored(new Error("Unknown error"));
1158
+ }
1198
1159
  }
1199
- findVersion(version2) {
1200
- const versions = this.getVersionsAsList();
1201
- return versions.find((npmVersion) => semver__default.default.eq(npmVersion.version, version2));
1160
+ }
1161
+ const ok = () => ({ pass: true, error: null });
1162
+ const errored = (error) => ({ pass: false, error });
1163
+ const requirementFactory = (name, testCallback, isRequired) => new Requirement(name, testCallback, isRequired);
1164
+ const REQUIRE_AVAILABLE_NEXT_MAJOR = requirementFactory(
1165
+ "REQUIRE_AVAILABLE_NEXT_MAJOR",
1166
+ (context) => {
1167
+ const { project, target } = context;
1168
+ const currentMajor = project.strapiVersion.major;
1169
+ const targetedMajor = target.major;
1170
+ if (targetedMajor === currentMajor) {
1171
+ throw new Error(`You're already on the latest major version (v${currentMajor})`);
1172
+ }
1202
1173
  }
1203
- async refresh() {
1204
- const response = await fetch(this.packageURL);
1205
- assert__default.default(response.ok, `Request failed for ${this.packageURL}`);
1206
- this.npmPackage = await response.json();
1207
- return this;
1174
+ );
1175
+ const REQUIRE_LATEST_FOR_CURRENT_MAJOR = requirementFactory(
1176
+ "REQUIRE_LATEST_FOR_CURRENT_MAJOR",
1177
+ (context) => {
1178
+ const { project, target, npmVersionsMatches } = context;
1179
+ const { major: currentMajor } = project.strapiVersion;
1180
+ const invalidMatches = npmVersionsMatches.filter(
1181
+ (match) => semVerFactory(match.version).major === currentMajor
1182
+ );
1183
+ if (invalidMatches.length > 0) {
1184
+ const invalidVersions = invalidMatches.map((match) => match.version);
1185
+ const invalidVersionsCount = invalidVersions.length;
1186
+ throw new Error(
1187
+ `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.`
1188
+ );
1189
+ }
1208
1190
  }
1209
- versionExists(version2) {
1210
- return this.findVersion(version2) !== void 0;
1191
+ );
1192
+ const REQUIRE_GIT_CLEAN_REPOSITORY = requirementFactory(
1193
+ "REQUIRE_GIT_CLEAN_REPOSITORY",
1194
+ async (context) => {
1195
+ const git = simpleGit__default.default({ baseDir: context.project.cwd });
1196
+ const status = await git.status();
1197
+ if (!status.isClean()) {
1198
+ throw new Error(
1199
+ "Repository is not clean. Please commit or stash any changes before upgrading"
1200
+ );
1201
+ }
1211
1202
  }
1212
- }
1213
- const npmPackageFactory = (name) => new Package(name);
1203
+ );
1204
+ const REQUIRE_GIT_REPOSITORY = requirementFactory(
1205
+ "REQUIRE_GIT_REPOSITORY",
1206
+ async (context) => {
1207
+ const git = simpleGit__default.default({ baseDir: context.project.cwd });
1208
+ const isRepo = await git.checkIsRepo();
1209
+ if (!isRepo) {
1210
+ throw new Error("Not a git repository (or any of the parent directories)");
1211
+ }
1212
+ }
1213
+ ).addChild(REQUIRE_GIT_CLEAN_REPOSITORY.asOptional());
1214
+ const REQUIRE_GIT_INSTALLED = requirementFactory(
1215
+ "REQUIRE_GIT_INSTALLED",
1216
+ async (context) => {
1217
+ const git = simpleGit__default.default({ baseDir: context.project.cwd });
1218
+ try {
1219
+ await git.version();
1220
+ } catch {
1221
+ throw new Error("Git is not installed");
1222
+ }
1223
+ }
1224
+ ).addChild(REQUIRE_GIT_REPOSITORY.asOptional());
1225
+ const REQUIRE_GIT = requirementFactory("REQUIRE_GIT", null).addChild(
1226
+ REQUIRE_GIT_INSTALLED.asOptional()
1227
+ );
1214
1228
  const upgrade$1 = async (options) => {
1215
1229
  const timer = timerFactory();
1216
1230
  const { logger, codemodsTarget } = options;
@@ -1222,6 +1236,9 @@ const upgrade$1 = async (options) => {
1222
1236
  `The "${options.target}" upgrade can only be run on a Strapi project; for plugins, please use "codemods".`
1223
1237
  );
1224
1238
  }
1239
+ logger.debug(
1240
+ `Application: VERSION=${version$1(project.packageJSON.version)}; STRAPI_VERSION=${version$1(project.strapiVersion)}`
1241
+ );
1225
1242
  const npmPackage = npmPackageFactory(STRAPI_PACKAGE_NAME);
1226
1243
  await npmPackage.refresh();
1227
1244
  const upgrader = upgraderFactory(project, options.target, npmPackage).dry(options.dry ?? false).onConfirm(options.confirm ?? null).setLogger(logger);
@@ -1237,7 +1254,7 @@ const upgrade$1 = async (options) => {
1237
1254
  throw upgradeReport.error;
1238
1255
  }
1239
1256
  timer.stop();
1240
- logger.info(`Completed in ${durationMs(timer.elapsedMs)}`);
1257
+ logger.info(`Completed in ${durationMs(timer.elapsedMs)}ms`);
1241
1258
  };
1242
1259
  const resolvePath = (cwd) => path__default.default.resolve(cwd ?? process.cwd());
1243
1260
  const getRangeFromTarget = (currentVersion, target) => {
@@ -1477,7 +1494,7 @@ When executed on a Strapi plugin project, it shows every codemods.
1477
1494
  return listCodemods(options);
1478
1495
  });
1479
1496
  };
1480
- const version = "0.0.0-experimental.fdacf4285d1cada9d94ab4dcd756c5362cba1b54";
1497
+ const version = "0.0.0-next.3c5400321681b66eb35ab84c11113a78c1d9386e";
1481
1498
  register$1(commander.program);
1482
1499
  register(commander.program);
1483
1500
  commander.program.usage("<command> [options]").on("command:*", ([invalidCmd]) => {