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.
Files changed (86) hide show
  1. package/README.md +39 -39
  2. package/package.json +56 -54
  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 +7 -9
  8. package/src/cli/feat.ts +266 -258
  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 -156
  15. package/src/core/client/appState.svelte.ts +33 -31
  16. package/src/core/client/enhance.ts +83 -78
  17. package/src/core/client/hydrate.ts +95 -81
  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 +196 -168
  25. package/src/core/env.ts +160 -148
  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 +184 -145
  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 -122
  34. package/src/core/renderer.ts +359 -286
  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 +538 -424
  39. package/src/core/types.ts +25 -20
  40. package/src/lib/index.ts +8 -8
  41. package/src/lib/utils.ts +44 -30
  42. package/templates/default/.prettierignore +5 -0
  43. package/templates/default/.prettierrc.json +9 -0
  44. package/templates/default/README.md +5 -5
  45. package/templates/default/package.json +22 -18
  46. package/templates/default/src/app.css +80 -80
  47. package/templates/default/src/app.d.ts +3 -3
  48. package/templates/default/src/routes/+error.svelte +7 -10
  49. package/templates/default/src/routes/+layout.svelte +2 -2
  50. package/templates/default/src/routes/+page.svelte +30 -32
  51. package/templates/default/src/routes/about/+page.svelte +3 -3
  52. package/templates/default/tsconfig.json +20 -20
  53. package/templates/demo/.prettierignore +5 -0
  54. package/templates/demo/.prettierrc.json +9 -0
  55. package/templates/demo/README.md +9 -9
  56. package/templates/demo/package.json +22 -17
  57. package/templates/demo/src/app.css +80 -80
  58. package/templates/demo/src/app.d.ts +3 -3
  59. package/templates/demo/src/hooks.server.ts +9 -9
  60. package/templates/demo/src/routes/(public)/+layout.svelte +45 -23
  61. package/templates/demo/src/routes/(public)/+page.svelte +96 -67
  62. package/templates/demo/src/routes/(public)/about/+page.svelte +13 -25
  63. package/templates/demo/src/routes/(public)/all/[...catchall]/+page.svelte +24 -28
  64. package/templates/demo/src/routes/(public)/blog/+page.svelte +55 -46
  65. package/templates/demo/src/routes/(public)/blog/[slug]/+page.server.ts +36 -38
  66. package/templates/demo/src/routes/(public)/blog/[slug]/+page.svelte +60 -42
  67. package/templates/demo/src/routes/+error.svelte +10 -7
  68. package/templates/demo/src/routes/+layout.server.ts +4 -4
  69. package/templates/demo/src/routes/+layout.svelte +2 -2
  70. package/templates/demo/src/routes/actions-test/+page.server.ts +16 -16
  71. package/templates/demo/src/routes/actions-test/+page.svelte +49 -49
  72. package/templates/demo/src/routes/api/hello/+server.ts +25 -25
  73. package/templates/demo/tsconfig.json +20 -20
  74. package/templates/todo/.prettierignore +5 -0
  75. package/templates/todo/.prettierrc.json +9 -0
  76. package/templates/todo/README.md +9 -9
  77. package/templates/todo/package.json +22 -17
  78. package/templates/todo/src/app.css +80 -80
  79. package/templates/todo/src/app.d.ts +7 -7
  80. package/templates/todo/src/hooks.server.ts +9 -9
  81. package/templates/todo/src/routes/+error.svelte +10 -7
  82. package/templates/todo/src/routes/+layout.server.ts +4 -4
  83. package/templates/todo/src/routes/+layout.svelte +2 -2
  84. package/templates/todo/src/routes/+page.svelte +44 -44
  85. package/templates/todo/template.json +1 -1
  86. package/templates/todo/tsconfig.json +20 -20
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
  });
@@ -7,162 +7,183 @@ import { spawn } from "bun";
7
7
  export const REGISTRY_URL = "https://raw.githubusercontent.com/bosapi/bosia/main/registry";
