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.
- package/index.js +277 -0
- package/package.json +18 -0
- package/templates/common/.env +57 -0
- package/templates/common/.env.development +7 -0
- package/templates/common/.env.mobile +10 -0
- package/templates/common/.env.production +7 -0
- package/templates/common/apps/server/src/app-loader.ts +41 -0
- package/templates/common/apps/server/src/fetch-data.ts +20 -0
- package/templates/common/apps/server/src/index.ts +66 -0
- package/templates/common/apps/server/src/server-env-keys.ts +22 -0
- package/templates/common/dev/dev-watch.js +422 -0
- package/templates/doc-starter/api/resources/config_default.json +6 -0
- package/templates/doc-starter/api/resources/install.sqlite.sql +4 -0
- package/templates/doc-starter/api/src/index.ts +15 -0
- package/templates/doc-starter/api/src/resources/config_default.json +6 -0
- package/templates/doc-starter/api/src/resources/install.sqlite.sql +4 -0
- package/templates/doc-starter/lupine.json +33 -0
- package/templates/doc-starter/package.json +13 -0
- package/templates/doc-starter/web/assets/android-chrome-192x192.png +0 -0
- package/templates/doc-starter/web/assets/apple-touch-icon.png +0 -0
- package/templates/doc-starter/web/assets/favicon-16x16.png +0 -0
- package/templates/doc-starter/web/assets/favicon-32x32.png +0 -0
- package/templates/doc-starter/web/assets/favicon.ico +0 -0
- package/templates/doc-starter/web/assets/site.webmanifest +14 -0
- package/templates/doc-starter/web/github-pj-name/index.html +21 -0
- package/templates/doc-starter/web/github-pj-name/index.tsx +35 -0
- package/templates/doc-starter/web/markdown/en/essentials/index.md +6 -0
- package/templates/doc-starter/web/markdown/en/essentials/list.md +18 -0
- package/templates/doc-starter/web/markdown/en/guide/install.md +18 -0
- package/templates/doc-starter/web/markdown/en/guide/started.md +22 -0
- package/templates/doc-starter/web/markdown/en/index.md +42 -0
- package/templates/doc-starter/web/markdown/index.md +7 -0
- package/templates/doc-starter/web/markdown/zh/essentials/index.md +6 -0
- package/templates/doc-starter/web/markdown/zh/essentials/list.md +18 -0
- package/templates/doc-starter/web/markdown/zh/guide/install.md +18 -0
- package/templates/doc-starter/web/markdown/zh/guide/started.md +22 -0
- package/templates/doc-starter/web/markdown/zh/index.md +42 -0
- package/templates/doc-starter/web/src/client-env-keys.ts +5 -0
- package/templates/doc-starter/web/src/index.html +21 -0
- package/templates/doc-starter/web/src/index.tsx +33 -0
- package/templates/doc-starter/web/src/markdown-built/en/essentials/index.html +0 -0
- package/templates/doc-starter/web/src/markdown-built/en/essentials/list.html +8 -0
- package/templates/doc-starter/web/src/markdown-built/en/guide/install.html +8 -0
- package/templates/doc-starter/web/src/markdown-built/en/guide/started.html +12 -0
- package/templates/doc-starter/web/src/markdown-built/en/index.html +0 -0
- package/templates/doc-starter/web/src/markdown-built/index.html +0 -0
- package/templates/doc-starter/web/src/markdown-built/markdown-config.ts +25 -0
- package/templates/doc-starter/web/src/markdown-built/zh/essentials/index.html +0 -0
- package/templates/doc-starter/web/src/markdown-built/zh/essentials/list.html +8 -0
- package/templates/doc-starter/web/src/markdown-built/zh/guide/install.html +8 -0
- package/templates/doc-starter/web/src/markdown-built/zh/guide/started.html +12 -0
- package/templates/doc-starter/web/src/markdown-built/zh/index.html +0 -0
- package/templates/doc-starter/web/src/styles/base-css.ts +15 -0
- package/templates/hello-world/api/resources/config_default.json +6 -0
- package/templates/hello-world/api/resources/install.sqlite.sql +4 -0
- package/templates/hello-world/api/src/index.ts +4 -0
- package/templates/hello-world/api/src/service/root-api.ts +18 -0
- package/templates/hello-world/lupine.json +23 -0
- package/templates/hello-world/web/assets/favicon.ico +0 -0
- package/templates/hello-world/web/package.json +6 -0
- package/templates/hello-world/web/src/index.html +16 -0
- package/templates/hello-world/web/src/index.tsx +30 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
// esbuild command tools
|
|
2
|
+
|
|
3
|
+
const esbuild = require('esbuild');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs/promises');
|
|
6
|
+
const {
|
|
7
|
+
runCmd,
|
|
8
|
+
copyFolder,
|
|
9
|
+
sendRequest,
|
|
10
|
+
loadEnv,
|
|
11
|
+
readJson,
|
|
12
|
+
pathExists,
|
|
13
|
+
cpIndexHtml,
|
|
14
|
+
pluginIfelse,
|
|
15
|
+
markdownProcessOnEnd,
|
|
16
|
+
} = require('lupine.api/dev');
|
|
17
|
+
|
|
18
|
+
const triggerHandle = {
|
|
19
|
+
restart: null,
|
|
20
|
+
refresh: null,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// restart server for server code changes
|
|
24
|
+
const triggerReStartServer = async (isDev, npmCmd, httpPort) => {
|
|
25
|
+
if (triggerHandle.restart) {
|
|
26
|
+
clearTimeout(triggerHandle.restart);
|
|
27
|
+
}
|
|
28
|
+
triggerHandle.restart = setTimeout(async () => {
|
|
29
|
+
const url = `http://127.0.0.1:${httpPort}/debug/shutdown`;
|
|
30
|
+
await sendRequest(url, isDev ? 2 : 0);
|
|
31
|
+
console.log('[dev-server] ReStart Server: ', url);
|
|
32
|
+
isDev && runCmd(npmCmd);
|
|
33
|
+
}, 500);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// refresh server for client code changes
|
|
37
|
+
const triggerRefreshServer = async (isDev, httpPort) => {
|
|
38
|
+
if (triggerHandle.refresh) {
|
|
39
|
+
clearTimeout(triggerHandle.refresh);
|
|
40
|
+
}
|
|
41
|
+
triggerHandle.refresh = setTimeout(async () => {
|
|
42
|
+
const url = `http://127.0.0.1:${httpPort}/debug/refresh`;
|
|
43
|
+
await sendRequest(url, isDev ? 2 : 0);
|
|
44
|
+
console.log('[dev-server] Refresh Server: ', url);
|
|
45
|
+
}, 500);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// watch server code changes
|
|
49
|
+
const watchServerPlugin = (isDev, npmCmd, httpPort) => {
|
|
50
|
+
return {
|
|
51
|
+
name: 'watchServerPlugin',
|
|
52
|
+
setup(build) {
|
|
53
|
+
build.onEnd(async (res) => {
|
|
54
|
+
// console.log(`Build meta data: `, res);
|
|
55
|
+
console.log(`[dev-server] Build finished`);
|
|
56
|
+
if (isDev) {
|
|
57
|
+
await triggerReStartServer(isDev, npmCmd, httpPort);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// javascript-obfuscator is needed
|
|
65
|
+
const obfuscatePlugin = (isObfuscate, entryPoints = [], skipPaths = []) => {
|
|
66
|
+
if (!isObfuscate) return { name: 'obfuscatePlugin', setup() {} };
|
|
67
|
+
|
|
68
|
+
const JavaScriptObfuscator = require('javascript-obfuscator');
|
|
69
|
+
return {
|
|
70
|
+
name: 'obfuscatePlugin',
|
|
71
|
+
setup(build) {
|
|
72
|
+
build.onLoad({ filter: /\.(js|ts|tsx|jsx)$/ }, async (args) => {
|
|
73
|
+
if (args.path.includes('node_modules')) return null;
|
|
74
|
+
if (skipPaths.some((skipPath) => args.path.includes(skipPath))) {
|
|
75
|
+
console.log(`[dev-server] Skip obfuscate: ${args.path}`);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const ext = path.extname(args.path);
|
|
80
|
+
console.log(`[dev-server] Obfuscate: ${args.path}`);
|
|
81
|
+
let content = await fs.readFile(args.path, 'utf8');
|
|
82
|
+
|
|
83
|
+
// Transpile TS/JSX to JS first because obfuscator works on JS
|
|
84
|
+
if (['.ts', '.tsx', '.jsx'].includes(ext)) {
|
|
85
|
+
const result = await esbuild.transform(content, {
|
|
86
|
+
loader: ext.substring(1),
|
|
87
|
+
sourcefile: args.path,
|
|
88
|
+
jsx: 'automatic',
|
|
89
|
+
jsxImportSource: 'lupine.web',
|
|
90
|
+
});
|
|
91
|
+
content = result.code;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const obfuscationResult = JavaScriptObfuscator.obfuscate(content, {
|
|
95
|
+
compact: true,
|
|
96
|
+
controlFlowFlattening: true,
|
|
97
|
+
controlFlowFlatteningThreshold: 0.75,
|
|
98
|
+
deadCodeInjection: false,
|
|
99
|
+
deadCodeInjectionThreshold: 0,
|
|
100
|
+
// debugProtection: isEntryPoint, // this should be done in code: disableDebug('xxx');
|
|
101
|
+
debugProtectionInterval: 2000,
|
|
102
|
+
// disableConsoleOutput: isEntryPoint, // this should be done in code: disableConsole();
|
|
103
|
+
identifierNamesGenerator: 'hexadecimal',
|
|
104
|
+
stringArray: true,
|
|
105
|
+
stringArrayThreshold: 0.75,
|
|
106
|
+
ignoreImports: true,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
contents: obfuscationResult.getObfuscatedCode(),
|
|
111
|
+
loader: 'js',
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// watch server code changes
|
|
119
|
+
const watchAppLoader = async (isDev, npmCmd, httpPort, serverRootPath) => {
|
|
120
|
+
const cmd = isDev ? esbuild.context : esbuild.build;
|
|
121
|
+
const ctx = await cmd({
|
|
122
|
+
entryPoints: ['apps/server/src/app-loader.ts'],
|
|
123
|
+
// outdir: path.join(serverRootPath, 'server'),
|
|
124
|
+
outfile: path.join(serverRootPath, 'server', 'app-loader.js'),
|
|
125
|
+
platform: 'node',
|
|
126
|
+
sourcemap: !!isDev,
|
|
127
|
+
format: 'cjs',
|
|
128
|
+
bundle: true,
|
|
129
|
+
treeShaking: true,
|
|
130
|
+
metafile: true,
|
|
131
|
+
minify: !isDev,
|
|
132
|
+
plugins: [watchServerPlugin(isDev, npmCmd, httpPort)],
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
isDev && (await ctx.watch());
|
|
136
|
+
isDev && console.log('[dev-server] Watching...');
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// watch server code changes
|
|
140
|
+
const watchServer = async (isDev, npmCmd, httpPort, serverRootPath) => {
|
|
141
|
+
const cmd = isDev ? esbuild.context : esbuild.build;
|
|
142
|
+
const ctx = await cmd({
|
|
143
|
+
entryPoints: ['apps/server/src/index.ts'],
|
|
144
|
+
outdir: path.join(serverRootPath, 'server'),
|
|
145
|
+
platform: 'node',
|
|
146
|
+
sourcemap: !!isDev,
|
|
147
|
+
format: 'cjs',
|
|
148
|
+
bundle: true,
|
|
149
|
+
treeShaking: true,
|
|
150
|
+
metafile: true,
|
|
151
|
+
external: ['better-sqlite3', 'nodemailer', 'pdfkit', 'sharp'],
|
|
152
|
+
minify: !isDev,
|
|
153
|
+
plugins: [watchServerPlugin(isDev, npmCmd, httpPort)],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
isDev && (await ctx.watch());
|
|
157
|
+
isDev && console.log('[dev-server] Watching...');
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// copy files to output directory
|
|
161
|
+
const copyCache = new Map();
|
|
162
|
+
const clientProcessOnEnd = async (saved) => {
|
|
163
|
+
saved.indexHtml &&
|
|
164
|
+
cpIndexHtml(
|
|
165
|
+
saved.indexHtml,
|
|
166
|
+
path.join(saved.outdir, 'index.html'),
|
|
167
|
+
saved.appName,
|
|
168
|
+
saved.isMobile,
|
|
169
|
+
saved.defaultThemeName,
|
|
170
|
+
saved.outdirData,
|
|
171
|
+
saved.outdirSub
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const assets = saved['copyFiles'];
|
|
175
|
+
if (assets) {
|
|
176
|
+
for (oneDir of assets) {
|
|
177
|
+
await copyFolder(copyCache, oneDir.from, oneDir.to, saved.isDev);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (saved.isDev) {
|
|
182
|
+
await triggerRefreshServer(saved.isDev, saved.httpPort);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// watch client code changes
|
|
187
|
+
const watchClientPlugin = (saved) => {
|
|
188
|
+
return {
|
|
189
|
+
name: 'watchClientPlugin',
|
|
190
|
+
setup(build) {
|
|
191
|
+
build.onEnd(async (res) => {
|
|
192
|
+
// console.log(`Build meta data: `, res);
|
|
193
|
+
console.log(`[dev-client:${saved.appName}] Build finished`);
|
|
194
|
+
await clientProcessOnEnd(saved);
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// plugin for conditional code logic
|
|
201
|
+
/*
|
|
202
|
+
#if MOBILE && DEV
|
|
203
|
+
console.log('this is mobile and dev');
|
|
204
|
+
#endif
|
|
205
|
+
*/
|
|
206
|
+
const ifPluginVars = {
|
|
207
|
+
MOBILE: '',
|
|
208
|
+
DEV: '',
|
|
209
|
+
};
|
|
210
|
+
const watchClient = async (saved, isDev, entryPoints, outbase) => {
|
|
211
|
+
const cmd = isDev ? esbuild.context : esbuild.build;
|
|
212
|
+
const ctx = await cmd({
|
|
213
|
+
entryPoints,
|
|
214
|
+
outdir: saved.outdir,
|
|
215
|
+
outbase,
|
|
216
|
+
// entryNames: '[name]-[hash]',
|
|
217
|
+
platform: 'browser',
|
|
218
|
+
sourcemap: !!isDev, // inline
|
|
219
|
+
format: 'iife',
|
|
220
|
+
bundle: true,
|
|
221
|
+
treeShaking: true,
|
|
222
|
+
external: [],
|
|
223
|
+
metafile: true,
|
|
224
|
+
minify: !isDev,
|
|
225
|
+
loader: { '.svg': 'text', '.glsl': 'text', '.png': 'file', '.gif': 'file', '.html': 'text' },
|
|
226
|
+
assetNames: '/pub_assets/[name]-[hash]',
|
|
227
|
+
publicPath: saved.outdirSub,
|
|
228
|
+
jsxImportSource: 'lupine.web',
|
|
229
|
+
jsx: 'automatic',
|
|
230
|
+
target: ['chrome87'],
|
|
231
|
+
plugins: [
|
|
232
|
+
watchClientPlugin(saved),
|
|
233
|
+
pluginIfelse(ifPluginVars),
|
|
234
|
+
obfuscatePlugin(saved.isObfuscate, entryPoints, []),
|
|
235
|
+
],
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
isDev && (await ctx.watch());
|
|
239
|
+
isDev && console.log(`[dev-client:${saved.appName}] Watching...`);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const watchApiPlugin = (isDev, httpPort) => {
|
|
243
|
+
return {
|
|
244
|
+
name: 'watchApiPlugin',
|
|
245
|
+
setup(build) {
|
|
246
|
+
build.onEnd(async (res) => {
|
|
247
|
+
console.log(`[dev-api] Build finished`);
|
|
248
|
+
|
|
249
|
+
if (isDev) {
|
|
250
|
+
await triggerRefreshServer(isDev, httpPort);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const watchApi = async (saved, isDev, entryPoints) => {
|
|
258
|
+
const cmd = isDev ? esbuild.context : esbuild.build;
|
|
259
|
+
const ctx = await cmd({
|
|
260
|
+
entryPoints,
|
|
261
|
+
outdir: saved.outdirApi,
|
|
262
|
+
// outbase,
|
|
263
|
+
platform: 'node',
|
|
264
|
+
sourcemap: !!isDev, // inline
|
|
265
|
+
format: 'cjs', // iife, cjs
|
|
266
|
+
bundle: true,
|
|
267
|
+
treeShaking: true,
|
|
268
|
+
metafile: true,
|
|
269
|
+
external: ['better-sqlite3', 'nodemailer', 'pdfkit', 'sharp'],
|
|
270
|
+
minify: !isDev,
|
|
271
|
+
plugins: [watchApiPlugin(isDev, saved.httpPort)],
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
isDev && (await ctx.watch());
|
|
275
|
+
isDev && console.log(`[dev-api:${saved.appName}] Watching...`);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const watchMarkdownPlugin = (saved) => {
|
|
279
|
+
return {
|
|
280
|
+
name: 'watchMarkdownPlugin',
|
|
281
|
+
setup(build) {
|
|
282
|
+
build.onEnd(async (res) => {
|
|
283
|
+
// console.log(`Build meta data: `, res);
|
|
284
|
+
console.log(`[dev-markdown:${saved.appName}] Build finished`);
|
|
285
|
+
await markdownProcessOnEnd(saved.markdownEntryPoints.indir, saved.markdownEntryPoints.outdir);
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const watchMarkdown = async (saved, entryPoints) => {
|
|
292
|
+
const cmd = saved.isDev ? esbuild.context : esbuild.build;
|
|
293
|
+
const ctx = await cmd({
|
|
294
|
+
entryPoints: [`${entryPoints.indir}/**/*.md`],
|
|
295
|
+
outdir: entryPoints.outdir,
|
|
296
|
+
write: false,
|
|
297
|
+
loader: { '.md': 'empty' },
|
|
298
|
+
plugins: [watchMarkdownPlugin({ ...saved, markdownEntryPoints: entryPoints })],
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
saved.isDev && (await ctx.watch());
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const watchAdditionalFiles = async (saved, entryPoints) => {
|
|
305
|
+
const cmd = saved.isDev ? esbuild.context : esbuild.build;
|
|
306
|
+
const ctx = await cmd({
|
|
307
|
+
entryPoints: entryPoints,
|
|
308
|
+
outdir: 'dist/tmp',
|
|
309
|
+
write: false,
|
|
310
|
+
loader: { '.html': 'empty', '.css': 'empty', '.js': 'empty', '.md': 'empty' },
|
|
311
|
+
plugins: [watchClientPlugin(saved)],
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
saved.isDev && (await ctx.watch());
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Add Server, API, Client and additional files entry points to esbuild
|
|
318
|
+
const start = async () => {
|
|
319
|
+
// Any code accessing process.env should be after loadEnv
|
|
320
|
+
// process env files, "#!import .env" in .env file can be used to include another env file
|
|
321
|
+
let envFile = process.argv.find((i) => i.startsWith('--env='))?.substring(6);
|
|
322
|
+
// load env file, but don't overwrite existing env variables
|
|
323
|
+
await loadEnv(envFile);
|
|
324
|
+
|
|
325
|
+
const serverRootPathEnv = process.env['SERVER_ROOT_PATH'];
|
|
326
|
+
if (!serverRootPathEnv) {
|
|
327
|
+
console.error('SERVER_ROOT_PATH is not set');
|
|
328
|
+
return 0;
|
|
329
|
+
}
|
|
330
|
+
const serverRootPath = path.resolve(serverRootPathEnv);
|
|
331
|
+
await fs.mkdir(serverRootPath, { recursive: true });
|
|
332
|
+
if (!(await pathExists(serverRootPath))) {
|
|
333
|
+
console.error(`Can't create server root path: ${serverRootPath}`);
|
|
334
|
+
return 0;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// command to start the server when source files are changed
|
|
338
|
+
const npmCmd = process.argv.find((i) => i.startsWith('--cmd='))?.substring(6);
|
|
339
|
+
const isDev = process.argv.find((i) => i === '--dev=1');
|
|
340
|
+
const isMobile = process.argv.find((i) => i === '--mobile=1'); // when this changed, need to rebuild index.html
|
|
341
|
+
const isObfuscate = !isDev && process.argv.find((i) => i === '--obfuscate=1');
|
|
342
|
+
// this is for esbuild conditional compile
|
|
343
|
+
ifPluginVars.DEV = isDev ? '1' : '0';
|
|
344
|
+
ifPluginVars.MOBILE = isMobile ? '1' : '0';
|
|
345
|
+
|
|
346
|
+
// All apps should be defined in .env file like this:
|
|
347
|
+
// APPS=domain1.com,domain2.com
|
|
348
|
+
const apps = (process.env['APPS'] || '').split(',');
|
|
349
|
+
|
|
350
|
+
const httpPort = process.env['HTTP_PORT'];
|
|
351
|
+
for (const app of apps) {
|
|
352
|
+
const appJson = `apps/${app}/lupine.json`;
|
|
353
|
+
const additionalFiles = [];
|
|
354
|
+
const appCfg = await readJson(appJson);
|
|
355
|
+
const appName = appCfg['name'];
|
|
356
|
+
const appDir = path.dirname(appJson);
|
|
357
|
+
if (appCfg['watchFiles']) {
|
|
358
|
+
appCfg['watchFiles'].forEach((item) => additionalFiles.push(`${appDir}/${item}`));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// create data folder
|
|
362
|
+
await fs.mkdir(`${serverRootPath}/${appName}_data`, { recursive: true });
|
|
363
|
+
|
|
364
|
+
const saved = {
|
|
365
|
+
isDev,
|
|
366
|
+
isMobile,
|
|
367
|
+
isObfuscate,
|
|
368
|
+
defaultThemeName: 'light',
|
|
369
|
+
appName,
|
|
370
|
+
httpPort,
|
|
371
|
+
serverRoot: serverRootPath,
|
|
372
|
+
outdirWeb: `${serverRootPath}/${appName}_web`,
|
|
373
|
+
outdirApi: `${serverRootPath}/${appName}_api`,
|
|
374
|
+
outdirData: `${serverRootPath}/${appName}_data`,
|
|
375
|
+
copyFiles: appCfg['copyFiles'].map((item) => ({
|
|
376
|
+
from: `${appDir}/${item.from}`,
|
|
377
|
+
to:
|
|
378
|
+
item.type == 'data'
|
|
379
|
+
? `${serverRootPath}/${appName}_data/${item.to}`
|
|
380
|
+
: `${serverRootPath}/${appName}_web/${item.to}`,
|
|
381
|
+
})),
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
appCfg['webEntryPoints'].forEach((item, index) => {
|
|
385
|
+
const entryPoint = `${appDir}/${item.index}`;
|
|
386
|
+
const indexHtml = `${appDir}/${item.html}`;
|
|
387
|
+
watchClient(
|
|
388
|
+
{
|
|
389
|
+
...saved,
|
|
390
|
+
outdirSub: item.outdir,
|
|
391
|
+
outdir: `${serverRootPath}/${appName}_web/` + item.outdir,
|
|
392
|
+
indexHtml,
|
|
393
|
+
copyFiles: [],
|
|
394
|
+
},
|
|
395
|
+
isDev,
|
|
396
|
+
[entryPoint],
|
|
397
|
+
path.dirname(entryPoint)
|
|
398
|
+
);
|
|
399
|
+
additionalFiles.push(indexHtml);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
if (appCfg['apiEntryPoint']) {
|
|
403
|
+
const entryPointApi = `${appDir}/${appCfg['apiEntryPoint']}`;
|
|
404
|
+
watchApi(saved, isDev, [entryPointApi]);
|
|
405
|
+
}
|
|
406
|
+
if (appCfg['markdownEntryPoints']) {
|
|
407
|
+
const markdownEntryPoints = {
|
|
408
|
+
indir: `${appDir}/${appCfg['markdownEntryPoints'].indir}`,
|
|
409
|
+
outdir: `${appDir}/${appCfg['markdownEntryPoints'].outdir}`,
|
|
410
|
+
};
|
|
411
|
+
watchMarkdown(saved, markdownEntryPoints);
|
|
412
|
+
}
|
|
413
|
+
if (additionalFiles.length > 0) {
|
|
414
|
+
// when some resources are changed, need to run command or refresh the server
|
|
415
|
+
watchAdditionalFiles(saved, additionalFiles);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
watchServer(isDev, npmCmd, httpPort, serverRootPath);
|
|
420
|
+
watchAppLoader(isDev, npmCmd, httpPort, serverRootPath);
|
|
421
|
+
};
|
|
422
|
+
start();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { IApiBase, ApiRouter, StaticServer, ApiModule } from 'lupine.api';
|
|
2
|
+
|
|
3
|
+
class DocApi implements IApiBase {
|
|
4
|
+
protected router = new ApiRouter();
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
this.router.use('*', new StaticServer().processRequest.bind(new StaticServer()));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public getRouter(): ApiRouter {
|
|
11
|
+
return this.router;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const apiModule = new ApiModule(new DocApi());
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "doc-starter",
|
|
3
|
+
"markdownEntryPoints": {
|
|
4
|
+
"indir": "web/markdown",
|
|
5
|
+
"outdir": "web/src/markdown-built"
|
|
6
|
+
},
|
|
7
|
+
"webEntryPoints": [
|
|
8
|
+
{
|
|
9
|
+
"index": "web/src/index.tsx",
|
|
10
|
+
"html": "web/src/index.html",
|
|
11
|
+
"outdir": "/"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"index": "web/github-pj-name/index.tsx",
|
|
15
|
+
"html": "web/github-pj-name/index.html",
|
|
16
|
+
"outdir": "/github-pj-name"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"apiEntryPoint": "api/src/index.ts",
|
|
20
|
+
"watchFiles": ["web/markdown/**/*.md"],
|
|
21
|
+
"copyFiles": [
|
|
22
|
+
{
|
|
23
|
+
"type": "web",
|
|
24
|
+
"from": "web/assets",
|
|
25
|
+
"to": "assets"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"type": "data",
|
|
29
|
+
"from": "api/resources",
|
|
30
|
+
"to": "resources"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "doc-starter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Documentation starter demo project",
|
|
5
|
+
"private": true,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "echo 'run build at project root directory'"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"lupine.press": "^1.0.1"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html data-theme="<!--META-THEME-->">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<link rel="manifest" href="{SUBDIR}/assets/site.webmanifest" />
|
|
6
|
+
<title><!--META-TITLE--></title>
|
|
7
|
+
<link rel="apple-touch-icon" sizes="180x180" href="{SUBDIR}/assets/apple-touch-icon.png" />
|
|
8
|
+
<link rel="icon" type="image/png" sizes="32x32" href="{SUBDIR}/assets/favicon-32x32.png" />
|
|
9
|
+
<link rel="icon" type="image/png" sizes="16x16" href="{SUBDIR}/assets/favicon-16x16.png" />
|
|
10
|
+
<link rel="icon" type="image/png" sizes="192x192" href="{SUBDIR}/assets/android-chrome-192x192.png" />
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
12
|
+
<!--META-ENV-START-->
|
|
13
|
+
<!--META-ENV-END-->
|
|
14
|
+
<link rel="shortcut icon" href="{SUBDIR}/assets/favicon.ico?t={hash}" />
|
|
15
|
+
<link rel="stylesheet" type="text/css" href="{SUBDIR}/index.css?t={hash}" />
|
|
16
|
+
<script defer src="{SUBDIR}/index.js#t={hash}"></script>
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<div class="lupine-root"></div>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bindRouter,
|
|
3
|
+
PageRouter,
|
|
4
|
+
bindTheme,
|
|
5
|
+
bindLang,
|
|
6
|
+
setDefaultPageTitle,
|
|
7
|
+
isFrontEnd,
|
|
8
|
+
debugWatch,
|
|
9
|
+
webEnv,
|
|
10
|
+
setDefaultMetaDescription,
|
|
11
|
+
bindGlobalStyle,
|
|
12
|
+
} from 'lupine.components';
|
|
13
|
+
import { bindPressData, PressPage, pressThemes, setPressSubDir } from 'lupine.press';
|
|
14
|
+
import { ClientEnvKeys } from '../src/client-env-keys';
|
|
15
|
+
import { baseCss } from '../src/styles/base-css';
|
|
16
|
+
import { markdownConfig } from '../src/markdown-built/markdown-config';
|
|
17
|
+
|
|
18
|
+
if (isFrontEnd() && webEnv(ClientEnvKeys.NODE_ENV, '') === 'development') {
|
|
19
|
+
debugWatch(webEnv(ClientEnvKeys.API_PORT, 0));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
bindLang('en', {});
|
|
23
|
+
bindTheme('light', pressThemes);
|
|
24
|
+
bindGlobalStyle('comm-css', baseCss, false, true);
|
|
25
|
+
setDefaultPageTitle('Doc Starter');
|
|
26
|
+
setDefaultMetaDescription('Doc Starter Demo');
|
|
27
|
+
|
|
28
|
+
bindPressData(markdownConfig);
|
|
29
|
+
setPressSubDir('/github-pj-name');
|
|
30
|
+
|
|
31
|
+
const pageRouter = new PageRouter();
|
|
32
|
+
pageRouter.setSubDir('/github-pj-name');
|
|
33
|
+
pageRouter.use('*', PressPage);
|
|
34
|
+
|
|
35
|
+
bindRouter(pageRouter);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Essentials List
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> This is a demo document.
|
|
7
|
+
|
|
8
|
+
# Essentials List
|
|
9
|
+
|
|
10
|
+
This is a simplified essentials list doc for demo purposes.
|
|
11
|
+
|
|
12
|
+
## Feature Overview
|
|
13
|
+
|
|
14
|
+
Here is an example listing key features:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
const features = ['Fast', 'Reliable', 'Easy to use'];
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Installation
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> This is a demo document.
|
|
7
|
+
|
|
8
|
+
# Installation
|
|
9
|
+
|
|
10
|
+
This is a simplified installation guide for demo purposes.
|
|
11
|
+
|
|
12
|
+
## Basic Install
|
|
13
|
+
|
|
14
|
+
Run the following command:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install my-project
|
|
18
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting Started
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> This is a demo document.
|
|
7
|
+
|
|
8
|
+
# Getting Started
|
|
9
|
+
|
|
10
|
+
This is a simplified getting started guide for demo purposes.
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
Initialize your project:
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
import { init } from 'my-project';
|
|
18
|
+
|
|
19
|
+
init({
|
|
20
|
+
debug: true,
|
|
21
|
+
});
|
|
22
|
+
```
|