@strapi/generators 4.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +18 -0
- package/lib/files/plugin/admin/src/components/Initializer/index.js +26 -0
- package/lib/files/plugin/admin/src/containers/App/index.js +25 -0
- package/lib/files/plugin/admin/src/containers/HomePage/index.js +20 -0
- package/lib/files/plugin/admin/src/containers/Initializer/index.js +26 -0
- package/lib/files/plugin/admin/src/index.js +65 -0
- package/lib/files/plugin/admin/src/pages/App/index.js +25 -0
- package/lib/files/plugin/admin/src/pages/HomePage/index.js +20 -0
- package/lib/files/plugin/admin/src/pluginId.js +5 -0
- package/lib/files/plugin/admin/src/translations/en.json +1 -0
- package/lib/files/plugin/admin/src/translations/fr.json +1 -0
- package/lib/files/plugin/admin/src/utils/getTrad.js +5 -0
- package/lib/index.js +37 -0
- package/lib/plopfile.js +24 -0
- package/lib/plops/api.js +108 -0
- package/lib/plops/content-type.js +174 -0
- package/lib/plops/controller.js +32 -0
- package/lib/plops/plugin.js +50 -0
- package/lib/plops/policy.js +36 -0
- package/lib/plops/service.js +29 -0
- package/lib/plops/utils/get-destination-prompts.js +69 -0
- package/lib/plops/utils/get-file-path.js +13 -0
- package/lib/plops/utils/validate-input.js +11 -0
- package/lib/templates/README.md.hbs +3 -0
- package/lib/templates/collection-type-routes.js.hbs +44 -0
- package/lib/templates/controller.js.hbs +15 -0
- package/lib/templates/model.schema.json.hbs +16 -0
- package/lib/templates/plugin-package.json.hbs +28 -0
- package/lib/templates/plugin-routes.json.hbs +12 -0
- package/lib/templates/policy.js.hbs +18 -0
- package/lib/templates/service.js.hbs +7 -0
- package/lib/templates/single-type-routes.js.hbs +28 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2015-present Strapi Solutions SAS
|
|
2
|
+
|
|
3
|
+
Portions of the Strapi software are licensed as follows:
|
|
4
|
+
|
|
5
|
+
* All software that resides under an "ee/" directory (the “EE Software”), if that directory exists, is licensed under the license defined in "ee/LICENSE".
|
|
6
|
+
|
|
7
|
+
* All software outside of the above-mentioned directories or restrictions above is available under the "MIT Expat" license as set forth below.
|
|
8
|
+
|
|
9
|
+
MIT Expat License
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @strapi/generators
|
|
2
|
+
|
|
3
|
+
This package contains strapi code generators available through the CLI or programmatically.
|
|
4
|
+
|
|
5
|
+
## API Reference
|
|
6
|
+
|
|
7
|
+
### `runCLI()`
|
|
8
|
+
|
|
9
|
+
Start the generator CLI.
|
|
10
|
+
|
|
11
|
+
### `generate(generatorName, options, plopOptions)`
|
|
12
|
+
|
|
13
|
+
Execute a generator without interactive mode.
|
|
14
|
+
|
|
15
|
+
- `generatorName` - one of `api`, `controller`, `service`, `model`, `plugin`, `policy`.
|
|
16
|
+
- `options` - options are specific to each generator
|
|
17
|
+
- `plopOtions`
|
|
18
|
+
- `dir`: base directory that plop will use as base directory for its actions
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Initializer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useEffect, useRef } from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import pluginId from '../../pluginId';
|
|
10
|
+
|
|
11
|
+
const Initializer = ({ setPlugin }) => {
|
|
12
|
+
const ref = useRef();
|
|
13
|
+
ref.current = setPlugin;
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
ref.current(pluginId);
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
Initializer.propTypes = {
|
|
23
|
+
setPlugin: PropTypes.func.isRequired,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default Initializer;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* This component is the skeleton around the actual pages, and should only
|
|
4
|
+
* contain code that should be seen on all pages. (e.g. navigation bar)
|
|
5
|
+
*
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Switch, Route } from 'react-router-dom';
|
|
10
|
+
import { NotFound } from '@strapi/helper-plugin';
|
|
11
|
+
import pluginId from '../../pluginId';
|
|
12
|
+
import HomePage from '../HomePage';
|
|
13
|
+
|
|
14
|
+
const App = () => {
|
|
15
|
+
return (
|
|
16
|
+
<div>
|
|
17
|
+
<Switch>
|
|
18
|
+
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
|
19
|
+
<Route component={NotFound} />
|
|
20
|
+
</Switch>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default App;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
*
|
|
3
|
+
* HomePage
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { memo } from 'react';
|
|
8
|
+
// import PropTypes from 'prop-types';
|
|
9
|
+
import pluginId from '../../pluginId';
|
|
10
|
+
|
|
11
|
+
const HomePage = () => {
|
|
12
|
+
return (
|
|
13
|
+
<div>
|
|
14
|
+
<h1>{pluginId}'s HomePage</h1>
|
|
15
|
+
<p>Happy coding</p>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default memo(HomePage);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Initializer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useEffect, useRef } from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import pluginId from '../../pluginId';
|
|
10
|
+
|
|
11
|
+
const Initializer = ({ setPlugin }) => {
|
|
12
|
+
const ref = useRef();
|
|
13
|
+
ref.current = setPlugin;
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
ref.current(pluginId);
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
Initializer.propTypes = {
|
|
23
|
+
setPlugin: PropTypes.func.isRequired,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default Initializer;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
|
2
|
+
import pluginPkg from '../../package.json';
|
|
3
|
+
import pluginId from './pluginId';
|
|
4
|
+
import Initializer from './components/Initializer';
|
|
5
|
+
|
|
6
|
+
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
|
|
7
|
+
const icon = pluginPkg.strapi.icon;
|
|
8
|
+
const name = pluginPkg.strapi.name;
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
register(app) {
|
|
12
|
+
app.addMenuLink({
|
|
13
|
+
to: `/plugins/${pluginId}`,
|
|
14
|
+
icon,
|
|
15
|
+
intlLabel: {
|
|
16
|
+
id: `${pluginId}.plugin.name`,
|
|
17
|
+
defaultMessage: name,
|
|
18
|
+
},
|
|
19
|
+
Component: async () => {
|
|
20
|
+
const component = await import(/* webpackChunkName: "[request]" */ './pages/App');
|
|
21
|
+
|
|
22
|
+
return component;
|
|
23
|
+
},
|
|
24
|
+
permissions: [
|
|
25
|
+
// Uncomment to set the permissions of the plugin here
|
|
26
|
+
// {
|
|
27
|
+
// action: '', // the action name should be plugin::plugin-name.actionType
|
|
28
|
+
// subject: null,
|
|
29
|
+
// },
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
app.registerPlugin({
|
|
33
|
+
description: pluginDescription,
|
|
34
|
+
icon,
|
|
35
|
+
id: pluginId,
|
|
36
|
+
initializer: Initializer,
|
|
37
|
+
isReady: false,
|
|
38
|
+
isRequired: pluginPkg.strapi.required || false,
|
|
39
|
+
name,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
bootstrap(app) {},
|
|
44
|
+
async registerTrads({ locales }) {
|
|
45
|
+
const importedTrads = await Promise.all(
|
|
46
|
+
locales.map(locale => {
|
|
47
|
+
return import(`./translations/${locale}.json`)
|
|
48
|
+
.then(({ default: data }) => {
|
|
49
|
+
return {
|
|
50
|
+
data: prefixPluginTranslations(data, pluginId),
|
|
51
|
+
locale,
|
|
52
|
+
};
|
|
53
|
+
})
|
|
54
|
+
.catch(() => {
|
|
55
|
+
return {
|
|
56
|
+
data: {},
|
|
57
|
+
locale,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return Promise.resolve(importedTrads);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* This component is the skeleton around the actual pages, and should only
|
|
4
|
+
* contain code that should be seen on all pages. (e.g. navigation bar)
|
|
5
|
+
*
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Switch, Route } from 'react-router-dom';
|
|
10
|
+
import { NotFound } from '@strapi/helper-plugin';
|
|
11
|
+
import pluginId from '../../pluginId';
|
|
12
|
+
import HomePage from '../HomePage';
|
|
13
|
+
|
|
14
|
+
const App = () => {
|
|
15
|
+
return (
|
|
16
|
+
<div>
|
|
17
|
+
<Switch>
|
|
18
|
+
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
|
19
|
+
<Route component={NotFound} />
|
|
20
|
+
</Switch>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default App;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
*
|
|
3
|
+
* HomePage
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { memo } from 'react';
|
|
8
|
+
// import PropTypes from 'prop-types';
|
|
9
|
+
import pluginId from '../../pluginId';
|
|
10
|
+
|
|
11
|
+
const HomePage = () => {
|
|
12
|
+
return (
|
|
13
|
+
<div>
|
|
14
|
+
<h1>{pluginId}'s HomePage</h1>
|
|
15
|
+
<p>Happy coding</p>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default memo(HomePage);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { join } = require('path');
|
|
4
|
+
const { Plop, run } = require('plop');
|
|
5
|
+
const nodePlop = require('node-plop');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Starts the Plop CLI programmatically
|
|
9
|
+
*/
|
|
10
|
+
const runCLI = () => {
|
|
11
|
+
Plop.launch({ configPath: join(__dirname, 'plopfile.js') }, env =>
|
|
12
|
+
run({ ...env, dest: join(process.cwd(), 'src') }, undefined, true)
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Runs a generator programmatically without prompts
|
|
18
|
+
* @param {string} generatorName
|
|
19
|
+
* @param {Object} options generator options replacing the prompts answers
|
|
20
|
+
* @param {Object} plopOptions
|
|
21
|
+
* @param {string} plopOptions.dir base path for plop to generate the files from
|
|
22
|
+
*/
|
|
23
|
+
const generate = async (generatorName, options, { dir = process.cwd() } = {}) => {
|
|
24
|
+
const plop = nodePlop(join(__dirname, 'plopfile.js'), { destBasePath: join(dir, 'src') });
|
|
25
|
+
|
|
26
|
+
const generator = plop.getGenerator(generatorName);
|
|
27
|
+
await generator.runActions(options, {
|
|
28
|
+
onSuccess() {},
|
|
29
|
+
onFailure() {},
|
|
30
|
+
onComment() {},
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
generate,
|
|
36
|
+
runCLI,
|
|
37
|
+
};
|
package/lib/plopfile.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const pluralize = require('pluralize');
|
|
4
|
+
|
|
5
|
+
const generateApi = require('./plops/api');
|
|
6
|
+
const generateController = require('./plops/controller');
|
|
7
|
+
const generateContentType = require('./plops/content-type');
|
|
8
|
+
const generatePlugin = require('./plops/plugin');
|
|
9
|
+
const generatePolicy = require('./plops/policy');
|
|
10
|
+
const generateService = require('./plops/service');
|
|
11
|
+
|
|
12
|
+
module.exports = plop => {
|
|
13
|
+
// Plop config
|
|
14
|
+
plop.setWelcomeMessage('Strapi Generators');
|
|
15
|
+
plop.addHelper('pluralize', text => pluralize(text));
|
|
16
|
+
|
|
17
|
+
// Generators
|
|
18
|
+
generateApi(plop);
|
|
19
|
+
generateController(plop);
|
|
20
|
+
generateContentType(plop);
|
|
21
|
+
generatePlugin(plop);
|
|
22
|
+
generatePolicy(plop);
|
|
23
|
+
generateService(plop);
|
|
24
|
+
};
|
package/lib/plops/api.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { join } = require('path');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const validateInput = require('./utils/validate-input');
|
|
6
|
+
|
|
7
|
+
module.exports = plop => {
|
|
8
|
+
// API generator
|
|
9
|
+
plop.setGenerator('api', {
|
|
10
|
+
description: 'Generate a basic API',
|
|
11
|
+
prompts: [
|
|
12
|
+
{
|
|
13
|
+
type: 'input',
|
|
14
|
+
name: 'id',
|
|
15
|
+
message: 'API name',
|
|
16
|
+
validate: input => validateInput(input),
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
type: 'confirm',
|
|
20
|
+
name: 'isPluginApi',
|
|
21
|
+
message: 'Is this API for a plugin?',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
when: answers => answers.isPluginApi,
|
|
25
|
+
type: 'list',
|
|
26
|
+
name: 'plugin',
|
|
27
|
+
message: 'Plugin name',
|
|
28
|
+
async choices() {
|
|
29
|
+
const pluginsPath = join(plop.getDestBasePath(), 'plugins');
|
|
30
|
+
const exists = await fs.pathExists(pluginsPath);
|
|
31
|
+
|
|
32
|
+
if (!exists) {
|
|
33
|
+
throw Error('Couldn\'t find a "plugins" directory');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const pluginsDir = await fs.readdir(pluginsPath, { withFileTypes: true });
|
|
37
|
+
const pluginsDirContent = pluginsDir.filter(fd => fd.isDirectory());
|
|
38
|
+
|
|
39
|
+
if (pluginsDirContent.length === 0) {
|
|
40
|
+
throw Error('The "plugins" directory is empty');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return pluginsDirContent;
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: 'list',
|
|
48
|
+
name: 'kind',
|
|
49
|
+
message: 'Please choose the model type',
|
|
50
|
+
default: 'collectionType',
|
|
51
|
+
choices: [
|
|
52
|
+
{ name: 'Collection Type', value: 'collectionType' },
|
|
53
|
+
{ name: 'Singe Type', value: 'singleType' },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'confirm',
|
|
58
|
+
name: 'useDraftAndPublish',
|
|
59
|
+
default: false,
|
|
60
|
+
message: 'Use draft and publish?',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
actions(answers) {
|
|
64
|
+
let filePath;
|
|
65
|
+
if (answers.isPluginApi && answers.plugin) {
|
|
66
|
+
filePath = `plugins/{{plugin}}`;
|
|
67
|
+
} else {
|
|
68
|
+
filePath = `api/{{id}}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const baseActions = [
|
|
72
|
+
{
|
|
73
|
+
type: 'add',
|
|
74
|
+
path: `${filePath}/controllers/{{id}}.js`,
|
|
75
|
+
templateFile: 'templates/controller.js.hbs',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: 'add',
|
|
79
|
+
path: `${filePath}/content-types/{{id}}/schema.json`,
|
|
80
|
+
templateFile: 'templates/model.schema.json.hbs',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: 'add',
|
|
84
|
+
path: `${filePath}/services/{{id}}.js`,
|
|
85
|
+
templateFile: 'templates/service.js.hbs',
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
if (answers.isPluginApi) {
|
|
90
|
+
return baseActions;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const routeType =
|
|
94
|
+
answers.kind === 'singleType'
|
|
95
|
+
? 'single-type-routes.js.hbs'
|
|
96
|
+
: 'collection-type-routes.js.hbs';
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
type: 'add',
|
|
101
|
+
path: `${filePath}/routes/{{id}}.js`,
|
|
102
|
+
templateFile: `templates/${routeType}`,
|
|
103
|
+
},
|
|
104
|
+
...baseActions,
|
|
105
|
+
];
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const getDestinationPrompts = require('./utils/get-destination-prompts');
|
|
4
|
+
const getFilePath = require('./utils/get-file-path');
|
|
5
|
+
const validateInput = require('./utils/validate-input');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_TYPES = [
|
|
8
|
+
// advanced types
|
|
9
|
+
'media',
|
|
10
|
+
|
|
11
|
+
// scalar types
|
|
12
|
+
'string',
|
|
13
|
+
'text',
|
|
14
|
+
'richtext',
|
|
15
|
+
'json',
|
|
16
|
+
'enumeration',
|
|
17
|
+
'password',
|
|
18
|
+
'email',
|
|
19
|
+
'integer',
|
|
20
|
+
'biginteger',
|
|
21
|
+
'float',
|
|
22
|
+
'decimal',
|
|
23
|
+
'date',
|
|
24
|
+
'time',
|
|
25
|
+
'datetime',
|
|
26
|
+
'timestamp',
|
|
27
|
+
'boolean',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const promptConfigQuestions = (plop, inquirer) => {
|
|
31
|
+
return inquirer.prompt([
|
|
32
|
+
{
|
|
33
|
+
type: 'input',
|
|
34
|
+
name: 'id',
|
|
35
|
+
message: 'Content type name',
|
|
36
|
+
validate: input => validateInput(input),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'list',
|
|
40
|
+
name: 'kind',
|
|
41
|
+
message: 'Please choose the model type',
|
|
42
|
+
default: 'collectionType',
|
|
43
|
+
choices: [
|
|
44
|
+
{ name: 'Collection Type', value: 'collectionType' },
|
|
45
|
+
{ name: 'Singe Type', value: 'singleType' },
|
|
46
|
+
],
|
|
47
|
+
validate: input => validateInput(input),
|
|
48
|
+
},
|
|
49
|
+
...getDestinationPrompts('model', plop.getDestBasePath()),
|
|
50
|
+
{
|
|
51
|
+
type: 'confirm',
|
|
52
|
+
name: 'useDraftAndPublish',
|
|
53
|
+
default: false,
|
|
54
|
+
message: 'Use draft and publish?',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'confirm',
|
|
58
|
+
name: 'addAttributes',
|
|
59
|
+
message: 'Do you want to add attributes?',
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const promptAttributeQuestions = inquirer => {
|
|
65
|
+
return inquirer.prompt([
|
|
66
|
+
{
|
|
67
|
+
type: 'input',
|
|
68
|
+
name: 'attributeName',
|
|
69
|
+
message: 'Name of attribute',
|
|
70
|
+
validate: input => validateInput(input),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: 'list',
|
|
74
|
+
name: 'attributeType',
|
|
75
|
+
message: 'What type of attribute',
|
|
76
|
+
pageSize: DEFAULT_TYPES.length,
|
|
77
|
+
choices: DEFAULT_TYPES.map(type => {
|
|
78
|
+
return { name: type, value: type };
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
when: answers => answers.attributeType === 'enumeration',
|
|
83
|
+
type: 'input',
|
|
84
|
+
name: 'enum',
|
|
85
|
+
message: 'Add values separated by a comma',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
when: answers => answers.attributeType === 'media',
|
|
89
|
+
type: 'list',
|
|
90
|
+
name: 'multiple',
|
|
91
|
+
message: 'Choose media type',
|
|
92
|
+
choices: [
|
|
93
|
+
{ name: 'Multiple', value: true },
|
|
94
|
+
{ name: 'Single', value: false },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
type: 'confirm',
|
|
99
|
+
name: 'addAttributes',
|
|
100
|
+
message: 'Do you want to add another attribute?',
|
|
101
|
+
},
|
|
102
|
+
]);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
module.exports = plop => {
|
|
106
|
+
// Model generator
|
|
107
|
+
plop.setGenerator('content-type', {
|
|
108
|
+
description: 'Generate a content type for an API',
|
|
109
|
+
async prompts(inquirer) {
|
|
110
|
+
const config = await promptConfigQuestions(plop, inquirer);
|
|
111
|
+
|
|
112
|
+
if (!config.addAttributes) {
|
|
113
|
+
return {
|
|
114
|
+
...config,
|
|
115
|
+
attributes: [],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const attributes = [];
|
|
120
|
+
|
|
121
|
+
const genAttribute = async () => {
|
|
122
|
+
const answers = await promptAttributeQuestions(inquirer);
|
|
123
|
+
|
|
124
|
+
attributes.push(answers);
|
|
125
|
+
|
|
126
|
+
if (answers.addAttributes) {
|
|
127
|
+
return genAttribute();
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
await genAttribute();
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
...config,
|
|
135
|
+
attributes,
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
actions(answers) {
|
|
139
|
+
const attributes = answers.attributes.reduce((object, answer) => {
|
|
140
|
+
const val = { type: answer.attributeType };
|
|
141
|
+
|
|
142
|
+
if (answer.attributeType === 'enumeration') {
|
|
143
|
+
val.enum = answer.enum.split(',').map(item => item.trim());
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (answer.attributeType === 'media') {
|
|
147
|
+
val.allowedTypes = ['images', 'files', 'videos'];
|
|
148
|
+
val.multiple = answer.multiple;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return Object.assign(object, { [answer.attributeName]: val }, {});
|
|
152
|
+
}, {});
|
|
153
|
+
|
|
154
|
+
const filePath = getFilePath(answers.destination);
|
|
155
|
+
|
|
156
|
+
return [
|
|
157
|
+
{
|
|
158
|
+
type: 'add',
|
|
159
|
+
path: `${filePath}/content-types/{{id}}/schema.json`,
|
|
160
|
+
templateFile: 'templates/model.schema.json.hbs',
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
type: 'modify',
|
|
164
|
+
path: `${filePath}/content-types/{{id}}/schema.json`,
|
|
165
|
+
transform(template) {
|
|
166
|
+
const parsedTemplate = JSON.parse(template);
|
|
167
|
+
parsedTemplate.attributes = attributes;
|
|
168
|
+
return JSON.stringify(parsedTemplate, null, 2);
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const getDestinationPrompts = require('./utils/get-destination-prompts');
|
|
4
|
+
const getFilePath = require('./utils/get-file-path');
|
|
5
|
+
const validateInput = require('./utils/validate-input');
|
|
6
|
+
|
|
7
|
+
module.exports = plop => {
|
|
8
|
+
// Controller generator
|
|
9
|
+
plop.setGenerator('controller', {
|
|
10
|
+
description: 'Generate a controller for an API',
|
|
11
|
+
prompts: [
|
|
12
|
+
{
|
|
13
|
+
type: 'input',
|
|
14
|
+
name: 'id',
|
|
15
|
+
message: 'Controller name',
|
|
16
|
+
validate: input => validateInput(input),
|
|
17
|
+
},
|
|
18
|
+
...getDestinationPrompts('controller', plop.getDestBasePath()),
|
|
19
|
+
],
|
|
20
|
+
actions(answers) {
|
|
21
|
+
const filePath = getFilePath(answers.destination);
|
|
22
|
+
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
type: 'add',
|
|
26
|
+
path: `${filePath}/controllers/{{id}}.js`,
|
|
27
|
+
templateFile: 'templates/controller.js.hbs',
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = plop => {
|
|
4
|
+
// Plugin generator
|
|
5
|
+
plop.setGenerator('plugin', {
|
|
6
|
+
description: 'Generate a basic plugin',
|
|
7
|
+
prompts: [
|
|
8
|
+
{
|
|
9
|
+
type: 'input',
|
|
10
|
+
name: 'id',
|
|
11
|
+
message: 'Plugin name',
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
actions() {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
type: 'addMany',
|
|
18
|
+
destination: 'plugins/{{id}}/admin',
|
|
19
|
+
base: 'files/plugin/admin',
|
|
20
|
+
templateFiles: 'files/plugin/admin/**',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: 'add',
|
|
24
|
+
path: 'plugins/{{id}}/services/{{id}}.js',
|
|
25
|
+
templateFile: 'templates/service.js.hbs',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: 'add',
|
|
29
|
+
path: 'plugins/{{id}}/controllers/{{id}}.js',
|
|
30
|
+
templateFile: 'templates/controller.js.hbs',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: 'add',
|
|
34
|
+
path: 'plugins/{{id}}/config/routes.json',
|
|
35
|
+
templateFile: 'templates/plugin-routes.json.hbs',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'add',
|
|
39
|
+
path: 'plugins/{{id}}/README.md',
|
|
40
|
+
templateFile: 'templates/README.md.hbs',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'add',
|
|
44
|
+
path: 'plugins/{{id}}/package.json',
|
|
45
|
+
templateFile: 'templates/plugin-package.json.hbs',
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const getDestinationPrompts = require('./utils/get-destination-prompts');
|
|
4
|
+
|
|
5
|
+
module.exports = plop => {
|
|
6
|
+
// Policy generator
|
|
7
|
+
plop.setGenerator('policy', {
|
|
8
|
+
description: 'Generate a policy for an API',
|
|
9
|
+
prompts: [
|
|
10
|
+
{
|
|
11
|
+
type: 'input',
|
|
12
|
+
name: 'id',
|
|
13
|
+
message: 'Policy name',
|
|
14
|
+
},
|
|
15
|
+
...getDestinationPrompts('policy', plop.getDestBasePath()),
|
|
16
|
+
],
|
|
17
|
+
actions(answers) {
|
|
18
|
+
let filePath;
|
|
19
|
+
if (answers.destination === 'api') {
|
|
20
|
+
filePath = `api/{{api}}`;
|
|
21
|
+
} else if (answers.destination === 'plugin') {
|
|
22
|
+
filePath = `plugins/{{plugin}}`;
|
|
23
|
+
} else {
|
|
24
|
+
filePath = `./`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
type: 'add',
|
|
30
|
+
path: `${filePath}/policies/{{id}}.js`,
|
|
31
|
+
templateFile: 'templates/policy.js.hbs',
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const getDestinationPrompts = require('./utils/get-destination-prompts');
|
|
4
|
+
const getFilePath = require('./utils/get-file-path');
|
|
5
|
+
|
|
6
|
+
module.exports = plop => {
|
|
7
|
+
// Service generator
|
|
8
|
+
plop.setGenerator('service', {
|
|
9
|
+
description: 'Generate a service for an API',
|
|
10
|
+
prompts: [
|
|
11
|
+
{
|
|
12
|
+
type: 'input',
|
|
13
|
+
name: 'id',
|
|
14
|
+
message: 'Service name',
|
|
15
|
+
},
|
|
16
|
+
...getDestinationPrompts('service', plop.getDestBasePath()),
|
|
17
|
+
],
|
|
18
|
+
actions(answers) {
|
|
19
|
+
const filePath = getFilePath(answers.destination);
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
type: 'add',
|
|
23
|
+
path: `${filePath}/services/{{id}}.js`,
|
|
24
|
+
templateFile: 'templates/service.js.hbs',
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
|
|
5
|
+
module.exports = (action, basePath) => {
|
|
6
|
+
return [
|
|
7
|
+
{
|
|
8
|
+
type: 'list',
|
|
9
|
+
name: 'destination',
|
|
10
|
+
message: `Where do you want to add this ${action}?`,
|
|
11
|
+
choices: [
|
|
12
|
+
{
|
|
13
|
+
name: `Add ${action} to ${action === 'policy' ? 'root of project' : 'new API'}`,
|
|
14
|
+
value: 'new',
|
|
15
|
+
},
|
|
16
|
+
{ name: `Add ${action} to an existing API`, value: 'api' },
|
|
17
|
+
{ name: `Add ${action} to an existing plugin`, value: 'plugin' },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
when: answers => answers.destination === 'api',
|
|
22
|
+
type: 'list',
|
|
23
|
+
message: 'Which API is this for?',
|
|
24
|
+
name: 'api',
|
|
25
|
+
async choices() {
|
|
26
|
+
const apiPath = join(basePath, 'api');
|
|
27
|
+
const exists = await fs.pathExists(apiPath);
|
|
28
|
+
|
|
29
|
+
if (!exists) {
|
|
30
|
+
throw Error('Couldn\'t find an "api" directory');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const apiDir = await fs.readdir(apiPath, { withFileTypes: true });
|
|
34
|
+
const apiDirContent = apiDir.filter(fd => fd.isDirectory());
|
|
35
|
+
|
|
36
|
+
if (apiDirContent.length === 0) {
|
|
37
|
+
throw Error('The "api" directory is empty');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return apiDirContent;
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
when: answers => answers.destination === 'plugin',
|
|
45
|
+
type: 'list',
|
|
46
|
+
message: 'Which plugin is this for?',
|
|
47
|
+
name: 'plugin',
|
|
48
|
+
async choices() {
|
|
49
|
+
const pluginsPath = join(basePath, 'plugins');
|
|
50
|
+
const exists = await fs.pathExists(pluginsPath);
|
|
51
|
+
|
|
52
|
+
if (!exists) {
|
|
53
|
+
throw Error('Couldn\'t find a "plugins" directory');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const pluginsDir = await fs.readdir(pluginsPath);
|
|
57
|
+
const pluginsDirContent = pluginsDir.filter(api =>
|
|
58
|
+
fs.lstatSync(join(pluginsPath, api)).isDirectory()
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (pluginsDirContent.length === 0) {
|
|
62
|
+
throw Error('The "plugins" directory is empty');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return pluginsDirContent;
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
routes: [
|
|
3
|
+
{
|
|
4
|
+
method: 'GET',
|
|
5
|
+
path: '/{{pluralize id}}',
|
|
6
|
+
handler: '{{id}}.find',
|
|
7
|
+
config: {
|
|
8
|
+
policies: [],
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
method: 'GET',
|
|
13
|
+
path: '/{{pluralize id}}/:id',
|
|
14
|
+
handler: '{{id}}.findOne',
|
|
15
|
+
config: {
|
|
16
|
+
policies: [],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
method: 'POST',
|
|
21
|
+
path: '/{{pluralize id}}',
|
|
22
|
+
handler: '{{id}}.create',
|
|
23
|
+
config: {
|
|
24
|
+
policies: [],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
method: 'PUT',
|
|
29
|
+
path: '/{{pluralize id}}/:id',
|
|
30
|
+
handler: '{{id}}.update',
|
|
31
|
+
config: {
|
|
32
|
+
policies: [],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
method: 'DELETE',
|
|
37
|
+
path: '/{{pluralize id}}/:id',
|
|
38
|
+
handler: '{{id}}.delete',
|
|
39
|
+
config: {
|
|
40
|
+
policies: [],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
{
|
|
3
|
+
"kind": "{{kind}}",
|
|
4
|
+
"collectionName": "{{id}}",
|
|
5
|
+
"info": {
|
|
6
|
+
"singularName": "{{id}}",
|
|
7
|
+
"pluralName": "{{pluralize id}}",
|
|
8
|
+
"displayName": "{{id}}",
|
|
9
|
+
"name": "{{id}}"
|
|
10
|
+
},
|
|
11
|
+
"options": {
|
|
12
|
+
"draftAndPublish": {{ useDraftAndPublish }},
|
|
13
|
+
"comment": ""
|
|
14
|
+
},
|
|
15
|
+
"attributes": {}
|
|
16
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "strapi-plugin-{{id}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "This is the description of the plugin.",
|
|
5
|
+
"strapi": {
|
|
6
|
+
"name": "{{id}}",
|
|
7
|
+
"icon": "plug",
|
|
8
|
+
"description": "Description of {{id}} plugin."
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {},
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "A Strapi developer",
|
|
13
|
+
"email": "",
|
|
14
|
+
"url": ""
|
|
15
|
+
},
|
|
16
|
+
"maintainers": [
|
|
17
|
+
{
|
|
18
|
+
"name": "A Strapi developer",
|
|
19
|
+
"email": "",
|
|
20
|
+
"url": ""
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=12.x. <=16.x.x",
|
|
25
|
+
"npm": ">=6.0.0"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT"
|
|
28
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `{{id}}` policy.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = async (ctx) => {
|
|
8
|
+
// Add your own logic here.
|
|
9
|
+
console.log('In {{id}} policy.');
|
|
10
|
+
|
|
11
|
+
const canDoSomething = true;
|
|
12
|
+
|
|
13
|
+
if (canDoSomething) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return false;
|
|
18
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
routes: [
|
|
3
|
+
{
|
|
4
|
+
method: 'GET',
|
|
5
|
+
path: '/{{id}}',
|
|
6
|
+
handler: '{{id}}.find',
|
|
7
|
+
config: {
|
|
8
|
+
policies: [],
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
method: 'PUT',
|
|
13
|
+
path: '/{{id}}',
|
|
14
|
+
handler: '{{id}}.update',
|
|
15
|
+
config: {
|
|
16
|
+
policies: [],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
method: 'DELETE',
|
|
21
|
+
path: '/{{id}}',
|
|
22
|
+
handler: '{{id}}.delete',
|
|
23
|
+
config: {
|
|
24
|
+
policies: [],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@strapi/generators",
|
|
3
|
+
"version": "4.0.0-beta.0",
|
|
4
|
+
"description": "Interactive API generator.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"strapi",
|
|
7
|
+
"generators"
|
|
8
|
+
],
|
|
9
|
+
"homepage": "https://strapi.io",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/strapi/strapi/issues"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git://github.com/strapi/strapi.git"
|
|
16
|
+
},
|
|
17
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
18
|
+
"author": {
|
|
19
|
+
"name": "Strapi team",
|
|
20
|
+
"email": "hi@strapi.io",
|
|
21
|
+
"url": "https://strapi.io"
|
|
22
|
+
},
|
|
23
|
+
"maintainers": [
|
|
24
|
+
{
|
|
25
|
+
"name": "Strapi team",
|
|
26
|
+
"email": "hi@strapi.io",
|
|
27
|
+
"url": "https://strapi.io"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"main": "lib/index.js",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"fs-extra": "10.0.0",
|
|
33
|
+
"node-plop": "0.26.2",
|
|
34
|
+
"plop": "2.7.4",
|
|
35
|
+
"pluralize": "8.0.0"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=12.x.x <=16.x.x",
|
|
39
|
+
"npm": ">=6.0.0"
|
|
40
|
+
},
|
|
41
|
+
"gitHead": "9807c78cb7ab6373b7abb46da5ecc4980a9ea56c"
|
|
42
|
+
}
|