doc-detective-common 3.4.0 → 3.4.1-dev.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/AGENTS.md ADDED
@@ -0,0 +1,285 @@
1
+ # Doc Detective Common - AI Coding Agent Guide
2
+
3
+ ## Project Overview
4
+
5
+ **doc-detective-common** is a shared utilities library for the Doc Detective test automation ecosystem. It provides:
6
+ - JSON Schema validation with AJV (50+ schemas for test specifications, configs, steps)
7
+ - Automatic schema transformation between versions (v2 → v3)
8
+ - Path resolution utilities (relative to absolute conversion)
9
+ - File reading with JSON/YAML parsing (local and remote URLs)
10
+
11
+ This is a dependency package, not a standalone application. Changes here affect downstream packages.
12
+
13
+ ## Architecture
14
+
15
+ ### Schema System (Core Component)
16
+
17
+ **Three-stage schema pipeline:**
18
+
19
+ 1. **Source schemas** (`src/schemas/src_schemas/*.json`) - Hand-maintained with `$ref` pointers
20
+ 2. **Build schemas** (`src/schemas/build/`) - References resolved to absolute paths
21
+ 3. **Output schemas** (`src/schemas/output_schemas/`) - Fully dereferenced, ready for distribution
22
+
23
+ **Build process:** Run `npm run build` which executes `dereferenceSchemas.js`:
24
+ - Updates `$ref` paths to absolute file paths
25
+ - Dereferences all references using `@apidevtools/json-schema-ref-parser`
26
+ - Removes `$id` properties
27
+ - Generates `schemas.json` (all schemas as single object)
28
+ - Publishes v3 schemas to `dist/schemas/` for external consumption
29
+
30
+ **Schema versioning:**
31
+ - v2 schemas: Legacy, supported via `compatibleSchemas` transformation map
32
+ - v3 schemas: Current version with new naming (e.g., `screenshot_v3`, `step_v3`)
33
+ - All schemas **must** include `examples` array (validated in tests)
34
+
35
+ ### Validation System
36
+
37
+ **Key feature:** Automatic backward compatibility transformation
38
+
39
+ When `validate()` is called:
40
+ 1. Validates against target schema (e.g., `step_v3`)
41
+ 2. If validation fails, checks `compatibleSchemas` map for older versions
42
+ 3. Validates against each compatible schema (e.g., `checkLink_v2`, `find_v2`)
43
+ 4. On match, calls `transformToSchemaKey()` to upgrade object structure
44
+ 5. Revalidates transformed object against target schema
45
+
46
+ **Important transformations in `validate.js`:**
47
+ - `step_v3` accepts 12 different v2 action schemas (`checkLink_v2`, `find_v2`, etc.)
48
+ - `config_v3` ← `config_v2`: Restructures nested `runTests` object
49
+ - `context_v3` ← `context_v2`: Changes `app` → `browsers` structure
50
+ - Property renames: `typeKeys.delay` → `type.inputDelay`, `maxVariation` (0-100) → (0-1), etc.
51
+
52
+ **AJV configuration** (in `validate.js`):
53
+ - `coerceTypes: true` - Auto-converts strings to numbers, etc.
54
+ - `useDefaults: true` - Applies default values from schema
55
+ - Dynamic defaults: `uuid` generates unique IDs for `stepId`, `configId`
56
+ - Custom errors via `ajv-errors`
57
+ - Formats via `ajv-formats`, keywords via `ajv-keywords`
58
+
59
+ ### Path Resolution (`resolvePaths.js`)
60
+
61
+ **Resolves relative paths to absolute based on:**
62
+ - `config.relativePathBase`: `"file"` (relative to file location) or `"cwd"` (relative to working directory)
63
+ - Object type: `config` or `spec` (different path properties)
64
+
65
+ **Properties resolved:**
66
+ - Config: `input`, `output`, `loadVariables`, `beforeAny`, `afterAll`, `mediaDirectory`, etc.
67
+ - Spec: `file`, `path`, `directory`, `before`, `after`, `workingDirectory`, etc.
68
+ - Skips: HTTP(S) URLs, already-absolute paths, user data properties (e.g., `requestData`)
69
+
70
+ **Recursive handling:**
71
+ - Processes nested objects and arrays
72
+ - Special case: `path` resolved relative to `directory` if `directory` is absolute
73
+
74
+ ### File Reading (`files.js`)
75
+
76
+ **Detects remote vs. local:**
77
+ - Remote: Uses `axios` for `http://` or `https://` URLs
78
+ - Local: Uses `fs.promises.readFile`
79
+
80
+ **Format detection by extension:**
81
+ - `.json` → JSON.parse()
82
+ - `.yaml`/`.yml` → YAML.parse()
83
+ - Other → raw string
84
+ - Parse errors → returns raw content (graceful degradation)
85
+
86
+ ## Development Workflow
87
+
88
+ ### Adding or Modifying Schemas
89
+
90
+ 1. Edit source schema in `src/schemas/src_schemas/`
91
+ 2. Add/update `examples` array (required for tests)
92
+ 3. If adding new file, add to `files` array in `dereferenceSchemas.js`
93
+ 4. Run `npm run build` (runs `dereferenceSchemas` + tests)
94
+ 5. If creating v3 schema, add to published list in `dereferenceSchemas.js` (line ~130)
95
+
96
+ ### Schema Compatibility
97
+
98
+ When creating new schema version (e.g., v4):
99
+ 1. Update `compatibleSchemas` map in `validate.js`
100
+ 2. Add transformation logic in `transformToSchemaKey()` function
101
+ 3. Handle all property renames, restructuring, and value conversions
102
+ 4. Test with examples from old schema versions
103
+
104
+ ### Testing
105
+
106
+ **Test structure (Mocha + Chai):**
107
+ - `test/schema.test.js`: Validates all schema examples (auto-generated from schemas)
108
+ - `test/files.test.js`: Unit tests for `readFile()` with Sinon stubs
109
+
110
+ **Run tests:** `npm test` (or `mocha`)
111
+
112
+ **Example pattern:**
113
+ ```javascript
114
+ const result = validate({ schemaKey: "step_v3", object: example });
115
+ assert.ok(result.valid, `Validation failed: ${result.errors}`);
116
+ ```
117
+
118
+ ### Version Management & CI/CD Workflows
119
+
120
+ #### Auto Dev Release (`.github/workflows/auto-dev-release.yml`)
121
+
122
+ **Triggers:** Every push to `main` branch (+ manual via `workflow_dispatch`)
123
+
124
+ **Smart skip logic:**
125
+ - Skip if commit message contains `[skip ci]` or `Release`
126
+ - Skip if only documentation files changed (`.md`, `.txt`, `.yml`, `.yaml`, `.github/`)
127
+ - Always runs on manual trigger
128
+
129
+ **Version generation:**
130
+ 1. Extract base version from `package.json` (e.g., `3.1.0`)
131
+ 2. Query npm for latest dev version: `npm view doc-detective-common@dev version`
132
+ 3. If exists and matches base: increment dev number (`.dev.2` → `.dev.3`)
133
+ 4. If none exists: start with `.dev.1`
134
+ 5. Update `package.json` with new version
135
+
136
+ **Pipeline steps:**
137
+ 1. Validate `package.json` (existence, valid JSON, required fields)
138
+ 2. Install dependencies with `npm ci`
139
+ 3. Run `npm test` (must pass)
140
+ 4. Run `npm run build` (builds schemas + post-build tests)
141
+ 5. Update version in `package.json`
142
+ 6. Commit with `[skip ci]` to prevent infinite loop
143
+ 7. Create and push git tag (e.g., `v3.1.0-dev.2`)
144
+ 8. Publish to npm with `dev` tag
145
+
146
+ **Key details:**
147
+ - Uses `DD_DEP_UPDATE_TOKEN` secret for git push permissions
148
+ - Uses `NPM_TOKEN` secret for npm publish
149
+ - Timeout: 5 minutes
150
+ - Runs on: `ubuntu-latest`, Node 18
151
+
152
+ **Install dev releases:** `npm install doc-detective-common@dev`
153
+
154
+ #### Test & Publish (`.github/workflows/npm-test.yml`)
155
+
156
+ **Triggers:** Push to main, PRs, GitHub releases, manual dispatch
157
+
158
+ **Multi-platform matrix testing:**
159
+ - OS: `ubuntu-latest`, `windows-latest`, `macos-latest`
160
+ - Node: `18`, `20`, `22`, `24`
161
+ - Total: 12 test combinations
162
+ - Runs `npm run build` which triggers `postbuild` → `npm test`
163
+
164
+ **Release pipeline (on GitHub release published):**
165
+
166
+ 1. **Test job:** Run full matrix tests
167
+ 2. **Threat assessment:** ReversingLabs security scan
168
+ - Creates npm pack tarball
169
+ - Uploads to RL Portal (`Trial/OSS-MannySilva`)
170
+ - Generates security report artifact
171
+ - Must pass before publish
172
+ 3. **Publish to npm:**
173
+ - Publishes to npm (stable, no tag)
174
+ - Uses `npm_token` secret
175
+ 4. **Update downstream:**
176
+ - Triggers `doc-detective/resolver` repository dispatch
177
+ - Triggers `doc-detective.github.io` repository dispatch
178
+ - Passes new version in payload
179
+
180
+ **Repository dispatch pattern:**
181
+ ```bash
182
+ curl -X POST https://api.github.com/repos/doc-detective/resolver/dispatches \
183
+ -d '{"event_type": "update-common-package-event", "client_payload": {"version": "3.1.0"}}'
184
+ ```
185
+
186
+ **Required secrets:**
187
+ - `RLPORTAL_ACCESS_TOKEN`: ReversingLabs security scanning
188
+ - `npm_token`: NPM publishing
189
+ - `DD_DEP_UPDATE_TOKEN`: Cross-repo dispatches
190
+
191
+ #### Release Strategy Summary
192
+
193
+ - **Dev releases** (`3.1.0-dev.X`): Automatic on every code commit to main
194
+ - **Stable releases** (`3.1.0`): Manual via GitHub releases UI
195
+ - **Cross-repo updates**: Automatic dispatch to dependent packages on stable release
196
+ - **Security scanning**: Required for all stable releases (threat assessment)
197
+
198
+ ## Important Patterns
199
+
200
+ ### Error Handling
201
+
202
+ - `validate()` returns `{ valid, errors, object }` - never throws
203
+ - `transformToSchemaKey()` throws on incompatible schemas or invalid results
204
+ - `readFile()` returns `null` on errors, logs warnings to console
205
+ - `resolvePaths()` throws on invalid object types or missing nested object types
206
+
207
+ ### Object Cloning
208
+
209
+ Always clone before validation to avoid mutations:
210
+ ```javascript
211
+ validationObject = JSON.parse(JSON.stringify(object));
212
+ ```
213
+
214
+ ### Regular Expression Escaping
215
+
216
+ Use `escapeRegExp()` helper when converting user strings to regex patterns (see `transformToSchemaKey()` for fileTypes transformation).
217
+
218
+ ### Schema References
219
+
220
+ - In source schemas: Use relative paths like `"$ref": "context_v3.schema.json#/properties/example"`
221
+ - Build process converts these to absolute file system paths
222
+ - Output schemas have all references fully inlined (dereferenced)
223
+
224
+ ## Common Tasks
225
+
226
+ **Add new schema property:**
227
+ 1. Edit `src/schemas/src_schemas/<schema>.json`
228
+ 2. Add to relevant `examples` array
229
+ 3. If path property, add to `configPaths` or `specPaths` in `resolvePaths.js`
230
+ 4. Run `npm run build`
231
+
232
+ **Add new step type:**
233
+ 1. Create schema in `src_schemas/` (e.g., `newAction_v3.schema.json`)
234
+ 2. Add to `files` array in `dereferenceSchemas.js`
235
+ 3. Reference in `step_v3.schema.json` oneOf
236
+ 4. Add transformation logic if upgrading from v2
237
+ 5. Update `compatibleSchemas` if needed
238
+
239
+ **Debug validation errors:**
240
+ - Check `result.errors` for detailed AJV error messages
241
+ - Error format: `${instancePath} ${message} (${JSON.stringify(params)})`
242
+ - Common issues: Missing required properties, incorrect types, additional properties when not allowed
243
+
244
+ **Trigger dev release:**
245
+ - Just push to main branch (automatic)
246
+ - Or add `[skip ci]` to commit message to prevent release
247
+ - Manual trigger: GitHub Actions → Auto Dev Release → Run workflow
248
+
249
+ **Create stable release:**
250
+ 1. Go to GitHub repository → Releases
251
+ 2. Click "Draft a new release"
252
+ 3. Create new tag (e.g., `v3.2.0`)
253
+ 4. Publish release
254
+ 5. Workflow automatically:
255
+ - Runs full test matrix
256
+ - Performs security scan
257
+ - Publishes to npm
258
+ - Updates downstream packages
259
+
260
+ ## External Dependencies
261
+
262
+ **Runtime:**
263
+ - `ajv` (v8): JSON Schema validator with strict mode disabled, coercion enabled
264
+ - `axios`: HTTP client for remote file reading
265
+ - `yaml`: YAML parser for config files
266
+ - `uuid`: UUID generation for default IDs
267
+ - `@apidevtools/json-schema-ref-parser`: Dereferences schemas
268
+
269
+ **Testing:**
270
+ - `mocha`: Test runner
271
+ - `chai`: Assertions (uses dynamic import for ESM compatibility)
272
+ - `sinon`: Mocking/stubbing
273
+
274
+ ## Anti-Patterns to Avoid
275
+
276
+ - ❌ Don't edit schemas in `build/` or `output_schemas/` (regenerated on build)
277
+ - ❌ Don't skip `examples` in schemas (breaks tests)
278
+ - ❌ Don't modify `schemas.json` directly (generated file)
279
+ - ❌ Don't add schemas without updating `files` array in `dereferenceSchemas.js`
280
+ - ❌ Don't assume validation mutates objects (it returns new validated object)
281
+ - ❌ Don't forget to clone objects before validation if original needs preservation
282
+ - ❌ Don't publish manually to npm (use GitHub releases for stable, auto-dev-release for dev)
283
+ - ❌ Don't commit version bumps manually (workflows handle this automatically)
284
+ - ❌ Don't modify `package.json` version in PRs (causes conflicts with auto-dev-release)
285
+ - ❌ Don't skip security scanning for releases (threat-assessment job required)
@@ -121,6 +121,11 @@
121
121
  "type": "boolean",
