Use npm to host templates instead of github

This commit is contained in:
Rémi de Juvigny 2021-09-27 19:16:08 +02:00
parent 0cc1c0bea0
commit 3656d7b181
4 changed files with 88 additions and 113 deletions

View File

@ -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 };

View 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 };

View File

@ -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');

View File

@ -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": {