ado-sync 0.1.31 → 0.1.33

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/docs/advanced.md CHANGED
@@ -374,7 +374,7 @@ Local source files are **never modified** by the AI summary feature.
374
374
  | `local` *(default)* | Good–Excellent | A GGUF model file (see setup below) |
375
375
  | `heuristic` | Basic | Nothing — zero dependencies, works offline |
376
376
  | `ollama` | Good–Excellent | [Ollama](https://ollama.com) server running locally |
377
- | `openai` | Excellent | OpenAI API key |
377
+ | `openai` | Excellent | OpenAI API key, or any OpenAI-compatible proxy (LiteLLM, Azure OpenAI, vLLM, etc.) |
378
378
  | `anthropic` | Excellent | Anthropic API key |
379
379
 
380
380
  > **No setup required to try it.** If no `--ai-model` is passed for `local`, it falls back to `heuristic` silently — so `ado-sync push` always works.
@@ -472,6 +472,61 @@ ado-sync push --ai-provider openai --ai-key $OPENAI_API_KEY
472
472
  ado-sync push --ai-provider anthropic --ai-key $ANTHROPIC_API_KEY
473
473
  ```
474
474
 
475
+ ### Using LiteLLM (or any OpenAI-compatible proxy)
476
+
477
+ [LiteLLM](https://github.com/BerriAI/litellm) is a proxy that exposes an OpenAI-compatible API for 100+ model providers (Azure OpenAI, Bedrock, Gemini, Mistral, Cohere, vLLM, and more). Use the `openai` provider with `--ai-url` pointing at your LiteLLM server:
478
+
479
+ ```bash
480
+ # Start LiteLLM proxy (example)
481
+ litellm --model gpt-4o-mini # listens on http://localhost:4000 by default
482
+
483
+ # Push using LiteLLM
484
+ ado-sync push \
485
+ --ai-provider openai \
486
+ --ai-url http://localhost:4000 \
487
+ --ai-key $LITELLM_API_KEY \
488
+ --ai-model gpt-4o-mini
489
+ ```
490
+
491
+ The same `--ai-url` override works for any other OpenAI-compatible server:
492
+
493
+ | Service | `--ai-url` |
494
+ |---------|-----------|
495
+ | LiteLLM (local proxy) | `http://localhost:4000` |
496
+ | Azure OpenAI | `https://<resource>.openai.azure.com/openai/deployments/<deployment>` |
497
+ | vLLM | `http://localhost:8000/v1` |
498
+ | LocalAI | `http://localhost:8080/v1` |
499
+ | LM Studio | `http://localhost:1234/v1` |
500
+
501
+ Config file equivalent — set any `--ai-*` flag in `sync.ai` to avoid repeating it on every push. CLI flags always take precedence over config values:
502
+
503
+ ```json
504
+ {
505
+ "sync": {
506
+ "ai": {
507
+ "provider": "openai",
508
+ "baseUrl": "http://localhost:4000",
509
+ "apiKey": "$LITELLM_API_KEY",
510
+ "model": "gpt-4o-mini"
511
+ }
512
+ }
513
+ }
514
+ ```
515
+
516
+ The `sync.ai` block works for any provider:
517
+
518
+ ```json
519
+ { "sync": { "ai": { "provider": "ollama", "model": "qwen2.5-coder:7b" } } }
520
+ ```
521
+
522
+ ```json
523
+ { "sync": { "ai": { "provider": "anthropic", "apiKey": "$ANTHROPIC_API_KEY" } } }
524
+ ```
525
+
526
+ ```json
527
+ { "sync": { "ai": { "provider": "none" } } }
528
+ ```
529
+
475
530
  ### Disabling AI summary
476
531
 
477
532
  ```bash
@@ -48,7 +48,7 @@ ado-sync publish-test-results \
48
48
  | NUnit XML (native) | `.xml` | Yes (`<test-run>` root) | Yes — via `[Property("tc","ID")]` | `<output>` + `<attachments>` |
49
49
  | JUnit XML | `.xml` | Yes (`<testsuites>` / `<testsuite>` root) | Optional — via `<property name="tc" value="ID"/>` | `<system-out>`, `<system-err>`, `[[ATTACHMENT\|path]]` (Playwright) |
50
50
  | Cucumber JSON | `.json` | Yes (JSON array, Cucumber format) | Yes — via `@tc:ID` tag on scenario | `step.embeddings[]` (base64 screenshots/video) |
51
- | Playwright JSON | `.json` | Yes (JSON object with `suites` key) | Via `@tc:ID` in test title | `test.results[].attachments[]` (screenshots, videos, traces) |
51
+ | Playwright JSON | `.json` | Yes (JSON object with `suites` key) | Yes — via `test.annotations[{ type: 'tc', description: 'ID' }]` (preferred) or `@tc:ID` in test title | `test.results[].attachments[]` (screenshots, videos, traces) |
52
52
 
53
53
  > **NUnit via TRX**: when NUnit tests are run through the VSTest adapter (`--logger trx`), `[Property]` values are **not** included in the TRX output. Use `--logger "nunit3;LogFileName=results.xml"` to get the native NUnit XML format, which does include property values.
54
54
 
@@ -64,7 +64,7 @@ ado-sync publish-test-results \
64
64
 
65
65
  Results are linked to Azure Test Cases in priority order:
66
66
 
67
- 1. **TC ID from file** (preferred) — when the result file contains a TC ID (`[TestProperty]`, `[Property]`, `<property name="tc">`, or `@tc:` tag), the result is posted with `testCase.id` set directly. This is robust to class/method renames.
67
+ 1. **TC ID from file** (preferred) — when the result file contains a TC ID (`[TestProperty]`, `[Property]`, `<property name="tc">`, `@tc:` tag, or Playwright `test.annotations[{ type: 'tc' }]`), the result is posted with `testCase.id` set directly. This is robust to class/method renames.
68
68
  2. **AutomatedTestName matching** (fallback) — when no TC ID is found, the result is posted with `automatedTestName` = the fully-qualified method name. Azure DevOps links it to a TC whose `AutomatedTestName` field matches. Requires `sync.markAutomated: true` on push.
69
69
 
70
70
  ---
@@ -303,6 +303,136 @@ ado-sync publish-test-results \
303
303
 
304
304
  ---
305
305
 
306
+ ### TestCafe
307
+
308
+ TestCafe requires the `testcafe-reporter-junit` package to produce JUnit XML:
309
+
310
+ ```bash
311
+ npm install --save-dev testcafe-reporter-junit
312
+ ```
313
+
314
+ ```bash
315
+ # Run tests with JUnit reporter
316
+ npx testcafe chrome tests/ --reporter junit:results/junit.xml
317
+
318
+ # Publish results
319
+ ado-sync publish-test-results --testResult results/junit.xml --testResultFormat junit
320
+ ```
321
+
322
+ TC linking uses `AutomatedTestName` matching — set `sync.markAutomated: true` on push. The `automatedTestName` format stored in the TC is `{fileBasename} > {fixture} > {testTitle}`, which matches the JUnit `suiteName.testName` format produced by `testcafe-reporter-junit`.
323
+
324
+ > **TC IDs are not embedded in JUnit output**: TestCafe's `test.meta('tc', 'N')` metadata is not written into the JUnit XML. Linking relies on AutomatedTestName matching only.
325
+
326
+ ---
327
+
328
+ ### Cypress
329
+
330
+ Cypress has a built-in JUnit reporter via Mocha:
331
+
332
+ ```bash
333
+ # Run tests with JUnit reporter (outputs one file per spec by default)
334
+ npx cypress run \
335
+ --reporter junit \
336
+ --reporter-options "mochaFile=results/junit-[hash].xml"
337
+
338
+ # Publish all result files
339
+ ado-sync publish-test-results \
340
+ --testResult "results/junit-*.xml" \
341
+ --testResultFormat junit
342
+ ```
343
+
344
+ TC linking uses `AutomatedTestName` matching — set `sync.markAutomated: true` on push.
345
+
346
+ > **JUnit classname format**: By default Cypress sets `classname` to the spec file path and `name` to the test title. Ensure your `automatedTestName` format matches by setting `reporterOptions: { suiteTitleSeparatedBy: ' > ' }` in `cypress.config.js`.
347
+
348
+ ---
349
+
350
+ ### Detox (React Native)
351
+
352
+ Detox uses Jest as its runner — use `jest-junit` the same way as Jest:
353
+
354
+ ```bash
355
+ npm install --save-dev jest-junit
356
+ ```
357
+
358
+ ```bash
359
+ # Run Detox tests
360
+ JEST_JUNIT_OUTPUT_DIR=results JEST_JUNIT_OUTPUT_NAME=junit.xml \
361
+ npx detox test --configuration ios.sim.release
362
+
363
+ # Publish results
364
+ ado-sync publish-test-results --testResult results/junit.xml --testResultFormat junit
365
+ ```
366
+
367
+ TC linking uses `AutomatedTestName` matching — set `sync.markAutomated: true` on push.
368
+
369
+ ---
370
+
371
+ ### XCUITest (iOS / macOS)
372
+
373
+ Export results from Xcode's `.xcresult` bundle to JUnit XML using `xcresulttool`:
374
+
375
+ ```bash
376
+ # Run tests and save result bundle
377
+ xcodebuild test \
378
+ -project MyApp.xcodeproj \
379
+ -scheme MyApp \
380
+ -destination 'platform=iOS Simulator,name=iPhone 15' \
381
+ -resultBundlePath TestResults.xcresult
382
+
383
+ # Export JUnit XML
384
+ xcrun xcresulttool get --path TestResults.xcresult --format junit > results/junit.xml
385
+
386
+ # Publish results
387
+ ado-sync publish-test-results --testResult results/junit.xml --testResultFormat junit
388
+ ```
389
+
390
+ TC linking uses `AutomatedTestName` matching — set `sync.markAutomated: true` on push. The `automatedTestName` format is `{className}/{funcTestName}` (e.g. `LoginUITests/testValidCredentialsNavigateToInventory`).
391
+
392
+ > **Attachments**: `xcresulttool` does not embed screenshots in the JUnit export. Use `--attachmentsFolder` to attach screenshot files produced by your test hooks separately.
393
+
394
+ ---
395
+
396
+ ### Espresso (Android)
397
+
398
+ Gradle's `connectedAndroidTest` task writes JUnit XML to `app/build/outputs/androidTest-results/connected/`:
399
+
400
+ ```bash
401
+ # Run instrumented tests
402
+ ./gradlew connectedAndroidTest
403
+
404
+ # Publish results
405
+ ado-sync publish-test-results \
406
+ --testResult "app/build/outputs/androidTest-results/connected/TEST-*.xml" \
407
+ --testResultFormat junit
408
+ ```
409
+
410
+ TC linking uses `AutomatedTestName` matching — set `sync.markAutomated: true` on push. The `automatedTestName` format is `{packageName}.{ClassName}.{methodName}`.
411
+
412
+ ---
413
+
414
+ ### Flutter
415
+
416
+ Flutter can produce JUnit XML via the `flutter_test_junit` package or by piping `--reporter junit`:
417
+
418
+ ```bash
419
+ # Option A — built-in reporter (Flutter ≥ 3.7)
420
+ flutter test --reporter junit > results/junit.xml
421
+
422
+ # Option B — flutter_test_junit package
423
+ flutter pub add --dev flutter_test_junit
424
+ dart run flutter_test_junit:main > results/junit.xml
425
+ ```
426
+
427
+ ```bash
428
+ # Publish results
429
+ ado-sync publish-test-results --testResult results/junit.xml --testResultFormat junit
430
+ ```
431
+
432
+ TC linking uses `AutomatedTestName` matching — set `sync.markAutomated: true` on push.
433
+
434
+ ---
435
+
306
436
  | Framework | Result format | TC ID in file | Attachments uploaded | Live-tested |
307
437
  |-----------|---------------|---------------|----------------------|-------------|
308
438
  | C# MSTest | TRX | ✅ `[TestProperty("tc","ID")]` | `<Output><StdOut>` + `<Output><ResultFiles>` files | ✅ |
@@ -314,8 +444,14 @@ ado-sync publish-test-results \
314
444
  | Jest | JUnit XML | ⚠️ optional `<property name="tc">` | `<system-out>`, `<system-err>` | ✅ |
315
445
  | WebdriverIO / Jasmine | JUnit XML | ⚠️ optional `<property name="tc">` | `<system-out>`, `<system-err>` | ✅ |
316
446
  | Cucumber JS | Cucumber JSON | ✅ `@tc:ID` tag | `step.embeddings[]` (base64 screenshots/video) | ✅ |
317
- | Playwright | Playwright JSON | Via `@tc:ID` in test title | Files from `attachments[].path` (screenshots, videos, traces) | ✅ |
318
- | Playwright | JUnit XML | Via `@tc:ID` in test title | `[[ATTACHMENT\|path]]` referenced files | ✅ |
447
+ | Playwright | Playwright JSON | native `annotation: { type: 'tc', description: 'ID' }`; or `@tc:ID` in test title | Files from `attachments[].path` (screenshots, videos, traces) | ✅ |
448
+ | Playwright | JUnit XML | ⚠️ `@tc:ID` in test title only (no annotation in JUnit format) | `[[ATTACHMENT\|path]]` referenced files | ✅ |
449
+ | TestCafe | JUnit XML | ❌ AutomatedTestName matching only | `<system-out>`, `<system-err>` | |
450
+ | Cypress | JUnit XML | ❌ AutomatedTestName matching only | `<system-out>`, `<system-err>` | |
451
+ | Detox | JUnit XML | ❌ AutomatedTestName matching only | `<system-out>`, `<system-err>` | |
452
+ | XCUITest | JUnit XML | ❌ AutomatedTestName matching only | none (use `--attachmentsFolder`) | |
453
+ | Espresso | JUnit XML | ❌ AutomatedTestName matching only | `<system-out>`, `<system-err>` | |
454
+ | Flutter | JUnit XML | ❌ AutomatedTestName matching only | `<system-out>`, `<system-err>` | |
319
455
 
320
456
  ---
321
457
 
@@ -471,6 +471,36 @@ Set `local.type: "playwright"`. Supports **Playwright Test** (`@playwright/test`
471
471
  | `test.describe.parallel` | Parallel describe block |
472
472
  | `test.describe.serial` | Serial describe block |
473
473
 
474
+ ### ID tagging — native annotation (recommended)
475
+
476
+ Playwright Test has a built-in annotation API. Use it to attach the TC ID directly in the test definition — no comments needed:
477
+
478
+ ```typescript
479
+ // Single annotation (most common)
480
+ test('completes checkout', {
481
+ annotation: { type: 'tc', description: '1041' },
482
+ tag: '@smoke',
483
+ }, async ({ page }) => { ... });
484
+
485
+ // Tag-style ID (alternative)
486
+ test('cart badge shows count', {
487
+ tag: ['@tc:1043', '@smoke'],
488
+ }, async ({ page }) => { ... });
489
+
490
+ // Array form — combine TC ID with other annotations
491
+ test.fixme('promo code applies discount', {
492
+ annotation: [
493
+ { type: 'tc', description: '1042' },
494
+ { type: 'issue', description: 'promo-code not yet implemented' },
495
+ ],
496
+ tag: '@wip',
497
+ }, async ({ page }) => { ... });
498
+ ```
499
+
500
+ **Writeback** — on the first `push`, ado-sync injects `{ annotation: { type: 'tc', description: 'N' } }` into the test options object. Subsequent pushes update the description in place.
501
+
502
+ **Comment fallback** — `// @tc:ID` above `test()` is still recognised and written for edge cases where the options object cannot be parsed (e.g., multi-line title spanning several lines).
503
+
474
504
  ### Source mapping
475
505
 
476
506
  | Playwright source | Azure TC field |
@@ -478,10 +508,16 @@ Set `local.type: "playwright"`. Supports **Playwright Test** (`@playwright/test`
478
508
  | JSDoc `/** ... */` first non-numbered line | TC **Title** |
479
509
  | Numbered lines `N. text` in JSDoc | TC **Steps** (action) |
480
510
  | Numbered lines `N. Check: text` in JSDoc | TC **Steps** (expected result, when `useExpectedResult: true`) |
481
- | `// @tags: smoke, regression` above `test()` | TC **Tags** |
482
- | `// @tc:ID` above `test()` | TC ID (written back after first push) |
511
+ | `annotation: { type: 'tc', description: 'N' }` | TC ID (**preferred**) |
512
+ | `annotation: [{ type: 'tc', description: 'N' }, ...]` | TC ID (array form) |
513
+ | `tag: '@tc:N'` or `tag: ['@tc:N', ...]` | TC ID (tag form) |
514
+ | `tag: '@smoke'` etc. | TC **Tags** |
515
+ | `// @tags: smoke, regression` above `test()` | TC **Tags** (comment form) |
516
+ | `// @tc:ID` above `test()` | TC ID (comment fallback) |
483
517
  | `{basename} > {describe} > {test title}` | `AutomatedTestName` |
484
518
 
519
+ **Priority**: native `annotation` > native `tag` > `// @tc:N` comment.
520
+
485
521
  ### TypeScript example
486
522
 
487
523
  ```typescript
@@ -496,28 +532,35 @@ test.describe('SauceDemo Checkout', () => {
496
532
  * 3. Proceed through checkout and enter name and zip
497
533
  * 4. Check: Order confirmation page is displayed
498
534
  */
499
- // @tc:1041 // written back by ado-sync after first push
500
- // @tags: smoke, checkout
501
- test('completes checkout with valid details', async ({ page }) => {
535
+ test('completes checkout with valid details', {
536
+ annotation: { type: 'tc', description: '1041' },
537
+ tag: '@smoke',
538
+ }, async ({ page }) => {
502
539
  // ...
503
540
  });
504
541
 
505
- // @tc:1042
506
- test.fixme('promo code applies discount', async ({ page }) => {
507
- // known issue still synced to Azure, tagged wip
542
+ test.fixme('promo code applies discount', {
543
+ annotation: [
544
+ { type: 'tc', description: '1042' },
545
+ { type: 'issue', description: 'promo-code not yet implemented' },
546
+ ],
547
+ tag: '@wip',
548
+ }, async ({ page }) => {
549
+ // known issue — still synced to Azure
508
550
  });
509
551
  });
510
552
 
511
553
  test.describe.parallel('Cart assertions', () => {
512
554
 
513
- // @tc:1043
514
- test('cart badge shows correct count', async ({ page }) => {
555
+ test('cart badge shows correct count', {
556
+ annotation: { type: 'tc', description: '1043' },
557
+ }, async ({ page }) => {
515
558
  // ...
516
559
  });
517
560
  });
518
561
  ```
519
562
 
520
- ### JavaScript example (CommonJS)
563
+ ### JavaScript example (comment fallback style)
521
564
 
522
565
  ```javascript
523
566
  const { test, expect } = require('@playwright/test');
@@ -531,7 +574,7 @@ test.describe('SauceDemo Login', () => {
531
574
  * 3. Click the login button
532
575
  * 4. Check: URL contains "inventory.html"
533
576
  */
534
- // @tc:1044
577
+ // @tc:1044 ← comment style also works; ado-sync upgrades to annotation on next push
535
578
  test('valid credentials redirect to inventory', async ({ page }) => {
536
579
  // ...
537
580
  });
@@ -557,8 +600,9 @@ test.describe('SauceDemo Login', () => {
557
600
 
558
601
  - **`pull` is not supported** for Playwright files. Only `push` applies.
559
602
  - **`test.describe` nesting** — arbitrarily deep nesting is supported. All enclosing describe titles are included in the `automatedTestName`.
560
- - **`test.fixme` / `test.fail`** — both are parsed and synced as normal test cases. Add `// @tags: wip` above `test.fixme` to mark them in Azure.
603
+ - **`test.fixme` / `test.fail`** — both are parsed and synced as normal test cases. Use `tag: '@wip'` on `test.fixme` to mark them in Azure.
561
604
  - **Publishing results** — set `testResultFormat: playwrightJson` when using `publish-test-results`.
605
+ - **Native annotation priority** — `annotation: { type: 'tc', … }` is read before `tag:` which is read before `// @tc:N` comments. The tool writes back using native annotation on every sync.
562
606
 
563
607
  ---
564
608
 
@@ -664,7 +708,45 @@ describe('SauceDemo Login', () => {
664
708
 
665
709
  Set `local.type: "testcafe"`. Supports **TestCafe** tests using the `fixture` / `test` API.
666
710
 
667
- ```typescript
711
+ ### ID tagging — native `test.meta()` (recommended)
712
+
713
+ TestCafe has a built-in `.meta()` method that attaches key-value metadata to a test. Use it to store the TC ID natively — no comments needed:
714
+
715
+ ```javascript
716
+ // Key-value form (recommended — clean and simple)
717
+ test.meta('tc', '1080')('redirects to inventory on valid login', async t => { ... });
718
+
719
+ // Object form (use when adding multiple metadata fields)
720
+ test.meta({ tc: '1081', priority: 'high' })('locked out user sees error', async t => { ... });
721
+
722
+ // Combined with test.skip
723
+ test.skip.meta('tc', '1082')('skipped test', async t => { ... });
724
+ ```
725
+
726
+ **Writeback** — on the first `push`, ado-sync injects `.meta('tc', 'N')` between the test function and its title call. For example:
727
+ ```
728
+ test('title', fn) → test.meta('tc', '12345')('title', fn)
729
+ ```
730
+ Subsequent pushes update the existing `.meta()` value in place.
731
+
732
+ **Comment fallback** — `// @tc:N` above `test()` is still recognised.
733
+
734
+ ### Source mapping
735
+
736
+ | TestCafe source | Azure TC field |
737
+ |-----------------|---------------|
738
+ | JSDoc `/** ... */` first non-numbered line | TC **Title** |
739
+ | Numbered lines `N. text` in JSDoc | TC **Steps** (action) |
740
+ | Numbered lines `N. Check: text` in JSDoc | TC **Steps** (expected result) |
741
+ | `test.meta('tc', 'N')` | TC ID (**preferred**) |
742
+ | `test.meta({ tc: 'N' })` | TC ID (object form) |
743
+ | `// @tags: smoke, regression` above `test()` | TC **Tags** |
744
+ | `// @tc:N` above `test()` | TC ID (comment fallback) |
745
+ | `{basename} > {fixture} > {test title}` | `AutomatedTestName` |
746
+
747
+ ### Example
748
+
749
+ ```javascript
668
750
  fixture('SauceDemo Login')
669
751
  .page('https://www.saucedemo.com');
670
752
 
@@ -675,16 +757,18 @@ fixture('SauceDemo Login')
675
757
  * 3. Click the login button
676
758
  * 4. Check: URL contains "inventory"
677
759
  */
678
- // @tc:1080
679
- // @tags: smoke
680
- test('redirects to inventory on valid login', async t => {
760
+ // @smoke
761
+ test.meta('tc', '1080')('redirects to inventory on valid login', async t => {
681
762
  // ...
682
763
  });
683
764
 
684
- // @tc:1081
685
- test.skip('locked out user sees error', async t => {
686
- // skipped — still synced to Azure
765
+ test.meta({ tc: '1081', priority: 'high' })('locked out user sees error', async t => {
766
+ // ...
687
767
  });
768
+
769
+ // Comment style still works (written by ado-sync before meta support was added)
770
+ // @tc:1082
771
+ test.skip('skipped test — still synced', async t => { ... });
688
772
  ```
689
773
 
690
774
  ### Recommended config
@@ -693,7 +777,7 @@ test.skip('locked out user sees error', async t => {
693
777
  {
694
778
  "local": {
695
779
  "type": "testcafe",
696
- "include": ["tests/**/*.ts"],
780
+ "include": ["tests/**/*.js", "tests/**/*.ts"],
697
781
  "exclude": ["tests/helpers/**"]
698
782
  },
699
783
  "sync": { "markAutomated": true }
@@ -704,9 +788,8 @@ test.skip('locked out user sees error', async t => {
704
788
 
705
789
  - The `fixture()` title is used as the group / suite name and included in `automatedTestName`.
706
790
  - `test.skip()` and `test.only()` are both parsed and synced.
707
- - ID writeback format: `// @tc:12345` immediately above the `test()` line.
708
791
  - `pull` is not supported for TestCafe files — only `push` applies.
709
- - JSDoc `/** ... */` above a `test()` call is parsed for TC title and numbered steps, same as Playwright and Jest.
792
+ - JSDoc `/** ... */` above a `test.meta(...)` call is parsed for TC title and numbered steps.
710
793
 
711
794
  ---
712
795
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ado-sync",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
4
4
  "description": "Bidirectional sync between local test specs (Cucumber/Markdown) and Azure DevOps Test Cases",
5
5
  "bin": {
6
6
  "ado-sync": "./dist/cli.js"