├── .editorconfig ├── .gitattributes ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── build ├── CREATE.md ├── cloudspark.json ├── cmdEntry.js ├── create.json ├── createShim.js └── index.ts ├── bun.lockb ├── package.json ├── src ├── constants.ts ├── index.ts └── init │ ├── clone.ts │ ├── index.ts │ ├── prompts.ts │ └── utils.ts ├── templates └── hello-world │ ├── info.json │ ├── js │ ├── .editorconfig │ ├── .prettierrc │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ └── wrangler.toml │ ├── rs │ ├── Cargo.toml │ ├── README.md │ ├── package.json │ ├── src │ │ └── lib.rs │ └── wrangler.toml │ └── ts │ ├── .editorconfig │ ├── .prettierrc │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ ├── tsconfig.json │ └── wrangler.toml └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.yml] 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.lockb binary diff=lockb -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.insertSpaces": false, 3 | "editor.detectIndentation": false, 4 | "editor.tabSize": 2, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode" 6 | } 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | their relevant emails. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 Cloudspark Contributors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudSpark 2 | 3 | _As with all projects in the Cloudflare-Community Github Organization, and the `cloudflare.community` domain, this is not an official Cloudflare product. It is not officially supported by Cloudflare or its associates/partners. Views expressed here do not represent the views of Cloudflare or its employees. All rights to the Cloudflare Logo and other assets belong to Cloudflare Inc._ 4 | 5 | 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. 6 | 7 | ## Usage 8 | 9 | Currently, CloudSpark only has the `init` command, which is also used by [`create-cloudspark`](https://npmjs.com/package/create-cloudspark). 10 | 11 | ```sh 12 | $ npx cloudspark init --help 13 | Usage: cloudspark init [options] [repo] [folder] 14 | 15 | Initialize a new Worker 16 | 17 | Arguments: 18 | repo The repository to initialize. 19 | folder The folder to initialize to. 20 | 21 | Options: 22 | -y Bypass prompts and use default values(doesn't apply to output folder conflicts). 23 | -f, --force Force clone the template, ignoring existing files. 24 | -h, --help display help for command 25 | ``` 26 | -------------------------------------------------------------------------------- /build/CREATE.md: -------------------------------------------------------------------------------- 1 | # `create-cloudspark` 2 | 3 | _As with all projects in the Cloudflare-Community Github Organization, and the `cloudflare.community` domain, this is not an official Cloudflare product. It is not officially supported by Cloudflare or its associates/partners. Views expressed here do not represent the views of Cloudflare or its employees. All rights to the Cloudflare Logo and other assets belong to Cloudflare Inc._ 4 | 5 | `create-cloudspark` is a single command to help you get started building projects on the Cloudflare Developer Platform. All you need to do is pick up your package manager of choice, and run: 6 | 7 | ```bash 8 | npm create cloudspark 9 | pnpm create cloudspark 10 | yarn create cloudspark 11 | bun create cloudspark 12 | ``` 13 | 14 | ## Options 15 | 16 | `-y`: Bypasses prompts and uses default values (does not apply to output folder conflicts). 17 | 18 | `-f`/`--force`: Force clone the template, ignoring existing files. 19 | -------------------------------------------------------------------------------- /build/cloudspark.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudspark", 3 | "type": "module", 4 | "version": "0.0.5", 5 | "main": "index.js", 6 | "license": "Apache-2.0", 7 | "repository": "Cloudflare-Community/cloudspark", 8 | "keywords": ["cloudflare", "init", "cli", "cloudspark"], 9 | "description": "Cloudspark CLI, your CommunityApproved™ Cloudflare Developer Platform CLI", 10 | "bin": { 11 | "cloudspark": "./entry.js" 12 | }, 13 | "files": ["./index.js", "./entry.js"] 14 | } 15 | -------------------------------------------------------------------------------- /build/cmdEntry.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { cli } from "./index.js"; 3 | await cli.parseAsync(); 4 | -------------------------------------------------------------------------------- /build/create.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-cloudspark", 3 | "type": "module", 4 | "version": "0.0.5", 5 | "main": "index.js", 6 | "license": "Apache-2.0", 7 | "repository": "Cloudflare-Community/cloudspark", 8 | "keywords": ["cloudflare", "init", "cli", "cloudspark"], 9 | "description": "create-cloudspark, just a few seconds to your first Cloudflare Developer Platform Project", 10 | "bin": { 11 | "create-cloudspark": "./index.js" 12 | }, 13 | "files": ["./index.js"], 14 | "dependencies": { 15 | "cloudspark": "0.0.5" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build/createShim.js: -------------------------------------------------------------------------------- 1 | // Shim used by create-cloudspark 2 | import { init } from "cloudspark"; 3 | import { program } from "commander"; 4 | 5 | const cli = program 6 | .name("create-cloudspark") 7 | .description( 8 | "create-cloudspark, just a few seconds to your first Cloudflare Developer Platform Project", 9 | ) 10 | .argument("[repo]", "The repository to initialize.") 11 | .argument("[folder]", "The folder to initialize to.") 12 | .option( 13 | "-y", 14 | "Bypass prompts and use default values(doesn't apply to output folder conflicts).", 15 | ) 16 | .option("-f, --force", "Force clone the template, ignoring existing files.") 17 | // .option("-p, --provider ", "Use a specific git provider(github, gitlab, bitbucket, sourcehut). Defaults to github. Only supported when providing your own repo.") 18 | .action(init); 19 | 20 | await cli.parseAsync(); 21 | 22 | export { init }; 23 | -------------------------------------------------------------------------------- /build/index.ts: -------------------------------------------------------------------------------- 1 | import { cpSync } from "fs"; 2 | import { buildSync } from "esbuild"; 3 | 4 | console.log("[Cloudspark] Building..."); 5 | let start = new Date(); 6 | buildSync({ 7 | entryPoints: ["./src/index.ts"], 8 | bundle: true, 9 | minify: true, 10 | outfile: "./dist/cloudspark/index.js", 11 | format: "esm", 12 | platform: "node", 13 | target: "esnext", 14 | banner: { 15 | js: `import { createRequire as topLevelCreateRequire } from "module";const require = topLevelCreateRequire(import.meta.url);`, 16 | }, 17 | }); 18 | console.log( 19 | "[Cloudspark] Built in " + (new Date().getTime() - start.getTime()) + "ms.", 20 | ); 21 | console.log("[Cloudspark] Scaffolding Project..."); 22 | cpSync("./build/cmdEntry.js", "./dist/cloudspark/entry.js"); 23 | cpSync("./build/cloudspark.json", "./dist/cloudspark/package.json"); 24 | cpSync("README.md", "./dist/cloudspark/README.md"); 25 | cpSync("LICENSE", "./dist/cloudspark/LICENSE"); 26 | console.log("[Cloudspark] Done!"); 27 | 28 | console.log("[Create] Building..."); 29 | start = new Date(); 30 | buildSync({ 31 | entryPoints: ["./build/createShim.js"], 32 | bundle: true, 33 | minify: true, 34 | outfile: "./dist/create/index.js", 35 | format: "esm", 36 | platform: "node", 37 | target: "esnext", 38 | // external: ["cloudspark"], 39 | banner: { 40 | js: `#!/usr/bin/env node\nimport { createRequire as topLevelCreateRequire } from "module";const require = topLevelCreateRequire(import.meta.url);`, 41 | }, 42 | }); 43 | console.log( 44 | "[Create] Built in " + (new Date().getTime() - start.getTime()) + "ms.", 45 | ); 46 | console.log("[Cloudspark] Scaffolding Project..."); 47 | cpSync("./build/create.json", "./dist/create/package.json"); 48 | cpSync("./build/CREATE.md", "./dist/create/README.md"); 49 | cpSync("LICENSE", "./dist/create/LICENSE"); 50 | console.log("[Cloudspark] Done!"); 51 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cloudflare-Community/cloudspark/0631d2a263f198d929a69dbd5f7d4a6a0ab72e84/bun.lockb -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "private": true, 4 | "scripts": { 5 | "dev": "tsx src/index.ts", 6 | "build": "tsx build/index.ts", 7 | "prepack": "npm run build", 8 | "prepublish": "npm run build" 9 | }, 10 | "devDependencies": { 11 | "@clack/prompts": "^0.7.0", 12 | "@types/node": "^20.10.4", 13 | "clack": "^0.1.0", 14 | "commander": "^11.1.0", 15 | "esbuild": "^0.19.9", 16 | "eslint": "^8.55.0", 17 | "giget": "github:helloimalastair/giget", 18 | "prettier": "^3.1.1", 19 | "tsx": "^4.6.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const THIS_REPO = "cloudflare-community/cloudspark"; 2 | 3 | const LANGUAGES = { 4 | js: { 5 | name: "js", 6 | displayName: "JavaScript", 7 | description: "Plain JavaScript", 8 | }, 9 | ts: { 10 | name: "ts", 11 | displayName: "TypeScript", 12 | description: "TypeScript with types set up for Cloudflare Workers", 13 | }, 14 | rs: { 15 | name: "rs", 16 | displayName: "Rust", 17 | description: "Rust, running using the workers.rs project", 18 | }, 19 | }; 20 | 21 | export type Language = { 22 | name: string; 23 | displayName: string; 24 | description: string; 25 | }; 26 | export type Template = { 27 | name: string; 28 | displayName: string; 29 | description: string; 30 | languages: Language[]; 31 | }; 32 | export const TEMPLATES = [ 33 | { 34 | name: "hello-world", 35 | displayName: "Hello World", 36 | description: 37 | "A simple Hello World script for getting started with Cloudflare Workers", 38 | languages: [LANGUAGES.ts, LANGUAGES.js, LANGUAGES.rs], 39 | }, 40 | ]; 41 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import init from "./init"; 2 | import { program } from "commander"; 3 | 4 | const cli = program 5 | .name("cloudspark") 6 | .description( 7 | "Cloudspark CLI, your CommunityApproved™ Cloudflare Developer Platform CLI", 8 | ); 9 | 10 | cli 11 | .command("init") 12 | .description("Initialize a new Worker") 13 | .argument("[repo]", "The repository to initialize.") 14 | .argument("[folder]", "The folder to initialize to.") 15 | .option( 16 | "-y", 17 | "Bypass prompts and use default values(doesn't apply to output folder conflicts).", 18 | ) 19 | .option("-f, --force", "Force clone the template, ignoring existing files.") 20 | // .option("-p, --provider ", "Use a specific git provider(github, gitlab, bitbucket, sourcehut). Defaults to github. Only supported when providing your own repo.") 21 | .action(init); 22 | 23 | export { init, cli }; 24 | -------------------------------------------------------------------------------- /src/init/clone.ts: -------------------------------------------------------------------------------- 1 | import { THIS_REPO } from "../constants"; 2 | import { downloadTemplate } from "giget"; 3 | import { outro, spinner } from "@clack/prompts"; 4 | 5 | const buildSource = (template: string, language: string) => 6 | `github:${THIS_REPO}/templates/${template}/${language}`; 7 | 8 | async function clone(source: string, target: string) { 9 | // Start a loading spinner. 10 | const spin = spinner(); 11 | spin.start(`Downloading template (this may take a while)...`); 12 | 13 | try { 14 | // Download the template. 15 | await downloadTemplate(source, { 16 | dir: target, 17 | force: true, 18 | }); 19 | } catch (e: any) { 20 | spin.stop(e.message, 1); 21 | outro("Exited. Artifacts may be present on filesystem."); 22 | process.exit(1); 23 | } 24 | 25 | spin.stop("Successfully cloned template."); 26 | } 27 | 28 | export { clone, buildSource }; 29 | -------------------------------------------------------------------------------- /src/init/index.ts: -------------------------------------------------------------------------------- 1 | import { validateFolder } from "./utils"; 2 | import { TEMPLATES } from "../constants"; 3 | import { clone, buildSource } from "./clone"; 4 | import { intro, log, outro } from "@clack/prompts"; 5 | import { 6 | promptForLanguage, 7 | promptForDirectory, 8 | promptForTemplate, 9 | } from "./prompts"; 10 | 11 | export default async ( 12 | repo: string | undefined, 13 | folder: string | undefined, 14 | args: { 15 | y: boolean; 16 | force: boolean; 17 | provider: string | undefined; 18 | }, 19 | ) => { 20 | intro("Cloudspark CLI ⚡️"); 21 | 22 | let templateSource: string; 23 | let targetDir: string; 24 | 25 | if (repo) { 26 | // Normalize Express-mode inputs 27 | log.message("Express mode active. Initializing the template."); 28 | const provider = args.provider ?? "github"; 29 | templateSource = `${provider}:${repo}`; 30 | targetDir = folder ?? "worker"; 31 | } else if (args.y) { 32 | // Bypass Mode 33 | log.message( 34 | "Bypassing all prompts, and initializing the default template.", 35 | ); 36 | const template = TEMPLATES[0]; 37 | templateSource = buildSource(template.name, template.languages[0].name); 38 | targetDir = "worker"; 39 | } else { 40 | // Ask the user for input. 41 | log.message("Let's get started by selecting a template."); 42 | const template = await promptForTemplate(TEMPLATES); 43 | const language = await promptForLanguage(template); 44 | templateSource = buildSource(template.name, language); 45 | targetDir = await promptForDirectory(); 46 | } 47 | 48 | // Perform validation on the path. 49 | await validateFolder(targetDir, args.force); 50 | 51 | // Clone the repo. 52 | await clone(templateSource, targetDir); 53 | 54 | outro(`Done! Your Worker is ready to go at "${targetDir}"`); 55 | process.exit(0); 56 | }; 57 | -------------------------------------------------------------------------------- /src/init/prompts.ts: -------------------------------------------------------------------------------- 1 | import { normalize } from "path"; 2 | import { Template } from "../constants"; 3 | import { isCancel, outro, select, text } from "@clack/prompts"; 4 | 5 | export const promptForTemplate = async (templates: Template[]) => { 6 | const result: string | symbol = await select({ 7 | message: "Select a template", 8 | options: templates.map((t) => ({ 9 | label: t.displayName, 10 | value: t.name, 11 | hint: t.description, 12 | })), 13 | }); 14 | if (isCancel(result)) { 15 | outro("Cancelled. No changes were made."); 16 | process.exit(0); 17 | } 18 | const template = templates.find((t) => t.name == result); 19 | return template!; 20 | }; 21 | export const promptForLanguage = async (template: Template) => { 22 | const result: string | symbol = await select({ 23 | message: "Select a language", 24 | options: template.languages.map((l) => ({ 25 | label: l.displayName, 26 | value: l.name, 27 | hint: l.description, 28 | })), 29 | }); 30 | if (isCancel(result)) { 31 | outro("Cancelled. No changes were made."); 32 | process.exit(0); 33 | } 34 | return result; 35 | }; 36 | export const promptForDirectory = async () => { 37 | const target = await text({ 38 | message: "Enter the target directory you want to initialize in", 39 | placeholder: `./worker`, 40 | defaultValue: `./worker`, 41 | }); 42 | if (isCancel(target)) { 43 | outro("Cancelled. No changes were made."); 44 | process.exit(0); 45 | } 46 | return normalize(target); 47 | }; 48 | -------------------------------------------------------------------------------- /src/init/utils.ts: -------------------------------------------------------------------------------- 1 | import { statSync, readdirSync, existsSync, rmSync } from "fs"; 2 | import { outro, log, isCancel, confirm } from "@clack/prompts"; 3 | 4 | export const validateFolder = async (target: string, force: boolean) => { 5 | if (existsSync(target)) { 6 | // Check whether target exists and is a file or symlink. 7 | const statResult = statSync(target); 8 | if (statResult.isFile() || statResult.isSymbolicLink()) { 9 | if (force) { 10 | log.warn( 11 | "Target is a file or symlink, but force flag is set, removing file and continuing.", 12 | ); 13 | } else { 14 | log.warn("Target is a file or symlink."); 15 | const overwrite = await confirm({ 16 | message: "Remove and continue?", 17 | initialValue: false, 18 | }); 19 | if (isCancel(overwrite) || !overwrite) { 20 | outro("Cancelled. No changes were made."); 21 | process.exit(0); 22 | } 23 | } 24 | rmSync(target); 25 | } 26 | 27 | // Check whether target already has files 28 | if (statResult.isDirectory() && readdirSync(target).length > 0) { 29 | if (force) { 30 | log.warn( 31 | "Target directory is not empty but force flag is set, continuing.", 32 | ); 33 | } else { 34 | log.warn("Target directory is not empty."); 35 | const overwrite = await confirm({ 36 | message: "Continue anyway?", 37 | initialValue: false, 38 | }); 39 | if (isCancel(overwrite) || !overwrite) { 40 | outro("Cancelled. No changes were made."); 41 | process.exit(0); 42 | } 43 | } 44 | } 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /templates/hello-world/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hello World", 3 | "description": "A first step in developing with Cloudflare Workers" 4 | } 5 | -------------------------------------------------------------------------------- /templates/hello-world/js/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.yml] 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /templates/hello-world/js/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 140, 3 | "singleQuote": true, 4 | "semi": true, 5 | "useTabs": true 6 | } 7 | -------------------------------------------------------------------------------- /templates/hello-world/js/README.md: -------------------------------------------------------------------------------- 1 | # Hello World! 2 | 3 | Welcome to Cloudflare Workers! This is your first worker. 4 | 5 | - Run `npm run dev` in your terminal to start a development server 6 | - Open a browser tab at http://localhost:8787/ to see your worker in action 7 | - Run `npm run deploy` to publish your worker 8 | 9 | Learn more at https://developers.cloudflare.com/workers/ 10 | -------------------------------------------------------------------------------- /templates/hello-world/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "description": "A first step in developing with Cloudflare Workers", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "start": "wrangler dev", 8 | "deploy": "wrangler deploy" 9 | }, 10 | "devDependencies": { 11 | "wrangler": "^3.15.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /templates/hello-world/js/src/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | async fetch(req, env, ctx) { 3 | return new Response('Hello World!'); 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /templates/hello-world/js/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "hello-world" 2 | main = "src/index.js" 3 | compatibility_date = "2023-11-12" 4 | -------------------------------------------------------------------------------- /templates/hello-world/rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "worker-rust" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # https://github.com/rustwasm/wasm-pack/issues/1247 7 | [package.metadata.wasm-pack.profile.release] 8 | wasm-opt = false 9 | 10 | [lib] 11 | crate-type = ["cdylib"] 12 | 13 | [dependencies] 14 | worker = "0.0.15" 15 | 16 | [profile.release] 17 | lto = true 18 | strip = true 19 | codegen-units = 1 20 | -------------------------------------------------------------------------------- /templates/hello-world/rs/README.md: -------------------------------------------------------------------------------- 1 | # Hello World! 2 | 3 | Welcome to Cloudflare Workers! This is your first worker(in Rust)! 4 | 5 | - Use [`rustup`](https://rustup.rs/) to install the WASM target with `rustup target add wasm32-unknown-unknown` 6 | - Run `npm run dev` in your terminal to start a development server 7 | - Open a browser tab at http://localhost:8787/ to see your worker in action 8 | - Run `npm run deploy` to publish your worker 9 | 10 | Learn more at https://developers.cloudflare.com/workers/runtime-apis/webassembly/rust/ 11 | -------------------------------------------------------------------------------- /templates/hello-world/rs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "description": "A first step in developing with Cloudflare Workers", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "start": "wrangler dev", 8 | "deploy": "wrangler deploy" 9 | }, 10 | "devDependencies": { 11 | "wrangler": "^3.15.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /templates/hello-world/rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | use worker::*; 2 | 3 | #[event(fetch)] 4 | async fn main(req: Request, env: Env, ctx: Context) -> Result { 5 | Response::ok("Hello, World!") 6 | } 7 | -------------------------------------------------------------------------------- /templates/hello-world/rs/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "worker-rust" 2 | main = "build/worker/shim.mjs" 3 | compatibility_date = "2023-03-22" 4 | 5 | [build] 6 | command = "cargo install -q worker-build && worker-build --release" 7 | -------------------------------------------------------------------------------- /templates/hello-world/ts/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.yml] 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /templates/hello-world/ts/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 140, 3 | "singleQuote": true, 4 | "semi": true, 5 | "useTabs": true 6 | } 7 | -------------------------------------------------------------------------------- /templates/hello-world/ts/README.md: -------------------------------------------------------------------------------- 1 | # Hello World! 2 | 3 | Welcome to Cloudflare Workers! This is your first worker. 4 | 5 | - Run `npm run dev` in your terminal to start a development server 6 | - Open a browser tab at http://localhost:8787/ to see your worker in action 7 | - Run `npm run deploy` to publish your worker 8 | 9 | Learn more at https://developers.cloudflare.com/workers/ 10 | -------------------------------------------------------------------------------- /templates/hello-world/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "description": "A first step in developing with Cloudflare Workers", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "start": "wrangler dev", 8 | "deploy": "wrangler deploy" 9 | }, 10 | "devDependencies": { 11 | "@cloudflare/workers-types": "^4.20231025.0", 12 | "typescript": "^5.2.2", 13 | "wrangler": "^3.15.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /templates/hello-world/ts/src/index.ts: -------------------------------------------------------------------------------- 1 | export interface Env { 2 | // Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/ 3 | // MY_KV_NAMESPACE: KVNamespace; 4 | // 5 | // Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/ 6 | // MY_DURABLE_OBJECT: DurableObjectNamespace; 7 | // 8 | // Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/ 9 | // MY_BUCKET: R2Bucket; 10 | // 11 | // Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/ 12 | // MY_SERVICE: Fetcher; 13 | } 14 | 15 | export default { 16 | async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise { 17 | return new Response('Hello World!'); 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /templates/hello-world/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "lib": ["esnext"], 7 | "types": ["@cloudflare/workers-types"], 8 | "isolatedModules": true, 9 | "noEmit": true, 10 | "strict": true, 11 | "esModuleInterop": true 12 | }, 13 | "include": ["src"] 14 | } 15 | -------------------------------------------------------------------------------- /templates/hello-world/ts/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "hello-world" 2 | main = "src/index.ts" 3 | compatibility_date = "2023-11-12" 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "lib": ["esnext"], 7 | "isolatedModules": true, 8 | "types": ["node"], 9 | "noEmit": true, 10 | "strict": true, 11 | "esModuleInterop": true 12 | }, 13 | "include": ["src", "types", "build"] 14 | } 15 | --------------------------------------------------------------------------------