Skip to content

Commit 5d20ae9

Browse files
committed
[scramjet] initialize create-proxy-app
1 parent edf7a38 commit 5d20ae9

File tree

9 files changed

+1394
-48
lines changed

9 files changed

+1394
-48
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@mercuryworkshop/create-proxy-app",
3+
"version": "0.0.1",
4+
"type": "module",
5+
"packageManager": "[email protected]",
6+
"dependencies": {
7+
"@clack/core": "^0.3.4",
8+
"@clack/prompts": "^0.7.0",
9+
"ajv": "^8.13.0",
10+
"chalk": "^5.3.0",
11+
"commander": "^12.0.0",
12+
"execa": "^9.1.0",
13+
"fs-extra": "^11.2.0",
14+
"giget": "^1.2.3",
15+
"sort-package-json": "^2.10.0"
16+
}
17+
}
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import * as prompt from "@clack/prompts";
2+
import chalk from "chalk";
3+
import { Command } from "commander";
4+
import { execa } from "execa";
5+
import { scaffold } from "./scaffold";
6+
7+
interface CliFlags {
8+
git: boolean;
9+
install: boolean;
10+
default: boolean;
11+
}
12+
13+
interface CliResults {
14+
dir: string;
15+
flags: CliFlags;
16+
}
17+
18+
const defaultOpts: CliResults = {
19+
dir: "proxy-app",
20+
flags: {
21+
git: false,
22+
install: false,
23+
default: false,
24+
},
25+
};
26+
27+
async function project() {
28+
const cliResults = defaultOpts;
29+
const program = new Command();
30+
program.name("Create Proxy");
31+
program.description(
32+
"A CLI to easily get started with creating a Scramjet or Ultraviolet Proxy"
33+
);
34+
program.argument(
35+
"[dir]",
36+
"The name of the program, and the directory to create"
37+
);
38+
program.option("--git", "Tell the CLI to create a Git repository", false);
39+
program.option("--install", "Tell the CLI to install dependencies", false);
40+
program.option(
41+
"-y, --default",
42+
"Skip any questions a bootstrap with default options"
43+
);
44+
program.parse(process.argv);
45+
const providedName = program.args[0];
46+
if (providedName) {
47+
cliResults.dir = providedName;
48+
}
49+
cliResults.flags = program.opts();
50+
if (cliResults.flags.default) {
51+
const defaultOptSpinner = prompt.spinner();
52+
defaultOptSpinner.start();
53+
defaultOptSpinner.message(
54+
chalk.yellow("Scaffolding using ALL default options")
55+
);
56+
await scaffold({
57+
projectName: providedName ?? "proxy-app",
58+
scaffoldType: "tsx/jsx",
59+
tsScaffold: true,
60+
});
61+
defaultOptSpinner.stop(chalk.green.bold("Scaffold complete!"));
62+
return prompt.note(
63+
`cd ${providedName ?? "proxy-app"} \nnpm run dev`,
64+
chalk.bold.magenta("Done creating. Now run:")
65+
);
66+
}
67+
if (process.env.TERM_PROGRAM?.toLowerCase().includes("mintty")) {
68+
console.log(
69+
chalk.yellow(
70+
"WARNING: It looks like you are using MinTTY which is not interactive. This is most likely because you are using Git Bash. \nIf you are using Git Bash, please use it from another terminal like Windows Terminal. "
71+
)
72+
);
73+
throw new Error("Terminal session is Non-Interactive");
74+
}
75+
const inital = await prompt.group(
76+
{
77+
...(!providedName && {
78+
path: () =>
79+
prompt.text({
80+
message: chalk.green(
81+
"Where would you like to create your project?"
82+
),
83+
placeholder: "project-name",
84+
}),
85+
}),
86+
type: () =>
87+
prompt.select({
88+
message: chalk.magenta(`How would you like to set up this proxy?`),
89+
initialValue: "dedicated",
90+
maxItems: 2,
91+
options: [
92+
{ value: "dedicated", label: "Dedicated Server" },
93+
{
94+
value: "static",
95+
label:
96+
"Static (can be deployed anywhere, but requires an external Wisp server)",
97+
},
98+
],
99+
}),
100+
},
101+
{
102+
onCancel: () => {
103+
prompt.cancel(chalk.bold.red("Operation canceled"));
104+
process.exit(0);
105+
},
106+
}
107+
);
108+
109+
const initGit = await prompt.group(
110+
{
111+
...(!cliResults.flags.git && {
112+
init: () =>
113+
prompt.confirm({
114+
message: chalk.green("Do you want a Git repository initalized?"),
115+
initialValue: false,
116+
}),
117+
}),
118+
},
119+
{
120+
onCancel: () => {
121+
prompt.cancel(chalk.bold.red("Operation canceled"));
122+
process.exit(0);
123+
},
124+
}
125+
);
126+
127+
const installDeps = await prompt.group(
128+
{
129+
...(!cliResults.flags.install && {
130+
install: () =>
131+
prompt.confirm({
132+
message: chalk.red("Do you want to install dependencies?"),
133+
initialValue: false,
134+
}),
135+
}),
136+
},
137+
{
138+
onCancel: () => {
139+
prompt.cancel(chalk.bold.red("Operation canceled"));
140+
process.exit(0);
141+
},
142+
}
143+
);
144+
145+
let packageManager = "npm";
146+
if (installDeps.install === true || cliResults.flags.install === true) {
147+
const pm = await prompt.group(
148+
{
149+
manager: () =>
150+
prompt.select({
151+
message: chalk.green("Select your package manager"),
152+
initialValue: "npm",
153+
maxItems: 3,
154+
options: [
155+
{ value: "npm", label: "npm" },
156+
{ value: "pnpm", label: "pnpm" },
157+
{ value: "yarn", label: "yarn" },
158+
{ value: "bun", label: "bun" },
159+
],
160+
}),
161+
},
162+
{
163+
onCancel: () => {
164+
prompt.cancel(chalk.bold.red("Operation canceled"));
165+
process.exit(0);
166+
},
167+
}
168+
);
169+
packageManager = pm.manager;
170+
}
171+
172+
const scaffoldSpinner = prompt.spinner();
173+
scaffoldSpinner.start();
174+
scaffoldSpinner.message(chalk.yellow("Scaffolding..."));
175+
await scaffold({
176+
projectName: inital.path ?? cliResults.dir,
177+
scaffoldType: inital.type,
178+
});
179+
scaffoldSpinner.stop(chalk.bold.green("Scaffold complete!"));
180+
if (initGit.init === true || cliResults.flags.git === true) {
181+
const gitSpinner = prompt.spinner();
182+
gitSpinner.start();
183+
gitSpinner.message(chalk.yellow("Initalizing a Git repo"));
184+
try {
185+
await execa("git", ["init"], { cwd: inital.path });
186+
await execa("git", ["add", "-A"], { cwd: inital.path });
187+
await execa(
188+
"git",
189+
[
190+
"commit",
191+
"-m",
192+
"Inital Commit from Create Proxy App",
193+
'--author="create-proxy-app[bot] <[email protected]>"',
194+
],
195+
{ cwd: inital.path }
196+
);
197+
} catch (err: any) {}
198+
gitSpinner.stop(chalk.bold.green("Git repo successfully intitalized!"));
199+
}
200+
if (installDeps.install === true || cliResults.flags.install === true) {
201+
const pmSpinner = prompt.spinner();
202+
pmSpinner.start();
203+
pmSpinner.message(chalk.yellow("Installing dependencies..."));
204+
try {
205+
await execa(packageManager, ["install"], { cwd: inital.path });
206+
} catch (err: any) {
207+
console.log(
208+
chalk.yellow.bold(
209+
`\n${packageManager} has failed to install dependencies. Defaulting to npm`
210+
)
211+
);
212+
packageManager = "npm";
213+
await execa("npm", ["install"], { cwd: inital.path });
214+
}
215+
pmSpinner.stop(chalk.bold.green("Dependencies installed!"));
216+
}
217+
switch (installDeps.install || cliResults.flags.install) {
218+
case true:
219+
prompt.note(
220+
`cd ${inital.path ?? providedName} \n${packageManager} run dev`,
221+
chalk.bold.magenta("Done creating. Now run:")
222+
);
223+
break;
224+
case false:
225+
prompt.note(
226+
`cd ${
227+
inital.path ?? providedName
228+
} \n${packageManager} install \n${packageManager} run dev`,
229+
chalk.bold.magenta("Done creating. Now run:")
230+
);
231+
break;
232+
}
233+
if (inital.type === "basic") {
234+
const spinner = prompt.spinner();
235+
spinner.start();
236+
spinner.message(chalk.yellow("Scaffolding project..."));
237+
await scaffold({
238+
projectName: inital.path ?? providedName,
239+
scaffoldType: inital.type,
240+
});
241+
spinner.stop(chalk.bold.green("Scaffold complete!"));
242+
prompt.note(
243+
`cd ${inital.path} \nAnd get to work!`,
244+
chalk.bold.magenta("Done. Now Do:")
245+
);
246+
}
247+
}
248+
249+
async function cli() {
250+
prompt.intro(
251+
chalk.magenta("Welcome to Create Proxy App CLI! Let's get started")
252+
);
253+
await project();
254+
}
255+
256+
export { cli };
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#! /usr/bin/env node
2+
import chalk from "chalk";
3+
import { cli } from "./cli";
4+
cli().catch((err) => {
5+
console.error(chalk.red("\n" + err));
6+
process.exit(0);
7+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import chalk from "chalk";
2+
import fs from "fs-extra";
3+
import { downloadTemplate } from "giget";
4+
import sortPackageJson from "sort-package-json";
5+
import path from "path";
6+
import { fileURLToPath } from "url";
7+
8+
interface options {
9+
projectName: string;
10+
scaffoldType: string;
11+
}
12+
13+
async function template(template: string, projectName: string) {
14+
try {
15+
await downloadTemplate(
16+
`github:MercuryWorkshop/scramjet/packages/create-proxy-app/templates/${template}`,
17+
{
18+
force: false,
19+
provider: "github",
20+
cwd: projectName,
21+
dir: ".",
22+
}
23+
);
24+
} catch (err: any) {
25+
//remove the dir if it's likely to be created by the CLI
26+
if (
27+
projectName !== "." &&
28+
projectName !== "./" &&
29+
projectName.startsWith("../")
30+
) {
31+
try {
32+
fs.rmdirSync(projectName);
33+
} catch (_) {}
34+
}
35+
if (err.message.includes("404")) {
36+
throw new Error(
37+
"It looks like we were not able to get the template. \n Please try again later"
38+
);
39+
} else {
40+
throw new Error(err.message);
41+
}
42+
}
43+
//doublecheck the folder to make sure it's not empty
44+
if (fs.readdirSync(projectName).length === 0) {
45+
throw new Error(
46+
"It looks like the folder is empty. \n Please try again later"
47+
);
48+
}
49+
}
50+
51+
async function scaffold(opts: options) {
52+
await template("default", opts.projectName);
53+
}
54+
55+
export { scaffold };
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { bootstrap } from "@mercuryworkshop/proxy-bootstrap";
2+
import express from "express";
3+
4+
const PORT = process.env.PORT || 8080;
5+
const app = express();
6+
7+
app.use(express.static("public"));
8+
9+
const { routeRequest, routeUpgrade } = await bootstrap();
10+
11+
const server = app.listen(PORT, () => {
12+
console.log(`Server is running on http://localhost:${PORT}`);
13+
});
14+
15+
server.on("request", routeRequest);
16+
server.on("upgrade", routeUpgrade);

0 commit comments

Comments
 (0)