diff --git a/.gitignore b/.gitignore index 88b3bfc..acb715d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ dist node_modules yarn.lock +package-lock.json diff --git a/packages/create-waku-app/.gitignore b/packages/create-waku-app/.gitignore new file mode 100644 index 0000000..577e9e6 --- /dev/null +++ b/packages/create-waku-app/.gitignore @@ -0,0 +1,2 @@ +examples +package-lock.json \ No newline at end of file diff --git a/packages/create-waku-app/README.md b/packages/create-waku-app/README.md new file mode 100644 index 0000000..6427100 --- /dev/null +++ b/packages/create-waku-app/README.md @@ -0,0 +1,12 @@ +## @waku/create-app + +This package is here to help you bootstrap your next Waku dapp. + +Usage: +- `yarn create @waku/app [options] ` +- `npx @waku/create-app [options] ` + +For options you can specify template from which to initialize your app. Template correlates directly to the name of example you can see in this repository. + +#### How to add support for new example: +Extend `wakuExamples` property defined in `package.json` in this package with the name of the example and relative path to it where folder of the example should be the same as it's name. \ No newline at end of file diff --git a/packages/create-waku-app/build.js b/packages/create-waku-app/build.js new file mode 100644 index 0000000..5e7d272 --- /dev/null +++ b/packages/create-waku-app/build.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node +const path = require("path"); +const fs = require("fs-extra"); + +const packageJson = require("./package.json"); +const examplesFolder = path.resolve("./examples"); + +async function run() { + fs.ensureDirSync(examplesFolder); + + const supportedExamples = Object.entries(packageJson.wakuExamples); + + console.log("Started copying supported Waku examples."); + + const copyPromises = supportedExamples.map(async ([name, relativePath]) => { + const resolvedPath = path.resolve(__dirname, relativePath); + const destinationPath = path.resolve(examplesFolder, name); + + try { + await fs.copy(resolvedPath, destinationPath, { filter: nodeModulesFiler }); + } catch (error) { + console.error(`Failed to copy example ${name} to ${destinationPath} with ${error.message}`); + throw Error(error.message); + } + }); + + try { + await Promise.all(copyPromises); + console.log("Finished copying examples."); + } catch (error) { + console.error("Failed to copy examples due to " + error.message); + } +} + +function nodeModulesFiler(src) { + if (src.includes("node_modules")) { + return false; + } + + return true; +} + +run(); diff --git a/packages/create-waku-app/createApp.js b/packages/create-waku-app/createApp.js new file mode 100644 index 0000000..e7316b0 --- /dev/null +++ b/packages/create-waku-app/createApp.js @@ -0,0 +1,121 @@ +const path = require("path"); +const fs = require("fs-extra"); +const execSync = require("child_process").execSync; + +const { Command } = require("commander"); +const validateProjectName = require("validate-npm-package-name"); + +const DEFAULT_TEMPLATE = "web-chat"; +const supportedExamplesDir = path.resolve(__dirname, "./examples"); + +const init = (name, description, version, supportedExamples) => { + let appName; + const program = new Command() + .name(name) + .description(description) + .version(version, "-v, --version", "output the version number") + .arguments("", "Project directory to initialize Waku app") + .action(_appName => { + appName = _appName; + }) + .option( + "-t, --template ", + "specify a template for the created project" + ) + .allowUnknownOption() + .parse(); + + const options = program.opts(); + const template = options.template || DEFAULT_TEMPLATE; + + if (!supportedExamples[template]) { + const supportedExamplesMessage = Object.keys(supportedExamples).reduce((acc, v) => { + acc += `\t${v}\n`; + return acc; + }, ""); + + console.error(`Unknown template: ${template}`); + console.error(`We support only following templates:\n${supportedExamplesMessage}`) + process.exit(1); + } + + createApp(appName, template); +}; + +function createApp(name, template) { + const appRoot = path.resolve(name); + const appName = path.basename(appRoot); + + const templateDir = path.resolve(supportedExamplesDir, template); + + terminateIfAppExists(appName); + terminateIfProjectNameInvalid(appName); + + console.log(`Initializing ${appName} from ${template} template.`); + + fs.ensureDirSync(appName); + fs.copySync(templateDir, appRoot); + + runNpmInApp(appRoot); + runGitInit(appRoot); +} + +function runNpmInApp(root) { + const packageJsonPath = path.resolve(root, "package.json"); + + if (!fs.existsSync(packageJsonPath)) { + return; + } + + console.log("Installing npm packages."); + try { + execSync(`npm install --prefix ${root}`, { stdio: "ignore" }); + console.log("Successfully installed npm dependencies."); + } catch (e) { + console.warn("Failed to install npm dependencies", e); + } +} + +function runGitInit(root) { + if (isInGitRepository()) { + return; + } + + console.log("Initiating git repository."); + try { + execSync(`git init ${root}`, { stdio: "ignore" }); + console.log("Successfully initialized git repo."); + } catch (e) { + console.warn("Git repo not initialized", e); + } +} + +function isInGitRepository() { + try { + execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" }); + return true; + } catch (e) { + return false; + } +} + +function terminateIfProjectNameInvalid(name) { + const validationResult = validateProjectName(name); + + if (!validationResult.validForNewPackages) { + console.error(`Cannot create a project named ${name} because of npm naming restrictions:\n`); + [...(validationResult.errors || []), ...(validationResult.warnings || [])] + .forEach(error => console.error(` * ${error}`)); + console.error("\nPlease choose a different project name."); + process.exit(1); + } +} + +function terminateIfAppExists(appRoot) { + if (fs.existsSync(appRoot)) { + console.error(`Cannot create a project because it already exists by the name: ${appRoot}`); + process.exit(1); + } +} + +module.exports = { init }; \ No newline at end of file diff --git a/packages/create-waku-app/index.js b/packages/create-waku-app/index.js new file mode 100644 index 0000000..c8bd301 --- /dev/null +++ b/packages/create-waku-app/index.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node +const packageJson = require("./package.json"); +const semver = require("semver"); + +const currentNodeVersion = process.versions.node; +const supportedNodeVersion = packageJson.engines.node; + +if (!semver.satisfies(currentNodeVersion, supportedNodeVersion)) { + console.error( + `You are running Node ${currentNodeVersion}.\n` + + `@waku/create-app works only with ${packageJson.engines.node}.\n` + + `Please update your version of Node.` + ); + process.exit(1); +} + +const { init } = require("./createApp"); + +init(packageJson.name, packageJson.description, packageJson.version, packageJson.wakuExamples); diff --git a/packages/create-waku-app/package.json b/packages/create-waku-app/package.json new file mode 100644 index 0000000..11e1269 --- /dev/null +++ b/packages/create-waku-app/package.json @@ -0,0 +1,55 @@ +{ + "name": "@waku/create-app", + "version": "0.1.0", + "description": "Helper package to bootstrap Waku app ", + "repository": { + "type": "git", + "url": "https://github.com/waku-org/js-waku-examples.git", + "directory": "packages/create-app" + }, + "engines": { + "node": ">=16" + }, + "bugs": { + "url": "https://github.com/waku-org/js-waku-examples/issues" + }, + "files": [ + "index.js", + "createApp.js", + "examples" + ], + "main": "index.js", + "bin": { + "create-waku-app": "./index.js" + }, + "scripts": { + "build": "node ./build.js", + "prepublishOnly": "npm run build" + }, + "keywords": [ + "waku", + "decentralised", + "communication", + "web3", + "ethereum", + "dapps" + ], + "license": "MIT OR Apache-2.0", + "wakuExamples": { + "eth-pm": "../../eth-pm", + "light-js": "../../light-js", + "relay-angular-chat": "../../relay-angular-chat", + "relay-js": "../../relay-js", + "relay-reactjs-chat": "../../relay-reactjs-chat", + "rln-js": "../../rln-js", + "store-js": "../../store-js", + "store-reactjs-chat": "../../store-reactjs-chat", + "web-chat": "../../web-chat" + }, + "dependencies": { + "commander": "^9.4.1", + "fs-extra": "^11.1.0", + "semver": "^7.3.8", + "validate-npm-package-name": "^5.0.0" + } +}