@twobrake/create-monorepo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/index.mjs +177 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<div>
|
|
3
|
+
<img src="https://raw.githubusercontent.com/TwoBrake/bun-monorepo/refs/heads/development/.github/images/logo.png" alt="Logo" width="50px" />
|
|
4
|
+
<h1>Bun Monorepo</h1>
|
|
5
|
+
</div>
|
|
6
|
+
<div>
|
|
7
|
+
<img src="https://img.shields.io/github/actions/workflow/status/twobrake/bun-monorepo/lint.yml?branch=main&label=CI&style=for-the-badge" alt="CI State" />
|
|
8
|
+
<img src="https://img.shields.io/github/license/twobrake/bun-monorepo?style=for-the-badge&label=License&color=white" alt="License" />
|
|
9
|
+
<img src="https://img.shields.io/github/last-commit/twobrake/bun-monorepo?style=for-the-badge&label=Committed&color=orange" alt="Committed" />
|
|
10
|
+
<img src="https://img.shields.io/github/issues/twobrake/bun-monorepo?style=for-the-badge&label=Issues&color=red" alt="Issues" />
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<div align="start">
|
|
14
|
+
<h2>Getting Started</h2>
|
|
15
|
+
<p>An opinionated starter template for TypeScript using the Bun ecosystem. The primary reason for this template being developed was to enhance the time it took for me to startup new projects without having to worry about restructuring the setup.</p>
|
|
16
|
+
<p>Obviously the structure isn't ideal for every project, but for general TypeScript setups that require a multi-package/app monorepo structure, this may be what you've been looking for.</p>
|
|
17
|
+
</div>
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile, readdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { exec } from "node:child_process";
|
|
4
|
+
import { confirm, intro, isCancel, log, outro, path, select, tasks, text } from "@clack/prompts";
|
|
5
|
+
import path$1 from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import { downloadTemplate } from "giget";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
//#region ../utility/src/errors.ts
|
|
10
|
+
var ApplicationError = class extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "ApplicationError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var InternalError = class extends ApplicationError {
|
|
17
|
+
code = "INTERNAL_ERROR";
|
|
18
|
+
constructor(note) {
|
|
19
|
+
super(`Something went wrong internally: ${note ?? "N/A"}`);
|
|
20
|
+
this.name = "InternalError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var ActionAbortedError = class extends ApplicationError {
|
|
24
|
+
code = "ACTION_ABORTED";
|
|
25
|
+
constructor(action) {
|
|
26
|
+
super(`Action aborted: ${action}`);
|
|
27
|
+
this.name = "ActionAbortedError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/lib.ts
|
|
32
|
+
const DEFAULT_PACKAGE = { version: "0.0.1" };
|
|
33
|
+
const PACKAGE_NAME = "TwoBrake/bun-monorepo";
|
|
34
|
+
const DEFAULT_INSTALL_COMMANDS = {
|
|
35
|
+
bun: "bun install",
|
|
36
|
+
npm: "npm install",
|
|
37
|
+
pnpm: "pnpm install"
|
|
38
|
+
};
|
|
39
|
+
const EXCLUDED_PACKAGE_SCRIPTS = new Set(["installer:build", "installer:publish"]);
|
|
40
|
+
const IGNORE_PATH_LIST = ["packages/installer", "README.md"];
|
|
41
|
+
const replaceOccurrences = async (data) => {
|
|
42
|
+
const { cwd, extensions, queries } = data;
|
|
43
|
+
const directory = await readdir(cwd, { withFileTypes: true });
|
|
44
|
+
await Promise.all(directory.map(async (file) => {
|
|
45
|
+
const filePath = path$1.join(cwd, file.name);
|
|
46
|
+
if (file.isDirectory()) {
|
|
47
|
+
await replaceOccurrences({
|
|
48
|
+
cwd: filePath,
|
|
49
|
+
extensions,
|
|
50
|
+
queries
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!extensions.includes(path$1.extname(file.name).slice(1))) return;
|
|
55
|
+
const raw = await readFile(filePath, "utf8");
|
|
56
|
+
let updated = raw;
|
|
57
|
+
for (const query of queries) updated = updated.replaceAll(query.query, query.replaceWith);
|
|
58
|
+
if (updated === raw) return;
|
|
59
|
+
await writeFile(filePath, updated, "utf8");
|
|
60
|
+
}));
|
|
61
|
+
};
|
|
62
|
+
const isValidPackage = (value) => typeof value === "object" && value !== null && "name" in value;
|
|
63
|
+
const isCancelled = (prompt) => isCancel(prompt);
|
|
64
|
+
const createPrompt = async (prompt) => {
|
|
65
|
+
const result = await prompt();
|
|
66
|
+
if (isCancelled(result)) throw new ActionAbortedError("PROMPT");
|
|
67
|
+
return result;
|
|
68
|
+
};
|
|
69
|
+
const execute = promisify(exec);
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region package.json
|
|
72
|
+
var version = "0.1.0";
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/index.ts
|
|
75
|
+
const main = async () => {
|
|
76
|
+
try {
|
|
77
|
+
intro(`bun-monorepo (${version})`);
|
|
78
|
+
const authorName = await createPrompt(async () => text({
|
|
79
|
+
message: "What is your name?",
|
|
80
|
+
placeholder: "Lucas Stranks",
|
|
81
|
+
validate: (name) => z.safeParse(z.string().min(3), name).success ? void 0 : "Must be at least 3 characters long."
|
|
82
|
+
}));
|
|
83
|
+
const authorEmail = await createPrompt(async () => text({
|
|
84
|
+
message: "What is your email address?",
|
|
85
|
+
placeholder: "name@domain.com",
|
|
86
|
+
validate: (email) => z.safeParse(z.email(), email).success ? void 0 : "Not a valid email address."
|
|
87
|
+
}));
|
|
88
|
+
const projectName = await createPrompt(async () => text({
|
|
89
|
+
message: "What would you like to call your project?",
|
|
90
|
+
placeholder: "cool-project",
|
|
91
|
+
validate: (name) => {
|
|
92
|
+
const result = z.safeParse(z.string().min(3, "Must be at least 3 characters long.").lowercase("Must be all lowercase.").refine((value) => !value.includes(" "), "Must not have any whitespace."), name);
|
|
93
|
+
return result.success ? void 0 : result.error.issues[0]?.message ?? "Unknown error.";
|
|
94
|
+
}
|
|
95
|
+
}));
|
|
96
|
+
const targetDirectory = await createPrompt(async () => path({
|
|
97
|
+
directory: true,
|
|
98
|
+
message: "Where do you want to create this project at?"
|
|
99
|
+
}));
|
|
100
|
+
const frameworkType = await createPrompt(async () => select({
|
|
101
|
+
message: "What framework would you like to use for the project?",
|
|
102
|
+
options: [
|
|
103
|
+
{
|
|
104
|
+
label: "Bun (Recommended)",
|
|
105
|
+
value: "bun"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
label: "NPM",
|
|
109
|
+
value: "npm"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
label: "PNPM",
|
|
113
|
+
value: "pnpm"
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}));
|
|
117
|
+
const shouldInstallDependencies = await createPrompt(async () => confirm({ message: "Once the template is ready, would you like me to install my dependencies?" }));
|
|
118
|
+
let clonedDirectory = void 0;
|
|
119
|
+
const installationTasks = [{
|
|
120
|
+
task: async () => {
|
|
121
|
+
const { dir } = await downloadTemplate(`gh:${PACKAGE_NAME}`, {
|
|
122
|
+
dir: targetDirectory,
|
|
123
|
+
ignore: IGNORE_PATH_LIST
|
|
124
|
+
});
|
|
125
|
+
clonedDirectory = dir;
|
|
126
|
+
},
|
|
127
|
+
title: "Pulling most recent version from GitHub."
|
|
128
|
+
}, {
|
|
129
|
+
task: async () => {
|
|
130
|
+
if (clonedDirectory === void 0) throw new InternalError("Failed to clone.");
|
|
131
|
+
const clonedPackageContents = await readFile(`${clonedDirectory}/package.json`, "utf8");
|
|
132
|
+
const clonedPackage = JSON.parse(clonedPackageContents);
|
|
133
|
+
if (!isValidPackage(clonedPackage)) throw new InternalError("Invalid package.");
|
|
134
|
+
Object.assign(clonedPackage, DEFAULT_PACKAGE);
|
|
135
|
+
clonedPackage.author = {
|
|
136
|
+
email: authorEmail,
|
|
137
|
+
name: authorName
|
|
138
|
+
};
|
|
139
|
+
clonedPackage.name = projectName;
|
|
140
|
+
if (clonedPackage.scripts) clonedPackage.scripts = Object.fromEntries(Object.entries(clonedPackage.scripts).filter(([script]) => !EXCLUDED_PACKAGE_SCRIPTS.has(script)));
|
|
141
|
+
await writeFile(`${clonedDirectory}/package.json`, JSON.stringify(clonedPackage, void 0, 2));
|
|
142
|
+
await replaceOccurrences({
|
|
143
|
+
cwd: clonedDirectory,
|
|
144
|
+
extensions: [
|
|
145
|
+
"ts",
|
|
146
|
+
"tsx",
|
|
147
|
+
"json"
|
|
148
|
+
],
|
|
149
|
+
queries: [{
|
|
150
|
+
query: `"@repo/`,
|
|
151
|
+
replaceWith: `"@${projectName}/`
|
|
152
|
+
}, {
|
|
153
|
+
query: "'@repo/",
|
|
154
|
+
replaceWith: `'@${projectName}/`
|
|
155
|
+
}]
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
title: "Applying configured options to template."
|
|
159
|
+
}];
|
|
160
|
+
if (shouldInstallDependencies) installationTasks.push({
|
|
161
|
+
task: async () => {
|
|
162
|
+
await execute(DEFAULT_INSTALL_COMMANDS[frameworkType], { cwd: clonedDirectory });
|
|
163
|
+
},
|
|
164
|
+
title: "Installing dependencies."
|
|
165
|
+
});
|
|
166
|
+
await tasks(installationTasks);
|
|
167
|
+
outro(`Your project was successfully created at: ${clonedDirectory}`);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (error instanceof ActionAbortedError) log.error("Installation was aborted.");
|
|
170
|
+
else if (error instanceof InternalError) log.error(error.message);
|
|
171
|
+
else log.error("Something went wrong, please try again.");
|
|
172
|
+
process.exitCode = 1;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
await main();
|
|
176
|
+
//#endregion
|
|
177
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@twobrake/create-monorepo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The installer for an opinionated monorepo starter for TypeScript built on the Bun ecosystem.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bun",
|
|
7
|
+
"installer",
|
|
8
|
+
"monorepo",
|
|
9
|
+
"oxfmt",
|
|
10
|
+
"oxlint",
|
|
11
|
+
"template",
|
|
12
|
+
"typescript"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Lucas Stranks"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"create-monorepo": "dist/index.mjs"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test-install": "bun run src/index.ts",
|
|
30
|
+
"build": "tsdown",
|
|
31
|
+
"build:watch": "tsdown --watch",
|
|
32
|
+
"release": "bun publish --access public",
|
|
33
|
+
"prepublishOnly": "bun run build"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@clack/prompts": "^1.8.1",
|
|
37
|
+
"giget": "^3.3.1",
|
|
38
|
+
"zod": "^4.6.5"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@repo/utility": "0.0.0",
|
|
42
|
+
"tsdown": "^0.23.0",
|
|
43
|
+
"type-fest": "^5.10.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20"
|
|
47
|
+
}
|
|
48
|
+
}
|