bosia 0.2.2 → 0.3.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.
Files changed (87) hide show
  1. package/README.md +39 -39
  2. package/package.json +56 -53
  3. package/src/ambient.d.ts +31 -0
  4. package/src/cli/add.ts +120 -114
  5. package/src/cli/build.ts +10 -10
  6. package/src/cli/create.ts +142 -137
  7. package/src/cli/dev.ts +8 -8
  8. package/src/cli/feat.ts +291 -132
  9. package/src/cli/index.ts +51 -42
  10. package/src/cli/registry.ts +136 -115
  11. package/src/cli/start.ts +17 -17
  12. package/src/cli/test.ts +25 -0
  13. package/src/core/build.ts +72 -56
  14. package/src/core/client/App.svelte +177 -153
  15. package/src/core/client/appState.svelte.ts +57 -0
  16. package/src/core/client/enhance.ts +112 -0
  17. package/src/core/client/hydrate.ts +97 -65
  18. package/src/core/client/prefetch.ts +101 -94
  19. package/src/core/client/router.svelte.ts +64 -51
  20. package/src/core/cookies.ts +70 -66
  21. package/src/core/cors.ts +44 -35
  22. package/src/core/csrf.ts +38 -38
  23. package/src/core/dedup.ts +17 -17
  24. package/src/core/dev.ts +165 -168
  25. package/src/core/env.ts +155 -128
  26. package/src/core/envCodegen.ts +73 -73
  27. package/src/core/errors.ts +48 -49
  28. package/src/core/hooks.ts +50 -50
  29. package/src/core/html.ts +192 -139
  30. package/src/core/matcher.ts +130 -121
  31. package/src/core/paths.ts +8 -10
  32. package/src/core/plugin.ts +113 -107
  33. package/src/core/prerender.ts +191 -118
  34. package/src/core/renderer.ts +359 -265
  35. package/src/core/routeFile.ts +140 -127
  36. package/src/core/routeTypes.ts +144 -83
  37. package/src/core/scanner.ts +125 -95
  38. package/src/core/server.ts +543 -370
  39. package/src/core/types.ts +25 -20
  40. package/src/lib/client.ts +12 -0
  41. package/src/lib/index.ts +8 -8
  42. package/src/lib/utils.ts +44 -30
  43. package/templates/default/.prettierignore +5 -0
  44. package/templates/default/.prettierrc.json +9 -0
  45. package/templates/default/README.md +5 -5
  46. package/templates/default/package.json +22 -18
  47. package/templates/default/src/app.css +80 -80
  48. package/templates/default/src/app.d.ts +3 -3
  49. package/templates/default/src/routes/+error.svelte +7 -10
  50. package/templates/default/src/routes/+layout.svelte +2 -2
  51. package/templates/default/src/routes/+page.svelte +31 -29
  52. package/templates/default/src/routes/about/+page.svelte +3 -3
  53. package/templates/default/tsconfig.json +20 -20
  54. package/templates/demo/.prettierignore +5 -0
  55. package/templates/demo/.prettierrc.json +9 -0
  56. package/templates/demo/README.md +9 -9
  57. package/templates/demo/package.json +22 -17
  58. package/templates/demo/src/app.css +80 -80
  59. package/templates/demo/src/app.d.ts +3 -3
  60. package/templates/demo/src/hooks.server.ts +9 -9
  61. package/templates/demo/src/routes/(public)/+layout.svelte +45 -23
  62. package/templates/demo/src/routes/(public)/+page.svelte +96 -67
  63. package/templates/demo/src/routes/(public)/about/+page.svelte +13 -25
  64. package/templates/demo/src/routes/(public)/all/[...catchall]/+page.svelte +24 -28
  65. package/templates/demo/src/routes/(public)/blog/+page.svelte +55 -46
  66. package/templates/demo/src/routes/(public)/blog/[slug]/+page.server.ts +36 -38
  67. package/templates/demo/src/routes/(public)/blog/[slug]/+page.svelte +60 -42
  68. package/templates/demo/src/routes/+error.svelte +10 -7
  69. package/templates/demo/src/routes/+layout.server.ts +4 -4
  70. package/templates/demo/src/routes/+layout.svelte +2 -2
  71. package/templates/demo/src/routes/actions-test/+page.server.ts +16 -16
  72. package/templates/demo/src/routes/actions-test/+page.svelte +49 -49
  73. package/templates/demo/src/routes/api/hello/+server.ts +25 -25
  74. package/templates/demo/tsconfig.json +20 -20
  75. package/templates/todo/.prettierignore +5 -0
  76. package/templates/todo/.prettierrc.json +9 -0
  77. package/templates/todo/README.md +9 -9
  78. package/templates/todo/package.json +22 -17
  79. package/templates/todo/src/app.css +80 -80
  80. package/templates/todo/src/app.d.ts +7 -7
  81. package/templates/todo/src/hooks.server.ts +9 -9
  82. package/templates/todo/src/routes/+error.svelte +10 -7
  83. package/templates/todo/src/routes/+layout.server.ts +4 -4
  84. package/templates/todo/src/routes/+layout.svelte +2 -2
  85. package/templates/todo/src/routes/+page.svelte +44 -44
  86. package/templates/todo/template.json +1 -1
  87. package/templates/todo/tsconfig.json +20 -20
