UNPKG

frontity

Version:

Frontity cli and entry point to other packages

362 lines (361 loc) 14 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.subscribe = exports.revertProgress = exports.createGitignore = exports.initializeGit = exports.downloadFavicon = exports.installDependencies = exports.cloneStarterTheme = exports.createTsConfig = exports.createFrontitySettings = exports.createReadme = exports.createPackageJson = exports.ensureProjectDir = exports.normalizeOptions = void 0; const os_1 = require("os"); const path_1 = require("path"); const child_process_1 = require("child_process"); const util_1 = require("util"); const fs_extra_1 = require("fs-extra"); const tar_1 = require("tar"); const node_fetch_1 = __importDefault(require("node-fetch")); const ramda_1 = require("ramda"); const utils_1 = require("../utils"); const allowedExistingContent = [ "readme.md", "license", ".git", ".gitignore", ".ds_store", ]; const faviconUrl = "https://frontity.org/wp-content/plugins/frontity-favicon/favicon.ico"; /** * This function normalizes and validates options. * * @param defaultOptions - The default options. Defined in {@link * CreateCommandOptions}. * @param passedOptions - The options from the user. Defined in {@link * CreateCommandOptions}. * * @returns The final options, normalized. Defined in {@link * CreateCommandOptions}. */ const normalizeOptions = (defaultOptions, passedOptions) => { const options = (0, ramda_1.mergeRight)(defaultOptions, passedOptions); // Normalize and validate `name` option. options.name = options.name.replace(/[\s_-]+/g, "-").toLowerCase(); if (!(0, utils_1.isPackageNameValid)(options.name)) throw new Error("The name of the package is not valid. Please enter a valid one (only letters and dashes)."); return options; }; exports.normalizeOptions = normalizeOptions; /** * This function ensures the path exists and checks if it's empty or it's a new * repo. Also returns a boolean indicating if the directory existed already. * * @param path - The path where the project will be installed. * * @returns A promise that resolves once the check has been made. */ const ensureProjectDir = async (path) => { const dirExisted = await (0, fs_extra_1.pathExists)(path); if (dirExisted) { // Check if the directory is a new repo. const dirContent = await (0, fs_extra_1.readdir)(path); const notAllowedContent = dirContent.filter((content) => !allowedExistingContent.includes(content.toLowerCase())); // If it's not, throw. if (notAllowedContent.length) { throw new Error("The directory passed to `create` function is not empty"); } } else { await (0, fs_extra_1.ensureDir)(path); } return dirExisted; }; exports.ensureProjectDir = ensureProjectDir; /** * Create a `package.json` file. * * @param name - The name of the project. * @param theme - The theme that will be cloned and installed locally. * @param path - The path where the file will be created. * @param typescript - Indicates if the typescript flag is active. */ const createPackageJson = async (name, theme, path, typescript) => { const packages = [ "frontity", "@frontity/core", "@frontity/wp-source", "@frontity/tiny-router", "@frontity/html2react", ]; // Add Frontity packages to the dependencies. const dependencies = (await Promise.all(packages.map(async (pkg) => { // Get the version of each package. const version = await (0, utils_1.fetchPackageVersion)(pkg); return [pkg, `^${version}`]; }))).reduce((final, current) => { // Reduce the packages into a dependecies object. final[current[0]] = current[1]; return final; }, {}); // Add the starter theme to the dependencies. const themeName = (theme.match(/\/?([\w-]+)$/) || ["", ""])[1]; dependencies[theme] = `./packages/${themeName}`; const packageJson = { name, version: "1.0.0", private: true, description: "Frontity project", keywords: ["frontity"], engines: { node: ">=10.0.0", npm: ">=6.0.0", }, scripts: { dev: "frontity dev", build: "frontity build", serve: "frontity serve", }, prettier: {}, dependencies, }; // If the typescript flag is active, add the needed devDependencies. if (typescript) { const devPackages = ["@types/react", "@types/node-fetch"]; const devDependencies = (await Promise.all(devPackages.map(async (pkg) => { // Get the version of each package. const version = await (0, utils_1.fetchPackageVersion)(pkg); return [pkg, `^${version}`]; }))).reduce((final, current) => { // Reduce the packages into a dependecies object. final[current[0]] = current[1]; return final; }, {}); packageJson.devDependencies = devDependencies; } const filePath = (0, path_1.resolve)(path, "package.json"); const fileData = `${JSON.stringify(packageJson, null, 2)}${os_1.EOL}`; await (0, fs_extra_1.writeFile)(filePath, fileData); }; exports.createPackageJson = createPackageJson; /** * Create a `README.md` file. * * @param name - The name of the project. * @param path - The path where the file will be created. */ const createReadme = async (name, path) => { const fileTemplate = await (0, fs_extra_1.readFile)((0, path_1.resolve)(__dirname, "../../templates/README.md"), { encoding: "utf8", }); const filePath = (0, path_1.resolve)(path, "README.md"); const fileData = fileTemplate.replace(/\$name\$/g, name); await (0, fs_extra_1.writeFile)(filePath, fileData); }; exports.createReadme = createReadme; /** * Create a `frontity.settings` file. * * @param extension - The extension of the file, either `.js` or `.ts`. * @param name - The name of the project. * @param path - The path where the file will be created. * @param theme - The theme installed in the project. */ const createFrontitySettings = async (extension, name, path, theme) => { const frontitySettings = { name, state: { frontity: { url: "https://test.frontity.org", title: "Test Frontity Blog", description: "WordPress installation for Frontity development", }, }, packages: [ { name: theme, state: { theme: { menu: [ ["Home", "/"], ["Nature", "/category/nature/"], ["Travel", "/category/travel/"], ["Japan", "/tag/japan/"], ["About Us", "/about-us/"], ], featured: { showOnList: false, showOnPost: false, }, }, }, }, { name: "@frontity/wp-source", state: { source: { url: "https://test.frontity.org", }, }, }, "@frontity/tiny-router", "@frontity/html2react", ], }; const fileTemplate = await (0, fs_extra_1.readFile)((0, path_1.resolve)(__dirname, `../../templates/settings-${extension}-template`), { encoding: "utf8" }); const filePath = (0, path_1.resolve)(path, `frontity.settings.${extension}`); const fileData = fileTemplate.replace(/\$([\w-]+)\$/g, (_match, key) => { if (key === "settings") return JSON.stringify(frontitySettings, null, 2); if (key === "theme") return theme; }); await (0, fs_extra_1.writeFile)(filePath, fileData); }; exports.createFrontitySettings = createFrontitySettings; /** * Create a `tsconfig.json` file. * * @param path - The path where the file will be created. */ const createTsConfig = async (path) => { const fileTemplate = await (0, fs_extra_1.readFile)((0, path_1.resolve)(__dirname, "../../templates/tsconfig.json"), { encoding: "utf8", }); const filePath = (0, path_1.resolve)(path, "tsconfig.json"); await (0, fs_extra_1.writeFile)(filePath, fileTemplate); }; exports.createTsConfig = createTsConfig; /** * Clone the starter theme. * * @param theme - The name of the theme. * @param path - The path where it needs to be installed. */ const cloneStarterTheme = async (theme, path) => { const packageJsonPath = (0, path_1.resolve)(path, "./package.json"); const packageJson = JSON.parse(await (0, fs_extra_1.readFile)(packageJsonPath, { encoding: "utf8" })); const themePath = (0, path_1.resolve)(path, packageJson.dependencies[theme]); await (0, fs_extra_1.ensureDir)(themePath); if (!(0, utils_1.isThemeNameValid)(theme)) throw new Error("The name of the theme is not a valid npm package name."); await (0, util_1.promisify)(child_process_1.exec)(`npm pack ${theme}`, { cwd: themePath }); const tarball = (await (0, fs_extra_1.readdir)(themePath)).find((file) => /\.tgz$/.test(file)); const tarballPath = (0, path_1.resolve)(themePath, tarball); await (0, tar_1.extract)({ cwd: themePath, file: tarballPath, strip: 1 }); await (0, fs_extra_1.remove)(tarballPath); }; exports.cloneStarterTheme = cloneStarterTheme; /** * Install the Frontity packages. * * @param path - The location where `npm install` should be run. */ const installDependencies = async (path) => { await (0, util_1.promisify)(child_process_1.exec)("npm install", { cwd: path }); }; exports.installDependencies = installDependencies; /** * Downlaod the favicon file. * * @param path - The path where the favicon should be downloaded. */ const downloadFavicon = async (path) => { const response = await (0, node_fetch_1.default)(faviconUrl); const fileStream = (0, fs_extra_1.createWriteStream)((0, path_1.resolve)(path, "favicon.ico")); response.body.pipe(fileStream); await new Promise((resolve) => fileStream.on("finish", resolve)); }; exports.downloadFavicon = downloadFavicon; /** * Initializes a new git repository. * * @param path - The path where git should be initialized. */ const initializeGit = async (path) => { try { (0, child_process_1.execSync)("git init", { cwd: path, stdio: "ignore" }); (0, child_process_1.execSync)("git add .", { cwd: path, stdio: "ignore" }); (0, child_process_1.execSync)('git commit -m "Initialized with Frontity"', { cwd: path, stdio: "ignore", }); } catch (e) { // If there is any issue we want to revert to "pre-git" state await (0, fs_extra_1.remove)((0, path_1.resolve)(path, ".git")); // Rethrow the error so that it can be caught by the `create` command. throw e; } }; exports.initializeGit = initializeGit; /** * Creates a .gitignore file. * * @param path - The path where .gitignore file should be created. * @returns - A promise which resolves with a cleanup function which * should be called if any subsequent step fails. */ const createGitignore = async (path) => { const fileTemplate = await (0, fs_extra_1.readFile)((0, path_1.resolve)(__dirname, "../../templates/gitignore-template"), { encoding: "utf8", }); const gitignorePath = (0, path_1.resolve)(path, ".gitignore"); const gitignoreExists = await (0, fs_extra_1.pathExists)(gitignorePath); if (gitignoreExists) { // Keep the existing .gitignore in memory in case we need to revert to it later. const backupGitignore = await (0, fs_extra_1.readFile)(gitignorePath, { encoding: "utf8", }); // Append if there's already a `.gitignore` file (0, fs_extra_1.appendFileSync)(gitignorePath, "node_modules\nbuild"); // Return a "cleanup" function return async () => { await (0, fs_extra_1.writeFile)(gitignorePath, backupGitignore); }; } else { await (0, fs_extra_1.writeFile)(gitignorePath, fileTemplate); // Return a "cleanup" function return async () => { // Remove the .gitignore file await (0, fs_extra_1.remove)(gitignorePath); }; } }; exports.createGitignore = createGitignore; /** * Remove the files and directories created with `frontity create` in case there * was a problem and we need to revert everything. * * @param dirExisted - If the directory existed already. * @param path - The path of the direcotry. */ const revertProgress = async (dirExisted, path) => { if (dirExisted) { const content = await (0, fs_extra_1.readdir)(path); const removableContent = content .filter((item) => !allowedExistingContent.includes(item.toLowerCase())) .map((item) => (0, path_1.resolve)(path, item)); for (const content of removableContent) await (0, fs_extra_1.remove)(content); } else { await (0, fs_extra_1.remove)(path); } }; exports.revertProgress = revertProgress; /** * Check if an email is valid or not. * * @param email - The email to be checked. * * @returns True or false depending if the email is valid. */ // const isEmailValid = (email: string): boolean => // /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,63}$/i.test(email); /** * Subscribe an email to the newsletter service. * * @param email - The email to be subscribed. * * @returns The response of the subscription. */ const subscribe = async () => { throw new Error("Frontity newsletter is currently disabled"); }; exports.subscribe = subscribe;