8
8
 
9
9
  export interface InstallOptions {
10
- skipInstall?: boolean; // write deps to package.json instead of `bun add`
11
- skipPrompts?: boolean; // auto-overwrite, no interactive prompts
12
- cwd?: string; // override process.cwd() for file operations
10
+ skipInstall?: boolean; // write deps to package.json instead of `bun add`
11
+ skipPrompts?: boolean; // auto-overwrite, no interactive prompts
12
+ cwd?: string; // override process.cwd() for file operations
13
13
  }
14
14
 
15
15
  // ─── Local registry resolution ────────────────────────────
16
16
 
17
17
  export function resolveLocalRegistry(): string {
18
- let dir = dirname(new URL(import.meta.url).pathname);
19
- for (let i = 0; i < 10; i++) {
20
- const candidate = join(dir, "registry");
21
- if (existsSync(join(candidate, "index.json"))) return candidate;
22
- const parent = dirname(dir);
23
- if (parent === dir) break;
24
- dir = parent;
25
- }
26
- throw new Error("Could not find local registry/ directory.");
18
+ let dir = dirname(new URL(import.meta.url).pathname);
19
+ for (let i = 0; i < 10; i++) {
20
+ const candidate = join(dir, "registry");
21
+ if (existsSync(join(candidate, "index.json"))) return candidate;
22
+ const parent = dirname(dir);
23
+ if (parent === dir) break;
24
+ dir = parent;
25
+ }
26
+ throw new Error("Could not find local registry/ directory.");
27
27
  }
28
28
 
29
29
  /** Resolve local registry, exiting with error message on failure. For CLI entry points. */
30
30
  export function resolveLocalRegistryOrExit(): string {
31
- try {
32
- return resolveLocalRegistry();
33
- } catch {
34
- console.error("āŒ Could not find local registry/ directory.");
35
- process.exit(1);
36
- }
31
+ try {
32
+ return resolveLocalRegistry();
33
+ } catch {
34
+ console.error("āŒ Could not find local registry/ directory.");
35
+ process.exit(1);
36
+ }
37
37
  }
38
38
 
39
39
  // ─── Registry file readers ────────────────────────────────
40
40
 
41
41
  /** Read and parse a JSON file from the registry (local or remote). */
42
42
  export async function readRegistryJSON<T>(
43
- registryRoot: string | null,
44
- category: string,
45
- name: string,
46
- file: string,
43
+ registryRoot: string | null,
44
+ category: string,
45
+ name: string,
46
+ file: string,
47
47
  ): Promise<T> {
48
- if (registryRoot) {
49
- const path = join(registryRoot, category, name, file);
50
- if (!existsSync(path)) {
51
- throw new Error(`"${file}" not found for ${category.slice(0, -1)} "${name}" in local registry`);
52
- }
53
- return JSON.parse(readFileSync(path, "utf-8"));
54
- }
55
- return fetchJSON<T>(`${REGISTRY_URL}/${category}/${name}/${file}`);
48
+ if (registryRoot) {
49
+ const path = join(registryRoot, category, name, file);
50
+ if (!existsSync(path)) {
51
+ throw new Error(
52
+ `"${file}" not found for ${category.slice(0, -1)} "${name}" in local registry`,
53
+ );
54
+ }
55
+ return JSON.parse(readFileSync(path, "utf-8"));
56
+ }
57
+ return fetchJSON<T>(`${REGISTRY_URL}/${category}/${name}/${file}`);
56
58
  }
57
59
 
58
60
  /** Read a text file from the registry (local or remote). */
