create-conjoon
Version:
A scaffolding utility to help you instantly set up a functional conjoon app.
364 lines (299 loc) • 14.6 kB
JavaScript
/**
* conjoon
* create-conjoon
* Copyright (C) 2022-2023 Thorsten Suckow-Homberg https://github.com/conjoon/create-conjoon
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* 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 SOFTWARE.
*/
/**
* This script was inspired by the great work of the docusaurus-team over at facebook/meta!
*/
import fs from "fs-extra";
import path from "path";
import shell from "shelljs";
import supportsColor from "supports-color";
import {fileURLToPath} from "url";
import logger from "@docusaurus/logger";
import * as client from "./prompts.js";
import * as util from "./utils.js";
import {printMergeMisses} from "./utils.js";
import got from "got";
import os from "os";
import AdmZip from "adm-zip";
import {execSync as runChild} from "child_process";
/**
* @param {Boolean} isExternal true if this script is not used with `npm init @conjoon/conjoon`,
* but npx create-conjoon directly
* @returns {Promise<void>}
*/
export default async function init (isExternal) {
/**
*
* @type {Object} clientConfig
* @type {String} clientConfig.installationType
* @type {String} clientConfig.siteName
* @type {String} clientConfig.targetDir
* @type {String} clientConfig.version
*/
const clientConfig = {};
const promptForTargetDir = true;
/**
* siteName used for this installation. Will be prompted later on,
* otherwise the siteName of an existing application is used.
* @type {string}
*/
clientConfig.siteName = "conjoon";
// +--------------------------------------------
// | 1. installationType
// +--------------------------------------------
clientConfig.installationType = await client.getInstallationType();
logger.info`Using "${clientConfig.installationType}" for installation.`;
// +--------------------------------------------
// | 2. require target directory for installation
// +--------------------------------------------
clientConfig.targetDir = await client.getTargetDir(
`./${encodeURIComponent(clientConfig.siteName)}`,
clientConfig.installationType
);
clientConfig.targetDir = path.resolve(clientConfig.targetDir);
// +--------------------------------------------
// | 3. choose version
// +--------------------------------------------
logger.info`Fetching available versions of name=${"conjoon"} ...`;
// do not list beta releases
let versions = runChild("npm view @conjoon/conjoon versions --json").toString(), latest;
if (!versions) {
logger.error`Could not retrieve versions. Exiting...`;
process.exit(1);
}
const preRelease = version => version.includes("beta") || version.includes("alpha");
versions = JSON.parse(versions).filter(version => !preRelease(version));
latest = versions[versions.length - 1];
logger.info`Latest release of name=${"conjoon"} is name=${latest}`;
clientConfig.version = await client.getVersion(versions);
const
debug = false,
pkgManager = "npm",
cdpath = path.relative(".", clientConfig.targetDir);
// +--------------------------------------------
// | 4. create target directory for installation
// +--------------------------------------------
let overwriteExistingRelease = false;
if (clientConfig.installationType === "release") {
if (fs.existsSync(clientConfig.targetDir)) {
overwriteExistingRelease = await client.confirmOverwriteDir(clientConfig.targetDir);
if (overwriteExistingRelease !== true) {
logger.error`The path path=${clientConfig.targetDir} already exists. Exiting...`;
process.exit(1);
}
}
} else {
try {
if (!fs.existsSync(clientConfig.targetDir)) {
fs.mkdirSync(clientConfig.targetDir);
} else {
logger.error`The path path=${clientConfig.targetDir} already exists. Exiting...`;
process.exit(1);
}
} catch (err) {
logger.error(err);
process.exit(1);
}
}
debug && logger.warn`debug set to name=${"true"}`;
logger.info`Using subdue=${isExternal ? "external" : "internal"} initializer, resolving paths...`;
let cwd = fileURLToPath(new URL(`${isExternal ? "" : "../"}../../../`, import.meta.url));
if (debug === true) {
cwd = "./";
}
if (clientConfig.installationType === "npm") { // dive into npm install
/**
* Request the siteName from the user, so the config can be updated
* accordingly.
* @type {String}
*/
clientConfig.siteName = await client.getSiteName();
// +--------------------------------------------
// | Download from npm
// +--------------------------------------------
logger.info`Running code=${`${pkgManager} i --prefix ${cwd} @conjoon/conjoon@${clientConfig.version}`}`;
if (shell.exec(`${pkgManager} i --prefix ${cwd} @conjoon/conjoon@${clientConfig.version}`, {
env: {
...process.env,
...(supportsColor.stdout ? { FORCE_COLOR: "1" } : {})
}
}).code !== 0) {
logger.error`Installing name=${"conjoon"} failed. We suggest you check for the correct version.`;
logger.info`Removing path=${clientConfig.targetDir} ...`;
fs.removeSync(clientConfig.targetDir);
process.exit(1);
}
let source = `${cwd}/node_modules/@conjoon/conjoon`;
if (!fs.existsSync(source)) {
logger.error`Target directory path=${source} does not exists.`;
logger.info`Removing path=${clientConfig.targetDir} and exiting...`;
fs.removeSync(clientConfig.targetDir);
process.exit(1);
}
logger.info`Copying release name=${`v${clientConfig.version}`} to path=${clientConfig.targetDir}...`;
fs.copySync(source, clientConfig.targetDir);
shell.cd(clientConfig.targetDir);
fs.moveSync(`${clientConfig.targetDir}/package.json`, `${clientConfig.targetDir}/package.json.tmp`);
logger.info`Installing name=${"webpack"}...`;
if (debug !== true && shell.exec(`${pkgManager} i --silent --prefix ${clientConfig.targetDir} webpack-dev-server@~3.8.0 webpack-cli@~3.3.6`, {
env: {
...process.env,
...(supportsColor.stdout ? { FORCE_COLOR: "1" } : {})
}
}).code !== 0) {
logger.error`Installing name=${"webpack"} failed, but base installation available at path=${cdpath}`;
process.exit(0);
}
logger.info("Cleaning up...");
fs.removeSync(`${clientConfig.targetDir}/package.json`);
fs.moveSync(`${clientConfig.targetDir}/package.json.tmp`, `${clientConfig.targetDir}/package.json`);
logger.info("Updating package information...");
util.updateApplicationConfigsSync(clientConfig.targetDir, clientConfig);
util.updatePackageJsonSync(`${clientConfig.targetDir}/package.json`, clientConfig);
} else if (clientConfig.installationType === "release") { // dive into release install
let prodFileBackup, devFileBackup, backupDir, backupTmpDir,prodConfig, devConfig, prodFile, devFile;
// +--------------------------------------------
// | If installatione exist, tmp-save configs.
// +--------------------------------------------
if (overwriteExistingRelease === true) {
logger.info("Looking for existing configuration...");
// +--------------------------------------------
// | 1.2. If existing installation was found, try to read out
// | config from existing config files of a release only
// +--------------------------------------------
// read in exsiting config from a possible existing release built.
prodFile = `${clientConfig.targetDir}/desktop/resources/conjoon.conf.json`;
devFile = `${clientConfig.targetDir}/desktop/resources/conjoon.dev.conf.json`;
if (fs.existsSync(prodFile)) {
logger.info`Found an existing configuration file at path=${prodFile}...`;
prodConfig = fs.readJsonSync(prodFile);
}
if (fs.existsSync(devFile)) {
logger.info`Found an existing dev-configuration file at path=${devFile}...`;
devConfig = fs.readJsonSync(devFile);
}
// read out version we are overwriting
const currVersionFile = `${clientConfig.targetDir}/generatedFiles/desktop.json`;
if (fs.existsSync(currVersionFile)) {
const backupMarker = fs.readJsonSync(currVersionFile)?.version;
backupDir = `${clientConfig.targetDir}/_backup/`;
prodFileBackup = `${backupDir}/conjoon.conf.${backupMarker}.json`;
devFileBackup = `${backupDir}/conjoon.dev.conf.${backupMarker}.json`;
backupTmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "conjoon"));
if (fs.existsSync(backupDir)) {
fs.copySync(backupDir, backupTmpDir);
}
}
fs.removeSync(`${clientConfig.targetDir}`, { recursive: true });
} else {
// if installation did not exist, require the siteName now
clientConfig.siteName = await client.getSiteName();
}
// +--------------------------------------------
// | Download as release from Github
// +--------------------------------------------
// url https://github.com/conjoon/conjoon/releases/download/v1.1.0-beta.0/conjoon.build.refs.tags.v1.1.0-beta.0.zip
const url = `https://github.com/conjoon/conjoon/releases/download/v${clientConfig.version}/conjoon.build.refs.tags.v${clientConfig.version}.zip`;
logger.info(`Downloading release: v${clientConfig.version}`);
const tmpDir = os.tmpdir();
const zipFile = `${tmpDir}/conjoon.v${clientConfig.version}.zip`;
let response;
try {
response = await got(url, {
responseType: "buffer"
});
} catch (e) {
logger.error(`Downloading release: v${clientConfig.version} failed, please check that ${url} still exists.`);
process.exit(1);
}
fs.writeFileSync(zipFile, response.body);
try {
const zip = new AdmZip(zipFile);
const outputDir = `${clientConfig.targetDir}`;
zip.extractAllTo(outputDir);
logger.info(`Extracted to "${outputDir}" successfully`);
if (fs.existsSync(backupTmpDir)) {
fs.copySync(backupTmpDir, backupDir);
}
if (prodConfig || devConfig) {
if (devConfig) {
logger.info`Creating backup for ${devFile}`;
!fs.existsSync(backupDir) && fs.mkdirSync(backupDir);
fs.outputFileSync(devFileBackup, `${JSON.stringify(devConfig, null, 4)}`);
logger.info`... backup available at ${devFileBackup}.`;
const mergeMisses = util.mergeDevConfigSync(`${clientConfig.targetDir}/desktop`, devConfig);
printMergeMisses(mergeMisses);
}
if (prodConfig) {
logger.info`Creating backup for ${prodFile}`;
!fs.existsSync(backupDir) && fs.mkdirSync(backupDir);
fs.outputFileSync(prodFileBackup, `${JSON.stringify(prodConfig, null, 4)}`);
logger.info`... backup available at ${prodFileBackup}.`;
const mergeMisses = util.mergeProdConfigSync(`${clientConfig.targetDir}/desktop`, prodConfig);
printMergeMisses(mergeMisses);
}
} else {
util.updateApplicationConfigsSync(`${clientConfig.targetDir}/desktop`, clientConfig);
}
} catch (e) {
logger.error(`Something went wrong. ${e}`);
process.exit(0);
}
if (fs.existsSync(backupTmpDir)) {
fs.removeSync(backupTmpDir, { recursive: true });
}
}
if (clientConfig.installationType === "npm") {
/* eslint-disable */
logger.success`
Created name=${cdpath}.
We recommend you get your development environment up and running with
code=${`cd ${cdpath}`}
code=${`${pkgManager} i`}
to install the required dependencies.
You can then run several commands:
code=${`${pkgManager} start`}:
- serves this instance locally
code=${`${pkgManager} run build`}:
- bundles your name=${`conjoon`} installation into static files
for production, followed by
code=${`${pkgManager} run stage`}
to serve a production build locally.
Make sure to read the documentation at url=${`https://conjoon.org`}.
Happy coding!
`;
/* eslint-enable */
} else {
/* eslint-disable */
logger.success`
Created name=${cdpath}.
Inside that directory, you will find an official release build of conjoon.
Make sure to read the documentation at url=${`https://conjoon.org`}.
Happy messaging!
`;
/* eslint-enable */
}
process.exit(0);
}