@thebase/ui 1.1.1 → 1.1.2
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/bin/create-baseui-app.mjs +450 -3
- package/dist/baseui.esm.js +1 -1
- package/dist/baseui.min.js +1 -1
- package/dist/baseui.pwa.esm.js +53 -0
- package/dist/baseui.registry.json +2 -2
- package/dist/types/pwa.d.ts +37 -0
- package/docs/analytics.md +1 -1
- package/docs/usage-guide.md +36 -0
- package/package.json +6 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
5
6
|
|
|
6
7
|
const ownPackageJson = JSON.parse(
|
|
7
8
|
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
@@ -16,13 +17,18 @@ function toPackageName(name) {
|
|
|
16
17
|
.replace(/^-+|-+$/g, "") || "baseui-app";
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
/**
|
|
20
|
-
function
|
|
21
|
-
|
|
20
|
+
/** Turns a kebab/snake-case app name into a human-readable title, e.g. "qr-page" -> "Qr Page". */
|
|
21
|
+
function toTitle(appName) {
|
|
22
|
+
return appName
|
|
22
23
|
.split(/[-_]/)
|
|
23
24
|
.filter(Boolean)
|
|
24
25
|
.map((word) => word[0].toUpperCase() + word.slice(1))
|
|
25
26
|
.join(" ");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Builds the fixed set of Vite + BaseUI scaffold files for a project named `appName`. */
|
|
30
|
+
function buildTemplateFiles(appName, baseuiVersion) {
|
|
31
|
+
const title = toTitle(appName);
|
|
26
32
|
|
|
27
33
|
return {
|
|
28
34
|
"package.json": JSON.stringify(
|
|
@@ -140,6 +146,375 @@ npm run dev
|
|
|
140
146
|
};
|
|
141
147
|
}
|
|
142
148
|
|
|
149
|
+
/** Builds one page's HTML shell: mounts #app and loads that page's own entry script. */
|
|
150
|
+
function pageHtml(title, entryScript) {
|
|
151
|
+
return `<!doctype html>
|
|
152
|
+
<html lang="en" b-theme="light">
|
|
153
|
+
<head>
|
|
154
|
+
<meta charset="utf-8" />
|
|
155
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
156
|
+
<title>${title}</title>
|
|
157
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
158
|
+
</head>
|
|
159
|
+
<body class="bg-body text-body">
|
|
160
|
+
<div id="app"></div>
|
|
161
|
+
<script type="module" src="${entryScript}"></script>
|
|
162
|
+
</body>
|
|
163
|
+
</html>
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Builds one page's entry script: loads its templates/component and mounts via the shared mountPage helper. */
|
|
168
|
+
function pageMain(componentName, templatesImportPath, componentImportPath) {
|
|
169
|
+
return `import "@thebase/ui/baseui.css";
|
|
170
|
+
import pageTemplates from "${templatesImportPath}?raw";
|
|
171
|
+
import { ${componentName} } from "${componentImportPath}";
|
|
172
|
+
import { mountPage } from "../mount-page.js";
|
|
173
|
+
import "../style.css";
|
|
174
|
+
|
|
175
|
+
await mountPage(${componentName}, pageTemplates);
|
|
176
|
+
`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Builds a 4-page Vite + BaseUI starter (landing, login, signup, an authenticated home shell) for a
|
|
180
|
+
* project named `appName` — each page is its own Owl root mounted by its own HTML entry, composed
|
|
181
|
+
* from the same blocks as `examples/blocks/login-03.html`, `signup-03.html` and `sidebar-02.html`. */
|
|
182
|
+
function buildStarterFiles(appName, baseuiVersion) {
|
|
183
|
+
const title = toTitle(appName);
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
"package.json": JSON.stringify(
|
|
187
|
+
{
|
|
188
|
+
name: appName,
|
|
189
|
+
private: true,
|
|
190
|
+
version: "0.0.1",
|
|
191
|
+
type: "module",
|
|
192
|
+
scripts: {
|
|
193
|
+
dev: "vite",
|
|
194
|
+
build: "vite build",
|
|
195
|
+
preview: "vite preview",
|
|
196
|
+
},
|
|
197
|
+
dependencies: {
|
|
198
|
+
"@thebase/ui": `^${baseuiVersion}`,
|
|
199
|
+
},
|
|
200
|
+
devDependencies: {
|
|
201
|
+
vite: "^8.2.1",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
null,
|
|
205
|
+
2,
|
|
206
|
+
) + "\n",
|
|
207
|
+
|
|
208
|
+
"vite.config.js": `import { defineConfig } from "vite";
|
|
209
|
+
import { fileURLToPath } from "node:url";
|
|
210
|
+
|
|
211
|
+
/** Resolves a file in this config's own directory, Node-ESM-safe (no __dirname in ESM). */
|
|
212
|
+
const here = (file) => fileURLToPath(new URL(file, import.meta.url));
|
|
213
|
+
|
|
214
|
+
export default defineConfig({
|
|
215
|
+
server: { open: true },
|
|
216
|
+
build: {
|
|
217
|
+
rollupOptions: {
|
|
218
|
+
input: {
|
|
219
|
+
main: here("./index.html"),
|
|
220
|
+
login: here("./login.html"),
|
|
221
|
+
signup: here("./signup.html"),
|
|
222
|
+
home: here("./home.html"),
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
`,
|
|
228
|
+
|
|
229
|
+
".gitignore": `node_modules/\ndist/\n`,
|
|
230
|
+
|
|
231
|
+
"public/favicon.svg": `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
|
232
|
+
fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
233
|
+
<rect width="24" height="24" rx="5" fill="#0f172a" stroke="none" />
|
|
234
|
+
<path d="M8 12h8" />
|
|
235
|
+
<path d="M12 8v8" />
|
|
236
|
+
</svg>
|
|
237
|
+
`,
|
|
238
|
+
|
|
239
|
+
"src/style.css": `/* App-specific styles layered on top of @thebase/ui/baseui.css go here. */\n`,
|
|
240
|
+
|
|
241
|
+
"src/mount-page.js": `import { mount } from "@thebase/ui";
|
|
242
|
+
import baseuiTemplates from "@thebase/ui/dist/baseui.templates.xml?raw";
|
|
243
|
+
|
|
244
|
+
/** Both files are full <templates>...</templates> documents; strip each one's outer wrapper before
|
|
245
|
+
* joining them, otherwise the merged string has two root elements and Owl's XML parser throws. */
|
|
246
|
+
const stripTemplatesWrapper = (xml) =>
|
|
247
|
+
xml.replace(/^\\s*<templates[^>]*>/, "").replace(/<\\/templates>\\s*$/, "");
|
|
248
|
+
|
|
249
|
+
/** Merges BaseUI's component templates with one page's own templates and mounts its root component. */
|
|
250
|
+
export async function mountPage(Root, pageTemplatesXml) {
|
|
251
|
+
const templates = \`<templates>\${stripTemplatesWrapper(baseuiTemplates)}\${stripTemplatesWrapper(pageTemplatesXml)}</templates>\`;
|
|
252
|
+
await mount(Root, document.getElementById("app"), { templates });
|
|
253
|
+
}
|
|
254
|
+
`,
|
|
255
|
+
|
|
256
|
+
"index.html": pageHtml(title, "/src/landing/main.js"),
|
|
257
|
+
"src/landing/main.js": pageMain("Landing", "./Landing.xml", "./Landing.js"),
|
|
258
|
+
"src/landing/Landing.js": `import { Component } from "@thebase/ui";
|
|
259
|
+
import { Button, Card, CardBody, CardHeader, CardTitle, Icon } from "@thebase/ui";
|
|
260
|
+
|
|
261
|
+
/** Marketing landing page: header with a login link, hero pitch, feature grid, footer. */
|
|
262
|
+
export class Landing extends Component {
|
|
263
|
+
static template = "app.Landing";
|
|
264
|
+
static components = { Button, Card, CardBody, CardHeader, CardTitle, Icon };
|
|
265
|
+
}
|
|
266
|
+
`,
|
|
267
|
+
"src/landing/Landing.xml": `<templates xml:space="preserve">
|
|
268
|
+
<t t-name="app.Landing">
|
|
269
|
+
<div class="d-flex flex-column min-vh-100">
|
|
270
|
+
<header class="border-bottom">
|
|
271
|
+
<div class="container d-flex align-items-center justify-content-between py-3">
|
|
272
|
+
<a href="/" class="d-inline-flex align-items-center gap-2 fw-medium text-decoration-none">
|
|
273
|
+
<Icon name="'gallery-vertical-end'"/>
|
|
274
|
+
${title}
|
|
275
|
+
</a>
|
|
276
|
+
<Button variant="'outline'" onClick="() => window.location.assign('/login.html')">Login</Button>
|
|
277
|
+
</div>
|
|
278
|
+
</header>
|
|
279
|
+
|
|
280
|
+
<section class="container py-5 text-center">
|
|
281
|
+
<h1 class="display-5 fw-bold mb-3">Build faster with ${title}</h1>
|
|
282
|
+
<p class="lead text-muted mx-auto mb-4" style="max-width: 40rem">
|
|
283
|
+
A starter scaffold wired up with BaseUI — pure Owl components, Bootstrap styling, and the pages you need to ship day one.
|
|
284
|
+
</p>
|
|
285
|
+
<div class="d-flex justify-content-center gap-3">
|
|
286
|
+
<Button icon="'rocket'" onClick="() => window.location.assign('/signup.html')">Get started</Button>
|
|
287
|
+
<Button variant="'outline'" onClick="() => window.location.assign('/login.html')">Login</Button>
|
|
288
|
+
</div>
|
|
289
|
+
</section>
|
|
290
|
+
|
|
291
|
+
<section class="container py-5">
|
|
292
|
+
<div class="row g-4">
|
|
293
|
+
<div class="col-12 col-md-4">
|
|
294
|
+
<Card className="'h-100'">
|
|
295
|
+
<CardHeader className="'d-flex align-items-center gap-2'"><Icon name="'zap'"/><CardTitle>Fast by default</CardTitle></CardHeader>
|
|
296
|
+
<CardBody>Vite dev server and production build, pre-wired to BaseUI's CDN or local dist.</CardBody>
|
|
297
|
+
</Card>
|
|
298
|
+
</div>
|
|
299
|
+
<div class="col-12 col-md-4">
|
|
300
|
+
<Card className="'h-100'">
|
|
301
|
+
<CardHeader className="'d-flex align-items-center gap-2'"><Icon name="'layout-dashboard'"/><CardTitle>Pages included</CardTitle></CardHeader>
|
|
302
|
+
<CardBody>Landing, login, signup and an authenticated home shell, ready to extend.</CardBody>
|
|
303
|
+
</Card>
|
|
304
|
+
</div>
|
|
305
|
+
<div class="col-12 col-md-4">
|
|
306
|
+
<Card className="'h-100'">
|
|
307
|
+
<CardHeader className="'d-flex align-items-center gap-2'"><Icon name="'component'"/><CardTitle>Pure Owl components</CardTitle></CardHeader>
|
|
308
|
+
<CardBody>Every page composes real BaseUI components — swap or extend without fighting the markup.</CardBody>
|
|
309
|
+
</Card>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
</section>
|
|
313
|
+
|
|
314
|
+
<footer class="border-top mt-auto">
|
|
315
|
+
<div class="container py-4 d-flex align-items-center justify-content-between">
|
|
316
|
+
<small class="text-muted">© ${title}. All rights reserved.</small>
|
|
317
|
+
<a href="/login.html" class="small text-decoration-underline">Login</a>
|
|
318
|
+
</div>
|
|
319
|
+
</footer>
|
|
320
|
+
</div>
|
|
321
|
+
</t>
|
|
322
|
+
</templates>
|
|
323
|
+
`,
|
|
324
|
+
|
|
325
|
+
"login.html": pageHtml(`Login — ${title}`, "/src/login/main.js"),
|
|
326
|
+
"src/login/main.js": pageMain("Login", "./Login.xml", "./Login.js"),
|
|
327
|
+
"src/login/Login.js": `import { Component } from "@thebase/ui";
|
|
328
|
+
import { Button, Card, CardBody, Field, FieldGroup, Icon, Input, Label } from "@thebase/ui";
|
|
329
|
+
|
|
330
|
+
/** Login page (ported from examples/blocks/login-03.html): email/password card, redirects home on submit. */
|
|
331
|
+
export class Login extends Component {
|
|
332
|
+
static template = "app.Login";
|
|
333
|
+
static components = { Button, Card, CardBody, Field, FieldGroup, Icon, Input, Label };
|
|
334
|
+
}
|
|
335
|
+
`,
|
|
336
|
+
"src/login/Login.xml": `<templates xml:space="preserve">
|
|
337
|
+
<t t-name="app.Login">
|
|
338
|
+
<div class="d-flex flex-column align-items-center justify-content-center gap-4 min-vh-100 bg-body-tertiary p-4">
|
|
339
|
+
<div class="d-flex flex-column align-items-center gap-4 col-12 col-sm-8 col-md-6 col-lg-4">
|
|
340
|
+
<a href="/" class="d-inline-flex align-items-center gap-2 fw-medium text-decoration-none">
|
|
341
|
+
<Icon name="'gallery-vertical-end'"/>
|
|
342
|
+
${title}
|
|
343
|
+
</a>
|
|
344
|
+
<Card className="'w-100'">
|
|
345
|
+
<CardBody>
|
|
346
|
+
<form class="d-flex flex-column gap-4">
|
|
347
|
+
<div class="d-flex flex-column gap-2 text-center">
|
|
348
|
+
<h2 class="h4 mb-0">Login to your account</h2>
|
|
349
|
+
<p class="text-muted mb-0">Enter your email below to login to your account</p>
|
|
350
|
+
</div>
|
|
351
|
+
<FieldGroup>
|
|
352
|
+
<Field>
|
|
353
|
+
<Label>Email</Label>
|
|
354
|
+
<Input type="'email'" placeholder="'m@example.com'"/>
|
|
355
|
+
</Field>
|
|
356
|
+
<Field>
|
|
357
|
+
<div class="d-flex align-items-center justify-content-between">
|
|
358
|
+
<Label>Password</Label>
|
|
359
|
+
<a href="#" class="small text-decoration-underline">Forgot your password?</a>
|
|
360
|
+
</div>
|
|
361
|
+
<Input type="'password'"/>
|
|
362
|
+
</Field>
|
|
363
|
+
<Button icon="'log-in'" onClick="() => window.location.assign('/home.html')">Login</Button>
|
|
364
|
+
<Button variant="'outline'" icon="'mail'">Login with email</Button>
|
|
365
|
+
</FieldGroup>
|
|
366
|
+
<p class="text-center small mb-0">Don't have an account? <a href="/signup.html" class="text-decoration-underline">Sign up</a></p>
|
|
367
|
+
</form>
|
|
368
|
+
</CardBody>
|
|
369
|
+
</Card>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
</t>
|
|
373
|
+
</templates>
|
|
374
|
+
`,
|
|
375
|
+
|
|
376
|
+
"signup.html": pageHtml(`Sign up — ${title}`, "/src/signup/main.js"),
|
|
377
|
+
"src/signup/main.js": pageMain("Signup", "./Signup.xml", "./Signup.js"),
|
|
378
|
+
"src/signup/Signup.js": `import { Component } from "@thebase/ui";
|
|
379
|
+
import { Button, Card, CardBody, Field, FieldGroup, Icon, Input, Label } from "@thebase/ui";
|
|
380
|
+
|
|
381
|
+
/** Signup page (ported from examples/blocks/signup-03.html): single email field, sends the user to login after submit. */
|
|
382
|
+
export class Signup extends Component {
|
|
383
|
+
static template = "app.Signup";
|
|
384
|
+
static components = { Button, Card, CardBody, Field, FieldGroup, Icon, Input, Label };
|
|
385
|
+
}
|
|
386
|
+
`,
|
|
387
|
+
"src/signup/Signup.xml": `<templates xml:space="preserve">
|
|
388
|
+
<t t-name="app.Signup">
|
|
389
|
+
<div class="d-flex flex-column align-items-center justify-content-center gap-4 min-vh-100 bg-body-tertiary p-4">
|
|
390
|
+
<div class="d-flex flex-column align-items-center gap-4 col-12 col-sm-8 col-md-6 col-lg-4">
|
|
391
|
+
<a href="/" class="d-inline-flex align-items-center gap-2 fw-medium text-decoration-none">
|
|
392
|
+
<Icon name="'gallery-vertical-end'"/>
|
|
393
|
+
${title}
|
|
394
|
+
</a>
|
|
395
|
+
<Card className="'w-100'">
|
|
396
|
+
<CardBody>
|
|
397
|
+
<form class="d-flex flex-column gap-4">
|
|
398
|
+
<div class="d-flex flex-column gap-2 text-center">
|
|
399
|
+
<h2 class="h4 mb-0">Create an account</h2>
|
|
400
|
+
<p class="text-muted mb-0">Enter your email below to create your account</p>
|
|
401
|
+
</div>
|
|
402
|
+
<FieldGroup>
|
|
403
|
+
<Field>
|
|
404
|
+
<Label>Email</Label>
|
|
405
|
+
<Input type="'email'" placeholder="'m@example.com'"/>
|
|
406
|
+
</Field>
|
|
407
|
+
<Button icon="'user-plus'" onClick="() => window.location.assign('/login.html')">Sign up</Button>
|
|
408
|
+
<Button variant="'outline'" icon="'mail'">Sign up with email</Button>
|
|
409
|
+
</FieldGroup>
|
|
410
|
+
<p class="text-center small mb-0">Already have an account? <a href="/login.html" class="text-decoration-underline">Login</a></p>
|
|
411
|
+
</form>
|
|
412
|
+
</CardBody>
|
|
413
|
+
</Card>
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
</t>
|
|
417
|
+
</templates>
|
|
418
|
+
`,
|
|
419
|
+
|
|
420
|
+
"home.html": pageHtml(`Home — ${title}`, "/src/home/main.js"),
|
|
421
|
+
"src/home/main.js": pageMain("Home", "./Home.xml", "./Home.js"),
|
|
422
|
+
"src/home/Home.js": `import { Component, useState } from "@thebase/ui";
|
|
423
|
+
import {
|
|
424
|
+
Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator,
|
|
425
|
+
Button, Icon, InputGroup, InputGroupAddon, InputGroupInput, Separator,
|
|
426
|
+
Shell, ShellContent, ShellHeader, ShellMain, ShellNav, ShellSidebar, ShellSidebarHeader,
|
|
427
|
+
SidebarGroupCollapsible, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarTrigger,
|
|
428
|
+
} from "@thebase/ui";
|
|
429
|
+
|
|
430
|
+
/** Authenticated home shell (ported from examples/blocks/sidebar-02.html): collapsible sidebar nav
|
|
431
|
+
* + sticky header, reached after login/signup; owns the sidebar's collapsed/expanded state. */
|
|
432
|
+
export class Home extends Component {
|
|
433
|
+
static template = "app.Home";
|
|
434
|
+
static components = {
|
|
435
|
+
Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator,
|
|
436
|
+
Button, Icon, InputGroup, InputGroupAddon, InputGroupInput, Separator,
|
|
437
|
+
Shell, ShellContent, ShellHeader, ShellMain, ShellNav, ShellSidebar, ShellSidebarHeader,
|
|
438
|
+
SidebarGroupCollapsible, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarTrigger,
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
setup() {
|
|
442
|
+
this.state = useState({ collapsed: false });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
`,
|
|
446
|
+
"src/home/Home.xml": `<templates xml:space="preserve">
|
|
447
|
+
<t t-name="app.Home">
|
|
448
|
+
<Shell className="'border'" collapsed="state.collapsed" onCollapsedChange="(value) => { state.collapsed = value; }">
|
|
449
|
+
<ShellSidebar>
|
|
450
|
+
<ShellSidebarHeader>
|
|
451
|
+
<a href="/" class="d-flex align-items-center gap-2 text-decoration-none fw-medium">
|
|
452
|
+
<Icon name="'gallery-vertical-end'"/>
|
|
453
|
+
<span class="d-flex flex-column lh-sm">
|
|
454
|
+
<span>${title}</span>
|
|
455
|
+
<small class="text-body-secondary fw-normal">Dashboard</small>
|
|
456
|
+
</span>
|
|
457
|
+
</a>
|
|
458
|
+
</ShellSidebarHeader>
|
|
459
|
+
<InputGroup>
|
|
460
|
+
<InputGroupAddon><Icon name="'search'"/></InputGroupAddon>
|
|
461
|
+
<InputGroupInput type="'search'" placeholder="'Search...'"/>
|
|
462
|
+
</InputGroup>
|
|
463
|
+
<ShellNav className="'d-flex flex-column gap-2'">
|
|
464
|
+
<SidebarGroupCollapsible label="'Getting Started'" defaultOpen="true">
|
|
465
|
+
<SidebarMenu>
|
|
466
|
+
<SidebarMenuItem><SidebarMenuButton href="'#'" icon="'layout-dashboard'" active="true">Overview</SidebarMenuButton></SidebarMenuItem>
|
|
467
|
+
<SidebarMenuItem><SidebarMenuButton href="'#'" icon="'users'">Customers</SidebarMenuButton></SidebarMenuItem>
|
|
468
|
+
<SidebarMenuItem><SidebarMenuButton href="'#'" icon="'settings'">Settings</SidebarMenuButton></SidebarMenuItem>
|
|
469
|
+
</SidebarMenu>
|
|
470
|
+
</SidebarGroupCollapsible>
|
|
471
|
+
</ShellNav>
|
|
472
|
+
</ShellSidebar>
|
|
473
|
+
<ShellMain>
|
|
474
|
+
<ShellHeader className="'border-bottom p-3'">
|
|
475
|
+
<SidebarTrigger/>
|
|
476
|
+
<Separator orientation="'vertical'"/>
|
|
477
|
+
<Breadcrumb>
|
|
478
|
+
<BreadcrumbList>
|
|
479
|
+
<BreadcrumbItem><BreadcrumbPage>Overview</BreadcrumbPage></BreadcrumbItem>
|
|
480
|
+
</BreadcrumbList>
|
|
481
|
+
</Breadcrumb>
|
|
482
|
+
<Button variant="'outline'" size="'sm'" className="'ms-auto'" icon="'log-out'" onClick="() => window.location.assign('/index.html')">Logout</Button>
|
|
483
|
+
</ShellHeader>
|
|
484
|
+
<ShellContent className="'d-flex flex-column gap-3'">
|
|
485
|
+
<div class="row row-cols-3 g-3">
|
|
486
|
+
<div class="col"><div class="ratio ratio-16x9 bg-body-secondary rounded-4"></div></div>
|
|
487
|
+
<div class="col"><div class="ratio ratio-16x9 bg-body-secondary rounded-4"></div></div>
|
|
488
|
+
<div class="col"><div class="ratio ratio-16x9 bg-body-secondary rounded-4"></div></div>
|
|
489
|
+
</div>
|
|
490
|
+
<div class="flex-grow-1 bg-body-secondary rounded-4"></div>
|
|
491
|
+
</ShellContent>
|
|
492
|
+
</ShellMain>
|
|
493
|
+
</Shell>
|
|
494
|
+
</t>
|
|
495
|
+
</templates>
|
|
496
|
+
`,
|
|
497
|
+
|
|
498
|
+
"README.md": `# ${title}
|
|
499
|
+
|
|
500
|
+
Scaffolded with \`@thebase/ui\`'s CLI (\`create-baseui-app init\`): Vite + BaseUI, pure Owl API, 4 pages.
|
|
501
|
+
|
|
502
|
+
- \`/\` — landing page (header, hero, features, footer)
|
|
503
|
+
- \`/login.html\` — login (redirects to \`/home.html\` on submit)
|
|
504
|
+
- \`/signup.html\` — signup (redirects to \`/login.html\` on submit)
|
|
505
|
+
- \`/home.html\` — authenticated home shell (sidebar + header, logout returns to \`/\`)
|
|
506
|
+
|
|
507
|
+
None of the pages call a real backend yet — wire up \`Login\`/\`Signup\`'s button \`onClick\` handlers to
|
|
508
|
+
your own auth API once you have one.
|
|
509
|
+
|
|
510
|
+
\`\`\`sh
|
|
511
|
+
npm install
|
|
512
|
+
npm run dev
|
|
513
|
+
\`\`\`
|
|
514
|
+
`,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
143
518
|
/** Writes each template file under targetDir, skipping any that already exist (convert mode is additive/non-destructive). */
|
|
144
519
|
function writeFiles(targetDir, files, { overwrite }) {
|
|
145
520
|
const written = [];
|
|
@@ -173,6 +548,7 @@ function parseFlags(argv) {
|
|
|
173
548
|
if (arg === "--no-install") flags.install = false;
|
|
174
549
|
else if (arg === "--force") flags.force = true;
|
|
175
550
|
else if (arg === "--version") flags.version = argv[++i];
|
|
551
|
+
else if (arg === "--config") flags.config = argv[++i];
|
|
176
552
|
else positional.push(arg);
|
|
177
553
|
}
|
|
178
554
|
return { flags, positional };
|
|
@@ -205,6 +581,37 @@ function cmdCreate(argv) {
|
|
|
205
581
|
console.log(`\nNext steps:\n cd ${rawName}${flags.install ? "" : "\n npm install"}\n npm run dev`);
|
|
206
582
|
}
|
|
207
583
|
|
|
584
|
+
/** Scaffolds the 4-page starter (landing/login/signup/home) in a new ./<app-name> folder. */
|
|
585
|
+
function cmdInit(argv) {
|
|
586
|
+
const { flags, positional } = parseFlags(argv);
|
|
587
|
+
const rawName = positional[0];
|
|
588
|
+
if (!rawName) {
|
|
589
|
+
console.error("Usage: create-baseui-app init <app-name> [--version <x.y.z>] [--no-install] [--force]");
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
const appName = toPackageName(rawName);
|
|
593
|
+
const targetDir = path.resolve(process.cwd(), rawName);
|
|
594
|
+
|
|
595
|
+
if (existsSync(targetDir) && readdirSync(targetDir).length > 0 && !flags.force) {
|
|
596
|
+
console.error(`Directory "${rawName}" already exists and is not empty. Use --force to write into it anyway.`);
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const baseuiVersion = flags.version || ownPackageJson.version;
|
|
601
|
+
const files = buildStarterFiles(appName, baseuiVersion);
|
|
602
|
+
const { written } = writeFiles(targetDir, files, { overwrite: flags.force ?? false });
|
|
603
|
+
|
|
604
|
+
console.log(`Scaffolded a 4-page Vite + BaseUI starter in ${targetDir}`);
|
|
605
|
+
written.forEach((f) => console.log(` create ${f}`));
|
|
606
|
+
|
|
607
|
+
if (flags.install) runInstall(targetDir);
|
|
608
|
+
|
|
609
|
+
console.log(
|
|
610
|
+
`\nNext steps:\n cd ${rawName}${flags.install ? "" : "\n npm install"}\n npm run dev` +
|
|
611
|
+
`\n\nPages: / (landing), /login.html, /signup.html, /home.html`,
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
208
615
|
function cmdConvert(argv) {
|
|
209
616
|
const { flags, positional } = parseFlags(argv);
|
|
210
617
|
const targetDir = path.resolve(process.cwd(), positional[0] || ".");
|
|
@@ -248,6 +655,34 @@ function cmdConvert(argv) {
|
|
|
248
655
|
console.log(`\nNext steps:\n npm run dev`);
|
|
249
656
|
}
|
|
250
657
|
|
|
658
|
+
/** Generates manifest.webmanifest + sw.js for a PWA from a `baseui.pwa.config.js` the user wrote,
|
|
659
|
+
* using @thebase/ui's buildManifest/buildServiceWorker (see docs/usage-guide.md "PWA toolkit"). */
|
|
660
|
+
async function cmdPwa(argv) {
|
|
661
|
+
const { flags, positional } = parseFlags(argv);
|
|
662
|
+
const targetDir = path.resolve(process.cwd(), positional[0] || ".");
|
|
663
|
+
const configPath = path.resolve(targetDir, flags.config || "baseui.pwa.config.js");
|
|
664
|
+
|
|
665
|
+
if (!existsSync(configPath)) {
|
|
666
|
+
console.error(
|
|
667
|
+
`Config file not found: ${configPath}\n` +
|
|
668
|
+
"Create it first — export default { manifest: {...}, serviceWorker: {...} }, " +
|
|
669
|
+
"see docs/usage-guide.md \"PWA toolkit\" for the full config shape.",
|
|
670
|
+
);
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const { manifest, serviceWorker } = (await import(pathToFileURL(configPath).href)).default;
|
|
675
|
+
const { buildManifest, buildServiceWorker } = await import("../dist/baseui.pwa.esm.js");
|
|
676
|
+
|
|
677
|
+
writeFileSync(
|
|
678
|
+
path.join(targetDir, "manifest.webmanifest"),
|
|
679
|
+
JSON.stringify(buildManifest(manifest), null, 2) + "\n",
|
|
680
|
+
);
|
|
681
|
+
writeFileSync(path.join(targetDir, "sw.js"), buildServiceWorker(serviceWorker));
|
|
682
|
+
|
|
683
|
+
console.log(`Wrote manifest.webmanifest and sw.js in ${targetDir}`);
|
|
684
|
+
}
|
|
685
|
+
|
|
251
686
|
function printHelp() {
|
|
252
687
|
console.log(`@thebase/ui project CLI
|
|
253
688
|
|
|
@@ -258,6 +693,16 @@ Usage:
|
|
|
258
693
|
create-baseui-app convert [path] [--version <x.y.z>] [--no-install]
|
|
259
694
|
Turn an existing (non-npm) folder into a Vite + BaseUI project, in place.
|
|
260
695
|
Never overwrites files that already exist; only fills in what's missing.
|
|
696
|
+
|
|
697
|
+
create-baseui-app init <app-name> [--version <x.y.z>] [--no-install] [--force]
|
|
698
|
+
Scaffold a ready-to-start 4-page project in ./<app-name>: a landing page
|
|
699
|
+
(header/hero/features/footer), login, signup, and an authenticated home
|
|
700
|
+
shell (sidebar + header) — ported from the login-03/signup-03/sidebar-02
|
|
701
|
+
example blocks.
|
|
702
|
+
|
|
703
|
+
create-baseui-app pwa [path] [--config <file>]
|
|
704
|
+
Generate manifest.webmanifest + sw.js in [path] (default ".") from a
|
|
705
|
+
baseui.pwa.config.js config file (default name, override with --config).
|
|
261
706
|
`);
|
|
262
707
|
}
|
|
263
708
|
|
|
@@ -265,4 +710,6 @@ const [, , command, ...rest] = process.argv;
|
|
|
265
710
|
|
|
266
711
|
if (command === "create") cmdCreate(rest);
|
|
267
712
|
else if (command === "convert") cmdConvert(rest);
|
|
713
|
+
else if (command === "init") cmdInit(rest);
|
|
714
|
+
else if (command === "pwa") await cmdPwa(rest);
|
|
268
715
|
else printHelp();
|