Fix Upgrade Tool Targeted Files For Plugins (#21033)

This commit is contained in:
Jean-Sébastien Herbaux 2024-08-23 14:20:46 +02:00 committed by GitHub
parent 45c8d25668
commit ca3cb5d50a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 105 additions and 50 deletions

View File

@ -3,7 +3,7 @@ import chalk from 'chalk';
import { constants as timerConstants } from '../timer';
import type { ProjectType } from '../project';
import type { AppProject, PluginProject, ProjectType } from '../project';
import type { Codemod } from '../codemod';
import type { Version } from '../version';
import type { Report } from '../report';
@ -18,6 +18,10 @@ export const codemodUID = (uid: string) => {
return chalk.bold.cyan(uid);
};
export const projectDetails = (project: AppProject | PluginProject) => {
return `Project: TYPE=${projectType(project.type)}; CWD=${path(project.cwd)}; PATHS=${project.paths.map(path)}`;
};
export const projectType = (type: ProjectType) => chalk.cyan(type);
export const versionRange = (range: Version.Range) => chalk.italic.yellow(range.raw);

View File

@ -8,7 +8,12 @@ jest.mock('fs', () => fs);
const srcFilename = (cwd: string, filename: string) => path.join(cwd, 'src', filename);
const srcFilenames = (cwd: string) => {
return Object.keys(srcFiles).map((filename) => srcFilename(cwd, filename));
return Object.keys(defaultFiles).map((filename) => srcFilename(cwd, filename));
};
const pluginServerFilename = (cwd: string, filename: string) => path.join(cwd, 'server', filename);
const pluginServerFilenames = (cwd: string) => {
return Object.keys(defaultFiles).map((filename) => pluginServerFilename(cwd, filename));
};
const currentStrapiVersion = '1.2.3';
@ -29,7 +34,7 @@ const pluginPackageJSONFile = `{
}
}`;
const srcFiles = {
const defaultFiles = {
'a.ts': 'console.log("a.ts")',
'b.ts': 'console.log("b.ts")',
'c.js': 'console.log("c.js")',
@ -40,12 +45,12 @@ const srcFiles = {
const appVolume = {
'package.json': appPackageJSONFile,
src: srcFiles,
src: defaultFiles,
};
const pluginVolume = {
'package.json': pluginPackageJSONFile,
src: srcFiles,
server: defaultFiles,
};
describe('Project', () => {
@ -70,7 +75,7 @@ describe('Project', () => {
});
test('Fails on project without package.json file', async () => {
vol.fromNestedJSON({ src: srcFiles }, defaultCWD);
vol.fromNestedJSON({ src: defaultFiles }, defaultCWD);
expect(() => projectFactory(defaultCWD)).toThrow(
`Could not find a package.json file in ${defaultCWD}`
@ -79,7 +84,7 @@ describe('Project', () => {
test('Fails when not a plugin and no @strapi/strapi dependency found', async () => {
vol.fromNestedJSON(
{ 'package.json': `{ "name": "test", "version": "1.2.3" }`, src: srcFiles },
{ 'package.json': `{ "name": "test", "version": "1.2.3" }`, src: defaultFiles },
defaultCWD
);
@ -92,7 +97,7 @@ describe('Project', () => {
vol.fromNestedJSON(
{
'package.json': `{ "name": "test", "version": "1.2.3", "dependencies": { "@strapi/strapi": "^4.0.0" } }`,
src: srcFiles,
src: defaultFiles,
},
defaultCWD
);
@ -133,7 +138,10 @@ describe('Project', () => {
expect(project.files.length).toBe(7);
expect(project.files).toStrictEqual(
expect.arrayContaining([path.join(defaultCWD, 'package.json'), ...srcFilenames(defaultCWD)])
expect.arrayContaining([
path.join(defaultCWD, 'package.json'),
...pluginServerFilenames(defaultCWD),
])
);
expect(project.cwd).toBe(defaultCWD);
@ -172,7 +180,10 @@ describe('Project', () => {
expect(project.files.length).toBe(7);
expect(project.files).toStrictEqual(
expect.arrayContaining([path.join(defaultCWD, 'package.json'), ...srcFilenames(defaultCWD)])
expect.arrayContaining([
path.join(defaultCWD, 'package.json'),
...pluginServerFilenames(defaultCWD),
])
);
expect(project.cwd).toBe(defaultCWD);

View File

@ -1,8 +1,12 @@
export const PROJECT_PACKAGE_JSON = 'package.json';
export const PROJECT_DEFAULT_ALLOWED_ROOT_PATHS = ['src', 'config', 'public', 'admin', 'server'];
export const PROJECT_APP_ALLOWED_ROOT_PATHS = ['src', 'config', 'public'];
export const PROJECT_DEFAULT_CODE_EXTENSIONS = [
export const PROJECT_PLUGIN_ALLOWED_ROOT_PATHS = ['admin', 'server'];
export const PROJECT_PLUGIN_ROOT_FILES = ['strapi-admin.js', 'strapi-server.js'];
export const PROJECT_CODE_EXTENSIONS = [
// Source files
'js',
'mjs',
@ -12,14 +16,9 @@ export const PROJECT_DEFAULT_CODE_EXTENSIONS = [
'tsx',
];
export const PROJECT_DEFAULT_JSON_EXTENSIONS = ['json'];
export const PROJECT_JSON_EXTENSIONS = ['json'];
export const PROJECT_DEFAULT_ALLOWED_EXTENSIONS = [
...PROJECT_DEFAULT_CODE_EXTENSIONS,
...PROJECT_DEFAULT_JSON_EXTENSIONS,
];
export const PROJECT_DEFAULT_PATTERNS = ['package.json'];
export const PROJECT_ALLOWED_EXTENSIONS = [...PROJECT_CODE_EXTENSIONS, ...PROJECT_JSON_EXTENSIONS];
export const SCOPED_STRAPI_PACKAGE_PREFIX = '@strapi/';

View File

@ -12,7 +12,13 @@ import * as constants from './constants';
import type { Version } from '../version';
import type { Codemod } from '../codemod';
import type { Report } from '../report';
import type { FileExtension, MinimalPackageJSON, ProjectType, RunCodemodsOptions } from './types';
import type {
FileExtension,
MinimalPackageJSON,
ProjectConfig,
ProjectType,
RunCodemodsOptions,
} from './types';
export class Project {
public cwd: string;
@ -25,12 +31,15 @@ export class Project {
public packageJSON!: MinimalPackageJSON;
constructor(cwd: string) {
public readonly paths: string[];
constructor(cwd: string, config: ProjectConfig) {
if (!fse.pathExistsSync(cwd)) {
throw new Error(`ENOENT: no such file or directory, access '${cwd}'`);
}
this.cwd = cwd;
this.paths = config.paths;
this.refresh();
}
@ -67,12 +76,8 @@ export class Project {
}
private createProjectCodemodsRunners(dry: boolean = false) {
const jsonExtensions = constants.PROJECT_DEFAULT_JSON_EXTENSIONS.map<FileExtension>(
(ext) => `.${ext}`
);
const codeExtensions = constants.PROJECT_DEFAULT_CODE_EXTENSIONS.map<FileExtension>(
(ext) => `.${ext}`
);
const jsonExtensions = constants.PROJECT_JSON_EXTENSIONS.map<FileExtension>((ext) => `.${ext}`);
const codeExtensions = constants.PROJECT_CODE_EXTENSIONS.map<FileExtension>((ext) => `.${ext}`);
const jsonFiles = this.getFilesByExtensions(jsonExtensions);
const codeFiles = this.getFilesByExtensions(codeExtensions);
@ -82,7 +87,7 @@ export class Project {
parser: 'ts',
runInBand: true,
babel: true,
extensions: constants.PROJECT_DEFAULT_CODE_EXTENSIONS.join(','),
extensions: constants.PROJECT_CODE_EXTENSIONS.join(','),
// Don't output any log coming from the runner
print: false,
silent: true,
@ -109,20 +114,9 @@ export class Project {
}
private refreshProjectFiles(): void {
const allowedRootPaths = formatGlobCollectionPattern(
constants.PROJECT_DEFAULT_ALLOWED_ROOT_PATHS
);
const allowedExtensions = formatGlobCollectionPattern(
constants.PROJECT_DEFAULT_ALLOWED_EXTENSIONS
);
const projectFilesPattern = `./${allowedRootPaths}/**/*.${allowedExtensions}`;
const patterns = [projectFilesPattern, ...constants.PROJECT_DEFAULT_PATTERNS];
const scanner = fileScannerFactory(this.cwd);
this.files = scanner.scan(patterns);
this.files = scanner.scan(this.paths);
}
}
@ -131,8 +125,25 @@ export class AppProject extends Project {
readonly type = 'application' as const satisfies ProjectType;
/**
* Returns an array of allowed file paths for a Strapi application
*
* The resulting paths include app default files and the root package.json file.
*/
private static get paths() {
const allowedRootPaths = formatGlobCollectionPattern(constants.PROJECT_APP_ALLOWED_ROOT_PATHS);
const allowedExtensions = formatGlobCollectionPattern(constants.PROJECT_ALLOWED_EXTENSIONS);
return [
// App default files
`./${allowedRootPaths}/**/*.${allowedExtensions}`,
// Root package.json file
constants.PROJECT_PACKAGE_JSON,
];
}
constructor(cwd: string) {
super(cwd);
super(cwd, { paths: AppProject.paths });
this.refreshStrapiVersion();
}
@ -206,6 +217,31 @@ const formatGlobCollectionPattern = (collection: string[]): string => {
export class PluginProject extends Project {
readonly type = 'plugin' as const satisfies ProjectType;
/**
* Returns an array of allowed file paths for a Strapi plugin
*
* The resulting paths include plugin default files, the root package.json file, and plugin-specific files.
*/
private static get paths() {
const allowedRootPaths = formatGlobCollectionPattern(
constants.PROJECT_PLUGIN_ALLOWED_ROOT_PATHS
);
const allowedExtensions = formatGlobCollectionPattern(constants.PROJECT_ALLOWED_EXTENSIONS);
return [
// Plugin default files
`./${allowedRootPaths}/**/*.${allowedExtensions}`,
// Root package.json file
constants.PROJECT_PACKAGE_JSON,
// Plugin root files
...constants.PROJECT_PLUGIN_ROOT_FILES,
];
}
constructor(cwd: string) {
super(cwd, { paths: PluginProject.paths });
}
}
const isPlugin = (cwd: string) => {
@ -228,9 +264,5 @@ const isPlugin = (cwd: string) => {
export const projectFactory = (cwd: string) => {
fse.accessSync(cwd);
if (isPlugin(cwd)) {
return new PluginProject(cwd);
}
return new AppProject(cwd);
return isPlugin(cwd) ? new PluginProject(cwd) : new AppProject(cwd);
};

View File

@ -22,3 +22,7 @@ export type MinimalPackageJSON = {
version: string;
dependencies?: Record<string, string>;
} & Utils.JSONObject;
export interface ProjectConfig {
paths: string[];
}

View File

@ -43,6 +43,7 @@ describe('codemods task', () => {
});
(projectFactory as jest.Mock).mockReturnValue({
paths: [],
dry: jest.fn().mockReturnThis(),
onSelectCodemods: jest.fn().mockReturnThis(),
setLogger: jest.fn().mockReturnThis(),

View File

@ -13,7 +13,7 @@ export const listCodemods = async (options: ListCodemodsOptions) => {
const project = projectFactory(cwd);
const range = findRangeFromTarget(project, target);
logger.debug(`Project: ${f.projectType(project.type)} found in ${f.path(cwd)}`);
logger.debug(f.projectDetails(project));
logger.debug(`Range: set to ${f.versionRange(range)}`);
// Create a codemod repository targeting the default location of the codemods

View File

@ -17,7 +17,7 @@ export const runCodemods = async (options: RunCodemodsOptions) => {
const project = projectFactory(cwd);
const range = findRangeFromTarget(project, options.target);
logger.debug(`Project: ${f.projectType(project.type)} found in ${f.path(cwd)}`);
logger.debug(f.projectDetails(project));
logger.debug(`Range: set to ${f.versionRange(range)}`);
const codemodRunner = codemodRunnerFactory(project, range)

View File

@ -19,12 +19,16 @@ export const upgrade = async (options: UpgradeOptions) => {
const project = projectFactory(cwd);
logger.debug(f.projectDetails(project));
if (!isApplicationProject(project)) {
throw new Error(
`The "${options.target}" upgrade can only be run on a Strapi project; for plugins, please use "codemods".`
);
}
const npmPackage = npmPackageFactory(upgraderConstants.STRAPI_PACKAGE_NAME);
// Load all versions from the registry
await npmPackage.refresh();
@ -46,11 +50,11 @@ export const upgrade = async (options: UpgradeOptions) => {
.addRequirement(requirements.major.REQUIRE_LATEST_FOR_CURRENT_MAJOR);
}
// Make sure the git repository is in an optimal state before running the upgrade
// Make sure the git repository is in an optional state before running the upgrade
// Mainly used to ease rollbacks in case the upgrade is corrupted
upgrader.addRequirement(requirements.common.REQUIRE_GIT.asOptional());
// Actually run the upgrade process once configured
// Actually run the upgrade process once configured,
// The response contains information about the final status (success/error)
const upgradeReport = await upgrader.upgrade();