mirror of
				https://github.com/strapi/strapi.git
				synced 2025-10-31 01:47:13 +00:00 
			
		
		
		
	Merge pull request #11635 from strapi/v4/npm-starters
Update create-strapi-starter to use npm on v4
This commit is contained in:
		
						commit
						6652e796ba
					
				| @ -21,7 +21,7 @@ const incompatibleQuickstartOptions = [ | |||||||
| 
 | 
 | ||||||
| program | program | ||||||
|   .version(packageJson.version) |   .version(packageJson.version) | ||||||
|   .arguments('[directory], [starterurl]') |   .arguments('[directory], [starter]') | ||||||
|   .option('--use-npm', 'Force usage of npm instead of yarn to create the project') |   .option('--use-npm', 'Force usage of npm instead of yarn to create the project') | ||||||
|   .option('--debug', 'Display database connection error') |   .option('--debug', 'Display database connection error') | ||||||
|   .option('--quickstart', 'Quickstart app creation') |   .option('--quickstart', 'Quickstart app creation') | ||||||
| @ -37,16 +37,16 @@ program | |||||||
|   .description( |   .description( | ||||||
|     'Create a fullstack monorepo application using the strapi backend template specified in the provided starter' |     'Create a fullstack monorepo application using the strapi backend template specified in the provided starter' | ||||||
|   ) |   ) | ||||||
|   .action((directory, starterUrl, programArgs) => { |   .action((directory, starter, programArgs) => { | ||||||
|     const projectArgs = { projectName: directory, starterUrl }; |     const projectArgs = { projectName: directory, starter }; | ||||||
| 
 | 
 | ||||||
|     initProject(projectArgs, programArgs); |     initProject(projectArgs, programArgs); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
| function generateApp(projectArgs, programArgs) { | function generateApp(projectArgs, programArgs) { | ||||||
|   if (!projectArgs.projectName || !projectArgs.starterUrl) { |   if (!projectArgs.projectName || !projectArgs.starter) { | ||||||
|     console.error( |     console.error( | ||||||
|       'Please specify the <directory> and <starterurl> of your project when using --quickstart' |       'Please specify the <directory> and <starter> of your project when using --quickstart' | ||||||
|     ); |     ); | ||||||
|     // eslint-disable-next-line no-process-exit
 |     // eslint-disable-next-line no-process-exit
 | ||||||
|     process.exit(1); |     process.exit(1); | ||||||
| @ -71,16 +71,17 @@ async function initProject(projectArgs, program) { | |||||||
|     program.quickstart = false; // Will disable the quickstart question because != 'undefined'
 |     program.quickstart = false; // Will disable the quickstart question because != 'undefined'
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const { projectName, starterUrl } = projectArgs; |   const { projectName, starter } = projectArgs; | ||||||
|  | 
 | ||||||
|   if (program.quickstart) { |   if (program.quickstart) { | ||||||
|     return generateApp(projectArgs, program); |     return generateApp(projectArgs, program); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const prompt = await promptUser(projectName, starterUrl, program); |   const prompt = await promptUser(projectName, starter, program); | ||||||
| 
 | 
 | ||||||
|   const promptProjectArgs = { |   const promptProjectArgs = { | ||||||
|     projectName: prompt.directory || projectName, |     projectName: prompt.directory || projectName, | ||||||
|     starterUrl: prompt.starter || starterUrl, |     starter: prompt.starter || starter, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const programArgs = { |   const programArgs = { | ||||||
|  | |||||||
| @ -16,18 +16,14 @@ | |||||||
|     "create-strapi-starter": "./index.js" |     "create-strapi-starter": "./index.js" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|  |     "@strapi/generate-new": "3.6.8", | ||||||
|     "chalk": "4.1.1", |     "chalk": "4.1.1", | ||||||
|     "ci-info": "3.1.1", |     "ci-info": "3.1.1", | ||||||
|     "commander": "7.1.0", |     "commander": "7.1.0", | ||||||
|     "execa": "5.0.0", |     "execa": "^5.1.1", | ||||||
|     "fs-extra": "9.1.0", |     "fs-extra": "9.1.0", | ||||||
|     "git-url-parse": "11.4.4", |  | ||||||
|     "inquirer": "8.2.0", |     "inquirer": "8.2.0", | ||||||
|     "js-yaml": "4.1.0", |     "ora": "5.4.0" | ||||||
|     "node-fetch": "^2.6.1", |  | ||||||
|     "ora": "5.4.0", |  | ||||||
|     "@strapi/generate-new": "3.6.8", |  | ||||||
|     "tar": "6.1.11" |  | ||||||
|   }, |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "test": "echo \"no tests yet\"" |     "test": "echo \"no tests yet\"" | ||||||
|  | |||||||
| @ -3,7 +3,6 @@ | |||||||
| const { resolve, join, basename } = require('path'); | const { resolve, join, basename } = require('path'); | ||||||
| const os = require('os'); | const os = require('os'); | ||||||
| const fse = require('fs-extra'); | const fse = require('fs-extra'); | ||||||
| 
 |  | ||||||
| const ora = require('ora'); | const ora = require('ora'); | ||||||
| const ciEnv = require('ci-info'); | const ciEnv = require('ci-info'); | ||||||
| const chalk = require('chalk'); | const chalk = require('chalk'); | ||||||
| @ -12,28 +11,30 @@ const { generateNewApp } = require('@strapi/generate-new'); | |||||||
| 
 | 
 | ||||||
| const hasYarn = require('./has-yarn'); | const hasYarn = require('./has-yarn'); | ||||||
| const { runInstall, runApp, initGit } = require('./child-process'); | const { runInstall, runApp, initGit } = require('./child-process'); | ||||||
| const { getRepoInfo, downloadGitHubRepo } = require('./fetch-github'); | const { getStarterPackageInfo, downloadNpmStarter } = require('./fetch-npm-starter'); | ||||||
| const logger = require('./logger'); | const logger = require('./logger'); | ||||||
| const stopProcess = require('./stop-process'); | const stopProcess = require('./stop-process'); | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param  {string} - filePath Path to starter.json file |  * @param  {string} - filePath Path to starter.json file | ||||||
|  */ |  */ | ||||||
| function readStarterJson(filePath, starterUrl) { | function readStarterJson(filePath, starter) { | ||||||
|   try { |   try { | ||||||
|     const data = fse.readFileSync(filePath); |     const data = fse.readFileSync(filePath); | ||||||
|     return JSON.parse(data); |     return JSON.parse(data); | ||||||
|   } catch (err) { |   } catch (err) { | ||||||
|     stopProcess(`Could not find ${chalk.yellow('starter.json')} in ${chalk.yellow(starterUrl)}`); |     stopProcess(`Could not find ${chalk.yellow('starter.json')} in ${chalk.yellow(starter)}`); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {string} rootPath - Path to the project directory |  * @param {string} rootPath - Path to the project directory | ||||||
|  * @param {string} projectName - Name of the project |  * @param {string} projectName - Name of the project | ||||||
|  |  * @param {Object} options | ||||||
|  |  * @param {boolean} options.useYarn - Use yarn instead of npm | ||||||
|  */ |  */ | ||||||
| async function initPackageJson(rootPath, projectName) { | async function initPackageJson(rootPath, projectName, { useYarn } = {}) { | ||||||
|   const packageManager = hasYarn() ? 'yarn --cwd' : 'npm run --prefix'; |   const packageManager = useYarn ? 'yarn --cwd' : 'npm run --prefix'; | ||||||
| 
 | 
 | ||||||
|   try { |   try { | ||||||
|     await fse.writeJson( |     await fse.writeJson( | ||||||
| @ -63,9 +64,11 @@ async function initPackageJson(rootPath, projectName) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param  {string} path - The directory path for install |  * @param {string} path The directory path for install | ||||||
|  |  * @param {Object} options | ||||||
|  |  * @param {boolean} options.useYarn Use yarn instead of npm | ||||||
|  */ |  */ | ||||||
| async function installWithLogs(path) { | async function installWithLogs(path, options) { | ||||||
|   const installPrefix = chalk.yellow('Installing dependencies:'); |   const installPrefix = chalk.yellow('Installing dependencies:'); | ||||||
|   const loader = ora(installPrefix).start(); |   const loader = ora(installPrefix).start(); | ||||||
|   const logInstall = (chunk = '') => { |   const logInstall = (chunk = '') => { | ||||||
| @ -75,7 +78,7 @@ async function installWithLogs(path) { | |||||||
|       .join(' ')}`;
 |       .join(' ')}`;
 | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const runner = runInstall(path); |   const runner = runInstall(path, options); | ||||||
|   runner.stdout.on('data', logInstall); |   runner.stdout.on('data', logInstall); | ||||||
|   runner.stderr.on('data', logInstall); |   runner.stderr.on('data', logInstall); | ||||||
| 
 | 
 | ||||||
| @ -85,30 +88,54 @@ async function installWithLogs(path) { | |||||||
|   console.log(`Dependencies installed ${chalk.green('successfully')}.`); |   console.log(`Dependencies installed ${chalk.green('successfully')}.`); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * @param {string} starter The name of the starter as provided by the user | ||||||
|  |  * @param {Object} options | ||||||
|  |  * @param {boolean} options.useYarn Use yarn instead of npm | ||||||
|  |  */ | ||||||
|  | async function getStarterInfo(starter, { useYarn } = {}) { | ||||||
|  |   const isLocalStarter = ['./', '../', '/'].some(filePrefix => starter.startsWith(filePrefix)); | ||||||
|  | 
 | ||||||
|  |   let starterPath; | ||||||
|  |   let starterParentPath; | ||||||
|  |   let starterPackageInfo = {}; | ||||||
|  | 
 | ||||||
|  |   if (isLocalStarter) { | ||||||
|  |     // Starter is a local directory
 | ||||||
|  |     console.log('Installing local starter.'); | ||||||
|  |     starterPath = resolve(starter); | ||||||
|  |   } else { | ||||||
|  |     // Starter should be an npm package. Fetch starter info
 | ||||||
|  |     starterPackageInfo = await getStarterPackageInfo(starter, { useYarn }); | ||||||
|  |     console.log(`Installing ${chalk.yellow(starterPackageInfo.name)} starter.`); | ||||||
|  | 
 | ||||||
|  |     // Download starter repository to a temporary directory
 | ||||||
|  |     starterParentPath = await fse.mkdtemp(join(os.tmpdir(), 'strapi-')); | ||||||
|  |     starterPath = await downloadNpmStarter(starterPackageInfo, starterParentPath, { useYarn }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return { isLocalStarter, starterPath, starterParentPath, starterPackageInfo }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * @param {Object} projectArgs - The arguments for create a project |  * @param {Object} projectArgs - The arguments for create a project | ||||||
|  * @param {string|null} projectArgs.projectName - The name/path of project |  * @param {string|null} projectArgs.projectName - The name/path of project | ||||||
|  * @param {string|null} projectArgs.starterUrl - The GitHub repo of the starter |  * @param {string|null} projectArgs.starter - The npm package of the starter | ||||||
|  * @param {Object} program - Commands for generating new application |  * @param {Object} program - Commands for generating new application | ||||||
|  */ |  */ | ||||||
| module.exports = async function buildStarter(programArgs, program) { | module.exports = async function buildStarter({ projectName, starter }, program) { | ||||||
|   let { projectName, starterUrl } = programArgs; |   const hasYarnInstalled = await hasYarn(); | ||||||
| 
 |   const { | ||||||
|   // Fetch repo info
 |     isLocalStarter, | ||||||
|   const repoInfo = await getRepoInfo(starterUrl); |     starterPath, | ||||||
|   const { fullName } = repoInfo; |     starterParentPath, | ||||||
| 
 |     starterPackageInfo, | ||||||
|   // Create temporary directory for starter
 |   } = await getStarterInfo(starter, { useYarn: hasYarnInstalled }); | ||||||
|   const tmpDir = await fse.mkdtemp(join(os.tmpdir(), 'strapi-')); |  | ||||||
| 
 |  | ||||||
|   // Download repo inside temporary directory
 |  | ||||||
|   await downloadGitHubRepo(repoInfo, tmpDir); |  | ||||||
| 
 |  | ||||||
|   const starterJson = readStarterJson(join(tmpDir, 'starter.json'), starterUrl); |  | ||||||
| 
 | 
 | ||||||
|   // Project directory
 |   // Project directory
 | ||||||
|   const rootPath = resolve(projectName); |   const rootPath = resolve(projectName); | ||||||
|   const projectBasename = basename(rootPath); |   const projectBasename = basename(rootPath); | ||||||
|  |   const starterJson = readStarterJson(join(starterPath, 'starter.json'), starter); | ||||||
| 
 | 
 | ||||||
|   try { |   try { | ||||||
|     await fse.ensureDir(rootPath); |     await fse.ensureDir(rootPath); | ||||||
| @ -119,10 +146,8 @@ module.exports = async function buildStarter(programArgs, program) { | |||||||
|   // Copy the downloaded frontend folder to the project folder
 |   // Copy the downloaded frontend folder to the project folder
 | ||||||
|   const frontendPath = join(rootPath, 'frontend'); |   const frontendPath = join(rootPath, 'frontend'); | ||||||
| 
 | 
 | ||||||
|   const starterDir = (await fse.pathExists(join(tmpDir, 'starter'))) ? 'starter' : 'frontend'; |  | ||||||
| 
 |  | ||||||
|   try { |   try { | ||||||
|     await fse.copy(join(tmpDir, starterDir), frontendPath, { |     await fse.copy(join(starterPath, 'starter'), frontendPath, { | ||||||
|       overwrite: true, |       overwrite: true, | ||||||
|       recursive: true, |       recursive: true, | ||||||
|     }); |     }); | ||||||
| @ -130,28 +155,33 @@ module.exports = async function buildStarter(programArgs, program) { | |||||||
|     stopProcess(`Failed to create ${chalk.yellow(frontendPath)}: ${error.message}`); |     stopProcess(`Failed to create ${chalk.yellow(frontendPath)}: ${error.message}`); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // Delete temporary directory
 |   // Delete the starter directory if it was downloaded
 | ||||||
|   await fse.remove(tmpDir); |   if (!isLocalStarter) { | ||||||
|  |     await fse.remove(starterParentPath); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   const fullUrl = `https://github.com/${fullName}`; |  | ||||||
|   // Set command options for Strapi app
 |   // Set command options for Strapi app
 | ||||||
|   const generateStrapiAppOptions = { |   const generateStrapiAppOptions = { | ||||||
|     ...program, |     ...program, | ||||||
|     starter: fullUrl, |     starter: starterPackageInfo.name, | ||||||
|     template: starterJson.template, |  | ||||||
|     run: false, |     run: false, | ||||||
|   }; |   }; | ||||||
|  |   if (starterPackageInfo.version) { | ||||||
|  |     starterPackageInfo.template = `${starterJson.template.name}@${starterJson.template.version}`; | ||||||
|  |   } else { | ||||||
|  |     starterPackageInfo.template = starterJson.template.name; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   // Create strapi app using the template
 |   // Create strapi app using the template
 | ||||||
|   await generateNewApp(join(rootPath, 'backend'), generateStrapiAppOptions); |   await generateNewApp(join(rootPath, 'backend'), generateStrapiAppOptions); | ||||||
| 
 | 
 | ||||||
|   // Install frontend dependencies
 |   // Install frontend dependencies
 | ||||||
|   console.log(`Creating Strapi starter frontend at ${chalk.green(frontendPath)}.`); |   console.log(`Creating Strapi starter frontend at ${chalk.yellow(frontendPath)}.`); | ||||||
|   console.log(`Installing ${chalk.yellow(fullName)} starter`); |   console.log('Installing frontend dependencies'); | ||||||
|   await installWithLogs(frontendPath); |   await installWithLogs(frontendPath, { useYarn: hasYarnInstalled }); | ||||||
| 
 | 
 | ||||||
|   // Setup monorepo
 |   // Setup monorepo
 | ||||||
|   initPackageJson(rootPath, projectBasename); |   initPackageJson(rootPath, projectBasename, { useYarn: hasYarnInstalled }); | ||||||
| 
 | 
 | ||||||
|   // Add gitignore
 |   // Add gitignore
 | ||||||
|   try { |   try { | ||||||
| @ -161,12 +191,12 @@ module.exports = async function buildStarter(programArgs, program) { | |||||||
|     logger.warn(`Failed to create file: ${chalk.yellow('.gitignore')}`); |     logger.warn(`Failed to create file: ${chalk.yellow('.gitignore')}`); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   await installWithLogs(rootPath); |   await installWithLogs(rootPath, { useYarn: hasYarnInstalled }); | ||||||
| 
 | 
 | ||||||
|   if (!ciEnv.isCI) { |   if (!ciEnv.isCI) { | ||||||
|     await initGit(rootPath); |     await initGit(rootPath); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   console.log(chalk.green('Starting the app')); |   console.log(chalk.green('Starting the app')); | ||||||
|   await runApp(rootPath); |   await runApp(rootPath, { useYarn: hasYarnInstalled }); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -2,25 +2,27 @@ | |||||||
| 
 | 
 | ||||||
| const { execSync } = require('child_process'); | const { execSync } = require('child_process'); | ||||||
| const execa = require('execa'); | const execa = require('execa'); | ||||||
| const hasYarn = require('./has-yarn'); |  | ||||||
| const logger = require('./logger'); | const logger = require('./logger'); | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {string} path Path to directory (frontend, backend) |  * @param {string} path Path to directory (frontend, backend) | ||||||
|  |  * @param {Object} options | ||||||
|  |  * @param {boolean} options.useYarn Use yarn instead of npm | ||||||
|  */ |  */ | ||||||
| function runInstall(path) { | function runInstall(path, { useYarn } = {}) { | ||||||
|   if (hasYarn()) { |   return execa(useYarn ? 'yarn' : 'npm', ['install'], { | ||||||
|     return execa('yarn', ['install'], { |  | ||||||
|     cwd: path, |     cwd: path, | ||||||
|     stdin: 'ignore', |     stdin: 'ignore', | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|   return execa('npm', ['install'], { cwd: path, stdin: 'ignore' }); | /** | ||||||
| } |  * @param {string} rootPath | ||||||
| 
 |  * @param {Object} options | ||||||
| function runApp(rootPath) { |  * @param {boolean} options.useYarn | ||||||
|   if (hasYarn()) { |  */ | ||||||
|  | function runApp(rootPath, { useYarn } = {}) { | ||||||
|  |   if (useYarn) { | ||||||
|     return execa('yarn', ['develop'], { |     return execa('yarn', ['develop'], { | ||||||
|       stdio: 'inherit', |       stdio: 'inherit', | ||||||
|       cwd: rootPath, |       cwd: rootPath, | ||||||
|  | |||||||
| @ -1,97 +0,0 @@ | |||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| const tar = require('tar'); |  | ||||||
| const fetch = require('node-fetch'); |  | ||||||
| const parseGitUrl = require('git-url-parse'); |  | ||||||
| const chalk = require('chalk'); |  | ||||||
| 
 |  | ||||||
| const stopProcess = require('./stop-process'); |  | ||||||
| 
 |  | ||||||
| function parseShorthand(starter) { |  | ||||||
|   // Determine if it is comes from another owner
 |  | ||||||
|   if (starter.includes('/')) { |  | ||||||
|     const [owner, partialName] = starter.split('/'); |  | ||||||
|     const name = `strapi-starter-${partialName}`; |  | ||||||
|     return { |  | ||||||
|       name, |  | ||||||
|       fullName: `${owner}/${name}`, |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   const name = `strapi-starter-${starter}`; |  | ||||||
|   return { |  | ||||||
|     name, |  | ||||||
|     fullName: `strapi/${name}`, |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * @param {string} repo The full name of the repository. |  | ||||||
|  */ |  | ||||||
| async function getDefaultBranch(repo) { |  | ||||||
|   const response = await fetch(`https://api.github.com/repos/${repo}`); |  | ||||||
| 
 |  | ||||||
|   if (!response.ok) { |  | ||||||
|     stopProcess( |  | ||||||
|       `Could not find the starter information for ${chalk.yellow( |  | ||||||
|         repo |  | ||||||
|       )}. Make sure it is publicly accessible on github.` |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   const { default_branch } = await response.json(); |  | ||||||
|   return default_branch; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * @param {string} starter GitHub URL or shorthand to a starter project. |  | ||||||
|  */ |  | ||||||
| async function getRepoInfo(starter) { |  | ||||||
|   const { name, full_name: fullName, ref, filepath, protocols, source } = parseGitUrl(starter); |  | ||||||
| 
 |  | ||||||
|   if (protocols.length === 0) { |  | ||||||
|     const repoInfo = parseShorthand(starter); |  | ||||||
|     return { |  | ||||||
|       ...repoInfo, |  | ||||||
|       branch: await getDefaultBranch(repoInfo.fullName), |  | ||||||
|       usedShorthand: true, |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (source !== 'github.com') { |  | ||||||
|     stopProcess(`GitHub URL not found for: ${chalk.yellow(starter)}.`); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   let branch; |  | ||||||
|   if (ref) { |  | ||||||
|     // Append the filepath to the parsed ref since a branch name could contain '/'
 |  | ||||||
|     // If so, the rest of the branch name will be considered 'filepath' by 'parseGitUrl'
 |  | ||||||
|     branch = filepath ? `${ref}/${filepath}` : ref; |  | ||||||
|   } else { |  | ||||||
|     branch = await getDefaultBranch(fullName); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   return { name, fullName, branch }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * @param {string} repoInfo GitHub repository information (full name, branch...). |  | ||||||
|  * @param {string} tmpDir Path to the destination temporary directory. |  | ||||||
|  */ |  | ||||||
| async function downloadGitHubRepo(repoInfo, tmpDir) { |  | ||||||
|   const { fullName, branch, usedShorthand } = repoInfo; |  | ||||||
| 
 |  | ||||||
|   const codeload = `https://codeload.github.com/${fullName}/tar.gz/${branch}`; |  | ||||||
|   const response = await fetch(codeload); |  | ||||||
| 
 |  | ||||||
|   if (!response.ok) { |  | ||||||
|     const message = usedShorthand ? `using the shorthand` : `using the url`; |  | ||||||
|     stopProcess(`Could not download the repository ${message}: ${chalk.yellow(fullName)}.`); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   await new Promise(resolve => { |  | ||||||
|     response.body.pipe(tar.extract({ strip: 1, cwd: tmpDir })).on('close', resolve); |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = { getRepoInfo, downloadGitHubRepo }; |  | ||||||
| @ -0,0 +1,86 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | const path = require('path'); | ||||||
|  | const execa = require('execa'); | ||||||
|  | const chalk = require('chalk'); | ||||||
|  | const stopProcess = require('./stop-process'); | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Gets the package version on npm. Will fail if the package does not exist | ||||||
|  |  * @param {string} packageName Name to look up on npm, may include a specific version | ||||||
|  |  * @param {Object} options | ||||||
|  |  * @param {boolean} options.useYarn Yarn instead of npm | ||||||
|  |  * @returns {Object} | ||||||
|  |  */ | ||||||
|  | async function getPackageInfo(packageName, { useYarn } = {}) { | ||||||
|  |   // Use yarn if possible because it's faster
 | ||||||
|  |   if (useYarn) { | ||||||
|  |     const { stdout } = await execa.command(`yarn info ${packageName} --json`); | ||||||
|  |     const yarnInfo = JSON.parse(stdout); | ||||||
|  |     return { | ||||||
|  |       name: yarnInfo.data.name, | ||||||
|  |       version: yarnInfo.data.version, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Fallback to npm
 | ||||||
|  |   const { stdout } = await execa.command(`npm view ${packageName} name version --silent`); | ||||||
|  |   // Use regex to parse name and version from CLI result
 | ||||||
|  |   const [name, version] = stdout.match(/(?<=')(.*?)(?=')/gm); | ||||||
|  |   return { name, version }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Get the version and full package name of the starter | ||||||
|  |  * @param {string} starter - The name of the starter as provided by the user | ||||||
|  |  * @param {Object} options | ||||||
|  |  * @param {boolean} options.useYarn - Use yarn instead of npm | ||||||
|  |  * @returns {Object} - Full name and version of the starter package on npm | ||||||
|  |  */ | ||||||
|  | async function getStarterPackageInfo(starter, { useYarn } = {}) { | ||||||
|  |   // Check if starter is a shorthand
 | ||||||
|  |   try { | ||||||
|  |     const longhand = `@strapi/starter-${starter}`; | ||||||
|  |     const packageInfo = await getPackageInfo(longhand, { useYarn }); | ||||||
|  |     // Hasn't crashed so it is indeed a shorthand
 | ||||||
|  |     return packageInfo; | ||||||
|  |   } catch (error) { | ||||||
|  |     // Ignore error, we now know it's not a shorthand
 | ||||||
|  |   } | ||||||
|  |   // Fetch version of the non-shorthand package
 | ||||||
|  |   try { | ||||||
|  |     return getPackageInfo(starter, { useYarn }); | ||||||
|  |   } catch (error) { | ||||||
|  |     stopProcess(`Could not find package ${chalk.yellow(starter)} on npm`); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Download a starter package from the npm registry | ||||||
|  |  * @param {Object} packageInfo - Starter's npm package information | ||||||
|  |  * @param {string} packageInfo.name | ||||||
|  |  * @param {string} packageInfo.version | ||||||
|  |  * @param {string} parentDir - Path inside of which we install the starter | ||||||
|  |  * @param {Object} options | ||||||
|  |  * @param {boolean} options.useYarn - Use yarn instead of npm | ||||||
|  |  */ | ||||||
|  | async function downloadNpmStarter({ name, version }, parentDir, { useYarn } = {}) { | ||||||
|  |   // Download from npm, using yarn if possible
 | ||||||
|  |   if (useYarn) { | ||||||
|  |     await execa.command(`yarn add ${name}@${version} --no-lockfile --silent`, { | ||||||
|  |       cwd: parentDir, | ||||||
|  |     }); | ||||||
|  |   } else { | ||||||
|  |     await execa.command(`npm install ${name}@${version} --no-save --silent`, { | ||||||
|  |       cwd: parentDir, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Return the path of the actual starter
 | ||||||
|  |   const exactStarterPath = path.dirname( | ||||||
|  |     require.resolve(`${name}/package.json`, { paths: [parentDir] }) | ||||||
|  |   ); | ||||||
|  |   return exactStarterPath; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports = { getStarterPackageInfo, downloadNpmStarter }; | ||||||
| @ -2,12 +2,11 @@ | |||||||
| 
 | 
 | ||||||
| const execa = require('execa'); | const execa = require('execa'); | ||||||
| 
 | 
 | ||||||
| module.exports = function hasYarn() { | module.exports = async function hasYarn() { | ||||||
|   try { |   try { | ||||||
|     const { exitCode } = execa.sync('yarn --version', { shell: true }); |     const { exitCode } = await execa.commandSync('yarn --version', { shell: true }); | ||||||
| 
 | 
 | ||||||
|     if (exitCode === 0) return true; |     if (exitCode === 0) return true; | ||||||
|     return false; |  | ||||||
|   } catch (err) { |   } catch (err) { | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| const inquirer = require('inquirer'); | const inquirer = require('inquirer'); | ||||||
| const fetch = require('node-fetch'); |  | ||||||
| const yaml = require('js-yaml'); |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {string|null} projectName - The name/path of project |  * @param {string|null} projectName - The name/path of project | ||||||
| @ -55,50 +53,10 @@ module.exports = async function promptUser(projectName, starter, program) { | |||||||
|  * @returns Prompt question object |  * @returns Prompt question object | ||||||
|  */ |  */ | ||||||
| async function getStarterQuestion() { | async function getStarterQuestion() { | ||||||
|   const content = await getStarterData(); |   // Ask user to manually input his starter
 | ||||||
| 
 |   // TODO: find way to suggest the possible v4 starters
 | ||||||
|   // Fallback to manual input when fetch fails
 |  | ||||||
|   if (!content) { |  | ||||||
|   return { |   return { | ||||||
|     type: 'input', |     type: 'input', | ||||||
|       message: 'Please provide the GitHub URL for the starter you would like to use:', |     message: 'Please provide the npm package name of the starter you want to use:', | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 |  | ||||||
|   const choices = content.map(option => { |  | ||||||
|     const name = option.title.replace('Starter', ''); |  | ||||||
| 
 |  | ||||||
|     return { |  | ||||||
|       name, |  | ||||||
|       value: `https://github.com/${option.repo}`, |  | ||||||
|     }; |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   return { |  | ||||||
|     type: 'list', |  | ||||||
|     message: |  | ||||||
|       'Which starter would you like to use? (Starters are fullstack Strapi applications designed for a specific use case)', |  | ||||||
|     pageSize: choices.length, |  | ||||||
|     choices, |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * |  | ||||||
|  * @returns JSON starter data |  | ||||||
|  */ |  | ||||||
| async function getStarterData() { |  | ||||||
|   const response = await fetch( |  | ||||||
|     `https://api.github.com/repos/strapi/community-content/contents/starters/starters.yml` |  | ||||||
|   ); |  | ||||||
| 
 |  | ||||||
|   if (!response.ok) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   const { content } = await response.json(); |  | ||||||
|   const buff = Buffer.from(content, 'base64'); |  | ||||||
|   const stringified = buff.toString('utf-8'); |  | ||||||
| 
 |  | ||||||
|   return yaml.load(stringified); |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -34,7 +34,7 @@ async function getTemplatePackageInfo(template) { | |||||||
|   try { |   try { | ||||||
|     return getPackageInfo(template); |     return getPackageInfo(template); | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|     throw new Error(`Could not find package ${chalk.green('template.json')} on npm`); |     throw new Error(`Could not find package ${chalk.yellow(template)} on npm`); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								yarn.lock
									
									
									
									
									
								
							| @ -11567,22 +11567,7 @@ execa@1.0.0, execa@^1.0.0: | |||||||
|     signal-exit "^3.0.0" |     signal-exit "^3.0.0" | ||||||
|     strip-eof "^1.0.0" |     strip-eof "^1.0.0" | ||||||
| 
 | 
 | ||||||
| execa@5.0.0: | execa@5.1.1, execa@^5.0.0, execa@^5.1.1: | ||||||
|   version "5.0.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" |  | ||||||
|   integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== |  | ||||||
|   dependencies: |  | ||||||
|     cross-spawn "^7.0.3" |  | ||||||
|     get-stream "^6.0.0" |  | ||||||
|     human-signals "^2.1.0" |  | ||||||
|     is-stream "^2.0.0" |  | ||||||
|     merge-stream "^2.0.0" |  | ||||||
|     npm-run-path "^4.0.1" |  | ||||||
|     onetime "^5.1.2" |  | ||||||
|     signal-exit "^3.0.3" |  | ||||||
|     strip-final-newline "^2.0.0" |  | ||||||
| 
 |  | ||||||
| execa@5.1.1, execa@^5.0.0: |  | ||||||
|   version "5.1.1" |   version "5.1.1" | ||||||
|   resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" |   resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" | ||||||
|   integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== |   integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== | ||||||
| @ -12587,13 +12572,6 @@ git-up@^4.0.0: | |||||||
|     is-ssh "^1.3.0" |     is-ssh "^1.3.0" | ||||||
|     parse-url "^6.0.0" |     parse-url "^6.0.0" | ||||||
| 
 | 
 | ||||||
| git-url-parse@11.4.4: |  | ||||||
|   version "11.4.4" |  | ||||||
|   resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.4.4.tgz#5d747debc2469c17bc385719f7d0427802d83d77" |  | ||||||
|   integrity sha512-Y4o9o7vQngQDIU9IjyCmRJBin5iYjI5u9ZITnddRZpD7dcCFQj2sL2XuMNbLRE4b4B/4ENPsp2Q8P44fjAZ0Pw== |  | ||||||
|   dependencies: |  | ||||||
|     git-up "^4.0.0" |  | ||||||
| 
 |  | ||||||
| git-url-parse@^11.1.2: | git-url-parse@^11.1.2: | ||||||
|   version "11.6.0" |   version "11.6.0" | ||||||
|   resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.6.0.tgz#c634b8de7faa66498a2b88932df31702c67df605" |   resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.6.0.tgz#c634b8de7faa66498a2b88932df31702c67df605" | ||||||
| @ -15355,13 +15333,6 @@ js-yaml@3.x, js-yaml@^3.10.0, js-yaml@^3.13.1: | |||||||
|     argparse "^1.0.7" |     argparse "^1.0.7" | ||||||
|     esprima "^4.0.0" |     esprima "^4.0.0" | ||||||
| 
 | 
 | ||||||
| js-yaml@4.1.0: |  | ||||||
|   version "4.1.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" |  | ||||||
|   integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== |  | ||||||
|   dependencies: |  | ||||||
|     argparse "^2.0.1" |  | ||||||
| 
 |  | ||||||
| jsbn@~0.1.0: | jsbn@~0.1.0: | ||||||
|   version "0.1.1" |   version "0.1.1" | ||||||
|   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" |   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Alexandre BODIN
						Alexandre BODIN