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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +439 -317
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +462 -343
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +458 -340
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/modules/error/utils.d.ts +8 -0
  9. package/dist/modules/error/utils.d.ts.map +1 -1
  10. package/dist/modules/file-scanner/scanner.d.ts.map +1 -1
  11. package/dist/modules/format/formats.d.ts +2 -1
  12. package/dist/modules/format/formats.d.ts.map +1 -1
  13. package/dist/modules/project/constants.d.ts +6 -5
  14. package/dist/modules/project/constants.d.ts.map +1 -1
  15. package/dist/modules/project/project.d.ts +16 -2
  16. package/dist/modules/project/project.d.ts.map +1 -1
  17. package/dist/modules/project/types.d.ts +3 -0
  18. package/dist/modules/project/types.d.ts.map +1 -1
  19. package/dist/modules/upgrader/types.d.ts +6 -0
  20. package/dist/modules/upgrader/types.d.ts.map +1 -1
  21. package/dist/modules/upgrader/upgrader.d.ts +4 -0
  22. package/dist/modules/upgrader/upgrader.d.ts.map +1 -1
  23. package/dist/modules/version/range.d.ts.map +1 -1
  24. package/dist/modules/version/types.d.ts +2 -1
  25. package/dist/modules/version/types.d.ts.map +1 -1
  26. package/dist/tasks/codemods/utils.d.ts.map +1 -1
  27. package/dist/tasks/upgrade/prompts/index.d.ts +2 -0
  28. package/dist/tasks/upgrade/prompts/index.d.ts.map +1 -0
  29. package/dist/tasks/upgrade/prompts/latest.d.ts +9 -0
  30. package/dist/tasks/upgrade/prompts/latest.d.ts.map +1 -0
  31. package/dist/tasks/upgrade/requirements/major.d.ts.map +1 -1
  32. package/dist/tasks/upgrade/upgrade.d.ts.map +1 -1
  33. package/package.json +8 -8
  34. package/resources/codemods/5.0.0/comment-out-lifecycle-files.code.ts +63 -0
  35. package/resources/codemods/5.0.0/dependency-upgrade-react-and-react-dom.json.ts +67 -0
  36. package/resources/codemods/5.0.0/dependency-upgrade-styled-components.json.ts +49 -0
  37. package/resources/codemods/5.0.0/deprecate-helper-plugin.code.ts +192 -0
  38. package/resources/codemods/5.0.0/sqlite3-to-better-sqlite3.json.ts +0 -1
  39. package/resources/codemods/5.1.0/dependency-better-sqlite3.json.ts +48 -0
  40. package/resources/utils/change-import.ts +118 -0
  41. package/resources/utils/replace-jsx.ts +49 -0
package/dist/cli.js CHANGED
@@ -5,25 +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");
13
- const glob = require("glob");
10
+ const fse = require("fs-extra");
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);
25
- const assert__default = /* @__PURE__ */ _interopDefault(assert);
26
23
  const CliTable3__default = /* @__PURE__ */ _interopDefault(CliTable3);
