Skip to content

Commit

Permalink
init commit
Browse files Browse the repository at this point in the history
Co-authored-by: Isaac McFadyen <[email protected]>
  • Loading branch information
helloimalastair and isaac-mcfadyen committed Nov 13, 2023
0 parents commit 8d3b74d
Show file tree
Hide file tree
Showing 19 changed files with 348 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.lockb binary diff=lockb
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"useTabs": true,
"trailingComma": "all"
}
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"editor.insertSpaces": false,
"editor.detectIndentation": false,
"editor.tabSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# CloudSpark

> [!WARNING]
> This package is under construction and is **not** ready for production use.
CloudSpark is a Node CLI tool that allows easy bootstrapping of a Cloudflare Developer Platform project, which may include a Worker, Pages site, or any other dependent bindings.

## Installation

While under development, CloudSpark is not on NPM. To install from the latest GitHub commit:
```bash
npm install -g https://github.com/Cloudflare-Community/cloudspark
```
15 changes: 15 additions & 0 deletions build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { build } from "esbuild";

await build({
entryPoints: ["./src/index.ts"],
bundle: true,
minify: true,
sourcemap: true,
outdir: "./dist",
format: "esm",
platform: "node",
target: "esnext",
banner: {
js: `import path from "path";\nimport { fileURLToPath } from "url";\nimport { createRequire as topLevelCreateRequire } from "module";\nconst require = topLevelCreateRequire(import.meta.url);\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);`,
},
})
Binary file added bun.lockb
Binary file not shown.
28 changes: 28 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "cloudspark",
"type": "module",
"bin": {
"cloudspark": "./dist/index.js"
},
"files": [
"./dist/*"
],
"scripts": {
"dev": "tsx src/index.ts",
"build": "tsx build.ts"
},
"devDependencies": {
"@clack/prompts": "^0.7.0",
"@cloudflare/workers-types": "^4.20231025.0",
"@types/argparse": "^2.0.13",
"@types/degit": "^2.8.6",
"@types/node": "^20.9.0",
"argparse": "^2.0.1",
"clack": "^0.1.0",
"commander": "^11.1.0",
"degit": "^2.8.4",
"esbuild": "^0.19.5",
"eslint": "^8.53.0",
"prettier": "^3.1.0"
}
}
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const THIS_REPO = "cloudflare/workers-sdk"
25 changes: 25 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { normalize } from "path";
import { intro, outro, select, spinner, text, log, isCancel, confirm } from "@clack/prompts";
import { statSync, readdirSync, existsSync } from "fs";
import { program } from "commander";
import interactive from "./init/interactive";
import express from "./init/express";

const cli = program
.name("cloudspark")
.description("Cloudspark CLI, your CommunityApproved™ Cloudflare Developer Platform CLI");

cli.command("init")
.description("Initialize a new Worker")
.argument("[repo]", "The repository to initialize.")
.argument("[folder]", "The folder to initialize to.")
.option("--force", "Force clone the template, ignoring existing files.")
.action(async (repo, folder, args) => {
if (repo == null) {
await interactive(args.force ?? false);
} else {
await express(repo, folder, args.force ?? false)
}
})

await cli.parseAsync();
26 changes: 26 additions & 0 deletions src/init/clone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import degit from "degit";
import { log, outro, spinner } from "@clack/prompts";

export default async function clone(repo: string, target: string) {
// Start a loading spinner.
const spin = spinner();
spin.start("Cloning template...");

// Create a degit emitter.
const emitter = degit(repo, {
cache: false,
force: true
});

// Clone the repo.
emitter.on("warn", (warning) => log.warn(warning.message));
try {
await emitter.clone(target);
} catch (e: any) {
spin.stop();
log.error(e.message);
outro("Cancelled. Artifacts may be present.");
process.exit(1);
}
spin.stop("Successfully cloned template.");
}
42 changes: 42 additions & 0 deletions src/init/express.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import clone from "./clone";
import { normalize } from "path";
import { statSync, readdirSync, existsSync } from "fs";
import { intro, outro, select, text, log, isCancel, confirm } from "@clack/prompts";
import { validateFolder } from "./utils";

export default async (template: string, target: string | null, force: boolean) => {
// User didn't provide a repo, enter interactive mode.
intro("Cloudspark CLI ⚡️");

let finalTarget: string;
if (target != null) {
log.message(`Express mode active. Initializing "${template}" in folder "${target}".`);
finalTarget = target;
} else {
log.message(`Express mode active. Initializing "${template}".`);

// Ask the user for a target directory.
const target = await text({
message: "Enter the target directory you want to initialize into",
placeholder: `./worker`,
defaultValue: `./worker`,
});
if (isCancel(target)) {
outro("Cancelled. No changes were made.");
process.exit(0);
}
finalTarget = target;
}

// Normalize the path.
const normalizedTarget = normalize(finalTarget);

// Perform validation on the path.
await validateFolder(normalizedTarget, force);

// Clone the repo.
await clone(template, normalizedTarget);

outro(`Done! Your Worker is ready to go at ${finalTarget}`)
process.exit(0);
}
51 changes: 51 additions & 0 deletions src/init/interactive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import clone from "./clone";
import { normalize } from "path";
import { statSync, readdirSync, existsSync, rmSync } from "fs";
import { intro, outro, select, text, log, isCancel, confirm } from "@clack/prompts";
import { validateFolder } from "./utils";

