create-lupine 1.0.22 → 1.0.23

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
@@ -289,8 +289,8 @@ async function init() {
289
289
  dev: 'node ./dev/dev-watch --env=.env.development --dev=1 --cmd=start-dev',
290
290
  build: 'node ./dev/dev-watch --env=.env.production --dev=0 --obfuscate=0',
291
291
  'build-mobile': 'node ./dev/dev-watch --env=.env.mobile --dev=0 --mobile=1',
292
- 'start-dev': 'node dist/server_root/server/app-loader.js --env=.env.development',
293
- 'start-production': 'node dist/server_root/server/app-loader.js --env=.env.production',
292
+ 'start-dev': 'node dist/server_root/server/server-loader.js --env=.env.development',
293
+ 'start-production': 'node dist/server_root/server/server-loader.js --env=.env.production',
294
294
  format: 'prettier --write "**/*.{js,json,css,scss,md,html,yaml,ts,jsx,tsx}"',
295
295
  },
296
296
  dependencies: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lupine",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
4
4
  "description": "Scaffolding tool for Lupine.js projects",
5
5
  "bin": {
6
6
  "create-lupine": "index.js"
@@ -7,7 +7,6 @@ When performing multi-line code refactoring or replacement operations (`replace_
7
7
  - The `StartLine` and `EndLine` range MUST be restricted strictly to the absolute minimum lines you intend to modify or delete.
8
8
  - If you are merely inserting new code (e.g., adding a button or appending logic), target ONLY the immediately preceding line or bracket as your anchor. You are strictly forbidden from wrapping innocent, unmodified surrounding code into the `Replacement` payload. Violating this red line causes severe production accidents!
9
9
 
10
-
11
10
  **SYSTEM ROLE**: You are an expert developer in `lupine.js`, a custom TypeScript full-stack framework.
12
11
 
13
12
  **🛑 CRITICAL WARNINGS 🛑**
@@ -77,23 +76,23 @@ Since Lupine.js uses a CSS-in-JS styling approach, when you need to define or ov
77
76
  // 1. Separate theme variables into their own CSS object
78
77
  const cssTheme: CssProps = {
79
78
  '[data-theme="light" i]': {
80
- '--my-comp-bg-color': '#e6e6e6',
79
+ "--my-comp-bg-color": "#e6e6e6",
81
80
  },
82
81
  '[data-theme="dark" i]': {
83
- '--my-comp-bg-color': 'var(--primary-accent-color)',
82
+ "--my-comp-bg-color": "var(--primary-accent-color)",
84
83
  },
85
84
  };
86
85
  // 2. Bind globally. Param 4 (noTopClassName) MUST be true to prevent injecting a namespace prefix.
87
- bindGlobalStyle('my-comp-theme', cssTheme, false, true);
86
+ bindGlobalStyle("my-comp-theme", cssTheme, false, true);
88
87
 
89
88
  // 3. Use the variable in your standard component styles
90
89
  const css: CssProps = {
91
- '.&-element': {
92
- backgroundColor: 'var(--my-comp-bg-color)',
93
- }
90
+ ".&-element": {
91
+ backgroundColor: "var(--my-comp-bg-color)",
92
+ },
94
93
  };
95
94
  // Bind your component styles normally
96
- bindGlobalStyle('my-comp-main', css);
95
+ bindGlobalStyle("my-comp-main", css);
97
96
  ```
98
97
 
99
98
  #### 🎨 Color Variable Semantics (CRITICAL FOR DARK MODE)
@@ -132,22 +131,22 @@ export const MyComponent = () => {
132
131
  const ref: RefProps = {
133
132
  onLoad: async () => {
134
133
  // 3. Querying namespaced elements
135
- const btn = ref.$('.&-btn');
136
- btn.innerHTML = 'Ready';
134
+ const btn = ref.$(".&-btn");
135
+ btn.innerHTML = "Ready";
137
136
  },
138
137
  };
139
138
 
140
139
  const css: CssProps = {
141
140
  // Top-level rules apply to the root component container itself
142
- width: '100%',
143
- padding: '1rem',
141
+ width: "100%",
142
+ padding: "1rem",
144
143
 
145
144
  // 1. Defining namespaced sub-classes in CSS:
146
- '.&-title': { fontWeight: 'bold' },
147
- '.&-btn': {
145
+ ".&-title": { fontWeight: "bold" },
146
+ ".&-btn": {
148
147
  // Nesting pseudo-classes and combination modifiers (no space after &)
149
- '&:hover': { background: '#f0f0f0' },
150
- '&.active': { color: 'var(--primary-accent-color)' },
148
+ "&:hover": { background: "#f0f0f0" },
149
+ "&.active": { color: "var(--primary-accent-color)" },
151
150
  },
152
151
  };
153
152
 
@@ -155,8 +154,8 @@ export const MyComponent = () => {
155
154
  // Setting css={css} safely bounds this style scope
156
155
  <aside css={css} ref={ref}>
157
156
  {/* 2. Applying namespaced classes in JSX */}
158
- <div class='&-title'>Hello</div>
159
- <button class='&-btn active'>Click Me</button>
157
+ <div class="&-title">Hello</div>
158
+ <button class="&-btn active">Click Me</button>
160
159
  </aside>
161
160
  );
162
161
  };
@@ -182,6 +181,7 @@ Lupine.js provides two main ways to inject component CSS (`css={}` vs `bindGloba
182
181
  **Best for**: Pages, views, or high-level containers that are only rendered once per screen.
183
182
 
184
183
  When you pass `css={css}` to a JSX element, Lupine automatically evaluates it and injects a new `<style>` tag directly wrapping that element.
184
+
185
185
  - **Pros**: Perfect isolation.
186
186
  - **Cons**: If you render 100 items using `css={}`, it will inject 100 identical `<style>` blocks into the DOM, severely bloating the page.
187
187
 
@@ -192,11 +192,15 @@ When you pass `css={css}` to a JSX element, Lupine automatically evaluates it an
192
192
  `bindGlobalStyle`, combined with `getGlobalStylesId`, places the `<style>` block in the `<head>` of the document **exactly once**. All instances of the component share the same CSS class names, but those names are still guaranteed to be collision-free!
193
193
 
194
194
  **How it works seamlessly with `&`**:
195
- 1. Generate an ID based on the `CssProps` content: `const globalCssId = getGlobalStylesId(css);`. (Call this *inside* the component!)
195
+
196
+ 1. Generate an ID based on the `CssProps` content: `const globalCssId = getGlobalStylesId(css);`. (Call this _inside_ the component!)
196
197
  2. Bind the style block globally once: `bindGlobalStyle(globalCssId, css);`
197
198
  3. Assign this ID to the component's `ref` to link the scope: `const ref: RefProps = { globalCssId };` / `<div ref={ref}>`
198
199
  4. Use `class="&-item"` normally. Lupine replaces `&` with the identical `globalCssId` across all instances.
199
200
 
201
+ > [!WARNING]
202
+ > Because `getGlobalStylesId` relies on `getRequestContext()` data to correctly attach and track styles (especially across SSR and interactive client renders), getGlobalStylesId and `bindGlobalStyle` **MUST** be called inside the component function scope. Calling them at the file/module level will result in runtime errors.
203
+
200
204
  ### ⚠️ IMPORTANT: The "Static `CssProps`" Rule
201
205
 
202
206
  Because `bindGlobalStyle` injects your `<style>` tags into the `<head>` globally, your `CssProps` definition **MUST** be entirely static.
@@ -205,42 +209,85 @@ Because `bindGlobalStyle` injects your `<style>` tags into the `<head>` globally
205
209
 
206
210
  ### 🔗 Sharing the same CSS scope (`globalCssId`) among Separated DOMs
207
211
 
208
- If your component divides its logic so that some internal floating DOM elements are rendered dynamically later (e.g. through a function passed to `HtmlVar`) *separated* from the root return statement, the inner DOM will automatically generate a **new, mismatched** CSS ID if not linked. Its internal `class="&-item"` references will break and styles will fail to apply.
212
+ If your component divides its logic so that some internal floating DOM elements are rendered dynamically later (e.g. through a function passed to `HtmlVar`) _separated_ from the root return statement, the inner DOM will automatically generate a **new, mismatched** CSS ID if not linked. Its internal `class="&-item"` references will break and styles will fail to apply.
209
213
 
210
214
  To force separated local DOM partitions to share the exact same `&` CSS Scope as their parent page, explicitly share a globally unique CSS ID using `globalStyleUniqueId()`:
211
215
 
212
216
  ```tsx
213
- import { globalStyleUniqueId, HtmlVar, RefProps, CssProps } from 'lupine.components';
217
+ import {
218
+ globalStyleUniqueId,
219
+ HtmlVar,
220
+ RefProps,
221
+ CssProps,
222
+ } from "lupine.components";
214
223
 
215
224
  export const HomePage = () => {
216
- // 1. Generate a manual ID for the container scope beforehand
217
- const cssId = globalStyleUniqueId();
218
-
219
- const listDom = new HtmlVar('');
220
-
221
- const renderList = () => {
222
- // 2. Explicitly bind the inner detached DOM to the parent's globalCssId
223
- listDom.value = (
224
- <div ref={{ globalCssId: cssId }} class="&-bundle-container">
225
- <div class="&-bundle-name">Basic Bundle</div>
226
- </div>
227
- );
228
- };
229
-
230
- const ref: RefProps = {
231
- globalCssId: cssId, // 3. The parent registers the ID as well
232
- onLoad: async () => renderList()
233
- };
234
- const css: CssProps = { '.&-bundle-name': { color: 'red' } };
225
+ // 1. Generate a manual ID for the container scope beforehand
226
+ const cssId = globalStyleUniqueId();
235
227
 
236
- return (
237
- <div css={css} ref={ref}>
238
- {/* 4. The dynamically injected nodes will properly map their &- prefixes */}
239
- {listDom.node}
240
- </div>
228
+ const listDom = new HtmlVar("");
229
+
230
+ const renderList = () => {
231
+ // 2. Explicitly bind the inner detached DOM to the parent's globalCssId
232
+ listDom.value = (
233
+ <div ref={{ globalCssId: cssId }} class="&-bundle-container">
234
+ <div class="&-bundle-name">Basic Bundle</div>
235
+ </div>
241
236
  );
237
+ };
238
+
239
+ const ref: RefProps = {
240
+ globalCssId: cssId, // 3. The parent registers the ID as well
241
+ onLoad: async () => renderList(),
242
+ };
243
+ const css: CssProps = { ".&-bundle-name": { color: "red" } };
244
+
245
+ return (
246
+ <div css={css} ref={ref}>
247
+ {/* 4. The dynamically injected nodes will properly map their &- prefixes */}
248
+ {listDom.node}
249
+ </div>
250
+ );
251
+ };
252
+ ```
253
+
254
+ ### Using `&` on Top-Level Tags
255
+
256
+ The following example illustrates how to correctly use `&` in the `class` of a top-level tag.
257
+
258
+ Generally, you should not need to use `&` classes on the top-level tag because you can reference the top-level tag directly via `ref.current`. For styling, the first-level styles defined directly under your `CssProps` object are automatically applied to the top-level tag (e.g., `color: 'red'` below).
259
+
260
+ However, when there is a special need to use an `&-` class prefix on the top-level tag, you must be careful: **`"&.&-box"`** is the correct syntax. This is because the standalone `&` selector is replaced by both the explicit `gCssId` and the CSS ID automatically generated for this top-level tag.
261
+
262
+ For instance, if `gCssId="g00"` and the auto-generated CSS ID applied by the `ref` is `"l01"`, then `"&.&-box"` compiles to `"g00.g00-box, l01.l01-box"`.
263
+
264
+ Similarly, a nested selector like `"&.&-box .&-item"` will be compiled into `"g00.g00-box .g00-item, l01.l01-box .l01-item"`.
265
+
266
+ *(Alternatively, if you define the class without the `&-` prefix like `class="box"`, you would target it using `"&.box"`).*
267
+ ```typescript
268
+ export const Component1 = () => {
269
+
270
+ const css: CssProps = {
271
+ color: 'red',
272
+ "&.&-box": { fontWeight: 'bold' },
273
+ "&.&-box .&-item": { backgroundColor: 'blue' },
274
+ };
275
+ const gCssId = getGlobalStylesId(css);
276
+ bindGlobalStyle(gCssId, css);
277
+
278
+ const ref: RefProps = {
279
+ globalCssId: gCssId,
280
+ };
281
+ return (
282
+ <div ref={ref} class='&-box'>
283
+ <div class='&-item'>item</div>
284
+ </div>
285
+ );
242
286
  };
243
287
  ```
288
+
289
+
290
+
244
291
  ---
245
292
 
246
293
  ## 5. Common Patterns ("The Lupine Way")
@@ -272,7 +319,7 @@ const MyPage = () => {
272
319
  // 4. Events
273
320
  const onSearch = async () => {
274
321
  // Read directly from DOM
275
- const query = ref.$('input.&-search').value;
322
+ const query = ref.$("input.&-search").value;
276
323
  // Update logic var
277
324
  pageIndex = 0;
278
325
  // Update UI manually
@@ -287,7 +334,7 @@ const MyPage = () => {
287
334
 
288
335
  return (
289
336
  <div ref={ref}>
290
- <input class='&-search' />
337
+ <input class="&-search" />
291
338
  <button onClick={onSearch}>Go</button>
292
339
  {/* Embed Dynamic Content */}
293
340
  {listDom.node}
@@ -295,22 +342,24 @@ const MyPage = () => {
295
342
  );
296
343
  };
297
344
  ```
345
+
298
346
  ### Page Navigation (`initializePage` vs `<a>`)
299
347
 
300
348
  In the Lupine.js system, all standard `<a>` HTML tags are automatically intercepted. If the link points to an internal route, Lupine safely binds it to `_lupineJs.initializePage(href)` behind the scenes to perform a seamless single-page application (SPA) transition without a full browser reload.
301
349
 
302
- When performing imperative or programmatic routing via JavaScript (e.g. clicking a `<button>` or a `div`), **DO NOT** use `window.location.href = '/path'`, as this forces a harsh full-page reload.
350
+ When performing imperative or programmatic routing via JavaScript (e.g. clicking a `<button>` or a `div`), **DO NOT** use `window.location.href = '/path'`, as this forces a harsh full-page reload.
303
351
 
304
352
  Instead, import and use `initializePage`:
353
+
305
354
  ```typescript
306
- import { initializePage } from 'lupine.web';
355
+ import { initializePage } from "lupine.web";
307
356
 
308
357
  const navigate = () => {
309
- // CORRECT: Seamless SPA transition
310
- initializePage('/play/diff01/1');
311
-
312
- // ERROR / ANTI-PATTERN: Forces full browser reload unless explicitly desired
313
- // window.location.href = '/play/diff01/1';
358
+ // CORRECT: Seamless SPA transition
359
+ initializePage("/play/diff01/1");
360
+
361
+ // ERROR / ANTI-PATTERN: Forces full browser reload unless explicitly desired
362
+ // window.location.href = '/play/diff01/1';
314
363
  };
315
364
  ```
316
365
 
@@ -319,7 +368,11 @@ const navigate = () => {
319
368
  Lupine uses a "Slide-over" model for navigation (Drill-down). To achieve infinite nesting (where a child page can open a grandchild page), each Component level simply needs to define its own `sliderHook` and its own `<SliderFrame>` tag to act as the placeholder for its children.
320
369
 
321
370
  ```typescript
322
- import { SliderFrame, SliderFrameHookProps, HeaderWithBackFrame } from 'lupine.components';
371
+ import {
372
+ SliderFrame,
373
+ SliderFrameHookProps,
374
+ HeaderWithBackFrame,
375
+ } from "lupine.components";
323
376
 
324
377
  // 1. Parent Component (or Level 1)
325
378
  const Parent = () => {
@@ -328,7 +381,9 @@ const Parent = () => {
328
381
 
329
382
  const openDetail = (id) => {
330
383
  // Push new view onto stack
331
- sliderHook.load!(<DetailComponent id={id} parentSliderFrameHook={sliderHook} />);
384
+ sliderHook.load!(
385
+ <DetailComponent id={id} parentSliderFrameHook={sliderHook} />,
386
+ );
332
387
  };
333
388
 
334
389
  return (
@@ -342,17 +397,28 @@ const Parent = () => {
342
397
  };
343
398
 
344
399
  // 2. Child Component (Level 2)
345
- const DetailComponent = (props: { id: number; parentSliderFrameHook: SliderFrameHookProps }) => {
400
+ const DetailComponent = (props: {
401
+ id: number;
402
+ parentSliderFrameHook: SliderFrameHookProps;
403
+ }) => {
346
404
  // Define hook for Level 3
347
405
  const childSliderHook: SliderFrameHookProps = {};
348
406
 
349
407
  const openDeeper = () => {
350
408
  // Load Level 3 component into this component's placeholder
351
- childSliderHook.load!(<DetailComponent id={props.id + 1} parentSliderFrameHook={childSliderHook} />);
409
+ childSliderHook.load!(
410
+ <DetailComponent
411
+ id={props.id + 1}
412
+ parentSliderFrameHook={childSliderHook}
413
+ />,
414
+ );
352
415
  };
353
416
 
354
417
  return (
355
- <HeaderWithBackFrame title='Detail Page' onBack={(e) => props.parentSliderFrameHook.close!(e)}>
418
+ <HeaderWithBackFrame
419
+ title="Detail Page"
420
+ onBack={(e) => props.parentSliderFrameHook.close!(e)}
421
+ >
356
422
  {/* Placeholder for Level 3 */}
357
423
  <SliderFrame hook={childSliderHook} />
358
424
 
@@ -388,7 +454,7 @@ const Parent = () => {
388
454
  const ref: RefProps = {
389
455
  onLoad: async () => {
390
456
  // Safe: Child has rendered and populated the hook
391
- myHook.setValue('Hello');
457
+ myHook.setValue("Hello");
392
458
  },
393
459
  };
394
460
 
@@ -441,14 +507,14 @@ You can import an `.svg` file (if your bundler supports it) or define a raw Data
441
507
 
442
508
  ```typescript
443
509
  // Option A: Using bundler import
444
- import githubIcon from 'github.svg';
510
+ import githubIcon from "github.svg";
445
511
 
446
512
  // Option B: Raw Data URI string
447
513
  const closeSvgData = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M18 6L6 18M6 6l12 12'/%3E%3C/svg%3E`;
448
514
 
449
515
  export const DemoIcons = {
450
516
  github: githubIcon,
451
- 'ma-close': closeSvgData,
517
+ "ma-close": closeSvgData,
452
518
  };
453
519
  ```
454
520
 
@@ -458,9 +524,9 @@ Use the `-webkit-mask-image` and `maskImage` property wrapped in `url()` to appl
458
524
  ```typescript
459
525
  const css: CssProps = {
460
526
  // Target the specific system icon class you wish to override
461
- '.ifc-icon.ma-close': {
462
- '-webkit-mask-image': `url("${DemoIcons['ma-close']}")`,
463
- maskImage: `url("${DemoIcons['ma-close']}")`,
527
+ ".ifc-icon.ma-close": {
528
+ "-webkit-mask-image": `url("${DemoIcons["ma-close"]}")`,
529
+ maskImage: `url("${DemoIcons["ma-close"]}")`,
464
530
  // If needed, specify mask sizing properties:
465
531
  // maskRepeat: 'no-repeat',
466
532
  // maskPosition: 'center',
@@ -555,42 +621,51 @@ For interactive lists, `createDragUtil()` from `lupine.components` handles compl
555
621
  **DO NOT USE browser native `alert()`, `confirm()`, or `prompt()`**. Instead, use the native `ActionSheet` promises from `lupine.components` for a modern, mobile-friendly overlay experience:
556
622
 
557
623
  1. **Option Selection (`ActionSheetSelectPromise`)** (Replaces `confirm()` or complex choices):
624
+
558
625
  ```typescript
559
- import { ActionSheetSelectPromise } from 'lupine.components';
560
-
626
+ import { ActionSheetSelectPromise } from "lupine.components";
627
+
561
628
  const index = await ActionSheetSelectPromise({
562
- title: 'Delete this saved game?', // Optional
563
- options: ['Delete', 'Edit'],
564
- cancelButtonText: 'Cancel',
629
+ title: "Delete this saved game?", // Optional
630
+ options: ["Delete", "Edit"],
631
+ cancelButtonText: "Cancel",
565
632
  });
566
-
567
- if (index === 0) { /* User clicked Delete (Index of options array) */ }
568
- if (index === -1) { /* User clicked Cancel or tapped background */ }
633
+
634
+ if (index === 0) {
635
+ /* User clicked Delete (Index of options array) */
636
+ }
637
+ if (index === -1) {
638
+ /* User clicked Cancel or tapped background */
639
+ }
569
640
  ```
570
641
 
571
642
  2. **Simple Messages (`ActionSheetMessagePromise`)** (Replaces `alert()`):
643
+
572
644
  ```typescript
573
- import { ActionSheetMessagePromise } from 'lupine.components';
574
-
645
+ import { ActionSheetMessagePromise } from "lupine.components";
646
+
575
647
  await ActionSheetMessagePromise({
576
- title: 'Success', // Optional
577
- message: 'Your profile has been saved.',
578
- closeButtonText: 'OK' // Optional, defaults to a close behavior
648
+ title: "Success", // Optional
649
+ message: "Your profile has been saved.",
650
+ closeButtonText: "OK", // Optional, defaults to a close behavior
579
651
  });
580
652
  ```
581
653
 
582
654
  3. **User Input (`ActionSheetInputPromise`)** (Replaces `prompt()`):
655
+
583
656
  ```typescript
584
- import { ActionSheetInputPromise } from 'lupine.components';
585
-
657
+ import { ActionSheetInputPromise } from "lupine.components";
658
+
586
659
  const value = await ActionSheetInputPromise({
587
- title: 'Enter your name',
660
+ title: "Enter your name",
588
661
  // placeholder: 'Player 1', // Optional
589
- confirmButtonText: 'Submit', // Optional
590
- cancelButtonText: 'Cancel' // Optional
662
+ confirmButtonText: "Submit", // Optional
663
+ cancelButtonText: "Cancel", // Optional
591
664
  });
592
-
593
- if (value !== null) { /* User submitted a string */ }
665
+
666
+ if (value !== null) {
667
+ /* User submitted a string */
668
+ }
594
669
  ```
595
670
 
596
671
  4. **Other Available Prompts (Investigate their API via `lupine.components` when needed)**:
@@ -605,14 +680,14 @@ When building mobile interfaces, users expect the physical hardware "Back" butto
605
680
  **The Rule**: Whenever you implement a cancel button, a close icon (`X`), or a back chevron (`<`) in a mobile overlay or frame, you **MUST** attach the `data-back-action` attribute using the `backActionHelper`.
606
681
 
607
682
  ```typescript
608
- import { backActionHelper } from 'lupine.components';
683
+ import { backActionHelper } from "lupine.components";
609
684
 
610
685
  export const MyCloseButton = ({ onClose }) => {
611
686
  return (
612
- <i
687
+ <i
613
688
  class="ifc-icon ma-close"
614
689
  // Generate a unique ID for the back stack
615
- data-back-action={backActionHelper.genBackActionId()}
690
+ data-back-action={backActionHelper.genBackActionId()}
616
691
  onClick={onClose}
617
692
  ></i>
618
693
  );
@@ -620,8 +695,9 @@ export const MyCloseButton = ({ onClose }) => {
620
695
  ```
621
696
 
622
697
  **How it works**:
698
+
623
699
  - When the hardware back button is pressed, the underlying system automatically queries the DOM for all elements with `[data-back-action^="bb-"]`.
624
700
  - It finds the most recently created component (the top-most overlay) and automatically triggers a `.click()` event on it.
625
- - **Dynamic Mounting vs Static**:
701
+ - **Dynamic Mounting vs Static**:
626
702
  - For components that are injected and removed dynamically (like `<ActionSheet />` or `<FloatWindow />`), simply attaching the property to the React/JSX node is sufficient.
627
703
  - For static components that always remain in the DOM but toggle visibility (like an off-canvas sidebar), you must dynamically add/remove the attribute in Javascript (`el.setAttribute` / `el.removeAttribute`) to prevent the back button from intercepting events when the menu is actually closed.
@@ -1,4 +1,4 @@
1
- import { appLoader } from 'lupine.api/app-loader';
1
+ import { serverLoader } from 'lupine.api/server-loader';
2
2
  import path from 'path';
3
3
 
4
- appLoader.startApp(path.join(__dirname, './index.js'));
4
+ serverLoader.startApp(path.join(__dirname, './index.js'));
@@ -1,81 +1,76 @@
1
- // initApp should be called before any other logics, so need to avoid `export default new Class()`
2
- import * as path from 'path';
3
- import {
4
- CryptoUtils,
5
- HostToPathProps,
6
- appStart,
7
- bindRenderPageFunctions,
8
- getDefaultDbConfig,
9
- getRenderPageFunctions,
10
- loadEnv,
11
- setAccessControlAllowHost,
12
- } from 'lupine.api';
13
- import { fetchData } from './fetch-data';
14
- import { ServerEnvKeys } from './server-env-keys';
15
-
16
- const initAndStartServer = async () => {
17
- setAccessControlAllowHost(['localhost', '127.0.0.1']);
18
-
19
- const envFile = process.argv.find((i) => i.startsWith('--env='))?.substring(6) || '.env';
20
- // it can use "#!import file_name" to import another env file
21
- await loadEnv(envFile);
22
- bindRenderPageFunctions({ fetchData });
23
-
24
- const dbConfig = { ...getDefaultDbConfig() };
25
- const serverRootPath = path.resolve(process.env[ServerEnvKeys.SERVER_ROOT_PATH]!);
26
- const apps = (process.env[ServerEnvKeys.APPS] || '').split(',');
27
- const webRootMap: HostToPathProps[] = [];
28
-
29
- const domainCerts: Record<string, { key: string; cert: string }> = {};
30
- for (const app of apps) {
31
- const appHosts = process.env[`${ServerEnvKeys.DOMAINS}:${app}`] || '';
32
- const dbFilename =
33
- process.env[`${ServerEnvKeys.DB_FILENAME}:${app}`] || process.env[`${ServerEnvKeys.DB_FILENAME}`] || 'sqlite3.db';
34
- webRootMap.push({
35
- appName: app,
36
- hosts: appHosts ? appHosts.split(',') : [],
37
- // web, data, api folders should be created in building process
38
- webPath: path.join(serverRootPath, app + '_web'),
39
- dataPath: path.join(serverRootPath, app + '_data'),
40
- apiPath: path.join(serverRootPath, app + '_api'),
41
- dbType: process.env[`${ServerEnvKeys.DB_TYPE}:${app}`] || process.env[`${ServerEnvKeys.DB_TYPE}`] || 'sqlite',
42
- dbConfig: { ...dbConfig, filename: dbFilename },
43
- });
44
-
45
- const appDomains = appHosts.split(',');
46
- for (const domain of appDomains) {
47
- domainCerts[domain] = {
48
- key: process.env[`${ServerEnvKeys.SSL_KEY_PATH}:${app}`] || '',
49
- cert: process.env[`${ServerEnvKeys.SSL_CRT_PATH}:${app}`] || '',
50
- };
51
- }
52
- }
53
-
54
- const bindIp = process.env[ServerEnvKeys.BIND_IP] || '::';
55
- // 0 to disable http/https server
56
- const httpPort = Number.parseInt(process.env[ServerEnvKeys.HTTP_PORT] || '8080');
57
- const httpsPort = Number.parseInt(process.env[ServerEnvKeys.HTTPS_PORT] || '8443');
58
- const sslKeyPath = process.env[ServerEnvKeys.SSL_KEY_PATH] || '';
59
- const sslCrtPath = process.env[ServerEnvKeys.SSL_CRT_PATH] || '';
60
-
61
- // Can't use log until initApp is called (after AppStart.start)
62
- await appStart.start({
63
- debug: process.env[ServerEnvKeys.NODE_ENV] === 'development',
64
- devToken: CryptoUtils.sha256(process.env['DEV_TOKEN'] || ''),
65
- appEnvFile: envFile,
66
- renderPageFunctions: getRenderPageFunctions(),
67
- apiConfig: {
68
- serverRoot: `${serverRootPath}`,
69
- webHostMap: webRootMap,
70
- },
71
- serverConfig: {
72
- bindIp,
73
- httpPort,
74
- httpsPort,
75
- sslKeyPath,
76
- sslCrtPath,
77
- domainCerts,
78
- },
79
- });
80
- };
81
- initAndStartServer();
1
+ // initApp should be called before any other logics, so need to avoid `export default new Class()`
2
+ import * as path from 'path';
3
+ import {
4
+ CryptoUtils,
5
+ HostToPathProps,
6
+ appStart,
7
+ getDefaultDbConfig,
8
+ loadEnv,
9
+ setAccessControlAllowHost,
10
+ } from 'lupine.api/server';
11
+ import { ServerEnvKeys } from './server-env-keys';
12
+
13
+ const initAndStartServer = async () => {
14
+ setAccessControlAllowHost(['localhost', '127.0.0.1']);
15
+
16
+ const envFile = process.argv.find((i) => i.startsWith('--env='))?.substring(6) || '.env';
17
+ // it can use "#!import file_name" to import another env file
18
+ await loadEnv(envFile);
19
+
20
+ const dbConfig = { ...getDefaultDbConfig() };
21
+ const serverRootPath = path.resolve(process.env[ServerEnvKeys.SERVER_ROOT_PATH]!);
22
+ const apps = (process.env[ServerEnvKeys.APPS] || '').split(',');
23
+ const webRootMap: HostToPathProps[] = [];
24
+
25
+ const domainCerts: Record<string, { key: string; cert: string }> = {};
26
+ for (const app of apps) {
27
+ const appHosts = process.env[`${ServerEnvKeys.DOMAINS}:${app}`] || '';
28
+ const dbFilename =
29
+ process.env[`${ServerEnvKeys.DB_FILENAME}:${app}`] || process.env[`${ServerEnvKeys.DB_FILENAME}`] || 'sqlite3.db';
30
+ webRootMap.push({
31
+ appName: app,
32
+ hosts: appHosts ? appHosts.split(',') : [],
33
+ // web, data, api folders should be created in building process
34
+ webPath: path.join(serverRootPath, app + '_web'),
35
+ dataPath: path.join(serverRootPath, app + '_data'),
36
+ apiPath: path.join(serverRootPath, app + '_api'),
37
+ dbType: process.env[`${ServerEnvKeys.DB_TYPE}:${app}`] || process.env[`${ServerEnvKeys.DB_TYPE}`] || 'sqlite',
38
+ dbConfig: { ...dbConfig, filename: dbFilename },
39
+ });
40
+
41
+ const appDomains = appHosts.split(',');
42
+ for (const domain of appDomains) {
43
+ domainCerts[domain] = {
44
+ key: process.env[`${ServerEnvKeys.SSL_KEY_PATH}:${app}`] || '',
45
+ cert: process.env[`${ServerEnvKeys.SSL_CRT_PATH}:${app}`] || '',
46
+ };
47
+ }
48
+ }
49
+
50
+ const bindIp = process.env[ServerEnvKeys.BIND_IP] || '::';
51
+ // 0 to disable http/https server
52
+ const httpPort = Number.parseInt(process.env[ServerEnvKeys.HTTP_PORT] || '8080');
53
+ const httpsPort = Number.parseInt(process.env[ServerEnvKeys.HTTPS_PORT] || '8443');
54
+ const sslKeyPath = process.env[ServerEnvKeys.SSL_KEY_PATH] || '';
55
+ const sslCrtPath = process.env[ServerEnvKeys.SSL_CRT_PATH] || '';
56
+
57
+ // Can't use log until initApp is called (after AppStart.start)
58
+ await appStart.start({
59
+ debug: process.env[ServerEnvKeys.NODE_ENV] === 'development',
60
+ devToken: CryptoUtils.sha256(process.env['DEV_TOKEN'] || ''),
61
+ appEnvFile: envFile,
62
+ apiConfig: {
63
+ serverRoot: `${serverRootPath}`,
64
+ webHostMap: webRootMap,
65
+ },
66
+ serverConfig: {
67
+ bindIp,
68
+ httpPort,
69
+ httpsPort,
70
+ sslKeyPath,
71
+ sslCrtPath,
72
+ domainCerts,
73
+ },
74
+ });
75
+ };
76
+ initAndStartServer();
@@ -0,0 +1,4 @@
1
+ import { serverLoader } from 'lupine.api/server-loader';
2
+ import path from 'path';
3
+
4
+ serverLoader.startApp(path.join(__dirname, './index.js'));
@@ -66,14 +66,14 @@ const watchServerPlugin = (isDev, npmCmd, httpPort) => {
66
66
  };
67
67
 
68
68
  // watch server code changes
69
- const watchAppLoader = async (isDev, npmCmd, httpPort, serverRootPath) => {
69
+ const watchServerLoader = async (isDev, npmCmd, httpPort, serverRootPath) => {
70
70
  const cmd = isDev ? esbuild.context : esbuild.build;
71
71
  const ctx = await cmd({
72
- entryPoints: ['apps/server/src/app-loader.ts'],
72
+ entryPoints: ['apps/server/src/server-loader.ts'],
73
73
  // outdir: path.join(serverRootPath, 'server'),
74
- outfile: path.join(serverRootPath, 'server', 'app-loader.js'),
74
+ outfile: path.join(serverRootPath, 'server', 'server-loader.js'),
75
75
  platform: 'node',
76
- sourcemap: !!isDev,
76
+ sourcemap: isDev ? 'inline' : false, // inline
77
77
  format: 'cjs',
78
78
  bundle: true,
79
79
  treeShaking: true,
@@ -93,7 +93,7 @@ const watchServer = async (isDev, npmCmd, httpPort, serverRootPath) => {
93
93
  entryPoints: ['apps/server/src/index.ts'],
94
94
  outdir: path.join(serverRootPath, 'server'),
95
95
  platform: 'node',
96
- sourcemap: !!isDev,
96
+ sourcemap: isDev ? 'inline' : false, // inline
97
97
  format: 'cjs',
98
98
  bundle: true,
99
99
  treeShaking: true,
@@ -167,7 +167,7 @@ const watchClient = async (saved, isDev, entryPoints, outbase) => {
167
167
  outbase,
168
168
  // entryNames: '[name]-[hash]',
169
169
  platform: 'browser',
170
- sourcemap: !!isDev, // inline
170
+ sourcemap: isDev ? 'inline' : false, // inline
171
171
  format: 'iife',
172
172
  bundle: true,
173
173
  treeShaking: true,
@@ -209,7 +209,7 @@ const watchApi = async (saved, isDev, entryPoints) => {
209
209
  outdir: saved.outdirApi,
210
210
  // outbase,
211
211
  platform: 'node',
212
- sourcemap: !!isDev, // inline
212
+ sourcemap: isDev ? 'inline' : false, // inline
213
213
  format: 'cjs', // iife, cjs
214
214
  bundle: true,
215
215
  treeShaking: true,
@@ -366,6 +366,6 @@ const start = async () => {
366
366
  }
367
367
 
368
368
  watchServer(isDev, npmCmd, httpPort, serverRootPath);
369
- watchAppLoader(isDev, npmCmd, httpPort, serverRootPath);
369
+ watchServerLoader(isDev, npmCmd, httpPort, serverRootPath);
370
370
  };
371
371
  start();
@@ -1,20 +0,0 @@
1
- import { JsonObject } from 'lupine.api';
2
-
3
- export const fetchData = async (urlWithoutHost: string, postData: string | JsonObject) => {
4
- const url = process.env['API_BASE_URL'] + urlWithoutHost;
5
- console.log('========fetchData', url);
6
-
7
- const option = {
8
- method: postData ? 'POST' : 'GET',
9
- body: postData ? (typeof postData === 'string' ? postData : JSON.stringify(postData)) : undefined,
10
- };
11
- const data = await fetch(url, option);
12
- // const json = await data.json();
13
- const text = await data.text();
14
- try {
15
- const json = JSON.parse(text);
16
- return { json };
17
- } catch (e) {
18
- return { text };
19
- }
20
- };