alemonjs 2.1.83-alpha.6 → 2.1.83-alpha.8

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.
@@ -1,2 +1,4 @@
1
+ import type { RuntimeAppRecord } from '../../store.js';
2
+ export declare const renderHelloHtml: (apps: RuntimeAppRecord[]) => string;
1
3
  declare const _default: string;
2
4
  export default _default;
@@ -1,31 +1,390 @@
1
- import { Component, Head, Title, Style, Body, H1, P, A, Div, Html } from '../../../../common/react.js';
2
-
3
- class App extends Component {
4
- render() {
5
- const style = `
1
+ const escapeHtml = (value) => String(value)
2
+ .replace(/&/g, '&')
3
+ .replace(/</g, '&lt;')
4
+ .replace(/>/g, '&gt;')
5
+ .replace(/"/g, '&quot;')
6
+ .replace(/'/g, '&#39;');
7
+ const appHref = (app) => {
8
+ return app.kind === 'main' ? '/app' : `/apps/${app.name}`;
9
+ };
10
+ const appTags = (app) => {
11
+ const tags = [app.kind === 'main' ? '主应用' : '插件'];
12
+ if (app.capabilities.web) {
13
+ tags.push('页面');
14
+ }
15
+ if (app.capabilities.httpApi) {
16
+ tags.push('接口');
17
+ }
18
+ if (app.capabilities.event) {
19
+ tags.push('事件');
20
+ }
21
+ if (app.capabilities.expose) {
22
+ tags.push('Expose');
23
+ }
24
+ return tags;
25
+ };
26
+ const renderCard = (app) => {
27
+ const href = appHref(app);
28
+ const tags = appTags(app)
29
+ .map(tag => `<span class="app-tag">${escapeHtml(tag)}</span>`)
30
+ .join('');
31
+ const desc = app.kind === 'main'
32
+ ? '访问主应用页面、接口与默认资源。'
33
+ : '访问插件页面、接口与公开入口。';
34
+ return `
35
+ <a
36
+ class="app-card"
37
+ href="${escapeHtml(href)}"
38
+ data-app-id="${escapeHtml(app.name)}"
39
+ data-app-kind="${escapeHtml(app.kind)}"
40
+ data-app-href="${escapeHtml(href)}"
41
+ >
42
+ <div class="app-card__top">
43
+ <div>
44
+ <p class="app-eyebrow">${app.kind === 'main' ? 'Main App' : 'Plugin App'}</p>
45
+ <h2 class="app-title">${escapeHtml(app.name)}</h2>
46
+ </div>
47
+ <div class="app-rank">
48
+ <span class="app-rank__label">热度</span>
49
+ <span class="app-rank__value" data-click-count>0</span>
50
+ </div>
51
+ </div>
52
+ <p class="app-desc">${escapeHtml(desc)}</p>
53
+ <div class="app-tags">${tags}</div>
54
+ <div class="app-card__bottom">
55
+ <span class="app-link">${escapeHtml(href)}</span>
56
+ <span class="app-action">进入</span>
57
+ </div>
58
+ </a>
59
+ `;
60
+ };
61
+ const renderEmpty = () => {
62
+ return `
63
+ <div class="empty-state">
64
+ <p class="empty-state__title">当前没有可展示的应用。</p>
65
+ <p class="empty-state__desc">请先加载主应用或插件,再刷新本页查看入口。</p>
66
+ </div>
67
+ `;
68
+ };
69
+ const renderHelloHtml = (apps) => {
70
+ const visibleApps = apps
71
+ .filter(app => app.enabled && app.status === 'ready' && (app.capabilities.web || app.capabilities.httpApi))
72
+ .sort((left, right) => {
73
+ if (left.kind === 'main' && right.kind !== 'main') {
74
+ return -1;
75
+ }
76
+ if (left.kind !== 'main' && right.kind === 'main') {
77
+ return 1;
78
+ }
79
+ return left.name.localeCompare(right.name);
80
+ });
81
+ const cards = visibleApps.length ? visibleApps.map(renderCard).join('') : renderEmpty();
82
+ const payload = JSON.stringify(visibleApps.map(app => ({
83
+ id: app.name,
84
+ href: appHref(app),
85
+ kind: app.kind
86
+ })));
87
+ return `<!DOCTYPE html>
88
+ <html lang="zh-CN">
89
+ <head>
90
+ <meta charset="utf-8" />
91
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
92
+ <title>ALemonJS 应用入口</title>
93
+ <style>
94
+ :root {
95
+ --bg: #f3efe5;
96
+ --panel: rgba(255, 251, 245, 0.88);
97
+ --panel-strong: #fffaf2;
98
+ --text: #17212b;
99
+ --muted: #6a7684;
100
+ --line: rgba(23, 33, 43, 0.08);
101
+ --accent: #d96c28;
102
+ --accent-strong: #a54a13;
103
+ --shadow: 0 24px 60px rgba(47, 35, 20, 0.12);
104
+ }
105
+ * { box-sizing: border-box; }
106
+ html, body { margin: 0; min-height: 100%; }
6
107
  body {
7
- width: 35em;
108
+ font-family: "SF Pro Display", "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
109
+ color: var(--text);
110
+ background:
111
+ radial-gradient(circle at top left, rgba(217, 108, 40, 0.18), transparent 28%),
112
+ radial-gradient(circle at right center, rgba(78, 140, 124, 0.14), transparent 26%),
113
+ linear-gradient(180deg, #f8f4ec 0%, var(--bg) 100%);
114
+ }
115
+ .page {
116
+ width: min(1680px, calc(100vw - 32px));
8
117
  margin: 0 auto;
9
- font-family: Tahoma, Verdana, Arial, sans-serif;
10
- background-color: #f8f8f8;
11
- color: #333;
12
- }
13
- h1 {
14
- color: #0099cc;
15
- margin-top: 2em;
16
- }
17
- .footer {
18
- margin-top: 3em;
19
- font-size: 0.9em;
20
- color: #888;
21
- }
22
- a { color: #0099cc; }
23
- `;
24
- const head = Head(null, Title('欢迎使用 ALemonJS!'), Style(style));
25
- const body = Body(null, H1('ALemonJS 启动成功!'), P(null, '已成功通过 ', A({ href: 'https://alemonjs.com', target: '_blank' }, 'ALemonJS 框架'), ' 启动。'), P(null, '如果想访问主应用,请访问, ', A({ href: '/app', target: '_blank' }, '/app'), '(对应根目录index.html)'), P(null, '如果想访问其他应用,请访问 ', A({ href: '/apps/[package-name]', target: '_blank' }, '/apps/[package-name]'), '。(对应/packages/[package-name]/index.html)'), Div({ className: 'footer' }, '— 感谢选择 ALemonJS。'));
26
- return Html(null, head, body);
27
- }
28
- }
29
- var hello = App.renderToHtml();
118
+ padding: clamp(28px, 4vw, 56px) 0 64px;
119
+ }
120
+ .hero {
121
+ position: relative;
122
+ overflow: hidden;
123
+ padding: clamp(28px, 4vw, 56px);
124
+ border-radius: 32px;
125
+ background: linear-gradient(135deg, rgba(255, 250, 242, 0.95), rgba(255, 244, 231, 0.88));
126
+ border: 1px solid rgba(255, 255, 255, 0.72);
127
+ box-shadow: var(--shadow);
128
+ }
129
+ .hero::after {
130
+ content: "";
131
+ position: absolute;
132
+ inset: auto -8% -48% auto;
133
+ width: min(38vw, 520px);
134
+ aspect-ratio: 1;
135
+ border-radius: 999px;
136
+ background: radial-gradient(circle, rgba(217, 108, 40, 0.18), transparent 62%);
137
+ pointer-events: none;
138
+ }
139
+ .hero-kicker {
140
+ margin: 0 0 12px;
141
+ font-size: clamp(14px, 1.2vw, 18px);
142
+ letter-spacing: 0.18em;
143
+ text-transform: uppercase;
144
+ color: var(--accent-strong);
145
+ }
146
+ .hero-title {
147
+ margin: 0;
148
+ max-width: 12ch;
149
+ font-size: clamp(40px, 7vw, 86px);
150
+ line-height: 0.95;
151
+ letter-spacing: -0.04em;
152
+ }
153
+ .hero-desc {
154
+ margin: 18px 0 0;
155
+ max-width: 56rem;
156
+ font-size: clamp(18px, 2vw, 28px);
157
+ line-height: 1.6;
158
+ color: var(--muted);
159
+ }
160
+ .hero-meta {
161
+ display: flex;
162
+ flex-wrap: wrap;
163
+ gap: 14px;
164
+ margin-top: 28px;
165
+ }
166
+ .hero-pill {
167
+ display: inline-flex;
168
+ align-items: center;
169
+ gap: 10px;
170
+ padding: 12px 18px;
171
+ border-radius: 999px;
172
+ background: rgba(255, 255, 255, 0.78);
173
+ border: 1px solid rgba(23, 33, 43, 0.08);
174
+ font-size: clamp(14px, 1.4vw, 18px);
175
+ }
176
+ .hero-pill strong {
177
+ color: var(--accent-strong);
178
+ }
179
+ .section-head {
180
+ display: flex;
181
+ align-items: end;
182
+ justify-content: space-between;
183
+ gap: 20px;
184
+ margin: 28px 0 18px;
185
+ padding: 0 6px;
186
+ }
187
+ .section-title {
188
+ margin: 0;
189
+ font-size: clamp(28px, 3vw, 42px);
190
+ }
191
+ .section-note {
192
+ margin: 0;
193
+ font-size: clamp(14px, 1.3vw, 18px);
194
+ color: var(--muted);
195
+ }
196
+ .app-grid {
197
+ display: grid;
198
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
199
+ gap: 18px;
200
+ }
201
+ .app-card {
202
+ display: flex;
203
+ flex-direction: column;
204
+ gap: 18px;
205
+ min-height: 280px;
206
+ padding: 24px;
207
+ border-radius: 28px;
208
+ text-decoration: none;
209
+ color: inherit;
210
+ background: var(--panel);
211
+ border: 1px solid var(--line);
212
+ box-shadow: 0 14px 40px rgba(30, 37, 44, 0.08);
213
+ transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease;
214
+ }
215
+ .app-card:hover {
216
+ transform: translateY(-4px);
217
+ border-color: rgba(217, 108, 40, 0.28);
218
+ box-shadow: 0 24px 50px rgba(30, 37, 44, 0.12);
219
+ }
220
+ .app-card__top,
221
+ .app-card__bottom {
222
+ display: flex;
223
+ align-items: center;
224
+ justify-content: space-between;
225
+ gap: 16px;
226
+ }
227
+ .app-eyebrow {
228
+ margin: 0 0 8px;
229
+ font-size: 13px;
230
+ letter-spacing: 0.16em;
231
+ text-transform: uppercase;
232
+ color: var(--accent-strong);
233
+ }
234
+ .app-title {
235
+ margin: 0;
236
+ font-size: clamp(28px, 2.8vw, 38px);
237
+ line-height: 1;
238
+ }
239
+ .app-desc {
240
+ margin: 0;
241
+ font-size: clamp(16px, 1.4vw, 20px);
242
+ line-height: 1.7;
243
+ color: var(--muted);
244
+ }
245
+ .app-tags {
246
+ display: flex;
247
+ flex-wrap: wrap;
248
+ gap: 10px;
249
+ }
250
+ .app-tag {
251
+ padding: 8px 12px;
252
+ border-radius: 999px;
253
+ background: rgba(217, 108, 40, 0.1);
254
+ color: var(--accent-strong);
255
+ font-size: 14px;
256
+ }
257
+ .app-rank {
258
+ min-width: 84px;
259
+ text-align: right;
260
+ }
261
+ .app-rank__label,
262
+ .app-link {
263
+ display: block;
264
+ font-size: 13px;
265
+ color: var(--muted);
266
+ }
267
+ .app-rank__value {
268
+ font-size: clamp(24px, 2vw, 30px);
269
+ font-weight: 700;
270
+ }
271
+ .app-action {
272
+ padding: 12px 18px;
273
+ border-radius: 999px;
274
+ background: #1d2d38;
275
+ color: #fff;
276
+ font-size: 15px;
277
+ }
278
+ .empty-state {
279
+ padding: 32px;
280
+ border-radius: 28px;
281
+ background: var(--panel-strong);
282
+ border: 1px dashed rgba(23, 33, 43, 0.16);
283
+ }
284
+ .empty-state__title {
285
+ margin: 0;
286
+ font-size: 24px;
287
+ }
288
+ .empty-state__desc {
289
+ margin: 10px 0 0;
290
+ color: var(--muted);
291
+ font-size: 16px;
292
+ }
293
+ @media (max-width: 820px) {
294
+ .page { width: min(100vw - 20px, 100%); padding-top: 18px; }
295
+ .hero { border-radius: 24px; padding: 22px; }
296
+ .section-head { align-items: start; flex-direction: column; }
297
+ .app-card { min-height: 0; padding: 20px; border-radius: 22px; }
298
+ .app-card__top,
299
+ .app-card__bottom { align-items: start; flex-direction: column; }
300
+ .app-rank { text-align: left; min-width: 0; }
301
+ }
302
+ </style>
303
+ </head>
304
+ <body>
305
+ <main class="page">
306
+ <section class="hero">
307
+ <p class="hero-kicker">ALemonJS Launchpad</p>
308
+ <h1 class="hero-title">欢迎使用阿柠檬机器人应用服务</h1>
309
+ <p class="hero-desc">这里会自动列出当前已准备好的主应用与插件。</p>
310
+ <div class="hero-meta">
311
+ <span class="hero-pill"><strong>${visibleApps.length}</strong> 个可访问入口</span>
312
+ <span class="hero-pill">主应用统一在 <strong>/app</strong></span>
313
+ <span class="hero-pill">插件统一在 <strong>/apps/&lt;name&gt;</strong></span>
314
+ </div>
315
+ </section>
316
+ <section>
317
+ <div class="section-head">
318
+ <div>
319
+ <h2 class="section-title">应用列表</h2>
320
+ <p class="section-note">点击次数越高,卡片排序越靠前。排序记录仅保存在当前浏览器本地。</p>
321
+ </div>
322
+ </div>
323
+ <div class="app-grid" id="app-grid">${cards}</div>
324
+ </section>
325
+ </main>
326
+ <script>
327
+ (() => {
328
+ const storageKey = 'alemonjs:launchpad:clicks';
329
+ const apps = ${payload};
330
+ const grid = document.getElementById('app-grid');
331
+ if (!grid) return;
332
+
333
+ const readClicks = () => {
334
+ try {
335
+ return JSON.parse(localStorage.getItem(storageKey) || '{}');
336
+ } catch {
337
+ return {};
338
+ }
339
+ };
340
+
341
+ const writeClicks = (value) => {
342
+ localStorage.setItem(storageKey, JSON.stringify(value));
343
+ };
344
+
345
+ const scoreOf = (id, clicks) => Number(clicks[id] || 0);
346
+ const clicks = readClicks();
347
+ const cards = Array.from(grid.querySelectorAll('[data-app-id]'));
348
+
349
+ const refreshCounts = () => {
350
+ cards.forEach((card) => {
351
+ const id = card.getAttribute('data-app-id') || '';
352
+ const node = card.querySelector('[data-click-count]');
353
+ if (node) {
354
+ node.textContent = String(scoreOf(id, clicks));
355
+ }
356
+ });
357
+ };
358
+
359
+ const sortCards = () => {
360
+ cards
361
+ .sort((left, right) => {
362
+ const leftId = left.getAttribute('data-app-id') || '';
363
+ const rightId = right.getAttribute('data-app-id') || '';
364
+ const diff = scoreOf(rightId, clicks) - scoreOf(leftId, clicks);
365
+ if (diff !== 0) return diff;
366
+ return leftId.localeCompare(rightId);
367
+ })
368
+ .forEach(card => grid.appendChild(card));
369
+ };
370
+
371
+ cards.forEach((card) => {
372
+ card.addEventListener('click', () => {
373
+ const id = card.getAttribute('data-app-id') || '';
374
+ clicks[id] = scoreOf(id, clicks) + 1;
375
+ writeClicks(clicks);
376
+ refreshCounts();
377
+ sortCards();
378
+ });
379
+ });
380
+
381
+ refreshCounts();
382
+ sortCards();
383
+ })();
384
+ </script>
385
+ </body>
386
+ </html>`;
387
+ };
388
+ var hello_html = renderHelloHtml([]);
30
389
 
31
- export { hello as default };
390
+ export { hello_html as default, renderHelloHtml };
@@ -2,7 +2,7 @@ import KoaRouter from 'koa-router';
2
2
  import fs__default, { existsSync } from 'fs';
3
3
  import path__default, { join, dirname } from 'path';
4
4
  import mime from 'mime-types';
5
- import hello from './hello.html.js';
5
+ import { renderHelloHtml } from './hello.html.js';
6
6
  import { safePath, getModuelFile, formatPath, isValidPackageName } from './utils.js';
7
7
  import { collectMiddlewares, runMiddlewares } from './middleware.js';
8
8
  import module$1 from 'module';
@@ -16,7 +16,7 @@ import 'os';
16
16
  import 'flatted';
17
17
  import '../../../../common/cbp/runtime.js';
18
18
  import 'ws';
19
- import { listRuntimeApps, getRuntimeApp, toRuntimeAppSnapshot, listRuntimeAppKoaRouters, hasRuntimeAppCapability, getRuntimeAppKoaRouters } from '../../store.js';
19
+ import { listRuntimeApps, getRuntimeApp, toRuntimeAppSnapshot, listRuntimeAppKoaRouters, hasRuntimeAppCapability, getChildrenApp, getRuntimeAppKoaRouters } from '../../store.js';
20
20
  import { dispatchHttpError } from '../../lifecycle-callbacks.js';
21
21
 
22
22
  const initRequire = () => { };
@@ -38,10 +38,46 @@ const resolvePackageRoot = (startDir) => {
38
38
  const readWebRootConfig = (packageRoot) => {
39
39
  const packageJsonPath = path__default.join(packageRoot, 'package.json');
40
40
  if (!existsSync(packageJsonPath)) {
41
+ if (existsSync(path__default.join(packageRoot, 'dist', 'index.html'))) {
42
+ return 'dist';
43
+ }
41
44
  return '';
42
45
  }
43
46
  const pkg = require$1(packageJsonPath) ?? {};
44
- return pkg?.alemonjs?.web?.root ?? '';
47
+ const configuredRoot = pkg?.alemonjs?.web?.root ?? '';
48
+ if (typeof configuredRoot === 'string' && configuredRoot.trim()) {
49
+ return configuredRoot;
50
+ }
51
+ if (existsSync(path__default.join(packageRoot, 'dist', 'index.html'))) {
52
+ return 'dist';
53
+ }
54
+ return '';
55
+ };
56
+ const matchBasePath = (requestPath, basePath) => {
57
+ if (!basePath) {
58
+ return requestPath;
59
+ }
60
+ if (requestPath === basePath) {
61
+ return '/';
62
+ }
63
+ if (requestPath.startsWith(`${basePath}/`)) {
64
+ return requestPath.slice(basePath.length) || '/';
65
+ }
66
+ return '';
67
+ };
68
+ const rewriteCtxPath = async (ctx, nextPath, handler) => {
69
+ const search = ctx.querystring ? `?${ctx.querystring}` : '';
70
+ const originalUrl = ctx.url;
71
+ const originalReqUrl = ctx.req.url;
72
+ ctx.url = `${nextPath}${search}`;
73
+ ctx.req.url = `${nextPath}${search}`;
74
+ try {
75
+ await handler();
76
+ }
77
+ finally {
78
+ ctx.url = originalUrl;
79
+ ctx.req.url = originalReqUrl;
80
+ }
45
81
  };
46
82
  const denyRuntimeAppAccess = (ctx, appName, capability) => {
47
83
  const runtimeApp = getRuntimeApp(appName);
@@ -105,63 +141,83 @@ const denyRuntimeAppAccess = (ctx, appName, capability) => {
105
141
  };
106
142
  const dispatchRegisteredKoaRouters = async (ctx) => {
107
143
  const registeredRouters = listRuntimeAppKoaRouters();
108
- for (const item of registeredRouters) {
109
- const runtimeApp = getRuntimeApp(item.name);
110
- if (!runtimeApp || !runtimeApp.enabled || runtimeApp.status !== 'ready' || !hasRuntimeAppCapability(item.name, 'httpApi')) {
144
+ const candidates = new Set(registeredRouters.map(item => item.name));
145
+ const runtimeApps = listRuntimeApps();
146
+ runtimeApps.forEach(item => {
147
+ if (item.capabilities?.httpApi) {
148
+ candidates.add(item.name);
149
+ }
150
+ });
151
+ for (const appName of candidates) {
152
+ const runtimeApp = getRuntimeApp(appName);
153
+ if (!runtimeApp || !runtimeApp.enabled || runtimeApp.status !== 'ready' || !hasRuntimeAppCapability(appName, 'httpApi')) {
111
154
  continue;
112
155
  }
113
- const routers = getRuntimeAppKoaRouters(item.name);
156
+ const registerRouters = getChildrenApp(appName)?.register?.koaRouter;
157
+ const storedRouters = getRuntimeAppKoaRouters(appName);
158
+ const routers = (storedRouters.length ? storedRouters : Array.isArray(registerRouters) ? registerRouters : registerRouters ? [registerRouters] : []).filter(Boolean);
159
+ const aliasBases = appName === 'main' ? ['', '/app'] : ['', `/apps/${appName}`];
114
160
  for (const koaRouter of routers) {
115
- try {
116
- const matchedContext = ctx;
117
- const beforeMatched = Array.isArray(matchedContext.matched) ? matchedContext.matched.length : 0;
118
- const beforeStatus = ctx.status;
119
- const beforeBody = ctx.body;
120
- const beforeMatchedRoute = ctx._matchedRoute;
121
- const beforeRouterPath = ctx.routerPath;
122
- let fallthrough = false;
123
- await koaRouter.routes()(ctx, async () => {
124
- fallthrough = true;
125
- });
126
- const afterMatched = Array.isArray(matchedContext.matched) ? matchedContext.matched.length : 0;
127
- const afterMatchedRoute = ctx._matchedRoute;
128
- const afterRouterPath = ctx.routerPath;
129
- const handled = afterMatched > beforeMatched
130
- || afterMatchedRoute !== beforeMatchedRoute
131
- || afterRouterPath !== beforeRouterPath
132
- || ctx.status !== beforeStatus
133
- || ctx.body !== beforeBody
134
- || !fallthrough;
135
- if (!handled) {
161
+ for (const basePath of aliasBases) {
162
+ const rewrittenPath = matchBasePath(ctx.path, basePath);
163
+ if (!rewrittenPath) {
136
164
  continue;
137
165
  }
138
- await koaRouter.allowedMethods()(ctx, async () => { });
139
- return true;
140
- }
141
- catch (error) {
142
- const handled = await dispatchHttpError({
143
- ctx,
144
- error,
145
- appName: item.name,
146
- path: ctx.path,
147
- method: ctx.method,
148
- kind: 'koa-router'
149
- });
150
- if (handled) {
166
+ try {
167
+ const matchedContext = ctx;
168
+ const beforeMatched = Array.isArray(matchedContext.matched) ? matchedContext.matched.length : 0;
169
+ const beforeStatus = ctx.status;
170
+ const beforeBody = ctx.body;
171
+ const beforeMatchedRoute = ctx._matchedRoute;
172
+ const beforeRouterPath = ctx.routerPath;
173
+ let fallthrough = false;
174
+ await rewriteCtxPath(ctx, rewrittenPath, async () => {
175
+ await koaRouter.routes()(ctx, async () => {
176
+ fallthrough = true;
177
+ });
178
+ });
179
+ const afterMatched = Array.isArray(matchedContext.matched) ? matchedContext.matched.length : 0;
180
+ const afterMatchedRoute = ctx._matchedRoute;
181
+ const afterRouterPath = ctx.routerPath;
182
+ const handled = afterMatched > beforeMatched
183
+ || afterMatchedRoute !== beforeMatchedRoute
184
+ || afterRouterPath !== beforeRouterPath
185
+ || ctx.status !== beforeStatus
186
+ || ctx.body !== beforeBody
187
+ || !fallthrough;
188
+ if (!handled) {
189
+ continue;
190
+ }
191
+ await rewriteCtxPath(ctx, rewrittenPath, async () => {
192
+ await koaRouter.allowedMethods()(ctx, async () => { });
193
+ });
194
+ return true;
195
+ }
196
+ catch (error) {
197
+ const handled = await dispatchHttpError({
198
+ ctx,
199
+ error,
200
+ appName,
201
+ path: ctx.path,
202
+ method: ctx.method,
203
+ kind: 'koa-router'
204
+ });
205
+ if (handled) {
206
+ return true;
207
+ }
208
+ logger.warn({
209
+ code: ResultCode.Fail,
210
+ message: `Error request ${ctx.path}:`,
211
+ data: error instanceof Error ? error.message : String(error)
212
+ });
213
+ ctx.status = 500;
214
+ ctx.body = {
215
+ code: 500,
216
+ message: '处理 Koa Router 请求时发生错误。',
217
+ error: error instanceof Error ? error.message : String(error)
218
+ };
151
219
  return true;
152
220
  }
153
- logger.warn({
154
- code: ResultCode.Fail,
155
- message: `Error request ${ctx.path}:`,
156
- data: error instanceof Error ? error.message : String(error)
157
- });
158
- ctx.status = 500;
159
- ctx.body = {
160
- code: 500,
161
- message: '处理 Koa Router 请求时发生错误。',
162
- error: error instanceof Error ? error.message : String(error)
163
- };
164
- return true;
165
221
  }
166
222
  }
167
223
  }
@@ -170,7 +226,7 @@ const dispatchRegisteredKoaRouters = async (ctx) => {
170
226
  router.get('/', ctx => {
171
227
  ctx.status = 200;
172
228
  ctx.set('Content-Type', 'text/html; charset=utf-8');
173
- ctx.body = hello;
229
+ ctx.body = renderHelloHtml(listRuntimeApps());
174
230
  });
175
231
  router.get('api/online', ctx => {
176
232
  ctx.status = 200;
@@ -221,7 +277,7 @@ router.use(async (ctx, next) => {
221
277
  }
222
278
  await next();
223
279
  });
224
- router.all('app/{*path}', async (ctx) => {
280
+ const handleMainAppRequest = async (ctx) => {
225
281
  if (!process.env.input) {
226
282
  ctx.status = 400;
227
283
  ctx.body = {
@@ -391,11 +447,11 @@ router.all('app/{*path}', async (ctx) => {
391
447
  };
392
448
  }
393
449
  }
394
- });
395
- router.all('app', ctx => {
396
- ctx.redirect('/app/');
397
- });
398
- router.all('apps/:app/{*path}', async (ctx) => {
450
+ };
451
+ router.all('app', handleMainAppRequest);
452
+ router.all('app/', handleMainAppRequest);
453
+ router.all('app/{*path}', handleMainAppRequest);
454
+ const handlePluginAppRequest = async (ctx) => {
399
455
  const appName = ctx.params.app;
400
456
  if (!isValidPackageName(appName)) {
401
457
  ctx.status = 400;
@@ -564,11 +620,9 @@ router.all('apps/:app/{*path}', async (ctx) => {
564
620
  };
565
621
  }
566
622
  }
567
- });
568
- router.all('apps/:name', ctx => {
569
- if (ctx.path === `/apps/${ctx.params.name}`) {
570
- ctx.redirect(`/apps/${ctx.params.name}/`);
571
- }
572
- });
623
+ };
624
+ router.all('apps/:app', handlePluginAppRequest);
625
+ router.all('apps/:app/', handlePluginAppRequest);
626
+ router.all('apps/:app/{*path}', handlePluginAppRequest);
573
627
 
574
628
  export { router as default };
@@ -36,7 +36,7 @@ const getModuelFile = (dir) => {
36
36
  };
37
37
  const formatPath = (pathValue) => {
38
38
  if (!pathValue || pathValue === '/') {
39
- return '/index.html';
39
+ return 'index.html';
40
40
  }
41
41
  const pates = pathValue.split('/');
42
42
  const lastPath = pates[pates.length - 1];
@@ -21,11 +21,20 @@ const resolvePackageRoot = (startDir) => {
21
21
  }
22
22
  return startDir;
23
23
  };
24
+ const detectDefaultWebRoot = (packageRoot) => {
25
+ if (existsSync(join(packageRoot, 'dist', 'index.html'))) {
26
+ return 'dist';
27
+ }
28
+ if (existsSync(join(packageRoot, 'index.html'))) {
29
+ return '';
30
+ }
31
+ return null;
32
+ };
24
33
  const detectWebCapability = (startDir) => {
25
34
  const packageRoot = resolvePackageRoot(startDir);
26
35
  const packageJsonPath = join(packageRoot, 'package.json');
27
36
  if (!existsSync(packageJsonPath)) {
28
- return existsSync(join(packageRoot, 'index.html'));
37
+ return Boolean(detectDefaultWebRoot(packageRoot));
29
38
  }
30
39
  try {
31
40
  const pkg = require$1(packageJsonPath) ?? {};
@@ -36,7 +45,7 @@ const detectWebCapability = (startDir) => {
36
45
  }
37
46
  catch {
38
47
  }
39
- return existsSync(join(packageRoot, 'index.html'));
48
+ return Boolean(detectDefaultWebRoot(packageRoot));
40
49
  };
41
50
  const loadChildren = async (mainPath, appName) => {
42
51
  if (!mainPath || typeof mainPath !== 'string') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alemonjs",
3
- "version": "2.1.83-alpha.6",
3
+ "version": "2.1.83-alpha.8",
4
4
  "description": "bot script",
5
5
  "author": "lemonade",
6
6
  "license": "MIT",