@strapi/strapi
Version:
An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite
231 lines (225 loc) • 9.65 kB
JavaScript
;
var os = require('node:os');
var fs = require('node:fs/promises');
var path = require('node:path');
var crypto = require('node:crypto');
var semver = require('semver');
var resolveFrom = require('resolve-from');
var execa = require('execa');
var readPkgUp = require('read-pkg-up');
var managers = require('./managers.js');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var os__default = /*#__PURE__*/_interopDefault(os);
var fs__default = /*#__PURE__*/_interopDefault(fs);
var path__default = /*#__PURE__*/_interopDefault(path);
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
var semver__default = /*#__PURE__*/_interopDefault(semver);
var resolveFrom__default = /*#__PURE__*/_interopDefault(resolveFrom);
var execa__default = /*#__PURE__*/_interopDefault(execa);
var readPkgUp__default = /*#__PURE__*/_interopDefault(readPkgUp);
const CACHE_PATH = path__default.default.join('node_modules', '.strapi', 'deps-check.hash');
const hashPackageJson = async (cwd)=>{
try {
const content = await fs__default.default.readFile(path__default.default.join(cwd, 'package.json'), 'utf8');
return crypto__default.default.createHash('sha1').update(content).digest('hex');
} catch {
return null;
}
};
const readCachedHash = (cwd)=>fs__default.default.readFile(path__default.default.join(cwd, CACHE_PATH), 'utf8').catch(()=>null);
const writeCachedHash = async (cwd, hash)=>{
try {
await fs__default.default.mkdir(path__default.default.dirname(path__default.default.join(cwd, CACHE_PATH)), {
recursive: true
});
await fs__default.default.writeFile(path__default.default.join(cwd, CACHE_PATH), hash, 'utf8');
} catch {
// best-effort cache write — silently ignore
}
};
/**
* From V5 this will be imported from the package.json of `@strapi/strapi`.
*/ const ADMIN_PEER_DEPS = {
react: '^18.0.0',
'react-dom': '^18.0.0',
'react-router-dom': '^6.0.0',
'styled-components': '^6.0.0'
};
class MissingAdminPeerDepsError extends Error {
constructor(missing){
super('Missing required dependencies. Please install them and re-run this command.');
this.name = 'MissingAdminPeerDepsError';
this.missing = missing;
}
}
const loadProjectPackageJson = async (cwd)=>{
const pkg = await readPkgUp__default.default({
cwd
});
if (!pkg) {
throw new Error(`Could not find package.json at path: ${cwd}`);
}
if (!pkg.packageJson.dependencies) {
throw new Error(`Could not find dependencies in package.json at path: ${cwd}`);
}
return pkg;
};
/**
* Returns admin peer dependencies that are not declared in the project's package.json.
*/ const findUndeclaredAdminPeerDeps = async (cwd)=>{
const pkg = await loadProjectPackageJson(cwd);
return Object.entries(ADMIN_PEER_DEPS).reduce((missing, [name, version])=>{
const declaredVersion = pkg.packageJson.dependencies?.[name] ?? pkg.packageJson.devDependencies?.[name];
if (!declaredVersion) {
missing.push({
name,
wantedVersion: version
});
}
return missing;
}, []);
};
const getDeclaredAdminPeerDeps = (packageJson)=>{
return Object.entries(ADMIN_PEER_DEPS).reduce((declared, [name, version])=>{
const declaredVersion = packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];
if (declaredVersion) {
declared.push({
name,
wantedVersion: version,
declaredVersion
});
}
return declared;
}, []);
};
const getInstallCommandHint = (missing)=>{
const packages = missing.map(({ name, wantedVersion })=>`${name}@${wantedVersion}`).join(' ');
const packageManager = managers.getPackageManager();
if (packageManager === 'yarn') {
return `yarn add ${packages}`;
}
if (packageManager === 'pnpm') {
return `pnpm add --save-prod ${packages}`;
}
return `npm install --legacy-peer-deps --save ${packages}`;
};
const reportMissingAdminPeerDeps = (logger, missing)=>{
logger.error('The Strapi admin is missing required dependencies:', os__default.default.EOL, missing.map(({ name, wantedVersion })=>` - ${name}@${wantedVersion}`).join(os__default.default.EOL));
logger.error('Please install them manually before re-running this command:', os__default.default.EOL, ` ${getInstallCommandHint(missing)}`);
};
/**
* Validates declared admin peer dependencies (versions and node_modules presence).
*/ const validateDeclaredAdminPeerDeps = async (cwd, logger)=>{
const pkg = await loadProjectPackageJson(cwd);
const declared = getDeclaredAdminPeerDeps(pkg.packageJson);
if (!declared.length) {
return;
}
logger.debug('Loaded package.json:', os__default.default.EOL, pkg.packageJson);
const errors = [];
for (const dep of declared){
let minDeclaredVersion = null;
try {
minDeclaredVersion = semver__default.default.minVersion(dep.declaredVersion);
} catch (err) {
// Intentional fall-through (variable will be left as null, throwing below)
}
if (!minDeclaredVersion) {
errors.push(`The declared dependency, ${dep.name} has an invalid version in package.json: ${dep.declaredVersion}`);
} else if (!semver__default.default.satisfies(minDeclaredVersion, dep.wantedVersion)) {
logger.warn([
`Declared version of ${dep.name} (${minDeclaredVersion}) is not compatible with the version required by Strapi (${dep.wantedVersion}).`,
'You may experience issues, we recommend you change this.'
].join(os__default.default.EOL));
}
const installedVersion = await getModuleVersion(dep.name, cwd);
if (!installedVersion) {
errors.push(`The declared dependency, ${dep.name} is not installed. You should install before re-running this command`);
} else if (!semver__default.default.satisfies(installedVersion, dep.wantedVersion)) {
logger.warn([
`Declared version of ${dep.name} (${installedVersion}) is not compatible with the version required by Strapi (${dep.wantedVersion}).`,
'You may experience issues, we recommend you change this.'
].join(os__default.default.EOL));
}
}
if (errors.length > 0 && process.env.NODE_ENV === 'development') {
throw new Error(`${os__default.default.EOL}- ${errors.join(`${os__default.default.EOL}- `)}`);
}
};
const installAdminPeerDeps = async (missing, { cwd, logger })=>{
const packageManager = managers.getPackageManager();
if (!packageManager) {
logger.error('Could not find a supported package manager, please install the dependencies manually.');
process.exit(1);
}
const execOptions = {
encoding: 'utf8',
cwd,
stdio: 'inherit'
};
const packages = missing.map(({ name, wantedVersion })=>`${name}@${wantedVersion}`);
let result;
if (packageManager === 'npm') {
const npmArgs = [
'install',
'--legacy-peer-deps',
'--save',
...packages
];
logger.info(`Running 'npm ${npmArgs.join(' ')}'`);
result = await execa__default.default('npm', npmArgs, execOptions);
} else if (packageManager === 'yarn') {
const yarnArgs = [
'add',
...packages
];
logger.info(`Running 'yarn ${yarnArgs.join(' ')}'`);
result = await execa__default.default('yarn', yarnArgs, execOptions);
} else if (packageManager === 'pnpm') {
const pnpmArgs = [
'add',
'--save-prod',
...packages
];
logger.info(`Running 'pnpm ${pnpmArgs.join(' ')}'`);
result = await execa__default.default('pnpm', pnpmArgs, execOptions);
}
if (result?.exitCode || result?.failed) {
throw new Error('Package installation failed');
}
};
const reexecCurrentCommand = async (cwd)=>{
const [file, ...args] = process.argv;
/**
* Re-run the same command after installation e.g. strapi build because the yarn.lock might
* not be the same and could break installations. It's not the best solution, but it works.
*/ await execa__default.default(file, args, {
cwd,
stdio: 'inherit'
});
};
const getModule = async (name, cwd)=>{
const modulePackagePath = resolveFrom__default.default.silent(cwd, path__default.default.join(name, 'package.json'));
if (!modulePackagePath) {
return null;
}
const file = await fs__default.default.readFile(modulePackagePath, 'utf8').then((res)=>JSON.parse(res));
return file;
};
const getModuleVersion = async (name, cwd)=>{
const pkg = await getModule(name, cwd);
return pkg?.version || null;
};
exports.ADMIN_PEER_DEPS = ADMIN_PEER_DEPS;
exports.MissingAdminPeerDepsError = MissingAdminPeerDepsError;
exports.findUndeclaredAdminPeerDeps = findUndeclaredAdminPeerDeps;
exports.getInstallCommandHint = getInstallCommandHint;
exports.getModule = getModule;
exports.hashPackageJson = hashPackageJson;
exports.installAdminPeerDeps = installAdminPeerDeps;
exports.readCachedHash = readCachedHash;
exports.reexecCurrentCommand = reexecCurrentCommand;
exports.reportMissingAdminPeerDeps = reportMissingAdminPeerDeps;
exports.validateDeclaredAdminPeerDeps = validateDeclaredAdminPeerDeps;
exports.writeCachedHash = writeCachedHash;
//# sourceMappingURL=dependencies.js.map