security: package upgrades to remove deprecated versions of boolean, tar, and glob (#25776)

* chore: upgrade tar, global-agent, dedupe yarn.lock

This removes the deprecated packages: boolean, tar<7, node-gyp<12, and others

* chore: upgrade inquirer, file-type, jscodeshift, supertest

* chore: update better-sqlite3

* chore: upgrade pg and mysql2

* fix: use dynamic imports for inquirer esm only

* test(cli): add create-strapi-app test

* test(cli): add scaffold cases

* chore: upgrade to rimraf 6.1.3

* chore: dedupe yarn.lock

* security: update jwk-to-pem

* chore: dedupe yarn.lock

* chore: align inquirer version

* chore: yarn.lock

* test: fix inquirer imports

* test(cli): support zero apps

* test: fix back-end

* test(cli): fix inquirer and fs

* chore: remove accidental file

* chore: dedupe and upgrade pinned deps
This commit is contained in:
Ben Irvin
2026-03-19 13:44:02 +01:00
committed by GitHub
parent b487698e10
commit 353a86a60d
46 changed files with 2535 additions and 6426 deletions
+420 -2297
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -28,7 +28,7 @@
"dependencies": {
"@strapi/plugin-users-permissions": "workspace:*",
"@strapi/strapi": "workspace:*",
"mysql2": "3.9.8",
"mysql2": "3.20.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.30.3",
+2 -2
View File
@@ -77,8 +77,8 @@ const packageJson = {
'@strapi/plugin-users-permissions': '4.26.0',
'@strapi/strapi': '4.26.0',
entities: '2.2.0',
mysql2: '^3.6.0',
pg: '^8.11.0',
mysql2: '3.20.0',
pg: '8.20.0',
react: '^18.0.0',
'react-dom': '^18.0.0',
'react-is': '^18.0.0',
+1 -1
View File
@@ -17,7 +17,7 @@
"dependencies": {
"@strapi/plugin-users-permissions": "workspace:*",
"@strapi/strapi": "workspace:*",
"better-sqlite3": "12.6.2",
"better-sqlite3": "12.8.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.30.3",
+1 -1
View File
@@ -16,7 +16,7 @@
"dependencies": {
"@strapi/plugin-users-permissions": "workspace:*",
"@strapi/strapi": "workspace:*",
"better-sqlite3": "12.6.2",
"better-sqlite3": "12.8.0",
"react": "rc",
"react-dom": "rc",
"react-router-dom": "6.30.3",
+3 -3
View File
@@ -24,11 +24,11 @@
"@strapi/provider-upload-aws-s3": "workspace:*",
"@strapi/provider-upload-cloudinary": "workspace:*",
"@strapi/strapi": "workspace:*",
"better-sqlite3": "12.6.2",
"better-sqlite3": "12.8.0",
"lodash": "4.17.23",
"mysql2": "3.9.8",
"mysql2": "3.20.0",
"passport-google-oauth2": "0.2.0",
"pg": "8.11.1",
"pg": "8.20.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-intl": "6.6.2",
+1 -1
View File
@@ -16,7 +16,7 @@
"dependencies": {
"@strapi/plugin-users-permissions": "workspace:*",
"@strapi/strapi": "workspace:*",
"better-sqlite3": "12.6.2",
"better-sqlite3": "12.8.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.30.3",
+3 -3
View File
@@ -17,11 +17,11 @@
"@strapi/provider-upload-aws-s3": "workspace:*",
"@strapi/provider-upload-cloudinary": "workspace:*",
"@strapi/strapi": "workspace:*",
"better-sqlite3": "12.6.2",
"better-sqlite3": "12.8.0",
"lodash": "4.17.23",
"mysql2": "3.9.8",
"mysql2": "3.20.0",
"passport-google-oauth2": "0.2.0",
"pg": "8.11.1",
"pg": "8.20.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.30.3",
+1 -1
View File
@@ -36,7 +36,7 @@
"styled-components": "^6.0.0"
},
"devDependencies": {
"@strapi/sdk-plugin": "^5.2.0",
"@strapi/sdk-plugin": "^6.0.1",
"@strapi/strapi": "workspace:*"
},
"engines": {
+6 -6
View File
@@ -152,9 +152,9 @@
"find-up": "5.0.0",
"fs-extra": "11.2.0",
"get-port": "5.1.1",
"glob": "13.0.0",
"glob": "13.0.6",
"husky": "9.1.7",
"inquirer": "8.2.5",
"inquirer": "9.3.8",
"jest": "29.6.0",
"jest-circus": "29.6.0",
"jest-cli": "29.6.0",
@@ -165,19 +165,19 @@
"minimatch": "10.2.4",
"npm-run-all": "4.1.5",
"nx": "20.4.6",
"plop": "4.0.1",
"plop": "4.0.5",
"prettier": "3.3.3",
"prettier-2": "npm:prettier@^2",
"qs": "6.15.0",
"rimraf": "5.0.5",
"rimraf": "6.1.3",
"rollup": "4.59.0",
"rollup-plugin-html": "0.2.1",
"semver": "7.5.4",
"stream-chain": "2.2.5",
"stream-json": "1.8.0",
"supertest": "6.3.3",
"supertest": "7.2.2",
"syncpack": "13.0.4",
"tar": "7.5.10",
"tar": "7.5.11",
"ts-jest": "29.1.0",
"typescript": "5.4.4",
"vitest": "catalog:",
+2 -2
View File
@@ -57,7 +57,7 @@
"eventsource": "2.0.2",
"fast-safe-stringify": "2.1.1",
"fs-extra": "11.2.0",
"inquirer": "8.2.5",
"inquirer": "9.3.8",
"jsonwebtoken": "9.0.0",
"jwks-rsa": "3.1.0",
"lodash": "4.17.23",
@@ -65,7 +65,7 @@
"open": "8.4.0",
"ora": "5.4.1",
"pkg-up": "3.1.0",
"tar": "7.5.10",
"tar": "7.5.11",
"xdg-app-paths": "8.3.0",
"yup": "0.32.9"
},
@@ -1,4 +1,3 @@
import inquirer from 'inquirer';
import { AxiosError } from 'axios';
import { defaults } from 'lodash/fp';
import {
@@ -134,6 +133,7 @@ export default async (ctx: CLIContext) => {
};
const projectAnswersDefaulted = defaults(defaultValues);
const { default: inquirer } = await import('inquirer');
const projectAnswers = await inquirer.prompt<ProjectAnswers>(questions);
const projectInput: ProjectInput = projectAnswersDefaulted(projectAnswers);
@@ -1,4 +1,4 @@
import { DistinctQuestion } from 'inquirer';
import type { DistinctQuestion } from 'inquirer';
import type { ProjectAnswers } from '../../types';
/**
@@ -1,5 +1,4 @@
import fse from 'fs-extra';
import inquirer from 'inquirer';
import boxen from 'boxen';
import path from 'path';
import chalk from 'chalk';
@@ -51,6 +50,7 @@ const boxenOptions: boxen.Options = {
const QUIT_OPTION = 'Quit';
async function promptForEnvironment(environments: string[]): Promise<string> {
const { default: inquirer } = await import('inquirer');
const choices = environments.map((env) => ({ name: env, value: env }));
const { selectedEnvironment } = await inquirer.prompt([
{
@@ -348,6 +348,7 @@ export default async (ctx: CLIContext, opts: CmdOptions) => {
);
if (shouldDisplayWarning) {
ctx.logger.log(boxen(cliConfig.projectDeployment.confirmationText, boxenOptions));
const { default: inquirer } = await import('inquirer');
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
@@ -1,5 +1,5 @@
import chalk from 'chalk';
import inquirer, { type Answers } from 'inquirer';
import type { Answers } from 'inquirer';
import { EnvironmentDetails, ProjectInput } from '../../services/cli-api';
import type { CLIContext, CloudApiService } from '../../types';
import { cloudApiFactory, tokenServiceFactory, local } from '../../services';
@@ -91,6 +91,7 @@ async function promptUserForEnvironment(
const { logger } = ctx;
try {
const { default: inquirer } = await import('inquirer');
const answer: LinkEnvironmentInput = await inquirer.prompt([
{
type: 'list',
+3 -1
View File
@@ -1,4 +1,3 @@
import inquirer from 'inquirer';
import chalk from 'chalk';
import type { Answers } from 'inquirer';
@@ -46,6 +45,7 @@ async function promptForRelink(
existingConfig: LocalSave | null
) {
if (existingConfig && existingConfig.project) {
const { default: inquirer } = await import('inquirer');
const { shouldRelink } = await inquirer.prompt([
{
type: 'confirm',
@@ -116,6 +116,7 @@ async function getUserSelection(
): Promise<LinkProjectAnswer | null> {
const { logger } = ctx;
try {
const { default: inquirer } = await import('inquirer');
const answer: LinkProjectInput = await inquirer.prompt([
{
type: 'list',
@@ -173,6 +174,7 @@ export default async (ctx: CLIContext) => {
}
try {
const { default: inquirer } = await import('inquirer');
const { confirmAction } = await inquirer.prompt([
{
type: 'confirm',
+1 -1
View File
@@ -1,6 +1,5 @@
import axios, { AxiosResponse, AxiosError } from 'axios';
import chalk from 'chalk';
import inquirer from 'inquirer';
import { tokenServiceFactory, cloudApiFactory } from '../services';
import type { CloudCliConfig, CLIContext } from '../types';
import { apiConfig } from '../config/api';
@@ -10,6 +9,7 @@ import { setContext } from '../services/context';
const openModule = import('open');
export async function promptLogin(ctx: CLIContext) {
const { default: inquirer } = await import('inquirer');
const response = await inquirer.prompt([
{
type: 'confirm',
+3 -3
View File
@@ -57,19 +57,19 @@
"commander": "8.3.0",
"execa": "5.1.1",
"fs-extra": "11.2.0",
"inquirer": "8.2.5",
"inquirer": "9.3.8",
"lodash": "4.17.23",
"node-machine-id": "^1.1.10",
"ora": "^5.4.1",
"rollup": "4.59.0",
"semver": "7.5.4",
"sort-package-json": "2.10.0",
"tar": "7.5.10"
"tar": "7.5.11"
},
"devDependencies": {
"@types/async-retry": "^1",
"@types/fs-extra": "11.0.4",
"@types/inquirer": "8.2.5",
"@types/inquirer": "9.0.9",
"eslint-config-custom": "5.40.0",
"tsconfig": "5.40.0"
},
+1 -1
View File
@@ -1,4 +1,3 @@
import inquirer from 'inquirer';
import { cli as cloudCli, services as cloudServices } from '@strapi/cloud-cli';
import parseToChalk from './utils/parse-to-chalk';
@@ -40,6 +39,7 @@ export async function handleCloudLogin(): Promise<boolean> {
logger.error(defaultErrorMessage);
return false;
}
const { default: inquirer } = await import('inquirer');
const { userChoice } = await inquirer.prompt<{ userChoice: string }>(
cloudApiConfig.projectCreation?.userChoice || [
{
@@ -1,6 +1,5 @@
import inquirer from 'inquirer';
async function directory() {
const { default: inquirer } = await import('inquirer');
const { directory } = await inquirer.prompt<{
directory: string;
}>([
@@ -16,6 +15,7 @@ async function directory() {
}
async function typescript() {
const { default: inquirer } = await import('inquirer');
const { useTypescript } = await inquirer.prompt<{
useTypescript: boolean;
}>([
@@ -31,6 +31,7 @@ async function typescript() {
}
async function example() {
const { default: inquirer } = await import('inquirer');
const { useExample } = await inquirer.prompt<{
useExample: boolean;
}>([
@@ -46,6 +47,7 @@ async function example() {
}
async function gitInit() {
const { default: inquirer } = await import('inquirer');
const { gitInit } = await inquirer.prompt<{
gitInit: boolean;
}>([
@@ -61,6 +63,7 @@ async function gitInit() {
}
async function installDependencies(packageManager: string) {
const { default: inquirer } = await import('inquirer');
const { installDependencies } = await inquirer.prompt<{
installDependencies: boolean;
}>([
@@ -76,6 +79,7 @@ async function installDependencies(packageManager: string) {
}
async function enableABTests() {
const { default: inquirer } = await import('inquirer');
const { enableABTests } = await inquirer.prompt<{
enableABTests: boolean;
}>([
@@ -1,4 +1,3 @@
import inquirer from 'inquirer';
import type { Question } from 'inquirer';
import type { Scope, Options, DBClient, DBConfig } from '../types';
@@ -16,6 +15,7 @@ const DEFAULT_CONFIG: DBConfig = {
};
async function dbPrompt() {
const { default: inquirer } = await import('inquirer');
const { useDefault } = await inquirer.prompt<{ useDefault: boolean }>([
{
type: 'confirm',
@@ -105,9 +105,9 @@ export async function getDatabaseInfos(options: Options): Promise<DBConfig> {
}
const sqlClientModule = {
mysql: { mysql2: '3.9.8' },
postgres: { pg: '8.8.0' },
sqlite: { 'better-sqlite3': '12.6.2' },
mysql: { mysql2: '3.20.0' },
postgres: { pg: '8.20.0' },
sqlite: { 'better-sqlite3': '12.8.0' },
};
export function addDatabaseDependencies(scope: Scope) {
+2 -2
View File
@@ -112,7 +112,7 @@
"fs-extra": "11.2.0",
"highlight.js": "^10.4.1",
"immer": "9.0.21",
"inquirer": "8.2.5",
"inquirer": "9.3.8",
"invariant": "^2.2.4",
"is-localhost-ip": "2.0.0",
"json-logic-js": "2.0.5",
@@ -138,7 +138,7 @@
"react-redux": "8.1.3",
"react-select": "5.8.0",
"react-window": "1.8.10",
"rimraf": "5.0.5",
"rimraf": "6.1.3",
"sanitize-html": "2.13.0",
"scheduler": "0.23.0",
"semver": "7.5.4",
+5 -6
View File
@@ -75,16 +75,15 @@
"cli-table3": "0.6.5",
"commander": "8.3.0",
"configstore": "5.0.1",
"copyfiles": "2.4.1",
"debug": "4.3.4",
"delegates": "1.0.0",
"dotenv": "16.4.5",
"execa": "5.1.1",
"fs-extra": "11.2.0",
"glob": "13.0.0",
"global-agent": "3.0.0",
"glob": "13.0.6",
"global-agent": "4.1.3",
"http-errors": "2.0.0",
"inquirer": "8.2.5",
"inquirer": "9.3.8",
"is-docker": "2.2.1",
"json-logic-js": "2.0.5",
"jsonwebtoken": "9.0.0",
@@ -120,7 +119,7 @@
"@types/configstore": "5.0.1",
"@types/delegates": "1.0.0",
"@types/fs-extra": "11.0.4",
"@types/global-agent": "2.1.3",
"@types/global-agent": "3.0.0",
"@types/http-errors": "2.0.4",
"@types/jest": "29.5.2",
"@types/json-logic-js": "2.0.8",
@@ -135,7 +134,7 @@
"@types/node-schedule": "2.1.7",
"@types/statuses": "2.0.1",
"eslint-config-custom": "5.40.0",
"supertest": "6.3.3",
"supertest": "7.2.2",
"tsconfig": "5.40.0",
"vitest": "catalog:",
"vitest-config": "5.40.0"
+2 -2
View File
@@ -1,4 +1,4 @@
import * as globalAgent from 'global-agent';
import { bootstrap as bootstrapGlobalAgent } from 'global-agent';
import path from 'path';
import _ from 'lodash';
import { isFunction } from 'lodash/fp';
@@ -515,7 +515,7 @@ class Strapi extends Container implements Core.Strapi {
return;
}
globalAgent.bootstrap();
bootstrapGlobalAgent();
if (httpProxy) {
this.log.info(`Using HTTP proxy: ${httpProxy}`);
+3 -4
View File
@@ -58,14 +58,14 @@
"cli-table3": "0.6.5",
"commander": "8.3.0",
"fs-extra": "11.2.0",
"inquirer": "8.2.5",
"inquirer": "9.3.8",
"lodash": "4.17.23",
"ora": "5.4.1",
"resolve-cwd": "3.0.0",
"semver": "7.5.4",
"stream-chain": "2.2.5",
"stream-json": "1.8.0",
"tar": "7.5.10",
"tar": "7.5.11",
"tar-stream": "2.2.0",
"ws": "8.17.1"
},
@@ -79,12 +79,11 @@
"@types/semver": "7.5.0",
"@types/stream-chain": "2.0.1",
"@types/stream-json": "1.7.3",
"@types/tar": "6.1.4",
"@types/tar-stream": "2.2.2",
"@types/ws": "^8.5.4",
"knex": "3.0.1",
"koa": "2.16.4",
"rimraf": "5.0.5",
"rimraf": "6.1.3",
"typescript": "5.4.4"
},
"engines": {
+1
View File
@@ -1,6 +1,7 @@
node_modules/
.eslintrc.js
jest.config.js
coverage/
dist/
index.d.ts
index.js
+3
View File
@@ -7,4 +7,7 @@ module.exports = {
},
testMatch: ['<rootDir>/**/*.test.ts'],
displayName: 'Strapi',
moduleNameMapper: {
'^inquirer$': '<rootDir>/src/test/mocks/inquirer.ts',
},
};
+4 -4
View File
@@ -140,7 +140,6 @@
"cli-table3": "0.6.5",
"commander": "8.3.0",
"concurrently": "8.2.2",
"copyfiles": "2.4.1",
"css-loader": "^6.10.0",
"dotenv": "16.4.5",
"esbuild-loader": "4.3.0",
@@ -151,7 +150,7 @@
"get-latest-version": "5.1.0",
"git-url-parse": "14.0.0",
"html-webpack-plugin": "5.6.0",
"inquirer": "8.2.5",
"inquirer": "9.3.8",
"lodash": "4.17.23",
"mini-css-extract-plugin": "2.7.7",
"nodemon": "3.0.2",
@@ -170,12 +169,12 @@
"webpack-bundle-analyzer": "^4.10.1",
"webpack-dev-middleware": "6.1.2",
"webpack-hot-middleware": "2.26.1",
"yalc": "1.0.0-pre.53",
"yup": "0.32.9"
},
"devDependencies": {
"@strapi/ts-zen": "^0.2.0",
"@types/fs-extra": "11.0.4",
"@types/inquirer": "9.0.9",
"@types/jest": "29.5.2",
"@types/lodash": "^4.14.191",
"@types/node": "24.10.0",
@@ -185,7 +184,8 @@
"jest": "29.6.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"tsconfig": "5.40.0"
"tsconfig": "5.40.0",
"yalc": "1.0.0-pre.53"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0",
@@ -2,6 +2,7 @@ import { createCommand } from 'commander';
import { yup } from '@strapi/utils';
import _ from 'lodash';
import inquirer from 'inquirer';
import type { QuestionCollection } from 'inquirer';
import { createStrapi, compileStrapi } from '@strapi/core';
import { runAction } from '../../utils/helpers';
@@ -49,10 +50,10 @@ interface Answers {
/**
* It's not an observable, in reality this is
* `ReadOnlyArray<inquirer.DistinctQuestion<Answers>>`
* `ReadOnlyArray<DistinctQuestion<Answers>>`
* but then the logic of the validate function needs to change.
*/
const promptQuestions: inquirer.QuestionCollection<Answers> = [
const promptQuestions: QuestionCollection<Answers> = [
{
type: 'input',
name: 'email',
@@ -1,5 +1,6 @@
import _ from 'lodash';
import inquirer from 'inquirer';
import type { DistinctQuestion } from 'inquirer';
import { createCommand } from 'commander';
import { createStrapi, compileStrapi } from '@strapi/core';
@@ -17,7 +18,7 @@ interface Answers {
confirm: boolean;
}
const promptQuestions: ReadonlyArray<inquirer.DistinctQuestion<Answers>> = [
const promptQuestions: ReadonlyArray<DistinctQuestion<Answers>> = [
{ type: 'input', name: 'email', message: 'User email?' },
{ type: 'password', name: 'password', message: 'New password?' },
{
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import chalk from 'chalk';
import { has, isString, isArray } from 'lodash/fp';
import { prompt } from 'inquirer';
import inquirer from 'inquirer';
import boxen from 'boxen';
import type { Command } from 'commander';
@@ -178,7 +178,7 @@ const notifyExperimentalCommand = async (name: string, { force }: { force?: bool
);
if (!force) {
const { confirmed } = await prompt({
const { confirmed } = await inquirer.prompt({
type: 'confirm',
name: 'confirmed',
message: 'Do you want to continue?',
@@ -0,0 +1,9 @@
/**
* inquirer v9+ is ESM-only; Jest resolves it here via moduleNameMapper in jest.config.js.
* Override per test with jest.spyOn(inquirer, 'prompt') or prompt.mockResolvedValue(...).
*/
export const prompt = jest.fn().mockResolvedValue({});
export default {
prompt,
};
+1 -1
View File
@@ -81,7 +81,7 @@
"byte-size": "8.1.1",
"cropperjs": "1.6.1",
"date-fns": "2.30.0",
"file-type": "21.0.0",
"file-type": "21.3.3",
"formik": "2.4.5",
"fs-extra": "11.2.0",
"immer": "9.0.21",
+3 -3
View File
@@ -53,17 +53,17 @@
"@strapi/typescript-utils": "5.40.0",
"@strapi/utils": "5.40.0",
"chalk": "4.1.2",
"copyfiles": "2.4.1",
"fs-extra": "11.2.0",
"handlebars": "4.7.7",
"jscodeshift": "17.3.0",
"lodash": "4.17.23",
"plop": "4.0.1",
"plop": "4.0.5",
"pluralize": "8.0.0"
},
"devDependencies": {
"@types/fs-extra": "11.0.4",
"@types/jscodeshift": "0.12.0",
"@types/jscodeshift": "17.3.0",
"copyfiles": "2.4.1",
"eslint-config-custom": "5.40.0",
"outdent": "^0.8.0",
"tsconfig": "5.40.0"
@@ -61,7 +61,7 @@
"grant": "^5.4.8",
"immer": "9.0.21",
"jsonwebtoken": "9.0.0",
"jwk-to-pem": "2.0.5",
"jwk-to-pem": "2.0.7",
"koa": "2.16.4",
"koa2-ratelimit": "^1.1.3",
"lodash": "4.17.23",
+1 -1
View File
@@ -6,6 +6,6 @@
"dotenv": "16.4.5",
"lodash": "4.17.23",
"qs": "6.15.0",
"supertest": "6.3.3"
"supertest": "7.2.2"
}
}
+2 -2
View File
@@ -85,9 +85,9 @@
"devDependencies": {
"@strapi/types": "5.40.0",
"@types/fs-extra": "11.0.4",
"@types/jscodeshift": "0.12.0",
"@types/jscodeshift": "17.3.0",
"eslint-config-custom": "5.40.0",
"rimraf": "5.0.5"
"rimraf": "6.1.3"
},
"engines": {
"node": ">=20.0.0 <=24.x.x",
@@ -4,7 +4,7 @@ import type { modules } from '../../../dist';
const DEP_NAME = 'better-sqlite3';
const DEP_PATH = `dependencies.${DEP_NAME}`;
const DEP_VERSION = '11.3.0';
const DEP_VERSION = '12.8.0';
/**
*
@@ -4,7 +4,6 @@
const path = require('path');
const chalk = require('chalk');
const inquirer = require('inquirer');
const { kebabCase } = require('lodash');
const FilesContentSearch = require('../utils/search-files-content');
const { readAllTranslationFiles, writeAllTranslationFiles } = require('../utils/translation-files');
@@ -26,28 +25,6 @@ const mapDuplicateValues = async (pkgs, fn) => {
});
};
const promptShouldMerge = async () => {
return (
await inquirer.prompt({
type: 'confirm',
message: 'Should merge?',
name: 'shouldMerge',
default: false,
})
).shouldMerge;
};
const promptTargetKey = async (valueGroup) => {
return (
await inquirer.prompt({
type: 'input',
name: 'targetKey',
message: 'Target key name:',
default: `global.${kebabCase(valueGroup[0].value)}`,
})
).targetKey;
};
const printToMerge = (valueGroup) => {
console.log(`Value: "${chalk.yellow(valueGroup[0].value)}"`);
@@ -121,7 +98,29 @@ const updateTranslationFiles = (keyGroup, targetKey) => {
// Displays and prompt for every detected duplications
// Triggers the merge if necessary
const merge = async (valuesToMerge) => {
const merge = async (inquirer, valuesToMerge) => {
const promptShouldMerge = async () => {
return (
await inquirer.prompt({
type: 'confirm',
message: 'Should merge?',
name: 'shouldMerge',
default: false,
})
).shouldMerge;
};
const promptTargetKey = async (valueGroup) => {
return (
await inquirer.prompt({
type: 'input',
name: 'targetKey',
message: 'Target key name:',
default: `global.${kebabCase(valueGroup[0].value)}`,
})
).targetKey;
};
let current = 1;
let mergedCount = 0;
@@ -147,11 +146,13 @@ const merge = async (valuesToMerge) => {
};
(async () => {
const { default: inquirer } = await import('inquirer');
await fcs.loadFiles();
const duplicates = findDuplicatedTranslations();
const keyUsage = getKeysUsage(duplicates);
const valuesToMerge = getValuesToMerge(keyUsage);
await merge(valuesToMerge);
await merge(inquirer, valuesToMerge);
})();
+1 -1
View File
@@ -13,7 +13,7 @@
"dependencies": {
"@strapi/plugin-users-permissions": "latest",
"@strapi/strapi": "latest",
"better-sqlite3": "12.6.2",
"better-sqlite3": "12.8.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.30.3",
+2
View File
@@ -43,6 +43,8 @@ After tests for remote data-transfer are implemented, there will be utility func
Each subdirectory within the `./tests` directory here is considered a test "domain" and will have its own test app(s) available. By default only one test app is made available unless additional ones are configured in a config.js within that test domain.
Some domains need no shared test app (e.g. **`create-strapi-app`** uses `testApps: 0` in `config.js`). Those domains can run in parallel with app-backed domains; the runner reserves apps only when `testApps > 0`. Others put tests in **subfolders** under the domain—e.g. `strapi/strapi/`, `strapi/data-transfer/`, `strapi/version/`. Use the `*.test.cli.js` / `*.test.cli.ts` naming from `jest.config.cli.js` at the repo root.
#### tests/{domain}/config.js
This optional file should return a function that returns a configuration object like the following complete example:
@@ -0,0 +1,3 @@
# create-strapi-app domain
CLI tests for the `create-strapi-app` package. This domain uses **`testApps: 0`** (see `config.js`): tests spawn the built `bin/index.js` and write to a temp directory instead of using `TEST_APPS`.
@@ -0,0 +1,8 @@
'use strict';
module.exports = () => {
return {
/** Scaffolding tests use a temp dir; no shared test-app from app-template. */
testApps: 0,
};
};
@@ -0,0 +1,136 @@
'use strict';
const path = require('path');
const fs = require('fs');
const os = require('os');
const coffee = require('coffee');
const semver = require('semver');
const repoRoot = path.resolve(__dirname, '../../../..');
const bin = path.join(repoRoot, 'packages/cli/create-strapi-app/bin/index.js');
const baseScaffoldArgs = ['--non-interactive', '--skip-cloud', '--no-install', '--no-git-init'];
function spawnCsa(args, env = {}) {
return coffee.spawn(process.execPath, [bin, ...args], {
cwd: repoRoot,
env: { ...process.env, ...env },
});
}
function mkProjectDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'strapi-csa-cli-test-'));
}
describe('create-strapi-app', () => {
beforeAll(() => {
if (!fs.existsSync(bin)) {
throw new Error(
`create-strapi-app bin missing at ${bin}; build the package first (yarn workspace create-strapi-app build)`
);
}
});
it('scaffolds a TypeScript project by default', async () => {
const projectDir = mkProjectDir();
try {
const { stdout } = await spawnCsa([projectDir, ...baseScaffoldArgs])
.expect('code', 0)
.end();
expect(stdout).toContain('Your application was created');
expect(fs.existsSync(path.join(projectDir, 'package.json'))).toBe(true);
expect(fs.existsSync(path.join(projectDir, 'tsconfig.json'))).toBe(true);
expect(fs.existsSync(path.join(projectDir, 'config', 'database.ts'))).toBe(true);
} finally {
fs.rmSync(projectDir, { recursive: true, force: true });
}
});
it('scaffolds a JavaScript project with --javascript', async () => {
const projectDir = mkProjectDir();
try {
await spawnCsa([projectDir, ...baseScaffoldArgs, '--javascript'])
.expect('code', 0)
.end();
expect(fs.existsSync(path.join(projectDir, 'config', 'database.js'))).toBe(true);
expect(fs.existsSync(path.join(projectDir, 'src', 'index.js'))).toBe(true);
expect(fs.existsSync(path.join(projectDir, 'tsconfig.json'))).toBe(false);
} finally {
fs.rmSync(projectDir, { recursive: true, force: true });
}
});
it('scaffolds the example template with --example', async () => {
const projectDir = mkProjectDir();
try {
await spawnCsa([projectDir, ...baseScaffoldArgs, '--example'])
.expect('code', 0)
.end();
expect(fs.existsSync(path.join(projectDir, 'data', 'data.json'))).toBe(true);
expect(fs.existsSync(path.join(projectDir, 'package.json'))).toBe(true);
} finally {
fs.rmSync(projectDir, { recursive: true, force: true });
}
});
it('fails when --non-interactive is used without a directory', async () => {
const { stderr, stdout } = await spawnCsa(['--non-interactive', '--skip-cloud'])
.expect('code', 1)
.end();
const out = `${stdout}${stderr}`;
expect(out).toMatch(/non-interactive|directory/i);
});
it('fails when --typescript and --javascript are both set', async () => {
const projectDir = mkProjectDir();
try {
const { stderr, stdout } = await spawnCsa([
projectDir,
...baseScaffoldArgs,
'--typescript',
'--javascript',
])
.expect('code', 1)
.end();
const out = `${stdout}${stderr}`;
expect(out).toMatch(/cannot use both|typescript.*javascript/i);
} finally {
if (fs.existsSync(projectDir)) {
fs.rmSync(projectDir, { recursive: true, force: true });
}
}
});
it('fails when multiple package manager flags are used', async () => {
const projectDir = mkProjectDir();
try {
const { stderr, stdout } = await spawnCsa([
projectDir,
...baseScaffoldArgs,
'--use-npm',
'--use-yarn',
])
.expect('code', 1)
.end();
const out = `${stdout}${stderr}`;
expect(out).toMatch(/package manager|use-npm|use-yarn/i);
} finally {
if (fs.existsSync(projectDir)) {
fs.rmSync(projectDir, { recursive: true, force: true });
}
}
});
it('prints a semver version with --version', async () => {
const { stdout } = await spawnCsa(['--version']).expect('code', 0).end();
const v = stdout.trim();
expect(semver.valid(v)).toBeTruthy();
});
});
+15 -7
View File
@@ -119,14 +119,19 @@ yargs
testAppsRequired = Math.min(selectedDomains.length, concurrency);
}
// CLI domains may use testApps: 0 (e.g. create-strapi-app scaffolds to tmp, not TEST_APPS).
let testAppPaths;
if (testAppsRequired === 0) {
throw new Error('No test apps to spawn');
if (type !== 'cli') {
throw new Error('No test apps to spawn');
}
testAppPaths = [];
} else {
testAppPaths = Array.from({ length: testAppsRequired }, (_, i) =>
path.join(testAppDirectory, `test-app-${i}`)
);
}
const testAppPaths = Array.from({ length: testAppsRequired }, (_, i) =>
path.join(testAppDirectory, `test-app-${i}`)
);
const currentTestApps = await getCurrentTestApps(testAppDirectory);
/**
@@ -407,13 +412,16 @@ module.exports = config
await Promise.all(
batch.map(async (domain) => {
const config = domainConfigs[domain];
// Must not call splice(0): that deletes the entire pool. Use splice only when n > 0.
const neededApps =
typeof config.testApps === 'number' && config.testApps >= 0 ? config.testApps : 1;
if (availableTestApps.length < config.testApps) {
if (availableTestApps.length < neededApps) {
console.error('Not enough test apps available; aborting');
process.exit(1);
}
const testApps = availableTestApps.splice(-1 * config.testApps);
const testApps = neededApps > 0 ? availableTestApps.splice(-neededApps) : [];
try {
const domainDir = path.join(testDomainRoot, domain);
+1834 -4027
View File
File diff suppressed because it is too large Load Diff