@nabilabs/create-builder 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nabi Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # @nabilabs/create-builder <a href="https://www.npmjs.com/package/@nabilabs/create-builder"><img src="https://img.shields.io/npm/v/@nabilabs/create-builder" alt="npm package"></a>
2
+
3
+ ## Scaffolding Your First Nabi Builder Project
4
+
5
+ > **Compatibility Note:**
6
+ > Generated projects require [Node.js](https://nodejs.org/) 22 or later
7
+
8
+ With npm:
9
+
10
+ ```bash
11
+ npm create @nabilabs/builder@latest
12
+ ```
13
+
14
+ With Yarn:
15
+
16
+ ```bash
17
+ yarn create @nabilabs/builder
18
+ ```
19
+
20
+ With pnpm:
21
+
22
+ ```bash
23
+ pnpm create @nabilabs/builder
24
+ ```
25
+
26
+ With Bun:
27
+
28
+ ```bash
29
+ bun create @nabilabs/builder
30
+ ```
31
+
32
+ Then follow the prompts.
33
+
34
+ You can specify the project name directly:
35
+
36
+ ```bash
37
+ # npm
38
+ npm create @nabilabs/builder@latest my-site
39
+
40
+ # Yarn
41
+ yarn create @nabilabs/builder my-site
42
+
43
+ # pnpm
44
+ pnpm create @nabilabs/builder my-site
45
+
46
+ # Bun
47
+ bun create @nabilabs/builder my-site
48
+ ```
49
+
50
+ The CLI always creates a two-page default project with `/` and `/about`, shared UI components, colocated styles, and local SVG assets.
51
+
52
+ Use `.` as the project name to scaffold in the current directory. It is allowed only when the directory is empty or contains only `package.json` and `node_modules`.
53
+
54
+ ## Builder Documentation
55
+
56
+ For Builder configuration, components, routing, and build modes, see the [Nabi Builder README](https://github.com/nabilabshq/builder#readme).
package/dist/cli.js ADDED
@@ -0,0 +1,363 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { spawn } from "node:child_process";
5
+ import { access, cp, lstat, readdir, readFile, rename, writeFile } from "node:fs/promises";
6
+ import { basename, dirname, resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ // src/errors.ts
10
+ class CreateBuilderError extends Error {
11
+ constructor(message, options) {
12
+ super(message, options);
13
+ this.name = "CreateBuilderError";
14
+ }
15
+ }
16
+
17
+ // src/index.ts
18
+ var templatesDirectory = resolve(dirname(fileURLToPath(import.meta.url)), "../templates");
19
+ var packageManagerVariable = /\{\{packageManager\}\}/g;
20
+ var templateVariable = /\{\{projectName\}\}/g;
21
+ var packageManagerFromUserAgent = (userAgent) => {
22
+ const invokingUserAgent = userAgent ?? process.env.npm_config_user_agent;
23
+ if (invokingUserAgent?.startsWith("bun/"))
24
+ return "bun";
25
+ if (invokingUserAgent?.startsWith("pnpm/"))
26
+ return "pnpm";
27
+ if (invokingUserAgent?.startsWith("yarn/"))
28
+ return "yarn";
29
+ if (userAgent === undefined && process.versions.bun)
30
+ return "bun";
31
+ return "npm";
32
+ };
33
+ var packageManagerSpecificationFromUserAgent = (packageManager, userAgent) => {
34
+ const invokingUserAgent = userAgent ?? process.env.npm_config_user_agent;
35
+ const version = invokingUserAgent?.match(new RegExp(`(?:^|\\s)${packageManager}/([^\\s]+)`))?.[1];
36
+ const runtimeVersion = packageManager === "bun" ? process.versions.bun : undefined;
37
+ const resolvedVersion = version ?? runtimeVersion;
38
+ return resolvedVersion ? `${packageManager}@${resolvedVersion}` : packageManager;
39
+ };
40
+ var isMissing = (error) => error instanceof Error && ("code" in error) && error.code === "ENOENT";
41
+ var validateProjectName = (projectName) => {
42
+ const name = projectName.trim();
43
+ if (!name)
44
+ throw new CreateBuilderError("Project name cannot be empty.");
45
+ if (!/^[a-z0-9][a-z0-9._-]*$/i.test(name)) {
46
+ throw new CreateBuilderError("Project name may only contain letters, numbers, dots, underscores, and hyphens.");
47
+ }
48
+ if (name === "." || name === "..")
49
+ throw new CreateBuilderError("Project name must name a new directory.");
50
+ return name;
51
+ };
52
+ var getTemplatePath = async (template) => {
53
+ if (!/^[a-z0-9][a-z0-9-]*$/i.test(template))
54
+ throw new CreateBuilderError(`Invalid template: ${template}`);
55
+ const path = resolve(templatesDirectory, template);
56
+ if (dirname(path) !== templatesDirectory)
57
+ throw new CreateBuilderError(`Invalid template: ${template}`);
58
+ try {
59
+ const stats = await lstat(path);
60
+ if (!stats.isDirectory())
61
+ throw new CreateBuilderError(`Template is not a directory: ${template}`);
62
+ } catch (error) {
63
+ if (isMissing(error))
64
+ throw new CreateBuilderError(`Unknown template: ${template}`);
65
+ throw error;
66
+ }
67
+ return path;
68
+ };
69
+ var getTemplateNames = async () => {
70
+ const entries = await readdir(templatesDirectory, { withFileTypes: true });
71
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((first, second) => first.localeCompare(second));
72
+ };
73
+ var validateTargetDirectory = async ({
74
+ cwd,
75
+ projectName
76
+ }) => {
77
+ const directory = resolve(cwd, projectName);
78
+ if (basename(directory) !== projectName)
79
+ throw new CreateBuilderError("Project name must not contain a path.");
80
+ try {
81
+ await access(directory);
82
+ } catch (error) {
83
+ if (isMissing(error))
84
+ return directory;
85
+ throw error;
86
+ }
87
+ throw new CreateBuilderError(`Directory already exists: ${directory}`);
88
+ };
89
+ var validateCurrentDirectory = async (directory) => {
90
+ const stats = await lstat(directory);
91
+ if (!stats.isDirectory())
92
+ throw new CreateBuilderError(`Current path is not a directory: ${directory}`);
93
+ const entries = await readdir(directory);
94
+ let hasPackageManifest = false;
95
+ for (const entry of entries) {
96
+ const path = resolve(directory, entry);
97
+ const entryStats = await lstat(path);
98
+ if (entry === "package.json" && entryStats.isFile()) {
99
+ hasPackageManifest = true;
100
+ continue;
101
+ }
102
+ if (entry === "node_modules" && entryStats.isDirectory())
103
+ continue;
104
+ throw new CreateBuilderError(`Current directory may only contain package.json and node_modules: ${directory}`);
105
+ }
106
+ return hasPackageManifest;
107
+ };
108
+ var resolveProjectTarget = async ({
109
+ cwd,
110
+ projectName
111
+ }) => {
112
+ if (projectName === "." || projectName === "./") {
113
+ const directory = resolve(cwd);
114
+ const hasPackageManifest = await validateCurrentDirectory(directory);
115
+ return {
116
+ directory,
117
+ hasPackageManifest,
118
+ isCurrentDirectory: true,
119
+ projectName: validateProjectName(basename(directory))
120
+ };
121
+ }
122
+ const name = validateProjectName(projectName);
123
+ return {
124
+ directory: await validateTargetDirectory({ cwd, projectName: name }),
125
+ hasPackageManifest: false,
126
+ isCurrentDirectory: false,
127
+ projectName: name
128
+ };
129
+ };
130
+ var copyTemplate = async ({
131
+ destination,
132
+ destinationExists = false,
133
+ skipPackageManifest = false,
134
+ source
135
+ }) => {
136
+ try {
137
+ if (destinationExists) {
138
+ const entries = (await readdir(source)).filter((entry) => !skipPackageManifest || entry !== "package.json");
139
+ await Promise.all(entries.map((entry) => cp(resolve(source, entry), resolve(destination, entry), {
140
+ errorOnExist: true,
141
+ force: false,
142
+ recursive: true
143
+ })));
144
+ } else {
145
+ await cp(source, destination, {
146
+ errorOnExist: true,
147
+ force: false,
148
+ recursive: true
149
+ });
150
+ }
151
+ await rename(resolve(destination, "gitignore"), resolve(destination, ".gitignore"));
152
+ } catch (error) {
153
+ if (error instanceof Error && "code" in error && error.code === "EEXIST") {
154
+ throw new CreateBuilderError(`Directory already exists: ${destination}`);
155
+ }
156
+ throw error;
157
+ }
158
+ };
159
+ var isManifest = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
160
+ var readPackageManifest = async (path) => {
161
+ try {
162
+ const manifest = JSON.parse(await readFile(path, "utf8"));
163
+ if (!isManifest(manifest))
164
+ throw new CreateBuilderError(`Invalid package.json: ${path}`);
165
+ return manifest;
166
+ } catch (error) {
167
+ if (error instanceof SyntaxError)
168
+ throw new CreateBuilderError(`Invalid package.json: ${path}`);
169
+ throw error;
170
+ }
171
+ };
172
+ var manifestSection = (manifest, section) => {
173
+ const value = manifest[section];
174
+ if (value === undefined)
175
+ return {};
176
+ if (!isManifest(value))
177
+ throw new CreateBuilderError(`Invalid package.json ${section}.`);
178
+ return value;
179
+ };
180
+ var mergePackageManifest = async ({
181
+ directory,
182
+ packageManagerSpecification,
183
+ projectName,
184
+ templatePath
185
+ }) => {
186
+ const path = resolve(directory, "package.json");
187
+ const [existing, templateManifest] = await Promise.all([
188
+ readPackageManifest(path),
189
+ readPackageManifest(resolve(templatePath, "package.json"))
190
+ ]);
191
+ const manifest = {
192
+ ...templateManifest,
193
+ ...existing,
194
+ devDependencies: {
195
+ ...manifestSection(existing, "devDependencies"),
196
+ ...manifestSection(templateManifest, "devDependencies")
197
+ },
198
+ name: projectName,
199
+ packageManager: packageManagerSpecification,
200
+ scripts: {
201
+ ...manifestSection(existing, "scripts"),
202
+ ...manifestSection(templateManifest, "scripts")
203
+ }
204
+ };
205
+ await writeFile(path, `${JSON.stringify(manifest, null, 2)}
206
+ `, "utf8");
207
+ };
208
+ var getFiles = async (directory) => {
209
+ const entries = await readdir(directory, { withFileTypes: true });
210
+ const paths = await Promise.all(entries.filter((entry) => entry.name !== "node_modules").map(async (entry) => {
211
+ const path = resolve(directory, entry.name);
212
+ if (entry.isDirectory())
213
+ return getFiles(path);
214
+ return [path];
215
+ }));
216
+ return paths.flat();
217
+ };
218
+ var replaceTemplateVariables = async ({
219
+ directory,
220
+ packageManagerSpecification,
221
+ projectName
222
+ }) => {
223
+ const files = await getFiles(directory);
224
+ await Promise.all(files.map(async (path) => {
225
+ const source = await readFile(path, "utf8");
226
+ const result = source.replace(templateVariable, projectName).replace(packageManagerVariable, packageManagerSpecification);
227
+ if (result !== source)
228
+ await writeFile(path, result, "utf8");
229
+ }));
230
+ };
231
+ var installDependencies = async (directory, packageManager) => {
232
+ await new Promise((resolve2, reject) => {
233
+ const child = spawn(packageManager, ["install"], {
234
+ cwd: directory,
235
+ shell: process.platform === "win32",
236
+ stdio: "inherit"
237
+ });
238
+ child.once("error", reject);
239
+ child.once("exit", (code) => {
240
+ if (code === 0)
241
+ return resolve2();
242
+ reject(new CreateBuilderError("Dependency installation failed."));
243
+ });
244
+ });
245
+ };
246
+ var createProject = async ({
247
+ cwd = process.cwd(),
248
+ install = installDependencies,
249
+ packageManager = "npm",
250
+ packageManagerSpecification = packageManager,
251
+ projectName,
252
+ template = "default"
253
+ }) => {
254
+ const target = await resolveProjectTarget({ cwd, projectName });
255
+ const source = await getTemplatePath(template);
256
+ if (target.hasPackageManifest)
257
+ await readPackageManifest(resolve(target.directory, "package.json"));
258
+ await copyTemplate({
259
+ destination: target.directory,
260
+ destinationExists: target.isCurrentDirectory,
261
+ skipPackageManifest: target.hasPackageManifest,
262
+ source
263
+ });
264
+ await replaceTemplateVariables({
265
+ directory: target.directory,
266
+ packageManagerSpecification,
267
+ projectName: target.projectName
268
+ });
269
+ if (target.hasPackageManifest) {
270
+ await mergePackageManifest({
271
+ directory: target.directory,
272
+ packageManagerSpecification,
273
+ projectName: target.projectName,
274
+ templatePath: source
275
+ });
276
+ }
277
+ await install(target.directory, packageManager);
278
+ return {
279
+ directory: target.directory,
280
+ projectName: target.projectName,
281
+ template
282
+ };
283
+ };
284
+
285
+ // src/cli.ts
286
+ import process2 from "node:process";
287
+ import { createInterface } from "node:readline/promises";
288
+ var usage = `Create a Nabi Builder project
289
+
290
+ Usage:
291
+ bun create @nabilabs/builder [project-name]`;
292
+ var promptValue = async (question) => {
293
+ const prompt = createInterface({
294
+ input: process2.stdin,
295
+ output: process2.stdout
296
+ });
297
+ const controller = new AbortController;
298
+ const onInterrupt = () => controller.abort();
299
+ process2.once("SIGINT", onInterrupt);
300
+ try {
301
+ return await prompt.question(question, { signal: controller.signal });
302
+ } catch (error) {
303
+ if (error instanceof Error && error.name === "AbortError")
304
+ return;
305
+ throw error;
306
+ } finally {
307
+ process2.removeListener("SIGINT", onInterrupt);
308
+ prompt.close();
309
+ }
310
+ };
311
+ var promptProjectName = async () => promptValue("Project name: ");
312
+ var printSuccess = ({
313
+ isCurrentDirectory,
314
+ packageManager,
315
+ projectName
316
+ }) => {
317
+ const runDev = packageManager === "yarn" ? "yarn dev" : `${packageManager} run dev`;
318
+ const nextSteps = isCurrentDirectory ? runDev : `cd ${projectName}
319
+ ${runDev}`;
320
+ console.log(`
321
+ ✔ Project created successfully.
322
+
323
+ Next steps:
324
+
325
+ ${nextSteps}`);
326
+ };
327
+ var run = async () => {
328
+ const arguments_ = process2.argv.slice(2);
329
+ if (arguments_.includes("--help") || arguments_.includes("-h"))
330
+ return console.log(usage);
331
+ let projectName;
332
+ for (let index = 0;index < arguments_.length; index += 1) {
333
+ const argument = arguments_[index];
334
+ if (argument.startsWith("-") || projectName !== undefined)
335
+ throw new CreateBuilderError(usage);
336
+ projectName = argument;
337
+ }
338
+ projectName ??= await promptProjectName();
339
+ if (projectName === undefined)
340
+ return;
341
+ const packageManager = packageManagerFromUserAgent();
342
+ const packageManagerSpecification = packageManagerSpecificationFromUserAgent(packageManager);
343
+ const project = await createProject({
344
+ packageManager,
345
+ packageManagerSpecification,
346
+ projectName
347
+ });
348
+ printSuccess({
349
+ isCurrentDirectory: projectName === "." || projectName === "./",
350
+ packageManager,
351
+ projectName: project.projectName
352
+ });
353
+ };
354
+ run().catch((error) => {
355
+ const message = error instanceof Error ? error.message : "Unexpected error while creating the project.";
356
+ console.error(`
357
+ Error: ${message}`);
358
+ process2.exitCode = 1;
359
+ });
360
+ export {
361
+ promptProjectName,
362
+ printSuccess
363
+ };