package/src/cli/feat.ts CHANGED
@@ -1,14 +1,14 @@
1
- import { join, dirname } from "path";
1
+ import { join, dirname, extname } from "path";
2
2
  import { mkdirSync, writeFileSync, readFileSync, existsSync } from "fs";
3
3
  import * as p from "@clack/prompts";
4
4
  import { addComponent, initAddRegistry } from "./add.ts";
5
5
  import {
6
- type InstallOptions,
7
- resolveLocalRegistryOrExit,
8
- readRegistryJSON,
9
- readRegistryFile,
10
- mergePkgJson,
11
- bunAdd,
6
+ type InstallOptions,
7
+ resolveLocalRegistryOrExit,
8
+ readRegistryJSON,
9
+ readRegistryFile,
10
+ mergePkgJson,
11
+ bunAdd,
12
12
  } from "./registry.ts";
13
13
 
14
14
  // ─── bosia feat <feature> [--local] ──────────────────────
@@ -16,17 +16,30 @@ import {
16
16
  // registry with --local) and copies route/lib files, installs npm deps.
17
17
  // Supports nested feature dependencies (e.g. todo → drizzle).
18
18
 
19
+ type FileStrategy =
20
+ | "write" // overwrite (prompt if interactive)
21
+ | "skip-if-exists" // bootstrap-once: never replace user copy
22
+ | "append-line" // idempotent line append (barrel re-exports)
23
+ | "append-block" // marker-delimited block, replaced by id on re-install
24
+ | "merge-json"; // deep-merge JSON, preserve existing keys
25
+
26
+ interface FileEntry {
27
+ src: string;
28
+ target: string;
29
+ strategy?: FileStrategy;
30
+ marker?: string; // unique id within target (default = feature name)
31
+ }
32
+
19
33
  interface FeatureMeta {
20
- name: string;
21
- description: string;
22
- features?: string[]; // other bosia features required
23
- components: string[]; // bosia components to install via `bosia add`
24
- files: string[]; // source filenames in the registry feature dir
25
- targets: string[]; // destination paths relative to project root
26
- npmDeps: Record<string, string>;
27
- npmDevDeps?: Record<string, string>;
28
- scripts?: Record<string, string>; // package.json scripts to add
29
- envVars?: Record<string, string>; // env vars to append to .env if missing
34
+ name: string;
35
+ description: string;
36
+ features?: string[]; // other bosia features required
37
+ components: string[]; // bosia components to install via `bosia add`
38
+ files: FileEntry[]; // file entries with per-file strategy
39
+ npmDeps: Record<string, string>;
40
+ npmDevDeps?: Record<string, string>;
41
+ scripts?: Record<string, string>; // package.json scripts to add
42
+ envVars?: Record<string, string>; // env vars to append to .env if missing
30
43
  }
31
44
 
32
45
  let registryRoot: string | null = null;
@@ -35,131 +48,277 @@ let registryRoot: string | null = null;
35
48
  const installedFeats = new Set<string>();
36
49
 
37
50
  export async function runFeat(name: string | undefined, flags: string[] = []) {
38
- if (!name) {
39
- console.error("❌ Please provide a feature name.\n Usage: bosia feat <feature> [--local]");
40
- process.exit(1);
41
- }
51
+ if (!name) {
52
+ console.error(
53
+ "❌ Please provide a feature name.\n Usage: bosia feat <feature> [--local]",
54
+ );
55
+ process.exit(1);
56
+ }
42
57
 
43
- if (flags.includes("--local")) {
44
- registryRoot = resolveLocalRegistryOrExit();
45
- console.log(`⬡ Using local registry: ${registryRoot}\n`);
46
- }
58
+ if (flags.includes("--local")) {
59
+ registryRoot = resolveLocalRegistryOrExit();
60
+ console.log(`⬡ Using local registry: ${registryRoot}\n`);
61
+ }
47
62
 
48
- // Initialize add.ts registry context so addComponent resolves paths correctly
49
- await initAddRegistry(registryRoot);
63
+ // Initialize add.ts registry context so addComponent resolves paths correctly
64
+ await initAddRegistry(registryRoot);
50
65
 
51
- await installFeature(name, true);
66
+ await installFeature(name, true);
52
67
  }
53
68
 
54
69
  /** Set the registry root for feature resolution. Called by create.ts for template features. */
55
70
  export function initFeatRegistry(root: string | null) {
56
- registryRoot = root;
71
+ registryRoot = root;
57
72
  }
58
73
 
59
74
  export async function installFeature(name: string, isRoot: boolean, options?: InstallOptions) {
60
- if (installedFeats.has(name)) return;
61
- installedFeats.add(name);
62
-
63
- const cwd = options?.cwd ?? process.cwd();
64
-
65
- console.log(isRoot ? `⬡ Installing feature: ${name}\n` : `\n⬡ Installing dependency feature: ${name}\n`);
66
-
67
- const meta = await readRegistryJSON<FeatureMeta>(registryRoot, "features", name, "meta.json");
68
-
69
- // Install required feature dependencies first (recursive)
70
- if (meta.features && meta.features.length > 0) {
71
- for (const feat of meta.features) {
72
- await installFeature(feat, false, options);
73
- }
74
- }
75
-
76
- // Install required UI components
77
- if (meta.components.length > 0) {
78
- console.log("📦 Installing required components...");
79
- for (const comp of meta.components) {
80
- await addComponent(comp, false, options);
81
- }
82
- console.log("");
83
- }
84
-
85
- // Copy feature files to their target paths
86
- const createdDirs = new Set<string>();
87
- for (let i = 0; i < meta.files.length; i++) {
88
- const file = meta.files[i]!;
89
- const target = meta.targets[i] ?? file;
90
- const dest = join(cwd, target);
91
-
92
- // Prompt before overwriting existing files (skip check entirely in non-interactive mode)
93
- if (!options?.skipPrompts && existsSync(dest)) {
94
- const replace = await p.confirm({
95
- message: `File "${target}" already exists. Replace it?`,
96
- });
97
- if (p.isCancel(replace) || !replace) {
98
- console.log(` ⏭️ Skipped ${target}`);
99
- continue;
100
- }
101
- }
102
-
103
- const content = await readRegistryFile(registryRoot, "features", name, file);
104
- const dir = dirname(dest);
105
- if (!createdDirs.has(dir)) {
106
- mkdirSync(dir, { recursive: true });
107
- createdDirs.add(dir);
108
- }
109
- writeFileSync(dest, content, "utf-8");
110
- console.log(` ✍️ ${target}`);
111
- }
112
-
113
- // Install npm dependencies
114
- const hasDeps = Object.keys(meta.npmDeps).length > 0;
115
- const hasDevDeps = Object.keys(meta.npmDevDeps ?? {}).length > 0;
116
- const hasScripts = Object.keys(meta.scripts ?? {}).length > 0;
117
-
118
- if (hasDeps || hasDevDeps) {
119
- if (options?.skipInstall) {
120
- const { addedDeps, addedScripts } = mergePkgJson(cwd, {
121
- deps: meta.npmDeps,
122
- devDeps: meta.npmDevDeps,
123
- scripts: meta.scripts,
124
- });
125
- if (addedDeps.length > 0) console.log(`\n📥 Added to package.json: ${addedDeps.join(", ")}`);
126
- if (addedScripts.length > 0) console.log(`\n📜 Added scripts: ${addedScripts.join(", ")}`);
127
- } else {
128
- await bunAdd(cwd, meta.npmDeps, meta.npmDevDeps);
129
- if (hasScripts) {
130
- const { addedScripts } = mergePkgJson(cwd, { scripts: meta.scripts });
131
- if (addedScripts.length > 0) console.log(`\n📜 Added scripts: ${addedScripts.join(", ")}`);
132
- }
133
- }
134
- } else if (hasScripts) {
135
- const { addedScripts } = mergePkgJson(cwd, { scripts: meta.scripts });
136
- if (addedScripts.length > 0) console.log(`\n📜 Added scripts: ${addedScripts.join(", ")}`);
137
- }
138
-
139
- // Append env vars to .env if missing
140
- const envEntries = Object.entries(meta.envVars ?? {});
141
- if (envEntries.length > 0) {
142
- const envPath = join(cwd, ".env");
143
- const existing = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
144
- const toAdd: string[] = [];
145
- for (const [key, val] of envEntries) {
146
- if (!existing.includes(`${key}=`)) {
147
- toAdd.push(`${key}=${val}`);
148
- }
149
- }
150
- if (toAdd.length > 0) {
151
- const nl = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
152
- writeFileSync(envPath, existing + nl + toAdd.join("\n") + "\n", "utf-8");
153
- console.log(`\n🔑 Added to .env: ${toAdd.map((l) => l.split("=")[0]).join(", ")}`);
154
- }
155
- }
156
-
157
- if (isRoot) {
158
- console.log(`\n✅ Feature "${name}" scaffolded!`);
159
- if (meta.description) console.log(` ${meta.description}`);
160
- } else {
161
- console.log(` ✅ Dependency feature "${name}" installed.`);
162
- }
75
+ if (installedFeats.has(name)) return;
76
+ installedFeats.add(name);
77
+
78
+ const cwd = options?.cwd ?? process.cwd();
79
+
80
+ console.log(
81
+ isRoot ? `⬡ Installing feature: ${name}\n` : `\n⬡ Installing dependency feature: ${name}\n`,
82
+ );
83
+
84
+ const meta = await readRegistryJSON<FeatureMeta>(registryRoot, "features", name, "meta.json");
85
+
86
+ // Install required feature dependencies first (recursive)
87
+ if (meta.features && meta.features.length > 0) {
88
+ for (const feat of meta.features) {
89
+ await installFeature(feat, false, options);
90
+ }
91
+ }
92
+
93
+ // Install required UI components
94
+ if (meta.components.length > 0) {
95
+ console.log("📦 Installing required components...");
96
+ for (const comp of meta.components) {
97
+ await addComponent(comp, false, options);
98
+ }
99
+ console.log("");
100
+ }
101
+
102
+ // Apply each file entry per its strategy
103
+ const createdDirs = new Set<string>();
104
+ for (const entry of meta.files) {
105
+ const dest = join(cwd, entry.target);
106
+ const strategy: FileStrategy = entry.strategy ?? "write";
107
+ const dir = dirname(dest);
108
+ if (!createdDirs.has(dir)) {
109
+ mkdirSync(dir, { recursive: true });
110
+ createdDirs.add(dir);
111
+ }
112
+ const content = await readRegistryFile(registryRoot, "features", name, entry.src);
113
+ await applyStrategy({
114
+ dest,
115
+ target: entry.target,
116
+ content,
117
+ strategy,
118
+ feat: name,
119
+ marker: entry.marker ?? name,
120
+ skipPrompts: options?.skipPrompts ?? false,
121
+ });
122
+ }
123
+
124
+ // Install npm dependencies
125
+ const hasDeps = Object.keys(meta.npmDeps).length > 0;
126
+ const hasDevDeps = Object.keys(meta.npmDevDeps ?? {}).length > 0;
127
+ const hasScripts = Object.keys(meta.scripts ?? {}).length > 0;
128
+
129
+ if (hasDeps || hasDevDeps) {
130
+ if (options?.skipInstall) {
131
+ const { addedDeps, addedScripts } = mergePkgJson(cwd, {
132
+ deps: meta.npmDeps,
133
+ devDeps: meta.npmDevDeps,
134
+ scripts: meta.scripts,
135
+ });
136
+ if (addedDeps.length > 0)
137
+ console.log(`\n📥 Added to package.json: ${addedDeps.join(", ")}`);
138
+ if (addedScripts.length > 0)
139
+ console.log(`\n📜 Added scripts: ${addedScripts.join(", ")}`);
140
+ } else {
141
+ await bunAdd(cwd, meta.npmDeps, meta.npmDevDeps);
142
+ if (hasScripts) {
143
+ const { addedScripts } = mergePkgJson(cwd, { scripts: meta.scripts });
144
+ if (addedScripts.length > 0)
145
+ console.log(`\n📜 Added scripts: ${addedScripts.join(", ")}`);
146
+ }
147
+ }
148
+ } else if (hasScripts) {
149
+ const { addedScripts } = mergePkgJson(cwd, { scripts: meta.scripts });
150
+ if (addedScripts.length > 0) console.log(`\n📜 Added scripts: ${addedScripts.join(", ")}`);
151
+ }
152
+
153
+ // Append env vars to .env if missing
154
+ const envEntries = Object.entries(meta.envVars ?? {});
155
+ if (envEntries.length > 0) {
156
+ const envPath = join(cwd, ".env");
157
+ const existing = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
158
+ const toAdd: string[] = [];
159
+ for (const [key, val] of envEntries) {
160
+ if (!existing.includes(`${key}=`)) {
161
+ toAdd.push(`${key}=${val}`);
162
+ }
163
+ }
164
+ if (toAdd.length > 0) {
165
+ const nl = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
166
+ writeFileSync(envPath, existing + nl + toAdd.join("\n") + "\n", "utf-8");
167
+ console.log(`\n🔑 Added to .env: ${toAdd.map((l) => l.split("=")[0]).join(", ")}`);
168
+ }
169
+ }
170
+
171
+ if (isRoot) {
172
+ console.log(`\n✅ Feature "${name}" scaffolded!`);
173
+ if (meta.description) console.log(` ${meta.description}`);
174
+ } else {
175
+ console.log(` ✅ Dependency feature "${name}" installed.`);
176
+ }
177
+ }
178
+
179
+ // ─── File strategies ──────────────────────────────────────
180
+
181
+ interface StrategyArgs {
182
+ dest: string;
183
+ target: string;
184
+ content: string;
185
+ strategy: FileStrategy;
186
+ feat: string;
187
+ marker: string;
188
+ skipPrompts: boolean;
189
+ }
190
+
191
+ async function applyStrategy(args: StrategyArgs): Promise<void> {
192
+ const { dest, target, content, strategy, feat, marker, skipPrompts } = args;
193
+
194
+ switch (strategy) {
195
+ case "write": {
196
+ if (existsSync(dest) && !skipPrompts) {
197
+ const replace = await p.confirm({
198
+ message: `File "${target}" already exists. Replace it?`,
199
+ });
200
+ if (p.isCancel(replace) || !replace) {
201
+ console.log(` ⏭️ Skipped ${target}`);
202
+ return;
203
+ }
204
+ }
205
+ writeFileSync(dest, content, "utf-8");
206
+ console.log(` ✍️ ${target}`);
207
+ return;
208
+ }
209
+
210
+ case "skip-if-exists": {
211
+ if (existsSync(dest)) {
212
+ console.log(` ⏭️ Kept existing ${target}`);
213
+ return;
214
+ }
215
+ writeFileSync(dest, content, "utf-8");
216
+ console.log(` ✍️ ${target}`);
217
+ return;
218
+ }
219
+
220
+ case "append-line": {
221
+ const existing = existsSync(dest) ? readFileSync(dest, "utf-8") : "";
222
+ const existingLines = new Set(
223
+ existing
224
+ .split("\n")
225
+ .map((l) => l.trim())
226
+ .filter(Boolean),
227
+ );
228
+ const newLines = content
229
+ .split("\n")
230
+ .map((l) => l.trim())
231
+ .filter(Boolean)
232
+ .filter((l) => !existingLines.has(l));
233
+
234
+ if (newLines.length === 0) {
235
+ console.log(` ⏭️ ${target} (no new lines)`);
236
+ return;
237
+ }
238
+
239
+ const nl = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
240
+ writeFileSync(dest, existing + nl + newLines.join("\n") + "\n", "utf-8");
241
+ console.log(
242
+ ` ➕ ${target} (+${newLines.length} line${newLines.length === 1 ? "" : "s"})`,
243
+ );
244
+ return;
245
+ }
246
+
247
+ case "append-block": {
248
+ const id = `bosia:${feat}:${marker}`;
249
+ const delim = blockDelim(extname(dest));
250
+ const startLine = delim.end
251
+ ? `${delim.start} >>> ${id} ${delim.end}`
252
+ : `${delim.start} >>> ${id}`;
253
+ const endLine = delim.end
254
+ ? `${delim.start} <<< ${id} ${delim.end}`
255
+ : `${delim.start} <<< ${id}`;
256
+ const block = `${startLine}\n${content.trimEnd()}\n${endLine}`;
257
+
258
+ const existing = existsSync(dest) ? readFileSync(dest, "utf-8") : "";
259
+
260
+ if (existing.includes(startLine) && existing.includes(endLine)) {
261
+ const re = new RegExp(`${escapeRegex(startLine)}[\\s\\S]*?${escapeRegex(endLine)}`);
262
+ writeFileSync(dest, existing.replace(re, block), "utf-8");
263
+ console.log(` ♻️ ${target} (replaced ${id})`);
264
+ } else {
265
+ const nl = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
266
+ writeFileSync(dest, existing + nl + block + "\n", "utf-8");
267
+ console.log(` ➕ ${target} (appended ${id})`);
268
+ }
269
+ return;
270
+ }
271
+
272
+ case "merge-json": {
273
+ const existing = existsSync(dest) ? JSON.parse(readFileSync(dest, "utf-8")) : {};
274
+ const incoming = JSON.parse(content);
275
+ const merged = mergeJsonPreserve(existing, incoming);
276
+ writeFileSync(dest, JSON.stringify(merged, null, 2) + "\n", "utf-8");
277
+ console.log(` 🔀 ${target} (merged json)`);
278
+ return;
279
+ }
280
+
281
+ default: {
282
+ const _exhaustive: never = strategy;
283
+ throw new Error(`Unknown file strategy: ${_exhaustive}`);
284
+ }
285
+ }
286
+ }
287
+
288
+ function blockDelim(ext: string): { start: string; end: string } {
289
+ if (ext === ".html" || ext === ".svelte") return { start: "<!--", end: "-->" };
290
+ if (ext === ".css") return { start: "/*", end: "*/" };
291
+ return { start: "//", end: "" };
292
+ }
293
+
294
+ function escapeRegex(s: string): string {
295
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
296
+ }
297
+
298
+ // Deep-merge `source` into `target`, preserving existing target values.
299
+ // Objects: recurse. Arrays: concat-dedupe by JSON identity. Primitives: keep target.
300
+ function mergeJsonPreserve(target: unknown, source: unknown): unknown {
301
+ if (Array.isArray(target) && Array.isArray(source)) {
302
+ const out = [...target];
303
+ for (const item of source) {
304
+ if (!out.some((x) => JSON.stringify(x) === JSON.stringify(item))) {
305
+ out.push(item);
306
+ }
307
+ }
308
+ return out;
309
+ }
310
+ if (isPlainObject(target) && isPlainObject(source)) {
311
+ const out: Record<string, unknown> = { ...target };
312
+ for (const [k, v] of Object.entries(source)) {
313
+ out[k] = k in target ? mergeJsonPreserve(target[k], v) : v;
314
+ }
315
+ return out;
316
+ }
317
+ return target !== undefined ? target : source;
318
+ }
319
+
320
+ function isPlainObject(v: unknown): v is Record<string, unknown> {
321
+ return typeof v === "object" && v !== null && !Array.isArray(v);
163
322
  }
164
323
 
165
324
  // Re-exports for create.ts
package/src/cli/index.ts CHANGED
@@ -10,43 +10,48 @@
10
10
  const [, , command, ...args] = process.argv;
11
11
 
12
12
  async function main() {
13
- switch (command) {
14
- case "create": {
15
- const { runCreate } = await import("./create.ts");
16
- await runCreate(args[0], args.slice(1));
17
- break;
18
- }
19
- case "dev": {
20
- const { runDev } = await import("./dev.ts");
21
- await runDev();
22
- break;
23
- }
24
- case "build": {
25
- const { runBuild } = await import("./build.ts");
26
- await runBuild();
27
- break;
28
- }
29
- case "start": {
30
- const { runStart } = await import("./start.ts");
31
- await runStart();
32
- break;
33
- }
34
- case "add": {
35
- const { runAdd } = await import("./add.ts");
36
- const addName = args.find((a) => !a.startsWith("--"));
37
- const addFlags = args.filter((a) => a.startsWith("--"));
38
- await runAdd(addName, addFlags);
39
- break;
40
- }
41
- case "feat": {
42
- const { runFeat } = await import("./feat.ts");
43
- const featName = args.find((a) => !a.startsWith("--"));
44
- const featFlags = args.filter((a) => a.startsWith("--"));
45
- await runFeat(featName, featFlags);
46
- break;
47
- }
48
- default: {
49
- console.log(`
13
+ switch (command) {
14
+ case "create": {
15
+ const { runCreate } = await import("./create.ts");
16
+ await runCreate(args[0], args.slice(1));
17
+ break;
18
+ }
19
+ case "dev": {
20
+ const { runDev } = await import("./dev.ts");
21
+ await runDev();
22
+ break;
23
+ }
24
+ case "build": {
25
+ const { runBuild } = await import("./build.ts");
26
+ await runBuild();
27
+ break;
28
+ }
29
+ case "start": {
30
+ const { runStart } = await import("./start.ts");
31
+ await runStart();
32
+ break;
33
+ }
34
+ case "test": {
35
+ const { runTest } = await import("./test.ts");
36
+ await runTest(args);
37
+ break;
38
+ }
39
+ case "add": {
40
+ const { runAdd } = await import("./add.ts");
41
+ const addName = args.find((a) => !a.startsWith("--"));
42
+ const addFlags = args.filter((a) => a.startsWith("--"));
43
+ await runAdd(addName, addFlags);
44
+ break;
45
+ }
46
+ case "feat": {
47
+ const { runFeat } = await import("./feat.ts");
48
+ const featName = args.find((a) => !a.startsWith("--"));
49
+ const featFlags = args.filter((a) => a.startsWith("--"));
50
+ await runFeat(featName, featFlags);
51
+ break;
52
+ }
53
+ default: {
54
+ console.log(`
50
55
  ⬡ Bosia
51
56
 
52
57
  Usage:
@@ -57,6 +62,7 @@ Commands:
57
62
  dev Start the development server
58
63
  build Build for production
59
64
  start Run the production server
65
+ test [args] Run tests with bun test (auto-loads .env.test, sets BOSIA_ENV=test)
60
66
  add <component> Add a UI component from the registry
61
67
  feat <feature> Add a feature scaffold from the registry [--local]
62
68
 
@@ -66,16 +72,19 @@ Examples:
66
72
  bosia dev
67
73
  bosia build
68
74
  bosia start
75
+ bosia test
76
+ bosia test --watch
77
+ bosia test --coverage
69
78
  bosia add button → src/lib/components/ui/button/
70
79
  bosia add shop/cart → src/lib/components/shop/cart/
71
80
  bosia feat login
72
81
  `);
73
- break;
74
- }
75
- }
82
+ break;
83
+ }
84
+ }
76
85
  }
77
86
 
78
87
  main().catch((err) => {
79
- console.error("❌", err.message);
80
- process.exit(1);
88
+ console.error("❌", err.message);
89
+ process.exit(1);
81
90
  });