122
122
  "description": "Whether or not to run potentially unsafe steps, such as those that might modify files or system state."
123
123
  },
124
+ "crawl": {
125
+ "description": "If `true`, crawls sitemap.xml files specified by URL to find additional files to test.",
126
+ "type": "boolean",
127
+ "default": false
128
+ },
124
129
  "processDitaMaps": {
125
130
  "description": "If `true`, processes DITA maps and includes generated files as inputs.",
126
131
  "type": "boolean",
@@ -2684,6 +2689,14 @@
2684
2689
  }
2685
2690
  ],
2686
2691
  "default": {}
2692
+ },
2693
+ "required": {
2694
+ "type": "array",
2695
+ "description": "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.",
2696
+ "items": {
2697
+ "type": "string"
2698
+ },
2699
+ "default": []
2687
2700
  }
2688
2701
  },
2689
2702
  "title": "Response"
@@ -3075,6 +3088,14 @@
3075
3088
  }
3076
3089
  ],
3077
3090
  "default": {}
3091
+ },
3092
+ "required": {
3093
+ "type": "array",
3094
+ "description": "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.",
3095
+ "items": {
3096
+ "type": "string"
3097
+ },
3098
+ "default": []
3078
3099
  }
3079
3100
  },
3080
3101
  "title": "Response"
