Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gold-students-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@strapi/sdk-plugin': minor
---

feat: add a command for generating boilerplate based on @strapi/generators
5 changes: 5 additions & 0 deletions .changeset/popular-pianos-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@strapi/sdk-plugin': patch
---

feat: add validation for the 'strapi' object in the package json
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,7 @@ Verifies the output of your plugin before publishing it
```sh
yarn run verify
```

### `generate`

Starts an interactive CLI to generate boilerplate code for your plugin
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"watch": "pack-up watch"
},
"dependencies": {
"@strapi/generators": "5.27.0",
"@strapi/pack-up": "^5.0.1",
"@types/prompts": "2.4.9",
"boxen": "5.1.2",
Expand All @@ -67,6 +68,7 @@
"execa": "^9.3.1",
"get-latest-version": "5.1.0",
"git-url-parse": "13.1.1",
"inquirer": "^9.3.8",
"nodemon": "^3.1.0",
"ora": "5.4.1",
"outdent": "0.8.0",
Expand All @@ -85,6 +87,7 @@
"@swc/core": "^1.4.13",
"@swc/jest": "^0.2.36",
"@types/git-url-parse": "9.0.3",
"@types/inquirer": "^9.0.9",
"@types/jest": "^29.5.12",
"@types/node": "^22.5.4",
"@types/prettier": "^2.0.0",
Expand Down
925 changes: 925 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/cli/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { command as buildPluginCommand } from './plugin/build';
import { command as generateCommand } from './plugin/generate';
import { command as initPluginCommand } from './plugin/init';
import { command as linkWatchPluginCommand } from './plugin/link-watch';
import { command as verifyPluginCommand } from './plugin/verify';
Expand All @@ -8,6 +9,7 @@ import type { StrapiCommand } from '../../types';

export const commands: StrapiCommand[] = [
buildPluginCommand,
generateCommand,
initPluginCommand,
linkWatchPluginCommand,
watchPluginCommand,
Expand Down
37 changes: 37 additions & 0 deletions src/cli/commands/plugin/generate/actions/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { generate } from '@strapi/generators';
import validateInput from '@strapi/generators/dist/plops/utils/validate-input';
import inquirer from 'inquirer';

import { loadPkg, validatePkg } from '../../../utils/pkg';

import type { CLIContext } from '../../../../../types';

/**
* api generator for Strapi plugins
*/
const action = async ({ ctx: { cwd, logger } }: { ctx: CLIContext }) => {
const pkg = await loadPkg({ cwd, logger });
const validatedPkg = await validatePkg({ pkg });

const config = await inquirer.prompt([
{
type: 'input',
name: 'id',
message: 'API name',
validate: (input) => validateInput(input),
},
]);

generate(
'api',
{
id: config.id,
isPluginApi: true,
destination: 'root',
plugin: validatedPkg.strapi.name,
},
{ dir: 'server' }
);
};

export default action;
40 changes: 40 additions & 0 deletions src/cli/commands/plugin/generate/actions/content-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { generate } from '@strapi/generators';
import bootstrapApiPrompts from '@strapi/generators/dist/plops/prompts/bootstrap-api-prompts';
import ctNamesPrompts from '@strapi/generators/dist/plops/prompts/ct-names-prompts';
import getAttributesPrompts from '@strapi/generators/dist/plops/prompts/get-attributes-prompts';
import kindPrompts from '@strapi/generators/dist/plops/prompts/kind-prompts';
import inquirer from 'inquirer';

import { loadPkg, validatePkg } from '../../../utils/pkg';

import type { CLIContext } from '../../../../../types';

/**
* content-type generator for Strapi plugins
*/
const action = async ({ ctx: { cwd, logger } }: { ctx: CLIContext }) => {
const pkg = await loadPkg({ cwd, logger });
const validatedPkg = await validatePkg({ pkg });

const nameInfo = await inquirer.prompt([...ctNamesPrompts, ...kindPrompts] as any);
const attributes = await getAttributesPrompts(inquirer);
const bootstrapInfo = await inquirer.prompt([...bootstrapApiPrompts] as any);

generate(
'content-type',
{
kind: nameInfo.kind,
singularName: nameInfo.singularName,
id: nameInfo.displayName,
pluralName: nameInfo.pluralName,
displayName: nameInfo.displayName,
destination: 'root',
bootstrapApi: bootstrapInfo.bootstrapApi,
attributes,
plugin: validatedPkg.strapi.name,
},
{ dir: 'server' }
);
};

export default action;
36 changes: 36 additions & 0 deletions src/cli/commands/plugin/generate/actions/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { generate } from '@strapi/generators';
import validateInput from '@strapi/generators/dist/plops/utils/validate-input';
import inquirer from 'inquirer';

import { loadPkg, validatePkg } from '../../../utils/pkg';

import type { CLIContext } from '../../../../../types';

/**
* controller generator for Strapi plugins
*/
const action = async ({ ctx: { cwd, logger } }: { ctx: CLIContext }) => {
const pkg = await loadPkg({ cwd, logger });
const validatedPkg = await validatePkg({ pkg });

const config = await inquirer.prompt([
{
type: 'input',
name: 'id',
message: 'Controller name',
validate: (input) => validateInput(input),
},
]);

generate(
'controller',
{
id: config.id,
destination: 'root',
plugin: validatedPkg.strapi.name,
},
{ dir: 'server' }
);
};

export default action;
36 changes: 36 additions & 0 deletions src/cli/commands/plugin/generate/actions/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { generate } from '@strapi/generators';
import validateInput from '@strapi/generators/dist/plops/utils/validate-input';
import inquirer from 'inquirer';

import { loadPkg, validatePkg } from '../../../utils/pkg';

import type { CLIContext } from '../../../../../types';

/**
* middleware generator for Strapi plugins
*/
const action = async ({ ctx: { cwd, logger } }: { ctx: CLIContext }) => {
const pkg = await loadPkg({ cwd, logger });
const validatedPkg = await validatePkg({ pkg });

const config = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Middleware name',
validate: (input) => validateInput(input),
},
]);

