create-lupine 1.0.0

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.
Files changed (62) hide show
  1. package/index.js +277 -0
  2. package/package.json +18 -0
  3. package/templates/common/.env +57 -0
  4. package/templates/common/.env.development +7 -0
  5. package/templates/common/.env.mobile +10 -0
  6. package/templates/common/.env.production +7 -0
  7. package/templates/common/apps/server/src/app-loader.ts +41 -0
  8. package/templates/common/apps/server/src/fetch-data.ts +20 -0
  9. package/templates/common/apps/server/src/index.ts +66 -0
  10. package/templates/common/apps/server/src/server-env-keys.ts +22 -0
  11. package/templates/common/dev/dev-watch.js +422 -0
  12. package/templates/doc-starter/api/resources/config_default.json +6 -0
  13. package/templates/doc-starter/api/resources/install.sqlite.sql +4 -0
  14. package/templates/doc-starter/api/src/index.ts +15 -0
  15. package/templates/doc-starter/api/src/resources/config_default.json +6 -0
  16. package/templates/doc-starter/api/src/resources/install.sqlite.sql +4 -0
  17. package/templates/doc-starter/lupine.json +33 -0
  18. package/templates/doc-starter/package.json +13 -0
  19. package/templates/doc-starter/web/assets/android-chrome-192x192.png +0 -0
  20. package/templates/doc-starter/web/assets/apple-touch-icon.png +0 -0
  21. package/templates/doc-starter/web/assets/favicon-16x16.png +0 -0
  22. package/templates/doc-starter/web/assets/favicon-32x32.png +0 -0
  23. package/templates/doc-starter/web/assets/favicon.ico +0 -0
  24. package/templates/doc-starter/web/assets/site.webmanifest +14 -0
  25. package/templates/doc-starter/web/github-pj-name/index.html +21 -0
  26. package/templates/doc-starter/web/github-pj-name/index.tsx +35 -0
  27. package/templates/doc-starter/web/markdown/en/essentials/index.md +6 -0
  28. package/templates/doc-starter/web/markdown/en/essentials/list.md +18 -0
  29. package/templates/doc-starter/web/markdown/en/guide/install.md +18 -0
  30. package/templates/doc-starter/web/markdown/en/guide/started.md +22 -0
  31. package/templates/doc-starter/web/markdown/en/index.md +42 -0
  32. package/templates/doc-starter/web/markdown/index.md +7 -0
  33. package/templates/doc-starter/web/markdown/zh/essentials/index.md +6 -0
  34. package/templates/doc-starter/web/markdown/zh/essentials/list.md +18 -0
  35. package/templates/doc-starter/web/markdown/zh/guide/install.md +18 -0
  36. package/templates/doc-starter/web/markdown/zh/guide/started.md +22 -0
  37. package/templates/doc-starter/web/markdown/zh/index.md +42 -0
  38. package/templates/doc-starter/web/src/client-env-keys.ts +5 -0
  39. package/templates/doc-starter/web/src/index.html +21 -0
  40. package/templates/doc-starter/web/src/index.tsx +33 -0
  41. package/templates/doc-starter/web/src/markdown-built/en/essentials/index.html +0 -0
  42. package/templates/doc-starter/web/src/markdown-built/en/essentials/list.html +8 -0
  43. package/templates/doc-starter/web/src/markdown-built/en/guide/install.html +8 -0
  44. package/templates/doc-starter/web/src/markdown-built/en/guide/started.html +12 -0
  45. package/templates/doc-starter/web/src/markdown-built/en/index.html +0 -0
  46. package/templates/doc-starter/web/src/markdown-built/index.html +0 -0
  47. package/templates/doc-starter/web/src/markdown-built/markdown-config.ts +25 -0
  48. package/templates/doc-starter/web/src/markdown-built/zh/essentials/index.html +0 -0
  49. package/templates/doc-starter/web/src/markdown-built/zh/essentials/list.html +8 -0
  50. package/templates/doc-starter/web/src/markdown-built/zh/guide/install.html +8 -0
  51. package/templates/doc-starter/web/src/markdown-built/zh/guide/started.html +12 -0
  52. package/templates/doc-starter/web/src/markdown-built/zh/index.html +0 -0
  53. package/templates/doc-starter/web/src/styles/base-css.ts +15 -0
  54. package/templates/hello-world/api/resources/config_default.json +6 -0
  55. package/templates/hello-world/api/resources/install.sqlite.sql +4 -0
  56. package/templates/hello-world/api/src/index.ts +4 -0
  57. package/templates/hello-world/api/src/service/root-api.ts +18 -0
  58. package/templates/hello-world/lupine.json +23 -0
  59. package/templates/hello-world/web/assets/favicon.ico +0 -0
  60. package/templates/hello-world/web/package.json +6 -0
  61. package/templates/hello-world/web/src/index.html +16 -0
  62. package/templates/hello-world/web/src/index.tsx +30 -0
