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,189 @@
1
+ /**
2
+ * CLI mock server runner – starts HTTP/HTTPS mock servers from .mmt server files.
3
+ * Mirrors the functionality in src/mmtAPI/mockRunner.ts but without VS Code dependencies.
4
+ */
5
+ import fs from 'fs';
6
+ import http from 'http';
7
+ import https from 'https';
8
+ import path from 'path';
9
+ import yaml from 'js-yaml';
10
+ import * as mmtcore from 'mmt-core';
11
+ const { mockParsePack, mockServer, variableReplacer } = mmtcore;
12
+ /** Track active servers so we can clean them all up at exit. */
13
+ const activeServers = new Map();
14
+ function resolveFilePath(relative, basePath) {
15
+ if (path.isAbsolute(relative)) {
16
+ return relative;
17
+ }
18
+ return path.resolve(path.dirname(basePath), relative);
19
+ }
20
+ /**
21
+ * Start a mock server from a .mmt server file.
22
+ * Returns a cleanup function that stops the server.
23
+ */
24
+ export async function startMockServerFromPath(filePath, envVars = {}) {
25
+ // Stop existing server on this path if any
26
+ const existing = activeServers.get(filePath);
27
+ if (existing) {
28
+ existing.dispose();
29
+ }
30
+ const rawContent = fs.readFileSync(filePath, 'utf-8');
31
+ let parsed;
32
+ try {
33
+ parsed = yaml.load(rawContent);
34
+ }
35
+ catch (err) {
36
+ throw new Error(`Mock server: YAML parse error in ${path.basename(filePath)}: ${err.message}`);
37
+ }
38
+ const { data, errors } = mockParsePack.parseMockData(parsed);
39
+ if (errors.length > 0 || !data) {
40
+ const msg = errors.map((e) => e.message).join('; ');
41
+ throw new Error(`Mock server validation errors in ${path.basename(filePath)}: ${msg}`);
42
+ }
43
+ // Check if a server is already running on this port
44
+ for (const [, handle] of activeServers) {
45
+ if (handle.port === data.port) {
46
+ // Server already running on this port — return a no‑op cleanup
47
+ return () => { };
48
+ }
49
+ }
50
+ // Create token resolver
51
+ const tokenResolver = (value) => {
52
+ variableReplacer.resetRandomTokenCache();
53
+ variableReplacer.resetCurrentTokenCache();
54
+ return variableReplacer.resolveEmbeddedTokens(value, envVars);
55
+ };
56
+ // Resolve tokens in global headers
57
+ if (data.headers) {
58
+ for (const [k, v] of Object.entries(data.headers)) {
59
+ if (typeof v === 'string') {
60
+ data.headers[k] = String(variableReplacer.resolveEmbeddedTokens(v, envVars));
61
+ }
62
+ }
63
+ }
64
+ // Build the router
65
+ const router = mockServer.createMockRouter(data, tokenResolver);
66
+ const requestHandler = (req, res) => {
67
+ const method = (req.method || 'GET').toLowerCase();
68
+ const urlStr = req.url || '/';
69
+ // Handle CORS preflight
70
+ if (data.cors) {
71
+ res.setHeader('Access-Control-Allow-Origin', '*');
72
+ res.setHeader('Access-Control-Allow-Methods', '*');
73
+ res.setHeader('Access-Control-Allow-Headers', '*');
74
+ if (method === 'options') {
75
+ res.statusCode = 204;
76
+ res.end();
77
+ return;
78
+ }
79
+ }
80
+ let body = '';
81
+ req.on('data', (chunk) => { body += chunk; });
82
+ req.on('end', async () => {
83
+ let pathname = urlStr;
84
+ const queryObj = {};
85
+ const qIdx = urlStr.indexOf('?');
86
+ if (qIdx >= 0) {
87
+ pathname = urlStr.slice(0, qIdx);
88
+ const searchParams = new URLSearchParams(urlStr.slice(qIdx + 1));
89
+ searchParams.forEach((v, k) => { queryObj[k] = v; });
90
+ }
91
+ let parsedBody;
92
+ try {
93
+ parsedBody = JSON.parse(body);
94
+ }
95
+ catch {
96
+ parsedBody = body || undefined;
97
+ }
98
+ const mockReq = {
99
+ method,
100
+ path: pathname,
101
+ headers: (req.headers || {}),
102
+ query: queryObj,
103
+ body: parsedBody,
104
+ };
105
+ let mockRes;
106
+ try {
107
+ mockRes = router(mockReq);
108
+ }
109
+ catch (err) {
110
+ res.statusCode = 500;
111
+ res.end(JSON.stringify({ error: 'Mock router error', message: err.message }));
112
+ return;
113
+ }
114
+ // Apply delay
115
+ if (mockRes.delay && mockRes.delay > 0) {
116
+ await new Promise(resolve => setTimeout(resolve, mockRes.delay));
117
+ }
118
+ // Resolve tokens in response headers per-request
119
+ if (mockRes.headers) {
120
+ for (const [k, v] of Object.entries(mockRes.headers)) {
121
+ if (typeof v === 'string') {
122
+ res.setHeader(k, String(variableReplacer.resolveEmbeddedTokens(v, envVars)));
123
+ }
124
+ else {
125
+ res.setHeader(k, v);
126
+ }
127
+ }
128
+ }
129
+ res.statusCode = mockRes.status;
130
+ const responseBody = mockRes.body !== undefined ? (typeof mockRes.body === 'string' ? mockRes.body : JSON.stringify(mockRes.body)) : '';
131
+ res.end(responseBody);
132
+ });
133
+ };
134
+ // Create server based on protocol
135
+ let server;
136
+ const protocol = data.protocol || 'http';
137
+ if (protocol === 'https' && data.tls) {
138
+ const certPath = resolveFilePath(data.tls.cert, filePath);
139
+ const keyPath = resolveFilePath(data.tls.key, filePath);
140
+ const tlsOptions = {
141
+ cert: fs.readFileSync(certPath),
142
+ key: fs.readFileSync(keyPath),
143
+ };
144
+ if (data.tls.ca) {
145
+ tlsOptions.ca = fs.readFileSync(resolveFilePath(data.tls.ca, filePath));
146
+ }
147
+ if (data.tls.requestCert) {
148
+ tlsOptions.requestCert = true;
149
+ tlsOptions.rejectUnauthorized = false;
150
+ }
151
+ server = https.createServer(tlsOptions, requestHandler);
152
+ }
153
+ else {
154
+ server = http.createServer(requestHandler);
155
+ }
156
+ return new Promise((resolve, reject) => {
157
+ server.on('listening', () => {
158
+ const dispose = () => {
159
+ try {
160
+ server.close();
161
+ }
162
+ catch {
163
+ // ignore
164
+ }
165
+ activeServers.delete(filePath);
166
+ };
167
+ activeServers.set(filePath, { server, port: data.port, dispose });
168
+ resolve(dispose);
169
+ });
170
+ server.on('error', (err) => {
171
+ activeServers.delete(filePath);
172
+ if (err.code === 'EADDRINUSE') {
173
+ reject(new Error(`Mock server: port ${data.port} is already in use.`));
174
+ }
175
+ else {
176
+ reject(new Error(`Mock server error: ${err.message}`));
177
+ }
178
+ });
179
+ server.listen(data.port);
180
+ });
181
+ }
182
+ /** Stop all active mock servers. */
183
+ export function stopAllServers() {
184
+ for (const [, handle] of activeServers) {
185
+ handle.dispose();
186
+ }
187
+ activeServers.clear();
188
+ }
189
+ //# sourceMappingURL=mockRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mockRunner.js","sourceRoot":"","sources":["../src/mockRunner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,304 @@
1
+ import { findProjectRootSync } from 'mmt-core/fileHelper';
2
+ import fs from 'fs';
3
+ import yaml from 'js-yaml';
4
+ import * as mmtcore from 'mmt-core';
5
+ import { DEFAULT_NETWORK_CONFIG, resolvePassphrase } from 'mmt-core/NetworkData';
6
+ import path from 'path';
7
+ const { mergeEnv, resolvePresetEnv, resolveEnvFromDoc } = (mmtcore.runConfig || {});
8
+ function coerceCliValue(v) {
9
+ const t = (v ?? '').trim();
10
+ if (/^(true|false)$/i.test(t)) {
11
+ return /^true$/i.test(t);
12
+ }
13
+ if (/^[-+]?\d+$/.test(t)) {
14
+ return Number(t);
15
+ }
16
+ if (/^[-+]?\d*\.\d+$/.test(t)) {
17
+ return Number(t);
18
+ }
19
+ if ((t.startsWith('"') && t.endsWith('"')) ||
20
+ (t.startsWith('\'') && t.endsWith('\''))) {
21
+ return t.slice(1, -1);
22
+ }
23
+ return t;
24
+ }
25
+ function parsePairs(list) {
26
+ const out = {};
27
+ const arr = Array.isArray(list) ? list : [];
28
+ for (let i = 0; i < arr.length; i++) {
29
+ const token = arr[i] ?? '';
30
+ const eq = token.indexOf('=');
31
+ if (eq > 0) {
32
+ const k = token.slice(0, eq).trim();
33
+ const v = token.slice(eq + 1);
34
+ if (k) {
35
+ out[k] = coerceCliValue(v);
36
+ }
37
+ }
38
+ else if (i + 1 < arr.length) {
39
+ const k = token.trim();
40
+ const v = arr[++i];
41
+ if (k) {
42
+ out[k] = coerceCliValue(v);
43
+ }
44
+ }
45
+ }
46
+ return out;
47
+ }
48
+ function loadEnvDoc(envPath) {
49
+ try {
50
+ const txt = fs.readFileSync(envPath, 'utf8');
51
+ const data = yaml.load(txt);
52
+ if (!data || typeof data !== 'object') {
53
+ return {};
54
+ }
55
+ if (data.type && String(data.type) !== 'env') {
56
+ return {};
57
+ }
58
+ return {
59
+ variables: data.variables || {},
60
+ presets: data.presets || {},
61
+ certificates: data.certificates || undefined,
62
+ };
63
+ }
64
+ catch {
65
+ return {};
66
+ }
67
+ }
68
+ // Resolve certificate path relative to env file directory
69
+ function resolveCertPath(certPath, envFileDir) {
70
+ if (!certPath) {
71
+ return '';
72
+ }
73
+ if (path.isAbsolute(certPath)) {
74
+ return certPath;
75
+ }
76
+ return path.resolve(envFileDir, certPath);
77
+ }
78
+ // Build NetworkConfig from certificate settings in env file
79
+ // Note: Boolean settings (sslValidation, allowSelfSigned, enabled) default to sensible values
80
+ // since they are not stored in YAML
81
+ export function buildNetworkConfigFromEnv(certSettings, envFileDir, envVars) {
82
+ if (!certSettings) {
83
+ return { ...DEFAULT_NETWORK_CONFIG };
84
+ }
85
+ // Load CA certs (multiple paths)
86
+ const caCertDataList = [];
87
+ const caPaths = certSettings.ca?.paths || [];
88
+ // CA is enabled if there are paths defined
89
+ const caEnabled = caPaths.length > 0;
90
+ for (const caPath of caPaths) {
91
+ if (caPath) {
92
+ try {
93
+ const resolvedPath = resolveCertPath(caPath, envFileDir);
94
+ caCertDataList.push(fs.readFileSync(resolvedPath));
95
+ }
96
+ catch (e) {
97
+ console.warn(`Failed to load CA certificate from ${caPath}: ${e}`);
98
+ }
99
+ }
100
+ }
101
+ // Load client certs (use snake_case fields from YAML)
102
+ const clients = (certSettings.clients || []).map((client, idx) => {
103
+ let certData = undefined;
104
+ let keyData = undefined;
105
+ // Client is enabled by default (boolean not in YAML)
106
+ const clientEnabled = true;
107
+ const certPath = client.cert_path || '';
108
+ const keyPath = client.key_path || '';
109
+ if (certPath && keyPath) {
110
+ try {
111
+ const certResolvedPath = resolveCertPath(certPath, envFileDir);
112
+ const keyResolvedPath = resolveCertPath(keyPath, envFileDir);
113
+ certData = fs.readFileSync(certResolvedPath);
114
+ keyData = fs.readFileSync(keyResolvedPath);
115
+ }
116
+ catch (e) {
117
+ console.warn(`Failed to load client certificate for ${client.host || 'unknown'}: ${e}`);
118
+ }
119
+ }
120
+ const passphrase = resolvePassphrase(client.passphrase_plain, client.passphrase_env, envVars, process.env);
121
+ return {
122
+ id: `client-${idx}`,
123
+ name: client.name || '',
124
+ host: client.host || '*',
125
+ cert_path: certPath,
126
+ key_path: keyPath,
127
+ passphrase_plain: passphrase,
128
+ certData,
129
+ keyData,
130
+ enabled: clientEnabled,
131
+ };
132
+ });
133
+ return {
134
+ ca: { enabled: caEnabled, certPaths: caPaths, certData: caCertDataList.length > 0 ? caCertDataList : undefined },
135
+ clients,
136
+ sslValidation: true, // Default true (not stored in YAML)
137
+ allowSelfSigned: false, // Default false (not stored in YAML)
138
+ timeout: 30000,
139
+ autoFormat: false,
140
+ };
141
+ }
142
+ /**
143
+ * Find the project root by walking up from startPath looking for multimeter.mmt.
144
+ * Returns the directory containing multimeter.mmt, or undefined if not found.
145
+ */
146
+ function findProjectRootForCli(startPath) {
147
+ return findProjectRootSync(startPath, fs.existsSync, path.dirname, path.join) ?? undefined;
148
+ }
149
+ export function buildCliRunArgs(file, opts) {
150
+ const full = path.resolve(process.cwd(), file);
151
+ const dir = path.dirname(full);
152
+ const rawText = fs.readFileSync(full, 'utf8');
153
+ const manualInputs = parsePairs(opts.input);
154
+ const manualEnvvars = parsePairs(opts.env);
155
+ const exampleOptRaw = typeof opts.example === 'string' ?
156
+ String(opts.example) :
157
+ undefined;
158
+ let exampleIndexOpt = undefined;
159
+ let exampleNameOpt = undefined;
160
+ if (exampleOptRaw && exampleOptRaw.trim()) {
161
+ const trimmed = exampleOptRaw.trim();
162
+ const numeric = trimmed.match(/^#?(\d+)$/);
163
+ if (numeric) {
164
+ const parsed = Number(numeric[1]);
165
+ exampleIndexOpt = parsed > 0 ? parsed - 1 : 0;
166
+ }
167
+ else {
168
+ exampleNameOpt = trimmed;
169
+ }
170
+ }
171
+ let envvar = undefined;
172
+ let networkConfig = undefined;
173
+ const envFileOpt = opts.envFile;
174
+ const presetName = opts.preset;
175
+ let envFileDir = dir;
176
+ // Detect if this is a suite file and check for suite environment config
177
+ let suiteEnvConfig;
178
+ try {
179
+ const parsed = yaml.load(rawText);
180
+ if (parsed && parsed.type === 'suite' && parsed.environment) {
181
+ suiteEnvConfig = parsed.environment;
182
+ }
183
+ }
184
+ catch {
185
+ // Not valid YAML or not a suite, continue normally
186
+ }
187
+ if (envFileOpt) {
188
+ let p = String(envFileOpt);
189
+ if (!path.isAbsolute(p)) {
190
+ const fromCwd = path.resolve(process.cwd(), p);
191
+ if (fs.existsSync(fromCwd)) {
192
+ p = fromCwd;
193
+ }
194
+ else {
195
+ p = path.resolve(dir, p);
196
+ }
197
+ }
198
+ envFileDir = path.dirname(p);
199
+ const doc = loadEnvDoc(p);
200
+ if (typeof resolveEnvFromDoc === 'function') {
201
+ envvar = resolveEnvFromDoc({ doc, presetName, manualEnvvars });
202
+ }
203
+ else {
204
+ const presetEnv = resolvePresetEnv(doc, presetName);
205
+ envvar = mergeEnv({ envvar: presetEnv, manualEnvvars });
206
+ }
207
+ // Build network config from certificates in env file
208
+ if (doc.certificates) {
209
+ networkConfig = buildNetworkConfigFromEnv(doc.certificates, envFileDir, envvar);
210
+ }
211
+ }
212
+ else {
213
+ envvar = mergeEnv({ envvar: undefined, manualEnvvars });
214
+ }
215
+ // Merge suite environment for CLI
216
+ // Priority: CLI -e > suite environment.variables > suite preset > --env-file/--preset > defaults
217
+ if (suiteEnvConfig) {
218
+ const projectRoot = findProjectRootForCli(full);
219
+ // Resolve suite preset if specified
220
+ let suitePresetEnv = {};
221
+ if (suiteEnvConfig.preset) {
222
+ let suiteEnvFilePath;
223
+ if (suiteEnvConfig.file) {
224
+ // Resolve relative to suite file or project root for +/ paths
225
+ if (suiteEnvConfig.file.startsWith('+/')) {
226
+ suiteEnvFilePath = projectRoot ? path.join(projectRoot, suiteEnvConfig.file.slice(2)) : undefined;
227
+ }
228
+ else {
229
+ suiteEnvFilePath = path.resolve(dir, suiteEnvConfig.file);
230
+ }
231
+ }
232
+ else if (projectRoot) {
233
+ // Use multimeter.mmt in project root
234
+ suiteEnvFilePath = path.join(projectRoot, 'multimeter.mmt');
235
+ }
236
+ if (suiteEnvFilePath && fs.existsSync(suiteEnvFilePath)) {
237
+ const suiteEnvDoc = loadEnvDoc(suiteEnvFilePath);
238
+ suitePresetEnv = resolvePresetEnv(suiteEnvDoc, suiteEnvConfig.preset);
239
+ }
240
+ }
241
+ // Merge: base (--env-file/--preset) < suite preset < suite variables < CLI -e
242
+ const baseEnv = { ...(envvar || {}) };
243
+ // Remove CLI -e from base (it will be applied at highest priority)
244
+ for (const key of Object.keys(manualEnvvars)) {
245
+ delete baseEnv[key];
246
+ }
247
+ const suiteVariables = suiteEnvConfig.variables ? { ...suiteEnvConfig.variables } : {};
248
+ envvar = { ...baseEnv, ...suitePresetEnv, ...suiteVariables, ...manualEnvvars };
249
+ }
250
+ const runFileOptions = {
251
+ file: rawText,
252
+ fileType: 'raw',
253
+ filePath: full,
254
+ exampleIndex: exampleIndexOpt,
255
+ exampleName: exampleNameOpt,
256
+ manualInputs,
257
+ envvar,
258
+ manualEnvvars,
259
+ fileLoader: async (p) => {
260
+ const rel = path.isAbsolute(p) ? p : path.join(dir, p);
261
+ if (!fs.existsSync(rel)) {
262
+ return '';
263
+ }
264
+ return fs.readFileSync(rel, 'utf8');
265
+ },
266
+ jsRunner: async () => { },
267
+ logger: (level, msg) => {
268
+ console.log(`[${level}] ${msg}`);
269
+ },
270
+ reporter: (_message) => { },
271
+ projectRoot: findProjectRootForCli(full),
272
+ };
273
+ // Wire collecting reporter when --report is requested
274
+ const reportFormat = opts.report;
275
+ let getReportResults;
276
+ if (reportFormat) {
277
+ const { createReportCollector } = mmtcore.reportCollector || {};
278
+ if (typeof createReportCollector === 'function') {
279
+ const collector = createReportCollector();
280
+ runFileOptions.reporter = collector.reporter;
281
+ getReportResults = collector.getResults;
282
+ }
283
+ }
284
+ const defaultReportFiles = {
285
+ junit: 'test-results.xml',
286
+ mmt: 'test-results.mmt',
287
+ html: 'test-results.html',
288
+ md: 'test-results.md',
289
+ };
290
+ return {
291
+ runFileOptions,
292
+ networkConfig,
293
+ quiet: !!opts.quiet,
294
+ outFile: opts.out ? String(opts.out) : undefined,
295
+ printJs: !!opts.printJs,
296
+ reportFormat,
297
+ reportFile: reportFormat
298
+ ? (opts.reportFile ? String(opts.reportFile) : defaultReportFiles[reportFormat])
299
+ : undefined,
300
+ getReportResults,
301
+ };
302
+ }
303
+ export { parsePairs, coerceCliValue };
304
+ //# sourceMappingURL=runArgs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runArgs.js","sourceRoot":"","sources":["../src/runArgs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ import { buildCliRunArgs } from './runArgs.js';
5
+ function createTempDir() {
6
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'cli-run-args-'));
7
+ }
8
+ function writeFile(dir, name, contents) {
9
+ const full = path.join(dir, name);
10
+ fs.writeFileSync(full, contents, 'utf8');
11
+ return full;
12
+ }
13
+ describe('buildCliRunArgs', () => {
14
+ const tempDirs = [];
15
+ let originalCwd;
16
+ beforeEach(() => {
17
+ originalCwd = process.cwd();
18
+ });
19
+ afterEach(() => {
20
+ process.chdir(originalCwd);
21
+ while (tempDirs.length) {
22
+ const dir = tempDirs.pop();
23
+ if (dir) {
24
+ fs.rmSync(dir, { recursive: true, force: true });
25
+ }
26
+ }
27
+ });
28
+ it('builds run options with manual input/env and env presets', async () => {
29
+ const dir = createTempDir();
30
+ tempDirs.push(dir);
31
+ const filePath = writeFile(dir, 'sample.mmt', 'type: api\nname: cli');
32
+ writeFile(dir, 'payload.txt', 'payload');
33
+ const envPath = writeFile(dir, 'envvars.mmt', `type: env\nvariables:\n API_URL:\n dev: http://dev.local\n prod: http://prod.local\npresets:\n runner:\n dev:\n API_URL: dev\n AUTH_TOKEN: static-token\n`);
34
+ process.chdir(dir);
35
+ const parsed = buildCliRunArgs('./sample.mmt', {
36
+ input: ['userId=42'],
37
+ env: ['trace=true'],
38
+ envFile: path.basename(envPath),
39
+ preset: 'runner.dev',
40
+ quiet: true,
41
+ out: 'result.json',
42
+ printJs: true,
43
+ });
44
+ expect(parsed.quiet).toBe(true);
45
+ expect(parsed.outFile).toBe('result.json');
46
+ expect(parsed.printJs).toBe(true);
47
+ const { runFileOptions } = parsed;
48
+ expect(runFileOptions.filePath).toBe(fs.realpathSync(path.join(dir, 'sample.mmt')));
49
+ expect(runFileOptions.file).toContain('type: api');
50
+ expect(runFileOptions.manualInputs).toEqual({ userId: 42 });
51
+ expect(runFileOptions.manualEnvvars).toEqual({ trace: true });
52
+ expect(runFileOptions.envvar).toEqual({
53
+ API_URL: 'http://dev.local',
54
+ AUTH_TOKEN: 'static-token',
55
+ });
56
+ const loaderResult = await runFileOptions.fileLoader('payload.txt');
57
+ expect(loaderResult).toBe('payload');
58
+ });
59
+ it('supports space-separated key/value tokens and relative env paths', async () => {
60
+ const dir = createTempDir();
61
+ tempDirs.push(dir);
62
+ const filePath = writeFile(dir, 'flow.mmt', 'type: test\nname: flow');
63
+ writeFile(dir, 'fixture.json', '{"ok":true}');
64
+ process.chdir(dir);
65
+ const parsed = buildCliRunArgs('./flow.mmt', {
66
+ input: ['limit', '5'],
67
+ env: ['region', 'us-east'],
68
+ quiet: false,
69
+ printJs: false,
70
+ });
71
+ expect(parsed.quiet).toBe(false);
72
+ expect(parsed.outFile).toBeUndefined();
73
+ expect(parsed.printJs).toBe(false);
74
+ const { runFileOptions } = parsed;
75
+ expect(runFileOptions.filePath).toBe(fs.realpathSync(filePath));
76
+ expect(runFileOptions.manualInputs).toEqual({ limit: 5 });
77
+ expect(runFileOptions.manualEnvvars).toEqual({ region: 'us-east' });
78
+ expect(runFileOptions.envvar).toBeUndefined();
79
+ const loaderResult = await runFileOptions.fileLoader('fixture.json');
80
+ expect(loaderResult).toBe('{"ok":true}');
81
+ });
82
+ });
83
+ //# sourceMappingURL=runArgs.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runArgs.test.js","sourceRoot":"","sources":["../src/runArgs.test.ts"],"names":[],"mappings":"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"}