@@ -3256,6 +3277,52 @@
3256
3277
  "request": {
3257
3278
  "headers": "Content-Type: application/json\\nAuthorization: Bearer token"
3258
3279
  }
3280
+ },
3281
+ {
3282
+ "url": "https://api.example.com/users/123",
3283
+ "response": {
3284
+ "required": [
3285
+ "id",
3286
+ "email",
3287
+ "createdAt"
3288
+ ]
3289
+ }
3290
+ },
3291
+ {
3292
+ "url": "https://api.example.com/users/123",
3293
+ "response": {
3294
+ "required": [
3295
+ "user.profile.name",
3296
+ "user.profile.avatar",
3297
+ "user.settings.notifications"
3298
+ ]
3299
+ }
3300
+ },
3301
+ {
3302
+ "url": "https://api.example.com/orders",
3303
+ "response": {
3304
+ "required": [
3305
+ "orders[0].id",
3306
+ "orders[0].total",
3307
+ "orders[0].items[0].productId"
3308
+ ]
3309
+ }
3310
+ },
3311
+ {
3312
+ "url": "https://api.example.com/users",
3313
+ "response": {
3314
+ "required": [
3315
+ "sessionToken",
3316
+ "expiresAt",
3317
+ "user.id"
3318
+ ],
3319
+ "body": {
3320
+ "status": "success",
3321
+ "user": {
3322
+ "role": "admin"
3323
+ }
3324
+ }
3325
+ }
3259
3326
  }
