alemonjs 2.1.0-alpha.53 → 2.1.0-alpha.55

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.
@@ -113,7 +113,6 @@ const cbpClient = (url, options = {}) => {
113
113
  }
114
114
  }
115
115
  else if (parsedMessage.name) {
116
- console.log('--- parsedMessage ----', parsedMessage);
117
116
  onProcessor(parsedMessage.name, parsedMessage, parsedMessage.value);
118
117
  }
119
118
  }
@@ -0,0 +1,2 @@
1
+ declare const _default: string;
2
+ export default _default;
@@ -0,0 +1,31 @@
1
+ import { Component, Head, Title, Style, Body, H1, P, A, Div, Html } from '../../core/react.js';
2
+
3
+ class App extends Component {
4
+ render() {
5
+ const style = `
6
+ body {
7
+ width: 35em;
8
+ 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 框架'), ' 启动。'), Div({ className: 'footer' }, '— 感谢选择 AlemonJS。'));
26
+ return Html(null, head, body);
27
+ }
28
+ }
29
+ var hello = App.renderToHtml();
30
+
31
+ export { hello as default };
@@ -1,21 +1,13 @@
1
1
  import KoaRouter from 'koa-router';
2
- import fs, { existsSync } from 'fs';
3
- import path, { join, dirname } from 'path';
4
- import mime from 'mime-types';
5
- import { createRequire } from 'module';
6
- import { html } from '../processor/hello.html.js';
7
- import { getModuelFile, formatPath } from './utils.js';
8
- import { collectMiddlewares, runMiddlewares } from './middleware.js';
2
+ import hello from './hello.html.js';
9
3
 
10
- const require = createRequire(import.meta.url);
11
- const mainDirMap = new Map();
12
4
  const router = new KoaRouter({
13
5
  prefix: '/'
14
6
  });
15
7
  router.get('/', ctx => {
16
8
  ctx.status = 200;
17
9
  ctx.set('Content-Type', 'text/html; charset=utf-8');
18
- ctx.body = html;
10
+ ctx.body = hello;
19
11
  });
20
12
  router.get('api/online', ctx => {
21
13
  ctx.status = 200;
@@ -25,231 +17,5 @@ router.get('api/online', ctx => {
25
17
  data: null
26
18
  };
27
19
  });
28
- router.all('app/{*path}', async (ctx) => {
29
- if (!process.env.input) {
30
- ctx.status = 400;
31
- ctx.body = {
32
- code: 400,
33
- message: '没有主要入口文件',
34
- data: null
35
- };
36
- return;
37
- }
38
- const rootPath = process.cwd();
39
- const apiPath = '/app/api';
40
- if (ctx.path.startsWith(apiPath)) {
41
- const mainPath = join(rootPath, process.env.input);
42
- if (!existsSync(mainPath)) {
43
- ctx.status = 400;
44
- ctx.body = {
45
- code: 400,
46
- message: '未找到主要入口文件',
47
- data: 'existsSync input'
48
- };
49
- return;
50
- }
51
- const mainDir = dirname(mainPath);
52
- try {
53
- const dir = join(mainDir, 'route', ctx.path?.replace(apiPath, '/api') || '');
54
- if (existsSync(dir) && fs.statSync(dir).isFile()) {
55
- ctx.status = 404;
56
- ctx.body = {
57
- code: 404,
58
- message: `API '${ctx.path}' 未找到。`,
59
- data: 'route path is file not directory'
60
- };
61
- return;
62
- }
63
- const modulePath = getModuelFile(dir);
64
- if (!modulePath) {
65
- ctx.status = 404;
66
- ctx.body = {
67
- code: 404,
68
- message: `API '${ctx.path}' 未找到。`,
69
- data: 'existsSync modulePath'
70
- };
71
- return;
72
- }
73
- const apiModule = await import(`file://${modulePath}`);
74
- const handler = apiModule[ctx.method];
75
- if (!handler || typeof handler !== 'function') {
76
- ctx.status = 405;
77
- return;
78
- }
79
- const middlewares = await collectMiddlewares(modulePath);
80
- await runMiddlewares(middlewares, ctx, handler);
81
- }
82
- catch (err) {
83
- console.error(`Error handling API request ${ctx.path}`);
84
- ctx.status = 500;
85
- ctx.body = {
86
- code: 500,
87
- message: '处理 API 请求时发生错误。',
88
- error: err.message
89
- };
90
- }
91
- return;
92
- }
93
- if (ctx.method !== 'GET') {
94
- ctx.status = 405;
95
- return;
96
- }
97
- let root = '';
98
- const resourcePath = formatPath(ctx.params?.path);
99
- try {
100
- const pkg = require(path.join(rootPath, 'package.json')) ?? {};
101
- root = pkg.alemonjs?.web?.root ?? '';
102
- }
103
- catch (err) {
104
- ctx.status = 500;
105
- ctx.body = {
106
- code: 500,
107
- message: '加载 package.json 时发生错误。',
108
- error: err.message
109
- };
110
- return;
111
- }
112
- const fullPath = root ? path.join(rootPath, root, resourcePath) : path.join(rootPath, resourcePath);
113
- try {
114
- const file = await fs.promises.readFile(fullPath);
115
- const mimeType = mime.lookup(fullPath) || 'application/octet-stream';
116
- ctx.set('Content-Type', mimeType);
117
- ctx.body = file;
118
- ctx.status = 200;
119
- }
120
- catch (err) {
121
- if (err?.status === 404) {
122
- ctx.status = 404;
123
- ctx.body = {
124
- code: 404,
125
- message: '资源中未找到。',
126
- data: null
127
- };
128
- }
129
- else {
130
- ctx.status = 500;
131
- ctx.body = {
132
- code: 500,
133
- message: '加载资源时发生服务器错误。',
134
- error: err.message
135
- };
136
- }
137
- }
138
- });
139
- router.all('app', ctx => {
140
- ctx.redirect('/app/');
141
- });
142
- router.all('apps/:app/{*path}', async (ctx) => {
143
- const appName = ctx.params.app;
144
- const apiPath = `/apps/${appName}/api`;
145
- if (ctx.path.startsWith(apiPath)) {
146
- try {
147
- if (!mainDirMap.has(appName)) {
148
- const mainPath = require.resolve(appName);
149
- if (!existsSync(mainPath)) {
150
- ctx.status = 400;
151
- ctx.body = {
152
- code: 400,
153
- message: '未找到主要入口文件',
154
- data: null
155
- };
156
- return;
157
- }
158
- const mainDir = dirname(mainPath);
159
- mainDirMap.set(appName, mainDir);
160
- }
161
- const dir = join(mainDirMap.get(appName), 'route', ctx.path?.replace(apiPath, '/api') || '');
162
- if (existsSync(dir) && fs.statSync(dir).isFile()) {
163
- ctx.status = 404;
164
- ctx.body = {
165
- code: 404,
166
- message: `API 'route/${ctx.path}' 未找到。`,
167
- data: 'route path is file not directory'
168
- };
169
- return;
170
- }
171
- const modulePath = getModuelFile(dir);
172
- if (!modulePath) {
173
- ctx.status = 404;
174
- ctx.body = {
175
- code: 404,
176
- message: `API '${ctx.path}' 未找到。`,
177
- data: 'existsSync modulePath'
178
- };
179
- return;
180
- }
181
- const apiModule = await import(`file://${modulePath}`);
182
- const handler = apiModule[ctx.method];
183
- if (!handler || typeof handler !== 'function') {
184
- ctx.status = 405;
185
- return;
186
- }
187
- const middlewares = await collectMiddlewares(modulePath);
188
- await runMiddlewares(middlewares, ctx, handler);
189
- }
190
- catch (err) {
191
- logger.warn(`Error request ${ctx.path}:`, err?.message || '');
192
- ctx.status = 500;
193
- ctx.body = {
194
- code: 500,
195
- message: '处理 API 请求时发生错误。',
196
- error: err.message
197
- };
198
- }
199
- return;
200
- }
201
- if (ctx.method !== 'GET') {
202
- ctx.status = 405;
203
- return;
204
- }
205
- const rootPath = path.join(process.cwd(), 'node_modules', appName);
206
- const resourcePath = formatPath(ctx.params?.path);
207
- let root = '';
208
- try {
209
- const pkg = require(`${appName}/package`) ?? {};
210
- root = pkg?.alemonjs?.web?.root ?? '';
211
- }
212
- catch (err) {
213
- ctx.status = 500;
214
- ctx.body = {
215
- code: 500,
216
- message: '加载 package.json 时发生错误。',
217
- error: err.message
218
- };
219
- return;
220
- }
221
- const fullPath = root ? path.join(rootPath, root, resourcePath) : path.join(rootPath, resourcePath);
222
- try {
223
- const file = await fs.promises.readFile(fullPath);
224
- const mimeType = mime.lookup(fullPath) || 'application/octet-stream';
225
- ctx.set('Content-Type', mimeType);
226
- ctx.body = file;
227
- ctx.status = 200;
228
- }
229
- catch (err) {
230
- if (err?.status === 404) {
231
- ctx.status = 404;
232
- ctx.body = {
233
- code: 404,
234
- message: `资源 '${resourcePath}' 在子应用 '${appName}' 中未找到。`,
235
- data: null
236
- };
237
- }
238
- else {
239
- logger.warn(`Error request ${ctx.path}:`, err?.message || '');
240
- ctx.status = 500;
241
- ctx.body = {
242
- code: 500,
243
- message: `加载子应用 '${appName}' 资源时发生服务器错误。`,
244
- error: err.message
245
- };
246
- }
247
- }
248
- });
249
- router.all('apps/:name', ctx => {
250
- if (ctx.path === `/apps/${ctx.params.name}`) {
251
- ctx.redirect(`/apps/${ctx.params.name}/`);
252
- }
253
- });
254
20
 
