mirror of
https://github.com/strapi/strapi.git
synced 2025-08-27 18:25:49 +00:00
feat(strapi): add experimental plugin init command (#18970)
This commit is contained in:
parent
40ef34cbcd
commit
46e6ffbb44
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: plugin:build
|
title: plugin:watch
|
||||||
description: An in depth look at the plugin:build command of the Strapi CLI
|
description: An in depth look at the plugin:watch command of the Strapi CLI
|
||||||
tags:
|
tags:
|
||||||
- CLI
|
- CLI
|
||||||
- commands
|
- commands
|
||||||
|
35
docs/docs/docs/01-core/strapi/commands/plugin/03-init.md
Normal file
35
docs/docs/docs/01-core/strapi/commands/plugin/03-init.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
title: plugin:init
|
||||||
|
description: An in depth look at the plugin:init command of the Strapi CLI
|
||||||
|
tags:
|
||||||
|
- CLI
|
||||||
|
- commands
|
||||||
|
- plugins
|
||||||
|
- initialization
|
||||||
|
---
|
||||||
|
|
||||||
|
The `plugin:init` command is used to create a plugin, by default in `src/plugins` – because this is the strapi CLI we assume we're in a user app by default. This is done by using `pack-up` underneath and a unique template configuration.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
strapi plugin:init [path]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Create a new plugin at a given path.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-d, --debug Enable debugging mode with verbose logs (default: false)
|
||||||
|
--silent Don't log anything (default: false)
|
||||||
|
-h, --help Display help for command
|
||||||
|
```
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
The command sequence can be visualised as follows:
|
||||||
|
|
||||||
|
- Ask the user a series of questions via prompts
|
||||||
|
- Generate a plugin folder structure based on that template
|
@ -142,6 +142,8 @@
|
|||||||
"dotenv": "14.2.0",
|
"dotenv": "14.2.0",
|
||||||
"execa": "5.1.1",
|
"execa": "5.1.1",
|
||||||
"fs-extra": "10.0.0",
|
"fs-extra": "10.0.0",
|
||||||
|
"get-latest-version": "5.1.0",
|
||||||
|
"git-url-parse": "13.1.0",
|
||||||
"glob": "7.2.3",
|
"glob": "7.2.3",
|
||||||
"http-errors": "1.8.1",
|
"http-errors": "1.8.1",
|
||||||
"https-proxy-agent": "5.0.1",
|
"https-proxy-agent": "5.0.1",
|
||||||
@ -163,6 +165,7 @@
|
|||||||
"node-schedule": "2.1.0",
|
"node-schedule": "2.1.0",
|
||||||
"open": "8.4.0",
|
"open": "8.4.0",
|
||||||
"ora": "5.4.1",
|
"ora": "5.4.1",
|
||||||
|
"outdent": "0.8.0",
|
||||||
"package-json": "7.0.0",
|
"package-json": "7.0.0",
|
||||||
"pkg-up": "3.1.0",
|
"pkg-up": "3.1.0",
|
||||||
"qs": "6.11.1",
|
"qs": "6.11.1",
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { RenderAdminArgs, renderAdmin, Store } from '@strapi/admin/strapi-admin';
|
import { RenderAdminArgs, renderAdmin, Store } from '@strapi/admin/strapi-admin';
|
||||||
// @ts-expect-error – No types, yet.
|
|
||||||
import contentTypeBuilder from '@strapi/plugin-content-type-builder/strapi-admin';
|
import contentTypeBuilder from '@strapi/plugin-content-type-builder/strapi-admin';
|
||||||
import email from '@strapi/plugin-email/strapi-admin';
|
import email from '@strapi/plugin-email/strapi-admin';
|
||||||
// @ts-expect-error – No types, yet.
|
// @ts-expect-error – No types, yet.
|
||||||
|
@ -2,15 +2,18 @@ import boxen from 'boxen';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { BuildCLIOptions, ConfigBundle, build } from '@strapi/pack-up';
|
import { BuildCLIOptions, ConfigBundle, build } from '@strapi/pack-up';
|
||||||
import { notifyExperimentalCommand } from '../../../utils/helpers';
|
import { notifyExperimentalCommand } from '../../../utils/helpers';
|
||||||
import { createLogger } from '../../../utils/logger';
|
|
||||||
import { Export, loadPkg, validatePkg } from '../../../utils/pkg';
|
import { Export, loadPkg, validatePkg } from '../../../utils/pkg';
|
||||||
|
import { CLIContext } from '../../../types';
|
||||||
|
|
||||||
interface ActionOptions extends BuildCLIOptions {
|
interface ActionOptions extends BuildCLIOptions {
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async ({ force, ...opts }: ActionOptions) => {
|
export default async (
|
||||||
const logger = createLogger({ debug: opts.debug, silent: opts.silent, timestamp: false });
|
{ force, ...opts }: ActionOptions,
|
||||||
|
_cmd: unknown,
|
||||||
|
{ logger, cwd }: CLIContext
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
/**
|
/**
|
||||||
* ALWAYS set production for using plugin build CLI.
|
* ALWAYS set production for using plugin build CLI.
|
||||||
@ -18,12 +21,10 @@ export default async ({ force, ...opts }: ActionOptions) => {
|
|||||||
process.env.NODE_ENV = 'production';
|
process.env.NODE_ENV = 'production';
|
||||||
/**
|
/**
|
||||||
* Notify users this is an experimental command and get them to approve first
|
* Notify users this is an experimental command and get them to approve first
|
||||||
* this can be opted out by setting the argument --yes
|
* this can be opted out by setting the argument --force
|
||||||
*/
|
*/
|
||||||
await notifyExperimentalCommand('plugin:build', { force });
|
await notifyExperimentalCommand('plugin:build', { force });
|
||||||
|
|
||||||
const cwd = process.cwd();
|
|
||||||
|
|
||||||
const pkg = await loadPkg({ cwd, logger });
|
const pkg = await loadPkg({ cwd, logger });
|
||||||
const pkgJson = await validatePkg({ pkg });
|
const pkgJson = await validatePkg({ pkg });
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import action from './action';
|
|||||||
/**
|
/**
|
||||||
* `$ strapi plugin:build`
|
* `$ strapi plugin:build`
|
||||||
*/
|
*/
|
||||||
const command: StrapiCommand = ({ command }) => {
|
const command: StrapiCommand = ({ command, ctx }) => {
|
||||||
command
|
command
|
||||||
.command('plugin:build')
|
.command('plugin:build')
|
||||||
.description('Bundle your strapi plugin for publishing.')
|
.description('Bundle your strapi plugin for publishing.')
|
||||||
@ -15,7 +15,7 @@ const command: StrapiCommand = ({ command }) => {
|
|||||||
.option('--silent', "Don't log anything", false)
|
.option('--silent', "Don't log anything", false)
|
||||||
.option('--sourcemap', 'produce sourcemaps', false)
|
.option('--sourcemap', 'produce sourcemaps', false)
|
||||||
.option('--minify', 'minify the output', false)
|
.option('--minify', 'minify the output', false)
|
||||||
.action(runAction('plugin:build', action));
|
.action((...args) => runAction('plugin:build', action)(...args, ctx));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default command;
|
export default command;
|
||||||
|
545
packages/core/strapi/src/commands/actions/plugin/init/action.ts
Normal file
545
packages/core/strapi/src/commands/actions/plugin/init/action.ts
Normal file
@ -0,0 +1,545 @@
|
|||||||
|
import path from 'node:path';
|
||||||
|
import boxen from 'boxen';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import getLatestVersion from 'get-latest-version';
|
||||||
|
import gitUrlParse from 'git-url-parse';
|
||||||
|
import {
|
||||||
|
InitOptions,
|
||||||
|
definePackageFeature,
|
||||||
|
definePackageOption,
|
||||||
|
defineTemplate,
|
||||||
|
init,
|
||||||
|
TemplateFile,
|
||||||
|
} from '@strapi/pack-up';
|
||||||
|
import { outdent } from 'outdent';
|
||||||
|
import { notifyExperimentalCommand } from '../../../utils/helpers';
|
||||||
|
|
||||||
|
import { CLIContext } from '../../../types';
|
||||||
|
import { gitIgnoreFile } from './files/gitIgnore';
|
||||||
|
|
||||||
|
interface ActionOptions extends Pick<InitOptions, 'silent' | 'debug'> {}
|
||||||
|
|
||||||
|
export default async (
|
||||||
|
packagePath: string,
|
||||||
|
{ silent, debug }: ActionOptions,
|
||||||
|
_cmd: unknown,
|
||||||
|
{ logger, cwd }: CLIContext
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Notify users this is an experimental command. We don't need to get them to approve first.
|
||||||
|
*/
|
||||||
|
await notifyExperimentalCommand('plugin:init', { force: true });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the package // plugin
|
||||||
|
*/
|
||||||
|
await init({
|
||||||
|
path: packagePath,
|
||||||
|
cwd,
|
||||||
|
silent,
|
||||||
|
debug,
|
||||||
|
template: PLUGIN_TEMPLATE,
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("Don't forget to enable your plugin in your configuration files.");
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(
|
||||||
|
'There seems to be an unexpected error, try again with --debug for more information \n'
|
||||||
|
);
|
||||||
|
if (err instanceof Error && err.stack) {
|
||||||
|
logger.log(
|
||||||
|
chalk.red(
|
||||||
|
boxen(err.stack, {
|
||||||
|
padding: 1,
|
||||||
|
align: 'left',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const PACKAGE_NAME_REGEXP = /^(?:@(?:[a-z0-9-*~][a-z0-9-*._~]*)\/)?[a-z0-9-~][a-z0-9-._~]*$/i;
|
||||||
|
|
||||||
|
interface PackageExport {
|
||||||
|
types?: string;
|
||||||
|
require: string;
|
||||||
|
import: string;
|
||||||
|
source: string;
|
||||||
|
default: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PluginPackageJson {
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
version?: string;
|
||||||
|
keywords?: string[];
|
||||||
|
type: 'commonjs';
|
||||||
|
license?: string;
|
||||||
|
repository?: {
|
||||||
|
type: 'git';
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
bugs?: {
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
homepage?: string;
|
||||||
|
author?: string;
|
||||||
|
exports: {
|
||||||
|
'./strapi-admin'?: PackageExport;
|
||||||
|
'./strapi-server'?: PackageExport;
|
||||||
|
'./package.json': `${string}.json`;
|
||||||
|
};
|
||||||
|
files: string[];
|
||||||
|
scripts: Record<string, string>;
|
||||||
|
dependencies: Record<string, string>;
|
||||||
|
devDependencies: Record<string, string>;
|
||||||
|
peerDependencies: Record<string, string>;
|
||||||
|
strapi: {
|
||||||
|
name?: string;
|
||||||
|
displayName?: string;
|
||||||
|
description?: string;
|
||||||
|
kind: 'plugin';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const PLUGIN_TEMPLATE = defineTemplate(async ({ logger, gitConfig, packagePath }) => {
|
||||||
|
let repo: {
|
||||||
|
source?: string;
|
||||||
|
owner?: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [packageFolder] = packagePath.split(path.sep).slice(-1);
|
||||||
|
|
||||||
|
return {
|
||||||
|
prompts: [
|
||||||
|
definePackageOption({
|
||||||
|
name: 'repo',
|
||||||
|
type: 'text',
|
||||||
|
message: 'git url',
|
||||||
|
validate(v) {
|
||||||
|
if (!v) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = gitUrlParse(v);
|
||||||
|
|
||||||
|
repo = { source: result.source, owner: result.owner, name: result.name };
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return 'invalid git url';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
definePackageOption({
|
||||||
|
name: 'pkgName',
|
||||||
|
type: 'text',
|
||||||
|
message: 'plugin name',
|
||||||
|
initial: () => repo?.name ?? '',
|
||||||
|
validate(v) {
|
||||||
|
if (!v) {
|
||||||
|
return 'package name is required';
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = PACKAGE_NAME_REGEXP.exec(v);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return 'invalid package name';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
definePackageOption({
|
||||||
|
name: 'displayName',
|
||||||
|
type: 'text',
|
||||||
|
message: 'plugin display name',
|
||||||
|
}),
|
||||||
|
definePackageOption({
|
||||||
|
name: 'description',
|
||||||
|
type: 'text',
|
||||||
|
message: 'plugin description',
|
||||||
|
}),
|
||||||
|
definePackageOption({
|
||||||
|
name: 'authorName',
|
||||||
|
type: 'text',
|
||||||
|
message: 'plugin author name',
|
||||||
|
initial: gitConfig?.user?.name,
|
||||||
|
}),
|
||||||
|
definePackageOption({
|
||||||
|
name: 'authorEmail',
|
||||||
|
type: 'text',
|
||||||
|
message: 'plugin author email',
|
||||||
|
initial: gitConfig?.user?.email,
|
||||||
|
}),
|
||||||
|
definePackageOption({
|
||||||
|
name: 'license',
|
||||||
|
type: 'text',
|
||||||
|
message: 'plugin license',
|
||||||
|
initial: 'MIT',
|
||||||
|
validate(v) {
|
||||||
|
if (!v) {
|
||||||
|
return 'license is required';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
definePackageOption({
|
||||||
|
name: 'client-code',
|
||||||
|
type: 'confirm',
|
||||||
|
message: 'register with the admin panel?',
|
||||||
|
initial: true,
|
||||||
|
}),
|
||||||
|
definePackageOption({
|
||||||
|
name: 'server-code',
|
||||||
|
type: 'confirm',
|
||||||
|
message: 'register with the server?',
|
||||||
|
initial: true,
|
||||||
|
}),
|
||||||
|
definePackageFeature({
|
||||||
|
name: 'editorconfig',
|
||||||
|
initial: true,
|
||||||
|
optional: true,
|
||||||
|
}),
|
||||||
|
definePackageFeature({
|
||||||
|
name: 'eslint',
|
||||||
|
initial: true,
|
||||||
|
optional: true,
|
||||||
|
}),
|
||||||
|
definePackageFeature({
|
||||||
|
name: 'prettier',
|
||||||
|
initial: true,
|
||||||
|
optional: true,
|
||||||
|
}),
|
||||||
|
definePackageFeature({
|
||||||
|
name: 'typescript',
|
||||||
|
initial: true,
|
||||||
|
optional: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
async getFiles(answers) {
|
||||||
|
const author: string[] = [];
|
||||||
|
|
||||||
|
const files: TemplateFile[] = [];
|
||||||
|
|
||||||
|
// package.json
|
||||||
|
const pkgJson: PluginPackageJson = {
|
||||||
|
version: '0.0.0',
|
||||||
|
keywords: [],
|
||||||
|
type: 'commonjs',
|
||||||
|
exports: {
|
||||||
|
'./package.json': './package.json',
|
||||||
|
},
|
||||||
|
files: ['dist'],
|
||||||
|
scripts: {
|
||||||
|
build: 'strapi plugin:build',
|
||||||
|
watch: 'strapi plugin:watch',
|
||||||
|
},
|
||||||
|
dependencies: {},
|
||||||
|
devDependencies: {
|
||||||
|
/**
|
||||||
|
* We set * as a default version, but further down
|
||||||
|
* we try to resolve each package to their latest
|
||||||
|
* version, failing that we leave the fallback of *.
|
||||||
|
*/
|
||||||
|
'@strapi/strapi': '*',
|
||||||
|
prettier: '*',
|
||||||
|
},
|
||||||
|
peerDependencies: {
|
||||||
|
'@strapi/strapi': '^4.0.0',
|
||||||
|
},
|
||||||
|
strapi: {
|
||||||
|
kind: 'plugin',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Array.isArray(answers)) {
|
||||||
|
for (const ans of answers) {
|
||||||
|
const { name, answer } = ans;
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case 'pkgName': {
|
||||||
|
pkgJson.name = String(answer);
|
||||||
|
pkgJson.strapi.name = String(answer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'description': {
|
||||||
|
pkgJson.description = String(answer) ?? undefined;
|
||||||
|
pkgJson.strapi.description = String(answer) ?? undefined;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'displayName': {
|
||||||
|
pkgJson.strapi.displayName = String(answer) ?? undefined;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'authorName': {
|
||||||
|
author.push(String(answer));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'authorEmail': {
|
||||||
|
if (answer) {
|
||||||
|
author.push(`<${answer}>`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'license': {
|
||||||
|
pkgJson.license = String(answer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'client-code': {
|
||||||
|
if (answer) {
|
||||||
|
pkgJson.exports['./strapi-admin'] = {
|
||||||
|
source: './src/admin/index.js',
|
||||||
|
import: './dist/admin/index.mjs',
|
||||||
|
require: './dist/admin/index.js',
|
||||||
|
default: './dist/admin/index.js',
|
||||||
|
};
|
||||||
|
|
||||||
|
pkgJson.dependencies = {
|
||||||
|
...pkgJson.dependencies,
|
||||||
|
'@strapi/helper-plugin': '*',
|
||||||
|
'@strapi/design-system': '*',
|
||||||
|
'@strapi/icons': '*',
|
||||||
|
};
|
||||||
|
|
||||||
|
pkgJson.devDependencies = {
|
||||||
|
...pkgJson.dependencies,
|
||||||
|
react: '*',
|
||||||
|
'react-dom': '*',
|
||||||
|
'react-router-dom': '5.3.4',
|
||||||
|
'styled-components': '5.3.3',
|
||||||
|
};
|
||||||
|
|
||||||
|
pkgJson.peerDependencies = {
|
||||||
|
...pkgJson.peerDependencies,
|
||||||
|
react: '^17.0.0 || ^18.0.0',
|
||||||
|
'react-dom': '^17.0.0 || ^18.0.0',
|
||||||
|
'react-router-dom': '5.2.0',
|
||||||
|
'styled-components': '5.2.1',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'server-code': {
|
||||||
|
if (answer) {
|
||||||
|
pkgJson.exports['./strapi-server'] = {
|
||||||
|
source: './src/server/index.js',
|
||||||
|
import: './dist/server/index.mjs',
|
||||||
|
require: './dist/server/index.js',
|
||||||
|
default: './dist/server/index.js',
|
||||||
|
};
|
||||||
|
|
||||||
|
pkgJson.files.push('./strapi-server.js');
|
||||||
|
|
||||||
|
files.push({
|
||||||
|
name: 'strapi-server.js',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = require('./dist/server');
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'typescript': {
|
||||||
|
const isTypescript = Boolean(answer);
|
||||||
|
|
||||||
|
if (isTypescript) {
|
||||||
|
if (isRecord(pkgJson.exports['./strapi-admin'])) {
|
||||||
|
pkgJson.exports['./strapi-admin'].source = './src/admin/index.ts';
|
||||||
|
|
||||||
|
pkgJson.exports['./strapi-admin'] = {
|
||||||
|
types: './dist/admin/src/index.d.ts',
|
||||||
|
...pkgJson.exports['./strapi-admin'],
|
||||||
|
};
|
||||||
|
|
||||||
|
pkgJson.scripts = {
|
||||||
|
...pkgJson.scripts,
|
||||||
|
'test:ts:front': 'run -T tsc -p admin/tsconfig.json',
|
||||||
|
};
|
||||||
|
|
||||||
|
pkgJson.devDependencies = {
|
||||||
|
...pkgJson.devDependencies,
|
||||||
|
'@types/react': '*',
|
||||||
|
'@types/react-dom': '*',
|
||||||
|
'@types/react-router-dom': '5.3.3',
|
||||||
|
'@types/styled-components': '5.1.26',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { adminTsconfigFiles } = await import('./files/typescript');
|
||||||
|
|
||||||
|
files.push(adminTsconfigFiles.tsconfigBuildFile, adminTsconfigFiles.tsconfigFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRecord(pkgJson.exports['./strapi-server'])) {
|
||||||
|
pkgJson.exports['./strapi-server'].source = './src/server/index.ts';
|
||||||
|
|
||||||
|
pkgJson.exports['./strapi-server'] = {
|
||||||
|
types: './dist/server/src/index.d.ts',
|
||||||
|
...pkgJson.exports['./strapi-server'],
|
||||||
|
};
|
||||||
|
|
||||||
|
pkgJson.scripts = {
|
||||||
|
...pkgJson.scripts,
|
||||||
|
'test:ts:back': 'run -T tsc -p server/tsconfig.json',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { serverTsconfigFiles } = await import('./files/typescript');
|
||||||
|
|
||||||
|
files.push(
|
||||||
|
serverTsconfigFiles.tsconfigBuildFile,
|
||||||
|
serverTsconfigFiles.tsconfigFile
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgJson.devDependencies = {
|
||||||
|
...pkgJson.devDependencies,
|
||||||
|
'@strapi/typescript-utils': '*',
|
||||||
|
typescript: '*',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is where we add all the source files regardless
|
||||||
|
* of whether they are typescript or javascript.
|
||||||
|
*/
|
||||||
|
if (isRecord(pkgJson.exports['./strapi-admin'])) {
|
||||||
|
files.push({
|
||||||
|
name: isTypescript ? 'admin/src/pluginId.ts' : 'admin/src/pluginId.js',
|
||||||
|
contents: outdent`
|
||||||
|
export const PLUGIN_ID = '${pkgJson.name!.replace(/^strapi-plugin-/i, '')}';
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isTypescript) {
|
||||||
|
const { adminTypescriptFiles } = await import('./files/admin');
|
||||||
|
|
||||||
|
files.push(...adminTypescriptFiles);
|
||||||
|
} else {
|
||||||
|
const { adminJavascriptFiles } = await import('./files/admin');
|
||||||
|
|
||||||
|
files.push(...adminJavascriptFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRecord(pkgJson.exports['./strapi-server'])) {
|
||||||
|
if (isTypescript) {
|
||||||
|
const { serverTypescriptFiles } = await import('./files/server');
|
||||||
|
|
||||||
|
files.push(...serverTypescriptFiles(packageFolder));
|
||||||
|
} else {
|
||||||
|
const { serverJavascriptFiles } = await import('./files/server');
|
||||||
|
|
||||||
|
files.push(...serverJavascriptFiles(packageFolder));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'eslint': {
|
||||||
|
if (answer) {
|
||||||
|
const { eslintIgnoreFile } = await import('./files/eslint');
|
||||||
|
|
||||||
|
files.push(eslintIgnoreFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'prettier': {
|
||||||
|
if (answer) {
|
||||||
|
const { prettierFile, prettierIgnoreFile } = await import('./files/prettier');
|
||||||
|
|
||||||
|
files.push(prettierFile, prettierIgnoreFile);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'editorconfig': {
|
||||||
|
if (answer) {
|
||||||
|
const { editorConfigFile } = await import('./files/editorConfig');
|
||||||
|
|
||||||
|
files.push(editorConfigFile);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repo) {
|
||||||
|
pkgJson.repository = {
|
||||||
|
type: 'git',
|
||||||
|
url: `git+ssh://git@${repo.source}/${repo.owner}/${repo.name}.git`,
|
||||||
|
};
|
||||||
|
pkgJson.bugs = {
|
||||||
|
url: `https://${repo.source}/${repo.owner}/${repo.name}/issues`,
|
||||||
|
};
|
||||||
|
pkgJson.homepage = `https://${repo.source}/${repo.owner}/${repo.name}#readme`;
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgJson.author = author.filter(Boolean).join(' ') ?? undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
pkgJson.devDependencies = await resolveLatestVerisonOfDeps(pkgJson.devDependencies);
|
||||||
|
pkgJson.dependencies = await resolveLatestVerisonOfDeps(pkgJson.dependencies);
|
||||||
|
pkgJson.peerDependencies = await resolveLatestVerisonOfDeps(pkgJson.peerDependencies);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
logger.error(err.message);
|
||||||
|
} else {
|
||||||
|
logger.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
files.push({
|
||||||
|
name: 'package.json',
|
||||||
|
contents: outdent`
|
||||||
|
${JSON.stringify(pkgJson, null, 2)}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
files.push({
|
||||||
|
name: 'README.md',
|
||||||
|
contents: outdent`
|
||||||
|
# ${pkgJson.name}
|
||||||
|
|
||||||
|
${pkgJson.description ?? ''}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
files.push(gitIgnoreFile);
|
||||||
|
|
||||||
|
return files;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||||
|
Boolean(value) && !Array.isArray(value) && typeof value === 'object';
|
||||||
|
|
||||||
|
const resolveLatestVerisonOfDeps = async (
|
||||||
|
deps: Record<string, string>
|
||||||
|
): Promise<Record<string, string>> => {
|
||||||
|
const latestDeps: Record<string, string> = {};
|
||||||
|
|
||||||
|
for (const [name, version] of Object.entries(deps)) {
|
||||||
|
try {
|
||||||
|
const latestVersion = await getLatestVersion(name, version);
|
||||||
|
latestDeps[name] = latestVersion ? `^${latestVersion}` : '*';
|
||||||
|
} catch (err) {
|
||||||
|
latestDeps[name] = '*';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return latestDeps;
|
||||||
|
};
|
@ -0,0 +1,18 @@
|
|||||||
|
import type { StrapiCommand } from '../../../types';
|
||||||
|
import { runAction } from '../../../utils/helpers';
|
||||||
|
import action from './action';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `$ strapi plugin:init`
|
||||||
|
*/
|
||||||
|
const command: StrapiCommand = ({ command, ctx }) => {
|
||||||
|
command
|
||||||
|
.command('plugin:init')
|
||||||
|
.description('Create a new plugin at a given path')
|
||||||
|
.argument('[path]', 'path to the plugin', './src/plugins/my-plugin')
|
||||||
|
.option('-d, --debug', 'Enable debugging mode with verbose logs', false)
|
||||||
|
.option('--silent', "Don't log anything", false)
|
||||||
|
.action((...args) => runAction('plugin:init', action)(...args, ctx));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default command;
|
@ -0,0 +1,286 @@
|
|||||||
|
import { TemplateFile } from '@strapi/pack-up';
|
||||||
|
import { outdent } from 'outdent';
|
||||||
|
|
||||||
|
const PLUGIN_ICON_CODE = outdent`
|
||||||
|
import { Puzzle } from '@strapi/icons';
|
||||||
|
|
||||||
|
const PluginIcon = () => <Puzzle />;
|
||||||
|
|
||||||
|
export { PluginIcon };
|
||||||
|
`;
|
||||||
|
|
||||||
|
const APP_CODE = outdent`
|
||||||
|
import { AnErrorOccurred } from '@strapi/helper-plugin';
|
||||||
|
import { Switch, Route } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { PLUGIN_ID } from '../pluginId';
|
||||||
|
|
||||||
|
import { HomePage } from './HomePage';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
<Route path={\`/plugins/\${PLUGIN_ID}\`} component={HomePage} exact />
|
||||||
|
<Route component={AnErrorOccurred} />
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { App };
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HOMEPAGE_CODE = outdent`
|
||||||
|
import { Main } from '@strapi/design-system';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { getTranslation } from '../utils/getTranslation';
|
||||||
|
|
||||||
|
const HomePage = () => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Main>
|
||||||
|
<h1>Welcome to {formatMessage({ id: getTranslation("plugin.name") })}</h1>
|
||||||
|
</Main>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { HomePage };
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TYPESCRIPT: TemplateFile[] = [
|
||||||
|
{
|
||||||
|
name: 'admin/src/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
||||||
|
import { PLUGIN_ID } from './pluginId';
|
||||||
|
import { Initializer } from './components/Initializer';
|
||||||
|
import { PluginIcon } from './components/PluginIcon';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
register(app: any) {
|
||||||
|
app.addMenuLink({
|
||||||
|
to: \`/plugins/\${PluginIcon}\`,
|
||||||
|
icon: PluginIcon,
|
||||||
|
intlLabel: {
|
||||||
|
id: \`\${PLUGIN_ID}.plugin.name\`,
|
||||||
|
defaultMessage: PLUGIN_ID,
|
||||||
|
},
|
||||||
|
Component: async () => {
|
||||||
|
const { App } = await import('./pages/App');
|
||||||
|
|
||||||
|
return App;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
app.registerPlugin({
|
||||||
|
id: PLUGIN_ID,
|
||||||
|
initializer: Initializer,
|
||||||
|
isReady: false,
|
||||||
|
name: PLUGIN_ID,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerTrads(app: any) {
|
||||||
|
const { locales } = app;
|
||||||
|
|
||||||
|
const importedTranslations = await Promise.all(
|
||||||
|
(locales as string[]).map((locale) => {
|
||||||
|
return import(\`./translations/\${locale}.json\`)
|
||||||
|
.then(({ default: data }) => {
|
||||||
|
return {
|
||||||
|
data: prefixPluginTranslations(data, PLUGIN_ID),
|
||||||
|
locale,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return {
|
||||||
|
data: {},
|
||||||
|
locale,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return importedTranslations;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin/src/components/PluginIcon.tsx',
|
||||||
|
contents: PLUGIN_ICON_CODE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin/src/components/Initializer.tsx',
|
||||||
|
contents: outdent`
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
import { PLUGIN_ID } from '../pluginId';
|
||||||
|
|
||||||
|
type InitializerProps = {
|
||||||
|
setPlugin: (id: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Initializer = ({ setPlugin }: InitializerProps) => {
|
||||||
|
const ref = useRef(setPlugin);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current(PLUGIN_ID);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Initializer };
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin/src/pages/App.tsx',
|
||||||
|
contents: APP_CODE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin/src/pages/HomePage.tsx',
|
||||||
|
contents: HOMEPAGE_CODE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin/src/utils/getTranslation.ts',
|
||||||
|
contents: outdent`
|
||||||
|
import { PLUGIN_ID } from '../pluginId';
|
||||||
|
|
||||||
|
const getTranslation = (id: string) => \`\${PLUGIN_ID}.\${id}\`;
|
||||||
|
|
||||||
|
export { getTranslation };
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin/src/translations/en.json',
|
||||||
|
contents: outdent`
|
||||||
|
{}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* TODO: remove this when we release design-system V2
|
||||||
|
*/
|
||||||
|
name: 'admin/custom.d.ts',
|
||||||
|
contents: outdent`
|
||||||
|
declare module '@strapi/design-system/*';
|
||||||
|
declare module '@strapi/design-system';
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const JAVASCRIPT: TemplateFile[] = [
|
||||||
|
{
|
||||||
|
name: 'admin/src/index.js',
|
||||||
|
contents: outdent`
|
||||||
|
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
||||||
|
import { PLUGIN_ID } from './pluginId';
|
||||||
|
import { Initializer } from './components/Initializer';
|
||||||
|
import { PluginIcon } from './components/PluginIcon';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
register(app) {
|
||||||
|
app.addMenuLink({
|
||||||
|
to: \`/plugins/\${PluginIcon}\`,
|
||||||
|
icon: PluginIcon,
|
||||||
|
intlLabel: {
|
||||||
|
id: \`\${PLUGIN_ID}.plugin.name\`,
|
||||||
|
defaultMessage: PLUGIN_ID,
|
||||||
|
},
|
||||||
|
Component: async () => {
|
||||||
|
const { App } = await import('./pages/App');
|
||||||
|
|
||||||
|
return App;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
app.registerPlugin({
|
||||||
|
id: PLUGIN_ID,
|
||||||
|
initializer: Initializer,
|
||||||
|
isReady: false,
|
||||||
|
name: PLUGIN_ID,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerTrads(app) {
|
||||||
|
const { locales } = app;
|
||||||
|
|
||||||
|
const importedTranslations = await Promise.all(
|
||||||
|
locales.map((locale) => {
|
||||||
|
return import(\`./translations/\${locale}.json\`)
|
||||||
|
.then(({ default: data }) => {
|
||||||
|
return {
|
||||||
|
data: prefixPluginTranslations(data, PLUGIN_ID),
|
||||||
|
locale,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return {
|
||||||
|
data: {},
|
||||||
|
locale,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return importedTranslations;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin/src/components/PluginIcon.jsx',
|
||||||
|
contents: PLUGIN_ICON_CODE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin/src/components/Initializer.jsx',
|
||||||
|
contents: outdent`
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
import { PLUGIN_ID } from '../pluginId';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import('react').FC<{ setPlugin: (id: string) => void }>}
|
||||||
|
*/
|
||||||
|
const Initializer = ({ setPlugin }) => {
|
||||||
|
const ref = useRef(setPlugin);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current(PLUGIN_ID);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Initializer };
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin/src/pages/App.jsx',
|
||||||
|
contents: APP_CODE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin/src/pages/HomePage.jsx',
|
||||||
|
contents: HOMEPAGE_CODE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin/src/utils/getTranslation.js',
|
||||||
|
contents: outdent`
|
||||||
|
import { PLUGIN_ID } from '../pluginId';
|
||||||
|
|
||||||
|
const getTranslation = (id) => \`\${PLUGIN_ID}.\${id}\`;
|
||||||
|
|
||||||
|
export { getTranslation };
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin/src/translations/en.json',
|
||||||
|
contents: outdent`
|
||||||
|
{}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export { TYPESCRIPT as adminTypescriptFiles, JAVASCRIPT as adminJavascriptFiles };
|
@ -0,0 +1,26 @@
|
|||||||
|
import { TemplateFile } from '@strapi/pack-up';
|
||||||
|
import { outdent } from 'outdent';
|
||||||
|
|
||||||
|
const editorConfigFile: TemplateFile = {
|
||||||
|
name: '.editorconfig',
|
||||||
|
contents: outdent`
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[{package.json,*.yml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { editorConfigFile };
|
@ -0,0 +1,11 @@
|
|||||||
|
import { TemplateFile } from '@strapi/pack-up';
|
||||||
|
import { outdent } from 'outdent';
|
||||||
|
|
||||||
|
const eslintIgnoreFile: TemplateFile = {
|
||||||
|
name: '.eslintignore',
|
||||||
|
contents: outdent`
|
||||||
|
dist
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { eslintIgnoreFile };
|
@ -0,0 +1,34 @@
|
|||||||
|
import { TemplateFile } from '@strapi/pack-up';
|
||||||
|
import { outdent } from 'outdent';
|
||||||
|
|
||||||
|
const gitIgnoreFile: TemplateFile = {
|
||||||
|
name: '.gitignore',
|
||||||
|
contents: outdent`
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules
|
||||||
|
.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
dist
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { gitIgnoreFile };
|
@ -0,0 +1,25 @@
|
|||||||
|
import { TemplateFile } from '@strapi/pack-up';
|
||||||
|
import { outdent } from 'outdent';
|
||||||
|
|
||||||
|
const prettierFile: TemplateFile = {
|
||||||
|
name: '.prettierrc',
|
||||||
|
contents: outdent`
|
||||||
|
{
|
||||||
|
"endOfLine": 'lf',
|
||||||
|
"tabWidth": 2,
|
||||||
|
"printWidth": 100,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": 'es5',
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const prettierIgnoreFile: TemplateFile = {
|
||||||
|
name: '.prettierignore',
|
||||||
|
contents: outdent`
|
||||||
|
dist
|
||||||
|
coverage
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { prettierFile, prettierIgnoreFile };
|
@ -0,0 +1,360 @@
|
|||||||
|
import { TemplateFile } from '@strapi/pack-up';
|
||||||
|
import { outdent } from 'outdent';
|
||||||
|
|
||||||
|
const TYPESCRIPT = (pluginName: string): TemplateFile[] => [
|
||||||
|
{
|
||||||
|
name: 'server/src/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
/**
|
||||||
|
* Application methods
|
||||||
|
*/
|
||||||
|
import bootstrap from './bootstrap';
|
||||||
|
import destroy from './destroy';
|
||||||
|
import register from './register';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin server methods
|
||||||
|
*/
|
||||||
|
import config from './config';
|
||||||
|
import contentTypes from './content-types';
|
||||||
|
import controllers from './controllers';
|
||||||
|
import middlewares from './middlewares';
|
||||||
|
import policies from './policies';
|
||||||
|
import routes from './routes';
|
||||||
|
import services from './services';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
bootstrap,
|
||||||
|
destroy,
|
||||||
|
register,
|
||||||
|
|
||||||
|
config,
|
||||||
|
controllers,
|
||||||
|
contentTypes,
|
||||||
|
middlewares,
|
||||||
|
policies,
|
||||||
|
routes,
|
||||||
|
services,
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/bootstrap.ts',
|
||||||
|
contents: outdent`
|
||||||
|
import type { Strapi } from '@strapi/strapi';
|
||||||
|
|
||||||
|
const bootstrap = ({ strapi }: { strapi: Strapi }) => {
|
||||||
|
// bootstrap phase
|
||||||
|
};
|
||||||
|
|
||||||
|
export default bootstrap;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/destroy.ts',
|
||||||
|
contents: outdent`
|
||||||
|
import type { Strapi } from '@strapi/strapi';
|
||||||
|
|
||||||
|
const destroy = ({ strapi }: { strapi: Strapi }) => {
|
||||||
|
// destroy phase
|
||||||
|
};
|
||||||
|
|
||||||
|
export default destroy;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/register.ts',
|
||||||
|
contents: outdent`
|
||||||
|
import type { Strapi } from '@strapi/strapi';
|
||||||
|
|
||||||
|
const register = ({ strapi }: { strapi: Strapi }) => {
|
||||||
|
// register phase
|
||||||
|
};
|
||||||
|
|
||||||
|
export default register;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/config/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
export default {
|
||||||
|
default: {},
|
||||||
|
validator() {},
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/content-types/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
export default {};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/controllers/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
import controller from './controller';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
controller,
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/controllers/controller.ts',
|
||||||
|
contents: outdent`
|
||||||
|
import type { Strapi } from '@strapi/strapi';
|
||||||
|
|
||||||
|
const controller = ({ strapi }: { strapi: Strapi }) => ({
|
||||||
|
index(ctx) {
|
||||||
|
ctx.body = strapi
|
||||||
|
.plugin('${pluginName}')
|
||||||
|
// the name of the service file & the method.
|
||||||
|
.service('service')
|
||||||
|
.getWelcomeMessage();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default controller
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/middlewares/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
export default {};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/policies/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
export default {};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/routes/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
path: '/',
|
||||||
|
// name of the controller file & the method.
|
||||||
|
handler: 'controller.index',
|
||||||
|
config: {
|
||||||
|
policies: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/services/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
import service from './service';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
service,
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/services/service.ts',
|
||||||
|
contents: outdent`
|
||||||
|
import type { Strapi } from '@strapi/strapi';
|
||||||
|
|
||||||
|
const service = ({ strapi }: { strapi: Strapi }) => ({
|
||||||
|
getWelcomeMessage() {
|
||||||
|
return 'Welcome to Strapi 🚀';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default service
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const JAVASCRIPT = (pluginName: string): TemplateFile[] => [
|
||||||
|
{
|
||||||
|
name: 'server/src/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application methods
|
||||||
|
*/
|
||||||
|
const bootstrap = require('./bootstrap');
|
||||||
|
const destroy = require('./destroy');
|
||||||
|
const register = require('./register');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin server methods
|
||||||
|
*/
|
||||||
|
const config = require('./config');
|
||||||
|
const contentTypes = require('./content-types');
|
||||||
|
const controllers = require('./controllers');
|
||||||
|
const middlewares = require('./middlewares');
|
||||||
|
const policies = require('./policies');
|
||||||
|
const routes = require('./routes');
|
||||||
|
const services = require('./services');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
bootstrap,
|
||||||
|
destroy,
|
||||||
|
register,
|
||||||
|
|
||||||
|
config,
|
||||||
|
controllers,
|
||||||
|
contentTypes,
|
||||||
|
middlewares,
|
||||||
|
policies,
|
||||||
|
routes,
|
||||||
|
services,
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/bootstrap.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const bootstrap = ({ strapi }) => {
|
||||||
|
// bootstrap phase
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = bootstrap;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/destroy.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const destroy = ({ strapi }) => {
|
||||||
|
// destroy phase
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = destroy;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/register.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const register = ({ strapi }) => {
|
||||||
|
// register phase
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = register;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/config/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
default: {},
|
||||||
|
validator() {},
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/content-types/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/controllers/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const controller = require('./controller');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
controller,
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/controllers/controller.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const controller = ({ strapi }) => ({
|
||||||
|
index(ctx) {
|
||||||
|
ctx.body = strapi
|
||||||
|
.plugin('${pluginName}')
|
||||||
|
// the name of the service file & the method.
|
||||||
|
.service('service')
|
||||||
|
.getWelcomeMessage();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = controller
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/middlewares/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/policies/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/routes/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
path: '/',
|
||||||
|
// name of the controller file & the method.
|
||||||
|
handler: 'controller.index',
|
||||||
|
config: {
|
||||||
|
policies: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/services/index.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const service = require('./service');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
service,
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server/src/services/service.ts',
|
||||||
|
contents: outdent`
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const service = ({ strapi }) => ({
|
||||||
|
getWelcomeMessage() {
|
||||||
|
return 'Welcome to Strapi 🚀';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = service
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export { TYPESCRIPT as serverTypescriptFiles, JAVASCRIPT as serverJavascriptFiles };
|
@ -0,0 +1,71 @@
|
|||||||
|
import { TemplateFile } from '@strapi/pack-up';
|
||||||
|
import { outdent } from 'outdent';
|
||||||
|
|
||||||
|
interface TsConfigFiles {
|
||||||
|
tsconfigFile: TemplateFile;
|
||||||
|
tsconfigBuildFile: TemplateFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ADMIN: TsConfigFiles = {
|
||||||
|
tsconfigFile: {
|
||||||
|
name: 'admin/tsconfig.json',
|
||||||
|
contents: outdent`
|
||||||
|
{
|
||||||
|
"extends": "@strapi/typescript-utils/tsconfigs/admin",
|
||||||
|
"include": ["./src", "./custom.d.ts"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "../",
|
||||||
|
"baseUrl": ".",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
tsconfigBuildFile: {
|
||||||
|
name: 'admin/tsconfig.build.json',
|
||||||
|
contents: outdent`
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig",
|
||||||
|
"include": ["./src", "./custom.d.ts"],
|
||||||
|
"exclude": ["**/*.test.ts", "**/*.test.tsx"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "../",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "./dist",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const SERVER: TsConfigFiles = {
|
||||||
|
tsconfigFile: {
|
||||||
|
name: 'server/tsconfig.json',
|
||||||
|
contents: outdent`
|
||||||
|
{
|
||||||
|
"extends": "@strapi/typescript-utils/tsconfigs/server",
|
||||||
|
"include": ["./src"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "../",
|
||||||
|
"baseUrl": ".",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
tsconfigBuildFile: {
|
||||||
|
name: 'server/tsconfig.build.json',
|
||||||
|
contents: outdent`
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig",
|
||||||
|
"include": ["./src"],
|
||||||
|
"exclude": ["**/*.test.ts"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "../",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "./dist",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ADMIN as adminTsconfigFiles, SERVER as serverTsconfigFiles };
|
@ -2,23 +2,17 @@ import boxen from 'boxen';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { ConfigBundle, WatchCLIOptions, watch } from '@strapi/pack-up';
|
import { ConfigBundle, WatchCLIOptions, watch } from '@strapi/pack-up';
|
||||||
import { notifyExperimentalCommand } from '../../../utils/helpers';
|
import { notifyExperimentalCommand } from '../../../utils/helpers';
|
||||||
import { createLogger } from '../../../utils/logger';
|
|
||||||
import { Export, loadPkg, validatePkg } from '../../../utils/pkg';
|
import { Export, loadPkg, validatePkg } from '../../../utils/pkg';
|
||||||
|
import { CLIContext } from '../../../types';
|
||||||
|
|
||||||
interface ActionOptions extends WatchCLIOptions {
|
interface ActionOptions extends WatchCLIOptions {}
|
||||||
force?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async ({ force, ...opts }: ActionOptions) => {
|
export default async (opts: ActionOptions, _cmd: unknown, { cwd, logger }: CLIContext) => {
|
||||||
const logger = createLogger({ debug: opts.debug, silent: opts.silent, timestamp: false });
|
|
||||||
try {
|
try {
|
||||||
/**
|
/**
|
||||||
* Notify users this is an experimental command and get them to approve first
|
* Notify users this is an experimental command.
|
||||||
* this can be opted out by setting the argument --yes
|
|
||||||
*/
|
*/
|
||||||
await notifyExperimentalCommand('plugin:watch', { force });
|
await notifyExperimentalCommand('plugin:watch', { force: true });
|
||||||
|
|
||||||
const cwd = process.cwd();
|
|
||||||
|
|
||||||
const pkg = await loadPkg({ cwd, logger });
|
const pkg = await loadPkg({ cwd, logger });
|
||||||
const pkgJson = await validatePkg({ pkg });
|
const pkgJson = await validatePkg({ pkg });
|
||||||
|
@ -3,15 +3,15 @@ import { runAction } from '../../../utils/helpers';
|
|||||||
import action from './action';
|
import action from './action';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `$ strapi plugin:build`
|
* `$ strapi plugin:watch`
|
||||||
*/
|
*/
|
||||||
const command: StrapiCommand = ({ command }) => {
|
const command: StrapiCommand = ({ command, ctx }) => {
|
||||||
command
|
command
|
||||||
.command('plugin:watch')
|
.command('plugin:watch')
|
||||||
.description('Watch & compile your strapi plugin for local development.')
|
.description('Watch & compile your strapi plugin for local development.')
|
||||||
.option('-d, --debug', 'Enable debugging mode with verbose logs', false)
|
.option('-d, --debug', 'Enable debugging mode with verbose logs', false)
|
||||||
.option('--silent', "Don't log anything", false)
|
.option('--silent', "Don't log anything", false)
|
||||||
.action(runAction('plugin:watch', action));
|
.action((...args) => runAction('plugin:watch', action)(...args, ctx));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default command;
|
export default command;
|
||||||
|
@ -27,6 +27,7 @@ import versionCommand from './actions/version/command';
|
|||||||
import watchAdminCommand from './actions/watch-admin/command';
|
import watchAdminCommand from './actions/watch-admin/command';
|
||||||
|
|
||||||
import buildPluginCommand from './actions/plugin/build-command/command';
|
import buildPluginCommand from './actions/plugin/build-command/command';
|
||||||
|
import initPluginCommand from './actions/plugin/init/command';
|
||||||
import watchPluginCommand from './actions/plugin/watch/command';
|
import watchPluginCommand from './actions/plugin/watch/command';
|
||||||
|
|
||||||
import { createLogger } from './utils/logger';
|
import { createLogger } from './utils/logger';
|
||||||
@ -63,6 +64,7 @@ const strapiCommands = {
|
|||||||
* Plugins
|
* Plugins
|
||||||
*/
|
*/
|
||||||
buildPluginCommand,
|
buildPluginCommand,
|
||||||
|
initPluginCommand,
|
||||||
watchPluginCommand,
|
watchPluginCommand,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "tsconfig/base.json",
|
"extends": "tsconfig/base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"noEmit": true
|
"noEmit": true,
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"module": "ESNext"
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
@ -19,4 +19,5 @@ export type {
|
|||||||
TemplateOrTemplateResolver,
|
TemplateOrTemplateResolver,
|
||||||
TemplateFeature,
|
TemplateFeature,
|
||||||
TemplateOption,
|
TemplateOption,
|
||||||
|
TemplateFile,
|
||||||
} from './node/templates/types';
|
} from './node/templates/types';
|
||||||
|
@ -58,3 +58,4 @@ const parseGlobalGitConfig = async (): Promise<GitConfig | null> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export { parseGlobalGitConfig };
|
export { parseGlobalGitConfig };
|
||||||
|
export type { GitConfig };
|
||||||
|
@ -5,6 +5,7 @@ import prettier, { Config as PrettierConfig } from 'prettier';
|
|||||||
import prompts from 'prompts';
|
import prompts from 'prompts';
|
||||||
|
|
||||||
import { isError } from '../core/errors';
|
import { isError } from '../core/errors';
|
||||||
|
import { parseGlobalGitConfig } from '../core/git';
|
||||||
import { Logger } from '../core/logger';
|
import { Logger } from '../core/logger';
|
||||||
|
|
||||||
import { Template, TemplateFeature, TemplateOption, TemplateOrTemplateResolver } from './types';
|
import { Template, TemplateFeature, TemplateOption, TemplateOrTemplateResolver } from './types';
|
||||||
@ -27,9 +28,11 @@ const createPackageFromTemplate = async (
|
|||||||
) => {
|
) => {
|
||||||
const { cwd, logger, template: templateOrResolver } = opts;
|
const { cwd, logger, template: templateOrResolver } = opts;
|
||||||
|
|
||||||
|
const gitConfig = await parseGlobalGitConfig();
|
||||||
|
|
||||||
const template =
|
const template =
|
||||||
typeof templateOrResolver === 'function'
|
typeof templateOrResolver === 'function'
|
||||||
? await templateOrResolver({ cwd, logger, packagePath })
|
? await templateOrResolver({ cwd, logger, packagePath, gitConfig })
|
||||||
: templateOrResolver;
|
: templateOrResolver;
|
||||||
|
|
||||||
logger.info('Creating a new package at: ', relative(cwd, packagePath));
|
logger.info('Creating a new package at: ', relative(cwd, packagePath));
|
||||||
|
@ -3,7 +3,6 @@ import gitUrlParse from 'git-url-parse';
|
|||||||
import { outdent } from 'outdent';
|
import { outdent } from 'outdent';
|
||||||
|
|
||||||
import { isError } from '../../core/errors';
|
import { isError } from '../../core/errors';
|
||||||
import { parseGlobalGitConfig } from '../../core/git';
|
|
||||||
import { PackageJson } from '../../core/pkg';
|
import { PackageJson } from '../../core/pkg';
|
||||||
import { definePackageFeature, definePackageOption, defineTemplate } from '../create';
|
import { definePackageFeature, definePackageOption, defineTemplate } from '../create';
|
||||||
import { TemplateFile } from '../types';
|
import { TemplateFile } from '../types';
|
||||||
@ -14,9 +13,7 @@ import { prettierFile, prettierIgnoreFile } from './files/prettier';
|
|||||||
|
|
||||||
const PACKAGE_NAME_REGEXP = /^(?:@(?:[a-z0-9-*~][a-z0-9-*._~]*)\/)?[a-z0-9-~][a-z0-9-._~]*$/i;
|
const PACKAGE_NAME_REGEXP = /^(?:@(?:[a-z0-9-*~][a-z0-9-*._~]*)\/)?[a-z0-9-~][a-z0-9-._~]*$/i;
|
||||||
|
|
||||||
const defaultTemplate = defineTemplate(async ({ logger }) => {
|
const defaultTemplate = defineTemplate(async ({ logger, gitConfig }) => {
|
||||||
const gitConfig = await parseGlobalGitConfig();
|
|
||||||
|
|
||||||
let repo: {
|
let repo: {
|
||||||
source?: string;
|
source?: string;
|
||||||
owner?: string;
|
owner?: string;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { PromptObject } from 'prompts';
|
import { PromptObject } from 'prompts';
|
||||||
|
|
||||||
|
import { GitConfig } from '../core/git';
|
||||||
import { Logger } from '../core/logger';
|
import { Logger } from '../core/logger';
|
||||||
|
|
||||||
interface TemplateFeature<T extends string = string> extends Pick<PromptObject<T>, 'initial'> {
|
interface TemplateFeature<T extends string = string> extends Pick<PromptObject<T>, 'initial'> {
|
||||||
@ -45,6 +46,7 @@ interface Template {
|
|||||||
|
|
||||||
interface TemplateContext {
|
interface TemplateContext {
|
||||||
cwd: string;
|
cwd: string;
|
||||||
|
gitConfig: GitConfig | null;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
packagePath: string;
|
packagePath: string;
|
||||||
}
|
}
|
||||||
|
@ -8476,6 +8476,8 @@ __metadata:
|
|||||||
eslint-config-custom: "npm:4.15.5"
|
eslint-config-custom: "npm:4.15.5"
|
||||||
execa: "npm:5.1.1"
|
execa: "npm:5.1.1"
|
||||||
fs-extra: "npm:10.0.0"
|
fs-extra: "npm:10.0.0"
|
||||||
|
get-latest-version: "npm:5.1.0"
|
||||||
|
git-url-parse: "npm:13.1.0"
|
||||||
glob: "npm:7.2.3"
|
glob: "npm:7.2.3"
|
||||||
http-errors: "npm:1.8.1"
|
http-errors: "npm:1.8.1"
|
||||||
https-proxy-agent: "npm:5.0.1"
|
https-proxy-agent: "npm:5.0.1"
|
||||||
@ -8497,6 +8499,7 @@ __metadata:
|
|||||||
node-schedule: "npm:2.1.0"
|
node-schedule: "npm:2.1.0"
|
||||||
open: "npm:8.4.0"
|
open: "npm:8.4.0"
|
||||||
ora: "npm:5.4.1"
|
ora: "npm:5.4.1"
|
||||||
|
outdent: "npm:0.8.0"
|
||||||
package-json: "npm:7.0.0"
|
package-json: "npm:7.0.0"
|
||||||
pkg-up: "npm:3.1.0"
|
pkg-up: "npm:3.1.0"
|
||||||
qs: "npm:6.11.1"
|
qs: "npm:6.11.1"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user