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.
@@ -0,0 +1,296 @@
1
+ "use strict";
2
+ /**
3
+ * Sync engine — orchestrates push, pull, and status operations.
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39
+ return (mod && mod.__esModule) ? mod : { "default": mod };
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.push = push;
43
+ exports.pull = pull;
44
+ exports.status = status;
45
+ const tag_expressions_1 = __importDefault(require("@cucumber/tag-expressions"));
46
+ const fs = __importStar(require("fs"));
47
+ const glob_1 = require("glob");
48
+ const client_1 = require("../azure/client");
49
+ const test_cases_1 = require("../azure/test-cases");
50
+ const gherkin_1 = require("../parsers/gherkin");
51
+ const markdown_1 = require("../parsers/markdown");
52
+ const writeback_1 = require("./writeback");
53
+ // ─── Tag filtering ────────────────────────────────────────────────────────────
54
+ /**
55
+ * Returns true when the test's tags satisfy the given tag expression.
56
+ * Expression syntax mirrors Cucumber: "@smoke and not @wip"
57
+ * Tags in ParsedTest are stored without the leading @.
58
+ * The expression evaluator expects them with @, so we re-add it here.
59
+ */
60
+ function matchesTags(test, expression) {
61
+ const node = (0, tag_expressions_1.default)(expression);
62
+ // Tags in ParsedTest have no leading '@'; tag-expressions evaluator needs them with '@'
63
+ const tagsWithAt = test.tags.map((t) => (t.startsWith('@') ? t : `@${t}`));
64
+ return node.evaluate(tagsWithAt);
65
+ }
66
+ // ─── File discovery ───────────────────────────────────────────────────────────
67
+ async function discoverFiles(config, configDir) {
68
+ const patterns = Array.isArray(config.local.include)
69
+ ? config.local.include
70
+ : [config.local.include];
71
+ const excludes = config.local.exclude
72
+ ? Array.isArray(config.local.exclude)
73
+ ? config.local.exclude
74
+ : [config.local.exclude]
75
+ : [];
76
+ const all = [];
77
+ for (const pattern of patterns) {
78
+ const matches = await (0, glob_1.glob)(pattern, {
79
+ cwd: configDir,
80
+ absolute: true,
81
+ ignore: excludes,
82
+ });
83
+ all.push(...matches);
84
+ }
85
+ return [...new Set(all)].sort();
86
+ }
87
+ // ─── Local file parsing ───────────────────────────────────────────────────────
88
+ function parseLocalFiles(filePaths, config, tagsFilter) {
89
+ const tagPrefix = config.sync?.tagPrefix ?? 'tc';
90
+ const results = [];
91
+ for (const fp of filePaths) {
92
+ try {
93
+ const tests = config.local.type === 'gherkin'
94
+ ? (0, gherkin_1.parseGherkinFile)(fp, tagPrefix)
95
+ : (0, markdown_1.parseMarkdownFile)(fp, tagPrefix);
96
+ for (const t of tests) {
97
+ if (tagsFilter && !matchesTags(t, tagsFilter))
98
+ continue;
99
+ results.push(t);
100
+ }
101
+ }
102
+ catch (err) {
103
+ console.warn(` [warn] Failed to parse ${fp}: ${err.message}`);
104
+ }
105
+ }
106
+ return results;
107
+ }
108
+ // ─── Push ─────────────────────────────────────────────────────────────────────
109
+ async function push(config, configDir, opts = {}) {
110
+ const files = await discoverFiles(config, configDir);
111
+ const tests = parseLocalFiles(files, config, opts.tags);
112
+ const client = await client_1.AzureClient.create(config);
113
+ const tagPrefix = config.sync?.tagPrefix ?? 'tc';
114
+ const results = [];
115
+ for (const test of tests) {
116
+ if (test.azureId) {
117
+ try {
118
+ const remote = await (0, test_cases_1.getTestCase)(client, test.azureId);
119
+ if (!remote) {
120
+ results.push({
121
+ action: 'error',
122
+ filePath: test.filePath,
123
+ title: test.title,
124
+ azureId: test.azureId,
125
+ detail: `Test case #${test.azureId} not found in Azure DevOps`,
126
+ });
127
+ continue;
128
+ }
129
+ const localStepsText = test.steps.map((s) => s.keyword + ' ' + s.text).join('\n');
130
+ const remoteStepsText = remote.steps.map((s) => s.action).join('\n');
131
+ const titleChanged = remote.title !== test.title;
132
+ const stepsChanged = localStepsText !== remoteStepsText;
133
+ if (!titleChanged && !stepsChanged) {
134
+ results.push({ action: 'skipped', filePath: test.filePath, title: test.title, azureId: test.azureId });
135
+ continue;
136
+ }
137
+ if (!opts.dryRun) {
138
+ await (0, test_cases_1.updateTestCase)(client, test.azureId, test, config);
139
+ }
140
+ results.push({ action: 'updated', filePath: test.filePath, title: test.title, azureId: test.azureId });
141
+ }
142
+ catch (err) {
143
+ results.push({ action: 'error', filePath: test.filePath, title: test.title, azureId: test.azureId, detail: err.message });
144
+ }
145
+ }
146
+ else {
147
+ try {
148
+ let newId;
149
+ if (!opts.dryRun) {
150
+ newId = await (0, test_cases_1.createTestCase)(client, test, config);
151
+ (0, writeback_1.writebackId)(test, newId, config.local.type, tagPrefix);
152
+ }
153
+ results.push({ action: 'created', filePath: test.filePath, title: test.title, azureId: newId });
154
+ }
155
+ catch (err) {
156
+ results.push({ action: 'error', filePath: test.filePath, title: test.title, detail: err.message });
157
+ }
158
+ }
159
+ }
160
+ return results;
161
+ }
162
+ // ─── Pull ─────────────────────────────────────────────────────────────────────
163
+ async function pull(config, configDir, opts = {}) {
164
+ const files = await discoverFiles(config, configDir);
165
+ const tests = parseLocalFiles(files, config, opts.tags);
166
+ const client = await client_1.AzureClient.create(config);
167
+ const results = [];
168
+ const linked = tests.filter((t) => t.azureId !== undefined);
169
+ for (const test of linked) {
170
+ try {
171
+ const remote = await (0, test_cases_1.getTestCase)(client, test.azureId);
172
+ if (!remote) {
173
+ results.push({
174
+ action: 'error',
175
+ filePath: test.filePath,
176
+ title: test.title,
177
+ azureId: test.azureId,
178
+ detail: `Test case #${test.azureId} not found in Azure DevOps`,
179
+ });
180
+ continue;
181
+ }
182
+ const titleChanged = remote.title !== test.title;
183
+ const remoteStepsText = remote.steps.map((s) => s.action + '|' + s.expected).join('\n');
184
+ const localStepsText = test.steps.map((s) => s.keyword + ' ' + s.text + '|' + (s.expected ?? '')).join('\n');
185
+ const stepsChanged = remoteStepsText !== localStepsText;
186
+ if (!titleChanged && !stepsChanged) {
187
+ results.push({ action: 'skipped', filePath: test.filePath, title: test.title, azureId: test.azureId });
188
+ continue;
189
+ }
190
+ if (!opts.dryRun) {
191
+ applyRemoteToLocal(test, remote.title, remote.steps.map((s) => ({ keyword: 'Step', text: s.action, expected: s.expected })), config.local.type);
192
+ }
193
+ results.push({
194
+ action: 'pulled',
195
+ filePath: test.filePath,
196
+ title: remote.title,
197
+ azureId: test.azureId,
198
+ detail: [titleChanged && 'title', stepsChanged && 'steps'].filter(Boolean).join(', ') + ' changed',
199
+ });
200
+ }
201
+ catch (err) {
202
+ results.push({ action: 'error', filePath: test.filePath, title: test.title, azureId: test.azureId, detail: err.message });
203
+ }
204
+ }
205
+ return results;
206
+ }
207
+ // ─── Status ───────────────────────────────────────────────────────────────────
208
+ async function status(config, configDir, opts = {}) {
209
+ return push(config, configDir, { dryRun: true, tags: opts.tags });
210
+ }
211
+ // ─── Apply remote changes to local file ───────────────────────────────────────
212
+ function applyRemoteToLocal(test, newTitle, newSteps, localType) {
213
+ if (localType === 'gherkin') {
214
+ applyRemoteToGherkin(test, newTitle, newSteps);
215
+ }
216
+ else {
217
+ applyRemoteToMarkdown(test, newTitle, newSteps);
218
+ }
219
+ }
220
+ function applyRemoteToGherkin(test, newTitle, newSteps) {
221
+ const raw = fs.readFileSync(test.filePath, 'utf8');
222
+ const lines = raw.split('\n');
223
+ const scenarioLineIdx = test.line - 1;
224
+ lines[scenarioLineIdx] = lines[scenarioLineIdx].replace(/^(\s*Scenario(?:\s+Outline)?:\s*)(.*)$/, `$1${newTitle}`);
225
+ const stepStart = scenarioLineIdx + 1;
226
+ let stepEnd = stepStart;
227
+ while (stepEnd < lines.length) {
228
+ const l = lines[stepEnd].trim();
229
+ if (!l || /^(Scenario|Feature|Background|Examples|@)/.test(l) || /^---/.test(l))
230
+ break;
231
+ stepEnd++;
232
+ }
233
+ const stepLines = newSteps.map((s) => ` ${s.keyword} ${s.text}`);
234
+ lines.splice(stepStart, stepEnd - stepStart, ...stepLines);
235
+ fs.writeFileSync(test.filePath, lines.join('\n'), 'utf8');
236
+ }
237
+ function applyRemoteToMarkdown(test, newTitle, newSteps) {
238
+ const raw = fs.readFileSync(test.filePath, 'utf8');
239
+ const lines = raw.split('\n');
240
+ const headingLineIdx = test.line - 1;
241
+ lines[headingLineIdx] = lines[headingLineIdx].replace(/^(###\s+(?:\d+\.\s+)?)(.*)$/, `$1${newTitle}`);
242
+ const STEPS_RE = /^steps\s*:/i;
243
+ const EXPECTED_RE = /^expected\s+results?\s*:/i;
244
+ const SEPARATOR_RE = /^---+\s*$/;
245
+ const HEADING_RE = /^#{1,6}\s/;
246
+ let stepsStart = -1;
247
+ let stepsEnd = -1;
248
+ let expectedStart = -1;
249
+ for (let i = headingLineIdx + 1; i < lines.length; i++) {
250
+ const trimmed = lines[i].trim();
251
+ if (HEADING_RE.test(lines[i]) || SEPARATOR_RE.test(trimmed)) {
252
+ if (stepsStart !== -1 && stepsEnd === -1)
253
+ stepsEnd = i;
254
+ break;
255
+ }
256
+ if (STEPS_RE.test(trimmed)) {
257
+ stepsStart = i;
258
+ continue;
259
+ }
260
+ if (EXPECTED_RE.test(trimmed)) {
261
+ if (stepsEnd === -1 && stepsStart !== -1)
262
+ stepsEnd = i;
263
+ expectedStart = i;
264
+ continue;
265
+ }
266
+ }
267
+ const newStepLines = newSteps.map((s, idx) => `${idx + 1}. ${s.text}`);
268
+ if (stepsStart !== -1 && stepsEnd !== -1) {
269
+ lines.splice(stepsStart, stepsEnd - stepsStart, 'Steps:', ...newStepLines);
270
+ }
271
+ // Recalculate after splice
272
+ const updatedLines = lines.join('\n').split('\n');
273
+ let newExpStart = -1;
274
+ let newExpEnd = -1;
275
+ if (expectedStart !== -1) {
276
+ for (let i = headingLineIdx + 1; i < updatedLines.length; i++) {
277
+ const trimmed = updatedLines[i].trim();
278
+ if (HEADING_RE.test(updatedLines[i]) || SEPARATOR_RE.test(trimmed)) {
279
+ if (newExpStart !== -1 && newExpEnd === -1)
280
+ newExpEnd = i;
281
+ break;
282
+ }
283
+ if (EXPECTED_RE.test(trimmed)) {
284
+ newExpStart = i;
285
+ continue;
286
+ }
287
+ }
288
+ const lastExpected = [...newSteps].reverse().find((s) => s.expected)?.expected;
289
+ if (newExpStart !== -1 && newExpEnd !== -1 && lastExpected) {
290
+ const expLines = lastExpected.split('\n').map((l) => `- ${l.trim()}`).filter((l) => l.length > 2);
291
+ updatedLines.splice(newExpStart, newExpEnd - newExpStart, 'Expected results:', ...expLines);
292
+ }
293
+ }
294
+ fs.writeFileSync(test.filePath, updatedLines.join('\n'), 'utf8');
295
+ }
296
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/sync/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * ID writeback — writes the Azure DevOps Test Case ID back to the local file
3
+ * after a test case is created, so subsequent syncs can match them.
4
+ *
5
+ * Gherkin strategy:
6
+ * Inserts (or replaces) a @{tagPrefix}:12345 tag line directly above the Scenario line.
7
+ * Default with tagPrefix "tc": @tc:12345
8
+ *
9
+ * Markdown strategy:
10
+ * Inserts (or replaces) a <!-- {tagPrefix}: 12345 --> comment on the line
11
+ * immediately following the ### heading.
12
+ * Default with tagPrefix "tc": <!-- tc: 12345 -->
13
+ */
14
+ import { ParsedTest } from '../types';
15
+ /**
16
+ * Write (or update) the @tc:ID tag in a .feature file for a given scenario.
17
+ *
18
+ * Strategy:
19
+ * 1. Find the scenario's line (1-based).
20
+ * 2. Look at lines above it for an existing @tc tag line.
21
+ * 3. If found, replace the tag in place.
22
+ * 4. If not found, insert a new tag line directly above the Scenario keyword.
23
+ */
24
+ export declare function writebackGherkin(test: ParsedTest, id: number, tagPrefix: string): void;
25
+ /**
26
+ * Write (or update) the <!-- {tagPrefix}: ID --> comment in a .md file
27
+ * for a given scenario heading.
28
+ *
29
+ * Strategy:
30
+ * 1. Find the ### heading line (1-based).
31
+ * 2. Scan the next ~15 lines for an existing comment.
32
+ * 3. If found, replace it.
33
+ * 4. If not found, insert immediately after the heading line.
34
+ */
35
+ export declare function writebackMarkdown(test: ParsedTest, id: number, tagPrefix: string): void;
36
+ export declare function writebackId(test: ParsedTest, id: number, localType: 'gherkin' | 'markdown', tagPrefix: string): void;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ /**
3
+ * ID writeback — writes the Azure DevOps Test Case ID back to the local file
4
+ * after a test case is created, so subsequent syncs can match them.
5
+ *
6
+ * Gherkin strategy:
7
+ * Inserts (or replaces) a @{tagPrefix}:12345 tag line directly above the Scenario line.
8
+ * Default with tagPrefix "tc": @tc:12345
9
+ *
10
+ * Markdown strategy:
11
+ * Inserts (or replaces) a <!-- {tagPrefix}: 12345 --> comment on the line
12
+ * immediately following the ### heading.
13
+ * Default with tagPrefix "tc": <!-- tc: 12345 -->
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.writebackGherkin = writebackGherkin;
50
+ exports.writebackMarkdown = writebackMarkdown;
51
+ exports.writebackId = writebackId;
52
+ const fs = __importStar(require("fs"));
53
+ // ─── Gherkin writeback ────────────────────────────────────────────────────────
54
+ /**
55
+ * Write (or update) the @tc:ID tag in a .feature file for a given scenario.
56
+ *
57
+ * Strategy:
58
+ * 1. Find the scenario's line (1-based).
59
+ * 2. Look at lines above it for an existing @tc tag line.
60
+ * 3. If found, replace the tag in place.
61
+ * 4. If not found, insert a new tag line directly above the Scenario keyword.
62
+ */
63
+ function writebackGherkin(test, id, tagPrefix) {
64
+ const raw = fs.readFileSync(test.filePath, 'utf8');
65
+ const lines = raw.split('\n');
66
+ const scenarioLineIdx = test.line - 1; // convert to 0-based
67
+ const tagToken = `@${tagPrefix}:${id}`;
68
+ const tagLineRe = new RegExp(`@${tagPrefix}:\\d+`);
69
+ // Walk upward from the scenario line to find an existing tag line
70
+ let replacedInline = false;
71
+ for (let i = scenarioLineIdx - 1; i >= 0 && i >= scenarioLineIdx - 5; i--) {
72
+ if (tagLineRe.test(lines[i])) {
73
+ // Replace the existing tc tag on this line, preserve other tags
74
+ lines[i] = lines[i].replace(tagLineRe, tagToken);
75
+ replacedInline = true;
76
+ break;
77
+ }
78
+ }
79
+ if (!replacedInline) {
80
+ // Insert a new tag line above the Scenario line
81
+ // Detect indentation of the scenario line
82
+ const scenarioLine = lines[scenarioLineIdx] ?? '';
83
+ const indentMatch = scenarioLine.match(/^(\s*)/);
84
+ const indent = indentMatch ? indentMatch[1] : '';
85
+ lines.splice(scenarioLineIdx, 0, `${indent}${tagToken}`);
86
+ }
87
+ fs.writeFileSync(test.filePath, lines.join('\n'), 'utf8');
88
+ }
89
+ // ─── Markdown writeback ───────────────────────────────────────────────────────
90
+ /**
91
+ * Write (or update) the <!-- {tagPrefix}: ID --> comment in a .md file
92
+ * for a given scenario heading.
93
+ *
94
+ * Strategy:
95
+ * 1. Find the ### heading line (1-based).
96
+ * 2. Scan the next ~15 lines for an existing comment.
97
+ * 3. If found, replace it.
98
+ * 4. If not found, insert immediately after the heading line.
99
+ */
100
+ function writebackMarkdown(test, id, tagPrefix) {
101
+ const raw = fs.readFileSync(test.filePath, 'utf8');
102
+ const lines = raw.split('\n');
103
+ const headingLineIdx = test.line - 1; // 0-based
104
+ const comment = `<!-- ${tagPrefix}: ${id} -->`;
105
+ const existingRe = new RegExp(`<!--\\s*${tagPrefix}\\s*:\\s*\\d+\\s*-->`, 'i');
106
+ // Scan forward for an existing comment (up to 15 lines after heading)
107
+ const scanEnd = Math.min(headingLineIdx + 15, lines.length);
108
+ let found = false;
109
+ for (let i = headingLineIdx + 1; i < scanEnd; i++) {
110
+ if (existingRe.test(lines[i])) {
111
+ lines[i] = comment;
112
+ found = true;
113
+ break;
114
+ }
115
+ // Stop at the next heading or separator
116
+ if (/^#{1,6}\s/.test(lines[i]) || /^---+\s*$/.test(lines[i]))
117
+ break;
118
+ }
119
+ if (!found) {
120
+ // Insert on the line immediately after the heading
121
+ lines.splice(headingLineIdx + 1, 0, comment);
122
+ }
123
+ fs.writeFileSync(test.filePath, lines.join('\n'), 'utf8');
124
+ }
125
+ // ─── Dispatcher ──────────────────────────────────────────────────────────────
126
+ function writebackId(test, id, localType, tagPrefix) {
127
+ if (localType === 'gherkin') {
128
+ writebackGherkin(test, id, tagPrefix);
129
+ }
130
+ else {
131
+ writebackMarkdown(test, id, tagPrefix);
132
+ }
133
+ }
134
+ //# sourceMappingURL=writeback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeback.js","sourceRoot":"","sources":["../../src/sync/writeback.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Shared types for azure-test-sync
3
+ */
4
+ export type AuthType = 'pat' | 'accessToken' | 'managedIdentity';
5
+ export interface SyncConfig {
6
+ /** Azure DevOps organisation URL, e.g. https://dev.azure.com/myorg */
7
+ orgUrl: string;
8
+ /** Azure DevOps project name */
9
+ project: string;
10
+ /** Authentication */
11
+ auth: {
12
+ type: AuthType;
13
+ /** Personal Access Token (or env var name prefixed with $) */
14
+ token?: string;
15
+ /** Required when type === 'managedIdentity' */
16
+ applicationIdURI?: string;
17
+ };
18
+ /** Target test plan / suite */
19
+ testPlan: {
20
+ id: number;
21
+ /** Root suite to create new test cases under. Defaults to plan root suite. */
22
+ suiteId?: number;
23
+ };
24
+ /** Local spec sources */
25
+ local: {
26
+ /** 'gherkin' for .feature files, 'markdown' for .md spec files */
27
+ type: 'gherkin' | 'markdown';
28
+ /** Glob pattern(s) relative to config file location */
29
+ include: string | string[];
30
+ /** Glob pattern(s) to exclude */
31
+ exclude?: string | string[];
32
+ };
33
+ /** Sync behaviour */
34
+ sync?: {
35
+ /** Prefix used in tags/comments to store the Azure test case ID. Default: 'tc' */
36
+ tagPrefix?: string;
37
+ /** Field name in Azure used as test title. Default: 'System.Title' */
38
+ titleField?: string;
39
+ /** Default area path for new test cases */
40
+ areaPath?: string;
41
+ /** Default iteration path for new test cases */
42
+ iterationPath?: string;
43
+ };
44
+ }
45
+ export interface ParsedStep {
46
+ keyword: string;
47
+ text: string;
48
+ expected?: string;
49
+ }
50
+ export interface ParsedTest {
51
+ /** Absolute path to the local file */
52
+ filePath: string;
53
+ /** Title of the test (Scenario title / Markdown heading) */
54
+ title: string;
55
+ /** Optional description / background text */
56
+ description?: string;
57
+ /** Steps */
58
+ steps: ParsedStep[];
59
+ /** Tags collected from the file (Gherkin tags or markdown metadata) */
60
+ tags: string[];
61
+ /** Azure DevOps Test Case ID if already synced, otherwise undefined */
62
+ azureId?: number;
63
+ /** Line number in the file where the scenario / heading starts (1-based) */
64
+ line: number;
65
+ }
66
+ export interface AzureTestCase {
67
+ id: number;
68
+ title: string;
69
+ description?: string;
70
+ steps: AzureStep[];
71
+ tags: string[];
72
+ /** ISO timestamp of last change in Azure */
73
+ changedDate?: string;
74
+ areaPath?: string;
75
+ iterationPath?: string;
76
+ }
77
+ export interface AzureStep {
78
+ action: string;
79
+ expected: string;
80
+ }
81
+ export type SyncAction = 'created' | 'updated' | 'skipped' | 'conflict' | 'pulled' | 'error';
82
+ export interface SyncResult {
83
+ action: SyncAction;
84
+ filePath: string;
85
+ title: string;
86
+ azureId?: number;
87
+ detail?: string;
88
+ }
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * Shared types for azure-test-sync
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;GAEG"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "ado-sync",
3
+ "version": "0.1.2",
4
+ "description": "Bidirectional sync between local test specs (Cucumber/Markdown) and Azure DevOps Test Cases",
5
+ "bin": {
6
+ "ado-sync": "./dist/cli.js"
7
+ },
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "build:watch": "tsc --watch",
13
+ "clean": "rm -rf dist",
14
+ "dev": "ts-node src/cli.ts",
15
+ "lint": "eslint src --ext .ts --quiet",
16
+ "lint:fix": "eslint src --ext .ts --fix --quiet",
17
+ "format": "prettier --write \"src/**/*.ts\"",
18
+ "prepublishOnly": "npm run clean && npm run build"
19
+ },
20
+ "keywords": [
21
+ "azure-devops",
22
+ "test-sync",
23
+ "cucumber",
24
+ "gherkin",
25
+ "playwright",
26
+ "specsync"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/PavanMudigonda/ado-sync"
31
+ },
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "@azure/identity": "^4.12.0",
35
+ "@cucumber/gherkin": "^29.0.0",
36
+ "@cucumber/messages": "^26.0.0",
37
+ "@cucumber/tag-expressions": "^6.2.0",
38
+ "azure-devops-node-api": "^15.1.1",
39
+ "chalk": "^5.3.0",
40
+ "commander": "^12.1.0",
41
+ "dotenv": "^16.4.5",
42
+ "fast-xml-parser": "^4.4.1",
43
+ "glob": "^11.0.0",
44
+ "js-yaml": "^4.1.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/js-yaml": "^4.0.9",
48
+ "@types/node": "^20.16.5",
49
+ "@typescript-eslint/eslint-plugin": "^8.41.0",
50
+ "@typescript-eslint/parser": "^8.41.0",
51
+ "eslint": "^9.34.0",
52
+ "eslint-plugin-simple-import-sort": "^12.1.1",
53
+ "prettier": "^3.6.2",
54
+ "ts-node": "^10.9.2",
55
+ "typescript": "^5.9.2"
56
+ }
57
+ }