24
+ const assert__default = /* @__PURE__ */ _interopDefault(assert);
25
+ const fse__default = /* @__PURE__ */ _interopDefault(fse);
26
+ const fastglob__default = /* @__PURE__ */ _interopDefault(fastglob);
27
+ const simpleGit__default = /* @__PURE__ */ _interopDefault(simpleGit);
27
28
  class Logger {
28
29
  isDebug;
29
30
  isSilent;
@@ -98,6 +99,7 @@ var ReleaseType = /* @__PURE__ */ ((ReleaseType2) => {
98
99
  ReleaseType2["Major"] = "major";
99
100
  ReleaseType2["Minor"] = "minor";
100
101
  ReleaseType2["Patch"] = "patch";
102
+ ReleaseType2["Latest"] = "latest";
101
103
  return ReleaseType2;
102
104
  })(ReleaseType || {});
103
105
  const semVerFactory = (version2) => {
@@ -119,17 +121,20 @@ const rangeFactory = (range) => {
119
121
  };
120
122
  const rangeFromReleaseType = (current, identifier) => {
121
123
  switch (identifier) {
122
- case ReleaseType.Major: {
123
- const nextMajor = semver__default.default.inc(current, "major");
124
- return rangeFactory(`>${current.raw} <=${nextMajor}`);
124
+ case ReleaseType.Latest: {
125
+ return rangeFactory(`>${current.raw}`);
125
126
  }
126
- case ReleaseType.Patch: {
127
- const minor = semver__default.default.inc(current, "minor");
128
- return rangeFactory(`>${current.raw} <${minor}`);
127
+ case ReleaseType.Major: {
128
+ const nextMajor = semVerFactory(current.raw).inc("major");
129
+ return rangeFactory(`>${current.raw} <=${nextMajor.major}`);
129
130
  }
130
131
  case ReleaseType.Minor: {
131
- const major = semver__default.default.inc(current, "major");
132
- return rangeFactory(`>${current.raw} <${major}`);
132
+ const nextMajor = semVerFactory(current.raw).inc("major");
133
+ return rangeFactory(`>${current.raw} <${nextMajor.raw}`);
134
+ }
135
+ case ReleaseType.Patch: {
136
+ const nextMinor = semVerFactory(current.raw).inc("minor");
137
+ return rangeFactory(`>${current.raw} <${nextMinor.raw}`);
133
138
  }
134
139
  default: {
135
140
  throw new Error("Not implemented");
@@ -149,7 +154,36 @@ const isValidStringifiedRange = (str) => semver__default.default.validRange(str)
149
154
  const isRangeInstance = (range) => {
150
155
  return range instanceof semver__default.default.Range;
151
156
  };
157
+ class UnexpectedError extends Error {
158
+ constructor() {
159
+ super("Unexpected Error");
160
+ }
161
+ }
162
+ class NPMCandidateNotFoundError extends Error {
163
+ target;
164
+ constructor(target, message = `Couldn't find a valid NPM candidate for "${target}"`) {
165
+ super(message);
166
+ this.target = target;
167
+ }
168
+ }
169
+ class AbortedError extends Error {
170
+ constructor(message = "Upgrade aborted") {
171
+ super(message);
172
+ }
173
+ }
174
+ const unknownToError = (e) => {
175
+ if (e instanceof Error) {
176
+ return e;
177
+ }
178
+ if (typeof e === "string") {
179
+ return new Error(e);
180
+ }
181
+ return new UnexpectedError();
182
+ };
152
183
  const handleError = (err, isSilent) => {
184
+ if (err instanceof AbortedError) {
185
+ process.exit(0);
186
+ }
153
187
  if (!isSilent) {
154
188
  console.error(
155
189
  chalk__default.default.red(`[ERROR] [${(/* @__PURE__ */ new Date()).toISOString()}]`),
@@ -158,115 +192,6 @@ const handleError = (err, isSilent) => {
158
192
  }
159
193
  process.exit(1);
160
194
  };
161
- class Requirement {
162
- isRequired;
163
- name;
164
- testCallback;
165
- children;
166
- constructor(name, testCallback, isRequired) {
167
- this.name = name;
168
- this.testCallback = testCallback;
169
- this.isRequired = isRequired ?? true;
170
- this.children = [];
171
- }
172
- setChildren(children) {
173
- this.children = children;
174
- return this;
175
- }
176
- addChild(child) {
177
- this.children.push(child);
178
- return this;
179
- }
180
- asOptional() {
181
- const newInstance = requirementFactory(this.name, this.testCallback, false);
182
- newInstance.setChildren(this.children);
183
- return newInstance;
184
- }
185
- asRequired() {
186
- const newInstance = requirementFactory(this.name, this.testCallback, true);
187
- newInstance.setChildren(this.children);
188
- return newInstance;
189
- }
190
- async test(context) {
191
- try {
192
- await this.testCallback?.(context);
193
- return ok();
194
- } catch (e) {
195
- if (e instanceof Error) {
196
- return errored(e);
197
- }
198
- if (typeof e === "string") {
199
- return errored(new Error(e));
200
- }
201
- return errored(new Error("Unknown error"));
202
- }
203
- }
204
- }
205
- const ok = () => ({ pass: true, error: null });
206
- const errored = (error) => ({ pass: false, error });
207
- const requirementFactory = (name, testCallback, isRequired) => new Requirement(name, testCallback, isRequired);
208
- const REQUIRE_AVAILABLE_NEXT_MAJOR = requirementFactory(
209
- "REQUIRE_AVAILABLE_NEXT_MAJOR",
210
- (context) => {
211
- const { project, target } = context;
212
- const currentMajor = project.strapiVersion.major;
213
- const targetedMajor = target.major;
214
- if (targetedMajor === currentMajor) {
215
- throw new Error(`You're already on the latest major version (v${currentMajor})`);
216
- }
217
- }
218
- );
219
- const REQUIRE_LATEST_FOR_CURRENT_MAJOR = requirementFactory(
220
- "REQUIRE_LATEST_FOR_CURRENT_MAJOR",
221
- (context) => {
222
- const { project, target, npmVersionsMatches } = context;
223
- if (npmVersionsMatches.length !== 1) {
224
- const invalidVersions = npmVersionsMatches.slice(0, -1);
225
- const invalidVersionsAsSemVer = invalidVersions.map((v) => v.version);
226
- const nbInvalidVersions = npmVersionsMatches.length;
227
- const currentMajor = project.strapiVersion.major;
228
- throw new Error(
229
- `Doing a major upgrade requires to be on the latest v${currentMajor} version, but found ${nbInvalidVersions} versions between the current one and ${target}: ${invalidVersionsAsSemVer}`
230
- );
231
- }
232
- }
233
- );
234
- const REQUIRE_GIT_CLEAN_REPOSITORY = requirementFactory(
235
- "REQUIRE_GIT_CLEAN_REPOSITORY",
236
- async (context) => {
237
- const git = simpleGit__default.default({ baseDir: context.project.cwd });
238
- const status = await git.status();
239
- if (!status.isClean()) {
240
- throw new Error(
241
- "Repository is not clean. Please commit or stash any changes before upgrading"
242
- );
243
- }
244
- }
245
- );
246
- const REQUIRE_GIT_REPOSITORY = requirementFactory(
247
- "REQUIRE_GIT_REPOSITORY",
248
- async (context) => {
249
- const git = simpleGit__default.default({ baseDir: context.project.cwd });
250
- const isRepo = await git.checkIsRepo();
251
- if (!isRepo) {
252
- throw new Error("Not a git repository (or any of the parent directories)");
253
- }
254
- }
255
- ).addChild(REQUIRE_GIT_CLEAN_REPOSITORY.asOptional());
256
- const REQUIRE_GIT_INSTALLED = requirementFactory(
257
- "REQUIRE_GIT_INSTALLED",
258
- async (context) => {
259
- const git = simpleGit__default.default({ baseDir: context.project.cwd });
260
- try {
261
- await git.version();
262
- } catch {
263
- throw new Error("Git is not installed");
264
- }
265
- }
266
- ).addChild(REQUIRE_GIT_REPOSITORY.asOptional());
267
- const REQUIRE_GIT = requirementFactory("REQUIRE_GIT", null).addChild(
268
- REQUIRE_GIT_INSTALLED.asOptional()
269
- );
270
195
  class Timer {
271
196
  interval;
272
197
  constructor() {
@@ -293,53 +218,126 @@ class Timer {
293
218
  }
294
219
  const timerFactory = () => new Timer();
295
220
  const ONE_SECOND_MS = 1e3;
296
- class JSONTransformAPI {
297
- json;
298
- constructor(json) {
299
- this.json = fp.cloneDeep(json);
221
+ const path = (path2) => chalk__default.default.blue(path2);
222
+ const version$1 = (version2) => {
223
+ return chalk__default.default.italic.yellow(`v${version2}`);
224
+ };
225
+ const codemodUID = (uid) => {
226
+ return chalk__default.default.bold.cyan(uid);
227
+ };
228
+ const projectDetails = (project) => {
229
+ return `Project: TYPE=${projectType(project.type)}; CWD=${path(project.cwd)}; PATHS=${project.paths.map(path)}`;
230
+ };
231
+ const projectType = (type) => chalk__default.default.cyan(type);
232
+ const versionRange = (range) => chalk__default.default.italic.yellow(range.raw);
233
+ const highlight = (arg) => chalk__default.default.bold.underline(arg);
234
+ const upgradeStep = (text, step) => {
235
+ return chalk__default.default.bold(`(${step[0]}/${step[1]}) ${text}...`);
236
+ };
237
+ const reports = (reports2) => {
238
+ const rows = reports2.map(({ codemod, report }, i) => {
239
+ const fIndex = chalk__default.default.grey(i);
240
+ const fVersion = chalk__default.default.magenta(codemod.version);
241
+ const fKind = chalk__default.default.yellow(codemod.kind);
242
+ const fFormattedTransformPath = chalk__default.default.cyan(codemod.format());
243
+ const fTimeElapsed = i === 0 ? `${report.timeElapsed}s ${chalk__default.default.dim.italic("(cold start)")}` : `${report.timeElapsed}s`;
244
+ const fAffected = report.ok > 0 ? chalk__default.default.green(report.ok) : chalk__default.default.grey(0);
245
+ const fUnchanged = report.ok === 0 ? chalk__default.default.red(report.nochange) : chalk__default.default.grey(report.nochange);
246
+ return [fIndex, fVersion, fKind, fFormattedTransformPath, fAffected, fUnchanged, fTimeElapsed];
247
+ });
248
+ const table = new CliTable3__default.default({
249
+ style: { compact: true },
250
+ head: [
251
+ chalk__default.default.bold.grey("N°"),
252
+ chalk__default.default.bold.magenta("Version"),
253
+ chalk__default.default.bold.yellow("Kind"),
254
+ chalk__default.default.bold.cyan("Name"),
255
+ chalk__default.default.bold.green("Affected"),
256
+ chalk__default.default.bold.red("Unchanged"),
257
+ chalk__default.default.bold.blue("Duration")
258
+ ]
259
+ });
260
+ table.push(...rows);
261
+ return table.toString();
262
+ };
263
+ const codemodList = (codemods) => {
264
+ const rows = codemods.map((codemod, index) => {
265
+ const fIndex = chalk__default.default.grey(index);
266
+ const fVersion = chalk__default.default.magenta(codemod.version);
267
+ const fKind = chalk__default.default.yellow(codemod.kind);
268
+ const fName = chalk__default.default.blue(codemod.format());
269
+ const fUID = codemodUID(codemod.uid);
270
+ return [fIndex, fVersion, fKind, fName, fUID];
271
+ });
272
+ const table = new CliTable3__default.default({
273
+ style: { compact: true },
274
+ head: [
275
+ chalk__default.default.bold.grey("N°"),
276
+ chalk__default.default.bold.magenta("Version"),
277
+ chalk__default.default.bold.yellow("Kind"),
278
+ chalk__default.default.bold.blue("Name"),
279
+ chalk__default.default.bold.cyan("UID")
280
+ ]
281
+ });
282
+ table.push(...rows);
283
+ return table.toString();
284
+ };
285
+ const durationMs = (elapsedMs) => {
286
+ const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
287
+ return `${elapsedSeconds}s`;
288
+ };
289
+ const NPM_REGISTRY_URL = "https://registry.npmjs.org";
290
+ class Package {
291
+ name;
292
+ packageURL;
293
+ npmPackage;
294
+ constructor(name) {
295
+ this.name = name;
296
+ this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
297
+ this.npmPackage = null;
300
298
  }
301
- get(path2, defaultValue) {
302
- if (!path2) {
303
- return this.root();
304
- }
305
- return fp.cloneDeep(fp.get(path2, this.json) ?? defaultValue);
299
+ get isLoaded() {
300
+ return this.npmPackage !== null;
306
301
  }
307
- has(path2) {
308
- return fp.has(path2, this.json);
302
+ assertPackageIsLoaded(npmPackage) {
303
+ assert__default.default(this.isLoaded, "The package is not loaded yet");
309
304
  }
310
- merge(other) {
311
- this.json = fp.merge(other, this.json);
312
- return this;
305
+ getVersionsDict() {
306
+ this.assertPackageIsLoaded(this.npmPackage);
307
+ return this.npmPackage.versions;
313
308
  }
314
- root() {
315
- return fp.cloneDeep(this.json);
309
+ getVersionsAsList() {
310
+ this.assertPackageIsLoaded(this.npmPackage);
311
+ return Object.values(this.npmPackage.versions);
316
312
  }
317
- set(path2, value) {
318
- this.json = fp.set(path2, value, this.json);
319
- return this;
313
+ findVersionsInRange(range) {
314
+ const versions = this.getVersionsAsList();
315
+ return versions.filter((v) => range.test(v.version)).filter((v) => isLiteralSemVer(v.version)).sort((v1, v2) => semver__default.default.compare(v1.version, v2.version));
320
316
  }
321
- remove(path2) {
322
- this.json = fp.omit(path2, this.json);
317
+ findVersion(version2) {
318
+ const versions = this.getVersionsAsList();
319
+ return versions.find((npmVersion) => semver__default.default.eq(npmVersion.version, version2));
320
+ }
321
+ async refresh() {
322
+ const response = await fetch(this.packageURL);
323
+ assert__default.default(response.ok, `Request failed for ${this.packageURL}`);
324
+ this.npmPackage = await response.json();
323
325
  return this;
324
326
  }
327
+ versionExists(version2) {
328
+ return this.findVersion(version2) !== void 0;
329
+ }
325
330
  }
326
- const createJSONTransformAPI = (object) => new JSONTransformAPI(object);
327
- const readJSON = async (path2) => {
328
- const buffer = await fse__default.default.readFile(path2);
329
- return JSON.parse(buffer.toString());
330
- };
331
- const saveJSON = async (path2, json) => {
332
- const jsonAsString = `${JSON.stringify(json, null, 2)}
333
- `;
334
- await fse__default.default.writeFile(path2, jsonAsString);
335
- };
331
+ const npmPackageFactory = (name) => new Package(name);
336
332
  class FileScanner {
337
333
  cwd;
338
334
  constructor(cwd) {
339
335
  this.cwd = cwd;
340
336
  }
341
337
  scan(patterns) {
342
- const filenames = glob.glob.sync(patterns, { cwd: this.cwd });
338
+ const filenames = fastglob__default.default.sync(patterns, {
339
+ cwd: this.cwd
340
+ });
343
341
  return filenames.map((filename) => path__default.default.join(this.cwd, filename));
344
342
  }
345
343
  }
@@ -369,6 +367,46 @@ class CodeRunner extends AbstractRunner {
369
367
  const codeRunnerFactory = (paths, configuration) => {
370
368
  return new CodeRunner(paths, configuration);
371
369
  };
370
+ class JSONTransformAPI {
371
+ json;
372
+ constructor(json) {
373
+ this.json = fp.cloneDeep(json);
374
+ }
375
+ get(path2, defaultValue) {
376
+ if (!path2) {
377
+ return this.root();
378
+ }
379
+ return fp.cloneDeep(fp.get(path2, this.json) ?? defaultValue);
380
+ }
381
+ has(path2) {
382
+ return fp.has(path2, this.json);
383
+ }
384
+ merge(other) {
385
+ this.json = fp.merge(other, this.json);
386
+ return this;
387
+ }
388
+ root() {
389
+ return fp.cloneDeep(this.json);
390
+ }
391
+ set(path2, value) {
392
+ this.json = fp.set(path2, value, this.json);
393
+ return this;
394
+ }
395
+ remove(path2) {
396
+ this.json = fp.omit(path2, this.json);
397
+ return this;
398
+ }
399
+ }
400
+ const createJSONTransformAPI = (object) => new JSONTransformAPI(object);
401
+ const readJSON = async (path2) => {
402
+ const buffer = await fse__default.default.readFile(path2);
403
+ return JSON.parse(buffer.toString());
404
+ };
405
+ const saveJSON = async (path2, json) => {
406
+ const jsonAsString = `${JSON.stringify(json, null, 2)}
407
+ `;
408
+ await fse__default.default.writeFile(path2, jsonAsString);
409
+ };
372
410
  const transformJSON = async (codemodPath, paths, config) => {
373
411
  const { dry } = config;
374
412
  const startTime = process.hrtime();
@@ -425,8 +463,10 @@ const jsonRunnerFactory = (paths, configuration) => {
425
463
  return new JSONRunner(paths, configuration);
426
464
  };
427
465
  const PROJECT_PACKAGE_JSON = "package.json";
428
- const PROJECT_DEFAULT_ALLOWED_ROOT_PATHS = ["src", "config", "public"];
429
- const PROJECT_DEFAULT_CODE_EXTENSIONS = [
466
+ const PROJECT_APP_ALLOWED_ROOT_PATHS = ["src", "config", "public"];
467
+ const PROJECT_PLUGIN_ALLOWED_ROOT_PATHS = ["admin", "server"];
468
+ const PROJECT_PLUGIN_ROOT_FILES = ["strapi-admin.js", "strapi-server.js"];
469
+ const PROJECT_CODE_EXTENSIONS = [
430
470
  // Source files
431
471
  "js",
432
472
  "mjs",
@@ -435,12 +475,8 @@ const PROJECT_DEFAULT_CODE_EXTENSIONS = [
435
475
  "jsx",
436
476
  "tsx"
437
477
  ];
438
- const PROJECT_DEFAULT_JSON_EXTENSIONS = ["json"];
439
- const PROJECT_DEFAULT_ALLOWED_EXTENSIONS = [
440
- ...PROJECT_DEFAULT_CODE_EXTENSIONS,
441
- ...PROJECT_DEFAULT_JSON_EXTENSIONS
442
- ];
443
- const PROJECT_DEFAULT_PATTERNS = ["package.json"];
478
+ const PROJECT_JSON_EXTENSIONS = ["json"];
479
+ const PROJECT_ALLOWED_EXTENSIONS = [...PROJECT_CODE_EXTENSIONS, ...PROJECT_JSON_EXTENSIONS];
444
480
  const SCOPED_STRAPI_PACKAGE_PREFIX = "@strapi/";
445
481
  const STRAPI_DEPENDENCY_NAME = `${SCOPED_STRAPI_PACKAGE_PREFIX}strapi`;
446
482
  class Project {
@@ -449,11 +485,13 @@ class Project {
449
485
  files;
450
486
  packageJSONPath;
451
487
  packageJSON;
452
- constructor(cwd) {
488
+ paths;
489
+ constructor(cwd, config) {
453
490
  if (!fse__default.default.pathExistsSync(cwd)) {
454
491
  throw new Error(`ENOENT: no such file or directory, access '${cwd}'`);
455
492
  }
456
493
  this.cwd = cwd;
494
+ this.paths = config.paths;
457
495
  this.refresh();
458
496
  }
459
497
  getFilesByExtensions(extensions) {
@@ -481,12 +519,8 @@ class Project {
481
519
  return reports2;
482
520
  }
483
521
  createProjectCodemodsRunners(dry = false) {
484
- const jsonExtensions = PROJECT_DEFAULT_JSON_EXTENSIONS.map(
485
- (ext) => `.${ext}`
486
- );
487
- const codeExtensions = PROJECT_DEFAULT_CODE_EXTENSIONS.map(
488
- (ext) => `.${ext}`
489
- );
522
+ const jsonExtensions = PROJECT_JSON_EXTENSIONS.map((ext) => `.${ext}`);
523
+ const codeExtensions = PROJECT_CODE_EXTENSIONS.map((ext) => `.${ext}`);
490
524
  const jsonFiles = this.getFilesByExtensions(jsonExtensions);
491
525
  const codeFiles = this.getFilesByExtensions(codeExtensions);
492
526
  const codeRunner = codeRunnerFactory(codeFiles, {
@@ -494,7 +528,7 @@ class Project {
494
528
  parser: "ts",
495
529
  runInBand: true,
496
530
  babel: true,
497
- extensions: PROJECT_DEFAULT_CODE_EXTENSIONS.join(","),
531
+ extensions: PROJECT_CODE_EXTENSIONS.join(","),
498
532
  // Don't output any log coming from the runner
499
533
  print: false,
500
534
  silent: true,
@@ -515,23 +549,32 @@ class Project {
515
549
  this.packageJSON = JSON.parse(packageJSONBuffer.toString());
516
550
  }
517
551
  refreshProjectFiles() {
518
- const allowedRootPaths = formatGlobCollectionPattern(
519
- PROJECT_DEFAULT_ALLOWED_ROOT_PATHS
520
- );
521
- const allowedExtensions = formatGlobCollectionPattern(
522
- PROJECT_DEFAULT_ALLOWED_EXTENSIONS
523
- );
524
- const projectFilesPattern = `./${allowedRootPaths}/**/*.${allowedExtensions}`;
525
- const patterns = [projectFilesPattern, ...PROJECT_DEFAULT_PATTERNS];
526
552
  const scanner = fileScannerFactory(this.cwd);
527
- this.files = scanner.scan(patterns);
553
+ this.files = scanner.scan(this.paths);
528
554
  }
529
555
  }
530
556
  class AppProject extends Project {
531
557
  strapiVersion;
532
558
  type = "application";
559
+ /**
560
+ * Returns an array of allowed file paths for a Strapi application
561
+ *
562
+ * The resulting paths include app default files and the root package.json file.
563
+ */
564
+ static get paths() {
565
+ const allowedRootPaths = formatGlobCollectionPattern(PROJECT_APP_ALLOWED_ROOT_PATHS);
566
+ const allowedExtensions = formatGlobCollectionPattern(PROJECT_ALLOWED_EXTENSIONS);
567
+ return [
568
+ // App default files
569
+ `./${allowedRootPaths}/**/*.${allowedExtensions}`,
570
+ `!./**/node_modules/**/*`,
571
+ `!./**/dist/**/*`,
572
+ // Root package.json file
573
+ PROJECT_PACKAGE_JSON
574
+ ];
575
+ }
533
576
  constructor(cwd) {
534
- super(cwd);
577
+ super(cwd, { paths: AppProject.paths });
535
578
  this.refreshStrapiVersion();
536
579
  }
537
580
  refresh() {
@@ -586,6 +629,30 @@ const formatGlobCollectionPattern = (collection) => {
586
629
  };
587
630
  class PluginProject extends Project {
588
631
  type = "plugin";
632
+ /**
633
+ * Returns an array of allowed file paths for a Strapi plugin
634
+ *
635
+ * The resulting paths include plugin default files, the root package.json file, and plugin-specific files.
636
+ */
637
+ static get paths() {
638
+ const allowedRootPaths = formatGlobCollectionPattern(
639
+ PROJECT_PLUGIN_ALLOWED_ROOT_PATHS
640
+ );
641
+ const allowedExtensions = formatGlobCollectionPattern(PROJECT_ALLOWED_EXTENSIONS);
642
+ return [
643
+ // Plugin default files
644
+ `./${allowedRootPaths}/**/*.${allowedExtensions}`,
645
+ `!./**/node_modules/**/*`,
646
+ `!./**/dist/**/*`,
647
+ // Root package.json file
648
+ PROJECT_PACKAGE_JSON,
649
+ // Plugin root files
650
+ ...PROJECT_PLUGIN_ROOT_FILES
651
+ ];
652
+ }
653
+ constructor(cwd) {
654
+ super(cwd, { paths: PluginProject.paths });
655
+ }
589
656
  }
590
657
  const isPlugin = (cwd) => {
591
658
  const packageJSONPath = path__default.default.join(cwd, PROJECT_PACKAGE_JSON);
@@ -600,93 +667,11 @@ const isPlugin = (cwd) => {
600
667
  };
601
668
  const projectFactory = (cwd) => {
602
669
  fse__default.default.accessSync(cwd);
603
- if (isPlugin(cwd)) {
604
- return new PluginProject(cwd);
605
- }
606
- return new AppProject(cwd);
670
+ return isPlugin(cwd) ? new PluginProject(cwd) : new AppProject(cwd);
607
671
  };
608
672
  const isApplicationProject = (project) => {
609
673
  return project instanceof AppProject;
610
674
  };
611
- class UnexpectedError extends Error {
612
- constructor() {
613
- super("Unexpected Error");
614
- }
615
- }
616
- const unknownToError = (e) => {
617
- if (e instanceof Error) {
618
- return e;
619
- }
620
- if (typeof e === "string") {
621
- return new Error(e);
622
- }
623
- return new UnexpectedError();
624
- };
625
- const path = (path2) => chalk__default.default.blue(path2);
626
- const version$1 = (version2) => {
627
- return chalk__default.default.italic.yellow(`v${version2}`);
628
- };
629
- const codemodUID = (uid) => {
630
- return chalk__default.default.bold.cyan(uid);
631
- };
632
- const projectType = (type) => chalk__default.default.cyan(type);
633
- const versionRange = (range) => chalk__default.default.italic.yellow(range.raw);
634
- const highlight = (arg) => chalk__default.default.bold.underline(arg);
635
- const upgradeStep = (text, step) => {
636
- return chalk__default.default.bold(`(${step[0]}/${step[1]}) ${text}...`);
637
- };
638
- const reports = (reports2) => {
639
- const rows = reports2.map(({ codemod, report }, i) => {
640
- const fIndex = chalk__default.default.grey(i);
641
- const fVersion = chalk__default.default.magenta(codemod.version);
642
- const fKind = chalk__default.default.yellow(codemod.kind);
643
- const fFormattedTransformPath = chalk__default.default.cyan(codemod.format());
644
- const fTimeElapsed = i === 0 ? `${report.timeElapsed}s ${chalk__default.default.dim.italic("(cold start)")}` : `${report.timeElapsed}s`;
645
- const fAffected = report.ok > 0 ? chalk__default.default.green(report.ok) : chalk__default.default.grey(0);
646
- const fUnchanged = report.ok === 0 ? chalk__default.default.red(report.nochange) : chalk__default.default.grey(report.nochange);
647
- return [fIndex, fVersion, fKind, fFormattedTransformPath, fAffected, fUnchanged, fTimeElapsed];
648
- });
649
- const table = new CliTable3__default.default({
650
- style: { compact: true },
651
- head: [
652
- chalk__default.default.bold.grey("N°"),
653
- chalk__default.default.bold.magenta("Version"),
654
- chalk__default.default.bold.yellow("Kind"),
655
- chalk__default.default.bold.cyan("Name"),
656
- chalk__default.default.bold.green("Affected"),
657
- chalk__default.default.bold.red("Unchanged"),
658
- chalk__default.default.bold.blue("Duration")
659
- ]
660
- });
661
- table.push(...rows);
662
- return table.toString();
663
- };
664
- const codemodList = (codemods) => {
665
- const rows = codemods.map((codemod, index) => {
666
- const fIndex = chalk__default.default.grey(index);
667
- const fVersion = chalk__default.default.magenta(codemod.version);
668
- const fKind = chalk__default.default.yellow(codemod.kind);
669
- const fName = chalk__default.default.blue(codemod.format());
670
- const fUID = codemodUID(codemod.uid);
671
- return [fIndex, fVersion, fKind, fName, fUID];
672
- });
673
- const table = new CliTable3__default.default({
674
- style: { compact: true },
675
- head: [
676
- chalk__default.default.bold.grey("N°"),
677
- chalk__default.default.bold.magenta("Version"),
678
- chalk__default.default.bold.yellow("Kind"),
679
- chalk__default.default.bold.blue("Name"),
680
- chalk__default.default.bold.cyan("UID")
681
- ]
682
- });
683
- table.push(...rows);
684
- return table.toString();
685
- };
686
- const durationMs = (elapsedMs) => {
687
- const elapsedSeconds = (elapsedMs / ONE_SECOND_MS).toFixed(3);
688
- return `${elapsedSeconds}s`;
689
- };
690
675
  const CODEMOD_CODE_SUFFIX = "code";
691
676
  const CODEMOD_JSON_SUFFIX = "json";
692
677
  const CODEMOD_ALLOWED_SUFFIXES = [CODEMOD_CODE_SUFFIX, CODEMOD_JSON_SUFFIX];
@@ -942,6 +927,15 @@ class Upgrader {
942
927
  this.logger = null;
943
928
  this.confirmationCallback = null;
944
929
  }
930
+ getNPMPackage() {
931
+ return this.npmPackage;
932
+ }
933
+ getProject() {
934
+ return this.project;
935
+ }
936
+ getTarget() {
937
+ return semVerFactory(this.target.raw);
938
+ }
945
939
  setRequirements(requirements) {
946
940
  this.requirements = requirements;
947
941
  return this;
@@ -1023,6 +1017,12 @@ class Upgrader {
1023
1017
  }
1024
1018
  return successReport();
1025
1019
  }
1020
+ async confirm(message) {
1021
+ if (typeof this.confirmationCallback !== "function") {
1022
+ return true;
1023
+ }
1024
+ return this.confirmationCallback(message);
1025
+ }
1026
1026
  async checkRequirements(requirements, context) {
1027
1027
  for (const requirement of requirements) {
1028
1028
  const { pass, error } = await requirement.test(context);
@@ -1094,7 +1094,7 @@ class Upgrader {
1094
1094
  const packageManagerName = await utils.packageManager.getPreferred(projectPath);
1095
1095
  this.logger?.debug?.(`Using ${highlight(packageManagerName)} as package manager`);
1096
1096
  if (this.isDry) {
1097
- this.logger?.debug?.(`Skipping dependencies installation (${chalk__default.default.italic("dry mode")}`);
1097
+ this.logger?.debug?.(`Skipping dependencies installation (${chalk__default.default.italic("dry mode")})`);
1098
1098
  return;
1099
1099
  }
1100
1100
  await utils.packageManager.installDependencies(projectPath, packageManagerName, {
@@ -1113,98 +1113,214 @@ class Upgrader {
1113
1113
  }
1114
1114
  const resolveNPMTarget = (project, target, npmPackage) => {
1115
1115
  if (isSemverInstance(target)) {
1116
- return npmPackage.findVersion(target);
1116
+ const version2 = npmPackage.findVersion(target);
1117
+ if (!version2) {
1118
+ throw new NPMCandidateNotFoundError(target);
1119
+ }
1120
+ return version2;
1117
1121
  }
1118
1122
  if (isSemVerReleaseType(target)) {
1119
1123
  const range = rangeFromVersions(project.strapiVersion, target);
1120
1124
  const npmVersionsMatches = npmPackage.findVersionsInRange(range);
1121
- return npmVersionsMatches.at(-1);
1125
+ const version2 = npmVersionsMatches.at(-1);
1126
+ if (!version2) {
1127
+ throw new NPMCandidateNotFoundError(range, `The project is already up-to-date (${target})`);
1128
+ }
1129
+ return version2;
1122
1130
  }
1123
- return void 0;
1131
+ throw new NPMCandidateNotFoundError(target);
1124
1132
  };
1125
1133
  const upgraderFactory = (project, target, npmPackage) => {
1126
- const targetedNPMVersion = resolveNPMTarget(project, target, npmPackage);
1127
- if (!targetedNPMVersion) {
1128
- throw new Error(`Couldn't find a matching version in the NPM registry for "${target}"`);
1129
- }
1130
- const semverTarget = semVerFactory(targetedNPMVersion.version);
1134
+ const npmTarget = resolveNPMTarget(project, target, npmPackage);
1135
+ const semverTarget = semVerFactory(npmTarget.version);
1131
1136
  if (semver__default.default.eq(semverTarget, project.strapiVersion)) {
1132
- throw new Error(`The project is already on ${version$1(semverTarget)}`);
1137
+ throw new Error(`The project is already using v${semverTarget}`);
1133
1138
  }
1134
1139
  return new Upgrader(project, semverTarget, npmPackage);
1135
1140
  };
1136
1141
  const successReport = () => ({ success: true, error: null });
1137
1142
  const erroredReport = (error) => ({ success: false, error });
1138
1143
  const STRAPI_PACKAGE_NAME = "@strapi/strapi";
1139
- const NPM_REGISTRY_URL = "https://registry.npmjs.org";
1140
- class Package {
1144
+ class Requirement {
1145
+ isRequired;
1141
1146
  name;
1142
- packageURL;
1143
- npmPackage;
1144
- constructor(name) {
1147
+ testCallback;
1148
+ children;
1149
+ constructor(name, testCallback, isRequired) {
1145
1150
  this.name = name;
1146
- this.packageURL = `${NPM_REGISTRY_URL}/${name}`;
1147
- this.npmPackage = null;
1151
+ this.testCallback = testCallback;
1152
+ this.isRequired = isRequired ?? true;
1153
+ this.children = [];
1148
1154
  }
1149
- get isLoaded() {
1150
- return this.npmPackage !== null;
1155
+ setChildren(children) {
1156
+ this.children = children;
1157
+ return this;
1151
1158
  }
1152
- assertPackageIsLoaded(npmPackage) {
1153
- assert__default.default(this.isLoaded, "The package is not loaded yet");
1159
+ addChild(child) {
1160
+ this.children.push(child);
1161
+ return this;
1154
1162
  }
1155
- getVersionsDict() {
1156
- this.assertPackageIsLoaded(this.npmPackage);
1157
- return this.npmPackage.versions;
1163
+ asOptional() {
1164
+ const newInstance = requirementFactory(this.name, this.testCallback, false);
1165
+ newInstance.setChildren(this.children);
1166
+ return newInstance;
1158
1167
  }
1159
- getVersionsAsList() {
1160
- this.assertPackageIsLoaded(this.npmPackage);
1161
- return Object.values(this.npmPackage.versions);
1168
+ asRequired() {
1169
+ const newInstance = requirementFactory(this.name, this.testCallback, true);
1170
+ newInstance.setChildren(this.children);
1171
+ return newInstance;
1162
1172
  }
1163
- findVersionsInRange(range) {
1164
- const versions = this.getVersionsAsList();
1165
- return versions.filter((v) => range.test(v.version)).filter((v) => isLiteralSemVer(v.version)).sort((v1, v2) => semver__default.default.compare(v1.version, v2.version));
1173
+ async test(context) {
1174
+ try {
1175
+ await this.testCallback?.(context);
1176
+ return ok();
1177
+ } catch (e) {
1178
+ if (e instanceof Error) {
1179
+ return errored(e);
1180
+ }
1181
+ if (typeof e === "string") {
1182
+ return errored(new Error(e));
1183
+ }
1184
+ return errored(new Error("Unknown error"));
1185
+ }
1166
1186
  }
1167
- findVersion(version2) {
1168
- const versions = this.getVersionsAsList();
1169
- return versions.find((npmVersion) => semver__default.default.eq(npmVersion.version, version2));
1187
+ }
1188
+ const ok = () => ({ pass: true, error: null });
1189
+ const errored = (error) => ({ pass: false, error });
1190
+ const requirementFactory = (name, testCallback, isRequired) => new Requirement(name, testCallback, isRequired);
1191
+ const REQUIRE_AVAILABLE_NEXT_MAJOR = requirementFactory(
1192
+ "REQUIRE_AVAILABLE_NEXT_MAJOR",
1193
+ (context) => {
1194
+ const { project, target } = context;
1195
+ const currentMajor = project.strapiVersion.major;
1196
+ const targetedMajor = target.major;
1197
+ if (targetedMajor === currentMajor) {
1198
+ throw new Error(`You're already on the latest major version (v${currentMajor})`);
1199
+ }
1170
1200
  }
1171
- async refresh() {
1172
- const response = await fetch(this.packageURL);
1173
- assert__default.default(response.ok, `Request failed for ${this.packageURL}`);
1174
- this.npmPackage = await response.json();
1175
- return this;
1201
+ );
1202
+ const REQUIRE_LATEST_FOR_CURRENT_MAJOR = requirementFactory(
1203
+ "REQUIRE_LATEST_FOR_CURRENT_MAJOR",
1204
+ (context) => {
1205
+ const { project, target, npmVersionsMatches } = context;
1206
+ const { major: currentMajor } = project.strapiVersion;
1207
+ const invalidMatches = npmVersionsMatches.filter(
1208
+ (match) => semVerFactory(match.version).major === currentMajor
1209
+ );
1210
+ if (invalidMatches.length > 0) {
1211
+ const invalidVersions = invalidMatches.map((match) => match.version);
1212
+ const invalidVersionsCount = invalidVersions.length;
1213
+ throw new Error(
1214
+ `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.`
1215
+ );
1216
+ }
1176
1217
  }
1177
- versionExists(version2) {
1178
- return this.findVersion(version2) !== void 0;
1218
+ );
1219
+ const REQUIRE_GIT_CLEAN_REPOSITORY = requirementFactory(
1220
+ "REQUIRE_GIT_CLEAN_REPOSITORY",
1221
+ async (context) => {
1222
+ const git = simpleGit__default.default({ baseDir: context.project.cwd });
1223
+ const status = await git.status();
1224
+ if (!status.isClean()) {
1225
+ throw new Error(
1226
+ "Repository is not clean. Please commit or stash any changes before upgrading"
1227
+ );
1228
+ }
1179
1229
  }
1180
- }
1181
- const npmPackageFactory = (name) => new Package(name);
1230
+ );
1231
+ const REQUIRE_GIT_REPOSITORY = requirementFactory(
1232
+ "REQUIRE_GIT_REPOSITORY",
1233
+ async (context) => {
1234
+ const git = simpleGit__default.default({ baseDir: context.project.cwd });
1235
+ const isRepo = await git.checkIsRepo();
1236
+ if (!isRepo) {
1237
+ throw new Error("Not a git repository (or any of the parent directories)");
1238
+ }
1239
+ }
1240
+ ).addChild(REQUIRE_GIT_CLEAN_REPOSITORY.asOptional());
1241
+ const REQUIRE_GIT_INSTALLED = requirementFactory(
1242
+ "REQUIRE_GIT_INSTALLED",
1243
+ async (context) => {
1244
+ const git = simpleGit__default.default({ baseDir: context.project.cwd });
1245
+ try {
1246
+ await git.version();
1247
+ } catch {
1248
+ throw new Error("Git is not installed");
1249
+ }
1250
+ }
1251
+ ).addChild(REQUIRE_GIT_REPOSITORY.asOptional());
1252
+ const REQUIRE_GIT = requirementFactory("REQUIRE_GIT", null).addChild(
1253
+ REQUIRE_GIT_INSTALLED.asOptional()
1254
+ );
1255
+ const latest = async (upgrader, options) => {
1256
+ if (options.target !== ReleaseType.Latest) {
1257
+ return;
1258
+ }
1259
+ const npmPackage = upgrader.getNPMPackage();
1260
+ const target = upgrader.getTarget();
1261
+ const project = upgrader.getProject();
1262
+ const { strapiVersion: current } = project;
1263
+ const fTargetMajor = highlight(`v${target.major}`);
1264
+ const fCurrentMajor = highlight(`v${current.major}`);
1265
+ const fTarget = version$1(target);
1266
+ const fCurrent = version$1(current);
1267
+ const isMajorUpgrade = target.major > current.major;
1268
+ if (isMajorUpgrade) {
1269
+ options.logger.warn(
1270
+ `Detected a major upgrade for the "${highlight(ReleaseType.Latest)}" tag: ${fCurrent} > ${fTarget}`
1271
+ );
1272
+ const newerPackageRelease = npmPackage.findVersionsInRange(rangeFactory(`>${current.raw} <${target.major}`)).at(-1);
1273
+ if (newerPackageRelease) {
1274
+ const fLatest = version$1(semVerFactory(newerPackageRelease.version));
1275
+ options.logger.warn(
1276
+ `It's recommended to first upgrade to the latest version of ${fCurrentMajor} (${fLatest}) before upgrading to ${fTargetMajor}.`
1277
+ );
1278
+ }
1279
+ const proceedAnyway = await upgrader.confirm(`I know what I'm doing. Proceed anyway!`);
1280
+ if (!proceedAnyway) {
1281
+ throw new AbortedError();
1282
+ }
1283
+ }
1284
+ };
1182
1285
  const upgrade$1 = async (options) => {
1183
1286
  const timer = timerFactory();
1184
1287
  const { logger, codemodsTarget } = options;
1185
1288
  const cwd = path__default.default.resolve(options.cwd ?? process.cwd());
1186
1289
  const project = projectFactory(cwd);
1290
+ logger.debug(projectDetails(project));
1187
1291
  if (!isApplicationProject(project)) {
1188
1292
  throw new Error(
1189
1293
  `The "${options.target}" upgrade can only be run on a Strapi project; for plugins, please use "codemods".`
1190
1294
  );
1191
1295
  }
1296
+ logger.debug(
1297
+ `Application: VERSION=${version$1(project.packageJSON.version)}; STRAPI_VERSION=${version$1(project.strapiVersion)}`
1298
+ );
1192
1299
  const npmPackage = npmPackageFactory(STRAPI_PACKAGE_NAME);
1193
1300
  await npmPackage.refresh();
1194
1301
  const upgrader = upgraderFactory(project, options.target, npmPackage).dry(options.dry ?? false).onConfirm(options.confirm ?? null).setLogger(logger);
1195
1302
  if (codemodsTarget !== void 0) {
1196
1303
  upgrader.overrideCodemodsTarget(codemodsTarget);
1197
1304
  }
1198
- if (options.target === ReleaseType.Major) {
1199
- upgrader.addRequirement(REQUIRE_AVAILABLE_NEXT_MAJOR).addRequirement(REQUIRE_LATEST_FOR_CURRENT_MAJOR);
1200
- }
1201
- upgrader.addRequirement(REQUIRE_GIT.asOptional());
1305
+ await runUpgradePrompts(upgrader, options);
1306
+ addUpgradeRequirements(upgrader, options);
1202
1307
  const upgradeReport = await upgrader.upgrade();
1203
1308
  if (!upgradeReport.success) {
1204
1309
  throw upgradeReport.error;
1205
1310
  }
1206
1311
  timer.stop();
1207
- logger.info(`Completed in ${durationMs(timer.elapsedMs)}`);
1312
+ logger.info(`Completed in ${durationMs(timer.elapsedMs)}ms`);
1313
+ };
1314
+ const runUpgradePrompts = async (upgrader, options) => {
1315
+ if (options.target === ReleaseType.Latest) {
1316
+ await latest(upgrader, options);
1317
+ }
1318
+ };
1319
+ const addUpgradeRequirements = (upgrader, options) => {
1320
+ if (options.target === ReleaseType.Major) {
1321
+ upgrader.addRequirement(REQUIRE_AVAILABLE_NEXT_MAJOR).addRequirement(REQUIRE_LATEST_FOR_CURRENT_MAJOR);
1322
+ }
1323
+ upgrader.addRequirement(REQUIRE_GIT.asOptional());
1208
1324
  };
1209
1325
  const resolvePath = (cwd) => path__default.default.resolve(cwd ?? process.cwd());
1210
1326
  const getRangeFromTarget = (currentVersion, target) => {
@@ -1213,6 +1329,8 @@ const getRangeFromTarget = (currentVersion, target) => {
1213
1329
  }
1214
1330
  const { major, minor, patch } = currentVersion;
1215
1331
  switch (target) {
1332
+ case ReleaseType.Latest:
1333
+ throw new Error("Can't use <latest> to create a codemods range: not implemented");
1216
1334
  case ReleaseType.Major:
1217
1335
  return rangeFactory(`${major}`);
1218
1336
  case ReleaseType.Minor:
@@ -1238,7 +1356,7 @@ const runCodemods$1 = async (options) => {
1238
1356
  const cwd = resolvePath(options.cwd);
1239
1357
  const project = projectFactory(cwd);
1240
1358
  const range = findRangeFromTarget(project, options.target);
1241
- logger.debug(`Project: ${projectType(project.type)} found in ${path(cwd)}`);
1359
+ logger.debug(projectDetails(project));
1242
1360
  logger.debug(`Range: set to ${versionRange(range)}`);
1243
1361
  const codemodRunner = codemodRunnerFactory(project, range).dry(options.dry ?? false).onSelectCodemods(options.selectCodemods ?? null).setLogger(logger);
1244
1362
  let report;
@@ -1259,7 +1377,7 @@ const listCodemods$1 = async (options) => {
1259
1377
  const cwd = resolvePath(options.cwd);
1260
1378
  const project = projectFactory(cwd);
1261
1379
  const range = findRangeFromTarget(project, target);
1262
- logger.debug(`Project: ${projectType(project.type)} found in ${path(cwd)}`);
1380
+ logger.debug(projectDetails(project));
1263
1381
  logger.debug(`Range: set to ${versionRange(range)}`);
1264
1382
  const repo = codemodRepositoryFactory();
1265
1383
  repo.refresh();
@@ -1332,6 +1450,10 @@ const register$1 = (program) => {
1332
1450
  return upgrade({ ...options, target: releaseType });
1333
1451
  });
1334
1452
  };
1453
+ addReleaseUpgradeCommand(
1454
+ ReleaseType.Latest,
1455
+ "Upgrade to the latest available version of Strapi"
1456
+ );
1335
1457
  addReleaseUpgradeCommand(
1336
1458
  ReleaseType.Major,
1337
1459
  "Upgrade to the next available major version of Strapi"
@@ -1444,7 +1566,7 @@ When executed on a Strapi plugin project, it shows every codemods.
1444
1566
  return listCodemods(options);
1445
1567
  });
1446
1568
  };
1447
- const version = "0.0.0-experimental.edc24aaa3bb5a90fa5fd4fee208167dd4e2e38d4";
1569
+ const version = "0.0.0-experimental.ee7402bacc4656d268ab76aa9c334a7b7a951201";
1448
1570
  register$1(commander.program);
1449
1571
  register(commander.program);
1450
1572
  commander.program.usage("<command> [options]").on("command:*", ([invalidCmd]) => {