diff --git a/package-lock.json b/package-lock.json index 199ce63..bd6368e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@clack/core": "0.3.4", "@clack/prompts": "0.7.0", - "@haxtheweb/haxcms-nodejs": "^9.0.11", + "@haxtheweb/haxcms-nodejs": "^9.0.13", "commander": "12.1.0", "ejs": "3.1.10", "picocolors": "1.0.1" @@ -27,7 +27,8 @@ "@babel/register": "^7.24.6", "@custom-elements-manifest/analyzer": "^0.10.2", "babel-plugin-transform-dynamic-import": "^2.1.0", - "commit-and-tag-version": "12.4.1" + "commit-and-tag-version": "12.4.1", + "nodemon": "^3.1.7" }, "engines": { "node": ">=18.20.3" @@ -1895,9 +1896,9 @@ "dev": true }, "node_modules/@haxtheweb/haxcms-nodejs": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/@haxtheweb/haxcms-nodejs/-/haxcms-nodejs-9.0.11.tgz", - "integrity": "sha512-r4h7+BAfCgvfZU3j1cAcCuibl7pOttiufPTX+7wvSsC5YBzYh9GQ1xaCplmBM3bP2Z8YNhBgMSvbvcRagYBGhA==", + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@haxtheweb/haxcms-nodejs/-/haxcms-nodejs-9.0.13.tgz", + "integrity": "sha512-4wnQlK5MmVl6w2yYmk1XcshMDLo83ti1lHhOzqLQFaKPa58u2+RgApzDWRaUOAYgG10V2VIpYwqX5cj8xu5lDA==", "license": "Apache-2.0", "dependencies": { "archiver": "7.0.1", @@ -3133,7 +3134,6 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "optional": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -5187,6 +5187,13 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -6288,6 +6295,48 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, + "node_modules/nodemon": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -6590,6 +6639,13 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7299,6 +7355,32 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -7631,6 +7713,16 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -7774,6 +7866,13 @@ "node": ">=0.8.0" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", diff --git a/package.json b/package.json index dccf6b3..7ad0573 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,8 @@ "build": "rm -rf dist && babel src --out-dir dist --copy-files --include-dotfiles && chmod 774 dist/create.js", "start": "npm run build && node ./dist/create.js && chmod 774 dist/create.js", "release": "npm run build && commit-and-tag-version && git push --follow-tags origin main && npm publish", + "hax": "hax", + "debug": "nodemon --watch src", "haxcms-nodejs-cli": "haxcms-nodejs-cli" }, "bin": { @@ -40,12 +42,13 @@ "dependencies": { "@clack/core": "0.3.4", "@clack/prompts": "0.7.0", - "@haxtheweb/haxcms-nodejs": "^9.0.11", + "@haxtheweb/haxcms-nodejs": "^9.0.13", "ejs": "3.1.10", "picocolors": "1.0.1", "commander": "12.1.0" }, "devDependencies": { + "nodemon": "^3.1.7", "@babel/cli": "^7.24.6", "@babel/core": "^7.24.6", "@babel/preset-env": "7.24.6", diff --git a/src/create.js b/src/create.js index 0552cd5..9fe1013 100644 --- a/src/create.js +++ b/src/create.js @@ -4,13 +4,14 @@ process.env.haxcms_middleware = "node-cli"; import * as fs from 'node:fs'; import * as path from 'node:path'; -import { setTimeout } from 'node:timers/promises'; -import * as ejs from "ejs"; import * as p from '@clack/prompts'; import color from 'picocolors'; import { haxIntro, communityStatement, merlinSays } from "./lib/statements.js"; -import { readAllFiles, dashToCamel } from './lib/utils.js'; +import { dashToCamel } from './lib/utils.js'; +import { webcomponentProcess } from "./lib/programs/webcomponent.js"; +import { siteProcess } from "./lib/programs/site.js"; + import * as hax from "@haxtheweb/haxcms-nodejs"; import * as haxcmsNodejsCli from "@haxtheweb/haxcms-nodejs/dist/cli.js"; const HAXCMS = hax.HAXCMS; @@ -19,57 +20,101 @@ import * as child_process from "child_process"; import * as util from "node:util"; import { program } from "commander"; const exec = util.promisify(child_process.exec); -const fakeSend = { - send: (json) => console.log(json), - sendStatus: (data) => console.log(data) -} -var hasGit = true; -exec('git --version', error => { + +var sysSurge = true; +exec('surge --version', error => { if (error) { - hasGit = false; + sysSurge = false; } }); -var hasSurge = true; -exec('surge --version', error => { + +let sysGit = true; +exec('git --version', error => { if (error) { - hasSurge = false; + sysGit = false; } }); - async function main() { + var commandRun = {}; program + .option('--') + .option('--path ', 'where to perform operation') // path + .option('--npm-client ', 'npm client to use (must be installed) npm, yarn, pnpm', 'npm') // npm yarn pnpm etc .option('--y', 'yes to all questions') // skip steps .option('--skip', 'skip frills like animations') // skip steps .option('--auto', 'yes to all questions, alias of y') // select defaults whenever possible - .option('--type ', 'type of project, haxsite or webcomponent') // haxsite, webcomponent - .option('--name ', 'name of the project') // haxsite / webcomponent name - .option('--title ', 'site: node title') // page title - .option('--domain ', 'site: published domain name') // haxsite / webcomponent name - .option('--action ', 'site: action to take') // action to take - .option('--org ', 'organization for package.json') // organization name - .option('--author ', 'author for site / package.json') // organization name - .option('--path ', 'where to perform operation') // path - .option('--npm-client ', 'npm client to use (must be installed) npm, yarn, pnpm') // npm yarn pnpm etc - .option('--') + + .option('--name ', 'name of the project') // site / webcomponent name + .option('--title ', 'node title', 'new page') // page title + .option('--domain ', 'published domain name') // site / webcomponent name .helpCommand(true); + + // default command which runs interactively + program + .command('start') + .description('Interactive program to pick options') + .action(() => { + commandRun = { + command: 'start', + arguments: {}, + options: {} + }; + }); + + // site operations and actions + program + .command('site') + .argument('[action]', 'action to take') + .action((action) => { + commandRun = { + command: 'site', + arguments: { + action: action + }, + options: { + skip: true, + y: (action) ? true : false + } + }; + }) + .option('--name ', 'name of the project') // site / webcomponent name + .option('--title ', 'node title', 'new page') // page title + .option('--domain ', 'published domain name') // site / webcomponent name + .version(await HAXCMS.getHAXCMSVersion()); + + // webcomponent program + program + .command('webcomponent') + .description('Create Lit based web components, with HAX recommendations') + .argument('[name]', 'name of the project') // site / webcomponent name + .action((name) => { + commandRun = { + command: 'webcomponent', + arguments: { + name: name + }, + options: { + skip: true, + y: (name) ? true : false + } + }; + }) + .option('--org ', 'organization for package.json') // organization name + .option('--author ', 'author for site / package.json'); // organization name + + // process program arguments program.parse(); - var cliOptions = program.opts(); + commandRun.options = {...commandRun.options, ...program.opts()}; + console.log(commandRun); // auto and y assume same thing - if (cliOptions.y || cliOptions.auto) { - cliOptions.y = true; - cliOptions.auto = true; - // assume we are creating a webcomponent if name supplied but no type defined - if (!cliOptions.type) { - cliOptions.type = 'webcomponent' - } + if (commandRun.options.y || commandRun.options.auto) { + commandRun.options.y = true; + commandRun.options.auto = true; } - if (!cliOptions.y && !cliOptions.auto && !cliOptions.skip) { + if (!commandRun.options.y && !commandRun.options.auto && !commandRun.options.skip) { await haxIntro(); } - if (!cliOptions.npmClient) { - cliOptions.npmClient = 'npm'; - } let author = ''; // should be able to grab if not predefined try { @@ -81,14 +126,12 @@ async function main() { console.log('git config --global user.name "namehere"'); console.log('git config --global user.email "email@here'); } - if (cliOptions.auto) { - cliOptions.path = process.cwd(); - cliOptions.org = ''; - cliOptions.author = author; + if (commandRun.options.auto) { + commandRun.options.path = process.cwd(); + commandRun.options.org = ''; + commandRun.options.author = author; } var port = "3000"; - // delay so that we clear and then let them visually react to change - const siteData = await hax.systemStructureContext(); let packageData = {}; let testPackages = [ path.join(process.cwd(), 'package.json'), @@ -104,56 +147,64 @@ async function main() { packageData = JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`)); // assume we are working on a web component / existing if we find this key if (packageData.hax && packageData.hax.cli) { - cliOptions.type = 'webcomponent'; + commandRun.program = 'webcomponent'; } // leverage these values if they exist downstream if (packageData.npmClient) { - cliOptions.npmClient = packageData.npmClient; + commandRun.options.npmClient = packageData.npmClient; } // see if we're in a monorepo if (packageData.useWorkspaces && packageData.workspaces && packageData.workspaces.packages && packageData.workspaces.packages[0]) { p.intro(`${color.bgBlack(color.white(` Monorepo detected : Setting relative defaults `))}`); - cliOptions.isMonorepo = true; - cliOptions.auto = true; + commandRun.options.isMonorepo = true; + commandRun.options.auto = true; // assumed if monorepo - cliOptions.type = 'webcomponent'; - cliOptions.path = path.join(process.cwd(), packageData.workspaces.packages[0].replace('/*','')); + commandRun.command = 'webcomponent'; + commandRun.options.path = path.join(process.cwd(), packageData.workspaces.packages[0].replace('/*','')); if (packageData.orgNpm) { - cliOptions.org = packageData.orgNpm; + commandRun.options.org = packageData.orgNpm; } - cliOptions.gitRepo = packageData.repository.url; - cliOptions.author = packageData.author.name ? packageData.author.name : author; + commandRun.options.gitRepo = packageData.repository.url; + commandRun.options.author = packageData.author.name ? packageData.author.name : author; } } catch (err) { console.error(err) } } } + var siteData = await hax.systemStructureContext(); // delay so that we clear and then let them visually react to change // CLI works within context of the site if one is detected, otherwise we can do other thingss if (siteData) { + // default to status unless already set so we don't issue a create in a create + if (!commandRun.arguments.action) { + commandRun.arguments.action = 'status'; + } p.intro(`${color.bgBlack(color.white(` HAXTheWeb : Site detected `))}`); - cliOptions.type = "haxsite"; + commandRun.command = "site"; p.intro(`${color.bgBlue(color.white(` Name: ${siteData.name} `))}`); // defaults if nothing set via CLI - let operation = { - action: null, - title: "New Page", - domain: `haxcli-${siteData.name}.surge.sh` - }; - operation = { - ...operation, - ...cliOptions + let operation = { + ...commandRun.arguments, + ...commandRun.options }; + console.log(commandRun.options.title); + if (!commandRun.options.title) { + commandRun.options.title = "New Page"; + } + if (!commandRun.options.domain) { + commandRun.options.domain = `haxcli-${siteData.name}.surge.sh`; + } // infinite loop until quitting the cli while (operation.action !== 'quit') { let actions = [ { value: 'status', label: "Site Status" }, { value: 'localhost', label: "Open Site (localhost)"}, - { value: 'sync-git', label: "Sync code in git"}, - { value: 'node-add', label: "Add New Page"}, + { value: 'git:sync', label: "Sync code in git"}, + { value: 'node:add', label: "Add New Page"}, + { value: 'node:delete', label: "Delete Page"}, ]; - if (hasSurge) { + if (sysSurge) { actions.push({ value: 'publish-surge', label: "Publish site using Surge.sh"}); } actions.push({ value: 'quit', label: "🚪 Quit"}); @@ -176,6 +227,7 @@ async function main() { } switch (operation.action) { case "status": + siteData = await hax.systemStructureContext(); p.intro(`${color.bgBlue(color.white(` Title: ${siteData.manifest.title} `))}`); p.intro(`${color.bgBlue(color.white(` Description: ${siteData.manifest.description} `))}`); p.intro(`${color.bgBlue(color.white(` Pages: ${siteData.manifest.items.length} `))}`); @@ -188,17 +240,27 @@ async function main() { console.log(e.stderr); } break; - case "node-add": - // @todo need to accept arguments + case "node:add": + try { + // @todo accept title if not supplied + haxcmsNodejsCli.cliBridge('createNode', { site: siteData, node: { title: commandRun.options.title }}); + console.log(`"${commandRun.options.title}" added to site`); + } + catch(e) { + console.log(e.stderr); + } + break; + case "node:delete": try { - haxcmsNodejsCli.cliBridge('createNode', { site: siteData, node: { title: operation.title }}); - console.log(`"${operation.title}" added to site`); + // @todo need to supply a command here + //haxcmsNodejsCli.cliBridge('deleteNode', { site: siteData, node: { id: id }}); + console.log(`"${commandRun.options.title}" added to site`); } catch(e) { console.log(e.stderr); } break; - case "sync-git": + case "git:sync": // @todo git sync might need other arguments / be combined with publishing try { await exec(`cd ${siteData.directory} && git pull && git push`); @@ -219,18 +281,20 @@ async function main() { break; case "quit": // quit + process.exit(0); break; } - if (cliOptions.y) { + if (commandRun.options.y) { process.exit(0); } operation.action = null; } + communityStatement(); } else if (packageData && packageData.hax && packageData.hax.cli && packageData.scripts.start) { + port = "8000"; p.intro(`${color.bgBlack(color.white(` HAXTheWeb : Webcomponent detected `))}`); p.intro(`${color.bgBlue(color.white(` Name: ${packageData.name} `))}`); - port = "8000"; p.note(`${merlinSays(`I have summoned a sub-process daemon 👹`)} 🚀 Running your ${color.bold('webcomponent')} ${color.bold(packageData.name)}: @@ -240,19 +304,19 @@ async function main() { 💻 Folder: ${color.bold(color.yellow(color.bgBlack(`cd ${process.cwd()}`)))} 📂 Open folder: ${color.bold(color.yellow(color.bgBlack(`open ${process.cwd()}`)))} 📘 VS Code Project: ${color.bold(color.yellow(color.bgBlack(`code ${process.cwd()}`)))} - 🚧 Launch later: ${color.bold(color.yellow(color.bgBlack(`${cliOptions.npmClient} start`)))} + 🚧 Launch later: ${color.bold(color.yellow(color.bgBlack(`${commandRun.options.npmClient} start`)))} ⌨️ To exit 🧙 Merlin press: ${color.bold(color.black(color.bgRed(` CTRL + C `)))} `); try { // ensure it's installed first, unless it's a monorepo - if (!cliOptions.isMonorepo) { + if (!commandRun.options.isMonorepo) { let s = p.spinner(); - s.start(merlinSays(`Installation magic (${cliOptions.npmClient} install)`)); - await exec(`${cliOptions.npmClient} install`); + s.start(merlinSays(`Installation magic (${commandRun.options.npmClient} install)`)); + await exec(`${commandRun.options.npmClient} install`); s.stop(merlinSays(`Everything is installed. It's go time`)); } - await exec(`${cliOptions.npmClient} start`); + await exec(`${commandRun.options.npmClient} start`); } catch(e) { // don't log bc output is odd @@ -264,11 +328,11 @@ async function main() { while (project.type !== 'quit') { if (activeProject) { p.note(` 🧙🪄 BE GONE ${color.bold(color.black(color.bgGreen(activeProject)))} sub-process daemon! 🪄 + ✨ 👹 = 💀 `); - cliOptions = {}; + commandRun.options = {}; } - if (['site', 'haxsite', 'webcomponent'].includes(cliOptions.type)) { + if (['site', 'webcomponent'].includes(commandRun.command)) { project = { - type: cliOptions.type + type: commandRun.command }; } else { @@ -280,8 +344,8 @@ async function main() { initialValue: 'webcomponent', required: true, options: [ - { value: 'webcomponent', label: '🏗️ Create a Web Component' }, - { value: 'haxsite', label: '🏡 Create a HAXcms site (single)'}, + { value: 'webcomponent', label: '🏗️ Create a Web Component' }, + { value: 'site', label: '🏡 Create a HAXcms site (single)'}, { value: 'quit', label: '🚪 Quit'}, ], }), @@ -307,9 +371,9 @@ async function main() { }, path: ({ results }) => { let initialPath = `${process.cwd()}`; - if (!cliOptions.path && !cliOptions.auto) { + if (!commandRun.options.path && !commandRun.options.auto) { return p.text({ - message: `What folder will your ${(cliOptions.type === "webcomponent" || results.type === "webcomponent") ? "project" : "site"} live in?`, + message: `What folder will your ${(commandRun.command === "webcomponent" || results.type === "webcomponent") ? "project" : "site"} live in?`, placeholder: initialPath, required: true, validate: (value) => { @@ -324,10 +388,10 @@ async function main() { } }, name: ({ results }) => { - if (!cliOptions.name) { + if (!commandRun.arguments.name) { let placeholder = "mysite"; let message = "Site name:"; - if (cliOptions.type === "webcomponent" ||results.type === "webcomponent") { + if (commandRun.command === "webcomponent" || results.type === "webcomponent") { placeholder = "my-element"; message = "Element name:"; } @@ -350,8 +414,8 @@ async function main() { } // assumes auto was selected in CLI let joint = process.cwd(); - if (cliOptions.path) { - joint = cliOptions.path; + if (commandRun.options.path) { + joint = commandRun.options.path; } else if (results.path) { joint = results.path; @@ -364,7 +428,7 @@ async function main() { } }, org: ({ results }) => { - if (results.type === "webcomponent" && !cliOptions.org && !cliOptions.auto) { + if (results.type === "webcomponent" && !commandRun.options.org && !commandRun.options.auto) { // @todo detect mono repo and automatically add this let initialOrg = '@yourOrganization'; return p.text({ @@ -380,7 +444,7 @@ async function main() { } }, author: ({ results }) => { - if (!cliOptions.author && !cliOptions.auto) { + if (!commandRun.options.author && !commandRun.options.auto) { return p.text({ message: 'Author:', required: false, @@ -389,17 +453,17 @@ async function main() { } }, extras: ({ results }) => { - if (!cliOptions.auto && !cliOptions.skip) { + if (!commandRun.options.auto && !commandRun.options.skip) { let options = []; let initialValues = []; - if (cliOptions.type === "webcomponent" || results.type === "webcomponent") { + if (commandRun.command === "webcomponent" || results.type === "webcomponent") { options = [ { value: 'launch', label: 'Launch project', hint: 'recommended' }, - { value: 'install', label: `Install dependencies via ${cliOptions.npmClient}`, hint: 'recommended' }, + { value: 'install', label: `Install dependencies via ${commandRun.options.npmClient}`, hint: 'recommended' }, { value: 'git', label: 'Apply version control via git', hint: 'recommended' }, ]; initialValues = ['launch', 'install', 'git'] - if (!hasGit || cliOptions.isMonorepo) { + if (!sysGit || commandRun.options.isMonorepo) { options.pop(); initialValues.pop(); } @@ -432,14 +496,14 @@ async function main() { project = { isMonorepo: false, ...project, - ...cliOptions, + ...commandRun.options, }; // auto select operations to perform if requested if (project.auto) { let extras = ['launch']; if (project.type === "webcomponent") { extras = ['launch', 'install', 'git']; - if (!hasGit || project.isMonorepo) { + if (!sysGit || project.isMonorepo) { extras.pop(); } } @@ -449,176 +513,20 @@ async function main() { project.className = dashToCamel(project.name); project.year = new Date().getFullYear(); project.version = await HAXCMS.getHAXCMSVersion(); - let s = p.spinner(); // resolve site vs multi-site switch (project.type) { case 'site': - case 'haxsite': - s.start(merlinSays(`Creating new site: ${project.name}`)); - let siteRequest = { - "site": { - "name": project.name, - "description": "own course", - "theme": "clean-one" - }, - "build": { - "type": "own", - "structure": "course", - "items": null, - "files": null - }, - "theme": { - "color": "green", - "icon": "av:library-add" - }, - }; - HAXCMS.cliWritePath = `${project.path}`; - await hax.RoutesMap.post.createSite({body: siteRequest}, fakeSend); - s.stop(merlinSays(`${project.name} created!`)); - await setTimeout(500); + siteProcess(commandRun, project, port); break; case 'webcomponent': port = "8000"; - // option to build github repo link for the user - if (project.extras.includes('git')) { - // @todo need to support git@ and https methods - if (cliOptions.auto) { - project.gitRepo = `https://github.com/${project.author}/${project.name}.git`; - } - else { - project.gitRepo = await p.text({ - message: 'Git Repo location:', - placeholder: `https://github.com/${project.author}/${project.name}.git` - }); - } - // if they supplied one and it has github in it, build a link automatically for ejs index - if (project.gitRepo && project.gitRepo.includes('github.com')) { - project.githubLink = project.gitRepo.replace('git@github.com:', 'https://github.com/').replace('.git', ''); - } - else { - project.githubLink = null; - } - } - else { - project.githubLink = null; - } - // if we have an org, add a / at the end so file name is written correctly - if (project.org) { - project.org += '/'; - } - else { - project.org = ''; - } - - s.start(merlinSays('Copying project files')); - // leverage this little helper from HAXcms - await HAXCMS.recurseCopy( - `${process.mainModule.path}/templates/${project.type}/hax/`, - `${project.path}/${project.name}` - ); - // rename paths that are of the element name in question - await fs.renameSync(`${project.path}/${project.name}/lib/webcomponent.haxProperties.json`, `${project.path}/${project.name}/lib/${project.name}.haxProperties.json`); - // loop through and rename all the localization files - fs.readdir(`${project.path}/${project.name}/locales/`, function (err, files) { - if (err) { - console.error("Could not list the directory.", err); - process.exit(1); - } - files.forEach(async function (file, index) { - await fs.renameSync(`${project.path}/${project.name}/locales/${file}`, `${project.path}/${project.name}/locales/${file.replace('webcomponent', project.name)}`); - }); - }); - await fs.renameSync(`${project.path}/${project.name}/webcomponent.js`, `${project.path}/${project.name}/${project.name}.js`); - await fs.renameSync(`${project.path}/${project.name}/test/webcomponent.test.js`, `${project.path}/${project.name}/test/${project.name}.test.js`); - s.stop(merlinSays('Files copied')); - await setTimeout(500); - s.start(merlinSays('Making files awesome')); - for (const filePath of readAllFiles(`${project.path}/${project.name}`)) { - try { - // ensure we don't try to pattern rewrite image files - if (!filePath.endsWith('.jpg') && !filePath.endsWith('.png')) { - const ejsString = ejs.fileLoader(filePath, 'utf8'); - let content = ejs.render(ejsString, project); - // file written successfully - fs.writeFileSync(filePath, content); - } - } catch (err) { - console.error(filePath); - console.error(err); - } - } - s.stop('Files are now awesome!'); + webcomponentProcess(commandRun, project, port); break; } - if (project.gitRepo && !cliOptions.isMonorepo) { - try { - await exec(`cd ${project.path}/${project.name} && git init && git add -A && git commit -m "first commit" && git branch -M main${project.gitRepo ? ` && git remote add origin ${project.gitRepo}` : ''}`); - } - catch(e) { - } - } - // options for install, git and other extras - // can't launch if we didn't install first so launch implies installation - if (project.extras.includes('launch') || project.extras.includes('install')) { - s.start(merlinSays(`Installation magic (${cliOptions.npmClient} install)`)); - try { - // monorepos install from top but then still need to launch from local location - if (!cliOptions.isMonorepo) { - await exec(`cd ${project.path}/${project.name} && ${cliOptions.npmClient} install`); - } - } - catch(e) { - console.log(e); - } - s.stop(merlinSays(`Everything is installed. It's go time`)); - } - // autolaunch if default was selected - if (project.extras.includes('launch')) { - let optionPath = `${project.path}/${project.name}`; - let command = `npx @haxtheweb/haxcms-nodejs`; - if (project.type === "webcomponent") { - command = `${cliOptions.npmClient} start`; - } - p.note(`${merlinSays(`I have summoned a sub-process daemon 👹`)} - -🚀 Running your ${color.bold(project.type)} ${color.bold(project.name)}: - ${color.underline(color.cyan(`http://localhost:${port}`))} - -🏠 Launched: ${color.underline(color.bold(color.yellow(color.bgBlack(`${optionPath}`))))} -💻 Folder: ${color.bold(color.yellow(color.bgBlack(`cd ${optionPath}`)))} -📂 Open folder: ${color.bold(color.yellow(color.bgBlack(`open ${optionPath}`)))} -📘 VS Code Project: ${color.bold(color.yellow(color.bgBlack(`code ${optionPath}`)))} -🚧 Launch later: ${color.bold(color.yellow(color.bgBlack(`${command}`)))} - -⌨️ To resume 🧙 Merlin press: ${color.bold(color.black(color.bgRed(` CTRL + C `)))} -`); - // at least a second to see the message print at all - await setTimeout(1000); - try { - await exec(`cd ${optionPath} && ${command}`); - } - catch(e) { - // don't log bc output is weird - } - } - else { - let nextSteps = `cd ${project.path}/${project.name} && `; - switch (project.type) { - case 'site': - case 'haxsite': - nextSteps += `npx @haxtheweb/haxcms-nodejs`; - break; - case 'webcomponent': - nextSteps += `${project.extras.includes('install') ? '' : `${cliOptions.npmClient} install && `}${cliOptions.npmClient} start`; - break; - } - p.note(`${project.name} is ready to go. Run the following to start development:`); - p.outro(nextSteps); - } } } + communityStatement(); } - communityStatement(); } main().catch(console.error); \ No newline at end of file diff --git a/src/lib/programs/site.js b/src/lib/programs/site.js new file mode 100644 index 0000000..2129bec --- /dev/null +++ b/src/lib/programs/site.js @@ -0,0 +1,98 @@ +#!/usr/bin/env node + +import { setTimeout } from 'node:timers/promises'; +import * as p from '@clack/prompts'; +import color from 'picocolors'; + +import { merlinSays } from "../statements.js"; +import * as hax from "@haxtheweb/haxcms-nodejs"; +const HAXCMS = hax.HAXCMS; + +import * as child_process from "child_process"; +import * as util from "node:util"; +const exec = util.promisify(child_process.exec); + +const fakeSend = { + send: (json) => console.log(json), + sendStatus: (data) => console.log(data) + } + +export async function siteProcess(commandRun, project, port) { + let s = p.spinner(); + s.start(merlinSays(`Creating new site: ${project.name}`)); + let siteRequest = { + "site": { + "name": project.name, + "description": "own course", + "theme": "clean-one" + }, + "build": { + "type": "own", + "structure": "course", + "items": null, + "files": null + }, + "theme": { + "color": "green", + "icon": "av:library-add" + }, + }; + HAXCMS.cliWritePath = `${project.path}`; + await hax.RoutesMap.post.createSite({body: siteRequest}, fakeSend); + s.stop(merlinSays(`${project.name} created!`)); + await setTimeout(500); + + if (project.gitRepo && !commandRun.options.isMonorepo) { + try { + await exec(`cd ${project.path}/${project.name} && git init && git add -A && git commit -m "first commit" && git branch -M main${project.gitRepo ? ` && git remote add origin ${project.gitRepo}` : ''}`); + } + catch(e) { + } + } + // options for install, git and other extras + // can't launch if we didn't install first so launch implies installation + if (project.extras.includes('launch') || project.extras.includes('install')) { + s.start(merlinSays(`Installation magic (${commandRun.options.npmClient} install)`)); + try { + // monorepos install from top but then still need to launch from local location + if (!commandRun.options.isMonorepo) { + await exec(`cd ${project.path}/${project.name} && ${commandRun.options.npmClient} install`); + } + } + catch(e) { + console.log(e); + } + s.stop(merlinSays(`Everything is installed. It's go time`)); + } + // autolaunch if default was selected + if (project.extras.includes('launch')) { + let optionPath = `${project.path}/${project.name}`; + let command = `npx @haxtheweb/haxcms-nodejs`; + p.note(`${merlinSays(`I have summoned a sub-process daemon 👹`)} + + 🚀 Running your ${color.bold(project.type)} ${color.bold(project.name)}: + ${color.underline(color.cyan(`http://localhost:${port}`))} + + 🏠 Launched: ${color.underline(color.bold(color.yellow(color.bgBlack(`${optionPath}`))))} + 💻 Folder: ${color.bold(color.yellow(color.bgBlack(`cd ${optionPath}`)))} + 📂 Open folder: ${color.bold(color.yellow(color.bgBlack(`open ${optionPath}`)))} + 📘 VS Code Project: ${color.bold(color.yellow(color.bgBlack(`code ${optionPath}`)))} + 🚧 Launch later: ${color.bold(color.yellow(color.bgBlack(`${command}`)))} + + ⌨️ To resume 🧙 Merlin press: ${color.bold(color.black(color.bgRed(` CTRL + C `)))} + `); + // at least a second to see the message print at all + await setTimeout(1000); + try { + await exec(`cd ${optionPath} && ${command}`); + } + catch(e) { + // don't log bc output is weird + } + } + else { + let nextSteps = `cd ${project.path}/${project.name} && ${project.extras.includes('install') ? '' : `${commandRun.options.npmClient} install && `}${commandRun.options.npmClient} start`; + p.note(`${project.name} is ready to go. Run the following to start development:`); + p.outro(nextSteps); + } +} \ No newline at end of file diff --git a/src/lib/programs/webcomponent.js b/src/lib/programs/webcomponent.js new file mode 100644 index 0000000..2757b19 --- /dev/null +++ b/src/lib/programs/webcomponent.js @@ -0,0 +1,141 @@ +#!/usr/bin/env node + +import * as fs from 'node:fs'; +import { setTimeout } from 'node:timers/promises'; +import * as ejs from "ejs"; +import * as p from '@clack/prompts'; +import color from 'picocolors'; + +import { merlinSays } from "../statements.js"; +import { readAllFiles } from '../utils.js'; +import * as hax from "@haxtheweb/haxcms-nodejs"; +const HAXCMS = hax.HAXCMS; + +import * as child_process from "child_process"; +import * as util from "node:util"; +const exec = util.promisify(child_process.exec); + +export async function webcomponentProcess(commandRun, project, port) { + // option to build github repo link for the user + if (project.extras.includes('git')) { + // @todo need to support git@ and https methods + if (commandRun.options.auto) { + project.gitRepo = `https://github.com/${project.author}/${project.name}.git`; + } + else { + project.gitRepo = await p.text({ + message: 'Git Repo location:', + placeholder: `https://github.com/${project.author}/${project.name}.git` + }); + } + // if they supplied one and it has github in it, build a link automatically for ejs index + if (project.gitRepo && project.gitRepo.includes('github.com')) { + project.githubLink = project.gitRepo.replace('git@github.com:', 'https://github.com/').replace('.git', ''); + } + else { + project.githubLink = null; + } + } + else { + project.githubLink = null; + } + // if we have an org, add a / at the end so file name is written correctly + if (project.org) { + project.org += '/'; + } + else { + project.org = ''; + } + + s.start(merlinSays('Copying project files')); + // leverage this little helper from HAXcms + await HAXCMS.recurseCopy( + `${process.mainModule.path}/templates/${project.type}/hax/`, + `${project.path}/${project.name}` + ); + // rename paths that are of the element name in question + await fs.renameSync(`${project.path}/${project.name}/lib/webcomponent.haxProperties.json`, `${project.path}/${project.name}/lib/${project.name}.haxProperties.json`); + // loop through and rename all the localization files + fs.readdir(`${project.path}/${project.name}/locales/`, function (err, files) { + if (err) { + console.error("Could not list the directory.", err); + process.exit(1); + } + files.forEach(async function (file, index) { + await fs.renameSync(`${project.path}/${project.name}/locales/${file}`, `${project.path}/${project.name}/locales/${file.replace('webcomponent', project.name)}`); + }); + }); + await fs.renameSync(`${project.path}/${project.name}/webcomponent.js`, `${project.path}/${project.name}/${project.name}.js`); + await fs.renameSync(`${project.path}/${project.name}/test/webcomponent.test.js`, `${project.path}/${project.name}/test/${project.name}.test.js`); + s.stop(merlinSays('Files copied')); + await setTimeout(500); + s.start(merlinSays('Making files awesome')); + for (const filePath of readAllFiles(`${project.path}/${project.name}`)) { + try { + // ensure we don't try to pattern rewrite image files + if (!filePath.endsWith('.jpg') && !filePath.endsWith('.png')) { + const ejsString = ejs.fileLoader(filePath, 'utf8'); + let content = ejs.render(ejsString, project); + // file written successfully + fs.writeFileSync(filePath, content); + } + } catch (err) { + console.error(filePath); + console.error(err); + } + } + s.stop('Files are now awesome!'); + if (project.gitRepo && !commandRun.options.isMonorepo) { + try { + await exec(`cd ${project.path}/${project.name} && git init && git add -A && git commit -m "first commit" && git branch -M main${project.gitRepo ? ` && git remote add origin ${project.gitRepo}` : ''}`); + } + catch(e) { + } + } + // options for install, git and other extras + // can't launch if we didn't install first so launch implies installation + if (project.extras.includes('launch') || project.extras.includes('install')) { + s.start(merlinSays(`Installation magic (${commandRun.options.npmClient} install)`)); + try { + // monorepos install from top but then still need to launch from local location + if (!commandRun.options.isMonorepo) { + await exec(`cd ${project.path}/${project.name} && ${commandRun.options.npmClient} install`); + } + } + catch(e) { + console.log(e); + } + s.stop(merlinSays(`Everything is installed. It's go time`)); + } + // autolaunch if default was selected + if (project.extras.includes('launch')) { + let optionPath = `${project.path}/${project.name}`; + let command = `${commandRun.options.npmClient} start`; + p.note(`${merlinSays(`I have summoned a sub-process daemon 👹`)} + +🚀 Running your ${color.bold(project.type)} ${color.bold(project.name)}: +${color.underline(color.cyan(`http://localhost:${port}`))} + +🏠 Launched: ${color.underline(color.bold(color.yellow(color.bgBlack(`${optionPath}`))))} +💻 Folder: ${color.bold(color.yellow(color.bgBlack(`cd ${optionPath}`)))} +📂 Open folder: ${color.bold(color.yellow(color.bgBlack(`open ${optionPath}`)))} +📘 VS Code Project: ${color.bold(color.yellow(color.bgBlack(`code ${optionPath}`)))} +🚧 Launch later: ${color.bold(color.yellow(color.bgBlack(`${command}`)))} + +⌨️ To resume 🧙 Merlin press: ${color.bold(color.black(color.bgRed(` CTRL + C `)))} +`); + // at least a second to see the message print at all + await setTimeout(1000); + try { + await exec(`cd ${optionPath} && ${command}`); + } + catch(e) { + // don't log bc output is weird + } + } + else { + let nextSteps = `cd ${project.path}/${project.name} && ${project.extras.includes('install') ? '' : `${commandRun.options.npmClient} install && `}${commandRun.options.npmClient} start`; + p.note(`${project.name} is ready to go. Run the following to start development:`); + p.outro(nextSteps); + } +} \ No newline at end of file diff --git a/src/lib/utils.js b/src/lib/utils.js index 0674cbd..345cd82 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,5 +1,8 @@ import * as fs from 'node:fs'; import * as path from "node:path" +import * as child_process from "child_process"; +import * as util from "node:util"; +const exec = util.promisify(child_process.exec); export const SITE_FILE_NAME = "site.json"; /** * Helper to convert dash to camel; important when reading attributes.