lumix-js 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 ADDED
@@ -0,0 +1,156 @@
1
+ The `lumix-js` package provides a modular, fine-grained reactive runtime. It is the core engine that powers state management and DOM updates, and it also contains the primary LumixJS CLI.
2
+
3
+ ## CLI Core Commands
4
+
5
+ The `lumix` binary is the entry point for managing LumixJS projects.
6
+
7
+ ### `lumix init`
8
+
9
+ Creates a new project from a template.
10
+
11
+ ```bash
12
+ lumix init <project-name>
13
+ ```
14
+
15
+ ### `lumix dev`
16
+
17
+ Starts the development server.
18
+
19
+ ```bash
20
+ lumix dev
21
+ ```
22
+
23
+ ### `lumix build`
24
+
25
+ Builds the project for production, generating compiled assets.
26
+
27
+ ```bash
28
+ lumix build
29
+ ```
30
+
31
+ ## Core Concepts
32
+
33
+ The runtime is built on the principle of fine-grained reactivity, using Signals and Effects to ensure that only the parts of the DOM that actually change are updated.
34
+
35
+ ### Signals
36
+
37
+ Signals are the primary primitive for reactive state. A signal holds a value and notifies its subscribers when that value changes.
38
+
39
+ ```typescript
40
+ import { signal } from "lumix-js";
41
+
42
+ const count = signal(0);
43
+
44
+ // Read value
45
+ console.log(count());
46
+
47
+ // Update value
48
+ count(count() + 1);
49
+
50
+ // Bulk update with .value
51
+ count.value = 10;
52
+ ```
53
+
54
+ ### Effects
55
+
56
+ Effects are functions that automatically re-run whenever the signals they depend on change.
57
+
58
+ ```typescript
59
+ import { signal, effect } from "lumix-js";
60
+
61
+ const name = signal("Lumin");
62
+
63
+ effect(() => {
64
+ console.log(`Hello, ${name()}!`);
65
+ });
66
+
67
+ name("World"); // Logs: "Hello, World!"
68
+ ```
69
+
70
+ ### Computed Signals
71
+
72
+ Computed signals derive their value from other signals. They are automatically updated whenever their dependencies change.
73
+
74
+ ```typescript
75
+ import { signal, computed } from "lumix-js";
76
+
77
+ const first = signal("John");
78
+ const last = signal("Doe");
79
+
80
+ const full = computed(() => `${first()} ${last()}`);
81
+
82
+ console.log(full()); // "John Doe"
83
+ ```
84
+
85
+ ## Lifecycle Hooks
86
+
87
+ LumixJS provides hooks for managing component lifecycles.
88
+
89
+ - `onMount(fn)`: Executes when the component is mounted to the DOM.
90
+ - `onDestroy(fn)`: Executes when the component is unmounted.
91
+
92
+ ## Global State Management (Store)
93
+
94
+ For complex state that needs to be shared across components or persisted, LumixJS provides a robust Store system.
95
+
96
+ ```typescript
97
+ import { store, persist } from "lumix-js";
98
+
99
+ // Create a reactive store
100
+ const auth = store({
101
+ user: null,
102
+ isLoggedIn: false,
103
+ });
104
+
105
+ // Update multiple values in a batch
106
+ auth.update({ user: "LuminUser", isLoggedIn: true });
107
+
108
+ // Reactively listen to all changes
109
+ auth.subscribe((state) => {
110
+ console.log("Auth changed:", state);
111
+ });
112
+
113
+ // Persist automatically to localStorage
114
+ persist(auth, { key: "lumin_auth" });
115
+ ```
116
+
117
+ ## Two-Way Binding
118
+
119
+ The `bind` helper simplifies the synchronization between UI elements and signals.
120
+
121
+ ```svelte
122
+ <script>
123
+ import { signal, bind } from "lumix-js";
124
+
125
+ const email = signal("");
126
+ </script>
127
+
128
+ <div class="field">
129
+ <label>Email</label>
130
+ <input type="email" bind:value={email} />
131
+ </div>
132
+ ```
133
+
134
+ The runtime includes specific helpers for different input types:
135
+
136
+ - `bindValue`: For text inputs, textareas, and selects.
137
+ - `bindChecked`: For checkboxes.
138
+ - `bindNumeric`: For number and range inputs.
139
+ - `bindGroup`: For radio button groups.
140
+ - `bindSelected`: For multiple selects.
141
+
142
+ ## Universal Rendering
143
+
144
+ The runtime includes primitives for both Server-Side Rendering (SSR) and Client-Side Hydration.
145
+
146
+ - `renderToString(component)`: Produces a static HTML string.
147
+ - `hydrate(root, component)`: Attaches event listeners and reactivity to existing HTML.
148
+
149
+ ## Advanced Control
150
+
151
+ - `batch(fn)`: Batches multiple signal updates to trigger effects only once.
152
+ - `untrack(fn)`: Executes a function without tracking dependencies.
153
+
154
+ ## License
155
+
156
+ MIT
package/bin/lumix.js ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import pc from "picocolors";
4
+ import { dev } from "../dist/cli/dev.js";
5
+ import { build } from "../dist/cli/build.js";
6
+ import { preview } from "../dist/cli/preview.js";
7
+ import { init } from "../dist/cli/init.js";
8
+
9
+ const VERSION = "0.1.0";
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name("lumix")
15
+ .description("CLI for the LumixJS framework")
16
+ .version(VERSION);
17
+
18
+ program.command("dev").description("Start the development server").action(dev);
19
+
20
+ program
21
+ .command("build")
22
+ .description("Build the project for production")
23
+ .action(build);
24
+
25
+ program
26
+ .command("preview")
27
+ .description("Preview the production build locally")
28
+ .action(preview);
29
+
30
+ program
31
+ .command("init")
32
+ .description("Scaffold a new LuminJS project")
33
+ .argument("[name]", "Project name")
34
+ .option("-t, --template <template>", "Project template (blank, blank-ts)")
35
+ .action((name, options) => init(name, options));
36
+
37
+ // Custom help output
38
+ program.configureHelp({
39
+ formatHelp(cmd, helper) {
40
+ const title = `\n ${pc.bold(pc.cyan("⚡ LumixJS"))} ${pc.dim(`v${VERSION}`)}\n`;
41
+ const desc = ` ${pc.dim(cmd.description())}\n`;
42
+
43
+ const cmds = cmd.commands.map((c) => {
44
+ const name = c.name().padEnd(12);
45
+ return ` ${pc.green(name)} ${pc.dim(c.description())}`;
46
+ });
47
+
48
+ const cmdSection = `\n ${pc.bold("Commands:")}\n${cmds.join("\n")}\n`;
49
+
50
+ const opts = cmd.options.map((o) => {
51
+ const flags = o.flags.padEnd(18);
52
+ return ` ${pc.yellow(flags)} ${pc.dim(o.description)}`;
53
+ });
54
+
55
+ const optSection = `\n ${pc.bold("Options:")}\n${opts.join("\n")}\n`;
56
+
57
+ const usage = `\n ${pc.bold("Usage:")} ${pc.dim("lumix")} ${pc.white("<command>")} ${pc.dim("[options]")}\n`;
58
+
59
+ return title + desc + usage + cmdSection + optSection + "\n";
60
+ },
61
+ });
62
+
63
+ program.parse();
package/dist/bind.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { Signal } from "./signals.js";
2
+ /** Two-way binding for text inputs, textareas, and selects */
3
+ export declare function bindValue(el: HTMLElement, sig: Signal<string>): void;
4
+ /** Two-way binding for checkboxes */
5
+ export declare function bindChecked(el: HTMLElement, sig: Signal<boolean>): void;
6
+ /** Two-way binding for number/range inputs */
7
+ export declare function bindNumeric(el: HTMLElement, sig: Signal<number>): void;
8
+ /** Two-way binding for radio button groups */
9
+ export declare function bindGroup(el: HTMLElement, sig: Signal<string>): void;
10
+ /** Two-way binding for `<select multiple>` */
11
+ export declare function bindSelected(el: HTMLElement, sig: Signal<string[]>): void;
12
+ /** Automatically detect input type and apply the right bind */
13
+ export declare function bind(el: HTMLElement, property: string, sig: Signal<any>): void;
package/dist/bind.js ADDED
@@ -0,0 +1,103 @@
1
+ import { effect } from "./signals.js";
2
+ // ─── Core bind helper ──────────────────────────────────────
3
+ function setupBind(el, sig, domProp, event, transform) {
4
+ // Signal → DOM
5
+ effect(() => {
6
+ const v = sig();
7
+ el[domProp] = v;
8
+ });
9
+ // DOM → Signal
10
+ el.addEventListener(event, () => {
11
+ const raw = el[domProp];
12
+ sig(transform ? transform(raw) : raw);
13
+ });
14
+ }
15
+ // ─── bindValue ─────────────────────────────────────────────
16
+ /** Two-way binding for text inputs, textareas, and selects */
17
+ export function bindValue(el, sig) {
18
+ const tag = el.tagName.toLowerCase();
19
+ const event = tag === "select" ? "change" : "input";
20
+ setupBind(el, sig, "value", event);
21
+ }
22
+ // ─── bindChecked ───────────────────────────────────────────
23
+ /** Two-way binding for checkboxes */
24
+ export function bindChecked(el, sig) {
25
+ setupBind(el, sig, "checked", "change");
26
+ }
27
+ // ─── bindNumeric ───────────────────────────────────────────
28
+ /** Two-way binding for number/range inputs */
29
+ export function bindNumeric(el, sig) {
30
+ // Signal → DOM
31
+ effect(() => {
32
+ el.value = String(sig());
33
+ });
34
+ // DOM → Signal (using valueAsNumber)
35
+ el.addEventListener("input", () => {
36
+ const n = el.valueAsNumber;
37
+ if (!Number.isNaN(n))
38
+ sig(n);
39
+ });
40
+ }
41
+ // ─── bindGroup ─────────────────────────────────────────────
42
+ /** Two-way binding for radio button groups */
43
+ export function bindGroup(el, sig) {
44
+ // Signal → DOM
45
+ effect(() => {
46
+ el.checked = el.value === sig();
47
+ });
48
+ // DOM → Signal
49
+ el.addEventListener("change", () => {
50
+ if (el.checked) {
51
+ sig(el.value);
52
+ }
53
+ });
54
+ }
55
+ // ─── bindSelected ──────────────────────────────────────────
56
+ /** Two-way binding for `<select multiple>` */
57
+ export function bindSelected(el, sig) {
58
+ const select = el;
59
+ // Signal → DOM
60
+ effect(() => {
61
+ const selected = sig();
62
+ for (const opt of Array.from(select.options)) {
63
+ opt.selected = selected.includes(opt.value);
64
+ }
65
+ });
66
+ // DOM → Signal
67
+ select.addEventListener("change", () => {
68
+ const values = [];
69
+ for (const opt of Array.from(select.selectedOptions)) {
70
+ values.push(opt.value);
71
+ }
72
+ sig(values);
73
+ });
74
+ }
75
+ // ─── Auto-detect bind ──────────────────────────────────────
76
+ /** Automatically detect input type and apply the right bind */
77
+ export function bind(el, property, sig) {
78
+ const tag = el.tagName.toLowerCase();
79
+ const type = el.type?.toLowerCase() || "";
80
+ switch (property) {
81
+ case "value":
82
+ if (tag === "input" && (type === "number" || type === "range")) {
83
+ bindNumeric(el, sig);
84
+ }
85
+ else {
86
+ bindValue(el, sig);
87
+ }
88
+ break;
89
+ case "checked":
90
+ bindChecked(el, sig);
91
+ break;
92
+ case "group":
93
+ bindGroup(el, sig);
94
+ break;
95
+ case "selected":
96
+ bindSelected(el, sig);
97
+ break;
98
+ default:
99
+ // Generic: treat as a property bind
100
+ setupBind(el, sig, property, "input");
101
+ break;
102
+ }
103
+ }
@@ -0,0 +1 @@
1
+ export declare function build(): Promise<void>;
@@ -0,0 +1,69 @@
1
+ import { build as viteBuild } from "vite";
2
+ import lumix from "../../../vite-plugin-lumix/dist/index.js";
3
+ import pc from "picocolors";
4
+ import { loadConfig } from "./loader.js";
5
+ import path from "path";
6
+ import fs from "fs-extra";
7
+ import { __dirname } from "./utils.js";
8
+ export async function build() {
9
+ const cwd = process.cwd();
10
+ const config = await loadConfig(cwd);
11
+ console.log("");
12
+ console.log(` ${pc.bold(pc.cyan("⚡ LumixJS"))} ${pc.dim("v0.1.0")}`);
13
+ console.log(pc.dim(" Building for production...\n"));
14
+ const indexPath = path.join(cwd, "index.html");
15
+ const hasIndex = fs.existsSync(indexPath);
16
+ let tempIndex = false;
17
+ try {
18
+ if (!hasIndex) {
19
+ tempIndex = true;
20
+ // Auto-detect entry: main.ts > main.js
21
+ const entry = fs.existsSync(path.join(cwd, "main.ts"))
22
+ ? "/main.ts"
23
+ : "/main.js";
24
+ const defaultHtml = `
25
+ <!DOCTYPE html>
26
+ <html lang="en">
27
+ <head>
28
+ <meta charset="UTF-8">
29
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
30
+ </head>
31
+ <body>
32
+ <div id="${config?.rootId || "app"}"></div>
33
+ <script type="module" src="${entry}"></script>
34
+ </body>
35
+ </html>
36
+ `.trim();
37
+ fs.writeFileSync(indexPath, defaultHtml);
38
+ }
39
+ await viteBuild({
40
+ root: cwd,
41
+ base: "/",
42
+ plugins: [lumix(config), ...(config.vite?.plugins || [])],
43
+ build: {
44
+ outDir: "dist",
45
+ emptyOutDir: true,
46
+ ...config.vite?.build,
47
+ },
48
+ resolve: {
49
+ alias: {
50
+ "lumix-js": path.resolve(__dirname, "../index.js"),
51
+ ...config.vite?.resolve?.alias,
52
+ },
53
+ },
54
+ ...config.vite,
55
+ });
56
+ console.log(pc.green(pc.bold("\n Build completed successfully!")));
57
+ console.log(pc.dim(` Output directory: ${pc.white("./dist")}\n`));
58
+ }
59
+ catch (e) {
60
+ console.error(pc.red(pc.bold("\n Build failed!")));
61
+ console.error(pc.red(` ${e.message}\n`));
62
+ process.exit(1);
63
+ }
64
+ finally {
65
+ if (tempIndex && fs.existsSync(indexPath)) {
66
+ fs.removeSync(indexPath);
67
+ }
68
+ }
69
+ }
@@ -0,0 +1 @@
1
+ export declare function dev(): Promise<void>;
@@ -0,0 +1,69 @@
1
+ import { createServer } from "vite";
2
+ import lumix from "../../../vite-plugin-lumix/dist/index.js";
3
+ import pc from "picocolors";
4
+ import { loadConfig, findConfigPath } from "./loader.js";
5
+ import path from "path";
6
+ import fs from "fs";
7
+ import { __dirname } from "./utils.js";
8
+ async function startServer(cwd) {
9
+ const config = await loadConfig(cwd);
10
+ const server = await createServer({
11
+ root: cwd,
12
+ base: "/",
13
+ mode: "development",
14
+ plugins: [lumix(config), ...(config.vite?.plugins || [])],
15
+ server: {
16
+ port: 3000,
17
+ ...config.vite?.server,
18
+ },
19
+ resolve: {
20
+ alias: {
21
+ "lumix-js": path.resolve(__dirname, "../index.js"),
22
+ ...config.vite?.resolve?.alias,
23
+ },
24
+ },
25
+ ...config.vite,
26
+ });
27
+ await server.listen();
28
+ return server;
29
+ }
30
+ export async function dev() {
31
+ const cwd = process.cwd();
32
+ console.log("");
33
+ console.log(` ${pc.bold(pc.cyan("⚡ LumixJS"))} ${pc.dim("v0.1.0")}`);
34
+ console.log(pc.dim(" Starting development server...\n"));
35
+ try {
36
+ let server = await startServer(cwd);
37
+ const port = server.config.server.port;
38
+ console.log(` ${pc.green("➜")} ${pc.bold("Local:")} ${pc.cyan(`http://localhost:${port}/`)}`);
39
+ console.log(` ${pc.green("➜")} ${pc.dim("Ready in")} ${pc.white(Math.round(performance.now()))}ms\n`);
40
+ // Watch config file for changes
41
+ const configPath = findConfigPath(cwd);
42
+ if (configPath) {
43
+ let debounce = null;
44
+ fs.watch(configPath, () => {
45
+ if (debounce)
46
+ return;
47
+ debounce = setTimeout(async () => {
48
+ debounce = null;
49
+ console.log(`\n ${pc.yellow("↻")} ${pc.dim("Config changed, restarting...")}`);
50
+ try {
51
+ await server.close();
52
+ server = await startServer(cwd);
53
+ const newPort = server.config.server.port;
54
+ console.log(` ${pc.green("➜")} ${pc.bold("Local:")} ${pc.cyan(`http://localhost:${newPort}/`)}`);
55
+ console.log(` ${pc.green("➜")} ${pc.dim("Restarted successfully")}\n`);
56
+ }
57
+ catch (e) {
58
+ console.error(pc.red(` ✗ Restart failed: ${e.message}\n`));
59
+ }
60
+ }, 300);
61
+ });
62
+ }
63
+ }
64
+ catch (e) {
65
+ console.error(pc.red(pc.bold("\n Failed to start server:")));
66
+ console.error(pc.red(` ${e.message}\n`));
67
+ process.exit(1);
68
+ }
69
+ }
@@ -0,0 +1,5 @@
1
+ type InitOptions = {
2
+ template?: string;
3
+ };
4
+ export declare function init(name?: string, options?: InitOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,199 @@
1
+ import prompts from "prompts";
2
+ import pc from "picocolors";
3
+ import fs from "fs-extra";
4
+ import path from "path";
5
+ import { __dirname } from "./utils.js";
6
+ import { spawnSync } from "child_process";
7
+ function hasCommand(cmd) {
8
+ const which = process.platform === "win32" ? "where" : "which";
9
+ const res = spawnSync(which, [cmd], { stdio: "ignore" });
10
+ return res.status === 0;
11
+ }
12
+ function detectPackageManager() {
13
+ if (hasCommand("bun"))
14
+ return "bun";
15
+ if (hasCommand("pnpm"))
16
+ return "pnpm";
17
+ return "npm";
18
+ }
19
+ function getInstalledPackageManagers() {
20
+ const out = [];
21
+ if (hasCommand("bun"))
22
+ out.push("bun");
23
+ if (hasCommand("pnpm"))
24
+ out.push("pnpm");
25
+ out.push("npm");
26
+ return Array.from(new Set(out));
27
+ }
28
+ function runDevCommand(pm) {
29
+ if (pm === "npm")
30
+ return "npm run dev";
31
+ return `${pm} run dev`;
32
+ }
33
+ function installCommand(pm) {
34
+ if (pm === "npm")
35
+ return "npm install";
36
+ return `${pm} install`;
37
+ }
38
+ function runInstall(pm, cwd) {
39
+ const cmd = pm;
40
+ const args = ["install"];
41
+ const res = spawnSync(cmd, args, { cwd, stdio: "inherit" });
42
+ if (res.error)
43
+ throw res.error;
44
+ if (res.signal === "SIGINT") {
45
+ const err = new Error("install interrupted");
46
+ err.code = "SIGINT";
47
+ throw err;
48
+ }
49
+ if (typeof res.status === "number" && res.status !== 0) {
50
+ throw new Error(`${cmd} install failed with exit code ${res.status}`);
51
+ }
52
+ }
53
+ function isEnoentSpawnError(e) {
54
+ return Boolean(e && typeof e === "object" && "code" in e && e.code === "ENOENT");
55
+ }
56
+ function isSigintError(e) {
57
+ return Boolean(e && typeof e === "object" && "code" in e && e.code === "SIGINT");
58
+ }
59
+ export async function init(name, options) {
60
+ console.log("");
61
+ console.log(` ${pc.bold(pc.cyan("⚡ LumixJS"))} ${pc.dim("v0.1.0")}`);
62
+ console.log(pc.dim(" Scaffolding a new project...\n"));
63
+ const templates = [
64
+ { title: "Blank", value: "blank" },
65
+ { title: "Blank (TypeScript)", value: "blank-ts" },
66
+ { title: "Sitemap (Coming soon...)", value: "sitemap", disabled: true },
67
+ ];
68
+ const allowedTemplates = new Set(templates
69
+ .filter((t) => !t.disabled)
70
+ .map((t) => t.value));
71
+ let template = options?.template;
72
+ if (template && !allowedTemplates.has(template)) {
73
+ console.log(pc.red(`\n Error: Unknown template "${template}". Valid templates are: ${[...allowedTemplates].join(", ")}.\n`));
74
+ process.exit(1);
75
+ }
76
+ const questions = [];
77
+ if (!name) {
78
+ questions.push({
79
+ type: "text",
80
+ name: "projectName",
81
+ message: "Project name:",
82
+ initial: "my-lumin-app",
83
+ });
84
+ }
85
+ if (!template) {
86
+ questions.push({
87
+ type: "select",
88
+ name: "template",
89
+ message: "Pick a template:",
90
+ choices: templates,
91
+ });
92
+ }
93
+ const detectedPm = detectPackageManager();
94
+ const installedPms = getInstalledPackageManagers();
95
+ questions.push({
96
+ type: "toggle",
97
+ name: "install",
98
+ message: "Install dependencies?",
99
+ initial: true,
100
+ active: "yes",
101
+ inactive: "no",
102
+ });
103
+ questions.push({
104
+ type: (prev) => (prev ? "select" : null),
105
+ name: "packageManager",
106
+ message: "Package manager:",
107
+ initial: Math.max(0, installedPms.indexOf(detectedPm)),
108
+ choices: installedPms.map((pm) => ({ title: pm, value: pm })),
109
+ });
110
+ const response = questions.length > 0
111
+ ? await prompts(questions, {
112
+ onCancel() {
113
+ console.log(pc.red("\n Aborted.\n"));
114
+ process.exit(0);
115
+ },
116
+ })
117
+ : {};
118
+ const projectName = name || response.projectName;
119
+ template = template || response.template;
120
+ const shouldInstall = response.install !== false;
121
+ const selected = (response.packageManager || detectedPm);
122
+ let packageManager = selected;
123
+ if (!projectName || !template) {
124
+ console.log(pc.red("\n Aborted.\n"));
125
+ process.exit(0);
126
+ }
127
+ const targetDir = path.resolve(process.cwd(), projectName);
128
+ if (fs.existsSync(targetDir)) {
129
+ console.log(pc.red(`\n Error: Directory "${projectName}" already exists.\n`));
130
+ process.exit(1);
131
+ }
132
+ const templateDir = path.resolve(__dirname, "../../templates", template);
133
+ console.log(pc.dim("\n Scaffolding project..."));
134
+ try {
135
+ await fs.ensureDir(targetDir);
136
+ await fs.copy(templateDir, targetDir);
137
+ // Update package.json name
138
+ const pkgPath = path.join(targetDir, "package.json");
139
+ if (fs.existsSync(pkgPath)) {
140
+ const pkg = await fs.readJson(pkgPath);
141
+ pkg.name = projectName;
142
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
143
+ }
144
+ console.log(pc.green(pc.bold("\n Success! Project created.")));
145
+ if (shouldInstall) {
146
+ console.log(pc.dim(`\n Installing dependencies (${packageManager})...`));
147
+ try {
148
+ runInstall(packageManager, targetDir);
149
+ console.log(pc.green(pc.bold("\n Dependencies installed.")));
150
+ }
151
+ catch (e) {
152
+ if (isSigintError(e)) {
153
+ console.log(pc.red("\n Aborted.\n"));
154
+ process.exit(0);
155
+ }
156
+ if (isEnoentSpawnError(e)) {
157
+ const fallback = detectedPm;
158
+ if (fallback !== packageManager && hasCommand(fallback)) {
159
+ console.log(pc.yellow(`\n ${packageManager} not found. Retrying with ${fallback}...`));
160
+ try {
161
+ packageManager = fallback;
162
+ runInstall(packageManager, targetDir);
163
+ console.log(pc.green(pc.bold("\n Dependencies installed.")));
164
+ }
165
+ catch (e2) {
166
+ if (isSigintError(e2)) {
167
+ console.log(pc.red("\n Aborted.\n"));
168
+ process.exit(0);
169
+ }
170
+ console.log(pc.red(`\n Failed to install dependencies: ${e2?.message || e2}`));
171
+ console.log(pc.dim(" Project created at:"));
172
+ console.log(` ${targetDir}\n`);
173
+ }
174
+ }
175
+ else {
176
+ console.log(pc.red(`\n Failed to install dependencies: ${e?.message || e}`));
177
+ console.log(pc.dim(" Project created at:"));
178
+ console.log(` ${targetDir}\n`);
179
+ }
180
+ }
181
+ else {
182
+ console.log(pc.red(`\n Failed to install dependencies: ${e?.message || e}`));
183
+ console.log(pc.dim(" Project created at:"));
184
+ console.log(` ${targetDir}\n`);
185
+ }
186
+ }
187
+ }
188
+ console.log(pc.dim("\n Next steps:"));
189
+ console.log(` cd ${projectName}`);
190
+ if (!shouldInstall) {
191
+ console.log(` ${installCommand(packageManager)}`);
192
+ }
193
+ console.log(` ${runDevCommand(packageManager)}\n`);
194
+ }
195
+ catch (e) {
196
+ console.error(pc.red(`\n Failed to initialize project: ${e.message}\n`));
197
+ process.exit(1);
198
+ }
199
+ }