export default async (force: boolean) => {
// User didn't provide a repo, enter interactive mode.
intro("Cloudspark CLI ⚡️");
log.message("Let's get started by selecting a template.");
log.message("You can also run `cloudspark init <repo> <folder>` to initialize a specific repository.");

// Ask the user for a template selection.
const result: string | symbol = await select({
message: "Select a template",
options: [
{
label: "Hello World",
value: "hello-world",
hint: "A simple hello world Worker template"
},
]
})
if (isCancel(result)) {
outro("Cancelled. No changes were made.");
process.exit(0);
}

// Ask the user for a target directory.
const target = await text({
message: "Enter the target directory you want to initialize in",
placeholder: `./${result}`,
defaultValue: `./${result}`,
});
if (isCancel(target)) {
outro("Cancelled. No changes were made.");
process.exit(0);
}

// Normalize the path.
const normalizedTarget = normalize(target);

// Perform validation on the path.
await validateFolder(normalizedTarget, force);

// Clone the repo.
await clone(`${THIS_REPO}/${result}`, normalizedTarget);

outro(`Done! Your Worker is ready to go at ${target}`)
process.exit(0);
}
42 changes: 42 additions & 0 deletions src/init/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { statSync, readdirSync, existsSync, rmSync } from "fs";
import { intro, outro, select, text, log, isCancel, confirm } from "@clack/prompts";

export const validateFolder = async (target: string, force: boolean) => {
if (existsSync(target)) {
// Check whether target exists and is a file or symlink.
const statResult = statSync(target);
if (statResult.isFile() || statResult.isSymbolicLink()) {
if (force) {
log.warn("Target is a file or symlink, but force flag is set, removing file and continuing.");
} else {
log.error("Target is a file or symlink.");
const overwrite = await confirm({
message: "Remove and continue?",
initialValue: false,
});
if (isCancel(overwrite) || !overwrite) {
outro("Cancelled. No changes were made.");
process.exit(0);
}
}
rmSync(target);
}

// Check whether target already has files
if (statResult.isDirectory() && readdirSync(target).length > 0) {
if (force) {
log.warn("Target directory is not empty but force flag is set, continuing.")
} else {
log.warn("Target directory is not empty.");
const overwrite = await confirm({
message: "Continue anyway?",
initialValue: false,
});
if (isCancel(overwrite) || !overwrite) {
outro("Cancelled. No changes were made.");
process.exit(0);
}
}
}
}
}
15 changes: 15 additions & 0 deletions templates/hello-world/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "hello-world",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "wrangler dev",
"deploy": "wrangler publish"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20231025.0",
"typescript": "^5.2.2",
"wrangler": "^3.15.0"
}
}
33 changes: 33 additions & 0 deletions templates/hello-world/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Welcome to Cloudflare Workers! This is your first worker.
*
* - Run `wrangler dev src/index.ts` in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run `wrangler publish src/index.ts --name my-worker` to publish your worker
*
* Learn more at https://developers.cloudflare.com/workers/
*/

export interface Env {
// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/
// MY_KV_NAMESPACE: KVNamespace;
//
// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/
// MY_DURABLE_OBJECT: DurableObjectNamespace;
//
// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/
// MY_BUCKET: R2Bucket;
//
// Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/
// MY_SERVICE: Fetcher;
}

export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
return new Response("Hello World!");
},
};
23 changes: 23 additions & 0 deletions templates/hello-world/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "es2021",
"lib": [
"es2021"
],
"jsx": "react",
"module": "es2022",
"moduleResolution": "node",
"types": [
"@cloudflare/workers-types"
],
"resolveJsonModule": true,
"allowJs": true,
"checkJs": false ,
"noEmit": true,
"isolatedModules": true ,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
3 changes: 3 additions & 0 deletions templates/hello-world/wrangler.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name = "hello-world"
main = "src/index.ts"
compatibility_date = "2023-11-12"
18 changes: 18 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"lib": ["esnext"],
"isolatedModules": true,
"types": ["node"],
"noEmit": true,
"strict": true,
"esModuleInterop": true,
},
"include": [
"src",
"types",
"build.ts"
]
}

0 comments on commit 8d3b74d

Please sign in to comment.