59
61
  export async function readRegistryFile(
60
- registryRoot: string | null,
61
- category: string,
62
- name: string,
63
- file: string,
62
+ registryRoot: string | null,
63
+ category: string,
64
+ name: string,
65
+ file: string,
64
66
  ): Promise<string> {
65
- if (registryRoot) {
66
- const path = join(registryRoot, category, name, file);
67
- if (!existsSync(path)) {
68
- throw new Error(`File "${file}" not found for ${category.slice(0, -1)} "${name}" in local registry`);
69
- }
70
- return readFileSync(path, "utf-8");
71
- }
72
- return fetchText(`${REGISTRY_URL}/${category}/${name}/${file}`);
67
+ if (registryRoot) {
68
+ const path = join(registryRoot, category, name, file);
69
+ if (!existsSync(path)) {
70
+ throw new Error(
71
+ `File "${file}" not found for ${category.slice(0, -1)} "${name}" in local registry`,
72
+ );
73
+ }
74
+ return readFileSync(path, "utf-8");
75
+ }
76
+ return fetchText(`${REGISTRY_URL}/${category}/${name}/${file}`);
73
77
  }
74
78
 
75
79
  // ─── package.json helpers ─────────────────────────────────
76
80
 
77
81
  export interface PkgDeps {
78
- deps?: Record<string, string>;
79
- devDeps?: Record<string, string>;
80
- scripts?: Record<string, string>;
82
+ deps?: Record<string, string>;
83
+ devDeps?: Record<string, string>;
84
+ scripts?: Record<string, string>;
81
85
  }
82
86
 
83
87
  /**
84
88
  * Merge dependencies and scripts into package.json in a single read/write.
85
89
  * Returns the list of added keys, or empty arrays if nothing changed.
86
90
  */
