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,247 @@
1
+ "use strict";
2
+ /**
3
+ * CRUD operations for Azure DevOps Test Cases (Work Items of type "Test Case").
4
+ *
5
+ * Azure stores test steps as XML in the field:
6
+ * Microsoft.VSTS.TCM.Steps
7
+ *
8
+ * Step XML shape:
9
+ * <steps id="0" last="N">
10
+ * <step id="2" type="ValidateStep">
11
+ * <parameterizedString isformatted="true">&lt;DIV&gt;Action text&lt;/DIV&gt;</parameterizedString>
12
+ * <parameterizedString isformatted="true">&lt;DIV&gt;Expected text&lt;/DIV&gt;</parameterizedString>
13
+ * <description/>
14
+ * </step>
15
+ * </steps>
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.getTestCase = getTestCase;
19
+ exports.createTestCase = createTestCase;
20
+ exports.updateTestCase = updateTestCase;
21
+ exports.updateLocalFromAzure = updateLocalFromAzure;
22
+ exports.getTestCasesInSuite = getTestCasesInSuite;
23
+ const fast_xml_parser_1 = require("fast-xml-parser");
24
+ // ─── XML helpers ─────────────────────────────────────────────────────────────
25
+ function escapeHtml(str) {
26
+ return str
27
+ .replace(/&/g, '&amp;')
28
+ .replace(/</g, '&lt;')
29
+ .replace(/>/g, '&gt;')
30
+ .replace(/"/g, '&quot;');
31
+ }
32
+ function wrapDiv(text) {
33
+ return `<DIV><P><B>${escapeHtml(text)}</B></P></DIV>`;
34
+ }
35
+ function buildStepsXml(steps) {
36
+ if (steps.length === 0)
37
+ return '<steps id="0" last="1"/>';
38
+ const builder = new fast_xml_parser_1.XMLBuilder({
39
+ ignoreAttributes: false,
40
+ attributeNamePrefix: '@_',
41
+ format: false,
42
+ });
43
+ const stepNodes = steps.map((s, i) => ({
44
+ '@_id': String(i + 2),
45
+ '@_type': 'ValidateStep',
46
+ parameterizedString: [
47
+ { '@_isformatted': 'true', '#text': wrapDiv(s.action) },
48
+ { '@_isformatted': 'true', '#text': wrapDiv(s.expected || '') },
49
+ ],
50
+ description: '',
51
+ }));
52
+ const xml = builder.build({
53
+ steps: {
54
+ '@_id': '0',
55
+ '@_last': String(steps.length + 1),
56
+ step: stepNodes,
57
+ },
58
+ });
59
+ return xml;
60
+ }
61
+ function parseStepsXml(xml) {
62
+ if (!xml)
63
+ return [];
64
+ const parser = new fast_xml_parser_1.XMLParser({
65
+ ignoreAttributes: false,
66
+ attributeNamePrefix: '@_',
67
+ isArray: (name) => name === 'step' || name === 'parameterizedString',
68
+ });
69
+ let parsed;
70
+ try {
71
+ parsed = parser.parse(xml);
72
+ }
73
+ catch {
74
+ return [];
75
+ }
76
+ const rawSteps = parsed?.steps?.step ?? [];
77
+ return rawSteps.map((s) => {
78
+ const strings = s.parameterizedString ?? [];
79
+ const stripTags = (v) => String(v ?? '')
80
+ .replace(/<[^>]+>/g, ' ')
81
+ .replace(/\s+/g, ' ')
82
+ .trim();
83
+ return {
84
+ action: stripTags(strings[0]?.['#text'] ?? strings[0] ?? ''),
85
+ expected: stripTags(strings[1]?.['#text'] ?? strings[1] ?? ''),
86
+ };
87
+ });
88
+ }
89
+ // ─── Tag helpers ─────────────────────────────────────────────────────────────
90
+ function tagsFromString(raw) {
91
+ if (!raw)
92
+ return [];
93
+ return raw
94
+ .split(/[;,]/)
95
+ .map((t) => t.trim())
96
+ .filter(Boolean);
97
+ }
98
+ // ─── API layer ───────────────────────────────────────────────────────────────
99
+ async function getTestCase(client, id) {
100
+ const wit = await client.getWitApi();
101
+ const fields = [
102
+ 'System.Title',
103
+ 'System.Description',
104
+ 'Microsoft.VSTS.TCM.Steps',
105
+ 'System.Tags',
106
+ 'System.ChangedDate',
107
+ 'System.AreaPath',
108
+ 'System.IterationPath',
109
+ ];
110
+ let wi;
111
+ try {
112
+ wi = await wit.getWorkItem(id, fields);
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ if (!wi)
118
+ return null;
119
+ const f = wi.fields ?? {};
120
+ return {
121
+ id,
122
+ title: f['System.Title'] ?? '',
123
+ description: f['System.Description'] ?? '',
124
+ steps: parseStepsXml(f['Microsoft.VSTS.TCM.Steps'] ?? ''),
125
+ tags: tagsFromString(f['System.Tags']),
126
+ changedDate: f['System.ChangedDate'],
127
+ areaPath: f['System.AreaPath'],
128
+ iterationPath: f['System.IterationPath'],
129
+ };
130
+ }
131
+ async function createTestCase(client, test, config) {
132
+ const wit = await client.getWitApi();
133
+ const steps = test.steps.map((s) => ({
134
+ action: `${s.keyword} ${s.text}`.trim(),
135
+ expected: s.expected ?? '',
136
+ }));
137
+ const syncCfg = config.sync ?? {};
138
+ const patchDoc = [
139
+ { op: 'add', path: '/fields/System.Title', value: test.title },
140
+ { op: 'add', path: '/fields/Microsoft.VSTS.TCM.Steps', value: buildStepsXml(steps) },
141
+ ];
142
+ if (test.description) {
143
+ patchDoc.push({ op: 'add', path: '/fields/System.Description', value: test.description });
144
+ }
145
+ const filteredTags = test.tags
146
+ .filter((t) => !t.startsWith(syncCfg.tagPrefix + ':'))
147
+ .join('; ');
148
+ if (filteredTags) {
149
+ patchDoc.push({ op: 'add', path: '/fields/System.Tags', value: filteredTags });
150
+ }
151
+ if (syncCfg.areaPath) {
152
+ patchDoc.push({ op: 'add', path: '/fields/System.AreaPath', value: syncCfg.areaPath });
153
+ }
154
+ if (syncCfg.iterationPath) {
155
+ patchDoc.push({ op: 'add', path: '/fields/System.IterationPath', value: syncCfg.iterationPath });
156
+ }
157
+ const wi = await wit.createWorkItem({}, patchDoc, config.project, 'Test Case');
158
+ if (!wi?.id)
159
+ throw new Error(`Failed to create test case for: ${test.title}`);
160
+ // Add to test suite
161
+ const suiteId = config.testPlan.suiteId;
162
+ if (suiteId) {
163
+ await addTestCaseToSuite(client, config, wi.id, suiteId);
164
+ }
165
+ else {
166
+ await addTestCaseToRootSuite(client, config, wi.id);
167
+ }
168
+ return wi.id;
169
+ }
170
+ async function updateTestCase(client, id, test, config) {
171
+ const wit = await client.getWitApi();
172
+ const steps = test.steps.map((s) => ({
173
+ action: `${s.keyword} ${s.text}`.trim(),
174
+ expected: s.expected ?? '',
175
+ }));
176
+ const syncCfg = config.sync ?? {};
177
+ const filteredTags = test.tags
178
+ .filter((t) => !t.startsWith(syncCfg.tagPrefix + ':'))
179
+ .join('; ');
180
+ const patchDoc = [
181
+ { op: 'replace', path: '/fields/System.Title', value: test.title },
182
+ { op: 'replace', path: '/fields/Microsoft.VSTS.TCM.Steps', value: buildStepsXml(steps) },
183
+ { op: 'replace', path: '/fields/System.Tags', value: filteredTags },
184
+ ];
185
+ if (test.description) {
186
+ patchDoc.push({ op: 'replace', path: '/fields/System.Description', value: test.description });
187
+ }
188
+ await wit.updateWorkItem({}, patchDoc, id);
189
+ }
190
+ async function updateLocalFromAzure(client, id) {
191
+ return getTestCase(client, id);
192
+ }
193
+ async function addTestCaseToSuite(client, config, testCaseId, suiteId) {
194
+ const api = await client.getTestPlanApi();
195
+ await api.addTestCasesToSuite([{ workItem: { id: testCaseId } }], config.project, config.testPlan.id, suiteId);
196
+ }
197
+ async function addTestCaseToRootSuite(client, config, testCaseId) {
198
+ // Fetch the plan to get its root suite id
199
+ const api = await client.getTestPlanApi();
200
+ const plan = await api.getTestPlanById(config.project, config.testPlan.id);
201
+ const rootSuiteId = plan?.rootSuite?.id;
202
+ if (!rootSuiteId)
203
+ return;
204
+ await api.addTestCasesToSuite([{ workItem: { id: testCaseId } }], config.project, config.testPlan.id, rootSuiteId);
205
+ }
206
+ async function getTestCasesInSuite(client, config, suiteId) {
207
+ const api = await client.getTestPlanApi();
208
+ const wit = await client.getWitApi();
209
+ // Resolve suiteId
210
+ let resolvedSuiteId = suiteId ?? config.testPlan.suiteId;
211
+ if (!resolvedSuiteId) {
212
+ const plan = await api.getTestPlanById(config.project, config.testPlan.id);
213
+ resolvedSuiteId = plan?.rootSuite?.id;
214
+ }
215
+ if (!resolvedSuiteId)
216
+ return [];
217
+ const suiteTestCases = await api.getTestCaseList(config.project, config.testPlan.id, resolvedSuiteId);
218
+ if (!suiteTestCases?.length)
219
+ return [];
220
+ const fields = [
221
+ 'System.Title',
222
+ 'System.Description',
223
+ 'Microsoft.VSTS.TCM.Steps',
224
+ 'System.Tags',
225
+ 'System.ChangedDate',
226
+ 'System.AreaPath',
227
+ 'System.IterationPath',
228
+ ];
229
+ const ids = suiteTestCases.map((tc) => tc.workItem?.id).filter(Boolean);
230
+ if (!ids.length)
231
+ return [];
232
+ const workItems = await wit.getWorkItems(ids, fields);
233
+ return (workItems ?? []).map((wi) => {
234
+ const f = wi.fields ?? {};
235
+ return {
236
+ id: wi.id,
237
+ title: f['System.Title'] ?? '',
238
+ description: f['System.Description'] ?? '',
239
+ steps: parseStepsXml(f['Microsoft.VSTS.TCM.Steps'] ?? ''),
240
+ tags: tagsFromString(f['System.Tags']),
241
+ changedDate: f['System.ChangedDate'],
242
+ areaPath: f['System.AreaPath'],
243
+ iterationPath: f['System.IterationPath'],
244
+ };
245
+ });
246
+ }
247
+ //# sourceMappingURL=test-cases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-cases.js","sourceRoot":"","sources":["../../src/azure/test-cases.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
package/dist/cli.js ADDED
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ require("dotenv/config");
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const commander_1 = require("commander");
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const config_1 = require("./config");
46
+ const engine_1 = require("./sync/engine");
47
+ // ─── CLI definition ───────────────────────────────────────────────────────────
48
+ const program = new commander_1.Command();
49
+ program
50
+ .name('ado-sync')
51
+ .description('Bidirectional sync between local test specs and Azure DevOps Test Cases')
52
+ .version('0.1.0');
53
+ // Global option: --config / -c
54
+ program.option('-c, --config <path>', 'Path to config file (default: ado-sync.json)');
55
+ // ─── init ─────────────────────────────────────────────────────────────────────
56
+ program
57
+ .command('init')
58
+ .description('Generate a starter ado-sync.json config file')
59
+ .option('-o, --output <path>', 'Output path', 'ado-sync.json')
60
+ .action((opts) => {
61
+ const outPath = path.resolve(opts.output);
62
+ if (fs.existsSync(outPath)) {
63
+ console.error(chalk_1.default.red(`File already exists: ${outPath}`));
64
+ process.exit(1);
65
+ }
66
+ fs.writeFileSync(outPath, config_1.CONFIG_TEMPLATE_JSON, 'utf8');
67
+ console.log(chalk_1.default.green(`Created ${outPath}`));
68
+ console.log(chalk_1.default.dim('Edit the file with your Azure DevOps details, then run:'));
69
+ console.log(chalk_1.default.dim(' ado-sync push (to create test cases from local specs)'));
70
+ console.log(chalk_1.default.dim(' ado-sync pull (to pull updates from Azure DevOps)'));
71
+ });
72
+ // ─── push ─────────────────────────────────────────────────────────────────────
73
+ program
74
+ .command('push')
75
+ .description('Push local test specs to Azure DevOps (create or update test cases)')
76
+ .option('--dry-run', 'Show what would change without making any modifications')
77
+ .option('--tags <expression>', 'Only sync scenarios matching this tag expression (e.g. "@smoke and not @wip")')
78
+ .action(async (opts) => {
79
+ const globalOpts = program.opts();
80
+ try {
81
+ const configPath = (0, config_1.resolveConfigPath)(globalOpts.config);
82
+ const config = (0, config_1.loadConfig)(configPath);
83
+ const configDir = path.dirname(configPath);
84
+ console.log(chalk_1.default.bold('ado-sync push'));
85
+ console.log(chalk_1.default.dim(`Config: ${configPath}`));
86
+ console.log(chalk_1.default.dim(`Project: ${config.project}`));
87
+ console.log(chalk_1.default.dim(`Plan: ${config.testPlan.id}`));
88
+ if (opts.dryRun)
89
+ console.log(chalk_1.default.yellow('Dry run — no changes will be made'));
90
+ if (opts.tags)
91
+ console.log(chalk_1.default.dim(`Tags: ${opts.tags}`));
92
+ console.log('');
93
+ const results = await (0, engine_1.push)(config, configDir, { dryRun: opts.dryRun, tags: opts.tags });
94
+ printResults(results);
95
+ }
96
+ catch (err) {
97
+ console.error(chalk_1.default.red(err.message));
98
+ process.exit(1);
99
+ }
100
+ });
101
+ // ─── pull ─────────────────────────────────────────────────────────────────────
102
+ program
103
+ .command('pull')
104
+ .description('Pull updates from Azure DevOps into local spec files')
105
+ .option('--dry-run', 'Show what would change without modifying local files')
106
+ .option('--tags <expression>', 'Only sync scenarios matching this tag expression (e.g. "@smoke and not @wip")')
107
+ .action(async (opts) => {
108
+ const globalOpts = program.opts();
109
+ try {
110
+ const configPath = (0, config_1.resolveConfigPath)(globalOpts.config);
111
+ const config = (0, config_1.loadConfig)(configPath);
112
+ const configDir = path.dirname(configPath);
113
+ console.log(chalk_1.default.bold('ado-sync pull'));
114
+ console.log(chalk_1.default.dim(`Config: ${configPath}`));
115
+ if (opts.dryRun)
116
+ console.log(chalk_1.default.yellow('Dry run — no changes will be made'));
117
+ if (opts.tags)
118
+ console.log(chalk_1.default.dim(`Tags: ${opts.tags}`));
119
+ console.log('');
120
+ const results = await (0, engine_1.pull)(config, configDir, { dryRun: opts.dryRun, tags: opts.tags });
121
+ printResults(results);
122
+ }
123
+ catch (err) {
124
+ console.error(chalk_1.default.red(err.message));
125
+ process.exit(1);
126
+ }
127
+ });
128
+ // ─── status ───────────────────────────────────────────────────────────────────
129
+ program
130
+ .command('status')
131
+ .description('Show diff between local specs and Azure DevOps without making changes')
132
+ .option('--tags <expression>', 'Only check scenarios matching this tag expression')
133
+ .action(async (opts) => {
134
+ const globalOpts = program.opts();
135
+ try {
136
+ const configPath = (0, config_1.resolveConfigPath)(globalOpts.config);
137
+ const config = (0, config_1.loadConfig)(configPath);
138
+ const configDir = path.dirname(configPath);
139
+ console.log(chalk_1.default.bold('ado-sync status'));
140
+ console.log(chalk_1.default.dim(`Config: ${configPath}`));
141
+ if (opts.tags)
142
+ console.log(chalk_1.default.dim(`Tags: ${opts.tags}`));
143
+ console.log('');
144
+ const results = await (0, engine_1.status)(config, configDir, { tags: opts.tags });
145
+ printResults(results);
146
+ }
147
+ catch (err) {
148
+ console.error(chalk_1.default.red(err.message));
149
+ process.exit(1);
150
+ }
151
+ });
152
+ // ─── Output helpers ───────────────────────────────────────────────────────────
153
+ function printResults(results) {
154
+ const counts = { created: 0, updated: 0, pulled: 0, skipped: 0, conflict: 0, error: 0 };
155
+ for (const r of results) {
156
+ counts[r.action]++;
157
+ const idStr = r.azureId ? chalk_1.default.dim(` [#${r.azureId}]`) : '';
158
+ const detailStr = r.detail ? chalk_1.default.dim(` — ${r.detail}`) : '';
159
+ const filePart = chalk_1.default.dim(path.relative(process.cwd(), r.filePath) + ':');
160
+ switch (r.action) {
161
+ case 'created':
162
+ console.log(`${chalk_1.default.green('+')} ${filePart} ${r.title}${idStr}`);
163
+ break;
164
+ case 'updated':
165
+ console.log(`${chalk_1.default.blue('~')} ${filePart} ${r.title}${idStr}`);
166
+ break;
167
+ case 'pulled':
168
+ console.log(`${chalk_1.default.cyan('↓')} ${filePart} ${r.title}${idStr}${detailStr}`);
169
+ break;
170
+ case 'skipped':
171
+ console.log(`${chalk_1.default.dim('=')} ${filePart} ${chalk_1.default.dim(r.title)}${idStr}`);
172
+ break;
173
+ case 'conflict':
174
+ console.log(`${chalk_1.default.yellow('!')} ${filePart} ${r.title}${idStr}${detailStr}`);
175
+ break;
176
+ case 'error':
177
+ console.log(`${chalk_1.default.red('✗')} ${filePart} ${r.title}${idStr}${chalk_1.default.red(detailStr)}`);
178
+ break;
179
+ }
180
+ }
181
+ console.log('');
182
+ const summary = [
183
+ counts.created && chalk_1.default.green(`${counts.created} created`),
184
+ counts.updated && chalk_1.default.blue(`${counts.updated} updated`),
185
+ counts.pulled && chalk_1.default.cyan(`${counts.pulled} pulled`),
186
+ counts.skipped && chalk_1.default.dim(`${counts.skipped} skipped`),
187
+ counts.conflict && chalk_1.default.yellow(`${counts.conflict} conflicts`),
188
+ counts.error && chalk_1.default.red(`${counts.error} errors`),
189
+ ].filter(Boolean).join(chalk_1.default.dim(' '));
190
+ console.log(summary || chalk_1.default.dim('Nothing to sync.'));
191
+ }
192
+ // ─── Run ─────────────────────────────────────────────────────────────────────
193
+ program.parse(process.argv);
194
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { SyncConfig } from './types';
2
+ export declare function resolveConfigPath(explicitPath?: string): string;
3
+ export declare function loadConfig(configPath: string): SyncConfig;
4
+ export declare const CONFIG_TEMPLATE_JSON: string;
package/dist/config.js ADDED
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CONFIG_TEMPLATE_JSON = void 0;
37
+ exports.resolveConfigPath = resolveConfigPath;
38
+ exports.loadConfig = loadConfig;
39
+ const fs = __importStar(require("fs"));
40
+ const yaml = __importStar(require("js-yaml"));
41
+ const path = __importStar(require("path"));
42
+ const CONFIG_FILENAMES = ['ado-sync.json', 'ado-sync.yml', 'ado-sync.yaml'];
43
+ function resolveConfigPath(explicitPath) {
44
+ if (explicitPath) {
45
+ const abs = path.resolve(explicitPath);
46
+ if (!fs.existsSync(abs))
47
+ throw new Error(`Config file not found: ${abs}`);
48
+ return abs;
49
+ }
50
+ for (const name of CONFIG_FILENAMES) {
51
+ const candidate = path.resolve(process.cwd(), name);
52
+ if (fs.existsSync(candidate))
53
+ return candidate;
54
+ }
55
+ throw new Error(`No config file found. Create one of: ${CONFIG_FILENAMES.join(', ')}\n` +
56
+ `Run 'ado-sync init' to generate a template.`);
57
+ }
58
+ function loadConfig(configPath) {
59
+ const raw = fs.readFileSync(configPath, 'utf8');
60
+ const ext = path.extname(configPath).toLowerCase();
61
+ let parsed;
62
+ if (ext === '.json') {
63
+ parsed = JSON.parse(raw);
64
+ }
65
+ else {
66
+ parsed = yaml.load(raw);
67
+ }
68
+ const cfg = parsed;
69
+ // Expand env var tokens like "$MY_TOKEN"
70
+ if (cfg.auth?.token?.startsWith('$')) {
71
+ const envKey = cfg.auth.token.slice(1);
72
+ const envVal = process.env[envKey];
73
+ if (!envVal) {
74
+ throw new Error(`Environment variable '${envKey}' (referenced in auth.token) is not set.`);
75
+ }
76
+ cfg.auth.token = envVal;
77
+ }
78
+ validateConfig(cfg, configPath);
79
+ // Default values
80
+ cfg.sync = cfg.sync ?? {};
81
+ cfg.sync.tagPrefix = cfg.sync.tagPrefix ?? 'tc';
82
+ return cfg;
83
+ }
84
+ function validateConfig(cfg, filePath) {
85
+ const err = (msg) => {
86
+ throw new Error(`Config error in ${filePath}: ${msg}`);
87
+ };
88
+ if (!cfg.orgUrl)
89
+ err('"orgUrl" is required');
90
+ if (!cfg.project)
91
+ err('"project" is required');
92
+ if (!cfg.auth?.type)
93
+ err('"auth.type" is required (pat | accessToken | managedIdentity)');
94
+ if (cfg.auth.type !== 'managedIdentity' && !cfg.auth.token) {
95
+ err('"auth.token" is required for auth type "' + cfg.auth.type + '"');
96
+ }
97
+ if (cfg.auth.type === 'managedIdentity' && !cfg.auth.applicationIdURI) {
98
+ err('"auth.applicationIdURI" is required when auth.type is "managedIdentity"');
99
+ }
100
+ if (!cfg.testPlan?.id)
101
+ err('"testPlan.id" is required');
102
+ if (!cfg.local?.type)
103
+ err('"local.type" is required (gherkin | markdown)');
104
+ if (!cfg.local?.include)
105
+ err('"local.include" is required');
106
+ }
107
+ exports.CONFIG_TEMPLATE_JSON = JSON.stringify({
108
+ orgUrl: 'https://dev.azure.com/YOUR_ORG',
109
+ project: 'YOUR_PROJECT',
110
+ auth: {
111
+ type: 'pat',
112
+ token: '$AZURE_DEVOPS_TOKEN',
113
+ },
114
+ testPlan: {
115
+ id: 0,
116
+ suiteId: 0,
117
+ },
118
+ local: {
119
+ type: 'gherkin',
120
+ include: 'specs/**/*.feature',
121
+ exclude: [],
122
+ },
123
+ sync: {
124
+ tagPrefix: 'tc',
125
+ areaPath: '',
126
+ iterationPath: '',
127
+ },
128
+ }, null, 2);
129
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Gherkin / Cucumber .feature file parser.
3
+ *
4
+ * Uses the modern @cucumber/gherkin generateMessages() API (same approach as
5
+ * playwright-bdd). This produces both the GherkinDocument (AST) and Pickles
6
+ * (compiled, example-substituted scenarios) in a single pass.
7
+ *
8
+ * Pickles are the canonical unit of work — they already have:
9
+ * - Example values substituted in titles and step text
10
+ * - Background steps merged into every scenario
11
+ * - Tags inherited from Feature + Scenario + Examples blocks
12
+ * - Step type (Context/Action/Outcome) instead of keyword
13
+ *
14
+ * ID tag convention: @tc:12345 (prefix configurable via sync.tagPrefix)
15
+ *
16
+ * Path-based auto-tagging:
17
+ * Directory segments prefixed with @ are added as tags automatically.
18
+ * e.g. specs/@smoke/@regression/login.feature → tags: ['smoke', 'regression']
19
+ */
20
+ import { ParsedTest } from '../types';
21
+ /** Extract Azure Test Case ID from a list of tag names. e.g. ['tc:123', 'smoke'] → 123 */
22
+ export declare function extractAzureId(tags: string[], tagPrefix: string): number | undefined;
23
+ /**
24
+ * Extract auto-tags from directory segments that start with '@'.
25
+ *
26
+ * Given /project/specs/@smoke/@regression/login.feature
27
+ * returns ['smoke', 'regression']
28
+ */
29
+ export declare function extractPathTags(filePath: string): string[];
30
+ export declare function parseGherkinFile(filePath: string, tagPrefix: string): ParsedTest[];