bosia 0.2.3 ā 0.3.1
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 +39 -39
- package/package.json +56 -54
- package/src/ambient.d.ts +31 -0
- package/src/cli/add.ts +120 -114
- package/src/cli/build.ts +10 -10
- package/src/cli/create.ts +142 -137
- package/src/cli/dev.ts +7 -9
- package/src/cli/feat.ts +266 -258
- package/src/cli/index.ts +51 -42
- package/src/cli/registry.ts +136 -115
- package/src/cli/start.ts +17 -17
- package/src/cli/test.ts +25 -0
- package/src/core/build.ts +72 -56
- package/src/core/client/App.svelte +177 -156
- package/src/core/client/appState.svelte.ts +33 -31
- package/src/core/client/enhance.ts +83 -78
- package/src/core/client/hydrate.ts +95 -81
- package/src/core/client/prefetch.ts +101 -94
- package/src/core/client/router.svelte.ts +64 -51
- package/src/core/cookies.ts +70 -66
- package/src/core/cors.ts +44 -35
- package/src/core/csrf.ts +38 -38
- package/src/core/dedup.ts +17 -17
- package/src/core/dev.ts +196 -168
- package/src/core/env.ts +160 -148
- package/src/core/envCodegen.ts +73 -73
- package/src/core/errors.ts +48 -49
- package/src/core/hooks.ts +50 -50
- package/src/core/html.ts +184 -145
- package/src/core/matcher.ts +130 -121
- package/src/core/paths.ts +8 -10
- package/src/core/plugin.ts +113 -107
- package/src/core/prerender.ts +191 -122
- package/src/core/renderer.ts +359 -286
- package/src/core/routeFile.ts +140 -127
- package/src/core/routeTypes.ts +144 -83
- package/src/core/scanner.ts +125 -95
- package/src/core/server.ts +538 -424
- package/src/core/types.ts +25 -20
- package/src/lib/index.ts +8 -8
- package/src/lib/utils.ts +44 -30
- package/templates/default/.prettierignore +5 -0
- package/templates/default/.prettierrc.json +9 -0
- package/templates/default/README.md +5 -5
- package/templates/default/package.json +22 -18
- package/templates/default/src/app.css +80 -80
- package/templates/default/src/app.d.ts +3 -3
- package/templates/default/src/routes/+error.svelte +7 -10
- package/templates/default/src/routes/+layout.svelte +2 -2
- package/templates/default/src/routes/+page.svelte +30 -32
- package/templates/default/src/routes/about/+page.svelte +3 -3
- package/templates/default/tsconfig.json +20 -20
- package/templates/demo/.prettierignore +5 -0
- package/templates/demo/.prettierrc.json +9 -0
- package/templates/demo/README.md +9 -9
- package/templates/demo/package.json +22 -17
- package/templates/demo/src/app.css +80 -80
- package/templates/demo/src/app.d.ts +3 -3
- package/templates/demo/src/hooks.server.ts +9 -9
- package/templates/demo/src/routes/(public)/+layout.svelte +45 -23
- package/templates/demo/src/routes/(public)/+page.svelte +96 -67
- package/templates/demo/src/routes/(public)/about/+page.svelte +13 -25
- package/templates/demo/src/routes/(public)/all/[...catchall]/+page.svelte +24 -28
- package/templates/demo/src/routes/(public)/blog/+page.svelte +55 -46
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.server.ts +36 -38
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.svelte +60 -42
- package/templates/demo/src/routes/+error.svelte +10 -7
- package/templates/demo/src/routes/+layout.server.ts +4 -4
- package/templates/demo/src/routes/+layout.svelte +2 -2
- package/templates/demo/src/routes/actions-test/+page.server.ts +16 -16
- package/templates/demo/src/routes/actions-test/+page.svelte +49 -49
- package/templates/demo/src/routes/api/hello/+server.ts +25 -25
- package/templates/demo/tsconfig.json +20 -20
- package/templates/todo/.prettierignore +5 -0
- package/templates/todo/.prettierrc.json +9 -0
- package/templates/todo/README.md +9 -9
- package/templates/todo/package.json +22 -17
- package/templates/todo/src/app.css +80 -80
- package/templates/todo/src/app.d.ts +7 -7
- package/templates/todo/src/hooks.server.ts +9 -9
- package/templates/todo/src/routes/+error.svelte +10 -7
- package/templates/todo/src/routes/+layout.server.ts +4 -4
- package/templates/todo/src/routes/+layout.svelte +2 -2
- package/templates/todo/src/routes/+page.svelte +44 -44
- package/templates/todo/template.json +1 -1
- package/templates/todo/tsconfig.json +20 -20
package/README.md
CHANGED
|
@@ -34,32 +34,32 @@ bun run start
|
|
|
34
34
|
|
|
35
35
|
## Tech Stack
|
|
36
36
|
|
|
37
|
-
| Layer
|
|
38
|
-
|
|
39
|
-
| Runtime
|
|
40
|
-
| HTTP Server | [ElysiaJS](https://elysiajs.com)
|
|
41
|
-
| UI
|
|
42
|
-
| CSS
|
|
43
|
-
| Bundler
|
|
37
|
+
| Layer | Technology |
|
|
38
|
+
| ----------- | ------------------------------------------ |
|
|
39
|
+
| Runtime | [Bun](https://bun.sh) |
|
|
40
|
+
| HTTP Server | [ElysiaJS](https://elysiajs.com) |
|
|
41
|
+
| UI | [Svelte 5](https://svelte.dev) (Runes) |
|
|
42
|
+
| CSS | [Tailwind CSS v4](https://tailwindcss.com) |
|
|
43
|
+
| Bundler | Bun.build |
|
|
44
44
|
|
|
45
45
|
## Routing Conventions
|
|
46
46
|
|
|
47
47
|
Files in `src/routes/` map to URLs automatically.
|
|
48
48
|
|
|
49
|
-
| File
|
|
50
|
-
|
|
51
|
-
| `+page.svelte`
|
|
52
|
-
| `+layout.svelte`
|
|
53
|
-
| `+page.server.ts`
|
|
54
|
-
| `+layout.server.ts` | Server loader for a layout
|
|
55
|
-
| `+server.ts`
|
|
49
|
+
| File | Purpose |
|
|
50
|
+
| ------------------- | -------------------------------- |
|
|
51
|
+
| `+page.svelte` | Page component |
|
|
52
|
+
| `+layout.svelte` | Layout that wraps child pages |
|
|
53
|
+
| `+page.server.ts` | Server loader for a page |
|
|
54
|
+
| `+layout.server.ts` | Server loader for a layout |
|
|
55
|
+
| `+server.ts` | API endpoint (export HTTP verbs) |
|
|
56
56
|
|
|
57
57
|
### Dynamic Routes
|
|
58
58
|
|
|
59
|
-
| Pattern
|
|
60
|
-
|
|
61
|
-
| `[param]`
|
|
62
|
-
| `[...rest]` | `/a/b/c` ā `params.rest = "a/b/c"`
|
|
59
|
+
| Pattern | Matches |
|
|
60
|
+
| ----------- | ---------------------------------------- |
|
|
61
|
+
| `[param]` | `/blog/hello` ā `params.param = "hello"` |
|
|
62
|
+
| `[...rest]` | `/a/b/c` ā `params.rest = "a/b/c"` |
|
|
63
63
|
|
|
64
64
|
### Route Groups
|
|
65
65
|
|
|
@@ -81,10 +81,10 @@ src/routes/
|
|
|
81
81
|
import type { LoadEvent } from "bosia";
|
|
82
82
|
|
|
83
83
|
export async function load({ params, url, locals, fetch, parent }: LoadEvent) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
const parentData = await parent(); // data from layout loaders above
|
|
85
|
+
return {
|
|
86
|
+
post: await getPost(params.slug),
|
|
87
|
+
};
|
|
88
88
|
}
|
|
89
89
|
```
|
|
90
90
|
|
|
@@ -92,8 +92,8 @@ Data returned is passed as the `data` prop to `+page.svelte`:
|
|
|
92
92
|
|
|
93
93
|
```svelte
|
|
94
94
|
<script lang="ts">
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
let { data } = $props();
|
|
96
|
+
// data.post, data.params ...
|
|
97
97
|
</script>
|
|
98
98
|
```
|
|
99
99
|
|
|
@@ -106,12 +106,12 @@ Export named HTTP verb functions from `+server.ts`:
|
|
|
106
106
|
import type { RequestEvent } from "bosia";
|
|
107
107
|
|
|
108
108
|
export function GET({ params, url, locals }: RequestEvent) {
|
|
109
|
-
|
|
109
|
+
return Response.json({ items: [] });
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
export async function POST({ request }: RequestEvent) {
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
const body = await request.json();
|
|
114
|
+
return Response.json({ created: body }, { status: 201 });
|
|
115
115
|
}
|
|
116
116
|
```
|
|
117
117
|
|
|
@@ -124,14 +124,14 @@ import { sequence } from "bosia";
|
|
|
124
124
|
import type { Handle } from "bosia";
|
|
125
125
|
|
|
126
126
|
const authHandle: Handle = async ({ event, resolve }) => {
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
event.locals.user = await getUser(event.request);
|
|
128
|
+
return resolve(event);
|
|
129
129
|
};
|
|
130
130
|
|
|
131
131
|
const loggingHandle: Handle = async ({ event, resolve }) => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
const res = await resolve(event);
|
|
133
|
+
console.log(`${event.request.method} ${event.url.pathname} ${res.status}`);
|
|
134
|
+
return res;
|
|
135
135
|
};
|
|
136
136
|
|
|
137
137
|
export const handle = sequence(authHandle, loggingHandle);
|
|
@@ -146,13 +146,13 @@ import { cn, sequence } from "bosia";
|
|
|
146
146
|
import type { RequestEvent, LoadEvent, Handle } from "bosia";
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
| Export
|
|
150
|
-
|
|
151
|
-
| `cn(...classes)`
|
|
152
|
-
| `sequence(...handlers)` | Compose multiple `Handle` middleware functions
|
|
153
|
-
| `RequestEvent`
|
|
154
|
-
| `LoadEvent`
|
|
155
|
-
| `Handle`
|
|
149
|
+
| Export | Description |
|
|
150
|
+
| ----------------------- | ---------------------------------------------------------------------- |
|
|
151
|
+
| `cn(...classes)` | Tailwind class merge utility (built-in class merging + tailwind-merge) |
|
|
152
|
+
| `sequence(...handlers)` | Compose multiple `Handle` middleware functions |
|
|
153
|
+
| `RequestEvent` | Type for API route and hook handlers |
|
|
154
|
+
| `LoadEvent` | Type for `load()` in `+page.server.ts` / `+layout.server.ts` |
|
|
155
|
+
| `Handle` | Type for a middleware function in `hooks.server.ts` |
|
|
156
156
|
|
|
157
157
|
## Path Alias
|
|
158
158
|
|
package/package.json
CHANGED
|
@@ -1,56 +1,58 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
2
|
+
"name": "bosia",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A fast, batteries-included fullstack framework ā SSR Ā· Svelte 5 Runes Ā· Bun Ā· ElysiaJS. File-based routing inspired by SvelteKit. No Node.js, no Vite, no adapters.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"bun",
|
|
8
|
+
"svelte",
|
|
9
|
+
"ssr",
|
|
10
|
+
"elysia",
|
|
11
|
+
"fullstack",
|
|
12
|
+
"framework"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Jekibus",
|
|
17
|
+
"url": "https://github.com/jekibus"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/bosapi/bosia#readme",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/bosapi/bosia.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/bosapi/bosia/issues"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"src",
|
|
29
|
+
"templates",
|
|
30
|
+
"README.md",
|
|
31
|
+
"package.json"
|
|
32
|
+
],
|
|
33
|
+
"exports": {
|
|
34
|
+
".": "./src/lib/index.ts",
|
|
35
|
+
"./client": "./src/lib/client.ts"
|
|
36
|
+
},
|
|
37
|
+
"bin": {
|
|
38
|
+
"bosia": "src/cli/index.ts"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"check": "tsc --noEmit && prettier --check .",
|
|
42
|
+
"test": "bun test",
|
|
43
|
+
"test:watch": "bun test --watch"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/bun": "latest",
|
|
47
|
+
"typescript": "^5"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@clack/prompts": "^1.1.0",
|
|
51
|
+
"@tailwindcss/cli": "^4.2.1",
|
|
52
|
+
"bun-plugin-svelte": "^0.0.6",
|
|
53
|
+
"elysia": "^1.4.26",
|
|
54
|
+
"svelte": "^5.53.6",
|
|
55
|
+
"tailwind-merge": "^3.5.0",
|
|
56
|
+
"tailwindcss": "^4.2.1"
|
|
57
|
+
}
|
|
56
58
|
}
|
package/src/ambient.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Virtual modules resolved by the bundler plugin (src/core/plugin.ts).
|
|
2
|
+
// Backed by .bosia/routes.ts and .bosia/routes.client.ts at build time.
|
|
3
|
+
|
|
4
|
+
declare module "bosia:routes" {
|
|
5
|
+
type Loader = () => Promise<any>;
|
|
6
|
+
type TrailingSlash = "never" | "always" | "ignore";
|
|
7
|
+
|
|
8
|
+
export const clientRoutes: Array<{
|
|
9
|
+
pattern: string;
|
|
10
|
+
page: Loader;
|
|
11
|
+
layouts: Loader[];
|
|
12
|
+
hasServerData: boolean;
|
|
13
|
+
trailingSlash: TrailingSlash;
|
|
14
|
+
}>;
|
|
15
|
+
|
|
16
|
+
export const serverRoutes: Array<{
|
|
17
|
+
pattern: string;
|
|
18
|
+
pageModule: Loader;
|
|
19
|
+
layoutModules: Loader[];
|
|
20
|
+
pageServer: Loader | null;
|
|
21
|
+
layoutServers: { loader: Loader; depth: number }[];
|
|
22
|
+
trailingSlash: TrailingSlash;
|
|
23
|
+
}>;
|
|
24
|
+
|
|
25
|
+
export const apiRoutes: Array<{
|
|
26
|
+
pattern: string;
|
|
27
|
+
module: Loader;
|
|
28
|
+
}>;
|
|
29
|
+
|
|
30
|
+
export const errorPage: Loader | null;
|
|
31
|
+
}
|
package/src/cli/add.ts
CHANGED
|
@@ -2,13 +2,13 @@ import { join, dirname } from "path";
|
|
|
2
2
|
import { mkdirSync, writeFileSync, readFileSync, existsSync } from "fs";
|
|
3
3
|
import * as p from "@clack/prompts";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
type InstallOptions,
|
|
6
|
+
REGISTRY_URL,
|
|
7
|
+
resolveLocalRegistryOrExit,
|
|
8
|
+
readRegistryJSON,
|
|
9
|
+
readRegistryFile,
|
|
10
|
+
mergePkgJson,
|
|
11
|
+
bunAdd,
|
|
12
12
|
} from "./registry.ts";
|
|
13
13
|
|
|
14
14
|
// āāā bosia add <component> āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
@@ -20,16 +20,16 @@ import {
|
|
|
20
20
|
// bosia add shop/cart ā src/lib/components/shop/cart/
|
|
21
21
|
|
|
22
22
|
interface ComponentMeta {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
dependencies: string[]; // other bosia components required
|
|
26
|
+
files: string[];
|
|
27
|
+
npmDeps: Record<string, string>;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
interface RegistryIndex {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
components: string[];
|
|
32
|
+
features: string[];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Track already-installed components within a session to avoid re-running deps
|
|
@@ -41,26 +41,28 @@ let registryIndex: RegistryIndex | null = null;
|
|
|
41
41
|
|
|
42
42
|
/** Initialize registry context so addComponent can be called externally (e.g. from feat.ts) */
|
|
43
43
|
export async function initAddRegistry(root: string | null) {
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
registryRoot = root;
|
|
45
|
+
registryIndex = await loadIndex();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export async function runAdd(name: string | undefined, flags: string[] = []) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
if (!name) {
|
|
50
|
+
console.error(
|
|
51
|
+
"ā Please provide a component name.\n Usage: bosia add <component> [--local]",
|
|
52
|
+
);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (flags.includes("--local")) {
|
|
57
|
+
registryRoot = resolveLocalRegistryOrExit();
|
|
58
|
+
console.log(`⬔ Using local registry: ${registryRoot}\n`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Load index once to resolve component paths
|
|
62
|
+
registryIndex = await loadIndex();
|
|
63
|
+
|
|
64
|
+
ensureUtils();
|
|
65
|
+
await addComponent(name, true);
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
/**
|
|
@@ -70,89 +72,93 @@ export async function runAdd(name: string | undefined, flags: string[] = []) {
|
|
|
70
72
|
* - "shop/cart" ā "shop/cart" (explicit path used as-is)
|
|
71
73
|
*/
|
|
72
74
|
function resolveDestPath(name: string): string {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Fallback for backwards compatibility
|
|
86
|
-
return `ui/${name}`;
|
|
75
|
+
if (name.includes("/")) return name;
|
|
76
|
+
|
|
77
|
+
if (registryIndex) {
|
|
78
|
+
// Exact match (e.g. "todo" ā "todo")
|
|
79
|
+
if (registryIndex.components.includes(name)) return name;
|
|
80
|
+
// Suffix match (e.g. "button" ā "ui/button")
|
|
81
|
+
const match = registryIndex.components.find((c) => c.endsWith(`/${name}`));
|
|
82
|
+
if (match) return match;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Fallback for backwards compatibility
|
|
86
|
+
return `ui/${name}`;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
async function loadIndex(): Promise<RegistryIndex | null> {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
try {
|
|
91
|
+
if (registryRoot) {
|
|
92
|
+
const path = join(registryRoot, "index.json");
|
|
93
|
+
if (existsSync(path)) return JSON.parse(readFileSync(path, "utf-8"));
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const res = await fetch(`${REGISTRY_URL}/index.json`);
|
|
97
|
+
if (!res.ok) return null;
|
|
98
|
+
return (await res.json()) as RegistryIndex;
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
export async function addComponent(name: string, root = false, options?: InstallOptions) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
105
|
+
// Resolve the full path (e.g. "button" ā "ui/button", "shop/cart" stays "shop/cart")
|
|
106
|
+
const fullPath = resolveDestPath(name);
|
|
107
|
+
|
|
108
|
+
if (installed.has(fullPath)) return;
|
|
109
|
+
installed.add(fullPath);
|
|
110
|
+
|
|
111
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
112
|
+
|
|
113
|
+
console.log(root ? `⬔ Installing component: ${name}\n` : ` š¦ Dependency: ${name}`);
|
|
114
|
+
|
|
115
|
+
const meta = await readRegistryJSON<ComponentMeta>(
|
|
116
|
+
registryRoot,
|
|
117
|
+
"components",
|
|
118
|
+
fullPath,
|
|
119
|
+
"meta.json",
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Install component dependencies first (recursive)
|
|
123
|
+
for (const dep of meta.dependencies) {
|
|
124
|
+
await addComponent(dep, false, options);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check if component already exists (skip check entirely in non-interactive mode)
|
|
128
|
+
const destDir = join(cwd, "src", "lib", "components", fullPath);
|
|
129
|
+
if (!options?.skipPrompts && existsSync(destDir)) {
|
|
130
|
+
const replace = await p.confirm({
|
|
131
|
+
message: `Component "${name}" already exists at src/lib/components/${fullPath}/. Replace it?`,
|
|
132
|
+
});
|
|
133
|
+
if (p.isCancel(replace) || !replace) {
|
|
134
|
+
console.log(` āļø Skipped ${name}`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Download/copy component files into src/lib/components/<fullPath>/
|
|
140
|
+
mkdirSync(destDir, { recursive: true });
|
|
141
|
+
|
|
142
|
+
for (const file of meta.files) {
|
|
143
|
+
const content = await readRegistryFile(registryRoot, "components", fullPath, file);
|
|
144
|
+
const dest = join(destDir, file);
|
|
145
|
+
if (file.includes("/")) mkdirSync(dirname(dest), { recursive: true });
|
|
146
|
+
writeFileSync(dest, content, "utf-8");
|
|
147
|
+
console.log(` āļø src/lib/components/${fullPath}/${file}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Install npm dependencies
|
|
151
|
+
if (Object.keys(meta.npmDeps).length > 0) {
|
|
152
|
+
if (options?.skipInstall) {
|
|
153
|
+
const { addedDeps } = mergePkgJson(cwd, { deps: meta.npmDeps });
|
|
154
|
+
if (addedDeps.length > 0)
|
|
155
|
+
console.log(` š„ Added to package.json: ${addedDeps.join(", ")}`);
|
|
156
|
+
} else {
|
|
157
|
+
await bunAdd(cwd, meta.npmDeps);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (root) console.log(`\nā
${name} installed at src/lib/components/${fullPath}/`);
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
// āāā Ensure $lib/utils.ts exists āāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
@@ -188,10 +194,10 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
188
194
|
`;
|
|
189
195
|
|
|
190
196
|
function ensureUtils() {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
const utilsPath = join(process.cwd(), "src", "lib", "utils.ts");
|
|
198
|
+
if (!existsSync(utilsPath)) {
|
|
199
|
+
mkdirSync(dirname(utilsPath), { recursive: true });
|
|
200
|
+
writeFileSync(utilsPath, UTILS_CONTENT, "utf-8");
|
|
201
|
+
console.log(" āļø src/lib/utils.ts (cn utility)\n");
|
|
202
|
+
}
|
|
197
203
|
}
|
package/src/cli/build.ts
CHANGED
|
@@ -3,14 +3,14 @@ import { resolve } from "path";
|
|
|
3
3
|
import { loadEnv } from "../core/env.ts";
|
|
4
4
|
|
|
5
5
|
export async function runBuild() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
loadEnv("production");
|
|
7
|
+
const buildScript = resolve(import.meta.dir, "../core/build.ts");
|
|
8
|
+
const proc = spawn(["bun", "run", buildScript], {
|
|
9
|
+
stdout: "inherit",
|
|
10
|
+
stderr: "inherit",
|
|
11
|
+
cwd: process.cwd(),
|
|
12
|
+
env: { ...process.env, NODE_ENV: process.env.NODE_ENV ?? "production" },
|
|
13
|
+
});
|
|
14
|
+
const exitCode = await proc.exited;
|
|
15
|
+
if (exitCode !== 0) process.exit(exitCode ?? 1);
|
|
16
16
|
}
|