@akemona-org/create-strapi-starter 3.7.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 +35 -0
- package/create-strapi-starter.js +76 -0
- package/index.js +4 -0
- package/package.json +55 -0
- package/resources/gitignore +114 -0
- package/utils/build-starter.js +169 -0
- package/utils/child-process.js +60 -0
- package/utils/fetch-github.js +97 -0
- package/utils/has-yarn.js +14 -0
- package/utils/logger.js +17 -0
- package/utils/prompt-user.js +103 -0
- package/utils/stop-process.js +7 -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,35 @@
|
|
|
1
|
+
# Create strapi starter
|
|
2
|
+
|
|
3
|
+
This package includes the `create-strapi-starter` CLI to simplify creating a Strapi project using starters and templates
|
|
4
|
+
|
|
5
|
+
## How to use
|
|
6
|
+
|
|
7
|
+
### Quick usage (recommended)
|
|
8
|
+
|
|
9
|
+
Using yarn create command
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
yarn create strapi-starter my-project starter-url
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Using npx
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
npx create-strapi-starter my-project starter-url
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Manual install
|
|
22
|
+
|
|
23
|
+
Using yarn
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
yarn global add create-strapi-app
|
|
27
|
+
create-strapi-starter my-project starter-url
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Using npm
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
npm install -g create-strapi-app
|
|
34
|
+
create-strapi-starter my-project starter-url
|
|
35
|
+
```
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const commander = require('commander');
|
|
4
|
+
|
|
5
|
+
const packageJson = require('./package.json');
|
|
6
|
+
const buildStarter = require('./utils/build-starter');
|
|
7
|
+
const promptUser = require('./utils/prompt-user');
|
|
8
|
+
|
|
9
|
+
const program = new commander.Command(packageJson.name);
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.version(packageJson.version)
|
|
13
|
+
.arguments('[directory], [starterurl]')
|
|
14
|
+
.option('--use-npm', 'Force usage of npm instead of yarn to create the project')
|
|
15
|
+
.option('--debug', 'Display database connection error')
|
|
16
|
+
.option('--quickstart', 'Quickstart app creation')
|
|
17
|
+
.option('--dbclient <dbclient>', 'Database client')
|
|
18
|
+
.option('--dbhost <dbhost>', 'Database host')
|
|
19
|
+
.option('--dbsrv <dbsrv>', 'Database srv')
|
|
20
|
+
.option('--dbport <dbport>', 'Database port')
|
|
21
|
+
.option('--dbname <dbname>', 'Database name')
|
|
22
|
+
.option('--dbusername <dbusername>', 'Database username')
|
|
23
|
+
.option('--dbpassword <dbpassword>', 'Database password')
|
|
24
|
+
.option('--dbssl <dbssl>', 'Database SSL')
|
|
25
|
+
.option('--dbauth <dbauth>', 'Authentication Database')
|
|
26
|
+
.option('--dbfile <dbfile>', 'Database file path for sqlite')
|
|
27
|
+
.option('--dbforce', 'Overwrite database content if any')
|
|
28
|
+
.description(
|
|
29
|
+
'Create a fullstack monorepo application using the strapi backend template specified in the provided starter'
|
|
30
|
+
)
|
|
31
|
+
.action((directory, starterUrl, programArgs) => {
|
|
32
|
+
const projectArgs = { projectName: directory, starterUrl };
|
|
33
|
+
|
|
34
|
+
initProject(projectArgs, programArgs);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function generateApp(projectArgs, programArgs) {
|
|
38
|
+
if (!projectArgs.projectName || !projectArgs.starterUrl) {
|
|
39
|
+
console.error(
|
|
40
|
+
'Please specify the <directory> and <starterurl> of your project when using --quickstart'
|
|
41
|
+
);
|
|
42
|
+
// eslint-disable-next-line no-process-exit
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return buildStarter(projectArgs, programArgs);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function initProject(projectArgs, program) {
|
|
50
|
+
const { projectName, starterUrl } = projectArgs;
|
|
51
|
+
if (program.quickstart) {
|
|
52
|
+
return generateApp(projectArgs, program);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const prompt = await promptUser(projectName, starterUrl);
|
|
56
|
+
|
|
57
|
+
const promptProjectArgs = {
|
|
58
|
+
projectName: prompt.directory || projectName,
|
|
59
|
+
starterUrl: prompt.starter || starterUrl,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const programArgs = {
|
|
63
|
+
...program,
|
|
64
|
+
quickstart: prompt.quick,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return generateApp(promptProjectArgs, programArgs);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
program.parse(process.argv);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (err.exitCode && err.exitCode != 0) {
|
|
74
|
+
program.outputHelp();
|
|
75
|
+
}
|
|
76
|
+
}
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@akemona-org/create-strapi-starter",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "3.7.0",
|
|
7
|
+
"description": "Generate a new Strapi application.",
|
|
8
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
9
|
+
"homepage": "https://strapi.akemona.com",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"create-strapi-starter",
|
|
12
|
+
"create",
|
|
13
|
+
"new",
|
|
14
|
+
"generate",
|
|
15
|
+
"strapi"
|
|
16
|
+
],
|
|
17
|
+
"main": "./index.js",
|
|
18
|
+
"bin": {
|
|
19
|
+
"create-strapi-starter": "./index.js"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@akemona-org/strapi-generate-new": "3.7.0",
|
|
23
|
+
"chalk": "4.1.1",
|
|
24
|
+
"ci-info": "3.1.1",
|
|
25
|
+
"commander": "7.1.0",
|
|
26
|
+
"execa": "5.0.0",
|
|
27
|
+
"fs-extra": "9.1.0",
|
|
28
|
+
"git-url-parse": "13.1.0",
|
|
29
|
+
"inquirer": "8.1.0",
|
|
30
|
+
"js-yaml": "4.1.0",
|
|
31
|
+
"node-fetch": "^2.6.1",
|
|
32
|
+
"ora": "5.4.0",
|
|
33
|
+
"tar": "6.1.9"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "echo \"no tests yet\""
|
|
37
|
+
},
|
|
38
|
+
"author": {
|
|
39
|
+
"email": "strapi@akemona.com",
|
|
40
|
+
"name": "Akemona team",
|
|
41
|
+
"url": "https://strapi.akemona.com"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git://github.com/akemona/strapi.git"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/akemona/strapi/issues"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=10.16.0 <=14.x.x",
|
|
52
|
+
"npm": ">=6.0.0"
|
|
53
|
+
},
|
|
54
|
+
"gitHead": "129a8d6191b55810fd66448dcc47fee829df986c"
|
|
55
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
############################
|
|
2
|
+
# OS X
|
|
3
|
+
############################
|
|
4
|
+
|
|
5
|
+
.DS_Store
|
|
6
|
+
.AppleDouble
|
|
7
|
+
.LSOverride
|
|
8
|
+
Icon
|
|
9
|
+
.Spotlight-V100
|
|
10
|
+
.Trashes
|
|
11
|
+
._*
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
############################
|
|
15
|
+
# Linux
|
|
16
|
+
############################
|
|
17
|
+
|
|
18
|
+
*~
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
############################
|
|
22
|
+
# Windows
|
|
23
|
+
############################
|
|
24
|
+
|
|
25
|
+
Thumbs.db
|
|
26
|
+
ehthumbs.db
|
|
27
|
+
Desktop.ini
|
|
28
|
+
$RECYCLE.BIN/
|
|
29
|
+
*.cab
|
|
30
|
+
*.msi
|
|
31
|
+
*.msm
|
|
32
|
+
*.msp
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
############################
|
|
36
|
+
# Packages
|
|
37
|
+
############################
|
|
38
|
+
|
|
39
|
+
*.7z
|
|
40
|
+
*.csv
|
|
41
|
+
*.dat
|
|
42
|
+
*.dmg
|
|
43
|
+
*.gz
|
|
44
|
+
*.iso
|
|
45
|
+
*.jar
|
|
46
|
+
*.rar
|
|
47
|
+
*.tar
|
|
48
|
+
*.zip
|
|
49
|
+
*.com
|
|
50
|
+
*.class
|
|
51
|
+
*.dll
|
|
52
|
+
*.exe
|
|
53
|
+
*.o
|
|
54
|
+
*.seed
|
|
55
|
+
*.so
|
|
56
|
+
*.swo
|
|
57
|
+
*.swp
|
|
58
|
+
*.swn
|
|
59
|
+
*.swm
|
|
60
|
+
*.out
|
|
61
|
+
*.pid
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
############################
|
|
65
|
+
# Logs and databases
|
|
66
|
+
############################
|
|
67
|
+
|
|
68
|
+
.tmp
|
|
69
|
+
*.log
|
|
70
|
+
*.sql
|
|
71
|
+
*.sqlite
|
|
72
|
+
*.sqlite3
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
############################
|
|
76
|
+
# Misc.
|
|
77
|
+
############################
|
|
78
|
+
|
|
79
|
+
*#
|
|
80
|
+
ssl
|
|
81
|
+
.idea
|
|
82
|
+
nbproject
|
|
83
|
+
public/uploads/*
|
|
84
|
+
!public/uploads/.gitkeep
|
|
85
|
+
|
|
86
|
+
############################
|
|
87
|
+
# Node.js
|
|
88
|
+
############################
|
|
89
|
+
|
|
90
|
+
lib-cov
|
|
91
|
+
lcov.info
|
|
92
|
+
pids
|
|
93
|
+
logs
|
|
94
|
+
results
|
|
95
|
+
node_modules
|
|
96
|
+
.node_history
|
|
97
|
+
|
|
98
|
+
############################
|
|
99
|
+
# Tests
|
|
100
|
+
############################
|
|
101
|
+
|
|
102
|
+
testApp
|
|
103
|
+
coverage
|
|
104
|
+
|
|
105
|
+
############################
|
|
106
|
+
# Strapi
|
|
107
|
+
############################
|
|
108
|
+
|
|
109
|
+
.env
|
|
110
|
+
license.txt
|
|
111
|
+
exports
|
|
112
|
+
*.cache
|
|
113
|
+
build
|
|
114
|
+
.strapi-updater.json
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { resolve, join, basename } = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const fse = require('fs-extra');
|
|
6
|
+
|
|
7
|
+
const ora = require('ora');
|
|
8
|
+
const ciEnv = require('ci-info');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
const generateNewApp = require('@akemona-org/strapi-generate-new');
|
|
12
|
+
|
|
13
|
+
const hasYarn = require('./has-yarn');
|
|
14
|
+
const { runInstall, runApp, initGit } = require('./child-process');
|
|
15
|
+
const { getRepoInfo, downloadGitHubRepo } = require('./fetch-github');
|
|
16
|
+
const logger = require('./logger');
|
|
17
|
+
const stopProcess = require('./stop-process');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} - filePath Path to starter.json file
|
|
21
|
+
*/
|
|
22
|
+
function readStarterJson(filePath, starterUrl) {
|
|
23
|
+
try {
|
|
24
|
+
const data = fse.readFileSync(filePath);
|
|
25
|
+
return JSON.parse(data);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
stopProcess(`Could not find ${chalk.yellow('starter.json')} in ${chalk.yellow(starterUrl)}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} rootPath - Path to the project directory
|
|
33
|
+
* @param {string} projectName - Name of the project
|
|
34
|
+
*/
|
|
35
|
+
async function initPackageJson(rootPath, projectName) {
|
|
36
|
+
const packageManager = hasYarn() ? 'yarn --cwd' : 'npm run --prefix';
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await fse.writeJson(
|
|
40
|
+
join(rootPath, 'package.json'),
|
|
41
|
+
{
|
|
42
|
+
name: projectName,
|
|
43
|
+
private: true,
|
|
44
|
+
version: '0.0.0',
|
|
45
|
+
scripts: {
|
|
46
|
+
'develop:backend': `${packageManager} backend develop`,
|
|
47
|
+
'develop:frontend': `wait-on http://localhost:1337/admin && ${packageManager} frontend develop`,
|
|
48
|
+
develop: 'cross-env FORCE_COLOR=1 npm-run-all -l -p develop:*',
|
|
49
|
+
},
|
|
50
|
+
devDependencies: {
|
|
51
|
+
'npm-run-all': '4.1.5',
|
|
52
|
+
'wait-on': '5.2.1',
|
|
53
|
+
'cross-env': '7.0.3',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
spaces: 2,
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
stopProcess(`Failed to create ${chalk.yellow(`package.json`)} in ${chalk.yellow(rootPath)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {string} path - The directory path for install
|
|
67
|
+
*/
|
|
68
|
+
async function installWithLogs(path) {
|
|
69
|
+
const installPrefix = chalk.yellow('Installing dependencies:');
|
|
70
|
+
const loader = ora(installPrefix).start();
|
|
71
|
+
const logInstall = (chunk = '') => {
|
|
72
|
+
loader.text = `${installPrefix} ${chunk.toString().split('\n').join(' ')}`;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const runner = runInstall(path);
|
|
76
|
+
runner.stdout.on('data', logInstall);
|
|
77
|
+
runner.stderr.on('data', logInstall);
|
|
78
|
+
|
|
79
|
+
await runner;
|
|
80
|
+
|
|
81
|
+
loader.stop();
|
|
82
|
+
console.log(`Dependencies installed ${chalk.green('successfully')}.`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {Object} projectArgs - The arguments for create a project
|
|
87
|
+
* @param {string|null} projectArgs.projectName - The name/path of project
|
|
88
|
+
* @param {string|null} projectArgs.starterUrl - The GitHub repo of the starter
|
|
89
|
+
* @param {Object} program - Commands for generating new application
|
|
90
|
+
*/
|
|
91
|
+
module.exports = async function buildStarter(programArgs, program) {
|
|
92
|
+
let { projectName, starterUrl } = programArgs;
|
|
93
|
+
|
|
94
|
+
// Fetch repo info
|
|
95
|
+
const repoInfo = await getRepoInfo(starterUrl);
|
|
96
|
+
const { fullName } = repoInfo;
|
|
97
|
+
|
|
98
|
+
// Create temporary directory for starter
|
|
99
|
+
const tmpDir = await fse.mkdtemp(join(os.tmpdir(), 'strapi-'));
|
|
100
|
+
|
|
101
|
+
// Download repo inside temporary directory
|
|
102
|
+
await downloadGitHubRepo(repoInfo, tmpDir);
|
|
103
|
+
|
|
104
|
+
const starterJson = readStarterJson(join(tmpDir, 'starter.json'), starterUrl);
|
|
105
|
+
|
|
106
|
+
// Project directory
|
|
107
|
+
const rootPath = resolve(projectName);
|
|
108
|
+
const projectBasename = basename(rootPath);
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await fse.ensureDir(rootPath);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
stopProcess(`Failed to create ${chalk.yellow(rootPath)}: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Copy the downloaded frontend folder to the project folder
|
|
117
|
+
const frontendPath = join(rootPath, 'frontend');
|
|
118
|
+
|
|
119
|
+
const starterDir = (await fse.pathExists(join(tmpDir, 'starter'))) ? 'starter' : 'frontend';
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await fse.copy(join(tmpDir, starterDir), frontendPath, {
|
|
123
|
+
overwrite: true,
|
|
124
|
+
recursive: true,
|
|
125
|
+
});
|
|
126
|
+
} catch (error) {
|
|
127
|
+
stopProcess(`Failed to create ${chalk.yellow(frontendPath)}: ${error.message}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Delete temporary directory
|
|
131
|
+
await fse.remove(tmpDir);
|
|
132
|
+
|
|
133
|
+
const fullUrl = `https://github.com/${fullName}`;
|
|
134
|
+
// Set command options for Strapi app
|
|
135
|
+
const generateStrapiAppOptions = {
|
|
136
|
+
...program,
|
|
137
|
+
starter: fullUrl,
|
|
138
|
+
template: starterJson.template,
|
|
139
|
+
run: false,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Create strapi app using the template
|
|
143
|
+
await generateNewApp(join(rootPath, 'backend'), generateStrapiAppOptions);
|
|
144
|
+
|
|
145
|
+
// Install frontend dependencies
|
|
146
|
+
console.log(`Creating Strapi starter frontend at ${chalk.green(frontendPath)}.`);
|
|
147
|
+
console.log(`Installing ${chalk.yellow(fullName)} starter`);
|
|
148
|
+
await installWithLogs(frontendPath);
|
|
149
|
+
|
|
150
|
+
// Setup monorepo
|
|
151
|
+
initPackageJson(rootPath, projectBasename);
|
|
152
|
+
|
|
153
|
+
// Add gitignore
|
|
154
|
+
try {
|
|
155
|
+
const gitignore = join(__dirname, '..', 'resources', 'gitignore');
|
|
156
|
+
await fse.copy(gitignore, join(rootPath, '.gitignore'));
|
|
157
|
+
} catch (err) {
|
|
158
|
+
logger.warn(`Failed to create file: ${chalk.yellow('.gitignore')}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await installWithLogs(rootPath);
|
|
162
|
+
|
|
163
|
+
if (!ciEnv.isCI) {
|
|
164
|
+
await initGit(rootPath);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log(chalk.green('Starting the app'));
|
|
168
|
+
await runApp(rootPath);
|
|
169
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const execa = require('execa');
|
|
5
|
+
const hasYarn = require('./has-yarn');
|
|
6
|
+
const logger = require('./logger');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} path Path to directory (frontend, backend)
|
|
10
|
+
*/
|
|
11
|
+
function runInstall(path) {
|
|
12
|
+
if (hasYarn()) {
|
|
13
|
+
return execa('yarn', ['install'], {
|
|
14
|
+
cwd: path,
|
|
15
|
+
stdin: 'ignore',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return execa('npm', ['install'], { cwd: path, stdin: 'ignore' });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function runApp(rootPath) {
|
|
23
|
+
if (hasYarn()) {
|
|
24
|
+
return execa('yarn', ['develop'], {
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
cwd: rootPath,
|
|
27
|
+
});
|
|
28
|
+
} else {
|
|
29
|
+
return execa('npm', ['run', 'develop'], {
|
|
30
|
+
stdio: 'inherit',
|
|
31
|
+
cwd: rootPath,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function initGit(rootPath) {
|
|
37
|
+
try {
|
|
38
|
+
await execa('git', ['init'], {
|
|
39
|
+
cwd: rootPath,
|
|
40
|
+
});
|
|
41
|
+
} catch (err) {
|
|
42
|
+
logger.warn(`Could not initialize a git repository`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await execa(`git`, [`add`, `-A`], { cwd: rootPath });
|
|
47
|
+
|
|
48
|
+
execSync(`git commit -m "Create Strapi starter project"`, {
|
|
49
|
+
cwd: rootPath,
|
|
50
|
+
});
|
|
51
|
+
} catch (err) {
|
|
52
|
+
logger.warn(`Could not create initial git commit`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
runInstall,
|
|
58
|
+
runApp,
|
|
59
|
+
initGit,
|
|
60
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const tar = require('tar');
|
|
4
|
+
const fetch = require('node-fetch');
|
|
5
|
+
const parseGitUrl = require('git-url-parse');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
const stopProcess = require('./stop-process');
|
|
9
|
+
|
|
10
|
+
function parseShorthand(starter) {
|
|
11
|
+
// Determine if it is comes from another owner
|
|
12
|
+
if (starter.includes('/')) {
|
|
13
|
+
const [owner, partialName] = starter.split('/');
|
|
14
|
+
const name = `strapi-starter-${partialName}`;
|
|
15
|
+
return {
|
|
16
|
+
name,
|
|
17
|
+
fullName: `${owner}/${name}`,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const name = `strapi-starter-${starter}`;
|
|
22
|
+
return {
|
|
23
|
+
name,
|
|
24
|
+
fullName: `strapi/${name}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} repo The full name of the repository.
|
|
30
|
+
*/
|
|
31
|
+
async function getDefaultBranch(repo) {
|
|
32
|
+
const response = await fetch(`https://api.github.com/repos/${repo}`);
|
|
33
|
+
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
stopProcess(
|
|
36
|
+
`Could not find the starter information for ${chalk.yellow(
|
|
37
|
+
repo
|
|
38
|
+
)}. Make sure it is publicly accessible on github.`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { default_branch } = await response.json();
|
|
43
|
+
return default_branch;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {string} starter GitHub URL or shorthand to a starter project.
|
|
48
|
+
*/
|
|
49
|
+
async function getRepoInfo(starter) {
|
|
50
|
+
const { name, full_name: fullName, ref, filepath, protocols, source } = parseGitUrl(starter);
|
|
51
|
+
|
|
52
|
+
if (protocols.length === 0) {
|
|
53
|
+
const repoInfo = parseShorthand(starter);
|
|
54
|
+
return {
|
|
55
|
+
...repoInfo,
|
|
56
|
+
branch: await getDefaultBranch(repoInfo.fullName),
|
|
57
|
+
usedShorthand: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (source !== 'github.com') {
|
|
62
|
+
stopProcess(`GitHub URL not found for: ${chalk.yellow(starter)}.`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let branch;
|
|
66
|
+
if (ref) {
|
|
67
|
+
// Append the filepath to the parsed ref since a branch name could contain '/'
|
|
68
|
+
// If so, the rest of the branch name will be considered 'filepath' by 'parseGitUrl'
|
|
69
|
+
branch = filepath ? `${ref}/${filepath}` : ref;
|
|
70
|
+
} else {
|
|
71
|
+
branch = await getDefaultBranch(fullName);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { name, fullName, branch };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} repoInfo GitHub repository information (full name, branch...).
|
|
79
|
+
* @param {string} tmpDir Path to the destination temporary directory.
|
|
80
|
+
*/
|
|
81
|
+
async function downloadGitHubRepo(repoInfo, tmpDir) {
|
|
82
|
+
const { fullName, branch, usedShorthand } = repoInfo;
|
|
83
|
+
|
|
84
|
+
const codeload = `https://codeload.github.com/${fullName}/tar.gz/${branch}`;
|
|
85
|
+
const response = await fetch(codeload);
|
|
86
|
+
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
const message = usedShorthand ? `using the shorthand` : `using the url`;
|
|
89
|
+
stopProcess(`Could not download the repository ${message}: ${chalk.yellow(fullName)}.`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await new Promise(resolve => {
|
|
93
|
+
response.body.pipe(tar.extract({ strip: 1, cwd: tmpDir })).on('close', resolve);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { getRepoInfo, downloadGitHubRepo };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const execa = require('execa');
|
|
4
|
+
|
|
5
|
+
module.exports = function hasYarn() {
|
|
6
|
+
try {
|
|
7
|
+
const { exitCode } = execa.sync('yarn --version', { shell: true });
|
|
8
|
+
|
|
9
|
+
if (exitCode === 0) return true;
|
|
10
|
+
return false;
|
|
11
|
+
} catch (err) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
};
|
package/utils/logger.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
error(message) {
|
|
7
|
+
console.error(`${chalk.red('error')}: ${message}`);
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
warn(message) {
|
|
11
|
+
console.log(`${chalk.yellow('warning')}: ${message}`);
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
info(message) {
|
|
15
|
+
console.log(`${chalk.blue('info')}: ${message}`);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
const fetch = require('node-fetch');
|
|
5
|
+
const yaml = require('js-yaml');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {string|null} projectName - The name/path of project
|
|
9
|
+
* @param {string|null} starterUrl - The GitHub repo of the starter
|
|
10
|
+
* @returns Object containting prompt answers
|
|
11
|
+
*/
|
|
12
|
+
module.exports = async function promptUser(projectName, starter) {
|
|
13
|
+
const mainQuestions = [
|
|
14
|
+
{
|
|
15
|
+
type: 'input',
|
|
16
|
+
default: 'my-strapi-project',
|
|
17
|
+
name: 'directory',
|
|
18
|
+
message: 'What would you like to name your project?',
|
|
19
|
+
when: !projectName,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
type: 'list',
|
|
23
|
+
name: 'quick',
|
|
24
|
+
message: 'Choose your installation type',
|
|
25
|
+
choices: [
|
|
26
|
+
{
|
|
27
|
+
name: 'Quickstart (recommended)',
|
|
28
|
+
value: true,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'Custom (manual settings)',
|
|
32
|
+
value: false,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const [mainResponse, starterQuestion] = await Promise.all([
|
|
39
|
+
inquirer.prompt(mainQuestions),
|
|
40
|
+
getStarterQuestion(),
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const starterResponse = await inquirer.prompt({
|
|
44
|
+
name: 'starter',
|
|
45
|
+
when: !starter,
|
|
46
|
+
...starterQuestion,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return { ...mainResponse, ...starterResponse };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
* @returns Prompt question object
|
|
55
|
+
*/
|
|
56
|
+
async function getStarterQuestion() {
|
|
57
|
+
const content = await getStarterData();
|
|
58
|
+
|
|
59
|
+
// Fallback to manual input when fetch fails
|
|
60
|
+
if (!content) {
|
|
61
|
+
return {
|
|
62
|
+
type: 'input',
|
|
63
|
+
message: 'Please provide the GitHub URL for the starter you would like to use:',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const choices = content.map(option => {
|
|
68
|
+
const name = option.title.replace('Starter', '');
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
name,
|
|
72
|
+
value: `https://github.com/${option.repo}`,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
type: 'list',
|
|
78
|
+
message:
|
|
79
|
+
'Which starter would you like to use? (Starters are fullstack Strapi applications designed for a specific use case)',
|
|
80
|
+
pageSize: choices.length,
|
|
81
|
+
choices,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
*
|
|
87
|
+
* @returns JSON starter data
|
|
88
|
+
*/
|
|
89
|
+
async function getStarterData() {
|
|
90
|
+
const response = await fetch(
|
|
91
|
+
`https://api.github.com/repos/strapi/community-content/contents/starters/starters.yml`
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { content } = await response.json();
|
|
99
|
+
const buff = Buffer.from(content, 'base64');
|
|
100
|
+
const stringified = buff.toString('utf-8');
|
|
101
|
+
|
|
102
|
+
return yaml.load(stringified);
|
|
103
|
+
}
|