255
21
  export { router as default };
@@ -198,7 +198,6 @@ const setPlatformClient = (originId, ws) => {
198
198
  }
199
199
  platformClient.set(originId, ws);
200
200
  ws.on('message', (message) => {
201
- console.log('平台消息', message);
202
201
  try {
203
202
  const parsedMessage = flattedJSON.parse(message.toString());
204
203
  logger.debug({
package/lib/client.d.ts CHANGED
@@ -1,2 +1 @@
1
- declare const main: () => void;
2
- export default main;
1
+ export {};
package/lib/client.js CHANGED
@@ -29,7 +29,18 @@ import './app/message-api.js';
29
29
  import './app/message-format.js';
30
30
  import './process/platform.js';
31
31
  import './process/module.js';
32
+ import { createServer } from './server/main.js';
32
33
 
34
+ const mainServer = () => {
35
+ const port = process.env.serverPort;
36
+ if (!port) {
37
+ return;
38
+ }
39
+ createServer(port, () => {
40
+ const httpURL = `http://127.0.0.1:${port}`;
41
+ logger.info(`应用服务器: ${httpURL}`);
42
+ });
43
+ };
33
44
  const main = () => {
34
45
  const login = process.env.login ?? '';
35
46
  const platform = process.env.platform ?? '';
@@ -61,6 +72,7 @@ const mainProcess = () => {
61
72
  const data = typeof msg === 'string' ? JSON.parse(msg) : msg;
62
73
  if (data?.type === 'start') {
63
74
  main();
75
+ mainServer();
64
76
  }
65
77
  else if (data?.type === 'stop') {
66
78
  process.exit(0);
@@ -73,5 +85,3 @@ const mainProcess = () => {
73
85
  }
74
86
  };
75
87
  mainProcess();
76
-
77
- export { main as default };
@@ -0,0 +1,28 @@
1
+ type Props = Record<string, any>;
2
+ type VNode = string | number | null | boolean | {
3
+ type: string | any;
4
+ props: Props;
5
+ children: any[];
6
+ };
7
+ export declare const DOCTYPE = "<!DOCTYPE html>";
8
+ export declare function createElement(type: string | any, props?: Props | null, ...children: any[]): VNode;
9
+ export declare class Component<P = object, S = object> {
10
+ props: P;
11
+ state: S;
12
+ constructor(props: P);
13
+ setState(partial: Partial<S>): void;
14
+ render(): VNode;
15
+ static renderToString(this: any): string;
16
+ static renderToHtml(this: any): string;
17
+ }
18
+ export declare function renderToString(vnode: any): string;
19
+ export declare const Html: (first?: any, ...restChildren: any[]) => VNode;
20
+ export declare const Head: (first?: any, ...restChildren: any[]) => VNode;
21
+ export declare const Body: (first?: any, ...restChildren: any[]) => VNode;
22
+ export declare const Title: (first?: any, ...restChildren: any[]) => VNode;
23
+ export declare const Style: (first?: any, ...restChildren: any[]) => VNode;
24
+ export declare const H1: (first?: any, ...restChildren: any[]) => VNode;
25
+ export declare const P: (first?: any, ...restChildren: any[]) => VNode;
26
+ export declare const A: (first?: any, ...restChildren: any[]) => VNode;
27
+ export declare const Div: (first?: any, ...restChildren: any[]) => VNode;
28
+ export {};
@@ -0,0 +1,147 @@
1
+ const DOCTYPE = '<!DOCTYPE html>';
2
+ const escapeHtml = (s) => String(s)
3
+ .replace(/&/g, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&#39;');
8
+ function createElement(type, props, ...children) {
9
+ props = props || {};
10
+ const flatChildren = children.flat(Infinity).filter((c) => c !== false && c !== null && c !== undefined);
11
+ return {
12
+ type,
13
+ props,
14
+ children: flatChildren
15
+ };
16
+ }
17
+ class Component {
18
+ props;
19
+ state;
20
+ constructor(props) {
21
+ this.props = props;
22
+ this.state = {};
23
+ }
24
+ setState(partial) {
25
+ this.state = Object.assign({}, this.state, partial);
26
+ }
27
+ render() {
28
+ return '';
29
+ }
30
+ static renderToString() {
31
+ return renderToString(createElement(this, null));
32
+ }
33
+ static renderToHtml() {
34
+ return DOCTYPE + this.renderToString();
35
+ }
36
+ }
37
+ function attrsToString(props = {}) {
38
+ const parts = [];
39
+ for (const key of Object.keys(props)) {
40
+ if (key === 'children') {
41
+ continue;
42
+ }
43
+ const val = props[key];
44
+ if (val === false || val === undefined || val === null) {
45
+ continue;
46
+ }
47
+ if (key === 'className') {
48
+ parts.push(`class="${escapeHtml(String(val))}"`);
49
+ continue;
50
+ }
51
+ if (key === 'style' && typeof val === 'object') {
52
+ const styleStr = Object.entries(val)
53
+ .map(([k, v]) => `${k.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase())}:${v}`)
54
+ .join(';');
55
+ parts.push(`style="${escapeHtml(styleStr)}"`);
56
+ continue;
57
+ }
58
+ if (val === true) {
59
+ parts.push(`${key}`);
60
+ continue;
61
+ }
62
+ parts.push(`${key}="${escapeHtml(String(val))}"`);
63
+ }
64
+ return parts.length ? ' ' + parts.join(' ') : '';
65
+ }
66
+ const voidTags = new Set([
67
+ 'area',
68
+ 'base',
69
+ 'br',
70
+ 'col',
71
+ 'embed',
72
+ 'hr',
73
+ 'img',
74
+ 'input',
75
+ 'link',
76
+ 'meta',
77
+ 'param',
78
+ 'source',
79
+ 'track',
80
+ 'wbr'
81
+ ]);
82
+ function renderToString(vnode) {
83
+ if (vnode === null || vnode === undefined || vnode === false) {
84
+ return '';
85
+ }
86
+ if (typeof vnode === 'string' || typeof vnode === 'number') {
87
+ return escapeHtml(String(vnode));
88
+ }
89
+ if (typeof vnode === 'object' && vnode.type === undefined) {
90
+ return (vnode.children ?? []).map(renderToString).join('');
91
+ }
92
+ if (typeof vnode.type === 'function') {
93
+ const Comp = vnode.type;
94
+ if (Comp.prototype && typeof Comp.prototype.render === 'function') {
95
+ const inst = new (Comp)(Object.assign({}, vnode.props, { children: vnode.children }));
96
+ const rendered = inst.render();
97
+ return renderToString(rendered);
98
+ }
99
+ else {
100
+ const rendered = (Comp)(Object.assign({}, vnode.props, { children: vnode.children }));
101
+ return renderToString(rendered);
102
+ }
103
+ }
104
+ const tag = String(vnode.type);
105
+ const props = vnode.props ?? {};
106
+ if (props?.dangerouslySetInnerHTML && typeof props.dangerouslySetInnerHTML === 'object' && props.dangerouslySetInnerHTML.__html !== null) {
107
+ const attrs = attrsToString(props);
108
+ const inner = String(props.dangerouslySetInnerHTML.__html);
109
+ return `<${tag}${attrs}>${inner}</${tag}>`;
110
+ }
111
+ const attrs = attrsToString(props);
112
+ if (voidTags.has(tag.toLowerCase())) {
113
+ return `<${tag}${attrs} />`;
114
+ }
115
+ const children = (vnode.children ?? []).map(renderToString).join('');
116
+ return `<${tag}${attrs}>${children}</${tag}>`;
117
+ }
118
+ function makeTag(tag) {
119
+ return function tagFactory(first, ...restChildren) {
120
+ const looksLikeProps = first !== null
121
+ && typeof first === 'object'
122
+ && !Array.isArray(first)
123
+ && Object.prototype.toString.call(first) === '[object Object]';
124
+ if (looksLikeProps) {
125
+ return createElement(tag, first, ...restChildren);
126
+ }
127
+ else {
128
+ const children = [];
129
+ if (first !== undefined && first !== null) {
130
+ children.push(first);
131
+ }
132
+ children.push(...restChildren);
133
+ return createElement(tag, null, ...children);
134
+ }
135
+ };
136
+ }
137
+ const Html = makeTag('html');
138
+ const Head = makeTag('head');
139
+ const Body = makeTag('body');
140
+ const Title = makeTag('title');
141
+ const Style = makeTag('style');
142
+ const H1 = makeTag('h1');
143
+ const P = makeTag('p');
144
+ const A = makeTag('a');
145
+ const Div = makeTag('div');
146
+
147
+ export { A, Body, Component, DOCTYPE, Div, H1, Head, Html, P, Style, Title, createElement, renderToString };
package/lib/index.d.ts CHANGED
@@ -4,4 +4,3 @@ export * from './core/index.js';
4
4
  export * from './cbp/index.js';
5
5
  export * from './app/index.js';
6
6
  export * from './main.js';
7
- export * from './app.js';
package/lib/index.js CHANGED
@@ -25,4 +25,3 @@ export { useObserver, useSubscribe } from './app/hook-use-subscribe.js';
25
25
  export { createDataFormat, createEventValue, format, sendToChannel, sendToUser } from './app/message-api.js';
26
26
  export { Ark, BT, Image, ImageFile, ImageURL, Link, MD, Mention, Text } from './app/message-format.js';
27
27
  export { start } from './main.js';
28
- export { App } from './app.js';
package/lib/main.js CHANGED
@@ -50,7 +50,9 @@ const start = (options = {}) => {
50
50
  options = { input: options };
51
51
  }
52
52
  const port = createOptionsByKey(options, 'port', defaultPort);
53
+ const serverPort = createOptionsByKey(options, 'serverPort', '');
53
54
  process.env.port = port;
55
+ process.env.serverPort = serverPort;
54
56
  cbpServer(port, () => {
55
57
  const httpURL = `http://127.0.0.1:${port}`;
56
58
  const wsURL = `ws://127.0.0.1:${port}`;
@@ -0,0 +1 @@
1
+ export declare const createServer: (port: any, listeningListener: any) => void;
@@ -0,0 +1,30 @@
1
+ import Koa from 'koa';
2
+ import koaCors from '@koa/cors';
3
+ import router from './routers/router.js';
4
+ import { ResultCode } from '../core/variable.js';
5
+ import 'fs';
6
+ import 'path';
7
+ import 'yaml';
8
+ import '../core/utils.js';
9
+
10
+ const createServer = (port, listeningListener) => {
11
+ try {
12
+ const app = new Koa();
13
+ app.use(router.routes());
14
+ app.use(router.allowedMethods());
15
+ app.use(koaCors({
16
+ origin: '*',
17
+ allowMethods: ['GET', 'POST', 'PUT', 'DELETE']
18
+ }));
19
+ app.listen(port, listeningListener);
20
+ }
21
+ catch (error) {
22
+ logger.error({
23
+ code: ResultCode.FailInternal,
24
+ message: '创建应用服务器失败',
25
+ data: error
26
+ });
27
+ }
28
+ };
29
+
30
+ export { createServer };
@@ -0,0 +1,2 @@
1
+ declare const _default: string;
2
+ export default _default;
@@ -0,0 +1,31 @@
1
+ import { Component, Head, Title, Style, Body, H1, P, A, Div, Html } from '../../core/react.js';
2
+
3
+ class App extends Component {
4
+ render() {
5
+ const style = `
6
+ body {
7
+ width: 35em;
8
+ 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();
30
+
31
+ export { hello as default };
@@ -0,0 +1,2 @@
1
+ export declare function collectMiddlewares(routeFile: string): Promise<Array<(ctx: any, next: () => Promise<void>) => Promise<void>>>;
2
+ export declare function runMiddlewares(middlewares: Array<(ctx: any, next: () => Promise<void>) => Promise<void>>, ctx: any, handler: (ctx: any) => Promise<void>): Promise<void>;
@@ -0,0 +1,40 @@
1
+ import { existsSync } from 'fs';
2
+ import path, { dirname } from 'path';
3
+
4
+ async function collectMiddlewares(routeFile) {
5
+ const middlewares = [];
6
+ let dir = dirname(routeFile);
7
+ const suffixes = ['.ts', '.js', '.cjs', '.mjs', '.tsx', '.jsx'];
8
+ while (true) {
9
+ for (const ext of suffixes) {
10
+ const mwPath = path.join(dir, `_middleware${ext}`);
11
+ if (existsSync(mwPath)) {
12
+ const module = await import(`file://${mwPath}`);
13
+ const mw = module?.default ?? {};
14
+ if (typeof mw === 'function') {
15
+ middlewares.unshift(mw);
16
+ }
17
+ }
18
+ }
19
+ const parent = dirname(dir);
20
+ if (parent === dir) {
21
+ break;
22
+ }
23
+ dir = parent;
24
+ }
25
+ return middlewares;
26
+ }
27
+ async function runMiddlewares(middlewares, ctx, handler) {
28
+ let idx = 0;
29
+ async function dispatch() {
30
+ if (idx < middlewares.length) {
31
+ await middlewares[idx++](ctx, dispatch);
32
+ }
33
+ else {
34
+ await handler(ctx);
35
+ }
36
+ }
37
+ await dispatch();
38
+ }
39
+
40
+ export { collectMiddlewares, runMiddlewares };
@@ -0,0 +1,3 @@
1
+ import KoaRouter from 'koa-router';
2
+ declare const router: KoaRouter<any, {}>;
3
+ export { router as default };
@@ -0,0 +1,255 @@
1
+ import KoaRouter from 'koa-router';
2
+ import fs, { existsSync } from 'fs';
3
+ import path, { join, dirname } from 'path';
4
+ import mime from 'mime-types';
5
+ import { createRequire } from 'module';
6
+ import hello from './hello.html.js';
7
+ import { getModuelFile, formatPath } from './utils.js';
8
+ import { collectMiddlewares, runMiddlewares } from './middleware.js';
9
+
10
+ const require = createRequire(import.meta.url);
11
+ const mainDirMap = new Map();
12
+ const router = new KoaRouter({
13
+ prefix: '/'
14
+ });
15
+ router.get('/', ctx => {
16
+ ctx.status = 200;
17
+ ctx.set('Content-Type', 'text/html; charset=utf-8');
18
+ ctx.body = hello;
19
+ });
20
+ router.get('api/online', ctx => {
21
+ ctx.status = 200;
22
+ ctx.body = {
23
+ code: 200,
24
+ message: 'service online',
25
+ data: null
26
+ };
27
+ });
28
+ router.all('app/{*path}', async (ctx) => {
29
+ if (!process.env.input) {
30
+ ctx.status = 400;
31
+ ctx.body = {
32
+ code: 400,
33
+ message: '没有主要入口文件',
34
+ data: null
35
+ };
36
+ return;
37
+ }
38
+ const rootPath = process.cwd();
39
+ const apiPath = '/app/api';
40
+ if (ctx.path.startsWith(apiPath)) {
41
+ const mainPath = join(rootPath, process.env.input);
42
+ if (!existsSync(mainPath)) {
43
+ ctx.status = 400;
44
+ ctx.body = {
45
+ code: 400,
46
+ message: '未找到主要入口文件',
47
+ data: 'existsSync input'
48
+ };
49
+ return;
50
+ }
51
+ const mainDir = dirname(mainPath);
52
+ try {
53
+ const dir = join(mainDir, 'route', ctx.path?.replace(apiPath, '/api') || '');
54
+ if (existsSync(dir) && fs.statSync(dir).isFile()) {
55
+ ctx.status = 404;
56
+ ctx.body = {
57
+ code: 404,
58
+ message: `API '${ctx.path}' 未找到。`,
59
+ data: 'route path is file not directory'
60
+ };
61
+ return;
62
+ }
63
+ const modulePath = getModuelFile(dir);
64
+ if (!modulePath) {
65
+ ctx.status = 404;
66
+ ctx.body = {
67
+ code: 404,
68
+ message: `API '${ctx.path}' 未找到。`,
69
+ data: 'existsSync modulePath'
70
+ };
71
+ return;
72
+ }
73
+ const apiModule = await import(`file://${modulePath}`);
74
+ const handler = apiModule[ctx.method];
75
+ if (!handler || typeof handler !== 'function') {
76
+ ctx.status = 405;
77
+ return;
78
+ }
79
+ const middlewares = await collectMiddlewares(modulePath);
80
+ await runMiddlewares(middlewares, ctx, handler);
81
+ }
82
+ catch (err) {
83
+ console.error(`Error handling API request ${ctx.path}`);
84
+ ctx.status = 500;
85
+ ctx.body = {
86
+ code: 500,
87
+ message: '处理 API 请求时发生错误。',
88
+ error: err.message
89
+ };
90
+ }
91
+ return;
92
+ }
93
+ if (ctx.method !== 'GET') {
94
+ ctx.status = 405;
95
+ return;
96
+ }
97
+ let root = '';
98
+ const resourcePath = formatPath(ctx.params?.path);
99
+ try {
100
+ const pkg = require(path.join(rootPath, 'package.json')) ?? {};
101
+ root = pkg.alemonjs?.web?.root ?? '';
102
+ }
103
+ catch (err) {
104
+ ctx.status = 500;
105
+ ctx.body = {
106
+ code: 500,
107
+ message: '加载 package.json 时发生错误。',
108
+ error: err.message
109
+ };
110
+ return;
111
+ }
112
+ const fullPath = root ? path.join(rootPath, root, resourcePath) : path.join(rootPath, resourcePath);
113
+ try {
114
+ const file = await fs.promises.readFile(fullPath);
115
+ const mimeType = mime.lookup(fullPath) || 'application/octet-stream';
116
+ ctx.set('Content-Type', mimeType);
117
+ ctx.body = file;
118
+ ctx.status = 200;
119
+ }
120
+ catch (err) {
121
+ if (err?.status === 404) {
122
+ ctx.status = 404;
123
+ ctx.body = {
124
+ code: 404,
125
+ message: '资源中未找到。',
126
+ data: null
127
+ };
128
+ }
129
+ else {
130
+ ctx.status = 500;
131
+ ctx.body = {
132
+ code: 500,
133
+ message: '加载资源时发生服务器错误。',
134
+ error: err.message
135
+ };
136
+ }
137
+ }
138
+ });
139
+ router.all('app', ctx => {
140
+ ctx.redirect('/app/');
141
+ });
142
+ router.all('apps/:app/{*path}', async (ctx) => {
143
+ const appName = ctx.params.app;
144
+ const apiPath = `/apps/${appName}/api`;
145
+ if (ctx.path.startsWith(apiPath)) {
146
+ try {
147
+ if (!mainDirMap.has(appName)) {
148
+ const mainPath = require.resolve(appName);
149
+ if (!existsSync(mainPath)) {
150
+ ctx.status = 400;
151
+ ctx.body = {
152
+ code: 400,
153
+ message: '未找到主要入口文件',
154
+ data: null
155
+ };
156
+ return;
157
+ }
158
+ const mainDir = dirname(mainPath);
159
+ mainDirMap.set(appName, mainDir);
160
+ }
161
+ const dir = join(mainDirMap.get(appName), 'route', ctx.path?.replace(apiPath, '/api') || '');
162
+ if (existsSync(dir) && fs.statSync(dir).isFile()) {
163
+ ctx.status = 404;
164
+ ctx.body = {
165
+ code: 404,
166
+ message: `API 'route/${ctx.path}' 未找到。`,
167
+ data: 'route path is file not directory'
168
+ };
169
+ return;
170
+ }
171
+ const modulePath = getModuelFile(dir);
172
+ if (!modulePath) {
173
+ ctx.status = 404;
174
+ ctx.body = {
175
+ code: 404,
176
+ message: `API '${ctx.path}' 未找到。`,
177
+ data: 'existsSync modulePath'
178
+ };
179
+ return;
180
+ }
181
+ const apiModule = await import(`file://${modulePath}`);
182
+ const handler = apiModule[ctx.method];
183
+ if (!handler || typeof handler !== 'function') {
184
+ ctx.status = 405;
185
+ return;
186
+ }
187
+ const middlewares = await collectMiddlewares(modulePath);
188
+ await runMiddlewares(middlewares, ctx, handler);
189
+ }
190
+ catch (err) {
191
+ logger.warn(`Error request ${ctx.path}:`, err?.message || '');
192
+ ctx.status = 500;
193
+ ctx.body = {
194
+ code: 500,
195
+ message: '处理 API 请求时发生错误。',
196
+ error: err.message
197
+ };
198
+ }
199
+ return;
200
+ }
201
+ if (ctx.method !== 'GET') {
202
+ ctx.status = 405;
203
+ return;
204
+ }
205
+ const rootPath = path.join(process.cwd(), 'node_modules', appName);
206
+ const resourcePath = formatPath(ctx.params?.path);
207
+ let root = '';
208
+ try {
209
+ const pkg = require(`${appName}/package`) ?? {};
210
+ root = pkg?.alemonjs?.web?.root ?? '';
211
+ }
212
+ catch (err) {
213
+ ctx.status = 500;
214
+ ctx.body = {
215
+ code: 500,
216
+ message: '加载 package.json 时发生错误。',
217
+ error: err.message
218
+ };
219
+ return;
220
+ }
221
+ const fullPath = root ? path.join(rootPath, root, resourcePath) : path.join(rootPath, resourcePath);
222
+ try {
223
+ const file = await fs.promises.readFile(fullPath);
224
+ const mimeType = mime.lookup(fullPath) || 'application/octet-stream';
225
+ ctx.set('Content-Type', mimeType);
226
+ ctx.body = file;
227
+ ctx.status = 200;
228
+ }
229
+ catch (err) {
230
+ if (err?.status === 404) {
231
+ ctx.status = 404;
232
+ ctx.body = {
233
+ code: 404,
234
+ message: `资源 '${resourcePath}' 在子应用 '${appName}' 中未找到。`,
235
+ data: null
236
+ };
237
+ }
238
+ else {
239
+ logger.warn(`Error request ${ctx.path}:`, err?.message || '');
240
+ ctx.status = 500;
241
+ ctx.body = {
242
+ code: 500,
243
+ message: `加载子应用 '${appName}' 资源时发生服务器错误。`,
244
+ error: err.message
245
+ };
246
+ }
247
+ }
248
+ });
249
+ router.all('apps/:name', ctx => {
250
+ if (ctx.path === `/apps/${ctx.params.name}`) {
251
+ ctx.redirect(`/apps/${ctx.params.name}/`);
252
+ }
253
+ });
254
+
255
+ export { router as default };
@@ -0,0 +1,2 @@
1
+ export declare const getModuelFile: (dir: string) => string;
2
+ export declare const formatPath: (path: string) => string;
@@ -0,0 +1,39 @@
1
+ import fs, { existsSync } from 'fs';
2
+
3
+ const getModuelFile = (dir) => {
4
+ const dirMap = {
5
+ '.js': `${dir}.js`,
6
+ '.jsx': `${dir}.jsx`,
7
+ '.mjs': `${dir}.mjs`,
8
+ '.cjs': `${dir}.cjs`,
9
+ '/index.js': `${dir}/index.js`,
10
+ '/index.jsx': `${dir}/index.jsx`,
11
+ '/index.mjs': `${dir}/index.mjs`,
12
+ '/index.cjs': `${dir}/index.cjs`,
13
+ '.ts': `${dir}.ts`,
14
+ '.tsx': `${dir}.tsx`,
15
+ '/index.ts': `${dir}/index.ts`,
16
+ '/index.tsx': `${dir}/index.tsx`
17
+ };
18
+ for (const key in dirMap) {
19
+ const filePath = dirMap[key];
20
+ if (existsSync(filePath) && fs.statSync(filePath)) {
21
+ return filePath;
22
+ }
23
+ }
24
+ return '';
25
+ };
26
+ const formatPath = (path) => {
27
+ if (!path || path === '/') {
28
+ return '/index.html';
29
+ }
30
+ const pates = path.split('/');
31
+ const lastPath = pates[pates.length - 1];
32
+ if (lastPath.includes('.')) {
33
+ return path;
34
+ }
35
+ path += '.html';
36
+ return path;
37
+ };
38
+
39
+ export { formatPath, getModuelFile };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alemonjs",
3
- "version": "2.1.0-alpha.53",
3
+ "version": "2.1.0-alpha.55",
4
4
  "description": "bot script",
5
5
  "author": "lemonade",
6
6
  "license": "MIT",