3260
3327
  ]
3261
3328
  }
@@ -6754,6 +6821,18 @@
6754
6821
  }
6755
6822
  ]
6756
6823
  }
6824
+ },
6825
+ "docDetectiveApi": {
6826
+ "type": "object",
6827
+ "description": "Configuration for Doc Detective Orchestration API integration.",
6828
+ "additionalProperties": false,
6829
+ "properties": {
6830
+ "apiKey": {
6831
+ "type": "string",
6832
+ "description": "API key for authenticating with the Doc Detective Orchestration API."
6833
+ }
6834
+ },
6835
+ "title": "Doc Detective Orchestration API"
6757
6836
  }
6758
6837
  },
6759
6838
  "title": "Integrations options"
@@ -8865,6 +8944,14 @@
8865
8944
  }
8866
8945
  ],
8867
8946
  "default": {}
8947
+ },
8948
+ "required": {
8949
+ "type": "array",
8950
+ "description": "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.",
8951
+ "items": {
8952
+ "type": "string"
8953
+ },
8954
+ "default": []
8868
8955
  }
8869
8956
  },
8870
8957
  "title": "Response"
@@ -9256,6 +9343,14 @@
9256
9343
  }
9257
9344
  ],
9258
9345
  "default": {}
9346
+ },
9347
+ "required": {
9348
+ "type": "array",
9349
+ "description": "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.",
9350
+ "items": {
9351
+ "type": "string"
9352
+ },
9353
+ "default": []
9259
9354
  }
