From 33ddc53075bb8b9284de8b10fb95fd86f7855927 Mon Sep 17 00:00:00 2001 From: Ayo Date: Mon, 11 May 2026 09:12:08 +0200 Subject: [PATCH] feat: new create-component package --- packages/create-component/LICENSE | 21 +++ packages/create-component/README.md | 12 ++ packages/create-component/package.json | 36 +++++ packages/create-component/pnpm-lock.yaml | 48 ++++++ packages/create-component/src/index.ts | 197 +++++++++++++++++++++++ 5 files changed, 314 insertions(+) create mode 100644 packages/create-component/LICENSE create mode 100644 packages/create-component/README.md create mode 100644 packages/create-component/package.json create mode 100644 packages/create-component/pnpm-lock.yaml create mode 100755 packages/create-component/src/index.ts diff --git a/packages/create-component/LICENSE b/packages/create-component/LICENSE new file mode 100644 index 0000000..009a8d8 --- /dev/null +++ b/packages/create-component/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ayo Ayco + +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. diff --git a/packages/create-component/README.md b/packages/create-component/README.md new file mode 100644 index 0000000..15b674d --- /dev/null +++ b/packages/create-component/README.md @@ -0,0 +1,12 @@ +# Create WC + +``` +npm create webcomponent@latest +``` + +Create a new web component powered by [webcomponent.io](https://webcomponent.io) + +--- + +_Just keep building._
+_A project by [Ayo](https://ayo.ayco.io)_ diff --git a/packages/create-component/package.json b/packages/create-component/package.json new file mode 100644 index 0000000..f5a558e --- /dev/null +++ b/packages/create-component/package.json @@ -0,0 +1,36 @@ +{ + "name": "@mcflyjs/create-component", + "version": "0.0.1-alpha", + "description": "Create a new web component", + "type": "module", + "bin": { + "create-mcfly-component": "./dist/index.js" + }, + "main": "./dist/index.js", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc --erasableSyntaxOnly", + "dev": "npx jiti ./src/index.ts", + "test": "echo \"Error: no test specified\" && exit 1", + "prepare": "husky" + }, + "repository": { + "type": "git", + "url": "https://git.ayo.run/ayo/mcfly", + "directory": "packates/create-component" + }, + "author": "Ayo Ayco", + "license": "MIT", + "dependencies": { + "consola": "^3.4.2", + "giget": "^3.2.0" + }, + "devDependencies": { + "@types/node": "^25.6.0" + } +} diff --git a/packages/create-component/pnpm-lock.yaml b/packages/create-component/pnpm-lock.yaml new file mode 100644 index 0000000..fae6010 --- /dev/null +++ b/packages/create-component/pnpm-lock.yaml @@ -0,0 +1,48 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + consola: + specifier: ^3.4.2 + version: 3.4.2 + giget: + specifier: ^3.2.0 + version: 3.2.0 + devDependencies: + '@types/node': + specifier: ^25.6.0 + version: 25.6.2 + +packages: + + '@types/node@25.6.2': + resolution: {integrity: sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + giget@3.2.0: + resolution: {integrity: sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==} + hasBin: true + + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + +snapshots: + + '@types/node@25.6.2': + dependencies: + undici-types: 7.19.2 + + consola@3.4.2: {} + + giget@3.2.0: {} + + undici-types@7.19.2: {} diff --git a/packages/create-component/src/index.ts b/packages/create-component/src/index.ts new file mode 100755 index 0000000..6f56480 --- /dev/null +++ b/packages/create-component/src/index.ts @@ -0,0 +1,197 @@ +#!/usr/bin/env node + +import { consola } from 'consola' +import { colorize } from 'consola/utils' +import { downloadTemplate } from 'giget' +import { spawnSync } from 'node:child_process' +import * as path from 'node:path' + +const [, , directoryArg] = process.argv + +type PromptAction = { + prompt: string + info?: string + startMessage: string + command: string + subCommand: string + error: string +} + +/** + * Create Web Component + */ +async function create() { + const defaultDirectory = 'hello-world' + consola.box(`Hello! Let's build a new ${colorize('bold', 'Web Component')}!`) + let directory = directoryArg + + if (!directory) { + directory = + (await consola.prompt('Give your new project a name:', { + placeholder: defaultDirectory, + })) ?? defaultDirectory + } else { + consola.success(`Using ${directory} as name.`) + } + + if (typeof directory !== 'string') { + return + } + + directory = getSafeDirectory(directory) + const hasErrors = await downloadTemplateToDirectory(directory) + + if (!hasErrors) { + const prompts: PromptAction[] = [ + { + prompt: `Would you like to install the dependencies to ${colorize( + 'bold', + directory + )}?`, + info: 'This might take some time depending on your connectivity.', + startMessage: 'Installing dependencies using npm...', + command: `npm`, + subCommand: 'install', + error: `Install dependencies later with ${colorize( + 'yellow', + 'npm install' + )}`, + }, + { + prompt: 'Would you like to initialize your git repository?', + startMessage: 'Initializing git repository...', + command: `git`, + subCommand: 'init', + error: `Initialize git repository later with ${colorize( + 'yellow', + 'git init' + )}`, + }, + ] + + const intentions = await askPrompts(prompts, directory) + if (!!intentions && intentions.length > 0) + showResults(directory, intentions[0]) + } +} + +/** + * Returns string that is safe for commands + * @param {string} directory + * @returns string + */ +function getSafeDirectory(directory: string): string { + const { platform } = process + const locale = path[platform === `win32` ? `win32` : `posix`] + const localePath = directory.split(path.sep).join(locale.sep) + return path.normalize(localePath) +} + +/** + * Tries to download the template to the directory and returns a Promise whether the operation resulted to errors + * @param {string} directory + * @returns Promise hasErrors + */ +async function downloadTemplateToDirectory( + directory: string +): Promise { + let hasErrors = false + + try { + consola.start( + `Copying template to ${colorize('bold', getSafeDirectory(directory))}...` + ) + await downloadTemplate('github:ayo-run/web-component', { + dir: directory, + }) + } catch (ㆆ_ㆆ) { + if (ㆆ_ㆆ instanceof Error) { + consola.error(ㆆ_ㆆ.message) + } else { + consola.error(ㆆ_ㆆ) + } + consola.info('Try a different name.\n') + hasErrors = true + } + + return hasErrors +} + +/** + * Iterate over an array of prompts and ask for user intention + * @param {Array} prompts + * @param {string} cwd + * @returns Promise | undefined> + */ +async function askPrompts( + prompts: PromptAction[], + cwd: string +): Promise { + const results: boolean[] = [] + + for (const p of prompts) { + const userIntends = await consola.prompt(p.prompt, { + type: 'confirm', + }) + + if (typeof userIntends !== 'boolean') { + return + } + + if (userIntends) { + p.info && consola.info(p.info) + consola.start(p.startMessage) + try { + spawnSync(p.command, [p.subCommand], { + cwd, + shell: true, + timeout: 100_000, + stdio: 'inherit', + }) + consola.success('Done!') + } catch (ㆆ_ㆆ) { + if (ㆆ_ㆆ instanceof Error) { + consola.error(ㆆ_ㆆ.message) + } else { + consola.error(ㆆ_ㆆ) + } + consola.info(p.error + '\n') + } + } + results.push(userIntends) + } + + return results +} + +/** + * Displays the success result string based on directory and choices + * @param {string} directory + * @param {boolean} installDeps + */ +function showResults(directory: string, installDeps: boolean) { + let nextActions = [ + `Go to your project by running ${colorize('yellow', `cd ${directory}`)}`, + ] + + if (!installDeps) { + nextActions.push( + `Install the dependencies with ${colorize('yellow', 'npm install')}` + ) + } + + nextActions = nextActions.concat([ + `Start the dev server with ${colorize('yellow', 'npm start')}`, + ]) + + const result = `🎉 Your new ${colorize( + 'bold', + 'Web Commponent' + )} app is ready: ${directory}\n\nNext actions: ${nextActions + .map((action, index) => `\n${++index}. ${action}`) + .join('')}` + + consola.box(result) +} + +create()