ado-sync 0.1.2
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 +527 -0
- package/dist/azure/client.d.ts +17 -0
- package/dist/azure/client.js +88 -0
- package/dist/azure/client.js.map +1 -0
- package/dist/azure/test-cases.d.ts +22 -0
- package/dist/azure/test-cases.js +247 -0
- package/dist/azure/test-cases.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +194 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +129 -0
- package/dist/config.js.map +1 -0
- package/dist/parsers/gherkin.d.ts +30 -0
- package/dist/parsers/gherkin.js +177 -0
- package/dist/parsers/gherkin.js.map +1 -0
- package/dist/parsers/markdown.d.ts +31 -0
- package/dist/parsers/markdown.js +185 -0
- package/dist/parsers/markdown.js.map +1 -0
- package/dist/sync/engine.d.ts +13 -0
- package/dist/sync/engine.js +296 -0
- package/dist/sync/engine.js.map +1 -0
- package/dist/sync/writeback.d.ts +36 -0
- package/dist/sync/writeback.js +134 -0
- package/dist/sync/writeback.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gherkin / Cucumber .feature file parser.
|
|
4
|
+
*
|
|
5
|
+
* Uses the modern @cucumber/gherkin generateMessages() API (same approach as
|
|
6
|
+
* playwright-bdd). This produces both the GherkinDocument (AST) and Pickles
|
|
7
|
+
* (compiled, example-substituted scenarios) in a single pass.
|
|
8
|
+
*
|
|
9
|
+
* Pickles are the canonical unit of work — they already have:
|
|
10
|
+
* - Example values substituted in titles and step text
|
|
11
|
+
* - Background steps merged into every scenario
|
|
12
|
+
* - Tags inherited from Feature + Scenario + Examples blocks
|
|
13
|
+
* - Step type (Context/Action/Outcome) instead of keyword
|
|
14
|
+
*
|
|
15
|
+
* ID tag convention: @tc:12345 (prefix configurable via sync.tagPrefix)
|
|
16
|
+
*
|
|
17
|
+
* Path-based auto-tagging:
|
|
18
|
+
* Directory segments prefixed with @ are added as tags automatically.
|
|
19
|
+
* e.g. specs/@smoke/@regression/login.feature → tags: ['smoke', 'regression']
|
|
20
|
+
*/
|
|
21
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
24
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
25
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
26
|
+
}
|
|
27
|
+
Object.defineProperty(o, k2, desc);
|
|
28
|
+
}) : (function(o, m, k, k2) {
|
|
29
|
+
if (k2 === undefined) k2 = k;
|
|
30
|
+
o[k2] = m[k];
|
|
31
|
+
}));
|
|
32
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
33
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
34
|
+
}) : function(o, v) {
|
|
35
|
+
o["default"] = v;
|
|
36
|
+
});
|
|
37
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
38
|
+
var ownKeys = function(o) {
|
|
39
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
40
|
+
var ar = [];
|
|
41
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
42
|
+
return ar;
|
|
43
|
+
};
|
|
44
|
+
return ownKeys(o);
|
|
45
|
+
};
|
|
46
|
+
return function (mod) {
|
|
47
|
+
if (mod && mod.__esModule) return mod;
|
|
48
|
+
var result = {};
|
|
49
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
50
|
+
__setModuleDefault(result, mod);
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
})();
|
|
54
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
+
exports.extractAzureId = extractAzureId;
|
|
56
|
+
exports.extractPathTags = extractPathTags;
|
|
57
|
+
exports.parseGherkinFile = parseGherkinFile;
|
|
58
|
+
const gherkin_1 = require("@cucumber/gherkin");
|
|
59
|
+
const messages_1 = require("@cucumber/messages");
|
|
60
|
+
const fs = __importStar(require("fs"));
|
|
61
|
+
const path = __importStar(require("path"));
|
|
62
|
+
// ─── Step type → keyword mapping ─────────────────────────────────────────────
|
|
63
|
+
const STEP_TYPE_KEYWORD = {
|
|
64
|
+
Context: 'Given',
|
|
65
|
+
Action: 'When',
|
|
66
|
+
Outcome: 'Then',
|
|
67
|
+
Unknown: 'Step',
|
|
68
|
+
};
|
|
69
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
70
|
+
/** Strip leading @ from a Gherkin tag name. */
|
|
71
|
+
function stripAt(name) {
|
|
72
|
+
return name.startsWith('@') ? name.slice(1) : name;
|
|
73
|
+
}
|
|
74
|
+
/** Extract Azure Test Case ID from a list of tag names. e.g. ['tc:123', 'smoke'] → 123 */
|
|
75
|
+
function extractAzureId(tags, tagPrefix) {
|
|
76
|
+
const prefix = tagPrefix + ':';
|
|
77
|
+
for (const tag of tags) {
|
|
78
|
+
if (tag.startsWith(prefix)) {
|
|
79
|
+
const n = parseInt(tag.slice(prefix.length), 10);
|
|
80
|
+
if (!isNaN(n))
|
|
81
|
+
return n;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Extract auto-tags from directory segments that start with '@'.
|
|
88
|
+
*
|
|
89
|
+
* Given /project/specs/@smoke/@regression/login.feature
|
|
90
|
+
* returns ['smoke', 'regression']
|
|
91
|
+
*/
|
|
92
|
+
function extractPathTags(filePath) {
|
|
93
|
+
const segments = filePath.split(path.sep);
|
|
94
|
+
const tags = [];
|
|
95
|
+
// Walk directory segments (not the filename itself)
|
|
96
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
97
|
+
const seg = segments[i];
|
|
98
|
+
// A segment may contain multiple @tags separated by spaces or be just one tag
|
|
99
|
+
const matches = seg.match(/@[^\s@/\\]+/g);
|
|
100
|
+
if (matches) {
|
|
101
|
+
tags.push(...matches.map(stripAt));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return tags;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Given a GherkinDocument and a pickle, find the source line of the
|
|
108
|
+
* scenario that produced this pickle (using astNodeIds).
|
|
109
|
+
*/
|
|
110
|
+
function findScenarioLine(doc, pickle) {
|
|
111
|
+
const scenarioId = pickle.astNodeIds[0];
|
|
112
|
+
for (const child of doc.feature?.children ?? []) {
|
|
113
|
+
if (child.scenario?.id === scenarioId) {
|
|
114
|
+
return child.scenario.location?.line ?? 1;
|
|
115
|
+
}
|
|
116
|
+
if (child.rule) {
|
|
117
|
+
for (const ruleChild of child.rule.children ?? []) {
|
|
118
|
+
if (ruleChild.scenario?.id === scenarioId) {
|
|
119
|
+
return ruleChild.scenario.location?.line ?? 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
function pickleStepToParsedStep(step) {
|
|
127
|
+
return {
|
|
128
|
+
keyword: STEP_TYPE_KEYWORD[step.type ?? 'Unknown'] ?? 'Step',
|
|
129
|
+
text: step.text.trim(),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// ─── Public parser ────────────────────────────────────────────────────────────
|
|
133
|
+
function parseGherkinFile(filePath, tagPrefix) {
|
|
134
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
135
|
+
const newId = messages_1.IdGenerator.uuid();
|
|
136
|
+
let messages;
|
|
137
|
+
try {
|
|
138
|
+
messages = (0, gherkin_1.generateMessages)(source, filePath, messages_1.SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN, {
|
|
139
|
+
newId,
|
|
140
|
+
includeGherkinDocument: true,
|
|
141
|
+
includePickles: true,
|
|
142
|
+
includeSource: false,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
throw new Error(`Failed to parse ${filePath}: ${err.message}`);
|
|
147
|
+
}
|
|
148
|
+
// Surface any parse errors from the envelope stream
|
|
149
|
+
const parseErrors = messages.filter((m) => m.parseError);
|
|
150
|
+
if (parseErrors.length > 0) {
|
|
151
|
+
const msg = parseErrors.map((m) => m.parseError?.message).join('; ');
|
|
152
|
+
throw new Error(`Gherkin parse error in ${filePath}: ${msg}`);
|
|
153
|
+
}
|
|
154
|
+
const docEnvelope = messages.find((m) => m.gherkinDocument);
|
|
155
|
+
if (!docEnvelope?.gherkinDocument?.feature)
|
|
156
|
+
return [];
|
|
157
|
+
const doc = docEnvelope.gherkinDocument;
|
|
158
|
+
const pickles = messages
|
|
159
|
+
.filter((m) => m.pickle)
|
|
160
|
+
.map((m) => m.pickle);
|
|
161
|
+
// Tags from directory path segments (e.g. specs/@smoke/ → 'smoke')
|
|
162
|
+
const pathTags = extractPathTags(filePath);
|
|
163
|
+
return pickles.map((pickle) => {
|
|
164
|
+
// Pickle tags already include Feature + Scenario + Examples tags (inherited)
|
|
165
|
+
const pickleTags = pickle.tags.map((t) => stripAt(t.name));
|
|
166
|
+
const allTags = [...new Set([...pathTags, ...pickleTags])];
|
|
167
|
+
return {
|
|
168
|
+
filePath,
|
|
169
|
+
title: pickle.name.trim(),
|
|
170
|
+
steps: pickle.steps.map(pickleStepToParsedStep),
|
|
171
|
+
tags: allTags,
|
|
172
|
+
azureId: extractAzureId(allTags, tagPrefix),
|
|
173
|
+
line: findScenarioLine(doc, pickle),
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=gherkin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gherkin.js","sourceRoot":"","sources":["../../src/parsers/gherkin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown test spec parser.
|
|
3
|
+
*
|
|
4
|
+
* Expected file structure:
|
|
5
|
+
*
|
|
6
|
+
* # Plan title
|
|
7
|
+
*
|
|
8
|
+
* ## Test scenarios ← optional section heading (ignored)
|
|
9
|
+
*
|
|
10
|
+
* ### 1. Scenario title ← H3 heading = one test case
|
|
11
|
+
*
|
|
12
|
+
* Assumption: ... ← optional prose, used as description
|
|
13
|
+
*
|
|
14
|
+
* Steps:
|
|
15
|
+
* 1. Do this
|
|
16
|
+
* 2. Do that
|
|
17
|
+
*
|
|
18
|
+
* Expected results:
|
|
19
|
+
* - Result A
|
|
20
|
+
* - Result B
|
|
21
|
+
*
|
|
22
|
+
* <!-- azure-tc: 12345 --> ← written back after first push
|
|
23
|
+
*
|
|
24
|
+
* --- ← separator between scenarios
|
|
25
|
+
*
|
|
26
|
+
* ID tag convention: <!-- {tagPrefix}: 12345 --> anywhere within the scenario block
|
|
27
|
+
* e.g. with default tagPrefix "tc": <!-- tc: 12345 -->
|
|
28
|
+
* The prefix is set via sync.tagPrefix in the config file.
|
|
29
|
+
*/
|
|
30
|
+
import { ParsedTest } from '../types';
|
|
31
|
+
export declare function parseMarkdownFile(filePath: string, tagPrefix: string): ParsedTest[];
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Markdown test spec parser.
|
|
4
|
+
*
|
|
5
|
+
* Expected file structure:
|
|
6
|
+
*
|
|
7
|
+
* # Plan title
|
|
8
|
+
*
|
|
9
|
+
* ## Test scenarios ← optional section heading (ignored)
|
|
10
|
+
*
|
|
11
|
+
* ### 1. Scenario title ← H3 heading = one test case
|
|
12
|
+
*
|
|
13
|
+
* Assumption: ... ← optional prose, used as description
|
|
14
|
+
*
|
|
15
|
+
* Steps:
|
|
16
|
+
* 1. Do this
|
|
17
|
+
* 2. Do that
|
|
18
|
+
*
|
|
19
|
+
* Expected results:
|
|
20
|
+
* - Result A
|
|
21
|
+
* - Result B
|
|
22
|
+
*
|
|
23
|
+
* <!-- azure-tc: 12345 --> ← written back after first push
|
|
24
|
+
*
|
|
25
|
+
* --- ← separator between scenarios
|
|
26
|
+
*
|
|
27
|
+
* ID tag convention: <!-- {tagPrefix}: 12345 --> anywhere within the scenario block
|
|
28
|
+
* e.g. with default tagPrefix "tc": <!-- tc: 12345 -->
|
|
29
|
+
* The prefix is set via sync.tagPrefix in the config file.
|
|
30
|
+
*/
|
|
31
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
32
|
+
if (k2 === undefined) k2 = k;
|
|
33
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
34
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
35
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
36
|
+
}
|
|
37
|
+
Object.defineProperty(o, k2, desc);
|
|
38
|
+
}) : (function(o, m, k, k2) {
|
|
39
|
+
if (k2 === undefined) k2 = k;
|
|
40
|
+
o[k2] = m[k];
|
|
41
|
+
}));
|
|
42
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
43
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
44
|
+
}) : function(o, v) {
|
|
45
|
+
o["default"] = v;
|
|
46
|
+
});
|
|
47
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
48
|
+
var ownKeys = function(o) {
|
|
49
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
50
|
+
var ar = [];
|
|
51
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
52
|
+
return ar;
|
|
53
|
+
};
|
|
54
|
+
return ownKeys(o);
|
|
55
|
+
};
|
|
56
|
+
return function (mod) {
|
|
57
|
+
if (mod && mod.__esModule) return mod;
|
|
58
|
+
var result = {};
|
|
59
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
60
|
+
__setModuleDefault(result, mod);
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
})();
|
|
64
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
65
|
+
exports.parseMarkdownFile = parseMarkdownFile;
|
|
66
|
+
const fs = __importStar(require("fs"));
|
|
67
|
+
// ─── Regexes ─────────────────────────────────────────────────────────────────
|
|
68
|
+
const H3_RE = /^###\s+(?:\d+\.\s+)?(.+)$/; // ### N. Title or ### Title
|
|
69
|
+
const STEPS_HEADING_RE = /^steps\s*:/i;
|
|
70
|
+
const EXPECTED_HEADING_RE = /^expected\s+results?\s*:/i;
|
|
71
|
+
const NUMBERED_STEP_RE = /^\s*\d+\.\s+(.+)$/;
|
|
72
|
+
const BULLET_STEP_RE = /^\s*[-*]\s+(.+)$/;
|
|
73
|
+
const SEPARATOR_RE = /^---+\s*$/;
|
|
74
|
+
const mdTcCommentRe = (prefix) => new RegExp(`<!--\\s*${prefix}\\s*:\\s*(\\d+)\\s*-->`, 'i');
|
|
75
|
+
function splitIntoScenarios(lines) {
|
|
76
|
+
const blocks = [];
|
|
77
|
+
let current = null;
|
|
78
|
+
for (let i = 0; i < lines.length; i++) {
|
|
79
|
+
const line = lines[i];
|
|
80
|
+
const h3Match = line.match(H3_RE);
|
|
81
|
+
if (h3Match) {
|
|
82
|
+
if (current)
|
|
83
|
+
blocks.push(current);
|
|
84
|
+
current = { title: h3Match[1].trim(), startLine: i + 1, lines: [] };
|
|
85
|
+
}
|
|
86
|
+
else if (current) {
|
|
87
|
+
if (SEPARATOR_RE.test(line)) {
|
|
88
|
+
blocks.push(current);
|
|
89
|
+
current = null;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
current.lines.push(line);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (current)
|
|
97
|
+
blocks.push(current);
|
|
98
|
+
return blocks;
|
|
99
|
+
}
|
|
100
|
+
function parseScenarioBlock(block, filePath, tagPrefix) {
|
|
101
|
+
const lines = block.lines;
|
|
102
|
+
const tcRe = mdTcCommentRe(tagPrefix);
|
|
103
|
+
// Find ID comment
|
|
104
|
+
let azureId;
|
|
105
|
+
const tcComment = lines.join('\n').match(tcRe);
|
|
106
|
+
if (tcComment) {
|
|
107
|
+
azureId = parseInt(tcComment[1], 10);
|
|
108
|
+
}
|
|
109
|
+
// Extract sections
|
|
110
|
+
let section = 'description';
|
|
111
|
+
const descLines = [];
|
|
112
|
+
const stepLines = [];
|
|
113
|
+
const expectedLines = [];
|
|
114
|
+
for (const line of lines) {
|
|
115
|
+
if (tcRe.test(line))
|
|
116
|
+
continue; // skip ID comment lines
|
|
117
|
+
if (STEPS_HEADING_RE.test(line.trim())) {
|
|
118
|
+
section = 'steps';
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (EXPECTED_HEADING_RE.test(line.trim())) {
|
|
122
|
+
section = 'expected';
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// A new unrecognised heading resets to 'other'
|
|
126
|
+
if (/^#{1,6}\s/.test(line)) {
|
|
127
|
+
section = 'other';
|
|
128
|
+
}
|
|
129
|
+
switch (section) {
|
|
130
|
+
case 'description':
|
|
131
|
+
descLines.push(line);
|
|
132
|
+
break;
|
|
133
|
+
case 'steps':
|
|
134
|
+
if (NUMBERED_STEP_RE.test(line) || BULLET_STEP_RE.test(line))
|
|
135
|
+
stepLines.push(line);
|
|
136
|
+
break;
|
|
137
|
+
case 'expected':
|
|
138
|
+
if (NUMBERED_STEP_RE.test(line) || BULLET_STEP_RE.test(line))
|
|
139
|
+
expectedLines.push(line);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Build steps
|
|
144
|
+
const parsedSteps = stepLines.map((l) => {
|
|
145
|
+
const m = l.match(NUMBERED_STEP_RE) ?? l.match(BULLET_STEP_RE);
|
|
146
|
+
return { keyword: 'Step', text: (m ? m[1] : l).trim() };
|
|
147
|
+
});
|
|
148
|
+
// Attach expected results as the expected value of the last step,
|
|
149
|
+
// or add a dedicated verification step if there are no regular steps.
|
|
150
|
+
const expectedText = expectedLines
|
|
151
|
+
.map((l) => {
|
|
152
|
+
const m = l.match(NUMBERED_STEP_RE) ?? l.match(BULLET_STEP_RE);
|
|
153
|
+
return (m ? m[1] : l).trim();
|
|
154
|
+
})
|
|
155
|
+
.join('\n');
|
|
156
|
+
if (expectedText) {
|
|
157
|
+
if (parsedSteps.length > 0) {
|
|
158
|
+
parsedSteps[parsedSteps.length - 1].expected = expectedText;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
parsedSteps.push({ keyword: 'Verify', text: 'Expected results', expected: expectedText });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Description: trim trailing blank lines
|
|
165
|
+
const description = descLines
|
|
166
|
+
.join('\n')
|
|
167
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
168
|
+
.trim() || undefined;
|
|
169
|
+
return {
|
|
170
|
+
filePath,
|
|
171
|
+
title: block.title,
|
|
172
|
+
description,
|
|
173
|
+
steps: parsedSteps,
|
|
174
|
+
tags: [], // markdown specs don't have Gherkin tags
|
|
175
|
+
azureId,
|
|
176
|
+
line: block.startLine,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function parseMarkdownFile(filePath, tagPrefix) {
|
|
180
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
181
|
+
const lines = source.split('\n');
|
|
182
|
+
const blocks = splitIntoScenarios(lines);
|
|
183
|
+
return blocks.map((b) => parseScenarioBlock(b, filePath, tagPrefix));
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/parsers/markdown.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync engine — orchestrates push, pull, and status operations.
|
|
3
|
+
*/
|
|
4
|
+
import { SyncConfig, SyncResult } from '../types';
|
|
5
|
+
export interface SyncOpts {
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
/** Cucumber tag expression to restrict which scenarios are synced.
|
|
8
|
+
* Examples: "@smoke" "@smoke and not @wip" "not @manual" */
|
|
9
|
+
tags?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function push(config: SyncConfig, configDir: string, opts?: SyncOpts): Promise<SyncResult[]>;
|
|
12
|
+
export declare function pull(config: SyncConfig, configDir: string, opts?: SyncOpts): Promise<SyncResult[]>;
|
|
13
|
+
export declare function status(config: SyncConfig, configDir: string, opts?: Pick<SyncOpts, 'tags'>): Promise<SyncResult[]>;
|