mirror of
https://github.com/strapi/strapi.git
synced 2025-11-02 19:04:38 +00:00
Merge branch 'main' into main
This commit is contained in:
commit
38cf9f837d
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16, 18]
|
||||
node: [18]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
@ -21,5 +21,5 @@ Alternatively, you can use [`yarn link`](https://classic.yarnpkg.com/lang/en/doc
|
||||
Once the link is setup, run the following command from the root of the monorepo
|
||||
|
||||
```
|
||||
yarn lerna clean && yarn setup
|
||||
yarn clean && yarn setup
|
||||
```
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
---
|
||||
title: How to install packages
|
||||
slug: /how-to-install-packages
|
||||
tags:
|
||||
- lerna
|
||||
- packages
|
||||
---
|
||||
|
||||
# Best practices for installing packages in Strapi
|
||||
|
||||
When working with the Strapi monorepo, it's important to follow best practices for installing packages to avoid potential issues and ensure consistent results. Instead of using the standard **`yarn add`** command, we recommend using **`yarn lerna add <package_name> --scope @strapi/<module_name>`** for installing packages. Actually, you may encounter the following error using `yarn add`:
|
||||
|
||||
`An unexpected error occurred: "expected workspace package to exist for \"@typescript-eslint/typescript-estree\'`
|
||||
|
||||
This approach uses Lerna, a tool for managing JavaScript projects with multiple packages, to ensure that the package is installed in the correct location(s) and version across all modules that include it. The **`--scope`** flag specifies the specific module(s) that the package should be installed in, ensuring that it's only installed where it's needed.
|
||||
|
||||
By using this method, Strapi developers can avoid issues with mismatched package versions or unnecessary dependencies in certain modules. This can help to keep the codebase clean and maintainable, and reduce the potential for conflicts or issues in the future.
|
||||
|
||||
Overall, we recommend using **`yarn lerna add`** with the **`--scope`** flag for installing packages in the Strapi mono repo, to ensure consistent and reliable results.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Lerna Docs](https://futurestud.io/tutorials/lerna-install-dependencies-for-a-specific-package)
|
||||
@ -188,15 +188,6 @@ const sidebars = {
|
||||
},
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'How to install packages in a module',
|
||||
link: {
|
||||
type: 'doc',
|
||||
id: 'how-to-install-packages',
|
||||
},
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
api: [{ type: 'autogenerated', dirName: 'api' }],
|
||||
community: [{ type: 'autogenerated', dirName: 'community' }],
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
node_modules/
|
||||
.eslintrc.js
|
||||
dist/
|
||||
bin/
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['custom/back'],
|
||||
extends: ['custom/typescript'],
|
||||
};
|
||||
|
||||
5
packages/cli/create-strapi-app/bin/index.js
Executable file
5
packages/cli/create-strapi-app/bin/index.js
Executable file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
require('../dist/create-strapi-app');
|
||||
@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
require('./create-strapi-app');
|
||||
@ -35,11 +35,23 @@
|
||||
"url": "https://strapi.io"
|
||||
}
|
||||
],
|
||||
"main": "./index.js",
|
||||
"bin": "./index.js",
|
||||
"bin": "./bin/index.js",
|
||||
"files": [
|
||||
"./dist",
|
||||
"./bin"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "run -T tsc",
|
||||
"build:ts": "run -T tsc",
|
||||
"watch": "run -T tsc -w --preserveWatchOutput",
|
||||
"clean": "run -T rimraf ./dist",
|
||||
"prepublishOnly": "yarn clean && yarn build",
|
||||
"lint": "run -T eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-config-custom": "*",
|
||||
"tsconfig": "*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
'use strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import commander from 'commander';
|
||||
import { checkInstallPath, generateNewApp } from '@strapi/generate-new';
|
||||
import promptUser from './utils/prompt-user';
|
||||
import type { Program } from './types';
|
||||
|
||||
const { resolve } = require('path');
|
||||
const commander = require('commander');
|
||||
const { checkInstallPath, generateNewApp } = require('@strapi/generate-new');
|
||||
const promptUser = require('./utils/prompt-user');
|
||||
// eslint-disable-next-line import/extensions
|
||||
const packageJson = require('./package.json');
|
||||
const packageJson = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf8'));
|
||||
|
||||
const program = new commander.Command();
|
||||
const command = new commander.Command(packageJson.name);
|
||||
|
||||
const databaseOptions = [
|
||||
const databaseOptions: Array<keyof Program> = [
|
||||
'dbclient',
|
||||
'dbhost',
|
||||
'dbport',
|
||||
@ -20,8 +20,7 @@ const databaseOptions = [
|
||||
'dbfile',
|
||||
];
|
||||
|
||||
program
|
||||
.command(packageJson.name)
|
||||
command
|
||||
.version(packageJson.version)
|
||||
.arguments('[directory]')
|
||||
.option('--no-run', 'Do not start the application after it is created')
|
||||
@ -40,12 +39,12 @@ program
|
||||
.option('--template <templateurl>', 'Specify a Strapi template')
|
||||
.option('--ts, --typescript', 'Use TypeScript to generate the project')
|
||||
.description('create a new application')
|
||||
.action((directory, options) => {
|
||||
initProject(directory, program, options);
|
||||
.action((directory, programArgs) => {
|
||||
initProject(directory, programArgs);
|
||||
})
|
||||
.parse(process.argv);
|
||||
|
||||
function generateApp(projectName, options) {
|
||||
function generateApp(projectName: string, options: unknown) {
|
||||
if (!projectName) {
|
||||
console.error('Please specify the <directory> of your project when using --quickstart');
|
||||
process.exit(1);
|
||||
@ -58,22 +57,25 @@ function generateApp(projectName, options) {
|
||||
});
|
||||
}
|
||||
|
||||
async function initProject(projectName, program, inputOptions) {
|
||||
async function initProject(projectName: string, programArgs: Program) {
|
||||
if (projectName) {
|
||||
await checkInstallPath(resolve(projectName));
|
||||
}
|
||||
|
||||
const programFlags = program.options
|
||||
.reduce((acc, { short, long }) => [...acc, short, long], [])
|
||||
const programFlags = command
|
||||
.createHelp()
|
||||
.visibleOptions(command)
|
||||
.reduce<Array<string | undefined>>((acc, { short, long }) => [...acc, short, long], [])
|
||||
.filter(Boolean);
|
||||
if (inputOptions.template && programFlags.includes(inputOptions.template)) {
|
||||
console.error(`${inputOptions.template} is not a valid template`);
|
||||
|
||||
if (programArgs.template && programFlags.includes(programArgs.template)) {
|
||||
console.error(`${programArgs.template} is not a valid template`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const hasDatabaseOptions = databaseOptions.some((opt) => inputOptions[opt]);
|
||||
const hasDatabaseOptions = databaseOptions.some((opt) => programArgs[opt]);
|
||||
|
||||
if (inputOptions.quickstart && hasDatabaseOptions) {
|
||||
if (programArgs.quickstart && hasDatabaseOptions) {
|
||||
console.error(
|
||||
`The quickstart option is incompatible with the following options: ${databaseOptions.join(
|
||||
', '
|
||||
@ -83,24 +85,24 @@ async function initProject(projectName, program, inputOptions) {
|
||||
}
|
||||
|
||||
if (hasDatabaseOptions) {
|
||||
inputOptions.quickstart = false; // Will disable the quickstart question because != 'undefined'
|
||||
programArgs.quickstart = false; // Will disable the quickstart question because != 'undefined'
|
||||
}
|
||||
|
||||
if (inputOptions.quickstart) {
|
||||
return generateApp(projectName, inputOptions);
|
||||
if (programArgs.quickstart) {
|
||||
return generateApp(projectName, programArgs);
|
||||
}
|
||||
|
||||
const prompt = await promptUser(projectName, inputOptions, hasDatabaseOptions);
|
||||
const prompt = await promptUser(projectName, programArgs, hasDatabaseOptions);
|
||||
const directory = prompt.directory || projectName;
|
||||
await checkInstallPath(resolve(directory));
|
||||
|
||||
const options = {
|
||||
template: inputOptions.template,
|
||||
quickstart: prompt.quick || inputOptions.quickstart,
|
||||
template: programArgs.template,
|
||||
quickstart: prompt.quick || programArgs.quickstart,
|
||||
};
|
||||
|
||||
const generateStrapiAppOptions = {
|
||||
...inputOptions,
|
||||
...programArgs,
|
||||
...options,
|
||||
};
|
||||
|
||||
1
packages/cli/create-strapi-app/src/index.d.ts
vendored
Normal file
1
packages/cli/create-strapi-app/src/index.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module '@strapi/generate-new';
|
||||
17
packages/cli/create-strapi-app/src/types.ts
Normal file
17
packages/cli/create-strapi-app/src/types.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export interface Program {
|
||||
noRun?: boolean;
|
||||
useNpm?: boolean;
|
||||
debug?: boolean;
|
||||
quickstart?: boolean;
|
||||
dbclient?: string;
|
||||
dbhost?: string;
|
||||
dbport?: string;
|
||||
dbname?: string;
|
||||
dbusername?: string;
|
||||
dbpassword?: string;
|
||||
dbssl?: string;
|
||||
dbfile?: string;
|
||||
dbforce?: boolean;
|
||||
template?: string;
|
||||
typescript?: boolean;
|
||||
}
|
||||
39
packages/cli/create-strapi-app/src/utils/prompt-user.ts
Normal file
39
packages/cli/create-strapi-app/src/utils/prompt-user.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import inquirer from 'inquirer';
|
||||
import type { Program } from '../types';
|
||||
|
||||
interface Answers {
|
||||
directory: string;
|
||||
quick: boolean;
|
||||
}
|
||||
|
||||
export default async function promptUser(
|
||||
projectName: string,
|
||||
program: Program,
|
||||
hasDatabaseOptions: boolean
|
||||
) {
|
||||
return inquirer.prompt<Answers>([
|
||||
{
|
||||
type: 'input',
|
||||
default: 'my-strapi-project',
|
||||
name: 'directory',
|
||||
message: 'What would you like to name your project?',
|
||||
when: !projectName,
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'quick',
|
||||
message: 'Choose your installation type',
|
||||
when: !program.quickstart && !hasDatabaseOptions,
|
||||
choices: [
|
||||
{
|
||||
name: 'Quickstart (recommended)',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'Custom (manual settings)',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
}
|
||||
8
packages/cli/create-strapi-app/tsconfig.eslint.json
Normal file
8
packages/cli/create-strapi-app/tsconfig.eslint.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
8
packages/cli/create-strapi-app/tsconfig.json
Normal file
8
packages/cli/create-strapi-app/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "**/__tests__/**"]
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const inquirer = require('inquirer');
|
||||
|
||||
/**
|
||||
* @param {string|null} projectName - The name/path of project
|
||||
* @param {string|null} template - The Github repo of the template
|
||||
* @returns Object containting prompt answers
|
||||
*/
|
||||
module.exports = async function promptUser(projectName, program, hasDatabaseOptions) {
|
||||
const questions = await getPromptQuestions(projectName, program, hasDatabaseOptions);
|
||||
return inquirer.prompt(questions);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string|null} projectName - The name of the project
|
||||
* @param {string|null} template - The template the project should use
|
||||
* @returns Array of prompt question objects
|
||||
*/
|
||||
async function getPromptQuestions(projectName, program, hasDatabaseOptions) {
|
||||
return [
|
||||
{
|
||||
type: 'input',
|
||||
default: 'my-strapi-project',
|
||||
name: 'directory',
|
||||
message: 'What would you like to name your project?',
|
||||
when: !projectName,
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'quick',
|
||||
message: 'Choose your installation type',
|
||||
when: !program.quickstart && !hasDatabaseOptions,
|
||||
choices: [
|
||||
{
|
||||
name: 'Quickstart (recommended)',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'Custom (manual settings)',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -1,2 +1,4 @@
|
||||
node_modules/
|
||||
.eslintrc.js
|
||||
dist/
|
||||
bin/
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['custom/back'],
|
||||
extends: ['custom/typescript'],
|
||||
};
|
||||
|
||||
5
packages/cli/create-strapi-starter/bin/index.js
Executable file
5
packages/cli/create-strapi-starter/bin/index.js
Executable file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
require('../dist/create-strapi-starter');
|
||||
@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
require('./create-strapi-starter');
|
||||
@ -30,9 +30,17 @@
|
||||
"url": "https://strapi.io"
|
||||
}
|
||||
],
|
||||
"main": "./index.js",
|
||||
"bin": "./index.js",
|
||||
"bin": "./bin/index.js",
|
||||
"files": [
|
||||
"./dist",
|
||||
"./bin"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "run -T tsc",
|
||||
"build:ts": "run -T tsc",
|
||||
"watch": "run -T tsc -w --preserveWatchOutput",
|
||||
"clean": "run -T rimraf ./dist",
|
||||
"prepublishOnly": "yarn clean && yarn build",
|
||||
"lint": "run -T eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
@ -45,6 +53,10 @@
|
||||
"inquirer": "8.2.5",
|
||||
"ora": "5.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-config-custom": "*",
|
||||
"tsconfig": "*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
|
||||
@ -1,15 +1,22 @@
|
||||
'use strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const commander = require('commander');
|
||||
import commander, { CommanderError } from 'commander';
|
||||
|
||||
// eslint-disable-next-line import/extensions
|
||||
const packageJson = require('./package.json');
|
||||
const buildStarter = require('./utils/build-starter');
|
||||
const promptUser = require('./utils/prompt-user');
|
||||
import buildStarter from './utils/build-starter';
|
||||
import promptUser from './utils/prompt-user';
|
||||
import type { Program } from './types';
|
||||
|
||||
interface ProjectArgs {
|
||||
projectName: string;
|
||||
starter: string;
|
||||
}
|
||||
|
||||
const packageJson = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf8'));
|
||||
|
||||
const program = new commander.Command(packageJson.name);
|
||||
|
||||
const incompatibleQuickstartOptions = [
|
||||
const incompatibleQuickstartOptions: Array<keyof Program> = [
|
||||
'dbclient',
|
||||
'dbhost',
|
||||
'dbport',
|
||||
@ -39,12 +46,12 @@ program
|
||||
'Create a fullstack monorepo application using the strapi backend template specified in the provided starter'
|
||||
)
|
||||
.action((directory, starter, programArgs) => {
|
||||
const projectArgs = { projectName: directory, starter };
|
||||
const projectArgs: ProjectArgs = { projectName: directory, starter };
|
||||
|
||||
initProject(projectArgs, programArgs);
|
||||
});
|
||||
|
||||
function generateApp(projectArgs, programArgs) {
|
||||
function generateApp(projectArgs: ProjectArgs, programArgs: Program) {
|
||||
if (!projectArgs.projectName || !projectArgs.starter) {
|
||||
console.error(
|
||||
'Please specify the <directory> and <starter> of your project when using --quickstart'
|
||||
@ -56,12 +63,12 @@ function generateApp(projectArgs, programArgs) {
|
||||
return buildStarter(projectArgs, programArgs);
|
||||
}
|
||||
|
||||
async function initProject(projectArgs, program) {
|
||||
async function initProject(projectArgs: ProjectArgs, programArgs: Program) {
|
||||
const hasIncompatibleQuickstartOptions = incompatibleQuickstartOptions.some(
|
||||
(opt) => program[opt]
|
||||
(opt) => programArgs[opt]
|
||||
);
|
||||
|
||||
if (program.quickstart && hasIncompatibleQuickstartOptions) {
|
||||
if (programArgs.quickstart && hasIncompatibleQuickstartOptions) {
|
||||
console.error(
|
||||
`The quickstart option is incompatible with the following options: ${incompatibleQuickstartOptions.join(
|
||||
', '
|
||||
@ -71,34 +78,34 @@ async function initProject(projectArgs, program) {
|
||||
}
|
||||
|
||||
if (hasIncompatibleQuickstartOptions) {
|
||||
program.quickstart = false; // Will disable the quickstart question because != 'undefined'
|
||||
programArgs.quickstart = false; // Will disable the quickstart question because != 'undefined'
|
||||
}
|
||||
|
||||
const { projectName, starter } = projectArgs;
|
||||
|
||||
if (program.quickstart) {
|
||||
return generateApp(projectArgs, program);
|
||||
if (programArgs.quickstart) {
|
||||
return generateApp(projectArgs, programArgs);
|
||||
}
|
||||
|
||||
const prompt = await promptUser(projectName, starter, program);
|
||||
const prompt = await promptUser(projectName, starter, programArgs);
|
||||
|
||||
const promptProjectArgs = {
|
||||
projectName: prompt.directory || projectName,
|
||||
starter: prompt.starter || starter,
|
||||
};
|
||||
|
||||
const programArgs = {
|
||||
...program,
|
||||
quickstart: prompt.quick || program.quickstart,
|
||||
};
|
||||
|
||||
return generateApp(promptProjectArgs, programArgs);
|
||||
return generateApp(promptProjectArgs, {
|
||||
...programArgs,
|
||||
quickstart: prompt.quick || programArgs.quickstart,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
program.parse(process.argv);
|
||||
} catch (err) {
|
||||
if (err.exitCode && err.exitCode !== 0) {
|
||||
program.outputHelp();
|
||||
if (err instanceof CommanderError) {
|
||||
if (err.exitCode && err.exitCode !== 0) {
|
||||
program.outputHelp();
|
||||
}
|
||||
}
|
||||
}
|
||||
1
packages/cli/create-strapi-starter/src/index.d.ts
vendored
Normal file
1
packages/cli/create-strapi-starter/src/index.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module '@strapi/generate-new';
|
||||
24
packages/cli/create-strapi-starter/src/types.ts
Normal file
24
packages/cli/create-strapi-starter/src/types.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export interface Options {
|
||||
useYarn?: boolean;
|
||||
}
|
||||
|
||||
export interface PackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface Program {
|
||||
useNpm?: boolean;
|
||||
debug?: boolean;
|
||||
quickstart?: boolean;
|
||||
dbclient?: string;
|
||||
dbhost?: string;
|
||||
dbport?: string;
|
||||
dbname?: string;
|
||||
dbusername?: string;
|
||||
dbpassword?: string;
|
||||
dbssl?: string;
|
||||
dbfile?: string;
|
||||
dbforce?: boolean;
|
||||
template?: string;
|
||||
}
|
||||
@ -1,39 +1,29 @@
|
||||
'use strict';
|
||||
import { resolve, join, basename } from 'path';
|
||||
import os from 'os';
|
||||
import fse from 'fs-extra';
|
||||
import ora from 'ora';
|
||||
import ciEnv from 'ci-info';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const { resolve, join, basename } = require('path');
|
||||
const os = require('os');
|
||||
const fse = require('fs-extra');
|
||||
const ora = require('ora');
|
||||
const ciEnv = require('ci-info');
|
||||
const chalk = require('chalk');
|
||||
import { generateNewApp } from '@strapi/generate-new';
|
||||
|
||||
const { generateNewApp } = require('@strapi/generate-new');
|
||||
import hasYarn from './has-yarn';
|
||||
import { runInstall, runApp, initGit } from './child-process';
|
||||
import { getStarterPackageInfo, downloadNpmStarter } from './fetch-npm-starter';
|
||||
import logger from './logger';
|
||||
import stopProcess from './stop-process';
|
||||
import type { Options, PackageInfo, Program } from '../types';
|
||||
|
||||
const hasYarn = require('./has-yarn');
|
||||
const { runInstall, runApp, initGit } = require('./child-process');
|
||||
const { getStarterPackageInfo, downloadNpmStarter } = require('./fetch-npm-starter');
|
||||
const logger = require('./logger');
|
||||
const stopProcess = require('./stop-process');
|
||||
|
||||
/**
|
||||
* @param {string} - filePath Path to starter.json file
|
||||
*/
|
||||
function readStarterJson(filePath, starter) {
|
||||
function readStarterJson(filePath: string, starter: string) {
|
||||
try {
|
||||
const data = fse.readFileSync(filePath);
|
||||
return JSON.parse(data);
|
||||
return JSON.parse(data.toString());
|
||||
} catch (err) {
|
||||
stopProcess(`Could not find ${chalk.yellow('starter.json')} in ${chalk.yellow(starter)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} rootPath - Path to the project directory
|
||||
* @param {string} projectName - Name of the project
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.useYarn - Use yarn instead of npm
|
||||
*/
|
||||
async function initPackageJson(rootPath, projectName, { useYarn } = {}) {
|
||||
async function initPackageJson(rootPath: string, projectName: string, { useYarn }: Options = {}) {
|
||||
const packageManager = useYarn ? 'yarn --cwd' : 'npm run --prefix';
|
||||
|
||||
try {
|
||||
@ -59,16 +49,11 @@ async function initPackageJson(rootPath, projectName, { useYarn } = {}) {
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
stopProcess(`Failed to create ${chalk.yellow(`package.json`)} in ${chalk.yellow(rootPath)}`);
|
||||
stopProcess(`Failed to create ${chalk.yellow('package.json')} in ${chalk.yellow(rootPath)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path The directory path for install
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.useYarn Use yarn instead of npm
|
||||
*/
|
||||
async function installWithLogs(path, options) {
|
||||
async function installWithLogs(path: string, options: Options) {
|
||||
const installPrefix = chalk.yellow('Installing dependencies:');
|
||||
const loader = ora(installPrefix).start();
|
||||
const logInstall = (chunk = '') => {
|
||||
@ -76,8 +61,8 @@ async function installWithLogs(path, options) {
|
||||
};
|
||||
|
||||
const runner = runInstall(path, options);
|
||||
runner.stdout.on('data', logInstall);
|
||||
runner.stderr.on('data', logInstall);
|
||||
runner.stdout?.on('data', logInstall);
|
||||
runner.stderr?.on('data', logInstall);
|
||||
|
||||
await runner;
|
||||
|
||||
@ -85,17 +70,12 @@ async function installWithLogs(path, options) {
|
||||
console.log(`Dependencies installed ${chalk.green('successfully')}.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} starter The name of the starter as provided by the user
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.useYarn Use yarn instead of npm
|
||||
*/
|
||||
async function getStarterInfo(starter, { useYarn } = {}) {
|
||||
async function getStarterInfo(starter: string, { useYarn }: Options = {}) {
|
||||
const isLocalStarter = ['./', '../', '/'].some((filePrefix) => starter.startsWith(filePrefix));
|
||||
|
||||
let starterPath;
|
||||
let starterParentPath;
|
||||
let starterPackageInfo = {};
|
||||
let starterPackageInfo: PackageInfo | undefined;
|
||||
|
||||
if (isLocalStarter) {
|
||||
// Starter is a local directory
|
||||
@ -120,7 +100,10 @@ async function getStarterInfo(starter, { useYarn } = {}) {
|
||||
* @param {string|null} projectArgs.starter - The npm package of the starter
|
||||
* @param {Object} program - Commands for generating new application
|
||||
*/
|
||||
module.exports = async function buildStarter({ projectName, starter }, program) {
|
||||
export default async function buildStarter(
|
||||
{ projectName, starter }: { projectName: string; starter: string },
|
||||
program: Program
|
||||
) {
|
||||
const hasYarnInstalled = await hasYarn();
|
||||
const { isLocalStarter, starterPath, starterParentPath, starterPackageInfo } =
|
||||
await getStarterInfo(starter, { useYarn: hasYarnInstalled });
|
||||
@ -133,7 +116,11 @@ module.exports = async function buildStarter({ projectName, starter }, program)
|
||||
try {
|
||||
await fse.ensureDir(rootPath);
|
||||
} catch (error) {
|
||||
stopProcess(`Failed to create ${chalk.yellow(rootPath)}: ${error.message}`);
|
||||
if (error instanceof Error) {
|
||||
stopProcess(`Failed to create ${chalk.yellow(rootPath)}: ${error.message}`);
|
||||
}
|
||||
|
||||
stopProcess(`Failed to create ${chalk.yellow(rootPath)}: ${error}`);
|
||||
}
|
||||
|
||||
// Copy the downloaded frontend folder to the project folder
|
||||
@ -145,18 +132,22 @@ module.exports = async function buildStarter({ projectName, starter }, program)
|
||||
recursive: true,
|
||||
});
|
||||
} catch (error) {
|
||||
stopProcess(`Failed to create ${chalk.yellow(frontendPath)}: ${error.message}`);
|
||||
if (error instanceof Error) {
|
||||
stopProcess(`Failed to create ${chalk.yellow(frontendPath)}: ${error.message}`);
|
||||
}
|
||||
|
||||
stopProcess(`Failed to create ${chalk.yellow(frontendPath)}`);
|
||||
}
|
||||
|
||||
// Delete the starter directory if it was downloaded
|
||||
if (!isLocalStarter) {
|
||||
if (!isLocalStarter && starterParentPath) {
|
||||
await fse.remove(starterParentPath);
|
||||
}
|
||||
|
||||
// Set command options for Strapi app
|
||||
const generateStrapiAppOptions = {
|
||||
...program,
|
||||
starter: starterPackageInfo.name,
|
||||
starter: starterPackageInfo?.name,
|
||||
run: false,
|
||||
};
|
||||
if (starterJson.template.version) {
|
||||
@ -192,4 +183,4 @@ module.exports = async function buildStarter({ projectName, starter }, program)
|
||||
|
||||
console.log(chalk.green('Starting the app'));
|
||||
await runApp(rootPath, { useYarn: hasYarnInstalled });
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
import { execSync } from 'child_process';
|
||||
import execa from 'execa';
|
||||
import logger from './logger';
|
||||
import type { Options } from '../types';
|
||||
|
||||
export function runInstall(path: string, { useYarn }: Options = {}) {
|
||||
return execa(useYarn ? 'yarn' : 'npm', ['install'], {
|
||||
cwd: path,
|
||||
stdin: 'ignore',
|
||||
});
|
||||
}
|
||||
export function runApp(rootPath: string, { useYarn }: Options = {}) {
|
||||
if (useYarn) {
|
||||
return execa('yarn', ['develop'], {
|
||||
stdio: 'inherit',
|
||||
cwd: rootPath,
|
||||
});
|
||||
}
|
||||
|
||||
return execa('npm', ['run', 'develop'], {
|
||||
stdio: 'inherit',
|
||||
cwd: rootPath,
|
||||
});
|
||||
}
|
||||
|
||||
export async function initGit(rootPath: string) {
|
||||
try {
|
||||
await execa('git', ['init'], {
|
||||
cwd: rootPath,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn('Could not initialize a git repository');
|
||||
}
|
||||
|
||||
try {
|
||||
await execa('git', ['add', '-A'], { cwd: rootPath });
|
||||
|
||||
execSync('git commit -m "Create Strapi starter project"', {
|
||||
cwd: rootPath,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn('Could not create initial git commit');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
import path from 'path';
|
||||
import execa from 'execa';
|
||||
import chalk from 'chalk';
|
||||
import stopProcess from './stop-process';
|
||||
import type { Options, PackageInfo } from '../types';
|
||||
|
||||
/**
|
||||
* Gets the package version on npm. Will fail if the package does not exist
|
||||
*/
|
||||
async function getPackageInfo(packageName: string, options?: Options): Promise<PackageInfo> {
|
||||
const { useYarn } = options ?? {};
|
||||
|
||||
// Use yarn if possible because it's faster
|
||||
if (useYarn) {
|
||||
const { stdout } = await execa('yarn', ['info', packageName, '--json']);
|
||||
const yarnInfo = JSON.parse(stdout);
|
||||
return {
|
||||
name: yarnInfo.data.name,
|
||||
version: yarnInfo.data.version,
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback to npm
|
||||
const { stdout } = await execa('npm', ['view', packageName, 'name', 'version', '--silent']);
|
||||
// Use regex to parse name and version from CLI result
|
||||
const match = stdout.match(/(?<=')(.*?)(?=')/gm);
|
||||
|
||||
if (!match) {
|
||||
throw new Error('No match for name@version');
|
||||
}
|
||||
|
||||
const [name, version] = match;
|
||||
return { name, version };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version and full package name of the starter
|
||||
*/
|
||||
export async function getStarterPackageInfo(
|
||||
starter: string,
|
||||
options?: Options
|
||||
): Promise<PackageInfo> {
|
||||
const { useYarn } = options ?? {};
|
||||
|
||||
// Check if starter is a shorthand
|
||||
try {
|
||||
const longhand = `@strapi/starter-${starter}`;
|
||||
return await getPackageInfo(longhand, { useYarn });
|
||||
} catch (error) {
|
||||
// Ignore error, we now know it's not a shorthand
|
||||
}
|
||||
// Fetch version of the non-shorthand package
|
||||
try {
|
||||
return await getPackageInfo(starter, { useYarn });
|
||||
} catch (error) {
|
||||
return stopProcess(`Could not find package ${chalk.yellow(starter)} on npm`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a starter package from the npm registry
|
||||
*/
|
||||
export async function downloadNpmStarter(
|
||||
packageInfo: PackageInfo,
|
||||
parentDir: string,
|
||||
options?: Options
|
||||
): Promise<string> {
|
||||
const { name, version } = packageInfo;
|
||||
const { useYarn } = options ?? {};
|
||||
|
||||
// Download from npm, using yarn if possible
|
||||
if (useYarn) {
|
||||
await execa('yarn', ['add', `${name}@${version}`, '--no-lockfile', '--silent'], {
|
||||
cwd: parentDir,
|
||||
});
|
||||
} else {
|
||||
await execa('npm', ['install', `${name}@${version}`, '--no-save', '--silent'], {
|
||||
cwd: parentDir,
|
||||
});
|
||||
}
|
||||
|
||||
// Return the path of the actual starter
|
||||
const exactStarterPath = path.dirname(
|
||||
require.resolve(`${name}/package.json`, { paths: [parentDir] })
|
||||
);
|
||||
return exactStarterPath;
|
||||
}
|
||||
@ -1,8 +1,6 @@
|
||||
'use strict';
|
||||
import execa from 'execa';
|
||||
|
||||
const execa = require('execa');
|
||||
|
||||
module.exports = async function hasYarn() {
|
||||
export default async function hasYarn() {
|
||||
try {
|
||||
const { exitCode } = await execa.commandSync('yarn --version', { shell: true });
|
||||
|
||||
@ -10,4 +8,4 @@ module.exports = async function hasYarn() {
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1,17 +1,15 @@
|
||||
'use strict';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
module.exports = {
|
||||
error(message) {
|
||||
export default {
|
||||
error(message: string) {
|
||||
console.error(`${chalk.red('error')}: ${message}`);
|
||||
},
|
||||
|
||||
warn(message) {
|
||||
warn(message: string) {
|
||||
console.log(`${chalk.yellow('warning')}: ${message}`);
|
||||
},
|
||||
|
||||
info(message) {
|
||||
info(message: string) {
|
||||
console.log(`${chalk.blue('info')}: ${message}`);
|
||||
},
|
||||
};
|
||||
45
packages/cli/create-strapi-starter/src/utils/prompt-user.ts
Normal file
45
packages/cli/create-strapi-starter/src/utils/prompt-user.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import inquirer from 'inquirer';
|
||||
import type { Program } from '../types';
|
||||
|
||||
interface Answers {
|
||||
directory: string;
|
||||
quick: boolean;
|
||||
starter: string;
|
||||
}
|
||||
|
||||
// Prompts the user with required questions to create the project and return the answers
|
||||
export default async function promptUser(projectName: string, starter: string, program: Program) {
|
||||
const questions: inquirer.QuestionCollection = [
|
||||
{
|
||||
type: 'input',
|
||||
default: 'my-strapi-project',
|
||||
name: 'directory',
|
||||
message: 'What would you like to name your project?',
|
||||
when: !projectName,
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'quick',
|
||||
message: 'Choose your installation type',
|
||||
when: !program.quickstart,
|
||||
choices: [
|
||||
{
|
||||
name: 'Quickstart (recommended)',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'Custom (manual settings)',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'starter',
|
||||
when: !starter,
|
||||
message: 'Please provide the npm package name of the starter you want to use:',
|
||||
},
|
||||
];
|
||||
|
||||
return inquirer.prompt<Answers>(questions);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import logger from './logger';
|
||||
|
||||
export default function stopProcess(message: string) {
|
||||
if (message) {
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
return process.exit(1);
|
||||
}
|
||||
8
packages/cli/create-strapi-starter/tsconfig.eslint.json
Normal file
8
packages/cli/create-strapi-starter/tsconfig.eslint.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
8
packages/cli/create-strapi-starter/tsconfig.json
Normal file
8
packages/cli/create-strapi-starter/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "**/__tests__/**"]
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const execa = require('execa');
|
||||
const logger = require('./logger');
|
||||
|
||||
/**
|
||||
* @param {string} path Path to directory (frontend, backend)
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.useYarn Use yarn instead of npm
|
||||
*/
|
||||
function runInstall(path, { useYarn } = {}) {
|
||||
return execa(useYarn ? 'yarn' : 'npm', ['install'], {
|
||||
cwd: path,
|
||||
stdin: 'ignore',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} rootPath
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.useYarn
|
||||
*/
|
||||
function runApp(rootPath, { useYarn } = {}) {
|
||||
if (useYarn) {
|
||||
return execa('yarn', ['develop'], {
|
||||
stdio: 'inherit',
|
||||
cwd: rootPath,
|
||||
});
|
||||
}
|
||||
return execa('npm', ['run', 'develop'], {
|
||||
stdio: 'inherit',
|
||||
cwd: rootPath,
|
||||
});
|
||||
}
|
||||
|
||||
async function initGit(rootPath) {
|
||||
try {
|
||||
await execa('git', ['init'], {
|
||||
cwd: rootPath,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(`Could not initialize a git repository`);
|
||||
}
|
||||
|
||||
try {
|
||||
await execa(`git`, [`add`, `-A`], { cwd: rootPath });
|
||||
|
||||
execSync(`git commit -m "Create Strapi starter project"`, {
|
||||
cwd: rootPath,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(`Could not create initial git commit`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
runInstall,
|
||||
runApp,
|
||||
initGit,
|
||||
};
|
||||
@ -1,86 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const execa = require('execa');
|
||||
const chalk = require('chalk');
|
||||
const stopProcess = require('./stop-process');
|
||||
|
||||
/**
|
||||
* Gets the package version on npm. Will fail if the package does not exist
|
||||
* @param {string} packageName Name to look up on npm, may include a specific version
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.useYarn Yarn instead of npm
|
||||
* @returns {Object}
|
||||
*/
|
||||
async function getPackageInfo(packageName, { useYarn } = {}) {
|
||||
// Use yarn if possible because it's faster
|
||||
if (useYarn) {
|
||||
const { stdout } = await execa('yarn', ['info', packageName, '--json']);
|
||||
const yarnInfo = JSON.parse(stdout);
|
||||
return {
|
||||
name: yarnInfo.data.name,
|
||||
version: yarnInfo.data.version,
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback to npm
|
||||
const { stdout } = await execa('npm', ['view', packageName, 'name', 'version', '--silent']);
|
||||
// Use regex to parse name and version from CLI result
|
||||
const [name, version] = stdout.match(/(?<=')(.*?)(?=')/gm);
|
||||
return { name, version };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version and full package name of the starter
|
||||
* @param {string} starter - The name of the starter as provided by the user
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.useYarn - Use yarn instead of npm
|
||||
* @returns {Object} - Full name and version of the starter package on npm
|
||||
*/
|
||||
async function getStarterPackageInfo(starter, { useYarn } = {}) {
|
||||
// Check if starter is a shorthand
|
||||
try {
|
||||
const longhand = `@strapi/starter-${starter}`;
|
||||
const packageInfo = await getPackageInfo(longhand, { useYarn });
|
||||
// Hasn't crashed so it is indeed a shorthand
|
||||
return packageInfo;
|
||||
} catch (error) {
|
||||
// Ignore error, we now know it's not a shorthand
|
||||
}
|
||||
// Fetch version of the non-shorthand package
|
||||
try {
|
||||
return getPackageInfo(starter, { useYarn });
|
||||
} catch (error) {
|
||||
stopProcess(`Could not find package ${chalk.yellow(starter)} on npm`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a starter package from the npm registry
|
||||
* @param {Object} packageInfo - Starter's npm package information
|
||||
* @param {string} packageInfo.name
|
||||
* @param {string} packageInfo.version
|
||||
* @param {string} parentDir - Path inside of which we install the starter
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.useYarn - Use yarn instead of npm
|
||||
*/
|
||||
async function downloadNpmStarter({ name, version }, parentDir, { useYarn } = {}) {
|
||||
// Download from npm, using yarn if possible
|
||||
if (useYarn) {
|
||||
await execa('yarn', ['add', `${name}@${version}`, '--no-lockfile', '--silent'], {
|
||||
cwd: parentDir,
|
||||
});
|
||||
} else {
|
||||
await execa('npm', ['install', `${name}@${version}`, '--no-save', '--silent'], {
|
||||
cwd: parentDir,
|
||||
});
|
||||
}
|
||||
|
||||
// Return the path of the actual starter
|
||||
const exactStarterPath = path.dirname(
|
||||
require.resolve(`${name}/package.json`, { paths: [parentDir] })
|
||||
);
|
||||
return exactStarterPath;
|
||||
}
|
||||
|
||||
module.exports = { getStarterPackageInfo, downloadNpmStarter };
|
||||
@ -1,62 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const inquirer = require('inquirer');
|
||||
|
||||
/**
|
||||
* @param {string|null} projectName - The name/path of project
|
||||
* @param {string|null} starterUrl - The GitHub repo of the starter
|
||||
* @returns Object containting prompt answers
|
||||
*/
|
||||
module.exports = async function promptUser(projectName, starter, program) {
|
||||
const mainQuestions = [
|
||||
{
|
||||
type: 'input',
|
||||
default: 'my-strapi-project',
|
||||
name: 'directory',
|
||||
message: 'What would you like to name your project?',
|
||||
when: !projectName,
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'quick',
|
||||
message: 'Choose your installation type',
|
||||
when: !program.quickstart,
|
||||
choices: [
|
||||
{
|
||||
name: 'Quickstart (recommended)',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'Custom (manual settings)',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const [mainResponse, starterQuestion] = await Promise.all([
|
||||
inquirer.prompt(mainQuestions),
|
||||
getStarterQuestion(),
|
||||
]);
|
||||
|
||||
const starterResponse = await inquirer.prompt({
|
||||
name: 'starter',
|
||||
when: !starter,
|
||||
...starterQuestion,
|
||||
});
|
||||
|
||||
return { ...mainResponse, ...starterResponse };
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns Prompt question object
|
||||
*/
|
||||
async function getStarterQuestion() {
|
||||
// Ask user to manually input his starter
|
||||
// TODO: find way to suggest the possible v4 starters
|
||||
return {
|
||||
type: 'input',
|
||||
message: 'Please provide the npm package name of the starter you want to use:',
|
||||
};
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const logger = require('./logger');
|
||||
|
||||
module.exports = function stopProcess(message) {
|
||||
if (message) logger.error(message);
|
||||
process.exit(1);
|
||||
};
|
||||
@ -222,6 +222,8 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
||||
|
||||
trackUsageRef.current('didDeleteEntry', trackerProperty);
|
||||
|
||||
replace(redirectionLink);
|
||||
|
||||
return Promise.resolve(data);
|
||||
} catch (err) {
|
||||
trackUsageRef.current('didNotDeleteEntry', { error: err, ...trackerProperty });
|
||||
@ -229,13 +231,9 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
||||
return Promise.reject(err);
|
||||
}
|
||||
},
|
||||
[id, slug, toggleNotification, del]
|
||||
[id, slug, toggleNotification, del, redirectionLink, replace]
|
||||
);
|
||||
|
||||
const onDeleteSucceeded = useCallback(() => {
|
||||
replace(redirectionLink);
|
||||
}, [redirectionLink, replace]);
|
||||
|
||||
const onPost = useCallback(
|
||||
async (body, trackerProperty) => {
|
||||
const endPoint = `${getRequestUrl(`collection-types/${slug}`)}${rawQuery}`;
|
||||
@ -409,7 +407,6 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
||||
isCreatingEntry,
|
||||
isLoadingForData: isLoading,
|
||||
onDelete,
|
||||
onDeleteSucceeded,
|
||||
onPost,
|
||||
onPublish,
|
||||
onDraftRelationCheck,
|
||||
|
||||
@ -172,6 +172,9 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
|
||||
|
||||
trackUsageRef.current('didDeleteEntry', trackerProperty);
|
||||
|
||||
setIsCreatingEntry(true);
|
||||
dispatch(initForm(rawQuery, true));
|
||||
|
||||
return Promise.resolve(data);
|
||||
} catch (err) {
|
||||
trackUsageRef.current('didNotDeleteEntry', { error: err, ...trackerProperty });
|
||||
@ -181,15 +184,9 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
},
|
||||
[del, slug, displayErrors, toggleNotification, searchToSend]
|
||||
[del, slug, displayErrors, toggleNotification, searchToSend, dispatch, rawQuery]
|
||||
);
|
||||
|
||||
const onDeleteSucceeded = useCallback(() => {
|
||||
setIsCreatingEntry(true);
|
||||
|
||||
dispatch(initForm(rawQuery, true));
|
||||
}, [dispatch, rawQuery]);
|
||||
|
||||
const onPost = useCallback(
|
||||
async (body, trackerProperty) => {
|
||||
const endPoint = getRequestUrl(`${slug}${rawQuery}`);
|
||||
@ -370,7 +367,6 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
|
||||
isCreatingEntry,
|
||||
isLoadingForData: isLoading,
|
||||
onDelete,
|
||||
onDeleteSucceeded,
|
||||
onPost,
|
||||
onDraftRelationCheck,
|
||||
onPublish,
|
||||
|
||||
@ -8,14 +8,14 @@ import PropTypes from 'prop-types';
|
||||
import { getTrad } from '../../../utils';
|
||||
import { connect, select } from './utils';
|
||||
|
||||
const DeleteLink = ({ isCreatingEntry, onDelete, onDeleteSucceeded, trackerProperty }) => {
|
||||
const [showWarningDelete, setWarningDelete] = useState(false);
|
||||
const DeleteLink = ({ onDelete, trackerProperty }) => {
|
||||
const [displayDeleteConfirmation, setDisplayDeleteConfirmation] = useState(false);
|
||||
const [isModalConfirmButtonLoading, setIsModalConfirmButtonLoading] = useState(false);
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatAPIError } = useAPIErrorHandler(getTrad);
|
||||
const toggleNotification = useNotification();
|
||||
|
||||
const toggleWarningDelete = () => setWarningDelete((prevState) => !prevState);
|
||||
const toggleWarningDelete = () => setDisplayDeleteConfirmation((prevState) => !prevState);
|
||||
|
||||
const handleConfirmDelete = async () => {
|
||||
try {
|
||||
@ -27,7 +27,6 @@ const DeleteLink = ({ isCreatingEntry, onDelete, onDeleteSucceeded, trackerPrope
|
||||
setIsModalConfirmButtonLoading(false);
|
||||
|
||||
toggleWarningDelete();
|
||||
onDeleteSucceeded();
|
||||
} catch (err) {
|
||||
setIsModalConfirmButtonLoading(false);
|
||||
toggleWarningDelete();
|
||||
@ -38,10 +37,6 @@ const DeleteLink = ({ isCreatingEntry, onDelete, onDeleteSucceeded, trackerPrope
|
||||
}
|
||||
};
|
||||
|
||||
if (isCreatingEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={toggleWarningDelete} size="S" startIcon={<Trash />} variant="danger-light">
|
||||
@ -50,9 +45,10 @@ const DeleteLink = ({ isCreatingEntry, onDelete, onDeleteSucceeded, trackerPrope
|
||||
defaultMessage: 'Delete this entry',
|
||||
})}
|
||||
</Button>
|
||||
|
||||
<ConfirmDialog
|
||||
isConfirmButtonLoading={isModalConfirmButtonLoading}
|
||||
isOpen={showWarningDelete}
|
||||
isOpen={displayDeleteConfirmation}
|
||||
onConfirm={handleConfirmDelete}
|
||||
onToggleDialog={toggleWarningDelete}
|
||||
/>
|
||||
@ -61,9 +57,7 @@ const DeleteLink = ({ isCreatingEntry, onDelete, onDeleteSucceeded, trackerPrope
|
||||
};
|
||||
|
||||
DeleteLink.propTypes = {
|
||||
isCreatingEntry: PropTypes.bool.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onDeleteSucceeded: PropTypes.func.isRequired,
|
||||
trackerProperty: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@ -77,7 +77,6 @@ const EditView = ({ allowedActions, isSingleType, goBack, slug, id, origin, user
|
||||
isCreatingEntry,
|
||||
isLoadingForData,
|
||||
onDelete,
|
||||
onDeleteSucceeded,
|
||||
onPost,
|
||||
onPublish,
|
||||
onDraftRelationCheck,
|
||||
@ -221,12 +220,8 @@ const EditView = ({ allowedActions, isSingleType, goBack, slug, id, origin, user
|
||||
</LinkButton>
|
||||
</CheckPermissions>
|
||||
|
||||
{allowedActions.canDelete && (
|
||||
<DeleteLink
|
||||
isCreatingEntry={isCreatingEntry}
|
||||
onDelete={onDelete}
|
||||
onDeleteSucceeded={onDeleteSucceeded}
|
||||
/>
|
||||
{allowedActions.canDelete && !isCreatingEntry && (
|
||||
<DeleteLink onDelete={onDelete} />
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
@ -187,7 +187,7 @@ const SocialLinks = () => {
|
||||
<GridGap>
|
||||
{socialLinksExtended.map(({ icon, link, name }) => {
|
||||
return (
|
||||
<GridItem col={6} s={12} key={name}>
|
||||
<GridItem col={6} s={12} key={name.id}>
|
||||
<LinkCustom size="L" startIcon={icon} variant="tertiary" href={link} isExternal>
|
||||
{formatMessage(name)}
|
||||
</LinkCustom>
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
"error.contentTypeName.reserved-name": "This name cannot be used in your project as it might break other functionalities",
|
||||
"error.validation.enum-duplicate": "Duplicate values are not allowed (only alphanumeric characters are taken into account).",
|
||||
"error.validation.enum-empty-string": "Empty strings are not allowed",
|
||||
"error.validation.enum-regex": "At least one value is invalid. Values should have at least one alphabetical character preceeding the first occurence of a number.",
|
||||
"error.validation.enum-regex": "At least one value is invalid. Values should have at least one alphabetical character preceding the first occurence of a number.",
|
||||
"error.validation.minSupMax": "Can't be superior",
|
||||
"error.validation.positive": "Must be a positive number",
|
||||
"error.validation.regex": "Regex pattern is invalid",
|
||||
|
||||
@ -121,6 +121,28 @@ describe('Given I have some relations in the database', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When you connect a relation before one with null order', () => {
|
||||
test('Then it replaces null order values to 1 and properly reorders relations', () => {
|
||||
const orderer = relationsOrderer(
|
||||
[
|
||||
{ id: 2, order: null },
|
||||
{ id: 3, order: null },
|
||||
],
|
||||
'id',
|
||||
'order'
|
||||
);
|
||||
|
||||
orderer.connect([{ id: 4, position: { before: 3 } }, { id: 5 }]);
|
||||
|
||||
expect(orderer.get()).toMatchObject([
|
||||
{ id: 2, order: 1 },
|
||||
{ id: 4, order: 0.5 },
|
||||
{ id: 3, order: 1 },
|
||||
{ id: 5, order: 1.5 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Given there are no relations in the database', () => {
|
||||
|
||||
@ -135,7 +135,7 @@ const relationsOrderer = (initArr, idColumn, orderColumn, strict) => {
|
||||
const computedRelations = _.castArray(initArr || []).map((r) => ({
|
||||
init: true,
|
||||
id: r[idColumn],
|
||||
order: r[orderColumn],
|
||||
order: r[orderColumn] || 1,
|
||||
}));
|
||||
|
||||
const maxOrder = _.maxBy('order', computedRelations)?.order || 0;
|
||||
|
||||
@ -57,15 +57,18 @@ class Database {
|
||||
|
||||
async function commit() {
|
||||
if (notNestedTransaction) {
|
||||
transactionCtx.clear();
|
||||
await trx.commit();
|
||||
}
|
||||
}
|
||||
|
||||
async function rollback() {
|
||||
if (notNestedTransaction) {
|
||||
transactionCtx.clear();
|
||||
await trx.rollback();
|
||||
}
|
||||
}
|
||||
|
||||
if (!cb) {
|
||||
return {
|
||||
commit,
|
||||
|
||||
@ -6,11 +6,19 @@ const storage = new AsyncLocalStorage();
|
||||
|
||||
const transactionCtx = {
|
||||
async run(store, cb) {
|
||||
return storage.run(store, cb);
|
||||
return storage.run({ trx: store }, cb);
|
||||
},
|
||||
|
||||
get() {
|
||||
return storage.getStore();
|
||||
const store = storage.getStore();
|
||||
return store?.trx;
|
||||
},
|
||||
|
||||
clear() {
|
||||
const store = storage.getStore();
|
||||
if (store?.trx) {
|
||||
store.trx = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -536,17 +536,17 @@ class Strapi {
|
||||
// plugins
|
||||
await this.container.get('modules')[lifecycleName]();
|
||||
|
||||
// user
|
||||
const userLifecycleFunction = this.app && this.app[lifecycleName];
|
||||
if (isFunction(userLifecycleFunction)) {
|
||||
await userLifecycleFunction({ strapi: this });
|
||||
}
|
||||
|
||||
// admin
|
||||
const adminLifecycleFunction = this.admin && this.admin[lifecycleName];
|
||||
if (isFunction(adminLifecycleFunction)) {
|
||||
await adminLifecycleFunction({ strapi: this });
|
||||
}
|
||||
|
||||
// user
|
||||
const userLifecycleFunction = this.app && this.app[lifecycleName];
|
||||
if (isFunction(userLifecycleFunction)) {
|
||||
await userLifecycleFunction({ strapi: this });
|
||||
}
|
||||
}
|
||||
|
||||
getModel(uid) {
|
||||
|
||||
@ -61,7 +61,7 @@ const contentTypeSchemaValidator = yup.object().shape({
|
||||
|
||||
// should match the GraphQL regex
|
||||
if (!regressedValues.every((value) => GRAPHQL_ENUM_REGEX.test(value))) {
|
||||
const message = `Invalid enumeration value. Values should have at least one alphabetical character preceeding the first occurence of a number. Update your enumeration '${attrName}'.`;
|
||||
const message = `Invalid enumeration value. Values should have at least one alphabetical character preceding the first occurence of a number. Update your enumeration '${attrName}'.`;
|
||||
|
||||
return this.createError({ message });
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ module.exports = {
|
||||
'node/no-missing-import': 'off',
|
||||
'@typescript-eslint/brace-style': 'off', // TODO: fix conflict with prettier/prettier in data-transfer/engine/index.ts
|
||||
// to be cleaned up throughout codebase (too many to fix at the moment)
|
||||
'@typescript-eslint/no-use-before-define': 'warn',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/comma-dangle': 'off',
|
||||
},
|
||||
overrides: [
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
|
||||
@ -13604,9 +13604,11 @@ __metadata:
|
||||
dependencies:
|
||||
"@strapi/generate-new": 4.9.1
|
||||
commander: 8.3.0
|
||||
eslint-config-custom: "*"
|
||||
inquirer: 8.2.5
|
||||
tsconfig: "*"
|
||||
bin:
|
||||
create-strapi-app: ./index.js
|
||||
create-strapi-app: ./bin/index.js
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@ -13618,12 +13620,14 @@ __metadata:
|
||||
chalk: 4.1.2
|
||||
ci-info: 3.8.0
|
||||
commander: 8.3.0
|
||||
eslint-config-custom: "*"
|
||||
execa: 5.1.1
|
||||
fs-extra: 10.0.0
|
||||
inquirer: 8.2.5
|
||||
ora: 5.4.1
|
||||
tsconfig: "*"
|
||||
bin:
|
||||
create-strapi-starter: ./index.js
|
||||
create-strapi-starter: ./bin/index.js
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user