mmt-testlight 0.4.3 → 0.4.5
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.
- package/README.md +1 -1
- package/dist/cli.js +26899 -9530
- package/dist/guides/agent-workflow.md +75 -0
- package/dist/guides/general.md +64 -0
- package/dist/guides/generate-api.md +221 -0
- package/dist/guides/generate-doc.md +103 -0
- package/dist/guides/generate-env.md +147 -0
- package/dist/guides/generate-loadtest.md +55 -0
- package/dist/guides/generate-suite.md +167 -0
- package/dist/guides/generate-test-skill.md +60 -0
- package/dist/guides/generate-test.md +335 -0
- package/dist/guides/generate.md +60 -0
- package/dist/guides/golden-smoke.md +52 -0
- package/dist/guides/min/api.md +29 -0
- package/dist/guides/min/constraints.md +9 -0
- package/dist/guides/min/doc.md +17 -0
- package/dist/guides/min/env.md +26 -0
- package/dist/guides/min/loadtest.md +17 -0
- package/dist/guides/min/overview.md +25 -0
- package/dist/guides/min/suite.md +18 -0
- package/dist/guides/min/test.md +42 -0
- package/dist/guides/min/workflow.md +16 -0
- package/dist/guides/offline-agent.md +46 -0
- package/esbuild.mjs +25 -0
- package/package.json +2 -1
- package/src/aiDocs.ts +88 -0
- package/src/cli.ts +315 -24
- package/src/pathNormalize.cjs +16 -0
- package/src/pathNormalize.test.ts +8 -0
- package/src/pkg-entry.cjs +75 -40
- package/src/runArgs.ts +4 -4
- package/src/selfUpdate.test.ts +79 -0
- package/src/selfUpdate.ts +471 -0
- package/src/validateMmt.ts +45 -0
package/src/pkg-entry.cjs
CHANGED
|
@@ -165,7 +165,7 @@ function createPkgJsRunner() {
|
|
|
165
165
|
'__checkLogMode',
|
|
166
166
|
`${helperDecls}\n${randomDecls}\n` +
|
|
167
167
|
`const report_ = (...args) => mmtHelper.reportWithContext_ ? mmtHelper.reportWithContext_(__reporter, __runId, __id, ...args) : undefined;\n` +
|
|
168
|
-
`const setenv_ = (
|
|
168
|
+
`const setenv_ = (vars) => { if (vars && typeof vars === 'object') { for (const [k, v] of Object.entries(vars)) { try { envVariables[k] = v; } catch (_e) {} } } if (mmtHelper.setenvWithContext_) mmtHelper.setenvWithContext_(__reporter, __runId, __id, vars); };\n` +
|
|
169
169
|
`const check_ = (passed, type, raw, reportLevel, title, details, actual, expected) => mmtHelper.check_(passed, type, raw, reportLevel, title, details, actual, expected, report_, console, __checkLogMode);\n` +
|
|
170
170
|
`const checkExpects_ = (items, type, reportLevel, title, details) => mmtHelper.checkExpects_ ? mmtHelper.checkExpects_(items, type, reportLevel, title, details, report_, console, __checkLogMode) : undefined;\n` +
|
|
171
171
|
`const checkAbort_ = () => { if (__abortSignal && __abortSignal.aborted) { const e = new Error('Test run was stopped'); e.name = 'TestAbortError'; throw e; } };\n` +
|
|
@@ -522,16 +522,16 @@ function printHelp() {
|
|
|
522
522
|
const options = formatHelpRows([
|
|
523
523
|
['-h, --help', 'Show help'],
|
|
524
524
|
['-v, --version', 'Show version'],
|
|
525
|
-
['--log-level <level>', 'Set log level (error|warn|info|debug|trace)'],
|
|
525
|
+
['-L, --log-level <level>', 'Set log level (error|warn|info|debug|trace)'],
|
|
526
526
|
]);
|
|
527
527
|
const runOptions = formatHelpRows([
|
|
528
528
|
['-q, --quiet', 'Minimal output'],
|
|
529
529
|
['-o, --out <file>', 'Write result JSON to file'],
|
|
530
530
|
['-i, --input <k=v...>', 'Input variables (repeatable)'],
|
|
531
531
|
['-e, --env <k=v...>', 'Environment variables (repeatable)'],
|
|
532
|
-
['--env-file <path>', 'Environment file (.mmt/.yaml)'],
|
|
533
|
-
['--preset <name>', 'Preset from env file (
|
|
534
|
-
['--example <name|#n>', 'Named example or index (#1 is first)'],
|
|
532
|
+
['-F, --env-file <path>', 'Environment file (.mmt/.yaml)'],
|
|
533
|
+
['-P, --preset <name>', 'Preset from env file (repeatable)'],
|
|
534
|
+
['-x, --example <name|#n>', 'Named example or index (#1 is first)'],
|
|
535
535
|
['--print-js', 'Print generated JS before executing'],
|
|
536
536
|
['--debug-env', 'Print resolved env vars'],
|
|
537
537
|
]);
|
|
@@ -567,12 +567,12 @@ function printRunHelp() {
|
|
|
567
567
|
['-o, --out <file>', 'Write result JSON to file'],
|
|
568
568
|
['-i, --input <k=v...>', 'Input variables (repeatable)'],
|
|
569
569
|
['-e, --env <k=v...>', 'Environment variables (repeatable)'],
|
|
570
|
-
['--env-file <path>', 'Environment file (.mmt/.yaml)'],
|
|
571
|
-
['--preset <name>', 'Preset from env file (
|
|
572
|
-
['--example <name|#n>', 'Named example or index (#1 is first)'],
|
|
570
|
+
['-F, --env-file <path>', 'Environment file (.mmt/.yaml)'],
|
|
571
|
+
['-P, --preset <name>', 'Preset from env file (repeatable)'],
|
|
572
|
+
['-x, --example <name|#n>', 'Named example or index (#1 is first)'],
|
|
573
573
|
['--print-js', 'Print generated JS before executing'],
|
|
574
574
|
['--debug-env', 'Print resolved env vars'],
|
|
575
|
-
['--log-level <level>', 'Set log level (error|warn|info|debug|trace)'],
|
|
575
|
+
['-L, --log-level <level>', 'Set log level (error|warn|info|debug|trace)'],
|
|
576
576
|
]);
|
|
577
577
|
const examples = formatHelpRows([
|
|
578
578
|
['testlight run path/to/test.mmt'],
|
|
@@ -598,9 +598,9 @@ function printPrintJsHelp() {
|
|
|
598
598
|
['-h, --help', 'Show help'],
|
|
599
599
|
['-i, --input <k=v...>', 'Input variables (repeatable)'],
|
|
600
600
|
['-e, --env <k=v...>', 'Environment variables (repeatable)'],
|
|
601
|
-
['--env-file <path>', 'Environment file (.mmt/.yaml)'],
|
|
602
|
-
['--preset <name>', 'Preset from env file (
|
|
603
|
-
['--example <name|#n>', 'Named example or index (#1 is first)'],
|
|
601
|
+
['-F, --env-file <path>', 'Environment file (.mmt/.yaml)'],
|
|
602
|
+
['-P, --preset <name>', 'Preset from env file (repeatable)'],
|
|
603
|
+
['-x, --example <name|#n>', 'Named example or index (#1 is first)'],
|
|
604
604
|
]);
|
|
605
605
|
const out = [
|
|
606
606
|
'Usage: testlight print-js [options] <file>',
|
|
@@ -653,6 +653,39 @@ function parsePairs(tokens) {
|
|
|
653
653
|
return out;
|
|
654
654
|
}
|
|
655
655
|
|
|
656
|
+
function pushPresetOpt(opts, value) {
|
|
657
|
+
if (!Array.isArray(opts.preset)) {
|
|
658
|
+
opts.preset = opts.preset ? [opts.preset] : [];
|
|
659
|
+
}
|
|
660
|
+
if (value == null) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
for (const part of String(value).split(',')) {
|
|
664
|
+
const name = part.trim();
|
|
665
|
+
if (name) {
|
|
666
|
+
opts.preset.push(name);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function resolvePresetsFromDoc(runConfig, doc, presetNames) {
|
|
672
|
+
if (!runConfig || !doc) {
|
|
673
|
+
return {};
|
|
674
|
+
}
|
|
675
|
+
if (typeof runConfig.resolvePresetsEnv === 'function') {
|
|
676
|
+
return runConfig.resolvePresetsEnv(doc, presetNames) || {};
|
|
677
|
+
}
|
|
678
|
+
const list = Array.isArray(presetNames) ? presetNames :
|
|
679
|
+
(presetNames ? [presetNames] : []);
|
|
680
|
+
let out = {};
|
|
681
|
+
if (typeof runConfig.resolvePresetEnv === 'function') {
|
|
682
|
+
for (const name of list) {
|
|
683
|
+
Object.assign(out, runConfig.resolvePresetEnv(doc, name) || {});
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return out;
|
|
687
|
+
}
|
|
688
|
+
|
|
656
689
|
function loadEnvDoc(envPath) {
|
|
657
690
|
try {
|
|
658
691
|
// eslint-disable-next-line global-require
|
|
@@ -690,7 +723,7 @@ function parseRunArgv(argv) {
|
|
|
690
723
|
input: [],
|
|
691
724
|
env: [],
|
|
692
725
|
envFile: undefined,
|
|
693
|
-
preset:
|
|
726
|
+
preset: [],
|
|
694
727
|
example: undefined,
|
|
695
728
|
printJs: false,
|
|
696
729
|
debugEnv: false,
|
|
@@ -711,7 +744,7 @@ function parseRunArgv(argv) {
|
|
|
711
744
|
if (a === '-h' || a === '--help') {
|
|
712
745
|
continue;
|
|
713
746
|
}
|
|
714
|
-
if (a === '--log-level') {
|
|
747
|
+
if (a === '--log-level' || a === '-L') {
|
|
715
748
|
consume(i + 1);
|
|
716
749
|
i += 1;
|
|
717
750
|
continue;
|
|
@@ -758,17 +791,17 @@ function parseRunArgv(argv) {
|
|
|
758
791
|
}
|
|
759
792
|
continue;
|
|
760
793
|
}
|
|
761
|
-
if (a === '--env-file') {
|
|
794
|
+
if (a === '--env-file' || a === '-F') {
|
|
762
795
|
consume(i + 1);
|
|
763
796
|
opts.envFile = argv[++i];
|
|
764
797
|
continue;
|
|
765
798
|
}
|
|
766
|
-
if (a === '--preset') {
|
|
799
|
+
if (a === '--preset' || a === '-P') {
|
|
767
800
|
consume(i + 1);
|
|
768
|
-
opts
|
|
801
|
+
pushPresetOpt(opts, argv[++i]);
|
|
769
802
|
continue;
|
|
770
803
|
}
|
|
771
|
-
if (a === '--example') {
|
|
804
|
+
if (a === '--example' || a === '-x') {
|
|
772
805
|
consume(i + 1);
|
|
773
806
|
opts.example = argv[++i];
|
|
774
807
|
continue;
|
|
@@ -802,7 +835,7 @@ function parsePrintJsArgv(argv) {
|
|
|
802
835
|
input: [],
|
|
803
836
|
env: [],
|
|
804
837
|
envFile: undefined,
|
|
805
|
-
preset:
|
|
838
|
+
preset: [],
|
|
806
839
|
example: undefined,
|
|
807
840
|
};
|
|
808
841
|
let filePath;
|
|
@@ -834,15 +867,15 @@ function parsePrintJsArgv(argv) {
|
|
|
834
867
|
}
|
|
835
868
|
continue;
|
|
836
869
|
}
|
|
837
|
-
if (a === '--env-file') {
|
|
870
|
+
if (a === '--env-file' || a === '-F') {
|
|
838
871
|
opts.envFile = argv[++i];
|
|
839
872
|
continue;
|
|
840
873
|
}
|
|
841
|
-
if (a === '--preset') {
|
|
842
|
-
opts
|
|
874
|
+
if (a === '--preset' || a === '-P') {
|
|
875
|
+
pushPresetOpt(opts, argv[++i]);
|
|
843
876
|
continue;
|
|
844
877
|
}
|
|
845
|
-
if (a === '--example') {
|
|
878
|
+
if (a === '--example' || a === '-x') {
|
|
846
879
|
opts.example = argv[++i];
|
|
847
880
|
continue;
|
|
848
881
|
}
|
|
@@ -857,17 +890,19 @@ function parsePrintJsArgv(argv) {
|
|
|
857
890
|
}
|
|
858
891
|
|
|
859
892
|
function parseLogLevel(argv) {
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
893
|
+
for (let i = 1; i < argv.length; i++) {
|
|
894
|
+
const a = argv[i];
|
|
895
|
+
if (a === '--log-level' || a === '-L') {
|
|
896
|
+
const v = argv[i + 1];
|
|
897
|
+
if (!v) {
|
|
898
|
+
return 'info';
|
|
899
|
+
}
|
|
900
|
+
const level = String(v).toLowerCase();
|
|
901
|
+
if (['error', 'warn', 'info', 'debug', 'trace'].includes(level)) {
|
|
902
|
+
return level;
|
|
903
|
+
}
|
|
904
|
+
return 'info';
|
|
905
|
+
}
|
|
871
906
|
}
|
|
872
907
|
return 'info';
|
|
873
908
|
}
|
|
@@ -1023,9 +1058,8 @@ async function main() {
|
|
|
1023
1058
|
const doc = loadEnvDoc(p);
|
|
1024
1059
|
// Defaults come from env file variables; optional preset overlays them.
|
|
1025
1060
|
let baseEnv = (doc && doc.variables) ? {...doc.variables} : {};
|
|
1026
|
-
if (parsed.opts.preset && runConfig
|
|
1027
|
-
|
|
1028
|
-
const presetEnv = runConfig.resolvePresetEnv(doc, parsed.opts.preset) || {};
|
|
1061
|
+
if (parsed.opts.preset.length && runConfig) {
|
|
1062
|
+
const presetEnv = resolvePresetsFromDoc(runConfig, doc, parsed.opts.preset);
|
|
1029
1063
|
baseEnv = {...baseEnv, ...presetEnv};
|
|
1030
1064
|
}
|
|
1031
1065
|
envvar = runConfig.mergeEnv({ envvar: baseEnv, manualEnvvars });
|
|
@@ -1033,7 +1067,7 @@ async function main() {
|
|
|
1033
1067
|
try {
|
|
1034
1068
|
process.stdout.write(`debug envFile raw: ${envFileRaw}\n`);
|
|
1035
1069
|
process.stdout.write(`debug envFile resolved: ${p}\n`);
|
|
1036
|
-
process.stdout.write(`debug preset: ${parsed.opts.preset
|
|
1070
|
+
process.stdout.write(`debug preset: ${parsed.opts.preset.length ? parsed.opts.preset.join(',') : '(none)'}\n`);
|
|
1037
1071
|
process.stdout.write(`debug envDoc variables keys: ${doc && doc.variables ? Object.keys(doc.variables).join(',') : '(none)'}\n`);
|
|
1038
1072
|
process.stdout.write(`debug envDoc presets keys: ${doc && doc.presets ? Object.keys(doc.presets).join(',') : '(none)'}\n`);
|
|
1039
1073
|
process.stdout.write(`debug baseEnv keys: ${baseEnv ? Object.keys(baseEnv).join(',') : '(none)'}\n`);
|
|
@@ -1123,8 +1157,9 @@ async function main() {
|
|
|
1123
1157
|
envFileForPreset = loadEnvDoc(p);
|
|
1124
1158
|
}
|
|
1125
1159
|
}
|
|
1126
|
-
if (envFileForPreset && coreRunConfig
|
|
1127
|
-
suitePresetEnv =
|
|
1160
|
+
if (envFileForPreset && coreRunConfig) {
|
|
1161
|
+
suitePresetEnv = resolvePresetsFromDoc(
|
|
1162
|
+
coreRunConfig, envFileForPreset, suiteEnv.preset) || {};
|
|
1128
1163
|
}
|
|
1129
1164
|
}
|
|
1130
1165
|
|
package/src/runArgs.ts
CHANGED
|
@@ -18,7 +18,7 @@ const {resolveUserPath, resolveUserPathPreferExisting} = requireFromRunArgs('../
|
|
|
18
18
|
|
|
19
19
|
export type ReportFormat = 'junit' | 'mmt' | 'html' | 'md' | 'md-detailed';
|
|
20
20
|
|
|
21
|
-
const {mergeEnv,
|
|
21
|
+
const {mergeEnv, resolvePresetsEnv, normalizePresetNames} =
|
|
22
22
|
((mmtcore as any).runConfig || {}) as any;
|
|
23
23
|
const OMIT_SENTINEL =
|
|
24
24
|
((mmtcore as any).omitKeyword || {}).OMIT_SENTINEL || '__MMT_OMIT__';
|
|
@@ -347,7 +347,7 @@ export async function buildCliRunArgs(file: string, opts: AnyOpts): Promise<Pars
|
|
|
347
347
|
let envvar: Record<string, any>|undefined = undefined;
|
|
348
348
|
let networkConfig: NetworkConfig|undefined = undefined;
|
|
349
349
|
const envFileOpt = opts.envFile as string | undefined;
|
|
350
|
-
const
|
|
350
|
+
const presetNames = normalizePresetNames(opts.preset as string | string[] | undefined);
|
|
351
351
|
let envFileDir = dir;
|
|
352
352
|
|
|
353
353
|
// Detect if this is a suite file and check for suite environment config
|
|
@@ -368,7 +368,7 @@ export async function buildCliRunArgs(file: string, opts: AnyOpts): Promise<Pars
|
|
|
368
368
|
envFileDir = path.dirname(p);
|
|
369
369
|
const doc = await loadEnvDoc(p, findProjectRootForCli(p) || undefined);
|
|
370
370
|
const defaultEnv = resolveDefaultEnvVariables(doc.variables);
|
|
371
|
-
const presetEnv =
|
|
371
|
+
const presetEnv = resolvePresetsEnv(doc, presetNames);
|
|
372
372
|
envvar = mergeEnv({baseEnv: defaultEnv, envvar: presetEnv, manualEnvvars});
|
|
373
373
|
// Build network config from file-driven HTTP settings and certificates.
|
|
374
374
|
if (doc.certificates || doc.setting) {
|
|
@@ -403,7 +403,7 @@ export async function buildCliRunArgs(file: string, opts: AnyOpts): Promise<Pars
|
|
|
403
403
|
if (suiteEnvFilePath && fs.existsSync(suiteEnvFilePath)) {
|
|
404
404
|
const suiteEnvDoc =
|
|
405
405
|
await loadEnvDoc(suiteEnvFilePath, projectRoot || undefined);
|
|
406
|
-
suitePresetEnv =
|
|
406
|
+
suitePresetEnv = resolvePresetsEnv(suiteEnvDoc, suiteEnvConfig.preset);
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildDownloadUrl,
|
|
3
|
+
compareVersions,
|
|
4
|
+
detectInstallChannel,
|
|
5
|
+
detectPlatform,
|
|
6
|
+
normalizeVersion,
|
|
7
|
+
planUpdate,
|
|
8
|
+
} from './selfUpdate';
|
|
9
|
+
|
|
10
|
+
describe('selfUpdate helpers', () => {
|
|
11
|
+
it('normalizes and compares versions', () => {
|
|
12
|
+
expect(normalizeVersion('v1.2.3')).toBe('1.2.3');
|
|
13
|
+
expect(compareVersions('1.2.3', '1.2.3')).toBe(0);
|
|
14
|
+
expect(compareVersions('1.2.3', '1.2.4')).toBe(-1);
|
|
15
|
+
expect(compareVersions('1.3.0', '1.2.9')).toBe(1);
|
|
16
|
+
expect(compareVersions('1.2.3-beta.1', '1.2.3')).toBe(-1);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('detects platforms', () => {
|
|
20
|
+
expect(detectPlatform('darwin', 'arm64')).toBe('macos-arm64');
|
|
21
|
+
expect(detectPlatform('linux', 'x64')).toBe('linux-x64');
|
|
22
|
+
expect(detectPlatform('win32', 'arm64')).toBe('win-x64');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('detects install channels', () => {
|
|
26
|
+
expect(detectInstallChannel('/usr/local/bin/testlight')).toBe('standalone');
|
|
27
|
+
expect(detectInstallChannel(
|
|
28
|
+
'/opt/homebrew/Cellar/mmt-testlight/0.4.4/bin/testlight'))
|
|
29
|
+
.toBe('homebrew');
|
|
30
|
+
expect(detectInstallChannel(
|
|
31
|
+
'/usr/local/bin/node',
|
|
32
|
+
'/usr/local/lib/node_modules/mmt-testlight/dist/cli.js'))
|
|
33
|
+
.toBe('npm');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('builds github and portal download urls', () => {
|
|
37
|
+
expect(buildDownloadUrl({version: '1.2.3', platform: 'linux-x64'}))
|
|
38
|
+
.toBe(
|
|
39
|
+
'https://github.com/mshobeyri/multimeter/releases/download/v1.2.3/testlight-linux-x64.tar.gz');
|
|
40
|
+
expect(buildDownloadUrl({
|
|
41
|
+
version: '1.2.3',
|
|
42
|
+
platform: 'win-x64',
|
|
43
|
+
releaseBaseUrl: 'https://portal.example/testlight',
|
|
44
|
+
})).toBe('https://portal.example/testlight/v1.2.3/testlight-win-x64.zip');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('plans npm advice instead of binary replace', async () => {
|
|
48
|
+
const plan = await planUpdate({
|
|
49
|
+
currentVersion: '0.4.0',
|
|
50
|
+
version: '0.4.4',
|
|
51
|
+
execPath: '/usr/local/bin/node',
|
|
52
|
+
scriptPath: '/usr/local/lib/node_modules/mmt-testlight/dist/cli.js',
|
|
53
|
+
fetchJson: async () => ({tag_name: 'v0.4.4'}),
|
|
54
|
+
});
|
|
55
|
+
expect(plan.action).toBe('advise-npm');
|
|
56
|
+
expect(plan.advice).toContain('npm install -g mmt-testlight@0.4.4');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('plans noop when already current', async () => {
|
|
60
|
+
const plan = await planUpdate({
|
|
61
|
+
currentVersion: '1.2.3',
|
|
62
|
+
version: '1.2.3',
|
|
63
|
+
execPath: '/usr/local/bin/testlight',
|
|
64
|
+
fetchJson: async () => ({tag_name: 'v1.2.3'}),
|
|
65
|
+
});
|
|
66
|
+
expect(plan.action).toBe('noop');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('plans download for standalone when newer', async () => {
|
|
70
|
+
const plan = await planUpdate({
|
|
71
|
+
currentVersion: '1.0.0',
|
|
72
|
+
version: '1.2.3',
|
|
73
|
+
execPath: '/opt/portal/bin/testlight',
|
|
74
|
+
fetchJson: async () => ({tag_name: 'v1.2.3'}),
|
|
75
|
+
});
|
|
76
|
+
expect(plan.action).toBe('download');
|
|
77
|
+
expect(plan.downloadUrl).toContain('/v1.2.3/testlight-');
|
|
78
|
+
});
|
|
79
|
+
});
|