bosia 0.7.1 → 0.7.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bosia",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "type": "module",
5
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
6
  "keywords": [
@@ -43,6 +43,7 @@
43
43
  "scripts": {
44
44
  "check": "tsc --noEmit && prettier --check .",
45
45
  "check:templates": "bun scripts/check-templates.ts",
46
+ "build:templates": "bun scripts/build-prebuilt-templates.ts",
46
47
  "test": "bun test",
47
48
  "test:watch": "bun test --watch"
48
49
  },
package/src/cli/create.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { resolve, join, basename, relative } from "path";
2
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
3
+ import { tmpdir } from "os";
3
4
  import { spawn } from "bun";
4
5
  import * as p from "@clack/prompts";
5
6
  import { installFeature, initFeatRegistry, resolveLocalRegistry } from "./feat.ts";
@@ -63,6 +64,26 @@ export async function runCreate(name: string | undefined, args: string[] = []) {
63
64
 
64
65
  console.log(`\n⬡ Creating Bosia project: ${basename(targetDir)} (template: ${template})\n`);
65
66
 
67
+ const templateConfigPath = join(templateDir, "template.json");
68
+ const config = existsSync(templateConfigPath)
69
+ ? JSON.parse(readFileSync(templateConfigPath, "utf-8"))
70
+ : null;
71
+
72
+ // Fast path: heavy templates marked `"prebuilt": true` ship a baked,
73
+ // version-locked artifact on GitHub Releases. One download + extract beats
74
+ // 150+ serial registry fetches. Skipped for `--local` (dev flow) and falls
75
+ // back to the registry path if the artifact is missing (offline / no asset yet).
76
+ // `BOSIA_BUILDING_PREBUILT` is set by the artifact generator so it scaffolds
77
+ // via the registry instead of trying to download the asset it's producing.
78
+ if (config?.prebuilt === true && !isLocal && !process.env.BOSIA_BUILDING_PREBUILT) {
79
+ const ok = await scaffoldFromPrebuilt(template, targetDir, name);
80
+ if (ok) {
81
+ await finishCreate(targetDir, name, templateDir, skipInstall);
82
+ return;
83
+ }
84
+ console.log("⚠️ Prebuilt artifact unavailable — installing from registry instead.\n");
85
+ }
86
+
66
87
  copyDir(templateDir, targetDir, name, isLocal);
67
88
 
68
89
  if (existsSync(join(targetDir, ".env.example"))) {
@@ -70,9 +91,7 @@ export async function runCreate(name: string | undefined, args: string[] = []) {
70
91
  }
71
92
 
72
93
  // Install template features from registry
73
- const templateConfigPath = join(templateDir, "template.json");
74
- if (existsSync(templateConfigPath)) {
75
- const config = JSON.parse(readFileSync(templateConfigPath, "utf-8"));
94
+ if (config) {
76
95
  if (config.features?.length) {
77
96
  let localRegistry: string | null = null;
78
97
  try {
@@ -96,15 +115,29 @@ export async function runCreate(name: string | undefined, args: string[] = []) {
96
115
  }
97
116
  }
98
117
 
99
- console.log(`\n✅ Project created at ${targetDir}\n`);
118
+ await finishCreate(targetDir, name, templateDir, skipInstall);
119
+ }
100
120
 
101
- if (skipInstall) {
102
- console.log(`Skipped \`bun install\` (--no-install).\n\ncd ${name} && bun install\n`);
121
+ // ─── Shared finish: optional `bun install` + printed instructions ──────────
122
+ async function finishCreate(
123
+ targetDir: string,
124
+ name: string,
125
+ templateDir: string,
126
+ skipInstall: boolean,
127
+ ) {
128
+ const printInstructions = () => {
103
129
  const instPath = join(templateDir, "instructions.txt");
104
130
  if (existsSync(instPath)) {
105
131
  const instructions = readFileSync(instPath, "utf-8").trimEnd();
106
132
  if (instructions) console.log(instructions);
107
133
  }
134
+ };
135
+
136
+ console.log(`\n✅ Project created at ${targetDir}\n`);
137
+
138
+ if (skipInstall) {
139
+ console.log(`Skipped \`bun install\` (--no-install).\n\ncd ${name} && bun install\n`);
140
+ printInstructions();
108
141
  return;
109
142
  }
110
143
 
@@ -119,14 +152,76 @@ export async function runCreate(name: string | undefined, args: string[] = []) {
119
152
  console.warn("⚠️ bun install failed — run it manually.");
120
153
  } else {
121
154
  console.log(`\n🎉 Ready!\n\ncd ${name}`);
155
+ printInstructions();
156
+ console.log(`bun x bosia dev\n`);
157
+ }
158
+ }
122
159
 
123
- const instPath = join(templateDir, "instructions.txt");
124
- if (existsSync(instPath)) {
125
- const instructions = readFileSync(instPath, "utf-8").trimEnd();
126
- if (instructions) console.log(instructions);
160
+ // ─── Prebuilt artifact fast path ──────────────────────────────────────────
161
+ // Downloads the version-locked `<template>.tar.gz` GitHub Release asset,
162
+ // extracts it into targetDir, then substitutes the `{{PROJECT_NAME}}`
163
+ // placeholder baked into the artifact. Returns false (caller falls back to the
164
+ // registry path) on any failure: 404, offline, or a corrupt archive.
165
+ async function scaffoldFromPrebuilt(
166
+ template: string,
167
+ targetDir: string,
168
+ name: string,
169
+ ): Promise<boolean> {
170
+ const url = `https://github.com/bosapi/bosia/releases/download/v${BOSIA_VERSION}/${template}.tar.gz`;
171
+ const tmpTar = join(tmpdir(), `bosia-${template}-${Date.now()}.tar.gz`);
172
+
173
+ try {
174
+ console.log(`⬇️ Downloading prebuilt template…`);
175
+ const res = await fetch(url);
176
+ if (!res.ok) return false;
177
+ writeFileSync(tmpTar, Buffer.from(await res.arrayBuffer()));
178
+ } catch {
179
+ return false;
180
+ }
181
+
182
+ try {
183
+ mkdirSync(targetDir, { recursive: true });
184
+ const tar = spawn(["tar", "-xzf", tmpTar, "-C", targetDir], {
185
+ stdout: "inherit",
186
+ stderr: "inherit",
187
+ });
188
+ if ((await tar.exited) !== 0) return false;
189
+ } catch {
190
+ return false;
191
+ } finally {
192
+ try {
193
+ unlinkSync(tmpTar);
194
+ } catch {
195
+ // best-effort cleanup
127
196
  }
197
+ }
128
198
 
129
- console.log(`bun x bosia dev\n`);
199
+ substitutePlaceholder(targetDir, name);
200
+
201
+ // Artifact bakes `.env` already, but restore from `.env.example` if missing.
202
+ const envPath = join(targetDir, ".env");
203
+ const envExample = join(targetDir, ".env.example");
204
+ if (!existsSync(envPath) && existsSync(envExample)) {
205
+ writeFileSync(envPath, readFileSync(envExample, "utf-8"));
206
+ }
207
+
208
+ return true;
209
+ }
210
+
211
+ // Replace the `{{PROJECT_NAME}}` placeholder (baked at generate-time) across
212
+ // every extracted file. Mirrors copyDir's utf-8 assumption — templates carry
213
+ // no binary files.
214
+ function substitutePlaceholder(dir: string, name: string) {
215
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
216
+ const full = join(dir, entry.name);
217
+ if (entry.isDirectory()) {
218
+ substitutePlaceholder(full, name);
219
+ } else if (entry.isFile()) {
220
+ const content = readFileSync(full, "utf-8");
221
+ if (content.includes("{{PROJECT_NAME}}")) {
222
+ writeFileSync(full, content.replaceAll("{{PROJECT_NAME}}", name), "utf-8");
223
+ }
224
+ }
130
225
  }
131
226
  }
132
227
 
@@ -109,7 +109,7 @@ function buildContext(el){
109
109
  var leaf=el.getAttribute("data-bosia-loc")||"";
110
110
  var frames=userFrames(el);
111
111
  var pageFile=findPageFile(frames);
112
- var lines=["[Inspector]"];
112
+ var lines=[];
113
113
  lines.push("url: "+location.href);
114
114
  if(pageFile)lines.push("pageFile: "+pageFile);
115
115
  if(leaf&&leaf!==pageFile)lines.push("component: "+leaf);
@@ -1,4 +1,5 @@
1
1
  {
2
+ "prebuilt": true,
2
3
  "features": ["auth", "rbac", "file-upload", "shop"],
3
4
  "featureOptions": {
4
5
  "drizzle.dialect": "sqlite",