package/index.js ADDED
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import crypto from 'node:crypto';
7
+
8
+ function generateRandomString(length) {
9
+ const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_!@&?-#$';
10
+ return [...crypto.randomBytes(length)].map((x) => chars[x % chars.length]).join('');
11
+ }
12
+
13
+ const red = (str) => `\x1b[31m${str}\x1b[0m`;
14
+ const green = (str) => `\x1b[32m${str}\x1b[0m`;
15
+ const bold = (str) => `\x1b[1m${str}\x1b[0m`;
16
+
17
+ const argv = process.argv.slice(2).reduce(
18
+ (acc, arg) => {
19
+ if (arg.startsWith('--template=')) {
20
+ acc.template = arg.split('=')[1];
21
+ } else if (arg.startsWith('--')) {
22
+ acc[arg.slice(2)] = true;
23
+ } else {
24
+ acc._.push(arg);
25
+ }
26
+ return acc;
27
+ },
28
+ { _: [] }
29
+ );
30
+ // Handle -t alias manually if needed, or just support --template
31
+ // The original code supported `argv.t`
32
+ const args = process.argv.slice(2);
33
+ for (let i = 0; i < args.length; i++) {
34
+ if (args[i] === '-t' && args[i + 1]) {
35
+ argv.template = args[i + 1];
36
+ }
37
+ }
38
+ const cwd = process.cwd();
39
+
40
+ const TEMPLATES = [
41
+ {
42
+ name: 'hello-world',
43
+ display: 'Hello World',
44
+ itemType: 'frontend',
45
+ color: green,
46
+ },
47
+ {
48
+ name: 'doc-starter',
49
+ display: 'Documentation Starter',
50
+ itemType: 'frontend',
51
+ color: green,
52
+ needsPress: true,
53
+ },
54
+ ];
55
+
56
+ const renameFiles = {
57
+ _gitignore: '.gitignore',
58
+ };
59
+
60
+ async function init() {
61
+ let targetDir = argv._[0];
62
+ let template = argv.template || argv.t;
63
+
64
+ const defaultTargetDir = 'lupine-project';
65
+
66
+ function isValidPackageName(projectName) {
67
+ return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(projectName);
68
+ }
69
+
70
+ function toValidPackageName(projectName) {
71
+ return projectName
72
+ .trim()
73
+ .toLowerCase()
74
+ .replace(/\s+/g, '-')
75
+ .replace(/^[._]/, '')
76
+ .replace(/[^a-z0-9-~]+/g, '-');
77
+ }
78
+
79
+ if (!targetDir) {
80
+ targetDir = (await prompt(`Project name: (${green(defaultTargetDir)}) `)) || defaultTargetDir;
81
+ }
82
+
83
+ while (!isValidPackageName(targetDir)) {
84
+ console.log(red(`✖ Invalid project name: "${targetDir}". Name can not contain spaces or sensitive characters.`));
85
+ const suggestion = toValidPackageName(targetDir);
86
+ targetDir = (await prompt(`Project name: (${green(suggestion)}) `)) || suggestion;
87
+ }
88
+
89
+ const root = path.join(cwd, targetDir);
90
+
91
+ if (fs.existsSync(root) && !isEmpty(root)) {
92
+ const overwrite = await prompt(
93
+ `Target directory "${green(targetDir)}" is not empty. Remove existing files and continue? (y/N) `
94
+ );
95
+ if (overwrite.toLowerCase() !== 'y') {
96
+ throw new Error(red('✖ Operation cancelled'));
97
+ }
98
+ emptyDir(root);
99
+ } else if (!fs.existsSync(root)) {
100
+ fs.mkdirSync(root, { recursive: true });
101
+ }
102
+
103
+ if (!template || !TEMPLATES.find((t) => t.name === template)) {
104
+ if (template && !TEMPLATES.find((t) => t.name === template)) {
105
+ console.log(`"${template}" isn't a valid template. Please choose from below:`);
106
+ } else {
107
+ console.log('Select a template:');
108
+ }
109
+
110
+ TEMPLATES.forEach((t, i) => {
111
+ console.log(` ${t.color(i + 1 + '. ' + (t.display || t.name))}`);
112
+ });
113
+
114
+ const templateIdx = await prompt(`Select a template (${green('1-' + TEMPLATES.length)}): `);
115
+ const selected = TEMPLATES[parseInt(templateIdx) - 1];
116
+ if (!selected) {
117
+ throw new Error(red('✖ Invalid template selected'));
118
+ }
119
+ template = selected.name;
120
+ }
121
+
122
+ const templateDir = path.resolve(fileURLToPath(import.meta.url), '../templates', template);
123
+ const commonDir = path.resolve(fileURLToPath(import.meta.url), '../templates', 'common');
124
+
125
+ // ... (copy logic remains same)
126
+
127
+ console.log(`\nScaffolding project in ${root}...`);
128
+
129
+ copyDir(commonDir, root);
130
+
131
+ const appsDir = path.join(root, 'apps');
132
+ if (!fs.existsSync(appsDir)) {
133
+ fs.mkdirSync(appsDir);
134
+ }
135
+
136
+ const appName = path.basename(root);
137
+ const targetAppDir = path.join(appsDir, appName);
138
+
139
+ copyDir(templateDir, targetAppDir);
140
+
141
+ const lupineJsonPath = path.join(targetAppDir, 'lupine.json');
142
+ if (fs.existsSync(lupineJsonPath)) {
143
+ const lupineJson = JSON.parse(fs.readFileSync(lupineJsonPath, 'utf-8'));
144
+ lupineJson.name = appName;
145
+ fs.writeFileSync(lupineJsonPath, JSON.stringify(lupineJson, null, 2));
146
+ }
147
+
148
+ const pkg = {
149
+ name: appName,
150
+ version: '0.0.0',
151
+ private: true,
152
+ workspaces: ['apps/*', 'apps/*/*', 'packages/*'],
153
+ scripts: {
154
+ 'app1:build-win': `electron-builder --win --config apps/${appName}/electron/builder.json`,
155
+ 'app1:build-linux': `electron-builder --linux --config apps/${appName}/electron/builder.json`,
156
+ 'app1:build-mac': `export CSC_IDENTITY_AUTO_DISCOVERY=true && electron-builder --mac --x64 --config apps/${appName}/electron/builder.json`,
157
+ 'app1:unpack-mac': `npx asar extract dist/build/mac-arm64-unpacked/${appName}.app/Contents/Resources/app.asar dist/build/mac-arm64-unpacked/${appName}.app/Contents/Resources/app.asar-unpack`,
158
+ 'app1:unpack-linux': `npx asar extract dist/build/linux-arm64-unpacked/resources/app.asar dist/build/linux-arm64-unpacked/resources/app.asar-unpack`,
159
+ 'app1:unpack-win': `npx asar extract dist/build/win-unpacked/resources/app.asar dist/build/win-unpacked/resources/app.asar-unpack`,
160
+ 'app1:desktop': `electron apps/${appName}/electron/main.js`,
161
+ 'app1:sync': `npm run sync --workspace=${appName}-web`,
162
+ 'app1:open-ios': `npm run open-ios --workspace=${appName}-web`,
163
+ 'app1:open-android': `npm run open-android --workspace=${appName}-web`,
164
+ dev: 'node ./dev/dev-watch --env=.env.development --dev=1 --cmd=start-dev',
165
+ build: 'node ./dev/dev-watch --env=.env.production --dev=0',
166
+ 'build-mobile': 'node ./dev/dev-watch --env=.env.mobile --dev=0 --mobile=1',
167
+ 'start-dev': 'node dist/server_root/server/app-loader.js --env=.env.development',
168
+ 'start-production': 'node dist/server_root/server/app-loader.js --env=.env.production',
169
+ format: 'prettier --write "**/*.{js,json,css,scss,md,html,yaml,ts,jsx,tsx}"',
170
+ },
171
+ dependencies: {
172
+ 'lupine.api': 'file:packages/lupine.api',
173
+ },
174
+ devDependencies: {
175
+ esbuild: '^0.24.2',
176
+ electron: '^35.2.0',
177
+ 'electron-builder': '^26.0.12',
178
+ typescript: '^5.7.2',
179
+ '@types/node': '^22.10.5',
180
+ prettier: '^2.7.1',
181
+ },
182
+ };
183
+
184
+ pkg.dependencies = {
185
+ 'lupine.api': '^1.0.1',
186
+ 'lupine.components': '^1.0.1',
187
+ 'lupine.web': '^1.0.1',
188
+ };
189
+
190
+ const templateObj = TEMPLATES.find((t) => t.name === template);
191
+ if (templateObj && templateObj.needsPress) {
192
+ pkg.dependencies['lupine.press'] = '^1.0.1';
193
+ }
194
+
195
+ fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(pkg, null, 2));
196
+
197
+ const gitignorePath = path.join(root, '_gitignore');
198
+ if (fs.existsSync(gitignorePath)) {
199
+ fs.renameSync(gitignorePath, path.join(root, '.gitignore'));
200
+ }
201
+
202
+ if (fs.existsSync(path.join(root, '.env'))) {
203
+ let envContent = fs.readFileSync(path.join(root, '.env'), 'utf-8');
204
+
205
+ // Auto-generate passwords
206
+ const keysToUpdate = ['ADMIN_PASS=', 'CRYPTO_KEY=', 'DEV_ADMIN_PASS=', 'DEV_CRYPTO_KEY='];
207
+ keysToUpdate.forEach((key) => {
208
+ // Replace line starting with key=
209
+ envContent = envContent.replace(new RegExp(`^${key}.*`, 'gm'), `${key}${generateRandomString(32)}`);
210
+ });
211
+
212
+ envContent = envContent.replace(/{APP-NAME}/g, appName);
213
+ fs.writeFileSync(path.join(root, '.env'), envContent);
214
+ }
215
+
216
+ console.log(`\nDone. Now run:\n`);
217
+ console.log(` cd ${targetDir}`);
218
+ console.log(` npm install`);
219
+ console.log(` npm run dev`);
220
+ console.log(``);
221
+ }
222
+
223
+ import readline from 'node:readline';
224
+
225
+ function prompt(question) {
226
+ const rl = readline.createInterface({
227
+ input: process.stdin,
228
+ output: process.stdout,
229
+ });
230
+ return new Promise((resolve) => {
231
+ rl.question(question, (answer) => {
232
+ rl.close();
233
+ resolve(answer.trim());
234
+ });
235
+ });
236
+ }
237
+
238
+ function copy(src, dest) {
239
+ const stat = fs.statSync(src);
240
+ if (stat.isDirectory()) {
241
+ copyDir(src, dest);
242
+ } else {
243
+ fs.copyFileSync(src, dest);
244
+ }
245
+ }
246
+
247
+ function copyDir(srcDir, destDir) {
248
+ fs.mkdirSync(destDir, { recursive: true });
249
+ for (const file of fs.readdirSync(srcDir)) {
250
+ const srcFile = path.resolve(srcDir, file);
251
+ const destFile = path.resolve(destDir, file);
252
+ copy(srcFile, destFile);
253
+ }
254
+ }
255
+
256
+ function isEmpty(path) {
257
+ const files = fs.readdirSync(path);
258
+ return files.length === 0 || (files.length === 1 && files[0] === '.git');
259
+ }
260
+
261
+ function emptyDir(dir) {
262
+ if (!fs.existsSync(dir)) {
263
+ return;
264
+ }
265
+ for (const file of fs.readdirSync(dir)) {
266
+ const abs = path.resolve(dir, file);
267
+ if (file === '.git') {
268
+ continue;
269
+ }
270
+ fs.rmSync(abs, { recursive: true, force: true });
271
+ }
272
+ }
273
+
274
+ init().catch((e) => {
275
+ console.error(e.message);
276
+ process.exit(1);
277
+ });
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "create-lupine",
3
+ "version": "1.0.0",
4
+ "description": "Scaffolding tool for Lupine.js projects",
5
+ "bin": {
6
+ "create-lupine": "index.js"
7
+ },
8
+ "files": [
9
+ "index.js",
10
+ "templates"
11
+ ],
12
+ "scripts": {
13
+ "npm-publish": "npm publish --access public",
14
+ "test": "echo \"Error: no test specified\" && exit 1"
15
+ },
16
+ "dependencies": {},
17
+ "type": "module"
18
+ }
@@ -0,0 +1,57 @@
1
+ # start with WEB. will be exposed to the frontend file
2
+ WEB.API_PORT=11080
3
+ WEB.API_BASE_URL=
4
+
5
+ # start with “app_name.” will be assigned to the individual app's env object
6
+ # demo.app.WEB.test2=11aa
7
+ # demo.app.test2=11aa
8
+
9
+ # used by Server Side Renderring for fetch (can't be localhost)
10
+ API_BASE_URL=http://127.0.0.1:11080
11
+ HTTP_PORT=11080
12
+ HTTPS_PORT=11083
13
+ #'127.0.0.1' or for all ips: "::"
14
+ BIND_IP=0.0.0.0
15
+
16
+ NODE_TLS_REJECT_UNAUTHORIZED=0
17
+
18
+ LOG_FOLDER=./dist/log
19
+ LOG_FILENAME=log-%index%.log
20
+ # size can be ?kb or ?mb
21
+ LOG_MAX_SIZE=1mb
22
+ LOG_MAX_COUNT=5
23
+ LOG_OUT_TO_FILE=true
24
+ LOG_OUT_TO_CONSOLE=true
25
+
26
+ # use following command to generate random keys
27
+ #node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"
28
+ # use following command to generate more complex strings
29
+ #node -e "l=32;c='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_!@&?-#$';console.log([...require('node:crypto').randomBytes(l)].map(x => c[x%c.length]).join(''))"
30
+
31
+ # server path for web (appName_web), server (server) and data (appName_data)
32
+ SERVER_ROOT_PATH=dist/server_root
33
+ ADMIN_USER=admin
34
+ ADMIN_PASS=
35
+ CRYPTO_KEY=
36
+
37
+ # for development
38
+ DEV_ADMIN_USER=admin
39
+ DEV_ADMIN_PASS=
40
+ DEV_CRYPTO_KEY=
41
+
42
+ # define apps on the same port with different domains (locally or on a real server)
43
+ # in local, the domains should be added to the /etc/hosts (or C:\Windows\System32\drivers\etc\hosts) file
44
+ APPS={APP-NAME}
45
+ # below is empty that means all domains will come to this app
46
+ DOMAINS:{APP-NAME}=
47
+
48
+ # Https Keys and Crts are for domains (not apps), use following command to generate
49
+ # mkcert example.com "*.example.com" localhost 127.0.0.1 ::1
50
+ SSL_KEY_PATH=dev/example.com+4-key.pem
51
+ SSL_CRT_PATH=dev/example.com+4.pem
52
+ HTTPS_KEY_PATH:{APP-NAME}=dev/example.com+4-key.pem
53
+ HTTPS_CRT_PATH:{APP-NAME}=dev/example.com+4.pem
54
+
55
+ DB_TYPE=sqlite
56
+ DB_FILENAME=sqlite3.db
57
+ DB_TYPE:{APP-NAME}=sqlite
@@ -0,0 +1,7 @@
1
+ # dev is used when run dev
2
+ # starting with WEB. will be exposed to env.web.ts file
3
+
4
+ #!import .env
5
+ WEB.NODE_ENV=development
6
+ NODE_ENV=development
7
+ LOG_LEVEL=debug
@@ -0,0 +1,10 @@
1
+ # production is used when run prod
2
+ # starting with WEB. will be exposed to the FrontEnd
3
+
4
+ #!import .env
5
+ WEB.NODE_ENV=production
6
+ NODE_ENV=production
7
+ LOG_LEVEL=info
8
+
9
+ WEB.API_BASE_URL=https://your-server/
10
+ WEB.API_PORT=80
@@ -0,0 +1,7 @@
1
+ # production is used when run prod
2
+ # starting with WEB. will be exposed to the FrontEnd
3
+
4
+ #!import .env
5
+ WEB.NODE_ENV=production
6
+ NODE_ENV=production
7
+ LOG_LEVEL=info
@@ -0,0 +1,41 @@
1
+ import { fork, ChildProcess } from 'child_process';
2
+ import path from 'path';
3
+
4
+ let app: ChildProcess | null = null;
5
+ const startApp = () => {
6
+ console.log('Loader: starting app...');
7
+ app = fork(path.join(__dirname, './index.js'), [], {
8
+ env: { ...process.env, FROM_LOADER: '1' },
9
+ });
10
+
11
+ // child->parent
12
+ app.on('message', async (msg: any) => {
13
+ if (msg?.id === 'debug' && msg?.message === 'restartApp') {
14
+ console.log('Loader: app requested restart.');
15
+ await restartApp();
16
+ }
17
+ });
18
+
19
+ // child exit
20
+ app.on('exit', (code) => {
21
+ console.log('Loader: app exited with code', code);
22
+ });
23
+ };
24
+
25
+ const restartApp = async () => {
26
+ if (!app) return;
27
+
28
+ return new Promise<void>((resolve) => {
29
+ console.log('Loader: sending shutdown to app...');
30
+ app!.send({ id: 'debug', message: 'shutdown' });
31
+
32
+ // wait for app.ts to exit
33
+ app!.once('exit', () => {
34
+ console.log('Loader: app fully exited, restarting...');
35
+ startApp();
36
+ resolve();
37
+ });
38
+ });
39
+ };
40
+
41
+ startApp();
@@ -0,0 +1,20 @@
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
+ };
@@ -0,0 +1,66 @@
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
+ HostToPathProps,
5
+ appStart,
6
+ bindRenderPageFunctions,
7
+ getDefaultDbConfig,
8
+ getRenderPageFunctions,
9
+ loadEnv,
10
+ } from 'lupine.api';
11
+ import { fetchData } from './fetch-data';
12
+ import { ServerEnvKeys } from './server-env-keys';
13
+
14
+ const initAndStartServer = async () => {
15
+ const envFile = process.argv.find((i) => i.startsWith('--env='))?.substring(6) || '.env';
16
+ // it can use "#!import file_name" to import another env file
17
+ await loadEnv(envFile);
18
+ bindRenderPageFunctions({ fetchData });
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
+ for (const app of apps) {
26
+ const appHosts = process.env[`${ServerEnvKeys.DOMAINS}@${app}`] || '';
27
+ const dbFilename =
28
+ process.env[`${ServerEnvKeys.DB_FILENAME}@${app}`] || process.env[`${ServerEnvKeys.DB_FILENAME}`] || 'sqlite3.db';
29
+ webRootMap.push({
30
+ appName: app,
31
+ hosts: appHosts ? appHosts.split(',') : [],
32
+ // web, data, api folders should be created in building process
33
+ webPath: path.join(serverRootPath, app + '_web'),
34
+ dataPath: path.join(serverRootPath, app + '_data'),
35
+ apiPath: path.join(serverRootPath, app + '_api'),
36
+ dbType: process.env[`${ServerEnvKeys.DB_TYPE}@${app}`] || process.env[`${ServerEnvKeys.DB_TYPE}`] || 'sqlite',
37
+ dbConfig: { ...dbConfig, filename: dbFilename },
38
+ });
39
+ }
40
+
41
+ const bindIp = process.env[ServerEnvKeys.BIND_IP] || '::';
42
+ // 0 to disable http/https server
43
+ const httpPort = Number.parseInt(process.env[ServerEnvKeys.HTTP_PORT] || '8080');
44
+ const httpsPort = Number.parseInt(process.env[ServerEnvKeys.HTTPS_PORT] || '8443');
45
+ const sslKeyPath = process.env[ServerEnvKeys.SSL_KEY_PATH] || '';
46
+ const sslCrtPath = process.env[ServerEnvKeys.SSL_CRT_PATH] || '';
47
+
48
+ // Can't use log until initApp is called (after AppStart.start)
49
+ await appStart.start({
50
+ debug: process.env[ServerEnvKeys.NODE_ENV] === 'development',
51
+ appEnvFile: envFile,
52
+ renderPageFunctions: getRenderPageFunctions(),
53
+ apiConfig: {
54
+ serverRoot: `${serverRootPath}`,
55
+ webHostMap: webRootMap,
56
+ },
57
+ serverConfig: {
58
+ bindIp,
59
+ httpPort,
60
+ httpsPort,
61
+ sslKeyPath,
62
+ sslCrtPath,
63
+ },
64
+ });
65
+ };
66
+ initAndStartServer();
@@ -0,0 +1,22 @@
1
+ // some like APPS, HTTP_PORT, SERVER_ROOT_PATH are also used in 'dev-watch.js'
2
+ export const enum ServerEnvKeys {
3
+ BIND_IP = 'BIND_IP',
4
+ HTTP_PORT = 'HTTP_PORT',
5
+ HTTPS_PORT = 'HTTPS_PORT',
6
+ SSL_KEY_PATH = 'SSL_KEY_PATH',
7
+ SSL_CRT_PATH = 'SSL_CRT_PATH',
8
+ WEB_ROOT_MAP = 'WEB_ROOT_MAP',
9
+ SERVER_ROOT_PATH = 'SERVER_ROOT_PATH',
10
+ NODE_ENV = 'NODE_ENV',
11
+ APPS = 'APPS',
12
+ DOMAINS = 'DOMAINS',
13
+ DB_TYPE = 'DB_TYPE',
14
+ DB_FILENAME = 'DB_FILENAME',
15
+ LOG_FOLDER = 'LOG_FOLDER',
16
+ LOG_FILENAME = 'LOG_FILENAME',
17
+ LOG_MAX_SIZE = 'LOG_MAX_SIZE',
18
+ LOG_MAX_COUNT = 'LOG_MAX_COUNT',
19
+ LOG_OUT_TO_FILE = 'LOG_OUT_TO_FILE',
20
+ LOG_OUT_TO_CONSOLE = 'LOG_OUT_TO_CONSOLE',
21
+ LOG_LEVEL = 'LOG_LEVEL',
22
+ }