9260
9355
  },
9261
9356
  "title": "Response"
@@ -9437,6 +9532,52 @@
9437
9532
  "request": {
9438
9533
  "headers": "Content-Type: application/json\\nAuthorization: Bearer token"
9439
9534
  }
9535
+ },
9536
+ {
9537
+ "url": "https://api.example.com/users/123",
9538
+ "response": {
9539
+ "required": [
9540
+ "id",
9541
+ "email",
9542
+ "createdAt"
9543
+ ]
9544
+ }
9545
+ },
9546
+ {
9547
+ "url": "https://api.example.com/users/123",
9548
+ "response": {
9549
+ "required": [
9550
+ "user.profile.name",
9551
+ "user.profile.avatar",
9552
+ "user.settings.notifications"
9553
+ ]
9554
+ }
9555
+ },
9556
+ {
9557
+ "url": "https://api.example.com/orders",
9558
+ "response": {
9559
+ "required": [
9560
+ "orders[0].id",
9561
+ "orders[0].total",
9562
+ "orders[0].items[0].productId"
9563
+ ]
9564
+ }
9565
+ },
9566
+ {
9567
+ "url": "https://api.example.com/users",
9568
+ "response": {
9569
+ "required": [
9570
+ "sessionToken",
9571
+ "expiresAt",
9572
+ "user.id"
9573
+ ],
9574
+ "body": {
9575
+ "status": "success",
9576
+ "user": {
9577
+ "role": "admin"
9578
+ }
9579
+ }
9580
+ }
9440
9581
  }
