@macle-community/simulator-web 0.0.0-stage → 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.
@@ -0,0 +1,117 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const os = require('os');
4
+
5
+ /**
6
+ * Search candidates for official @macle/compiler
7
+ */
8
+ function findMacleCompiler() {
9
+ const candidates = [
10
+ path.join(__dirname, '../../DevToolkit_26.6.0/DevToolkit_26.6.0-LG0001/Macle_26.6.0-LG0001_Engine_Compiler/node_modules/@macle/compiler'),
11
+ path.join(__dirname, '../../../DevToolkit_26.6.0/DevToolkit_26.6.0-LG0001/Macle_26.6.0-LG0001_Engine_Compiler/node_modules/@macle/compiler'),
12
+ path.join(__dirname, '../../DevToolkit_25.9.0/Macle_25.9.0_DevSuite_IDE/macle-miniprogram-tools-25.9.0-file/extension/node_modules/@macle/compiler'),
13
+ path.join(__dirname, '../node_modules/@macle/compiler')
14
+ ];
15
+
16
+ for (const cand of candidates) {
17
+ if (fs.existsSync(cand)) {
18
+ try {
19
+ const compiler = require(cand);
20
+ return { compiler, path: cand };
21
+ } catch (err) {
22
+ console.warn(`[Compiler] Notice: Candidate found at ${cand} but could not be loaded:`, err.message);
23
+ }
24
+ }
25
+ }
26
+ return null;
27
+ }
28
+
29
+ /**
30
+ * Compile or prepare project assets for web simulator
31
+ */
32
+ async function compileProject(sourceDir, projectName) {
33
+ const compilerInfo = findMacleCompiler();
34
+ const outComposeDir = path.join(os.homedir(), '.macle/target/compose', projectName, 'root/debug/app');
35
+
36
+ // Check if sourceDir is already compiled (e.g. contains app-service.js)
37
+ const isAlreadyCompiled = fs.existsSync(path.join(sourceDir, 'app-service.js')) ||
38
+ fs.existsSync(path.join(sourceDir, 'app-config.json'));
39
+
40
+ fs.mkdirSync(outComposeDir, { recursive: true });
41
+
42
+ if (isAlreadyCompiled) {
43
+ // Copy compiled files directly
44
+ copyDirectory(sourceDir, outComposeDir);
45
+ return {
46
+ success: true,
47
+ mode: 'precompiled',
48
+ outDir: outComposeDir
49
+ };
50
+ }
51
+
52
+ // Needs compilation via @macle/compiler
53
+ if (!compilerInfo) {
54
+ console.warn('[Compiler] Warning: @macle/compiler not found; copying source files directly.');
55
+ copyDirectory(sourceDir, outComposeDir);
56
+ return {
57
+ success: false,
58
+ mode: 'raw-copy',
59
+ outDir: outComposeDir,
60
+ error: '@macle/compiler not found'
61
+ };
62
+ }
63
+
64
+ try {
65
+ const startTime = Date.now();
66
+ console.log(`[Compiler] Pre-compiling Macle source (${sourceDir})...`);
67
+ await compilerInfo.compiler.compile(sourceDir, outComposeDir, { debug: true, es5: false });
68
+
69
+ // Sync project config if available
70
+ const projConf = path.join(sourceDir, 'project.config.json');
71
+ if (fs.existsSync(projConf)) {
72
+ fs.copyFileSync(projConf, path.join(outComposeDir, 'project.config.json'));
73
+ }
74
+
75
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
76
+ console.log(`[Compiler] Pre-compilation successful -> ${outComposeDir} (${elapsed}s)`);
77
+
78
+ return {
79
+ success: true,
80
+ mode: 'compiled',
81
+ outDir: outComposeDir,
82
+ compilerPath: compilerInfo.path,
83
+ elapsed
84
+ };
85
+ } catch (err) {
86
+ console.error('[Compiler] Compilation error:', err.message);
87
+ return {
88
+ success: false,
89
+ mode: 'error',
90
+ outDir: outComposeDir,
91
+ error: err.message
92
+ };
93
+ }
94
+ }
95
+
96
+ function copyDirectory(src, dest) {
97
+ if (!fs.existsSync(src)) return;
98
+ fs.mkdirSync(dest, { recursive: true });
99
+ const entries = fs.readdirSync(src, { withFileTypes: true });
100
+ for (const entry of entries) {
101
+ const srcPath = path.join(src, entry.name);
102
+ const destPath = path.join(dest, entry.name);
103
+ if (entry.isDirectory()) {
104
+ if (entry.name !== 'node_modules' && entry.name !== '.git') {
105
+ copyDirectory(srcPath, destPath);
106
+ }
107
+ } else {
108
+ fs.copyFileSync(srcPath, destPath);
109
+ }
110
+ }
111
+ }
112
+
113
+ module.exports = {
114
+ findMacleCompiler,
115
+ compileProject,
116
+ copyDirectory
117
+ };
@@ -0,0 +1,225 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const http = require('http');
4
+ const https = require('https');
5
+ const crypto = require('crypto');
6
+ const url = require('url');
7
+
8
+ /**
9
+ * Native Macle MCSS Simulator Backend
10
+ * Handles /mcss/simulate/* endpoints
11
+ */
12
+ function createMockBackend(appDir) {
13
+ const storeDir = path.join(appDir, 'store');
14
+ const tmpDir = path.join(appDir, 'tmp');
15
+ fs.mkdirSync(storeDir, { recursive: true });
16
+ fs.mkdirSync(tmpDir, { recursive: true });
17
+
18
+ return {
19
+ handleRequestData(req, res, body) {
20
+ try {
21
+ const data = body.data || {};
22
+ const params = data.mockType ? data.inputParams : (typeof data.inputParams === 'string' ? JSON.parse(data.inputParams) : data.inputParams);
23
+ const targetUrl = params.url;
24
+ const method = (params.method || 'GET').toUpperCase();
25
+ const headers = params.header || {};
26
+ const postData = params.data ? (typeof params.data === 'string' ? params.data : JSON.stringify(params.data)) : null;
27
+
28
+ if (!targetUrl) {
29
+ res.writeHead(400, { 'Content-Type': 'application/json' });
30
+ return res.end(JSON.stringify({ errMsg: 'requestData:fail missing url' }));
31
+ }
32
+
33
+ const parsedUrl = new URL(targetUrl);
34
+ const isHttps = parsedUrl.protocol === 'https:';
35
+ const client = isHttps ? https : http;
36
+
37
+ if (postData && !headers['Content-Type']) {
38
+ headers['Content-Type'] = 'application/json';
39
+ }
40
+ if (postData) {
41
+ headers['Content-Length'] = Buffer.byteLength(postData);
42
+ }
43
+
44
+ const requestOptions = {
45
+ hostname: parsedUrl.hostname,
46
+ port: parsedUrl.port || (isHttps ? 443 : 80),
47
+ path: parsedUrl.pathname + parsedUrl.search,
48
+ method: method,
49
+ headers: headers,
50
+ timeout: params.timeout || 10000
51
+ };
52
+
53
+ const proxyReq = client.request(requestOptions, (proxyRes) => {
54
+ let chunks = [];
55
+ proxyRes.on('data', chunk => chunks.push(chunk));
56
+ proxyRes.on('end', () => {
57
+ const buffer = Buffer.concat(chunks);
58
+ const contentType = proxyRes.headers['content-type'] || '';
59
+ let responseData;
60
+
61
+ if (contentType.includes('application/json')) {
62
+ try {
63
+ responseData = JSON.parse(buffer.toString('utf8'));
64
+ } catch (e) {
65
+ responseData = buffer.toString('utf8');
66
+ }
67
+ } else {
68
+ responseData = buffer.toString('utf8');
69
+ }
70
+
71
+ res.writeHead(proxyRes.statusCode || 200, {
72
+ 'Content-Type': 'application/json',
73
+ 'Access-Control-Allow-Origin': '*'
74
+ });
75
+ res.end(JSON.stringify(responseData));
76
+ });
77
+ });
78
+
79
+ proxyReq.on('error', (err) => {
80
+ res.writeHead(502, { 'Content-Type': 'application/json' });
81
+ res.end(JSON.stringify({ errMsg: `requestData:fail ${err.message}` }));
82
+ });
83
+
84
+ proxyReq.on('timeout', () => {
85
+ proxyReq.destroy();
86
+ res.writeHead(504, { 'Content-Type': 'application/json' });
87
+ res.end(JSON.stringify({ errMsg: 'requestData:fail timeout' }));
88
+ });
89
+
90
+ if (postData) {
91
+ proxyReq.write(postData);
92
+ }
93
+ proxyReq.end();
94
+ } catch (err) {
95
+ res.writeHead(500, { 'Content-Type': 'application/json' });
96
+ res.end(JSON.stringify({ errMsg: `requestData:error ${err.message}` }));
97
+ }
98
+ },
99
+
100
+ handleSaveFile(req, res, body) {
101
+ try {
102
+ const data = body.data || {};
103
+ const params = typeof data.inputParams === 'string' ? JSON.parse(data.inputParams) : (data.inputParams || {});
104
+ const tempPath = params.tempFilePath || '';
105
+ const fileName = path.basename(tempPath);
106
+ const sourceFile = path.join(tmpDir, fileName);
107
+ const targetFile = path.join(storeDir, fileName);
108
+
109
+ if (!fileName || !fs.existsSync(sourceFile)) {
110
+ // If file not in tmpDir, create a mock saved file
111
+ const mockContent = 'mock-saved-content';
112
+ fs.writeFileSync(targetFile, mockContent);
113
+ } else {
114
+ fs.copyFileSync(sourceFile, targetFile);
115
+ }
116
+
117
+ res.writeHead(200, { 'Content-Type': 'application/json' });
118
+ res.end(JSON.stringify({
119
+ errMsg: 'savedFile:ok',
120
+ savedFilePath: `/store/${fileName}`
121
+ }));
122
+ } catch (err) {
123
+ res.writeHead(500, { 'Content-Type': 'application/json' });
124
+ res.end(JSON.stringify({ errMsg: `saveFile:fail ${err.message}` }));
125
+ }
126
+ },
127
+
128
+ handleRemoveSaved(req, res, body) {
129
+ try {
130
+ const data = body.data || {};
131
+ const params = typeof data.inputParams === 'string' ? JSON.parse(data.inputParams) : (data.inputParams || {});
132
+ const filePath = params.filePath || '';
133
+ const fileName = path.basename(filePath);
134
+ const targetFile = path.join(storeDir, fileName);
135
+
136
+ if (fs.existsSync(targetFile)) {
137
+ fs.unlinkSync(targetFile);
138
+ res.writeHead(200, { 'Content-Type': 'application/json' });
139
+ res.end(JSON.stringify({ errMsg: 'removeSavedFile:ok' }));
140
+ } else {
141
+ res.writeHead(404, { 'Content-Type': 'application/json' });
142
+ res.end(JSON.stringify({ errMsg: 'removeSavedFile:fail file not exist' }));
143
+ }
144
+ } catch (err) {
145
+ res.writeHead(500, { 'Content-Type': 'application/json' });
146
+ res.end(JSON.stringify({ errMsg: `removeSaved:fail ${err.message}` }));
147
+ }
148
+ },
149
+
150
+ handleFileInfo(req, res, query) {
151
+ try {
152
+ const dataStr = query.data;
153
+ const params = typeof dataStr === 'string' ? JSON.parse(dataStr) : (dataStr || {});
154
+ const filePath = params.filePath || '';
155
+ const algorithm = (params.digestAlgorithm || 'sha256').toLowerCase();
156
+ const targetFile = path.resolve(appDir, filePath.replace(/^\//, ''));
157
+
158
+ if (fs.existsSync(targetFile)) {
159
+ const content = fs.readFileSync(targetFile);
160
+ const hash = crypto.createHash(algorithm).update(content).digest('hex');
161
+ const stat = fs.statSync(targetFile);
162
+ res.writeHead(200, { 'Content-Type': 'application/json' });
163
+ res.end(JSON.stringify({
164
+ errMsg: 'getFileInfo:ok',
165
+ digest: hash,
166
+ size: stat.size
167
+ }));
168
+ } else {
169
+ res.writeHead(404, { 'Content-Type': 'application/json' });
170
+ res.end(JSON.stringify({ errMsg: 'getFileInfo:fail file not exist' }));
171
+ }
172
+ } catch (err) {
173
+ res.writeHead(500, { 'Content-Type': 'application/json' });
174
+ res.end(JSON.stringify({ errMsg: `getFileInfo:fail ${err.message}` }));
175
+ }
176
+ },
177
+
178
+ handleSavedFileList(req, res) {
179
+ try {
180
+ const files = fs.readdirSync(storeDir);
181
+ const fileList = files.map(file => {
182
+ const p = path.join(storeDir, file);
183
+ const stat = fs.statSync(p);
184
+ return {
185
+ filePath: `/store/${file}`,
186
+ size: stat.size,
187
+ creatTime: stat.ctimeMs
188
+ };
189
+ });
190
+ res.writeHead(200, { 'Content-Type': 'application/json' });
191
+ res.end(JSON.stringify({ errMsg: 'getSavedFileList:ok', fileList }));
192
+ } catch (err) {
193
+ res.writeHead(500, { 'Content-Type': 'application/json' });
194
+ res.end(JSON.stringify({ errMsg: `getSavedFileList:fail ${err.message}` }));
195
+ }
196
+ },
197
+
198
+ handleSavedFileInfo(req, res, query) {
199
+ try {
200
+ const dataStr = query.data;
201
+ const params = typeof dataStr === 'string' ? JSON.parse(dataStr) : (dataStr || {});
202
+ const fileName = path.basename(params.filePath || '');
203
+ const targetFile = path.join(storeDir, fileName);
204
+
205
+ if (fs.existsSync(targetFile)) {
206
+ const stat = fs.statSync(targetFile);
207
+ res.writeHead(200, { 'Content-Type': 'application/json' });
208
+ res.end(JSON.stringify({
209
+ errMsg: 'getSavedFileInfo:ok',
210
+ size: stat.size,
211
+ createTime: stat.ctimeMs
212
+ }));
213
+ } else {
214
+ res.writeHead(404, { 'Content-Type': 'application/json' });
215
+ res.end(JSON.stringify({ errMsg: 'getSavedFileInfo:fail file not find' }));
216
+ }
217
+ } catch (err) {
218
+ res.writeHead(500, { 'Content-Type': 'application/json' });
219
+ res.end(JSON.stringify({ errMsg: `getSavedFileInfo:fail ${err.message}` }));
220
+ }
221
+ }
222
+ };
223
+ }
224
+
225
+ module.exports = { createMockBackend };
@@ -0,0 +1,87 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Discovers and parses Macle miniprogram project configuration
6
+ */
7
+ function parseProject(projectDir) {
8
+ const resolvedDir = path.resolve(projectDir);
9
+ let appConfig = null;
10
+ let projectConfig = null;
11
+ let isCompiled = false;
12
+ let appDir = resolvedDir;
13
+
14
+ // Check if projectDir is already a compiled dist folder
15
+ const appServiceCandidates = [
16
+ path.join(resolvedDir, 'app-service.js'),
17
+ path.join(resolvedDir, 'root/debug/app/app-service.js')
18
+ ];
19
+
20
+ for (const cand of appServiceCandidates) {
21
+ if (fs.existsSync(cand)) {
22
+ isCompiled = true;
23
+ appDir = path.dirname(cand);
24
+ break;
25
+ }
26
+ }
27
+
28
+ // Look for app-config.json or app.json
29
+ const configCandidates = [
30
+ path.join(appDir, 'app-config.json'),
31
+ path.join(resolvedDir, 'app-config.json'),
32
+ path.join(resolvedDir, 'app.json'),
33
+ path.join(appDir, 'app.json')
34
+ ];
35
+
36
+ for (const cand of configCandidates) {
37
+ if (fs.existsSync(cand)) {
38
+ try {
39
+ appConfig = JSON.parse(fs.readFileSync(cand, 'utf8'));
40
+ break;
41
+ } catch (err) {
42
+ console.warn(`[Parser] Warning: Failed to parse ${cand}:`, err.message);
43
+ }
44
+ }
45
+ }
46
+
47
+ // Look for project.config.json
48
+ const projCandidates = [
49
+ path.join(resolvedDir, 'project.config.json'),
50
+ path.join(path.dirname(resolvedDir), 'project.config.json')
51
+ ];
52
+
53
+ for (const cand of projCandidates) {
54
+ if (fs.existsSync(cand)) {
55
+ try {
56
+ projectConfig = JSON.parse(fs.readFileSync(cand, 'utf8'));
57
+ break;
58
+ } catch (e) {}
59
+ }
60
+ }
61
+
62
+ const projectName = (projectConfig && projectConfig.projectname) ||
63
+ path.basename(resolvedDir);
64
+
65
+ const pages = (appConfig && Array.isArray(appConfig.pages)) ? appConfig.pages : ['pages/index/index'];
66
+ const entryPagePath = (appConfig && appConfig.entryPagePath) || pages[0] || 'pages/index/index';
67
+ const windowConfig = (appConfig && appConfig.window) || {
68
+ navigationBarTitleText: projectName,
69
+ navigationBarBackgroundColor: '#000000',
70
+ navigationBarTextStyle: 'white'
71
+ };
72
+
73
+ return {
74
+ sourceDir: resolvedDir,
75
+ appDir,
76
+ isCompiled,
77
+ projectName,
78
+ pages,
79
+ entryPagePath,
80
+ window: windowConfig,
81
+ tabBar: (appConfig && appConfig.tabBar) || null,
82
+ appConfig,
83
+ projectConfig
84
+ };
85
+ }
86
+
87
+ module.exports = { parseProject };