create-lupine 1.0.22 → 1.0.24
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 +18 -12
- package/package.json +1 -1
- package/templates/common/AI_CONTEXT.md +101 -49
- package/templates/common/apps/server/src/app-loader.ts +2 -2
- package/templates/common/apps/server/src/index.ts +76 -81
- package/templates/common/apps/server/src/server-loader.ts +4 -0
- package/templates/common/dev/dev-watch.js +8 -8
- package/templates/common/apps/server/src/fetch-data.ts +0 -20
package/index.js
CHANGED
|
@@ -6,17 +6,23 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import crypto from 'node:crypto';
|
|
7
7
|
import { execSync } from 'node:child_process';
|
|
8
8
|
|
|
9
|
-
function getLatestVersion(pkgName, fallback) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
function getLatestVersion(pkgName, fallback, retries = 2) {
|
|
10
|
+
for (let i = 0; i <= retries; i++) {
|
|
11
|
+
try {
|
|
12
|
+
const version = execSync(`npm view ${pkgName} version`, {
|
|
13
|
+
encoding: 'utf8',
|
|
14
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
15
|
+
timeout: 5000,
|
|
16
|
+
}).trim();
|
|
17
|
+
return version ? `^${version}` : fallback;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
if (i === retries) {
|
|
20
|
+
console.warn(`\x1b[33mWarning: Failed to fetch latest version for ${pkgName}. Using fallback ${fallback}\x1b[0m`);
|
|
21
|
+
return fallback;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
19
24
|
}
|
|
25
|
+
return fallback;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
function generateRandomString(length) {
|
|
@@ -289,8 +295,8 @@ async function init() {
|
|
|
289
295
|
dev: 'node ./dev/dev-watch --env=.env.development --dev=1 --cmd=start-dev',
|
|
290
296
|
build: 'node ./dev/dev-watch --env=.env.production --dev=0 --obfuscate=0',
|
|
291
297
|
'build-mobile': 'node ./dev/dev-watch --env=.env.mobile --dev=0 --mobile=1',
|
|
292
|
-
'start-dev': 'node dist/server_root/server/
|
|
293
|
-
'start-production': 'node dist/server_root/server/
|
|
298
|
+
'start-dev': 'node dist/server_root/server/server-loader.js --env=.env.development',
|
|
299
|
+
'start-production': 'node dist/server_root/server/server-loader.js --env=.env.production',
|
|
294
300
|
format: 'prettier --write "**/*.{js,json,css,scss,md,html,yaml,ts,jsx,tsx}"',
|
|
295
301
|
},
|
|
296
302
|
dependencies: {
|
package/package.json
CHANGED
|
@@ -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 🛑**
|
|
@@ -90,7 +89,7 @@ bindGlobalStyle('my-comp-theme', cssTheme, false, true);
|
|
|
90
89
|
const css: CssProps = {
|
|
91
90
|
'.&-element': {
|
|
92
91
|
backgroundColor: 'var(--my-comp-bg-color)',
|
|
93
|
-
}
|
|
92
|
+
},
|
|
94
93
|
};
|
|
95
94
|
// Bind your component styles normally
|
|
96
95
|
bindGlobalStyle('my-comp-main', css);
|
|
@@ -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
|
-
|
|
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,7 +209,7 @@ 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`)
|
|
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
|
|
|
@@ -213,34 +217,70 @@ To force separated local DOM partitions to share the exact same `&` CSS Scope as
|
|
|
213
217
|
import { globalStyleUniqueId, HtmlVar, RefProps, CssProps } from 'lupine.components';
|
|
214
218
|
|
|
215
219
|
export const HomePage = () => {
|
|
216
|
-
|
|
217
|
-
|
|
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' } };
|
|
220
|
+
// 1. Generate a manual ID for the container scope beforehand
|
|
221
|
+
const cssId = globalStyleUniqueId();
|
|
235
222
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
223
|
+
const listDom = new HtmlVar('');
|
|
224
|
+
|
|
225
|
+
const renderList = () => {
|
|
226
|
+
// 2. Explicitly bind the inner detached DOM to the parent's globalCssId
|
|
227
|
+
listDom.value = (
|
|
228
|
+
<div ref={{ globalCssId: cssId }} class='&-bundle-container'>
|
|
229
|
+
<div class='&-bundle-name'>Basic Bundle</div>
|
|
230
|
+
</div>
|
|
241
231
|
);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const ref: RefProps = {
|
|
235
|
+
globalCssId: cssId, // 3. The parent registers the ID as well
|
|
236
|
+
onLoad: async () => renderList(),
|
|
237
|
+
};
|
|
238
|
+
const css: CssProps = { '.&-bundle-name': { color: 'red' } };
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<div css={css} ref={ref}>
|
|
242
|
+
{/* 4. The dynamically injected nodes will properly map their &- prefixes */}
|
|
243
|
+
{listDom.node}
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
};
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Using `&` on Top-Level Tags
|
|
250
|
+
|
|
251
|
+
The following example illustrates how to correctly use `&` in the `class` of a top-level tag.
|
|
252
|
+
|
|
253
|
+
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).
|
|
254
|
+
|
|
255
|
+
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.
|
|
256
|
+
|
|
257
|
+
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"`.
|
|
258
|
+
|
|
259
|
+
Similarly, a nested selector like `"&.&-box .&-item"` will be compiled into `"g00.g00-box .g00-item, l01.l01-box .l01-item"`.
|
|
260
|
+
|
|
261
|
+
_(Alternatively, if you define the class without the `&-` prefix like `class="box"`, you would target it using `"&.box"`)._
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
export const Component1 = () => {
|
|
265
|
+
const css: CssProps = {
|
|
266
|
+
color: 'red',
|
|
267
|
+
'&.&-box': { fontWeight: 'bold' },
|
|
268
|
+
'&.&-box .&-item': { backgroundColor: 'blue' },
|
|
269
|
+
};
|
|
270
|
+
const gCssId = getGlobalStylesId(css);
|
|
271
|
+
bindGlobalStyle(gCssId, css);
|
|
272
|
+
|
|
273
|
+
const ref: RefProps = {
|
|
274
|
+
globalCssId: gCssId,
|
|
275
|
+
};
|
|
276
|
+
return (
|
|
277
|
+
<div ref={ref} class='&-box'>
|
|
278
|
+
<div class='&-item'>item</div>
|
|
279
|
+
</div>
|
|
280
|
+
);
|
|
242
281
|
};
|
|
243
282
|
```
|
|
283
|
+
|
|
244
284
|
---
|
|
245
285
|
|
|
246
286
|
## 5. Common Patterns ("The Lupine Way")
|
|
@@ -295,22 +335,24 @@ const MyPage = () => {
|
|
|
295
335
|
);
|
|
296
336
|
};
|
|
297
337
|
```
|
|
338
|
+
|
|
298
339
|
### Page Navigation (`initializePage` vs `<a>`)
|
|
299
340
|
|
|
300
341
|
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
342
|
|
|
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.
|
|
343
|
+
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
344
|
|
|
304
345
|
Instead, import and use `initializePage`:
|
|
346
|
+
|
|
305
347
|
```typescript
|
|
306
348
|
import { initializePage } from 'lupine.web';
|
|
307
349
|
|
|
308
350
|
const navigate = () => {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
351
|
+
// CORRECT: Seamless SPA transition
|
|
352
|
+
initializePage('/play/diff01/1');
|
|
353
|
+
|
|
354
|
+
// ERROR / ANTI-PATTERN: Forces full browser reload unless explicitly desired
|
|
355
|
+
// window.location.href = '/play/diff01/1';
|
|
314
356
|
};
|
|
315
357
|
```
|
|
316
358
|
|
|
@@ -555,42 +597,51 @@ For interactive lists, `createDragUtil()` from `lupine.components` handles compl
|
|
|
555
597
|
**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
598
|
|
|
557
599
|
1. **Option Selection (`ActionSheetSelectPromise`)** (Replaces `confirm()` or complex choices):
|
|
600
|
+
|
|
558
601
|
```typescript
|
|
559
602
|
import { ActionSheetSelectPromise } from 'lupine.components';
|
|
560
|
-
|
|
603
|
+
|
|
561
604
|
const index = await ActionSheetSelectPromise({
|
|
562
605
|
title: 'Delete this saved game?', // Optional
|
|
563
606
|
options: ['Delete', 'Edit'],
|
|
564
607
|
cancelButtonText: 'Cancel',
|
|
565
608
|
});
|
|
566
|
-
|
|
567
|
-
if (index === 0) {
|
|
568
|
-
|
|
609
|
+
|
|
610
|
+
if (index === 0) {
|
|
611
|
+
/* User clicked Delete (Index of options array) */
|
|
612
|
+
}
|
|
613
|
+
if (index === -1) {
|
|
614
|
+
/* User clicked Cancel or tapped background */
|
|
615
|
+
}
|
|
569
616
|
```
|
|
570
617
|
|
|
571
618
|
2. **Simple Messages (`ActionSheetMessagePromise`)** (Replaces `alert()`):
|
|
619
|
+
|
|
572
620
|
```typescript
|
|
573
621
|
import { ActionSheetMessagePromise } from 'lupine.components';
|
|
574
|
-
|
|
622
|
+
|
|
575
623
|
await ActionSheetMessagePromise({
|
|
576
|
-
title: 'Success',
|
|
624
|
+
title: 'Success', // Optional
|
|
577
625
|
message: 'Your profile has been saved.',
|
|
578
|
-
closeButtonText: 'OK'
|
|
626
|
+
closeButtonText: 'OK', // Optional, defaults to a close behavior
|
|
579
627
|
});
|
|
580
628
|
```
|
|
581
629
|
|
|
582
630
|
3. **User Input (`ActionSheetInputPromise`)** (Replaces `prompt()`):
|
|
631
|
+
|
|
583
632
|
```typescript
|
|
584
633
|
import { ActionSheetInputPromise } from 'lupine.components';
|
|
585
|
-
|
|
634
|
+
|
|
586
635
|
const value = await ActionSheetInputPromise({
|
|
587
636
|
title: 'Enter your name',
|
|
588
637
|
// placeholder: 'Player 1', // Optional
|
|
589
638
|
confirmButtonText: 'Submit', // Optional
|
|
590
|
-
cancelButtonText: 'Cancel'
|
|
639
|
+
cancelButtonText: 'Cancel', // Optional
|
|
591
640
|
});
|
|
592
|
-
|
|
593
|
-
if (value !== null) {
|
|
641
|
+
|
|
642
|
+
if (value !== null) {
|
|
643
|
+
/* User submitted a string */
|
|
644
|
+
}
|
|
594
645
|
```
|
|
595
646
|
|
|
596
647
|
4. **Other Available Prompts (Investigate their API via `lupine.components` when needed)**:
|
|
@@ -609,10 +660,10 @@ import { backActionHelper } from 'lupine.components';
|
|
|
609
660
|
|
|
610
661
|
export const MyCloseButton = ({ onClose }) => {
|
|
611
662
|
return (
|
|
612
|
-
<i
|
|
613
|
-
class=
|
|
663
|
+
<i
|
|
664
|
+
class='ifc-icon ma-close'
|
|
614
665
|
// Generate a unique ID for the back stack
|
|
615
|
-
data-back-action={backActionHelper.genBackActionId()}
|
|
666
|
+
data-back-action={backActionHelper.genBackActionId()}
|
|
616
667
|
onClick={onClose}
|
|
617
668
|
></i>
|
|
618
669
|
);
|
|
@@ -620,8 +671,9 @@ export const MyCloseButton = ({ onClose }) => {
|
|
|
620
671
|
```
|
|
621
672
|
|
|
622
673
|
**How it works**:
|
|
674
|
+
|
|
623
675
|
- When the hardware back button is pressed, the underlying system automatically queries the DOM for all elements with `[data-back-action^="bb-"]`.
|
|
624
676
|
- It finds the most recently created component (the top-most overlay) and automatically triggers a `.click()` event on it.
|
|
625
|
-
- **Dynamic Mounting vs Static**:
|
|
677
|
+
- **Dynamic Mounting vs Static**:
|
|
626
678
|
- For components that are injected and removed dynamically (like `<ActionSheet />` or `<FloatWindow />`), simply attaching the property to the React/JSX node is sufficient.
|
|
627
679
|
- 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,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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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();
|
|
@@ -66,14 +66,14 @@ const watchServerPlugin = (isDev, npmCmd, httpPort) => {
|
|
|
66
66
|
};
|
|
67
67
|
|
|
68
68
|
// watch server code changes
|
|
69
|
-
const
|
|
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/
|
|
72
|
+
entryPoints: ['apps/server/src/server-loader.ts'],
|
|
73
73
|
// outdir: path.join(serverRootPath, 'server'),
|
|
74
|
-
outfile: path.join(serverRootPath, 'server', '
|
|
74
|
+
outfile: path.join(serverRootPath, 'server', 'server-loader.js'),
|
|
75
75
|
platform: 'node',
|
|
76
|
-
sourcemap:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
};
|