9441
9582
  ]
9442
9583
  }
@@ -12702,6 +12843,16 @@
12702
12843
  },
12703
12844
  {
12704
12845
  "debug": "stepThrough"
12846
+ },
12847
+ {
12848
+ "integrations": {
12849
+ "docDetectiveApi": {
12850
+ "apiKey": "your-api-key-here"
12851
+ }
12852
+ }
12853
+ },
12854
+ {
12855
+ "crawl": true
12705
12856
  }
12706
12857
  ]
12707
12858
  }
@@ -355,6 +355,14 @@
355
355
  }
356
356
  ],
357
357
  "default": {}
358
+ },
359
+ "required": {
360
+ "type": "array",
361
+ "description": "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.",
362
+ "items": {
363
+ "type": "string"
364
+ },
365
+ "default": []
358
366
  }
359
367
  },
360
368
  "title": "Response"
@@ -746,6 +754,14 @@
746
754
  }
747
755
  ],
748
756
  "default": {}
757
+ },
758
+ "required": {
759
+ "type": "array",
760
+ "description": "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.",
761
+ "items": {
762
+ "type": "string"
763
+ },
764
+ "default": []
749
765
  }
750
766
  },
751
767
  "title": "Response"
@@ -927,6 +943,52 @@
927
943
  "request": {
928
944
  "headers": "Content-Type: application/json\\nAuthorization: Bearer token"
929
945
  }
946
+ },
947
+ {
948
+ "url": "https://api.example.com/users/123",
949
+ "response": {
950
+ "required": [
951
+ "id",
952
+ "email",
953
+ "createdAt"
954
+ ]
955
+ }
956
+ },
957
+ {
958
+ "url": "https://api.example.com/users/123",
959
+ "response": {
960
+ "required": [
961
+ "user.profile.name",
962
+ "user.profile.avatar",
963
+ "user.settings.notifications"
964
+ ]
965
+ }
966
+ },
967
+ {
968
+ "url": "https://api.example.com/orders",
969
+ "response": {
970
+ "required": [
971
+ "orders[0].id",
972
+ "orders[0].total",
973
+ "orders[0].items[0].productId"
974
+ ]
975
+ }
976
+ },
977
+ {
978
+ "url": "https://api.example.com/users",
979
+ "response": {
980
+ "required": [
981
+ "sessionToken",
982
+ "expiresAt",
983
+ "user.id"
984
+ ],
985
+ "body": {
986
+ "status": "success",
987
+ "user": {
988
+ "role": "admin"
989
+ }
990
+ }
991
+ }
930
992
  }
931
993
  ]
932
994
  }