mmt-testlight 0.3.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,187 @@
1
+ import { outputExtractor, Random, testHelper } from 'mmt-core';
2
+ import { performance } from 'perf_hooks';
3
+ import vm from 'vm';
4
+ import { maybeGenerateJs } from './loadTest.js';
5
+ export async function runTestObject(testObj) {
6
+ const start = performance.now();
7
+ const errors = [];
8
+ try {
9
+ const js = maybeGenerateJs(testObj) || '';
10
+ if (!js.trim()) {
11
+ errors.push('No executable flow content.');
12
+ }
13
+ if (js) {
14
+ const forward = (method) => (...args) => {
15
+ const msg = args.map(a => {
16
+ if (typeof a === 'string') {
17
+ return a;
18
+ }
19
+ try {
20
+ return JSON.stringify(a);
21
+ }
22
+ catch {
23
+ return String(a);
24
+ }
25
+ })
26
+ .join(' ');
27
+ const out = method === 'error' || method === 'warn' ?
28
+ process.stderr :
29
+ process.stdout;
30
+ out.write(msg + '\n');
31
+ };
32
+ const sandbox = {
33
+ console: {
34
+ trace: forward('trace'),
35
+ debug: forward('debug'),
36
+ log: forward('log'),
37
+ info: forward('info'),
38
+ warn: forward('warn'),
39
+ error: forward('error'),
40
+ },
41
+ lg: forward('log'),
42
+ setTimeout,
43
+ clearTimeout,
44
+ };
45
+ // Inject helpers and random generators (functions only) so generated code
46
+ // can call randomEmail(), randomIP(), etc.
47
+ Object.assign(sandbox, testHelper);
48
+ for (const [k, v] of Object.entries(Random)) {
49
+ if (typeof v === 'function' && !(k in sandbox)) {
50
+ sandbox[k] = v;
51
+ }
52
+ }
53
+ vm.createContext(sandbox);
54
+ try {
55
+ vm.runInContext(js, sandbox, { timeout: 5000 });
56
+ }
57
+ catch (e) {
58
+ errors.push(`Execution error: ${e?.message || e}`);
59
+ }
60
+ }
61
+ return {
62
+ success: errors.length === 0,
63
+ durationMs: performance.now() - start,
64
+ errors
65
+ };
66
+ }
67
+ catch (e) {
68
+ errors.push(e?.message || String(e));
69
+ return { success: false, durationMs: performance.now() - start, errors };
70
+ }
71
+ }
72
+ export async function runGeneratedJs(js) {
73
+ const start = performance.now();
74
+ const errors = [];
75
+ if (!js || !js.trim()) {
76
+ return {
77
+ success: false,
78
+ durationMs: performance.now() - start,
79
+ errors: ['Empty JS input']
80
+ };
81
+ }
82
+ try {
83
+ const forward = (method) => (...args) => {
84
+ const msg = args.map(a => {
85
+ if (typeof a === 'string') {
86
+ return a;
87
+ }
88
+ try {
89
+ return JSON.stringify(a);
90
+ }
91
+ catch {
92
+ return String(a);
93
+ }
94
+ })
95
+ .join(' ');
96
+ const out = method === 'error' || method === 'warn' ? process.stderr :
97
+ process.stdout;
98
+ out.write(msg + '\n');
99
+ };
100
+ const sandbox = {
101
+ console: {
102
+ trace: forward('trace'),
103
+ debug: forward('debug'),
104
+ log: forward('log'),
105
+ info: forward('info'),
106
+ warn: forward('warn'),
107
+ error: forward('error'),
108
+ },
109
+ lg: forward('log'),
110
+ setTimeout,
111
+ clearTimeout,
112
+ };
113
+ // Inject runtime helpers required by generated code
114
+ sandbox.extractOutputs = outputExtractor.extractOutputs;
115
+ // Use axios for HTTP; under pkg snapshot ensure module is reachable
116
+ sandbox.send = async (req) => {
117
+ // Use axios Node CJS build explicitly so pkg bundles it reliably
118
+ let axiosReq;
119
+ try {
120
+ axiosReq = require('axios/dist/node/axios.cjs');
121
+ }
122
+ catch {
123
+ // Fallback for dev environments
124
+ axiosReq = require('axios');
125
+ }
126
+ const axiosMod = axiosReq.default || axiosReq;
127
+ const headers = {};
128
+ for (const [k, v] of Object.entries(req.headers || {})) {
129
+ headers[String(k)] = String(v);
130
+ }
131
+ if (req.cookies && Object.keys(req.cookies).length) {
132
+ const cookie = Object.entries(req.cookies).map(([k, v]) => `${k}=${v}`).join('; ');
133
+ if (cookie) {
134
+ headers['Cookie'] = cookie;
135
+ }
136
+ }
137
+ const request = {
138
+ url: req.url,
139
+ method: req.method || 'GET',
140
+ headers,
141
+ data: req.body,
142
+ responseType: 'text',
143
+ transformResponse: [(data) => data],
144
+ timeout: 30000,
145
+ validateStatus: () => true,
146
+ };
147
+ const t0 = Date.now();
148
+ const res = await axiosMod.request(request);
149
+ const outHeaders = {};
150
+ Object.entries(res.headers || {}).forEach(([k, v]) => {
151
+ if (v !== undefined) {
152
+ outHeaders[k] = String(v);
153
+ }
154
+ });
155
+ return {
156
+ body: res.data,
157
+ headers: outHeaders,
158
+ status: res.status,
159
+ statusText: res.statusText,
160
+ duration: Date.now() - t0,
161
+ autoformat: false,
162
+ };
163
+ };
164
+ Object.assign(sandbox, testHelper);
165
+ // Random generator functions injection
166
+ for (const [k, v] of Object.entries(Random)) {
167
+ if (typeof v === 'function' && !(k in sandbox)) {
168
+ sandbox[k] = v;
169
+ }
170
+ }
171
+ vm.createContext(sandbox);
172
+ const wrapped = `(async () => {\n${js}\n})()`;
173
+ try {
174
+ const res = await vm.runInContext(wrapped, sandbox);
175
+ return { success: true, durationMs: performance.now() - start, errors };
176
+ }
177
+ catch (e) {
178
+ errors.push(`Execution error: ${e?.message || e}`);
179
+ return { success: false, durationMs: performance.now() - start, errors };
180
+ }
181
+ }
182
+ catch (e) {
183
+ errors.push(e?.message || String(e));
184
+ return { success: false, durationMs: performance.now() - start, errors };
185
+ }
186
+ }
187
+ //# sourceMappingURL=runTest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runTest.js","sourceRoot":"","sources":["../src/runTest.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,445 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const commander_1 = require("commander");
40
+ const fs_1 = __importDefault(require("fs"));
41
+ const js_yaml_1 = __importDefault(require("js-yaml"));
42
+ const mmtcore = __importStar(require("mmt-core"));
43
+ // Import from mmt-core root exports to avoid subpath resolution issues under
44
+ // pkg
45
+ const mmt_core_1 = require("mmt-core");
46
+ /**
47
+ * Generic resolver for mmt-core exports that handles three resolution strategies:
48
+ * 1. pkg snapshot paths (require 'mmt-core/dist/...')
49
+ * 2. CJS/Node16 paths (require 'mmt-core/...')
50
+ * 3. Fallback from mmtcore namespace
51
+ */
52
+ function resolveCoreExport(module, exportName, fallback) {
53
+ try {
54
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
55
+ const mod = require(`mmt-core/dist/${module}.js`);
56
+ if (mod && typeof mod[exportName] === 'function') {
57
+ return mod[exportName];
58
+ }
59
+ }
60
+ catch { /* pkg path not available */ }
61
+ try {
62
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
63
+ const mod = require(`mmt-core/${module}`);
64
+ if (mod && typeof mod[exportName] === 'function') {
65
+ return mod[exportName];
66
+ }
67
+ }
68
+ catch { /* CJS path not available */ }
69
+ return fallback ?? mmtcore?.[module]?.[exportName] ?? null;
70
+ }
71
+ async function loadCoreExport(module, exportName) {
72
+ // When running as ESM, prefer dynamic import
73
+ try {
74
+ const mod = await Promise.resolve(`${`mmt-core/${module}`}`).then(s => __importStar(require(s)));
75
+ if (mod && typeof mod[exportName] === 'function') {
76
+ return mod[exportName];
77
+ }
78
+ }
79
+ catch { /* ESM path not available */ }
80
+ return resolveCoreExport(module, exportName);
81
+ }
82
+ const path_1 = __importDefault(require("path"));
83
+ const loadTest_js_1 = require("./loadTest.js");
84
+ const mockRunner_js_1 = require("./mockRunner.js");
85
+ const runArgs_js_1 = require("./runArgs.js");
86
+ // Defer importing runTest until needed to avoid pulling axios for to-js
87
+ const program = new commander_1.Command();
88
+ // Resolve version from package.json when available; fallback to a single const
89
+ // here.
90
+ function resolveCliVersion() {
91
+ const fallback = '0.2.0';
92
+ try {
93
+ const pkgPath = path_1.default.resolve('mmtcli', '.', 'package.json');
94
+ const candidates = [pkgPath];
95
+ for (const p of candidates) {
96
+ if (p && fs_1.default.existsSync(p)) {
97
+ const txt = fs_1.default.readFileSync(p, 'utf8');
98
+ const pkg = JSON.parse(txt);
99
+ if (pkg && typeof pkg.version === 'string' && pkg.version) {
100
+ return pkg.version;
101
+ }
102
+ }
103
+ }
104
+ }
105
+ catch {
106
+ }
107
+ return fallback;
108
+ }
109
+ const CLI_VERSION = resolveCliVersion();
110
+ program.name('multimeter')
111
+ .description('Multimeter CLI runner')
112
+ .version(CLI_VERSION, '-v, --version', 'output the version number');
113
+ program.option('--log-level <level>', 'Set log level (error|warn|info|debug|trace)', 'info');
114
+ program.command('run')
115
+ .argument('<file>', 'Test file (.yaml/.yml/.json/.mmt)')
116
+ .option('-q, --quiet', 'Minimal output', false)
117
+ .option('-o, --out <file>', 'Write result JSON to file')
118
+ .option('-i, --input <values...>', 'Input variables as key value pairs (repeatable)')
119
+ .option('-e, --env <values...>', 'Environment variables as key value pairs or key=val (repeatable)')
120
+ .option('--env-file <path>', 'Environment file (.mmt/.yaml) to read variables from')
121
+ .option('--preset <name>', 'Preset name from env file (e.g., runner.dev) or just name under runner')
122
+ .option('--example <name|#n>', 'Run a named example (matches name) or numeric index (#1 = first)')
123
+ .option('-p, --print-js', 'Print generated JS before executing', false)
124
+ .option('--report <format>', 'Generate test report: junit, mmt, html, or md')
125
+ .option('--report-file <path>', 'Output path for the report file (default depends on format)')
126
+ .action(async (file, opts) => {
127
+ try {
128
+ const runJSCode = await loadCoreExport('jsRunner', 'runJSCode');
129
+ if (typeof runJSCode !== 'function') {
130
+ throw new Error('Internal error: runJSCode is not available');
131
+ }
132
+ const full = path_1.default.resolve(process.cwd(), file);
133
+ const rawText = fs_1.default.readFileSync(full, 'utf8');
134
+ const raw = /\.json$/i.test(full) ? JSON.parse(rawText) : js_yaml_1.default.load(rawText);
135
+ const summary = (0, loadTest_js_1.summarize)(raw);
136
+ if (!opts.quiet) {
137
+ console.log(`Loaded: ${path_1.default.resolve(file)} (${summary})`);
138
+ }
139
+ const { runFileOptions, networkConfig, outFile, printJs, reportFormat, reportFile, getReportResults } = (0, runArgs_js_1.buildCliRunArgs)(file, { ...opts, logLevel: program.opts().logLevel });
140
+ // Apply network config if certificates are configured
141
+ if (networkConfig) {
142
+ try {
143
+ const setRunnerNetworkConfig = await loadCoreExport('jsRunner', 'setRunnerNetworkConfig');
144
+ if (setRunnerNetworkConfig) {
145
+ setRunnerNetworkConfig(networkConfig);
146
+ }
147
+ }
148
+ catch (e) {
149
+ console.warn(`Unable to apply certificate settings: ${e}`);
150
+ }
151
+ }
152
+ const runOpts = { ...runFileOptions };
153
+ // Create serverRunner to start mock servers from suite servers and test run steps
154
+ const cliServerRunner = async (alias, filePath) => {
155
+ if (!opts.quiet) {
156
+ console.log(`Starting mock server: ${alias}`);
157
+ }
158
+ return (0, mockRunner_js_1.startMockServerFromPath)(filePath, runOpts.envvar || {});
159
+ };
160
+ runOpts.serverRunner = cliServerRunner;
161
+ runOpts.jsRunner = (ctx) => runJSCode({ ...ctx, serverRunner: cliServerRunner });
162
+ let runOutcome;
163
+ try {
164
+ runOutcome = await mmt_core_1.runner.runFile(runOpts);
165
+ }
166
+ finally {
167
+ // Always stop mock servers after run completes
168
+ (0, mockRunner_js_1.stopAllServers)();
169
+ }
170
+ const { js, result } = runOutcome;
171
+ if (printJs) {
172
+ console.log(js.trim());
173
+ }
174
+ if (!opts.quiet) {
175
+ console.log(`Success: ${result.success}`);
176
+ console.log(`Duration: ${mmtcore.CommonData.formatDuration(result.durationMs)}`);
177
+ if (result.errors.length) {
178
+ console.log('Errors:');
179
+ result.errors.forEach((e) => console.log(' -', e));
180
+ }
181
+ }
182
+ if (outFile) {
183
+ const outPath = path_1.default.resolve(outFile);
184
+ fs_1.default.writeFileSync(outPath, JSON.stringify(result, null, 2), 'utf8');
185
+ if (!opts.quiet) {
186
+ console.log(`Result written: ${outPath}`);
187
+ }
188
+ }
189
+ // Generate and write test report if --report was specified
190
+ if (reportFormat && reportFile && getReportResults) {
191
+ const collectedResults = getReportResults();
192
+ let reportContent;
193
+ const serializers = {
194
+ junit: mmtcore.junitXml?.generateJunitXml,
195
+ mmt: mmtcore.mmtReport?.generateMmtReport,
196
+ html: mmtcore.reportHtml?.generateReportHtml,
197
+ md: mmtcore.reportMarkdown?.generateReportMarkdown,
198
+ };
199
+ const serializer = serializers[reportFormat];
200
+ if (typeof serializer === 'function') {
201
+ reportContent = serializer(collectedResults);
202
+ }
203
+ if (reportContent) {
204
+ const reportPath = path_1.default.resolve(reportFile);
205
+ fs_1.default.writeFileSync(reportPath, reportContent, 'utf8');
206
+ if (!opts.quiet) {
207
+ console.log(`Report written: ${reportPath}`);
208
+ }
209
+ }
210
+ else if (!opts.quiet) {
211
+ console.warn(`Unknown report format: ${reportFormat}`);
212
+ }
213
+ }
214
+ // Handle suite exports (from suite file's export: field)
215
+ const suiteExports = runOutcome.suiteExports;
216
+ if (suiteExports && Array.isArray(suiteExports.paths) && suiteExports.collectedResults) {
217
+ const suiteDir = path_1.default.dirname(path_1.default.resolve(process.cwd(), file));
218
+ const projectRoot = runFileOptions.projectRoot;
219
+ for (const exportPath of suiteExports.paths) {
220
+ try {
221
+ // Resolve path relative to suite file or +/ project root
222
+ let resolvedPath;
223
+ if (exportPath.startsWith('+/')) {
224
+ if (projectRoot) {
225
+ resolvedPath = path_1.default.resolve(projectRoot, exportPath.slice(2));
226
+ }
227
+ else {
228
+ if (!opts.quiet) {
229
+ console.warn(`Cannot resolve +/ path without project root: ${exportPath}`);
230
+ }
231
+ continue;
232
+ }
233
+ }
234
+ else {
235
+ resolvedPath = path_1.default.resolve(suiteDir, exportPath);
236
+ }
237
+ // Determine format from extension
238
+ const ext = path_1.default.extname(resolvedPath).toLowerCase();
239
+ const formatForExt = {
240
+ '.xml': 'junit',
241
+ '.html': 'html',
242
+ '.md': 'md',
243
+ '.mmt': 'mmt',
244
+ };
245
+ const format = formatForExt[ext];
246
+ if (!format) {
247
+ if (!opts.quiet) {
248
+ console.warn(`Unknown export format for extension ${ext}: ${exportPath}`);
249
+ }
250
+ continue;
251
+ }
252
+ // Generate report content
253
+ const exportSerializers = {
254
+ junit: mmtcore.junitXml?.generateJunitXml,
255
+ mmt: mmtcore.mmtReport?.generateMmtReport,
256
+ html: mmtcore.reportHtml?.generateReportHtml,
257
+ md: mmtcore.reportMarkdown?.generateReportMarkdown,
258
+ };
259
+ const serializer = exportSerializers[format];
260
+ if (typeof serializer !== 'function') {
261
+ if (!opts.quiet) {
262
+ console.warn(`Export serializer not available for format: ${format}`);
263
+ }
264
+ continue;
265
+ }
266
+ if (!opts.quiet) {
267
+ console.log(`Exporting results to ${resolvedPath}`);
268
+ }
269
+ const content = serializer(suiteExports.collectedResults);
270
+ // Create parent directories if they don't exist
271
+ const parentDir = path_1.default.dirname(resolvedPath);
272
+ if (!fs_1.default.existsSync(parentDir)) {
273
+ fs_1.default.mkdirSync(parentDir, { recursive: true });
274
+ }
275
+ fs_1.default.writeFileSync(resolvedPath, content, 'utf8');
276
+ if (!opts.quiet) {
277
+ console.log(`Suite export written: ${resolvedPath}`);
278
+ }
279
+ }
280
+ catch (e) {
281
+ if (!opts.quiet) {
282
+ console.warn(`Failed to write suite export ${exportPath}: ${e?.message || e}`);
283
+ }
284
+ }
285
+ }
286
+ }
287
+ process.exit(result.success ? 0 : 1);
288
+ }
289
+ catch (e) {
290
+ if (!opts.quiet) {
291
+ }
292
+ console.error('Error:', e?.message || e);
293
+ process.exit(2);
294
+ }
295
+ });
296
+ program.command('print-js')
297
+ .argument('<file>', 'Test file (.yaml/.yml/.json/.mmt)')
298
+ .description('Convert a test definition file to executable JS using JSer and print to stdout')
299
+ .option('-s, --stages', 'Include stage headers as comments when stages exist', true)
300
+ .option('-i, --input <values...>', 'Input variables as key value pairs (repeatable)')
301
+ .option('-e, --env <values...>', 'Environment variables as key value pairs or key=val (repeatable)')
302
+ .option('--env-file <path>', 'Environment file (.mmt/.yaml) to read variables from')
303
+ .option('--preset <name>', 'Preset name from env file (e.g., runner.dev) or just name under runner')
304
+ .option('--example <name|#n>', 'Select a named example (matches name) or numeric index (#1 = first)')
305
+ .action(async (file, opts) => {
306
+ try {
307
+ const { runFileOptions } = (0, runArgs_js_1.buildCliRunArgs)(file, { ...opts, logLevel: program.opts().logLevel });
308
+ const rawText = runFileOptions.file;
309
+ const envVars = (runFileOptions.envvar || {});
310
+ const inputs = (runFileOptions.manualInputs || {});
311
+ const fullPath = runFileOptions.filePath || path_1.default.resolve(process.cwd(), file);
312
+ const js = await mmtcore.runner.generateTestJs({
313
+ rawText,
314
+ name: path_1.default.basename(fullPath).replace(/[^a-zA-Z0-9_]/g, '_'),
315
+ inputs,
316
+ envVars,
317
+ fileLoader: runFileOptions.fileLoader,
318
+ });
319
+ if (!js.trim()) {
320
+ console.error('No JS could be generated (empty flow).');
321
+ process.exit(1);
322
+ }
323
+ console.log(js.trim());
324
+ }
325
+ catch (e) {
326
+ console.error('Error generating JS:', e?.message || e);
327
+ process.exit(2);
328
+ }
329
+ });
330
+ program.command('version-info')
331
+ .description('Show environment info')
332
+ .action(() => {
333
+ console.log(`multimeter cli ${CLI_VERSION}`);
334
+ console.log('Node:', process.version);
335
+ });
336
+ program.command('doc')
337
+ .argument('<file>', 'Doc file (.mmt/.yaml/.yml)')
338
+ .description('Generate documentation from a doc .mmt')
339
+ .option('-o, --out <file>', 'Write output to file (default: <docname>.<ext>)')
340
+ .option('--html', 'Generate HTML (default)', false)
341
+ .option('--md', 'Generate Markdown instead of HTML', false)
342
+ .action(async (file, opts) => {
343
+ try {
344
+ const full = path_1.default.resolve(process.cwd(), file);
345
+ const docDir = path_1.default.dirname(full);
346
+ const text = fs_1.default.readFileSync(full, 'utf8');
347
+ const doc = mmt_core_1.docParsePack.yamlToDoc(text);
348
+ const sources = [];
349
+ if (Array.isArray(doc.sources)) {
350
+ sources.push(...doc.sources);
351
+ }
352
+ if (Array.isArray(doc.services)) {
353
+ for (const s of doc.services) {
354
+ if (Array.isArray(s?.sources)) {
355
+ sources.push(...s.sources);
356
+ }
357
+ }
358
+ }
359
+ const files = new Set();
360
+ const walk = (p) => {
361
+ if (!fs_1.default.existsSync(p)) {
362
+ return;
363
+ }
364
+ const stat = fs_1.default.statSync(p);
365
+ if (stat.isDirectory()) {
366
+ for (const entry of fs_1.default.readdirSync(p)) {
367
+ walk(path_1.default.join(p, entry));
368
+ }
369
+ }
370
+ else if (stat.isFile() && p.toLowerCase().endsWith('.mmt')) {
371
+ files.add(p);
372
+ }
373
+ };
374
+ for (const s of sources) {
375
+ const abs = path_1.default.isAbsolute(s) ? s : path_1.default.join(docDir, s);
376
+ if (/\.mmt$/i.test(abs)) {
377
+ files.add(abs);
378
+ }
379
+ else {
380
+ walk(abs);
381
+ }
382
+ }
383
+ const apis = [];
384
+ for (const f of Array.from(files)) {
385
+ try {
386
+ const t = fs_1.default.readFileSync(f, 'utf8');
387
+ const parsed = js_yaml_1.default.load(t);
388
+ if (parsed && parsed.type === 'api') {
389
+ const api = mmt_core_1.apiParsePack.yamlToAPI(t);
390
+ api.__file = f; // attach file path for grouping
391
+ apis.push(api);
392
+ }
393
+ }
394
+ catch {
395
+ }
396
+ }
397
+ // logo embedding
398
+ let logoDataUrl = undefined;
399
+ const logo = doc?.logo;
400
+ if (logo && typeof logo === 'string' && !/^https?:\/\//i.test(logo) &&
401
+ !/^data:/i.test(logo)) {
402
+ const p = path_1.default.isAbsolute(logo) ? logo : path_1.default.join(docDir, logo);
403
+ try {
404
+ const data = fs_1.default.readFileSync(p);
405
+ const ext = (path_1.default.extname(p) || '').toLowerCase();
406
+ const mime = ext === '.png' ? 'image/png' :
407
+ ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' :
408
+ ext === '.svg' ? 'image/svg+xml' :
409
+ ext === '.gif' ? 'image/gif' :
410
+ 'application/octet-stream';
411
+ logoDataUrl = `data:${mime};base64,${data.toString('base64')}`;
412
+ }
413
+ catch {
414
+ }
415
+ }
416
+ const useMd = opts.md && !opts.html; // HTML remains default
417
+ const htmlOrMd = useMd ?
418
+ mmtcore.docMarkdown.buildDocMarkdown(apis, {
419
+ title: doc.title,
420
+ description: doc.description,
421
+ logo: logoDataUrl || doc.logo,
422
+ sources: Array.isArray(doc.sources) ? doc.sources : undefined,
423
+ services: Array.isArray(doc.services) ? doc.services : undefined,
424
+ }) :
425
+ mmt_core_1.docHtml.buildDocHtml(apis, {
426
+ title: doc.title,
427
+ description: doc.description,
428
+ logo: logoDataUrl || doc.logo,
429
+ sources: Array.isArray(doc.sources) ? doc.sources : undefined,
430
+ services: Array.isArray(doc.services) ? doc.services : undefined,
431
+ });
432
+ const defExt = useMd ? '.md' : '.html';
433
+ const outPath = opts.out ?
434
+ path_1.default.resolve(process.cwd(), opts.out) :
435
+ path_1.default.resolve(process.cwd(), `${path_1.default.basename(full, path_1.default.extname(full))}${defExt}`);
436
+ fs_1.default.writeFileSync(outPath, htmlOrMd, 'utf8');
437
+ console.log(`Doc generated: ${outPath}`);
438
+ }
439
+ catch (e) {
440
+ console.error('Error generating doc:', e?.message || e);
441
+ process.exit(2);
442
+ }
443
+ });
444
+ program.parseAsync(process.argv);
445
+ //# sourceMappingURL=cli.js.map