87
- export function mergePkgJson(cwd: string, changes: PkgDeps): { addedDeps: string[]; addedScripts: string[] } {
88
- const pkgPath = join(cwd, "package.json");
89
- if (!existsSync(pkgPath)) return { addedDeps: [], addedScripts: [] };
90
-
91
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
92
- let changed = false;
93
- const addedDeps: string[] = [];
94
- const addedScripts: string[] = [];
95
-
96
- if (changes.deps && Object.keys(changes.deps).length > 0) {
97
- pkg.dependencies = pkg.dependencies ?? {};
98
- for (const [name, ver] of Object.entries(changes.deps)) {
99
- if (!pkg.dependencies[name]) {
100
- pkg.dependencies[name] = ver;
101
- addedDeps.push(name);
102
- changed = true;
103
- }
104
- }
105
- }
106
-
107
- if (changes.devDeps && Object.keys(changes.devDeps).length > 0) {
108
- pkg.devDependencies = pkg.devDependencies ?? {};
109
- for (const [name, ver] of Object.entries(changes.devDeps)) {
110
- if (!pkg.devDependencies[name]) {
111
- pkg.devDependencies[name] = ver;
112
- addedDeps.push(name);
113
- changed = true;
114
- }
115
- }
116
- }
117
-
118
- if (changes.scripts && Object.keys(changes.scripts).length > 0) {
119
- pkg.scripts = pkg.scripts ?? {};
120
- for (const [key, val] of Object.entries(changes.scripts)) {
121
- if (!pkg.scripts[key]) {
122
- pkg.scripts[key] = val;
123
- addedScripts.push(key);
124
- changed = true;
125
- }
126
- }
127
- }
128
-
129
- if (changed) {
130
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
131
- }
132
-
133
- return { addedDeps, addedScripts };
91
+ export function mergePkgJson(
92
+ cwd: string,
93
+ changes: PkgDeps,
94
+ ): { addedDeps: string[]; addedScripts: string[] } {
95
+ const pkgPath = join(cwd, "package.json");
96
+ if (!existsSync(pkgPath)) return { addedDeps: [], addedScripts: [] };
97
+
98
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
99
+ let changed = false;
100
+ const addedDeps: string[] = [];
101
+ const addedScripts: string[] = [];
102
+
103
+ if (changes.deps && Object.keys(changes.deps).length > 0) {
104
+ pkg.dependencies = pkg.dependencies ?? {};
105
+ for (const [name, ver] of Object.entries(changes.deps)) {
106
+ if (!pkg.dependencies[name]) {
107
+ pkg.dependencies[name] = ver;
108
+ addedDeps.push(name);
109
+ changed = true;
110
+ }
111
+ }
112
+ }
113
+
114
+ if (changes.devDeps && Object.keys(changes.devDeps).length > 0) {
115
+ pkg.devDependencies = pkg.devDependencies ?? {};
116
+ for (const [name, ver] of Object.entries(changes.devDeps)) {
117
+ if (!pkg.devDependencies[name]) {
118
+ pkg.devDependencies[name] = ver;
119
+ addedDeps.push(name);
120
+ changed = true;
121
+ }
122
+ }
123
+ }
124
+
125
+ if (changes.scripts && Object.keys(changes.scripts).length > 0) {
126
+ pkg.scripts = pkg.scripts ?? {};
127
+ for (const [key, val] of Object.entries(changes.scripts)) {
128
+ if (!pkg.scripts[key]) {
129
+ pkg.scripts[key] = val;
130
+ addedScripts.push(key);
131
+ changed = true;
132
+ }
133
+ }
134
+ }
135
+
136
+ if (changed) {
137
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
138
+ }
139
+
140
+ return { addedDeps, addedScripts };
134
141
  }
135
142
 
136
143
  /** Run `bun add` for deps and optionally `bun add --dev` for devDeps. */
137
- export async function bunAdd(cwd: string, deps: Record<string, string>, devDeps?: Record<string, string>): Promise<void> {
138
- const packages = Object.entries(deps).map(([pkg, ver]) => (ver ? `${pkg}@${ver}` : pkg));
139
- if (packages.length > 0) {
140
- console.log(`\nšŸ“„ npm: ${packages.join(", ")}`);
141
- const proc = spawn(["bun", "add", ...packages], { stdout: "inherit", stderr: "inherit", cwd });
142
- if ((await proc.exited) !== 0) {
143
- console.warn(`āš ļø bun add failed for: ${packages.join(", ")}`);
144
- }
145
- }
146
- const devPackages = Object.entries(devDeps ?? {}).map(([pkg, ver]) => (ver ? `${pkg}@${ver}` : pkg));
147
- if (devPackages.length > 0) {
148
- console.log(`\nšŸ“„ npm (dev): ${devPackages.join(", ")}`);
149
- const proc = spawn(["bun", "add", "--dev", ...devPackages], { stdout: "inherit", stderr: "inherit", cwd });
150
- if ((await proc.exited) !== 0) {
151
- console.warn(`āš ļø bun add --dev failed for: ${devPackages.join(", ")}`);
152
- }
153
- }
144
+ export async function bunAdd(
145
+ cwd: string,
146
+ deps: Record<string, string>,
147
+ devDeps?: Record<string, string>,
148
+ ): Promise<void> {
149
+ const packages = Object.entries(deps).map(([pkg, ver]) => (ver ? `${pkg}@${ver}` : pkg));
150
+ if (packages.length > 0) {
151
+ console.log(`\nšŸ“„ npm: ${packages.join(", ")}`);
152
+ const proc = spawn(["bun", "add", ...packages], {
153
+ stdout: "inherit",
154
+ stderr: "inherit",
155
+ cwd,
156
+ });
157
+ if ((await proc.exited) !== 0) {
158
+ console.warn(`āš ļø bun add failed for: ${packages.join(", ")}`);
159
+ }
160
+ }
161
+ const devPackages = Object.entries(devDeps ?? {}).map(([pkg, ver]) =>
162
+ ver ? `${pkg}@${ver}` : pkg,
163
+ );
164
+ if (devPackages.length > 0) {
165
+ console.log(`\nšŸ“„ npm (dev): ${devPackages.join(", ")}`);
166
+ const proc = spawn(["bun", "add", "--dev", ...devPackages], {
167
+ stdout: "inherit",
168
+ stderr: "inherit",
169
+ cwd,
170
+ });
171
+ if ((await proc.exited) !== 0) {
172
+ console.warn(`āš ļø bun add --dev failed for: ${devPackages.join(", ")}`);
173
+ }
174
+ }
154
175
  }
155
176
 
156
177
  // ─── HTTP helpers ─────────────────────────────────────────
157
178
 
158
179
  async function fetchJSON<T>(url: string): Promise<T> {
159
- const res = await fetch(url);
160
- if (!res.ok) throw new Error(`Failed to fetch ${url} (${res.status})`);
161
- return res.json() as Promise<T>;
180
+ const res = await fetch(url);
181
+ if (!res.ok) throw new Error(`Failed to fetch ${url} (${res.status})`);
182
+ return res.json() as Promise<T>;
162
183
  }
163
184
 
164
185
  async function fetchText(url: string): Promise<string> {
165
- const res = await fetch(url);
166
- if (!res.ok) throw new Error(`Failed to fetch ${url} (${res.status})`);
167
- return res.text();
186
+ const res = await fetch(url);
187
+ if (!res.ok) throw new Error(`Failed to fetch ${url} (${res.status})`);
188
+ return res.text();
168
189
  }
package/src/cli/start.ts CHANGED
@@ -3,24 +3,24 @@ import { loadEnv } from "../core/env.ts";
3
3
  import { BOSIA_NODE_PATH } from "../core/paths.ts";
4
4
 
5
5
  export async function runStart() {
6
- loadEnv("production");
6
+ loadEnv("production");
7
7
 
8
- let serverEntry = "index.js";
9
- try {
10
- const manifest = await Bun.file("./dist/manifest.json").json();
11
- serverEntry = manifest.serverEntry ?? "index.js";
12
- } catch { }
8
+ let serverEntry = "index.js";
9
+ try {
10
+ const manifest = await Bun.file("./dist/manifest.json").json();
11
+ serverEntry = manifest.serverEntry ?? "index.js";
12
+ } catch {}
13
13
 
14
- const proc = spawn(["bun", "run", `dist/server/${serverEntry}`], {
15
- stdout: "inherit",
16
- stderr: "inherit",
17
- cwd: process.cwd(),
18
- env: {
19
- ...process.env,
20
- NODE_ENV: "production",
21
- NODE_PATH: BOSIA_NODE_PATH,
22
- },
23
- });
14
+ const proc = spawn(["bun", "run", `dist/server/${serverEntry}`], {
15
+ stdout: "inherit",
16
+ stderr: "inherit",
17
+ cwd: process.cwd(),
18
+ env: {
19
+ ...process.env,
20
+ NODE_ENV: "production",
21
+ NODE_PATH: BOSIA_NODE_PATH,
22
+ },
23
+ });
24
24
 
25
- await proc.exited;
25
+ await proc.exited;
26
26
  }
@@ -0,0 +1,25 @@
1
+ import { spawn } from "bun";
2
+ import { loadEnv } from "../core/env.ts";
3
+ import { BOSIA_NODE_PATH } from "../core/paths.ts";
4
+
5
+ export async function runTest(args: string[]) {
6
+ loadEnv("test");
7
+
8
+ if (!process.env.BOSIA_ENV) process.env.BOSIA_ENV = "test";
9
+ if (!process.env.NODE_ENV) process.env.NODE_ENV = "test";
10
+
11
+ const proc = spawn(["bun", "test", ...args], {
12
+ stdout: "inherit",
13
+ stderr: "inherit",
14
+ cwd: process.cwd(),
15
+ env: {
16
+ ...process.env,
17
+ BOSIA_ENV: process.env.BOSIA_ENV,
18
+ NODE_ENV: process.env.NODE_ENV,
19
+ NODE_PATH: BOSIA_NODE_PATH,
20
+ },
21
+ });
22
+
23
+ const code = await proc.exited;
24
+ process.exit(code);
25
+ }