mirror of
https://github.com/strapi/strapi.git
synced 2025-12-29 08:04:51 +00:00
Use npm to host templates instead of github
This commit is contained in:
parent
0cc1c0bea0
commit
3656d7b181
@ -1,94 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const fetch = require('node-fetch');
|
||||
const tar = require('tar');
|
||||
const parseGitUrl = require('git-url-parse');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const stopProcess = require('./stop-process');
|
||||
|
||||
function parseShorthand(template) {
|
||||
// Determine if it is comes from another owner
|
||||
if (template.includes('/')) {
|
||||
const [owner, partialName] = template.split('/');
|
||||
const name = `strapi-template-${partialName}`;
|
||||
return {
|
||||
name,
|
||||
fullName: `${owner}/${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
const name = `strapi-template-${template}`;
|
||||
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 information for ${chalk.yellow(
|
||||
repo
|
||||
)}. Make sure it is publicly accessible on github.`
|
||||
);
|
||||
}
|
||||
|
||||
const { default_branch } = await response.json();
|
||||
return default_branch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} template GitHub URL or shorthand to a template project.
|
||||
*/
|
||||
async function getRepoInfo(template) {
|
||||
const { name, full_name: fullName, ref, filepath, protocols, source } = parseGitUrl(template);
|
||||
|
||||
if (protocols.length === 0) {
|
||||
const repoInfo = parseShorthand(template);
|
||||
return {
|
||||
...repoInfo,
|
||||
branch: await getDefaultBranch(repoInfo.fullName),
|
||||
usedShorthand: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (source !== 'github.com') {
|
||||
stopProcess(`GitHub URL not found for: ${chalk.yellow(template)}.`);
|
||||
}
|
||||
|
||||
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) {
|
||||
// Download from GitHub
|
||||
const { fullName, branch } = repoInfo;
|
||||
const codeload = `https://codeload.github.com/${fullName}/tar.gz/${branch}`;
|
||||
const response = await fetch(codeload);
|
||||
if (!response.ok) {
|
||||
throw Error(`Could not download the ${chalk.yellow(fullName)} repository.`);
|
||||
}
|
||||
|
||||
await new Promise(resolve => {
|
||||
response.body.pipe(tar.extract({ strip: 1, cwd: tmpDir })).on('close', resolve);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { getRepoInfo, downloadGitHubRepo };
|
||||
57
packages/generators/app/lib/utils/fetch-npm-template.js
Normal file
57
packages/generators/app/lib/utils/fetch-npm-template.js
Normal file
@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
const execSync = require('child_process').execSync;
|
||||
const path = require('path');
|
||||
// const fetch = require('node-fetch');
|
||||
const chalk = require('chalk');
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @returns {Object}
|
||||
*/
|
||||
function getPackageInfo(packageName) {
|
||||
const npmInfo = execSync(`npm view ${packageName} name version --silent`).toString();
|
||||
// Use regex to parse name and version from CLI result
|
||||
const [name, version] = npmInfo.match(/(?<=')(.*?)(?=')/gm);
|
||||
return { name, version };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} template - The name of the template as provided by the user. Can be a shorthand.
|
||||
* @returns {Object} - The full name of the template package's name on npm
|
||||
*/
|
||||
async function getTemplatePackageInfo(template) {
|
||||
// Check if template is a s horthand
|
||||
try {
|
||||
const longhand = `@strapi/template-${template}`;
|
||||
const packageInfo = getPackageInfo(longhand);
|
||||
// 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(template);
|
||||
} catch (error) {
|
||||
throw new Error(`Could not find package ${chalk.green('template.json')} on npm`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} packageInfo - Template's npm package information
|
||||
* @param {string} packageInfo.name
|
||||
* @param {string} packageInfo.version
|
||||
* @param {string} parentDir - Path inside of which we install the template.
|
||||
*/
|
||||
function downloadNpmTemplate({ name, version }, parentDir) {
|
||||
// Download from npm
|
||||
execSync(`cd ${parentDir} && npm install ${name}@${version} --no-save --silent`).toString();
|
||||
|
||||
// Return the path of the actual template
|
||||
const exactTemplatePath = path.resolve(parentDir, 'node_modules', name);
|
||||
return exactTemplatePath;
|
||||
}
|
||||
|
||||
module.exports = { getTemplatePackageInfo, downloadNpmTemplate };
|
||||
@ -5,8 +5,7 @@ const path = require('path');
|
||||
const fse = require('fs-extra');
|
||||
const _ = require('lodash');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const { getRepoInfo, downloadGitHubRepo } = require('./fetch-github');
|
||||
const { getTemplatePackageInfo, downloadNpmTemplate } = require('./fetch-npm-template');
|
||||
|
||||
// Specify all the files and directories a template can have
|
||||
const allowFile = Symbol();
|
||||
@ -15,6 +14,7 @@ const allowedTemplateContents = {
|
||||
'README.md': allowFile,
|
||||
'.env.example': allowFile,
|
||||
'package.json': allowFile,
|
||||
config: allowChildren,
|
||||
src: {
|
||||
admin: allowChildren,
|
||||
api: allowChildren,
|
||||
@ -30,34 +30,37 @@ const allowedTemplateContents = {
|
||||
};
|
||||
|
||||
/**
|
||||
* merge template with new project being created
|
||||
* Merge template with new project being created
|
||||
* @param {string} scope project creation params
|
||||
* @param {string} rootPath project path
|
||||
*/
|
||||
module.exports = async function mergeTemplate(scope, rootPath) {
|
||||
// Parse template info
|
||||
const repoInfo = await getRepoInfo(scope.template);
|
||||
const { fullName } = repoInfo;
|
||||
console.log(`Installing ${chalk.yellow(fullName)} template.`);
|
||||
const templatePackageInfo = await getTemplatePackageInfo(scope.template);
|
||||
console.log(templatePackageInfo);
|
||||
console.log(`Installing ${chalk.yellow(templatePackageInfo.name)} template.`);
|
||||
|
||||
// Download template repository to a temporary directory
|
||||
const templatePath = await fse.mkdtemp(path.join(os.tmpdir(), 'strapi-'));
|
||||
await downloadGitHubRepo(repoInfo, templatePath);
|
||||
const templateParentPath = await fse.mkdtemp(path.join(os.tmpdir(), 'strapi-'));
|
||||
const templatePath = downloadNpmTemplate(templatePackageInfo, templateParentPath);
|
||||
|
||||
// Make sure the downloaded template matches the required format
|
||||
const { templateConfig } = await checkTemplateRootStructure(templatePath, scope);
|
||||
await checkTemplateContentsStructure(path.resolve(templatePath, 'template'));
|
||||
|
||||
// Merge contents of the template in the project
|
||||
const fullTemplateUrl = `https://github.com/${fullName}`;
|
||||
await mergePackageJSON(rootPath, templateConfig, fullTemplateUrl);
|
||||
await mergePackageJSON({ rootPath, templateConfig, templatePackageInfo });
|
||||
await mergeFilesAndDirectories(rootPath, templatePath);
|
||||
|
||||
// Delete the downloaded template repo
|
||||
await fse.remove(templatePath);
|
||||
await fse.remove(templateParentPath);
|
||||
};
|
||||
|
||||
// Make sure the template has the required top-level structure
|
||||
/**
|
||||
* Make sure the template has the required top-level structure
|
||||
* @param {string} templatePath - Path of the locally downloaded template
|
||||
* @param {Object} scope - Information about the Strapi app's config
|
||||
*/
|
||||
async function checkTemplateRootStructure(templatePath, scope) {
|
||||
// Make sure the root of the repo has a template.json or a template.js file
|
||||
const templateJsonPath = path.join(templatePath, 'template.json');
|
||||
@ -118,7 +121,10 @@ async function checkTemplateRootStructure(templatePath, scope) {
|
||||
return { templateConfig };
|
||||
}
|
||||
|
||||
// Traverse template tree to make sure each file and folder is allowed
|
||||
/**
|
||||
* Traverse template tree to make sure each file and folder is allowed
|
||||
* @param {string} templateContentsPath
|
||||
*/
|
||||
async function checkTemplateContentsStructure(templateContentsPath) {
|
||||
// Recursively check if each item in a directory is allowed
|
||||
const checkPathContents = (pathToCheck, parents) => {
|
||||
@ -168,8 +174,16 @@ async function checkTemplateContentsStructure(templateContentsPath) {
|
||||
checkPathContents(templateContentsPath, []);
|
||||
}
|
||||
|
||||
// Merge the template's template.json into the Strapi project's package.json
|
||||
async function mergePackageJSON(rootPath, templateConfig, templateUrl) {
|
||||
/**
|
||||
* Merge the template's template.json into the Strapi project's package.json
|
||||
* @param {Object} config
|
||||
* @param {string} config.rootPath
|
||||
* @param {string} config.templateConfig
|
||||
* @param {Object} config.templatePackageInfo - Info about the template's package on npm
|
||||
* @param {Object} config.templatePackageInfo.name - The name of the template's package on npm
|
||||
* @param {Object} config.templatePackageInfo.version - The name of the template's package on npm
|
||||
*/
|
||||
async function mergePackageJSON({ rootPath, templateConfig, templatePackageName }) {
|
||||
// Import the package.json as an object
|
||||
const packageJSON = require(path.resolve(rootPath, 'package.json'));
|
||||
|
||||
@ -187,7 +201,7 @@ async function mergePackageJSON(rootPath, templateConfig, templateUrl) {
|
||||
const mergedConfig = _.merge(packageJSON, templateConfig.package);
|
||||
|
||||
// Add starter info to package.json
|
||||
_.set(mergedConfig, 'strapi.template', templateUrl);
|
||||
_.set(mergedConfig, 'strapi.template', templatePackageName);
|
||||
|
||||
// Save the merged config as the new package.json
|
||||
const packageJSONPath = path.join(rootPath, 'package.json');
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/generate-new",
|
||||
"version": "3.6.8",
|
||||
"version": "4.0.0-next.13",
|
||||
"description": "Generate a new Strapi application.",
|
||||
"homepage": "https://strapi.io",
|
||||
"keywords": [
|
||||
@ -17,13 +17,11 @@
|
||||
"chalk": "^4.1.1",
|
||||
"execa": "^1.0.0",
|
||||
"fs-extra": "^9.1.0",
|
||||
"git-url-parse": "^11.4.4",
|
||||
"inquirer": "^6.3.1",
|
||||
"lodash": "4.17.21",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-machine-id": "^1.1.10",
|
||||
"ora": "^5.4.0",
|
||||
"tar": "6.1.11",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user