generate(
'middleware',
{
name: config.name,
destination: 'root',
plugin: validatedPkg.strapi.name,
},
{ dir: 'server' }
);
};

export default action;
36 changes: 36 additions & 0 deletions src/cli/commands/plugin/generate/actions/policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { generate } from '@strapi/generators';
import validateInput from '@strapi/generators/dist/plops/utils/validate-input';
import inquirer from 'inquirer';

import { loadPkg, validatePkg } from '../../../utils/pkg';

import type { CLIContext } from '../../../../../types';

/**
* policy generator for Strapi plugins
*/
const action = async ({ ctx: { cwd, logger } }: { ctx: CLIContext }) => {
const pkg = await loadPkg({ cwd, logger });
const validatedPkg = await validatePkg({ pkg });

const config = await inquirer.prompt([
{
type: 'input',
name: 'id',
message: 'Policy name',
validate: (input) => validateInput(input),
},
]);

generate(
'policy',
{
id: config.id,
destination: 'root',
plugin: validatedPkg.strapi.name,
},
{ dir: 'server' }
);
};

export default action;
36 changes: 36 additions & 0 deletions src/cli/commands/plugin/generate/actions/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { generate } from '@strapi/generators';
import validateInput from '@strapi/generators/dist/plops/utils/validate-input';
import inquirer from 'inquirer';

import { loadPkg, validatePkg } from '../../../utils/pkg';

import type { CLIContext } from '../../../../../types';

/**
* service generator for Strapi plugins
*/
const action = async ({ ctx: { cwd, logger } }: { ctx: CLIContext }) => {
const pkg = await loadPkg({ cwd, logger });
const validatedPkg = await validatePkg({ pkg });

const config = await inquirer.prompt([
{
type: 'input',
name: 'id',
message: 'Service name',
validate: (input) => validateInput(input),
},
]);

generate(
'service',
{
id: config.id,
destination: 'root',
plugin: validatedPkg.strapi.name,
},
{ dir: 'server' }
);
};

export default action;
65 changes: 65 additions & 0 deletions src/cli/commands/plugin/generate/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import inquirer from 'inquirer';

import apiAction from './actions/api';
import ctAction from './actions/content-type';
import controllerAction from './actions/controller';
import middlewareAction from './actions/middleware';
import policyAction from './actions/policy';
import serviceAction from './actions/service';

import type { StrapiCommand } from '../../../../types';

/**
* `$ strapi-plugin generate`
*/
const command: StrapiCommand = ({ command: commanderCommand, ctx }) => {
commanderCommand
.command('generate')
.description('Generate some boilerplate code for a Strapi plugin')
.option('-d, --debug', 'Enable debugging mode with verbose logs', false)
.option('--silent', "Don't log anything", false)
.action(async () => {
const options = [
{ name: 'api', value: 'api' },
{ name: 'controller', value: 'controller' },
{ name: 'content-type', value: 'content-type' },
{ name: 'policy', value: 'policy' },
{ name: 'middleware', value: 'middleware' },
{ name: 'service', value: 'service' },
];

const { generator } = await inquirer.prompt([
{
type: 'list',
name: 'generator',
message: 'Strapi Generators',
choices: options,
},
]);

switch (generator) {
case 'api':
apiAction({ ctx });
break;
case 'controller':
controllerAction({ ctx });
break;
case 'content-type':
ctAction({ ctx });
break;
case 'policy':
policyAction({ ctx });
break;
case 'middleware':
middlewareAction({ ctx });
break;
case 'service':
serviceAction({ ctx });
break;
default:
ctx.logger.error('Unknown generator type');
}
});
};

export default command;
1 change: 1 addition & 0 deletions src/cli/commands/plugin/generate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as command } from './command';
6 changes: 6 additions & 0 deletions src/cli/commands/utils/pkg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ interface Export {

const packageJsonSchema = yup.object({
name: yup.string().required(),
strapi: yup.object({
kind: yup.string().required(),
name: yup.string().required(),
displayName: yup.string().required(),
description: yup.string().optional(),
}),
exports: yup.lazy((value) =>
yup
.object(
Expand Down