create-lupine 1.0.26 → 1.0.27

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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lupine",
3
- "version": "1.0.26",
3
+ "version": "1.0.27",
4
4
  "description": "Scaffolding tool for Lupine.js projects",
5
5
  "bin": {
6
6
  "create-lupine": "index.js"
@@ -477,7 +477,231 @@ const Parent = () => {
477
477
  - Fixed parameters use `/fixed-parameter/` (e.g., `pageRouter.use('/page/:id/detail/', PlayPage)`), `detail` is a fixed parameter.
478
478
  - Optional parameters use `?` (e.g., `/page/:userId/?option1/?option2`). Once an optional parameter is declared, all subsequent route sections become optional (It's not a query string).
479
479
 
480
- ## 6. Coding Standards & Gotchas
480
+ ## 6. Lupine.cms App Integration Guide
481
+
482
+ This section describes Lupine.cms from the app user's point of view: how a frontend app routes public/SSR requests into CMS rendering, how CMS URLs are resolved, how language fallback works, how coded pages/components override CMS content, and where registration code must live.
483
+
484
+ ### 6.1 Required frontend entry point
485
+
486
+ A CMS-enabled frontend app must register `CmsRenderPage` as the catch-all page handler in its public app entry, for example in `apps/cms/web/src/index.tsx`:
487
+
488
+ ```tsx
489
+ pageRouter.use('*', CmsRenderPage);
490
+ ```
491
+
492
+ This is the key integration point. During frontend rendering or SSR, any page request that reaches this catch-all route is delegated to `CmsRenderPage`. Without this route, CMS page JSON will not be used for public page requests.
493
+
494
+ Typical public entry responsibilities:
495
+
496
+ - initialize app/global styles and theme
497
+ - register any public coded CMS frame/content pages with `cmsRegisterPage()`
498
+ - configure `pageRouter`
499
+ - end with `pageRouter.use('*', CmsRenderPage)` so unresolved public routes are served by CMS
500
+
501
+ ### 6.2 CMS URL structure: `/lang/frame/content`
502
+
503
+ CMS public URLs are interpreted as three logical levels:
504
+
505
+ ```text
506
+ /lang/frame/content
507
+ ```
508
+
509
+ Meanings:
510
+
511
+ - `lang`: optional language code
512
+ - `frame`: optional frame page ID
513
+ - `content`: optional content page ID
514
+
515
+ If the first URL segment matches a configured site language, it is treated as `lang`. Otherwise the URL is treated as not having a language prefix.
516
+
517
+ Examples:
518
+
519
+ ```text
520
+ /en/home/article-1 -> lang=en, frame=home, content=article-1
521
+ /home/article-1 -> lang is resolved automatically, frame=home, content=article-1
522
+ /en/home -> lang=en, frame=home, content defaults from config
523
+ /home -> lang auto, frame=home, content defaults from config
524
+ / -> lang auto, frame and content default from config
525
+ ```
526
+
527
+ ### 6.3 Language resolution and default language
528
+
529
+ If `lang` is omitted from the URL, CMS resolves language in this order:
530
+
531
+ 1. explicit `lang` parameter passed to the render/embed API, if any
532
+ 2. language in the URL prefix, if any
533
+ 3. browser language
534
+ 4. the default language configured in `siteLangs`
535
+
536
+ The available languages are configured in the web settings UI using the `siteLangs` setting declared in `packages/lupine.api/admin/admin-setting-web.tsx`:
537
+
538
+ ```typescript
539
+ { label: 'Languages', type: 'text', name: 'siteLangs', tip: 'Languages separated by commas. Format: code:title,code:title. First is default language. Example: en:English,cn:Chinese,ja:Japanese' }
540
+ ```
541
+
542
+ The first item in `siteLangs` is the default language. If the browser language is not in `siteLangs`, CMS falls back to this first language.
543
+
544
+ Agent rules:
545
+
546
+ - Do not assume browser language is always accepted. Always compare it against configured `siteLangs`.
547
+ - Keep the first `siteLangs` entry stable because it is the fallback default for public rendering and data fallback.
548
+ - When testing multilingual pages, test both explicit language URLs and language omitted URLs.
549
+
550
+ ### 6.4 Default frame and content IDs
551
+
552
+ When `frame` or `content` is missing from the URL, CMS uses web configuration defaults from `render-page.tsx`:
553
+
554
+ ```typescript
555
+ const frameId = (hasLangPrefix ? urlParts[1] : urlParts[0]) || await WebConfig.get('cmsPageDefault', '');
556
+ const contentId = (hasLangPrefix ? urlParts[2] : urlParts[1]) || await WebConfig.get('cmsContentDefault', '');
557
+ ```
558
+
559
+ Therefore:
560
+
561
+ - missing frame ID falls back to `cmsPageDefault`
562
+ - missing content ID falls back to `cmsContentDefault`
563
+
564
+ Agent rules:
565
+
566
+ - If `/` renders incorrectly, inspect `cmsPageDefault` and `cmsContentDefault` first.
567
+ - If `/en` renders incorrectly, remember it may still be using default frame/content IDs.
568
+ - Do not hardcode default page IDs in rendering logic; use `WebConfig` settings.
569
+
570
+ ### 6.5 CMS page data lookup and language fallback
571
+
572
+ When CMS loads persisted page JSON from the backend, it uses language-qualified IDs first:
573
+
574
+ 1. try `lang:id`
575
+ 2. if missing, try `defaultLang:id`
576
+ 3. if still missing, try plain `id`
577
+
578
+ This applies to frame/content page data loaded from CMS storage.
579
+
580
+ Practical result:
581
+
582
+ - Save `en:home` to create an English-specific home page.
583
+ - Save `cn:home` to create a Chinese-specific home page.
584
+ - Save `home` as a language-neutral fallback.
585
+ - If `ja:home` does not exist, CMS can fall back to default language and then plain `home`.
586
+
587
+ Agent rules:
588
+
589
+ - When investigating "wrong language page displayed", check all three possible records: `lang:id`, `defaultLang:id`, and `id`.
590
+ - When creating multilingual content, use `lang:id` consistently in the designer save dialog.
591
+ - Keep fallback pages plain or default-language records intentionally; do not create accidental stale fallbacks.
592
+
593
+ ### 6.6 Coded frame/content pages with `cmsRegisterPage()`
594
+
595
+ A frontend app can override or provide a frame/content page with code:
596
+
597
+ ```tsx
598
+ const SampleFrame = async (props: PageProps) => {
599
+ return <div>Frame from code</div>;
600
+ };
601
+
602
+ cmsRegisterPage('pagex', SampleFrame);
603
+ ```
604
+
605
+ When CMS resolves a frame/content ID, registered pages are checked before persisted CMS JSON. This means a coded registration for `pagex` takes priority over a database page with the same ID.
606
+
607
+ Use this when:
608
+
609
+ - a frame should be implemented in code for full control
610
+ - content is dynamic and not suited to page-designer JSON
611
+ - you want a coded fallback/override for a CMS ID
612
+
613
+ Agent rules:
614
+
615
+ - Registered page keys are public render contracts. Changing them can break URLs and saved CMS references.
616
+ - Keep `cmsRegisterPage()` calls in the public frontend entry when they are needed for public `CmsRenderPage` routing.
617
+ - If a CMS page ID is not loading from database, check whether the same ID was registered with `cmsRegisterPage()` and is taking priority.
618
+
619
+ ### 6.7 Saving pages and using `My Components`
620
+
621
+ In the design UI, pages can be saved with language-qualified IDs such as:
622
+
623
+ ```text
624
+ en:home
625
+ cn:home
626
+ home
627
+ ```
628
+
629
+ If the save dialog has `component` checked, the saved content is also treated as a reusable saved component. It becomes available under `My Components` in the designer and can be dragged into other pages.
630
+
631
+ Important distinction:
632
+
633
+ - A normal saved page/content is resolved by URL and ID.
634
+ - A checked component save is additionally discoverable as a reusable design component.
635
+ - Saved components are persisted CMS data, unlike registered code components which are runtime registrations.
636
+
637
+ Agent rules:
638
+
639
+ - Use saved components for reusable page-designer JSON fragments.
640
+ - Use registered code components for reusable coded JSX/TSX components.
641
+ - Do not confuse `My Components` with `Registered Components`.
642
+
643
+ ### 6.8 Coded designer components with `cmsRegisterComponent()`
644
+
645
+ The designer can also expose coded components as draggable design items:
646
+
647
+ ```tsx
648
+ const SampleContent = async (props: PageProps) => {
649
+ return (
650
+ <div class='sample-content-box'>
651
+ This is a sample registered components.
652
+ </div>
653
+ );
654
+ };
655
+
656
+ cmsRegisterComponent('contentx', SampleContent, 'Sample Content');
657
+ ```
658
+
659
+ These appear in the designer under `Registered Components`. Dragging one into a page stores a `block-registered-component` node with only a component key/label. At render time, CMS looks up the real function from the registered component map and renders it.
660
+
661
+ Critical placement rule:
662
+
663
+ - Register designer components in the admin/dashboard entry, such as `apps/cms/web/src/admin_dev/index.tsx`.
664
+ - Do not register designer-only components only in the public frontend entry, such as `apps/cms/web/src/index.tsx`, because the design page does not go through that public route. If registration only happens there, the admin designer will not see the component.
665
+
666
+ Agent rules:
667
+
668
+ - Use `cmsRegisterComponent()` in the admin dashboard bundle for components that must appear in the design sidebar.
669
+ - Use `cmsRegisterPage()` in the public frontend bundle for pages/frames/content that must be resolved during public CMS rendering.
670
+ - If a registered component is visible in public render but missing in the design sidebar, check whether it was registered in the admin dashboard entry.
671
+ - If a saved page contains `block-registered-component` but render says component not found, check whether the same component key was registered in the currently running bundle/process.
672
+
673
+ ### 6.9 Embedding CMS content inside coded components
674
+
675
+ Use `CmsEmbedContent` when coded components need to render CMS-managed content by ID. It accepts an `id` and optional `lang`, and follows the same language priority/fallback logic as CMS public rendering.
676
+
677
+ Use cases:
678
+
679
+ - coded page shells that embed CMS-managed regions
680
+ - coded landing pages with editable CMS sections
681
+ - admin/demo pages that need to preview CMS content by ID
682
+
683
+ Agent rules:
684
+
685
+ - Prefer `CmsEmbedContent` for embedding CMS content inside coded components.
686
+ - Pass `lang` explicitly only when the component must force a language. Otherwise let URL/browser/default language resolution work.
687
+ - Remember backend data fallback still follows `lang:id -> defaultLang:id -> id`.
688
+
689
+ ### 6.10 User-facing CMS troubleshooting checklist
690
+
691
+ When CMS routing/rendering does not behave as expected, check in this order:
692
+
693
+ 1. Does the public app entry call `pageRouter.use('*', CmsRenderPage)`?
694
+ 2. Is the URL parsed as `/lang/frame/content` or `/frame/content`?
695
+ 3. Is the `lang` segment present in `siteLangs`?
696
+ 4. Are `cmsPageDefault` and `cmsContentDefault` configured when frame/content are omitted?
697
+ 5. Does a coded `cmsRegisterPage(id, ...)` override the database page?
698
+ 6. Do the expected records exist as `lang:id`, `defaultLang:id`, or plain `id`?
699
+ 7. Was the page saved with the intended language-qualified ID?
700
+ 8. If using `My Components`, was the saved item checked as component?
701
+ 9. If using `Registered Components`, was `cmsRegisterComponent()` called in the admin/dashboard entry?
702
+ 10. If rendering a registered component, is the same key registered in the current runtime bundle?
703
+
704
+ ## 7. Coding Standards & Gotchas
481
705
 
482
706
  - **`useState` vs `HtmlVar`**: `useState` exists (`import { useState } from 'lupine.components'`) and is elegant for small components. But it rerenders the **entire** component — for large/complex components or high-frequency updates, prefer `HtmlVar` for surgical partial updates. `useEffect`, `useReducer`, `useCallback`, `useContext` **do NOT exist**.
483
707
  - **❌ `className`**: Use standard HTML `class`.
@@ -485,7 +709,7 @@ const Parent = () => {
485
709
  - **✅ Native Events**: `onClick`, `onChange`, `onInput`, `onMouseMove` etc. are standard HTML events and **ARE ALLOWED**. Use them for triggering logic or callbacks (e.g., `onInput={(e) => updateOtherThing(e.target.value)}`).
486
710
  - **✅ Uncontrolled Inputs**: While you _can_ use `onInput` to track state, the default efficient pattern is often to read `ref.$('input').value` only when the user clicks "Save" or "Search".
487
711
 
488
- ## 7. System Icons & Customization
712
+ ## 8. System Icons & Customization
489
713
 
490
714
  Lupine.components uses a set of built-in system icons (like `ma-close` and `mg-arrow_back_ios_new_outlined` found in components like `MobileHeaderWithBack`).
491
715
 
@@ -528,7 +752,7 @@ const css: CssProps = {
528
752
  };
529
753
  ```
530
754
 
531
- ## 8. Cross-Platform App Bootstrapping Guidance
755
+ ## 9. Cross-Platform App Bootstrapping Guidance
532
756
 
533
757
  When creating a new Cross-Platform App using `lupine.js`, follow this standard procedure for scaffolding the entry point, navigation, and icons:
534
758
 
@@ -550,7 +774,7 @@ When creating a new Cross-Platform App using `lupine.js`, follow this standard p
550
774
  5. **Local Storage Patterns**:
551
775
  For pure frontend utility apps (compatible with browsers, Capacitor, and Electron), wrap `localStorage.getItem()` and `localStorage.setItem()` inside dedicated Service singletons (e.g., `LocalNotesService`). Always parse/serialize consistently and assign standard unique IDs (like `Date.now()`) for newly inserted records. Combine this with the `onLoad` pattern inside `RefProps` to fetch data immediately when components render, injecting it directly into an `HtmlVar` wrapping the list.
552
776
 
553
- ## 9. Standard Mobile App Layout & Interactions
777
+ ## 10. Standard Mobile App Layout & Interactions
554
778
 
555
779
  When asked to "create a list page" or "initialize a standard mobile framework", rigorously apply this exact structural pattern based on the cross-platform starter app.
556
780
 
@@ -2,7 +2,6 @@ import {
2
2
  bindRouter,
3
3
  PageRouter,
4
4
  bindTheme,
5
- bindLang,
6
5
  setDefaultPageTitle,
7
6
  isFrontEnd,
8
7
  debugWatch,
@@ -17,8 +16,6 @@ import { baseCss } from './styles/base-css';
17
16
  if (isFrontEnd() && webEnv(ClientEnvKeys.NODE_ENV, '') === 'development') {
18
17
  debugWatch(webEnv(ClientEnvKeys.API_PORT, 0));
19
18
  }
20
-
21
- bindLang('en', {});
22
19
  bindTheme('light', pressThemes);
23
20
  bindAppGlobalStyle('comm-css', baseCss, false, true);
24
21
  setDefaultPageTitle('Lupine.js Doc');
@@ -2,7 +2,6 @@ import {
2
2
  bindRouter,
3
3
  PageRouter,
4
4
  bindTheme,
5
- bindLang,
6
5
  setDefaultPageTitle,
7
6
  isFrontEnd,
8
7
  debugWatch,
@@ -17,8 +16,6 @@ import { markdownConfig } from '../markdown-built/markdown-config';
17
16
  if (isFrontEnd() && webEnv(ClientEnvKeys.NODE_ENV, '') === 'development') {
18
17
  debugWatch(webEnv(ClientEnvKeys.API_PORT, 0));
19
18
  }
20
-
21
- bindLang('en', {});
22
19
  bindTheme('light', pressThemes);
23
20
  bindAppGlobalStyle('comm-css', baseCss, false, true);
24
21
  setDefaultPageTitle('Doc Starter');
@@ -2,7 +2,6 @@ import {
2
2
  bindRouter,
3
3
  PageRouter,
4
4
  bindTheme,
5
- bindLang,
6
5
  setDefaultPageTitle,
7
6
  isFrontEnd,
8
7
  debugWatch,
@@ -17,8 +16,6 @@ import { baseCss } from './styles/base-css';
17
16
  if (isFrontEnd() && webEnv(ClientEnvKeys.NODE_ENV, '') === 'development') {
18
17
  debugWatch(webEnv(ClientEnvKeys.API_PORT, 0));
19
18
  }
20
-
21
- bindLang('en', {});
22
19
  bindTheme('light', pressThemes);
23
20
  bindAppGlobalStyle('comm-css', baseCss, false, true);
24
21
  setDefaultPageTitle('Lupine.js Doc');
@@ -2,7 +2,6 @@ import {
2
2
  bindRouter,
3
3
  PageRouter,
4
4
  bindTheme,
5
- bindLang,
6
5
  setDefaultPageTitle,
7
6
  isFrontEnd,
8
7
  debugWatch,
@@ -17,8 +16,6 @@ import { markdownConfig } from '../markdown-built/markdown-config';
17
16
  if (isFrontEnd() && webEnv(ClientEnvKeys.NODE_ENV, '') === 'development') {
18
17
  debugWatch(webEnv(ClientEnvKeys.API_PORT, 0));
19
18
  }
20
-
21
- bindLang('en', {});
22
19
  bindTheme('light', pressThemes);
23
20
  bindAppGlobalStyle('comm-css', baseCss, false, true);
24
21
  setDefaultPageTitle('Doc Starter');
@@ -9,7 +9,6 @@ import {
9
9
  isFrontEnd,
10
10
  PageRouter,
11
11
  webEnv,
12
- bindLang,
13
12
  bindTheme,
14
13
  setDefaultPageTitle,
15
14
  } from 'lupine.components';
@@ -21,8 +20,6 @@ import { FinancePage } from './pages/finance-page';
21
20
  import { AboutPage } from './pages/about-page';
22
21
  import { MinePage } from './pages/mine-page';
23
22
  import { ToolsPage } from './pages/tools-page';
24
-
25
- bindLang('zh-cn', {});
26
23
  bindTheme('light', themes);
27
24
  bindGlobalStyle('comm-css', baseCss, false, true);
28
25
  setDefaultPageTitle('Lupine.js Note Starter');
@@ -6,7 +6,6 @@ import {
6
6
  bindRouter,
7
7
  PageRouter,
8
8
  bindTheme,
9
- bindLang,
10
9
  setDefaultPageTitle,
11
10
  isFrontEnd,
12
11
  debugWatch,
@@ -21,8 +20,6 @@ import { HomePage } from '../pages/home-page';
21
20
  if (isFrontEnd() && webEnv('NODE_ENV', '') === 'development') {
22
21
  debugWatch(webEnv('API_PORT', 0));
23
22
  }
24
-
25
- bindLang('zh-cn', {});
26
23
  bindTheme('light', themes);
27
24
  bindGlobalStyle('comm-css', baseCss, false, true);
28
25
  setDefaultPageTitle('Lupine Template Responsive Starter');