mirror of
https://github.com/strapi/strapi.git
synced 2025-10-25 06:51:12 +00:00
chore: implement v5 codemods
This commit is contained in:
parent
a063b749b6
commit
c81b5f327c
368
packages/utils/upgrade/resources/codemods/5.0.0/entity-service-document-service.code.ts
vendored
Normal file
368
packages/utils/upgrade/resources/codemods/5.0.0/entity-service-document-service.code.ts
vendored
Normal file
@ -0,0 +1,368 @@
|
||||
import type { Transform, JSCodeshift, ASTPath, ObjectExpression } from 'jscodeshift';
|
||||
|
||||
/*
|
||||
This codemod transforms entity service calls to match the new document service interface.
|
||||
It supports all kind of argument parsing, including spread elements & deeply nested objects.
|
||||
|
||||
Here is a list of scenarios this was tested against
|
||||
|
||||
const uid = "api::xxx.xxx";
|
||||
const entityId = 1;
|
||||
|
||||
Case: basic call
|
||||
|
||||
strapi.entityService.findOne(uid, entityId, {
|
||||
fields: ["id", "name", "description"],
|
||||
populate: ["author", "comments"],
|
||||
publicationState: "preview",
|
||||
});
|
||||
|
||||
|
||||
Case: using a variable declared somewhere else
|
||||
|
||||
const objectParam_2 = {
|
||||
fields: ["id", "name", "description"],
|
||||
populate: ["author", "comments"],
|
||||
publicationState: "preview",
|
||||
};
|
||||
|
||||
strapi.entityService.findOne(uid, entityId, objectParam_2);
|
||||
|
||||
Case: using a variable declared somewhere else with a spread element
|
||||
|
||||
const objectParam_3 = {
|
||||
fields: ["id", "name", "description"],
|
||||
populate: ["author", "comments"],
|
||||
publicationState: "preview",
|
||||
};
|
||||
|
||||
strapi.entityService.findOne(uid, entityId, {
|
||||
...objectParam_3,
|
||||
});
|
||||
|
||||
|
||||
Case: using a variable declared somewhere else with a spread element and overwritten properties
|
||||
|
||||
const objectParam_4_1 = {
|
||||
fields: ["id", "name", "description"],
|
||||
populate: ["author", "comments"],
|
||||
publicationState: "preview",
|
||||
};
|
||||
|
||||
const objectParam_4 = {
|
||||
publicationState: "live",
|
||||
...objectParam_4_1,
|
||||
};
|
||||
|
||||
strapi.entityService.findOne(uid, entityId, {
|
||||
...objectParam_4,
|
||||
});
|
||||
|
||||
Case: using a variable declared somewhere else with a spread array element while that need its 1st element to be moved
|
||||
|
||||
const objectParam_5 = [
|
||||
uid,
|
||||
entityId,
|
||||
{
|
||||
fields: ["id", "name", "description"],
|
||||
populate: ["author", "comments"],
|
||||
publicationState: "preview",
|
||||
},
|
||||
];
|
||||
|
||||
strapi.entityService.findOne(...objectParam_5);
|
||||
|
||||
Case: using a variable declared somewhere else with a partial spread array
|
||||
|
||||
const objectParam_6 = [
|
||||
entityId,
|
||||
{
|
||||
fields: ["id", "name", "description"],
|
||||
populate: ["author", "comments"],
|
||||
publicationState: "preview",
|
||||
},
|
||||
];
|
||||
|
||||
strapi.entityService.findOne(uid, ...objectParam_6);
|
||||
|
||||
Case: using a variable declared somewhere else with a partial & nested spread arrays
|
||||
|
||||
const objectParam_7_1 = [
|
||||
{
|
||||
fields: ["id", "name", "description"],
|
||||
populate: ["author", "comments"],
|
||||
publicationState: "preview",
|
||||
},
|
||||
];
|
||||
|
||||
const objectParam_7 = [entityId, ...objectParam_7_1];
|
||||
|
||||
strapi.entityService.findOne(uid, ...objectParam_7);
|
||||
|
||||
Case: using a variable declared somewhere else with a partial & nested spread arrays & objects
|
||||
|
||||
const objectParam_8_1 = {
|
||||
publicationState: "preview",
|
||||
};
|
||||
|
||||
const objectParam_8 = [
|
||||
entityId,
|
||||
{
|
||||
fields: ["id", "name", "description"],
|
||||
populate: ["author", "comments"],
|
||||
...objectParam_8_1,
|
||||
},
|
||||
];
|
||||
|
||||
strapi.entityService.findOne(uid, ...objectParam_8);
|
||||
|
||||
|
||||
Case: some sort of mix of all the above
|
||||
|
||||
const objectParam_9_1 = {
|
||||
publicationState: "preview",
|
||||
};
|
||||
|
||||
const objectParam_9 = {
|
||||
fields: ["id", "name", "description"],
|
||||
populate: ["author", "comments"],
|
||||
...objectParam_9_1,
|
||||
};
|
||||
|
||||
strapi.entityService.findOne(uid, ...[entityId, [objectParam_9]]);
|
||||
|
||||
Case: even more complex
|
||||
|
||||
const objectParam_10_1 = {
|
||||
publicationState: "preview",
|
||||
};
|
||||
|
||||
const objectParam_10_2 = [uid, ...[12], ...[objectParam_10_1]];
|
||||
const objectParam_10 = [...objectParam_10_2];
|
||||
|
||||
strapi.entityService.findOne(...[...objectParam_10]);
|
||||
|
||||
*/
|
||||
|
||||
const transformDeclaration = (path: ASTPath<any>, name: any, j: JSCodeshift) => {
|
||||
const declaration = findClosesDeclaration(path, name, j);
|
||||
|
||||
if (!declaration) {
|
||||
return;
|
||||
}
|
||||
|
||||
transformElement(path, declaration.init, j);
|
||||
};
|
||||
|
||||
const transformElement = (path: ASTPath<any>, element: any, j: JSCodeshift) => {
|
||||
switch (true) {
|
||||
case j.ObjectExpression.check(element): {
|
||||
transformObjectParam(path, element, j);
|
||||
break;
|
||||
}
|
||||
|
||||
case j.Identifier.check(element): {
|
||||
transformDeclaration(path, element.name, j);
|
||||
break;
|
||||
}
|
||||
|
||||
case j.SpreadElement.check(element): {
|
||||
transformElement(path, element.argument, j);
|
||||
break;
|
||||
}
|
||||
|
||||
case j.ArrayExpression.check(element): {
|
||||
element.elements.forEach((element) => {
|
||||
transformElement(path, element, j);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const transformObjectParam = (path: ASTPath<any>, expression: ObjectExpression, j: JSCodeshift) => {
|
||||
expression.properties.forEach((prop) => {
|
||||
switch (true) {
|
||||
case j.ObjectProperty.check(prop): {
|
||||
if (!j.Identifier.check(prop.key) && !j.Literal.check(prop.key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (j.Identifier.check(prop.key) && prop.key.name !== 'publicationState') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (j.Literal.check(prop.key) && prop.key.value !== 'publicationState') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (j.Identifier.check(prop.key) && prop.key.name === 'publicationState') {
|
||||
if (!prop.computed && !prop.shorthand) {
|
||||
prop.key.name = 'status';
|
||||
}
|
||||
|
||||
if (prop.shorthand && !prop.computed) {
|
||||
prop.shorthand = false;
|
||||
prop.key = j.identifier('status');
|
||||
prop.value = j.identifier('publicationState');
|
||||
}
|
||||
} else if (j.Literal.check(prop.key) && prop.key.value === 'publicationState') {
|
||||
prop.key.value = 'status';
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case j.Literal.check(prop.value): {
|
||||
prop.value = prop.value.value === 'live' ? j.literal('published') : j.literal('draft');
|
||||
|
||||
break;
|
||||
}
|
||||
case j.Identifier.check(prop.value): {
|
||||
const declaration = findClosesDeclaration(path, prop.value.name, j);
|
||||
|
||||
if (!declaration) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (j.Literal.check(declaration.init)) {
|
||||
declaration.init =
|
||||
declaration.init.value === 'live' ? j.literal('published') : j.literal('draft');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case j.SpreadElement.check(prop): {
|
||||
transformElement(path, prop.argument, j);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const findClosesDeclaration = (path: ASTPath<any>, name: string, j) => {
|
||||
// find Identifier declaration
|
||||
const scope = path.scope.lookup(name);
|
||||
|
||||
if (!scope) {
|
||||
return;
|
||||
}
|
||||
|
||||
return j(scope.path)
|
||||
.find(j.VariableDeclarator, { id: { type: 'Identifier', name } })
|
||||
.nodes()[0];
|
||||
};
|
||||
|
||||
const transform: Transform = (file, api) => {
|
||||
const j = api.jscodeshift;
|
||||
|
||||
const root = j(file.source);
|
||||
|
||||
root
|
||||
.find(j.CallExpression, {
|
||||
callee: {
|
||||
type: 'MemberExpression',
|
||||
object: {
|
||||
type: 'MemberExpression',
|
||||
object: {
|
||||
type: 'Identifier',
|
||||
name: 'strapi',
|
||||
},
|
||||
property: {
|
||||
type: 'Identifier',
|
||||
name: 'entityService',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.replaceWith((path) => {
|
||||
if (!j.MemberExpression.check(path.value.callee)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const args = path.value.arguments;
|
||||
|
||||
if (args.length === 0) {
|
||||
// we don't know how to transform this
|
||||
return;
|
||||
}
|
||||
|
||||
type Args = typeof path.value.arguments;
|
||||
|
||||
function resolveArgs(args: Args): Args {
|
||||
return args.flatMap((arg: Args[number]) => {
|
||||
switch (true) {
|
||||
case j.Identifier.check(arg):
|
||||
case j.Literal.check(arg): {
|
||||
return arg;
|
||||
}
|
||||
case j.SpreadElement.check(arg): {
|
||||
switch (true) {
|
||||
case j.Identifier.check(arg.argument): {
|
||||
const identifier = arg.argument;
|
||||
|
||||
const declaration = findClosesDeclaration(path, identifier.name, j);
|
||||
|
||||
if (!declaration) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case j.ArrayExpression.check(declaration.init): {
|
||||
return resolveArgs(declaration.init.elements);
|
||||
}
|
||||
default:
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
case j.ArrayExpression.check(arg.argument): {
|
||||
return resolveArgs(arg.argument.elements as Args);
|
||||
}
|
||||
default: {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const resolvedArgs = resolveArgs(args);
|
||||
|
||||
const [docUID, ...rest] = resolvedArgs;
|
||||
|
||||
path.value.arguments.forEach((arg) => {
|
||||
transformElement(path, arg, j);
|
||||
});
|
||||
|
||||
return j.callExpression(
|
||||
j.memberExpression(
|
||||
j.callExpression(j.memberExpression(j.identifier('strapi'), j.identifier('documents')), [
|
||||
docUID,
|
||||
]),
|
||||
path.value.callee.property
|
||||
),
|
||||
rest
|
||||
);
|
||||
});
|
||||
|
||||
return root.toSource();
|
||||
};
|
||||
|
||||
export const parser = 'tsx';
|
||||
|
||||
export default transform;
|
||||
@ -1,11 +1,44 @@
|
||||
import { Transform, JSCodeshift, Collection } from 'jscodeshift';
|
||||
|
||||
/*
|
||||
This codemod transforms @strapi/strapi imports to use the new public interface.
|
||||
|
||||
ESM
|
||||
Before:
|
||||
|
||||
import strapi from '@strapi/strapi';
|
||||
strapi();
|
||||
|
||||
After:
|
||||
|
||||
import { createStrapi } from '@strapi/strapi'; // keeps the default import
|
||||
createStrapi();
|
||||
|
||||
---
|
||||
|
||||
Common JS
|
||||
Before:
|
||||
|
||||
const strapi = require('@strapi/strapi');
|
||||
strapi();
|
||||
|
||||
After:
|
||||
|
||||
const strapi = require('@strapi/strapi');
|
||||
strapi.createStrapi();
|
||||
|
||||
*/
|
||||
|
||||
const transformStrapiImport = (root: Collection, j: JSCodeshift) => {
|
||||
root.find(j.ImportDefaultSpecifier).forEach((path) => {
|
||||
if (path.parent.value.source.value === '@strapi/strapi') {
|
||||
const newSpecifiers = path.parent.value.specifiers.filter(
|
||||
(specifier) => specifier.type !== 'ImportDefaultSpecifier'
|
||||
);
|
||||
|
||||
j(path.parent).replaceWith(
|
||||
j.importDeclaration(
|
||||
[...path.parent.value.specifiers, j.importSpecifier(j.identifier('createStrapi'))],
|
||||
[...newSpecifiers, j.importSpecifier(j.identifier('createStrapi'))],
|
||||
j.literal('@strapi/strapi')
|
||||
)
|
||||
);
|
||||
|
||||
315
packages/utils/upgrade/resources/codemods/5.0.0/utils-public-interface.code.ts
vendored
Normal file
315
packages/utils/upgrade/resources/codemods/5.0.0/utils-public-interface.code.ts
vendored
Normal file
@ -0,0 +1,315 @@
|
||||
import { Transform, JSCodeshift, Collection } from 'jscodeshift';
|
||||
|
||||
/*
|
||||
|
||||
This codemod transforms @strapi/utils imports to change method calls to math the new public interface.
|
||||
It will also warn about removed functions to avoid breaking user code.
|
||||
|
||||
ESM
|
||||
|
||||
Before:
|
||||
|
||||
import * as utils from '@strapi/utils';
|
||||
|
||||
utils.nameToSlug();
|
||||
|
||||
After:
|
||||
|
||||
import { strings } from '@strapi/utils';
|
||||
|
||||
strings.nameToSlug();
|
||||
|
||||
---
|
||||
ESM
|
||||
|
||||
Before:
|
||||
|
||||
import { nameToSlug } from '@strapi/utils';
|
||||
|
||||
nameToSlug();
|
||||
|
||||
After:
|
||||
|
||||
import { strings } from '@strapi/utils';
|
||||
|
||||
strings.nameToSlug();
|
||||
|
||||
---
|
||||
|
||||
Common JS
|
||||
|
||||
Before:
|
||||
|
||||
const utils = require('@strapi/utils');
|
||||
|
||||
utils.nameToSlug();
|
||||
|
||||
After:
|
||||
|
||||
const { strings } = require('@strapi/utils');
|
||||
|
||||
strings.nameToSlug();
|
||||
|
||||
---
|
||||
Common JS
|
||||
|
||||
Before:
|
||||
|
||||
const { nameToSlug } = require('@strapi/utils');
|
||||
|
||||
nameToSlug();
|
||||
|
||||
After:
|
||||
|
||||
const { strings } = require('@strapi/utils');
|
||||
|
||||
strings.nameToSlug();
|
||||
|
||||
*/
|
||||
|
||||
const changes = {
|
||||
strings: {
|
||||
nameToSlug: 'nameToSlug',
|
||||
nameToCollectionName: 'nameToCollectionName',
|
||||
stringEquals: 'isEqual',
|
||||
isCamelCase: 'isCamelCase',
|
||||
isKebabCase: 'isKebabCase',
|
||||
toKebabCase: 'toKebabCase',
|
||||
toRegressedEnumValue: 'toRegressedEnumValue',
|
||||
startsWithANumber: 'startsWithANumber',
|
||||
joinBy: 'joinBy',
|
||||
},
|
||||
arrays: {
|
||||
stringIncludes: 'includesString',
|
||||
},
|
||||
objects: {
|
||||
keysDeep: 'keysDeep',
|
||||
},
|
||||
dates: {
|
||||
generateTimestampCode: 'timestampCode',
|
||||
},
|
||||
async: {
|
||||
pipeAsync: 'pipe',
|
||||
mapAsync: 'map',
|
||||
reduceAsync: 'reduce',
|
||||
},
|
||||
};
|
||||
|
||||
const removed = [
|
||||
'getCommonBeginning',
|
||||
'templateConfiguration',
|
||||
'removeUndefined',
|
||||
'getConfigUrls',
|
||||
'getAbsoluteAdminUrl',
|
||||
'getAbsoluteServerUrl',
|
||||
'forEachAsync',
|
||||
];
|
||||
|
||||
const transformImports = (root: Collection, j: JSCodeshift) => {
|
||||
root
|
||||
.find(j.ImportDeclaration, {
|
||||
source: { value: '@strapi/utils' },
|
||||
})
|
||||
.forEach((path) => {
|
||||
path.value.specifiers?.forEach((specifier) => {
|
||||
if (!j.ImportSpecifier.check(specifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (removed.includes(specifier.imported.name)) {
|
||||
console.warn(
|
||||
`Function "${specifier.imported.name}" as removed. You will have to remove it from your code.`
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
for (const primitive of Object.keys(changes)) {
|
||||
const functions = Object.keys(changes[primitive]);
|
||||
|
||||
const specifiersToRefactor = path.value.specifiers?.filter((specifier) => {
|
||||
return j.ImportSpecifier.check(specifier) && functions.includes(specifier.imported.name);
|
||||
});
|
||||
|
||||
if (specifiersToRefactor?.length > 0) {
|
||||
path.value.specifiers?.unshift(j.importSpecifier(j.identifier(primitive)));
|
||||
|
||||
specifiersToRefactor.forEach((specifier) => {
|
||||
const index = path.value.specifiers?.indexOf(specifier);
|
||||
path.value.specifiers?.splice(index, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (path.value.specifiers?.length === 0) {
|
||||
j(path).remove();
|
||||
}
|
||||
});
|
||||
|
||||
root.find(j.ImportNamespaceSpecifier).forEach((specifierPath) => {
|
||||
if (specifierPath.parent.value.source.value === '@strapi/utils') {
|
||||
for (const primitive of Object.keys(changes)) {
|
||||
const functions = Object.keys(changes[primitive]);
|
||||
functions.forEach((funcName) => {
|
||||
root
|
||||
.find(j.CallExpression, {
|
||||
callee: {
|
||||
type: 'MemberExpression',
|
||||
property: {
|
||||
type: 'Identifier',
|
||||
name: funcName,
|
||||
},
|
||||
object: {
|
||||
type: 'Identifier',
|
||||
name: specifierPath.value.local.name,
|
||||
},
|
||||
},
|
||||
})
|
||||
.replaceWith((path) => {
|
||||
return j.callExpression(
|
||||
j.memberExpression(
|
||||
j.memberExpression(
|
||||
j.identifier(specifierPath.value.local.name),
|
||||
j.identifier(primitive)
|
||||
),
|
||||
j.identifier(changes[primitive][funcName])
|
||||
),
|
||||
path.value.arguments
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
root
|
||||
.find(j.VariableDeclarator, {
|
||||
init: {
|
||||
callee: {
|
||||
name: 'require',
|
||||
},
|
||||
arguments: [{ value: '@strapi/utils' }],
|
||||
},
|
||||
})
|
||||
.forEach((path) => {
|
||||
// destrucured require
|
||||
if (j.ObjectPattern.check(path.value.id)) {
|
||||
const properties = path.value.id.properties;
|
||||
|
||||
properties?.forEach((property) => {
|
||||
if (!j.ObjectProperty.check(property) || !j.Identifier.check(property.value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (removed.includes(property.value.name)) {
|
||||
console.warn(
|
||||
`Function "${property.value.name}" as removed. You will have to remove it from your code.`
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
for (const primitive of Object.keys(changes)) {
|
||||
const functions = Object.keys(changes[primitive]);
|
||||
|
||||
const propertiesToRefactor = properties?.filter((property) => {
|
||||
return (
|
||||
j.ObjectProperty.check(property) &&
|
||||
j.Identifier.check(property.value) &&
|
||||
functions.includes(property.value.name)
|
||||
);
|
||||
});
|
||||
|
||||
if (propertiesToRefactor?.length > 0) {
|
||||
const identifier = j.identifier(primitive);
|
||||
|
||||
properties?.unshift(
|
||||
j.objectProperty.from({
|
||||
key: identifier,
|
||||
value: identifier,
|
||||
shorthand: true,
|
||||
})
|
||||
);
|
||||
|
||||
propertiesToRefactor.forEach((property) => {
|
||||
const index = properties?.indexOf(property);
|
||||
properties?.splice(index, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (path.value.id.properties?.length === 0) {
|
||||
j(path).remove();
|
||||
}
|
||||
}
|
||||
|
||||
// namespace require
|
||||
if (path.value.id.type === 'Identifier') {
|
||||
const identifier = path.value.id.name;
|
||||
|
||||
for (const primitive of Object.keys(changes)) {
|
||||
const functions = Object.keys(changes[primitive]);
|
||||
functions.forEach((funcName) => {
|
||||
root
|
||||
.find(j.CallExpression, {
|
||||
callee: {
|
||||
type: 'MemberExpression',
|
||||
property: {
|
||||
type: 'Identifier',
|
||||
name: funcName,
|
||||
},
|
||||
object: {
|
||||
type: 'Identifier',
|
||||
name: identifier,
|
||||
},
|
||||
},
|
||||
})
|
||||
.replaceWith((path) => {
|
||||
return j.callExpression(
|
||||
j.memberExpression(
|
||||
j.memberExpression(j.identifier(identifier), j.identifier(primitive)),
|
||||
j.identifier(changes[primitive][funcName])
|
||||
),
|
||||
path.value.arguments
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (const primitive of Object.keys(changes)) {
|
||||
const functions = Object.keys(changes[primitive]);
|
||||
functions.forEach((funcName) => {
|
||||
root
|
||||
.find(j.CallExpression, {
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
name: funcName,
|
||||
},
|
||||
})
|
||||
.replaceWith((path) => {
|
||||
if (j.Identifier.check(path.value.callee)) {
|
||||
path.value.callee.name = changes[primitive][funcName];
|
||||
return j.memberExpression(j.identifier(primitive), path.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const transform: Transform = (file, api) => {
|
||||
const j = api.jscodeshift;
|
||||
|
||||
const root = j(file.source);
|
||||
|
||||
transformImports(root, j);
|
||||
|
||||
return root.toSource();
|
||||
};
|
||||
|
||||
export const parser = 'tsx';
|
||||
|
||||
export default transform;
|
||||
Loading…
x
Reference in New Issue
Block a user