From 1e7c97e36737e02bdc55bcd6502cd6015c23ebe1 Mon Sep 17 00:00:00 2001 From: Gabriel Massadas Date: Sun, 1 Dec 2024 21:46:49 +0000 Subject: [PATCH] Remove create-r2-explorer in favor of using cloudflare c3 --- .eslintignore | 1 - .gitignore | 3 + DEVELOPMENT.md | 10 - LICENSE | 2 +- README.md | 2 +- READMEes.md | 2 +- READMEfr.md | 2 +- READMEpt.md | 2 +- c3.ts | 7 - package.json | 3 +- packages/create-r2-explorer/README.md | 26 -- packages/create-r2-explorer/package.json | 69 ---- packages/create-r2-explorer/scripts/build.js | 37 -- packages/create-r2-explorer/src/cli.ts | 69 ---- packages/create-r2-explorer/src/common.ts | 217 ------------ .../create-r2-explorer/src/helpers/cli.ts | 127 ------- .../create-r2-explorer/src/helpers/codemod.ts | 57 --- .../create-r2-explorer/src/helpers/colors.ts | 32 -- .../create-r2-explorer/src/helpers/command.ts | 330 ------------------ .../create-r2-explorer/src/helpers/files.ts | 108 ------ .../src/helpers/interactive.ts | 321 ----------------- .../src/helpers/packages.ts | 65 ---- .../create-r2-explorer/src/helpers/poll.ts | 136 -------- packages/create-r2-explorer/src/types.ts | 9 - packages/create-r2-explorer/src/workers.ts | 91 ----- .../create-r2-explorer/template/.gitignore | 171 --------- .../create-r2-explorer/template/README.md | 43 --- .../create-r2-explorer/template/package.json | 15 - .../create-r2-explorer/template/src/index.ts | 3 - .../create-r2-explorer/template/wrangler.toml | 3 - packages/create-r2-explorer/tsconfig.json | 20 -- packages/worker/rollup.config.js | 4 - pnpm-lock.yaml | 90 ----- template/package.json | 4 +- template/src/index.ts | 1 + template/wrangler.toml | 4 +- 36 files changed, 14 insertions(+), 2072 deletions(-) delete mode 100644 c3.ts delete mode 100644 packages/create-r2-explorer/README.md delete mode 100644 packages/create-r2-explorer/package.json delete mode 100644 packages/create-r2-explorer/scripts/build.js delete mode 100644 packages/create-r2-explorer/src/cli.ts delete mode 100644 packages/create-r2-explorer/src/common.ts delete mode 100644 packages/create-r2-explorer/src/helpers/cli.ts delete mode 100644 packages/create-r2-explorer/src/helpers/codemod.ts delete mode 100644 packages/create-r2-explorer/src/helpers/colors.ts delete mode 100644 packages/create-r2-explorer/src/helpers/command.ts delete mode 100644 packages/create-r2-explorer/src/helpers/files.ts delete mode 100644 packages/create-r2-explorer/src/helpers/interactive.ts delete mode 100644 packages/create-r2-explorer/src/helpers/packages.ts delete mode 100644 packages/create-r2-explorer/src/helpers/poll.ts delete mode 100644 packages/create-r2-explorer/src/types.ts delete mode 100644 packages/create-r2-explorer/src/workers.ts delete mode 100644 packages/create-r2-explorer/template/.gitignore delete mode 100644 packages/create-r2-explorer/template/README.md delete mode 100644 packages/create-r2-explorer/template/package.json delete mode 100644 packages/create-r2-explorer/template/src/index.ts delete mode 100644 packages/create-r2-explorer/template/wrangler.toml delete mode 100644 packages/create-r2-explorer/tsconfig.json diff --git a/.eslintignore b/.eslintignore index a04639a..ee0f302 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,5 @@ dist node_modules -packages/create-r2-explorer/* packages/github-action/* packages/worker/bin/* packages/worker/config/* diff --git a/.gitignore b/.gitignore index 78d7420..58a37c6 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ packages/github-action/src packages/dashboard/.env packages/dashboard/.quasar .dev.vars + +template/package-lock.json +template/node_modules/ diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 6b9ae01..df3a31a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -15,16 +15,6 @@ npm run build wrangler pages publish --branch main --project-name r2-explorer-dashboard dist/ ``` -## Publish create-r2-explorer package - -Increase version in `packages/create-r2-explorer/package.json` - -```bash -cd packages/create-r2-explorer/ -npm run build -npm publish --access public -``` - ## Manually publish worker ```bash diff --git a/LICENSE b/LICENSE index ec3487e..c7d6a0f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Gabriel Massadas +Copyright (c) 2023-2024 Gabriel Massadas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 6da1145..b09dba4 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Read this in other languages: [Español](READMEes.md), [Português](READMEpt.md) Run this command to get an example project setup ```bash -npm create r2-explorer@latest +npm create cloudflare@latest r2-explorer -- --template "G4brym/R2-Explorer/template" ``` ## Upgrading your installation diff --git a/READMEes.md b/READMEes.md index 0adcbf8..332f843 100644 --- a/READMEes.md +++ b/READMEes.md @@ -39,7 +39,7 @@ Leer esto en otros idiomas: [English](README.md), [Português](READMEpt.md), [Fr Ejecuta este comando para configurar un proyecto de ejemplo ```bash -npm create r2-explorer@latest +npm create cloudflare@latest r2-explorer -- --template "G4brym/R2-Explorer/template" ``` ## Actualizar tu instalación diff --git a/READMEfr.md b/READMEfr.md index 91f8989..ab6d438 100644 --- a/READMEfr.md +++ b/READMEfr.md @@ -39,7 +39,7 @@ Lisez ceci dans d'autres langues : [English](README.md), [Español](READMEes.md) Exécutez cette commande pour configurer un projet exemple ```bash -npm create r2-explorer@latest +npm create cloudflare@latest r2-explorer -- --template "G4brym/R2-Explorer/template" ``` ## Mise à jour de votre installation diff --git a/READMEpt.md b/READMEpt.md index 88ecd22..df95ae0 100644 --- a/READMEpt.md +++ b/READMEpt.md @@ -39,7 +39,7 @@ Leia isso em outros idiomas: [English](README.md), [Español](READMEes.md), [Fra Execute este comando para configurar um projeto de exemplo ```bash -npm create r2-explorer@latest +npm create cloudflare@latest r2-explorer -- --template "G4brym/R2-Explorer/template" ``` ## Atualizar sua instalação diff --git a/c3.ts b/c3.ts deleted file mode 100644 index 1b9d4f1..0000000 --- a/c3.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default { - configVersion: 1, - id: "r2explorer", - displayName: "R2Explorer interface for your Cloudflare R2 Buckets!", - platform: "workers", - copyFiles: { path: './template' } -}; diff --git a/package.json b/package.json index d1bd6b2..af4676a 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,9 @@ "scripts": { "prepare": "husky install", "lint": "(eslint . || (eslint . --fix; exit 1)) && (prettier --check '*.{json,js}' 'packages/*/src/**/*.{js,ts}' || (prettier -w '*.{json,js}' 'packages/*/src/**/*.{js,ts}'; exit 1))", - "build-create": "pnpm run --filter create-r2-explorer build", "build-dashboard": "pnpm run --filter r2-explorer-dashboard build", "build-worker": "pnpm run --filter r2-explorer build", - "build": "pnpm build-create && pnpm build-dashboard && pnpm build-worker", + "build": "pnpm build-dashboard && pnpm build-worker", "deploy-dashboard": "pnpm run --filter r2-explorer-dashboard deploy", "deploy-dashboard-dev": "pnpm run --filter r2-explorer-dashboard deploy-dev", "deploy-dev-worker": "pnpm run --filter r2-explorer-dev-worker deploy", diff --git a/packages/create-r2-explorer/README.md b/packages/create-r2-explorer/README.md deleted file mode 100644 index a10a6d5..0000000 --- a/packages/create-r2-explorer/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# create-r2-explorer - -A CLI for creating and deploying new [R2 Explorer](https://github.com/G4brym/R2-Explorer) applications to -[Cloudflare](https://developers.cloudflare.com/). - -This package is a fork from [create-cloudflare](https://www.npmjs.com/package/create-cloudflare). - -## Usage - -### Setup via Interactive CLI - -To create new applications via interactive CLI, run: - -```bash -npm create r2-explorer@latest -# or -pnpm create r2-explorer@latest -# or -yarn create r2-explorer -``` - - -### Community - -- Join us [on Discord](https://discord.cloudflare.com) in the workers-libraries and workers-discussions channels -- File an issue [on Github](https://github.com/G4brym/R2-Explorer/issues/new) diff --git a/packages/create-r2-explorer/package.json b/packages/create-r2-explorer/package.json deleted file mode 100644 index c7157e1..0000000 --- a/packages/create-r2-explorer/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "create-r2-explorer", - "version": "1.0.1", - "description": "A CLI for creating and deploying new R2 Explorer applications to Cloudflare.", - "keywords": [ - "cloudflare", - "workers", - "cloudflare workers", - "serverless", - "serverless application", - "r2-explorer", - "cloudflare-r2" - ], - "repository": { - "type": "git", - "url": "https://github.com/G4brym/R2-Explorer.git", - "directory": "packages/create-r2-explorer" - }, - "license": "MIT", - "author": "Gabriel Massadas", - "exports": { - ".": "./dist/cli.js" - }, - "main": "./dist/cli.js", - "bin": "./dist/cli.js", - "files": [ - "dist", - "template" - ], - "scripts": { - "build": "node scripts/build.js", - "prepublishOnly": "npm run build", - "watch": "node scripts/build.js --watch" - }, - "devDependencies": { - "@babel/parser": "^7.21.3", - "@babel/types": "^7.21.4", - "@clack/prompts": "^0.6.3", - "@cloudflare/workers-types": "^4.20230419.0", - "@types/command-exists": "^1.2.0", - "@types/cross-spawn": "^6.0.2", - "@types/esprima": "^4.0.3", - "@types/node": "^18.15.3", - "@types/which-pm-runs": "^1.0.0", - "@types/yargs": "^17.0.22", - "@typescript-eslint/parser": "^5.55.0", - "chalk": "^5.2.0", - "command-exists": "^1.2.9", - "cross-spawn": "^7.0.3", - "dns2": "^2.1.0", - "esbuild": "^0.17.12", - "execa": "^7.1.1", - "haikunator": "^2.1.2", - "log-update": "^5.0.1", - "open": "^8.4.0", - "pnpm": "^8.6.11", - "typescript": "^5.0.2", - "which-pm-runs": "^1.1.0", - "wrangler": "*", - "yargs": "^17.7.1", - "yarn": "^1.22.19", - "@clack/core": "^0.3.4", - "semver": "^7.6.0", - "undici": "^6.7.1" - }, - "engines": { - "node": ">=16.13.0" - } -} diff --git a/packages/create-r2-explorer/scripts/build.js b/packages/create-r2-explorer/scripts/build.js deleted file mode 100644 index 22caee3..0000000 --- a/packages/create-r2-explorer/scripts/build.js +++ /dev/null @@ -1,37 +0,0 @@ -const { build, context } = require("esbuild"); -const { cp } = require("fs/promises"); - -const run = async () => { - const argv = process.argv.slice(2); - const watchMode = argv[0] === "--watch"; - - const config = { - entryPoints: ["./src/cli.ts"], - bundle: true, - outdir: "./dist", - platform: "node", - format: "cjs", - }; - - const runBuild = async () => { - await build(config); - await build({...config, outdir: undefined, outfile: '../worker/bin/r2-explorer.js'}); - }; - - const runWatch = async () => { - let ctx = await context(config); - await ctx.watch(); - console.log("Watching..."); - }; - - if (watchMode) { - await runWatch(); - } else { - await runBuild(); - } -}; - -run().catch((error) => { - console.error(error); - process.exit(1); -}); diff --git a/packages/create-r2-explorer/src/cli.ts b/packages/create-r2-explorer/src/cli.ts deleted file mode 100644 index 409676f..0000000 --- a/packages/create-r2-explorer/src/cli.ts +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env node -import {crash, logRaw, startSection} from "helpers/cli"; -import {dim} from "helpers/colors"; -import {processArgument} from "helpers/interactive"; -import yargs from "yargs"; -import {hideBin} from "yargs/helpers"; -import {version} from "../package.json"; -import {validateProjectDirectory} from "./common"; -import {runWorkersGenerator} from "./workers"; -import {CArgs} from "types"; - -export const C_DEFAULTS = { - projectName: 'my-r2-explorer', - deploy: undefined, - open: true, -}; - -export const main = async (argv: string[]) => { - const args = await parseArgs(argv); - - printBanner(); - - const projectName = await processArgument(args, "projectName", { - type: "text", - question: `In which directory do you want to create your application?`, - helpText: "also used as application name", - defaultValue: C_DEFAULTS.projectName, - label: "dir", - validate: (value) => - validateProjectDirectory(String(value) || C_DEFAULTS.projectName), - format: (val) => `./${val}`, - }); - - const validatedArgs: CArgs = { - ...args, - projectName, - }; - - await runWorkersGenerator(validatedArgs); -}; - -const printBanner = () => { - logRaw(dim(`\nusing create-r2-explorer version ${version}\n`)); - startSection(`Create an application with Cloudflare`, "Step 1 of 3"); -}; - -export const parseArgs = async (argv: string[]): Promise> => { - const args = await yargs(hideBin(argv)) - .scriptName("create-r2-explorer") - .usage("$0 [args]") - .positional("name", {type: "string"}) - .option("deploy", {type: "boolean"}) - .option("open", { - type: "boolean", - default: true, - description: - "opens your browser after your deployment, set --no-open to disable", - }) - .version(version) - .help().argv; - - return { - ...C_DEFAULTS, - projectName: args._[0] as string | undefined, - ...args, - }; -}; - -main(process.argv).catch((e) => crash(e)); diff --git a/packages/create-r2-explorer/src/common.ts b/packages/create-r2-explorer/src/common.ts deleted file mode 100644 index 133fa06..0000000 --- a/packages/create-r2-explorer/src/common.ts +++ /dev/null @@ -1,217 +0,0 @@ -import {existsSync, mkdirSync, readdirSync} from "fs"; -import {basename, dirname, resolve} from "path"; -import {chdir} from "process"; -import {crash, endSection, log, logRaw, newline, openInBrowser, shapes, startSection,} from "helpers/cli"; -import {bgGreen, blue, brandColor, dim, gray} from "helpers/colors"; -import {listAccounts, runCommand, wranglerLogin,} from "helpers/command"; -import {inputPrompt, processArgument, spinner} from "helpers/interactive"; -import {detectPackageManager} from "helpers/packages"; -import {C_DEFAULTS} from "./cli"; -import type {CArgs, PagesGeneratorContext} from "types"; -import {poll} from "helpers/poll"; - -const {npm} = detectPackageManager(); - -export const validateProjectDirectory = (relativePath: string) => { - const path = resolve(relativePath); - const existsAlready = existsSync(path); - const isEmpty = existsAlready && readdirSync(path).length === 0; // allow existing dirs _if empty_ to ensure c3 is non-destructive - - if (existsAlready && !isEmpty) { - return `Directory \`${relativePath}\` already exists and is not empty. Please choose a new name.`; - } -}; - -export const setupProjectDirectory = (args: CArgs) => { - // Crash if the directory already exists - const path = resolve(args.projectName); - const err = validateProjectDirectory(path); - if (err) { - crash(err); - } - - const directory = dirname(path); - const name = basename(path); - - // If the target is a nested directory, create the parent - mkdirSync(directory, {recursive: true}); - - // Change to the parent directory - chdir(directory); - - return {name, path}; -}; - -export const offerToDeploy = async (ctx: PagesGeneratorContext) => { - startSection(`Deploy with Cloudflare`, `Step 3 of 3`); - - const label = `deploy via \`${npm} run ${ - ctx.framework?.config.deployCommand ?? "deploy" - }\``; - - ctx.args.deploy = await processArgument(ctx.args, "deploy", { - type: "confirm", - question: "Do you want to deploy your application?", - label, - defaultValue: C_DEFAULTS.deploy, - }); - - if (!ctx.args.deploy) return; - - const loginSuccess = await wranglerLogin(); - if (!loginSuccess) return; - - await chooseAccount(ctx); -}; - -export const runDeploy = async (ctx: PagesGeneratorContext) => { - if (ctx.args.deploy === false) return; - if (!ctx.account?.id) { - crash("Failed to read Cloudflare account."); - return; - } - - const deployCmd = `${npm} run ${ - ctx.framework?.config.deployCommand ?? "deploy" - }`; - const result = await runCommand(deployCmd, { - silent: true, - cwd: ctx.project.path, - env: {CLOUDFLARE_ACCOUNT_ID: ctx.account.id, NODE_ENV: "production"}, - startText: `Deploying your application`, - doneText: `${brandColor("deployed")} ${dim(`via \`${deployCmd}\``)}`, - }); - - const deployedUrlRegex = /https:\/\/.+\.(pages|workers)\.dev/; - const deployedUrlMatch = result.match(deployedUrlRegex); - if (deployedUrlMatch) { - ctx.deployedUrl = deployedUrlMatch[0]; - } else { - crash("Failed to find deployment url."); - } - - // if a pages url (..pages.dev), remove the sha1 - if (ctx.deployedUrl?.endsWith(".pages.dev")) { - const [proto, hostname] = ctx.deployedUrl.split("://"); - const hostnameWithoutSHA1 = hostname.split(".").slice(-3).join("."); // only keep the last 3 parts (discard the 4th, i.e. the SHA1) - - ctx.deployedUrl = `${proto}://${hostnameWithoutSHA1}`; - } -}; - -export const chooseAccount = async (ctx: PagesGeneratorContext) => { - const s = spinner(); - s.start(`Selecting Cloudflare account ${dim("retrieving accounts")}`); - const accounts = await listAccounts(); - - let accountId: string; - - if (Object.keys(accounts).length == 1) { - const accountName = Object.keys(accounts)[0]; - accountId = accounts[accountName]; - s.stop(`${brandColor("account")} ${dim(accountName)}`); - } else { - s.stop( - `${brandColor("account")} ${dim("more than one account available")}` - ); - const accountOptions = Object.entries(accounts).map(([name, id]) => ({ - label: name, - value: id, - })); - - accountId = await inputPrompt({ - type: "select", - question: "Which account do you want to use?", - options: accountOptions, - label: "account", - defaultValue: accountOptions[0].value, - }); - } - const accountName = Object.keys(accounts).find( - (name) => accounts[name] == accountId - ) as string; - - ctx.account = {id: accountId, name: accountName}; -}; - -export const printSummary = async (ctx: PagesGeneratorContext) => { - const nextSteps = [ - [`Navigate to the new directory`, `cd ${ctx.project.name}`], - [ - `Run the development server`, - `${npm} run ${ctx.framework?.config.devCommand ?? "start"}`, - ], - [ - `Deploy your application`, - `${npm} run ${ctx.framework?.config.deployCommand ?? "deploy"}`, - ], - [ - `Read the documentation`, - `https://developers.cloudflare.com/${ - ctx.framework - ? ctx.framework.config.type === "workers" - ? "workers" - : "pages" - : "workers" - }`, - ], - [`Stuck? Join us at`, `https://discord.gg/cloudflaredev`], - ]; - - if (ctx.deployedUrl) { - const msg = [ - `${gray(shapes.leftT)}`, - `${bgGreen(" SUCCESS ")}`, - `${dim(`View your deployed application at`)}`, - `${blue(ctx.deployedUrl)}`, - ].join(" "); - logRaw(msg); - } else { - const msg = [ - `${gray(shapes.leftT)}`, - `${bgGreen(" APPLICATION CREATED ")}`, - `${dim(`Deploy your application with`)}`, - `${blue( - `${npm} run ${ctx.framework?.config.deployCommand ?? "deploy"}` - )}`, - ].join(" "); - logRaw(msg); - } - - newline(); - nextSteps.forEach((entry) => { - log(`${dim(entry[0])} ${blue(entry[1])}`); - }); - newline(); - - if (ctx.deployedUrl) { - const success = await poll(ctx.deployedUrl); - if (success) { - if (ctx.args.open) { - await openInBrowser(ctx.deployedUrl); - } - } - } - endSection("See you again soon!"); - process.exit(0); -}; - -export async function getProductionBranch(cwd: string) { - try { - const productionBranch = await runCommand( - // "git branch --show-current", // git@^2.22 - "git rev-parse --abbrev-ref HEAD", // git@^1.6.3 - { - silent: true, - cwd, - useSpinner: false, - captureOutput: true, - } - ); - - return productionBranch.trim(); - } catch (err) { - } - - return "main"; -} diff --git a/packages/create-r2-explorer/src/helpers/cli.ts b/packages/create-r2-explorer/src/helpers/cli.ts deleted file mode 100644 index 9b12ca8..0000000 --- a/packages/create-r2-explorer/src/helpers/cli.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { exit } from "process"; -import open from "open"; -import { brandColor, dim, gray, white, red, hidden, bgRed } from "./colors"; - -export const shapes = { - diamond: "◇", - dash: "─", - radioInactive: "○", - radioActive: "●", - - bar: "│", - leftT: "├", - rigthT: "┤", - - arrows: { - left: "‹", - right: "›", - }, - - corners: { - tl: "╭", - bl: "╰", - tr: "╮", - br: "╯", - }, -}; - -export const status = { - error: bgRed(` ERROR `), - warning: bgRed(` WARNING `), - info: bgRed(` INFO `), - success: bgRed(` SUCCESS `), -}; - -// Returns a string containing n non-trimmable spaces -// This is useful for places where clack trims lines of output -// but we need leading spaces -export const space = (n = 1) => { - return hidden("\u200A".repeat(n)); -}; - -// Primitive for printing to stdout. Use this instead of -// console.log or printing to stdout directly -export const logRaw = (msg: string) => { - process.stdout.write(`${msg}\n`); -}; - -// A simple stylized log for use within a prompt -export const log = (msg: string) => { - const lines = msg.split("\n").map((ln) => `${gray(shapes.bar)} ${white(ln)}`); - - logRaw(lines.join("\n")); -}; - -export const newline = () => { - log(""); -}; - -// Log a simple status update with a style similar to the clack spinner -export const updateStatus = (msg: string) => { - logRaw(`${gray(shapes.leftT)} ${msg}`); - newline(); -}; - -export const startSection = (heading: string, subheading?: string) => { - logRaw( - `${gray(shapes.corners.tl)} ${brandColor(heading)} ${ - subheading ? dim(subheading) : "" - }` - ); - newline(); -}; - -export const endSection = (heading: string, subheading?: string) => { - logRaw( - `${gray(shapes.corners.bl)} ${brandColor(heading)} ${ - subheading ? dim(subheading) : "" - }\n` - ); -}; - -export const cancel = (msg: string) => { - newline(); - logRaw(`${gray(shapes.corners.bl)} ${white.bgRed(` X `)} ${dim(msg)}`); -}; - -export const warn = (msg: string) => { - newline(); - logRaw(`${gray(shapes.corners.bl)} ${status.warning} ${dim(msg)}`); -}; - -// Strip the ansi color characters out of the line when calculating -// line length, otherwise the padding will be thrown off -// Used from https://github.com/natemoo-re/clack/blob/main/packages/prompts/src/index.ts -export const stripAnsi = (str: string) => { - const pattern = [ - "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", - "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))", - ].join("|"); - const regex = RegExp(pattern, "g"); - - return str.replace(regex, ""); -}; - -export const crash = (msg?: string): never => { - if (msg) { - process.stderr.write(red(msg)); - process.stderr.write("\n"); - } - exit(1); -}; - -/** - * An extremely simple wrapper around the open command. - * Specifically, it adds an 'error' event handler so that when this function - * is called in environments where we can't open the browser (e.g. GitHub Codespaces, - * StackBlitz, remote servers), it doesn't just crash the process. - * - * @param url the URL to point the browser at - */ -export async function openInBrowser(url: string): Promise { - updateStatus("Opening browser"); - const childProcess = await open(url); - childProcess.on("error", () => { - warn("Failed to open browser"); - }); -} diff --git a/packages/create-r2-explorer/src/helpers/codemod.ts b/packages/create-r2-explorer/src/helpers/codemod.ts deleted file mode 100644 index 9b1301b..0000000 --- a/packages/create-r2-explorer/src/helpers/codemod.ts +++ /dev/null @@ -1,57 +0,0 @@ -import path from "path"; -import * as recast from "recast"; -import * as esprimaParser from "recast/parsers/esprima"; -import * as typescriptParser from "recast/parsers/typescript"; -import { crash } from "./cli"; -import { readFile, writeFile } from "./files"; -import type { Program } from "esprima"; - -// Parse an input string as javascript and return an ast -export const parseJs = (src: string) => { - try { - return recast.parse(src, { parser: esprimaParser }); - } catch (error) { - crash("Error parsing js template."); - } -}; - -// Parse an input string as typescript and return an ast -export const parseTs = (src: string) => { - try { - return recast.parse(src, { parser: typescriptParser }); - } catch (error) { - crash("Error parsing ts template."); - } -}; - -// Parse a provided file with recast and return an ast -// Selects the correct parser based on the file extension -export const parseFile = (filePath: string) => { - const lang = path.extname(filePath).slice(1); - const parser = lang === "js" ? esprimaParser : typescriptParser; - - try { - const fileContents = readFile(path.resolve(filePath)); - - if (fileContents) { - return recast.parse(fileContents, { parser }) as Program; - } - } catch (error) { - crash(`Error parsing file: ${filePath}`); - } - - return null; -}; - -// Transform a file with the provided transformer methods and write it back to disk -export const transformFile = ( - filePath: string, - methods: recast.types.Visitor -) => { - const ast = parseFile(filePath); - - if (ast) { - recast.visit(ast, methods); - writeFile(filePath, recast.print(ast).code); - } -}; diff --git a/packages/create-r2-explorer/src/helpers/colors.ts b/packages/create-r2-explorer/src/helpers/colors.ts deleted file mode 100644 index b15721f..0000000 --- a/packages/create-r2-explorer/src/helpers/colors.ts +++ /dev/null @@ -1,32 +0,0 @@ -import chalk from "chalk"; - -const { white, gray, dim, hidden, bold } = chalk; - -const brandColor = chalk.hex("#BD5B08"); - -const black = chalk.hex("#111"); -const blue = chalk.hex("#0E838F"); -const bgBlue = black.bgHex("#0E838F"); -const red = chalk.hex("#AB2526"); -const bgRed = black.bgHex("#AB2526"); -const green = chalk.hex("#218529"); -const bgGreen = black.bgHex("#218529"); -const yellow = chalk.hex("#7F7322"); -const bgYellow = black.bgHex("#7F7322"); - -export { - blue, - bgBlue, - green, - bgGreen, - red, - bgRed, - yellow, - bgYellow, - brandColor, - dim, - white, - gray, - hidden, - bold, -}; diff --git a/packages/create-r2-explorer/src/helpers/command.ts b/packages/create-r2-explorer/src/helpers/command.ts deleted file mode 100644 index 9c10f17..0000000 --- a/packages/create-r2-explorer/src/helpers/command.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { existsSync, rmSync } from "fs"; -import path from "path"; -import { spawn } from "cross-spawn"; -import { endSection, stripAnsi } from "./cli"; -import { brandColor, dim } from "./colors"; -import { spinner } from "./interactive"; -import { detectPackageManager } from "./packages"; -import type { PagesGeneratorContext } from "types"; - -/** - * Command can be either: - * - a string, like `git commit -m "Changes"` - * - a string array, like ['git', 'commit', '-m', '"Initial commit"'] - * - * The string version is a convenience but is unsafe if your args contain spaces - */ -type Command = string | string[]; - -type RunOptions = { - startText?: string; - doneText?: string | ((output: string) => string); - silent?: boolean; - captureOutput?: boolean; - useSpinner?: boolean; - env?: NodeJS.ProcessEnv; - cwd?: string; - /** If defined this function is called to all you to transform the output from the command into a new string. */ - transformOutput?: (output: string) => string; - /** If defined, this function is called to return a string that is used if the `transformOutput()` fails. */ - fallbackOutput?: (error: unknown) => string; -}; - -type MultiRunOptions = RunOptions & { - commands: Command[]; - startText: string; -}; - -type PrintOptions = { - promise: Promise | (() => Promise); - useSpinner?: boolean; - startText: string; - doneText?: string | ((output: T) => string); -}; - -export const runCommand = async ( - command: Command, - opts: RunOptions = {} -): Promise => { - if (typeof command === "string") { - command = command.trim().replace(/\s+/g, ` `).split(" "); - } - - return printAsyncStatus({ - useSpinner: opts.useSpinner ?? opts.silent, - startText: opts.startText || command.join(" ").trim(), - doneText: opts.doneText, - promise() { - const [executable, ...args] = command; - - const cmd = spawn(executable, [...args], { - // TODO: ideally inherit stderr, but npm install uses this for warnings - // stdio: [ioMode, ioMode, "inherit"], - stdio: opts.silent ? "pipe" : "inherit", - env: { - ...process.env, - ...opts.env, - }, - cwd: opts.cwd, - }); - - let output = ``; - - if (opts.captureOutput ?? opts.silent) { - cmd.stdout?.on("data", (data) => { - output += data; - }); - cmd.stderr?.on("data", (data) => { - output += data; - }); - } - - return new Promise((resolve, reject) => { - cmd.on("close", (code) => { - try { - if (code !== 0) { - throw new Error(output, { cause: code }); - } - - // Process any captured output - const transformOutput = - opts.transformOutput ?? ((result: string) => result); - const processedOutput = transformOutput(stripAnsi(output)); - - // Send the captured (and processed) output back to the caller - resolve(processedOutput); - } catch (e) { - // Something went wrong. - // Perhaps the command or the transform failed. - // If there is a fallback use the result of calling that - if (opts.fallbackOutput) { - resolve(opts.fallbackOutput(e)); - } else { - reject(new Error(output, { cause: e })); - } - } - }); - - cmd.on("error", (code) => { - reject(code); - }); - }); - }, - }); -}; - -// run multiple commands in sequence (not parallel) -export async function runCommands({ commands, ...opts }: MultiRunOptions) { - return printAsyncStatus({ - useSpinner: opts.useSpinner ?? opts.silent, - startText: opts.startText, - doneText: opts.doneText, - async promise() { - const results = []; - for (const command of commands) { - results.push(await runCommand(command, { ...opts, useSpinner: false })); - } - return results.join("\n"); - }, - }); -} - -export const printAsyncStatus = async ({ - promise, - ...opts -}: PrintOptions): Promise => { - let s: ReturnType | undefined; - - if (opts.useSpinner) { - s = spinner(); - } - - s?.start(opts?.startText); - - if (typeof promise === "function") { - promise = promise(); - } - - try { - const output = await promise; - - const doneText = - typeof opts.doneText === "function" - ? opts.doneText(output) - : opts.doneText; - s?.stop(doneText); - } catch (err) { - s?.stop((err as Error).message); - } finally { - s?.stop(); - } - - return promise; -}; - -export const retry = async (times: number, fn: () => Promise) => { - let error: unknown = null; - while (times > 0) { - try { - return await fn(); - } catch (e) { - error = e; - times--; - } - } - throw error; -}; - -// Prints the section header & footer while running the `cmd` -export const runFrameworkGenerator = async ( - ctx: PagesGeneratorContext, - cmd: string -) => { - endSection( - `Continue with ${ctx.framework?.config.displayName}`, - `via \`${cmd.trim()}\`` - ); - - if (process.env.VITEST) { - const flags = ctx.framework?.config.testFlags ?? []; - cmd = `${cmd} ${flags.join(" ")}`; - } - - await runCommand(cmd); -}; - -type InstallConfig = { - startText?: string; - doneText?: string; - dev?: boolean; -}; - -export const installPackages = async ( - packages: string[], - config: InstallConfig -) => { - const { npm } = detectPackageManager(); - - let saveFlag; - let cmd; - switch (npm) { - case "yarn": - cmd = "add"; - saveFlag = config.dev ? "-D" : ""; - break; - case "npm": - case "pnpm": - default: - cmd = "install"; - saveFlag = config.dev ? "--save-dev" : "--save"; - break; - } - - await runCommand(`${npm} ${cmd} ${saveFlag} ${packages.join(" ")}`, { - ...config, - silent: true, - }); -}; - -export const npmInstall = async () => { - const { npm } = detectPackageManager(); - - await runCommand(`${npm} install`, { - silent: true, - startText: "Installing dependencies", - doneText: `${brandColor("installed")} ${dim(`via \`${npm} install\``)}`, - }); -}; - -// Resets the package manager context for a project by clearing out existing dependencies -// and lock files then re-installing. -export const resetPackageManager = async (ctx: PagesGeneratorContext) => { - const { npm } = detectPackageManager(); - - // Only do this when using pnpm or yarn - if (npm === "npm") return; - - const nodeModulesPath = path.join(ctx.project.path, "node_modules"); - rmSync(nodeModulesPath, { recursive: true }); - const lockfilePath = path.join(ctx.project.path, "package-lock.json"); - rmSync(lockfilePath); - - await runCommand(`${npm} install`, { - silent: true, - startText: "Installing dependencies", - doneText: `${brandColor("installed")} ${dim(`via \`${npm} install\``)}`, - }); -}; - -export const installWrangler = async () => { - const { npm } = detectPackageManager(); - - // Exit early if already installed - if (existsSync(path.resolve("node_modules", "wrangler"))) { - return; - } - - await installPackages([`wrangler`], { - dev: true, - startText: `Installing wrangler ${dim( - "A command line tool for building Cloudflare Workers" - )}`, - doneText: `${brandColor("installed")} ${dim( - `via \`${npm} install wrangler --save-dev\`` - )}`, - }); -}; - -export const isLoggedIn = async () => { - const { npx } = detectPackageManager(); - try { - const output = await runCommand(`${npx} wrangler whoami`, { - silent: true, - }); - return /You are logged in/.test(output); - } catch (error) { - return false; - } -}; - -export const wranglerLogin = async () => { - const { npx } = detectPackageManager(); - - const s = spinner(); - s.start(`Logging into Cloudflare ${dim("checking authentication status")}`); - const alreadyLoggedIn = await isLoggedIn(); - s.stop(brandColor(alreadyLoggedIn ? "logged in" : "not logged in")); - if (alreadyLoggedIn) return true; - - s.start(`Logging into Cloudflare ${dim("This will open a browser window")}`); - - // We're using a custom spinner since this is a little complicated. - // We want to vary the done status based on the output - const output = await runCommand(`${npx} wrangler login`, { - silent: true, - }); - const success = /Successfully logged in/.test(output); - - const verb = success ? "allowed" : "denied"; - s.stop(`${brandColor(verb)} ${dim("via `wrangler login`")}`); - - return success; -}; - -export const listAccounts = async () => { - const { npx } = detectPackageManager(); - - const output = await runCommand(`${npx} wrangler whoami`, { - silent: true, - }); - - const accounts: Record = {}; - output.split("\n").forEach((line) => { - const match = line.match(/│\s+(.+)\s+│\s+(\w{32})\s+│/); - if (match) { - accounts[match[1].trim()] = match[2].trim(); - } - }); - - return accounts; -}; diff --git a/packages/create-r2-explorer/src/helpers/files.ts b/packages/create-r2-explorer/src/helpers/files.ts deleted file mode 100644 index 84f6f4f..0000000 --- a/packages/create-r2-explorer/src/helpers/files.ts +++ /dev/null @@ -1,108 +0,0 @@ -import fs, { existsSync } from "fs"; -import { crash } from "./cli"; -import type { PagesGeneratorContext } from "types"; - -export const writeFile = (path: string, content: string) => { - try { - fs.writeFileSync(path, content); - } catch (error) { - crash(error as string); - } -}; - -export const readFile = (path: string) => { - try { - return fs.readFileSync(path, "utf-8"); - } catch (error) { - return crash(error as string); - } -}; - -export const readJSON = (path: string) => { - const contents = readFile(path); - return contents ? JSON.parse(contents) : contents; -}; - -export const writeJSON = ( - path: string, - object: object, - stringifySpace?: number | string -) => { - writeFile(path, JSON.stringify(object, null, stringifySpace)); -}; - -// Probes a list of paths and returns the first one that exists -// If one isn't found, throws an error with the given message -export const probePaths = ( - paths: string[], - errorMsg = "Failed to find required file." -) => { - for (const path of paths) { - if (existsSync(path)) { - return path; - } - } - - crash(errorMsg); - process.exit(1); // hack to make typescript happy -}; - -export const usesTypescript = (projectRoot = ".") => { - return existsSync(`${projectRoot}/tsconfig.json`); -}; - -const eslintRcExts = ["js", "cjs", "yaml", "yml", "json"] as const; - -type EslintRcFileName = `.eslintrc.${typeof eslintRcExts[number]}`; - -type EslintUsageInfo = - | { - used: true; - configType: EslintRcFileName | "eslint.config.js" | "package.json"; - } - | { - used: false; - }; - -/* - checks if eslint is used and if so returns the configuration type - (for the various configuration types see: - - https://eslint.org/docs/latest/use/configure/configuration-files#configuration-file-formats - - https://eslint.org/docs/latest/use/configure/configuration-files-new ) -*/ -export const usesEslint = (ctx: PagesGeneratorContext): EslintUsageInfo => { - for (const ext of eslintRcExts) { - const eslintRcFilename = `.eslintrc.${ext}` as EslintRcFileName; - if (existsSync(`${ctx.project.path}/${eslintRcFilename}`)) { - return { - used: true, - configType: eslintRcFilename, - }; - } - } - - if (existsSync(`${ctx.project.path}/eslint.config.js`)) { - return { - used: true, - configType: "eslint.config.js", - }; - } - - try { - const pkgJson = readJSON(`${ctx.project.path}/package.json`); - if (pkgJson.eslintConfig) { - return { - used: true, - configType: "package.json", - }; - } - } catch {} - - return { used: false }; -}; - -// Generate a compatibility date flag -export const compatDateFlag = () => { - const date = new Date(); - return `--compatibility-date=${date.toISOString().slice(0, 10)}`; -}; diff --git a/packages/create-r2-explorer/src/helpers/interactive.ts b/packages/create-r2-explorer/src/helpers/interactive.ts deleted file mode 100644 index dc10d3d..0000000 --- a/packages/create-r2-explorer/src/helpers/interactive.ts +++ /dev/null @@ -1,321 +0,0 @@ -import { TextPrompt, SelectPrompt, ConfirmPrompt } from "@clack/core"; -import { isCancel } from "@clack/prompts"; -import logUpdate from "log-update"; -import { shapes, cancel, space, status, newline, logRaw } from "./cli"; -import { blue, dim, gray, brandColor, bold } from "./colors"; -import type { C3Arg, CArgs } from "types"; - -const grayBar = gray(shapes.bar); -const blCorner = gray(shapes.corners.bl); -const leftT = gray(shapes.leftT); - -export type Option = { - label: string; - value: string; -}; - -export type BasePromptConfig = { - // Displayed to the user while prompting for this input - question: string; - // Further clarifies the question - helpText?: string; - // The value to use by default - defaultValue?: string | boolean; - // The status label to be shown after submitting - label: string; - // Pretty-prints the value in the interactive prompt - format?: (value: C3Arg) => string; - // Returns a user displayed error if the value is invalid - validate?: (value: C3Arg) => string | void; -}; - -export type TextPromptConfig = BasePromptConfig & { - type: "text"; -}; -export type SelectPromptConfig = - | BasePromptConfig & { - type: "select"; - options: Option[]; - }; -export type ConfirmPromptConfig = - | BasePromptConfig & { - type: "confirm"; - activeText?: string; - inactiveText?: string; - }; - -export type PromptConfig = - | TextPromptConfig - | ConfirmPromptConfig - | SelectPromptConfig; - -export const processArgument = async ( - args: Partial, - name: keyof CArgs, - promptConfig: PromptConfig -) => { - let value = args[name]; - const renderSubmitted = getRenderers(promptConfig).submit; - - // If the value has already been set via args, use that - if (value !== undefined) { - promptConfig.validate?.(value); - - const lines = renderSubmitted({ value }); - logRaw(lines.join("\n")); - - return value as T; - } - - value = await inputPrompt(promptConfig); - - return value as T; -}; - -type RenderProps = - | Omit, "prompt"> - | Omit - | Omit; - -export const inputPrompt = async (promptConfig: PromptConfig) => { - const renderers = getRenderers(promptConfig); - - let prompt: SelectPrompt