@contractkit/plugin-bruno 1.2.0 → 1.4.0
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/.turbo/turbo-build$colon$ci.log +7 -7
- package/.turbo/turbo-test$colon$ci.log +15 -14
- package/CHANGELOG.md +26 -0
- package/README.md +7 -5
- package/coverage/clover.xml +395 -307
- package/coverage/coverage-final.json +3 -2
- package/coverage/index.html +19 -19
- package/coverage/src/codegen-bruno.ts.html +765 -177
- package/coverage/src/index.html +34 -19
- package/coverage/src/index.ts.html +658 -0
- package/coverage/tests/helpers.ts.html +16 -16
- package/coverage/tests/index.html +1 -1
- package/dist/codegen-bruno.d.ts +41 -3
- package/dist/codegen-bruno.d.ts.map +1 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +216 -85
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/codegen-bruno.ts +236 -40
- package/src/index.ts +89 -51
- package/tests/codegen-bruno.test.ts +164 -24
|
@@ -266,11 +266,11 @@
|
|
|
266
266
|
<span class="cline-any cline-neutral"> </span>
|
|
267
267
|
<span class="cline-any cline-neutral"> </span>
|
|
268
268
|
<span class="cline-any cline-neutral"> </span>
|
|
269
|
-
<span class="cline-any cline-yes">
|
|
269
|
+
<span class="cline-any cline-yes">288x</span>
|
|
270
270
|
<span class="cline-any cline-neutral"> </span>
|
|
271
271
|
<span class="cline-any cline-neutral"> </span>
|
|
272
272
|
<span class="cline-any cline-neutral"> </span>
|
|
273
|
-
<span class="cline-any cline-yes">
|
|
273
|
+
<span class="cline-any cline-yes">53x</span>
|
|
274
274
|
<span class="cline-any cline-neutral"> </span>
|
|
275
275
|
<span class="cline-any cline-neutral"> </span>
|
|
276
276
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -298,7 +298,7 @@
|
|
|
298
298
|
<span class="cline-any cline-neutral"> </span>
|
|
299
299
|
<span class="cline-any cline-neutral"> </span>
|
|
300
300
|
<span class="cline-any cline-neutral"> </span>
|
|
301
|
-
<span class="cline-any cline-yes">
|
|
301
|
+
<span class="cline-any cline-yes">12x</span>
|
|
302
302
|
<span class="cline-any cline-neutral"> </span>
|
|
303
303
|
<span class="cline-any cline-neutral"> </span>
|
|
304
304
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -310,7 +310,7 @@
|
|
|
310
310
|
<span class="cline-any cline-neutral"> </span>
|
|
311
311
|
<span class="cline-any cline-neutral"> </span>
|
|
312
312
|
<span class="cline-any cline-neutral"> </span>
|
|
313
|
-
<span class="cline-any cline-yes">
|
|
313
|
+
<span class="cline-any cline-yes">39x</span>
|
|
314
314
|
<span class="cline-any cline-neutral"> </span>
|
|
315
315
|
<span class="cline-any cline-neutral"> </span>
|
|
316
316
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -322,7 +322,7 @@
|
|
|
322
322
|
<span class="cline-any cline-neutral"> </span>
|
|
323
323
|
<span class="cline-any cline-neutral"> </span>
|
|
324
324
|
<span class="cline-any cline-neutral"> </span>
|
|
325
|
-
<span class="cline-any cline-yes">
|
|
325
|
+
<span class="cline-any cline-yes">11x</span>
|
|
326
326
|
<span class="cline-any cline-neutral"> </span>
|
|
327
327
|
<span class="cline-any cline-neutral"> </span>
|
|
328
328
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -332,7 +332,7 @@
|
|
|
332
332
|
<span class="cline-any cline-neutral"> </span>
|
|
333
333
|
<span class="cline-any cline-neutral"> </span>
|
|
334
334
|
<span class="cline-any cline-neutral"> </span>
|
|
335
|
-
<span class="cline-any cline-yes">
|
|
335
|
+
<span class="cline-any cline-yes">10x</span>
|
|
336
336
|
<span class="cline-any cline-neutral"> </span>
|
|
337
337
|
<span class="cline-any cline-neutral"> </span>
|
|
338
338
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -355,8 +355,8 @@
|
|
|
355
355
|
<span class="cline-any cline-neutral"> </span>
|
|
356
356
|
<span class="cline-any cline-neutral"> </span>
|
|
357
357
|
<span class="cline-any cline-neutral"> </span>
|
|
358
|
-
<span class="cline-any cline-yes">
|
|
359
|
-
<span class="cline-any cline-yes">
|
|
358
|
+
<span class="cline-any cline-yes">22x</span>
|
|
359
|
+
<span class="cline-any cline-yes">22x</span>
|
|
360
360
|
<span class="cline-any cline-neutral"> </span>
|
|
361
361
|
<span class="cline-any cline-neutral"> </span>
|
|
362
362
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -384,10 +384,10 @@
|
|
|
384
384
|
<span class="cline-any cline-neutral"> </span>
|
|
385
385
|
<span class="cline-any cline-neutral"> </span>
|
|
386
386
|
<span class="cline-any cline-neutral"> </span>
|
|
387
|
-
<span class="cline-any cline-yes">
|
|
388
|
-
<span class="cline-any cline-yes">
|
|
389
|
-
<span class="cline-any cline-yes">
|
|
390
|
-
<span class="cline-any cline-yes">
|
|
387
|
+
<span class="cline-any cline-yes">115x</span>
|
|
388
|
+
<span class="cline-any cline-yes">115x</span>
|
|
389
|
+
<span class="cline-any cline-yes">115x</span>
|
|
390
|
+
<span class="cline-any cline-yes">115x</span>
|
|
391
391
|
<span class="cline-any cline-neutral"> </span>
|
|
392
392
|
<span class="cline-any cline-neutral"> </span>
|
|
393
393
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -402,12 +402,12 @@
|
|
|
402
402
|
<span class="cline-any cline-neutral"> </span>
|
|
403
403
|
<span class="cline-any cline-neutral"> </span>
|
|
404
404
|
<span class="cline-any cline-neutral"> </span>
|
|
405
|
-
<span class="cline-any cline-yes">
|
|
406
|
-
<span class="cline-any cline-yes">
|
|
405
|
+
<span class="cline-any cline-yes">104x</span>
|
|
406
|
+
<span class="cline-any cline-yes">104x</span>
|
|
407
407
|
<span class="cline-any cline-neutral"> </span>
|
|
408
408
|
<span class="cline-any cline-neutral"> </span>
|
|
409
409
|
<span class="cline-any cline-neutral"> </span>
|
|
410
|
-
<span class="cline-any cline-yes">
|
|
410
|
+
<span class="cline-any cline-yes">101x</span>
|
|
411
411
|
<span class="cline-any cline-neutral"> </span>
|
|
412
412
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type {
|
|
413
413
|
ContractRootNode,
|
|
@@ -589,7 +589,7 @@ export function opRoot(routes: OpRouteNode[], file = 'users.op', meta: Record<
|
|
|
589
589
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
590
590
|
Code coverage generated by
|
|
591
591
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
592
|
-
at 2026-05-
|
|
592
|
+
at 2026-05-07T11:09:13.542Z
|
|
593
593
|
</div>
|
|
594
594
|
<script src="../prettify.js"></script>
|
|
595
595
|
<script>
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
102
102
|
Code coverage generated by
|
|
103
103
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
104
|
-
at 2026-05-
|
|
104
|
+
at 2026-05-07T11:09:13.542Z
|
|
105
105
|
</div>
|
|
106
106
|
<script src="../prettify.js"></script>
|
|
107
107
|
<script>
|
package/dist/codegen-bruno.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OpRootNode, ContractRootNode } from '@contractkit/core';
|
|
1
|
+
import type { OpRootNode, ContractRootNode, IncrementalManifest, IncrementalResult as IncrementalResultBase } from '@contractkit/core';
|
|
2
2
|
/** A single file produced by the Bruno codegen — a relative output path and its YAML content. */
|
|
3
3
|
export interface OpenCollectionFile {
|
|
4
4
|
relativePath: string;
|
|
@@ -6,6 +6,12 @@ export interface OpenCollectionFile {
|
|
|
6
6
|
}
|
|
7
7
|
/** Manifest filename — tracks which files this plugin previously generated so subsequent runs can clean up only those, leaving any user-added files alone. */
|
|
8
8
|
export declare const MANIFEST_FILENAME = ".contractkit-bruno-manifest.json";
|
|
9
|
+
/**
|
|
10
|
+
* Bumped whenever the codegen output shape changes in a way that should bust
|
|
11
|
+
* every per-op fingerprint. Mixed into the per-op fingerprint so a plugin
|
|
12
|
+
* upgrade forces full regeneration even when source `.ck` files are unchanged.
|
|
13
|
+
*/
|
|
14
|
+
export declare const BRUNO_CODEGEN_VERSION = "1";
|
|
9
15
|
/** Subset of a security scheme sufficient for Bruno auth generation (non-HMAC). */
|
|
10
16
|
export interface BrunoSecurityScheme {
|
|
11
17
|
type: string;
|
|
@@ -49,14 +55,46 @@ export interface OpenCollectionOptions {
|
|
|
49
55
|
*/
|
|
50
56
|
environments?: Record<string, Record<string, unknown>>;
|
|
51
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* @deprecated Use {@link IncrementalManifest} from `@contractkit/core`. Re-exported here for backwards compatibility.
|
|
60
|
+
*/
|
|
61
|
+
export type BrunoManifest = IncrementalManifest;
|
|
62
|
+
/** Result of {@link generateOpenCollectionIncremental}. Renamed `skippedOpCount` for Bruno-specific clarity but otherwise the shared {@link IncrementalResultBase}. */
|
|
63
|
+
export interface IncrementalResult extends Omit<IncrementalResultBase, 'skippedUnitCount'> {
|
|
64
|
+
/** Number of ops whose codegen was skipped because their fingerprint matched. */
|
|
65
|
+
skippedOpCount: number;
|
|
66
|
+
}
|
|
52
67
|
/**
|
|
53
68
|
* Generates an OpenCollection (https://spec.opencollection.com/) API collection
|
|
54
69
|
* from a set of operation roots. Produces opencollection.yml, an environment
|
|
55
70
|
* file, and one .yml request file per operation.
|
|
71
|
+
*
|
|
72
|
+
* This is the full-regeneration entry point — every file is rebuilt from scratch.
|
|
73
|
+
* For cache-aware incremental builds, use {@link generateOpenCollectionIncremental}.
|
|
56
74
|
*/
|
|
57
75
|
export declare function generateOpenCollection(roots: OpRootNode[], options: OpenCollectionOptions): OpenCollectionFile[];
|
|
58
|
-
/**
|
|
59
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Cache-aware variant of {@link generateOpenCollection}. Skips re-rendering YAML
|
|
78
|
+
* for any op whose fingerprint matches the entry in `prevManifest`. The caller is
|
|
79
|
+
* responsible for emitting `filesToWrite`, deleting `deletedPaths`, and persisting
|
|
80
|
+
* `manifest` so the next run can match against it.
|
|
81
|
+
*
|
|
82
|
+
* Global files (collection root, env files, folder.yml) are always regenerated —
|
|
83
|
+
* they're cheap and depend on options the manifest doesn't fingerprint.
|
|
84
|
+
*/
|
|
85
|
+
export declare function generateOpenCollectionIncremental(roots: OpRootNode[], options: OpenCollectionOptions, prevManifest: IncrementalManifest, fileExists?: (relativePath: string) => boolean): IncrementalResult;
|
|
86
|
+
/** Empty-state manifest, used when none has been written yet (or when the cache is being bypassed). */
|
|
87
|
+
export declare function emptyManifest(): IncrementalManifest;
|
|
88
|
+
/**
|
|
89
|
+
* Parse a previously-written manifest. Accepts both v1 (`{ files: string[] }`) and v2
|
|
90
|
+
* (full {@link IncrementalManifest}) shapes. v1 manifests are returned with an empty
|
|
91
|
+
* `units` map so the next run treats every op as a cache miss while still cleaning up
|
|
92
|
+
* the tracked file list.
|
|
93
|
+
*
|
|
94
|
+
* Returns an empty manifest on any parse error so a stale/garbled file never blocks
|
|
95
|
+
* regeneration.
|
|
96
|
+
*/
|
|
97
|
+
export declare function parseManifest(content: string): IncrementalManifest;
|
|
60
98
|
/**
|
|
61
99
|
* Deep-merges a YAML override string into a generated YAML string.
|
|
62
100
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen-bruno.d.ts","sourceRoot":"","sources":["../src/codegen-bruno.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,UAAU,EAMV,gBAAgB,
|
|
1
|
+
{"version":3,"file":"codegen-bruno.d.ts","sourceRoot":"","sources":["../src/codegen-bruno.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,UAAU,EAMV,gBAAgB,EAGhB,mBAAmB,EAEnB,iBAAiB,IAAI,qBAAqB,EAC7C,MAAM,mBAAmB,CAAC;AAe3B,iGAAiG;AACjG,MAAM,WAAW,kBAAkB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,8JAA8J;AAC9J,eAAO,MAAM,iBAAiB,qCAAqC,CAAC;AAEpE;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,mFAAmF;AACnF,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACf;AAED,kHAAkH;AAClH,MAAM,WAAW,gBAAgB;IAC7B,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CACjD;AAED,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACnC,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC1D;AAiBD;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,mBAAmB,CAAC;AAEhD,uKAAuK;AACvK,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;IACtF,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,qBAAqB,GAAG,kBAAkB,EAAE,CAGhH;AAED;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAC7C,KAAK,EAAE,UAAU,EAAE,EACnB,OAAO,EAAE,qBAAqB,EAC9B,YAAY,EAAE,mBAAmB,EACjC,UAAU,GAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAoB,GAC3D,iBAAiB,CAiEnB;AAkHD,uGAAuG;AACvG,wBAAgB,aAAa,IAAI,mBAAmB,CAEnD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CAmBlE;AAsBD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAOxF;AA2cD,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASjD"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BrunoSecurityScheme } from './codegen-bruno.js';
|
|
2
|
-
import type { ContractKitPlugin } from '@contractkit/core';
|
|
2
|
+
import type { ContractKitPlugin, PluginValue } from '@contractkit/core';
|
|
3
3
|
/** Configuration accepted by the Bruno plugin, both via `contractkit.config.json` and `createBrunoPlugin`. */
|
|
4
4
|
export interface BrunoPluginConfig {
|
|
5
5
|
baseDir?: string;
|
|
@@ -33,6 +33,15 @@ export interface BrunoPluginOptions extends BrunoPluginConfig {
|
|
|
33
33
|
schemes?: Record<string, BrunoSecurityScheme>;
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Validates a `plugins.bruno` extension entry on an operation. The expected shape is
|
|
38
|
+
* `{ template?: string }`, where `template` is a YAML fragment to deep-merge into the
|
|
39
|
+
* generated request file (typically a `file://...` URL whose contents have already
|
|
40
|
+
* been loaded by the CLI resolver).
|
|
41
|
+
*/
|
|
42
|
+
export declare function validateBrunoExtension(value: PluginValue): {
|
|
43
|
+
errors?: string[];
|
|
44
|
+
} | void;
|
|
36
45
|
declare const plugin: ContractKitPlugin;
|
|
37
46
|
export default plugin;
|
|
38
47
|
/**
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAiB,WAAW,EAAqD,MAAM,mBAAmB,CAAC;AAE1I,8GAA8G;AAC9G,MAAM,WAAW,iBAAiB;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC1D;AAED,mHAAmH;AACnH,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IACzD,IAAI,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;KAAE,CAAC;CACnF;AAID;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,WAAW,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAavF;AAQD,QAAA,MAAM,MAAM,EAAE,iBAOb,CAAC;AAEF,eAAe,MAAM,CAAC;AAItB;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC7B,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;CAAE,GAChF,iBAAiB,CAUnB"}
|
package/dist/index.js
CHANGED
|
@@ -6,23 +6,24 @@ import { resolve, basename as basename2, dirname } from "path";
|
|
|
6
6
|
import { existsSync, readFileSync, rmSync, readdirSync, rmdirSync } from "fs";
|
|
7
7
|
|
|
8
8
|
// src/codegen-bruno.ts
|
|
9
|
-
import { resolveSecurity, resolveModifiers, SECURITY_NONE } from "@contractkit/core";
|
|
9
|
+
import { resolveSecurity, resolveModifiers, SECURITY_NONE, collectTransitiveModelRefs, runIncrementalCodegen, parseIncrementalManifest, emptyIncrementalManifest, hashFingerprint, INCREMENTAL_MANIFEST_VERSION } from "@contractkit/core";
|
|
10
10
|
import { basename } from "path";
|
|
11
11
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
12
12
|
var MANIFEST_FILENAME = ".contractkit-bruno-manifest.json";
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
var BRUNO_CODEGEN_VERSION = "1";
|
|
14
|
+
function generateOpenCollectionIncremental(roots, options, prevManifest, fileExists = () => true) {
|
|
15
15
|
const modelMap = buildModelMap(options.contractRoots ?? []);
|
|
16
16
|
const authOpts = options.auth;
|
|
17
17
|
const defaultScheme = authOpts?.defaultScheme ? authOpts.schemes?.[authOpts.defaultScheme] : void 0;
|
|
18
18
|
const randomExamples = options.randomExamples ?? false;
|
|
19
19
|
const includeInternal = options.includeInternal ?? true;
|
|
20
|
-
|
|
20
|
+
const globalFiles = [];
|
|
21
|
+
globalFiles.push({
|
|
21
22
|
relativePath: "opencollection.yml",
|
|
22
23
|
content: generateCollectionRoot(options.collectionName, defaultScheme)
|
|
23
24
|
});
|
|
24
25
|
for (const envFile of generateEnvFiles(options.environments, defaultScheme)) {
|
|
25
|
-
|
|
26
|
+
globalFiles.push(envFile);
|
|
26
27
|
}
|
|
27
28
|
const sortedRoots = [
|
|
28
29
|
...roots
|
|
@@ -33,11 +34,12 @@ function generateOpenCollection(roots, options) {
|
|
|
33
34
|
if (cmp !== 0) return cmp;
|
|
34
35
|
return (a.meta["subarea"] ?? "").localeCompare(b.meta["subarea"] ?? "");
|
|
35
36
|
});
|
|
37
|
+
const units = [];
|
|
36
38
|
for (let rootIdx = 0; rootIdx < sortedRoots.length; rootIdx++) {
|
|
37
39
|
const root = sortedRoots[rootIdx];
|
|
38
40
|
const folder = root.meta["area"] ? slugifyName(root.meta["area"]) : deriveFolderName(root.file);
|
|
39
41
|
const displayName = (root.meta["area"] ?? folder).charAt(0).toUpperCase() + (root.meta["area"] ?? folder).slice(1);
|
|
40
|
-
|
|
42
|
+
globalFiles.push({
|
|
41
43
|
relativePath: `${folder}/folder.yml`,
|
|
42
44
|
content: generateFolderFile(displayName, rootIdx + 1)
|
|
43
45
|
});
|
|
@@ -46,60 +48,164 @@ function generateOpenCollection(roots, options) {
|
|
|
46
48
|
const requestDir = subareaSlug ? `${folder}/${subareaSlug}` : folder;
|
|
47
49
|
if (subareaSlug) {
|
|
48
50
|
const subareaDisplayName = subarea.charAt(0).toUpperCase() + subarea.slice(1);
|
|
49
|
-
|
|
51
|
+
globalFiles.push({
|
|
50
52
|
relativePath: `${requestDir}/folder.yml`,
|
|
51
53
|
content: generateFolderFile(subareaDisplayName, 1)
|
|
52
54
|
});
|
|
53
55
|
}
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
});
|
|
63
|
-
}
|
|
56
|
+
for (const entry of buildOpEntries(root, requestDir, includeInternal)) {
|
|
57
|
+
units.push({
|
|
58
|
+
key: entry.opKey,
|
|
59
|
+
fingerprint: computeOpFingerprint(entry, modelMap, defaultScheme, randomExamples),
|
|
60
|
+
render: /* @__PURE__ */ __name(() => [
|
|
61
|
+
renderOpFile(entry, modelMap, defaultScheme, randomExamples)
|
|
62
|
+
], "render")
|
|
63
|
+
});
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
}
|
|
66
|
+
const result = runIncrementalCodegen({
|
|
67
|
+
codegenVersion: BRUNO_CODEGEN_VERSION,
|
|
68
|
+
manifestFilename: MANIFEST_FILENAME,
|
|
69
|
+
prevManifest,
|
|
70
|
+
globalFiles,
|
|
71
|
+
units,
|
|
72
|
+
fileExists
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
filesToWrite: result.filesToWrite,
|
|
76
|
+
manifest: result.manifest,
|
|
77
|
+
deletedPaths: result.deletedPaths,
|
|
78
|
+
skippedOpCount: result.skippedUnitCount
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
__name(generateOpenCollectionIncremental, "generateOpenCollectionIncremental");
|
|
82
|
+
function buildOpEntries(root, requestDir, includeInternal) {
|
|
83
|
+
const requests = [];
|
|
84
|
+
for (const route of root.routes) {
|
|
85
|
+
for (const op of route.operations) {
|
|
86
|
+
if (!includeInternal && resolveModifiers(route, op).includes("internal")) continue;
|
|
87
|
+
requests.push({
|
|
88
|
+
route,
|
|
89
|
+
op,
|
|
90
|
+
requestName: op.name ?? route.path
|
|
77
91
|
});
|
|
78
|
-
seq++;
|
|
79
92
|
}
|
|
80
93
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
requests.sort((a, b) => a.requestName.localeCompare(b.requestName));
|
|
95
|
+
const entries = [];
|
|
96
|
+
let seq = 1;
|
|
97
|
+
for (const { route, op, requestName } of requests) {
|
|
98
|
+
const fileName = op.name ? `${slugifyName(op.name)}.yml` : `${op.method}-${sanitizePath(route.path)}.yml`;
|
|
99
|
+
entries.push({
|
|
100
|
+
opKey: `${root.file}::${op.method.toUpperCase()} ${route.path}`,
|
|
101
|
+
relativePath: `${requestDir}/${fileName}`,
|
|
102
|
+
requestName,
|
|
103
|
+
seq,
|
|
104
|
+
route,
|
|
105
|
+
op,
|
|
106
|
+
root
|
|
107
|
+
});
|
|
108
|
+
seq++;
|
|
109
|
+
}
|
|
110
|
+
return entries;
|
|
111
|
+
}
|
|
112
|
+
__name(buildOpEntries, "buildOpEntries");
|
|
113
|
+
function renderOpFile(entry, modelMap, defaultScheme, randomExamples) {
|
|
114
|
+
let content = generateRequestFile(entry.route, entry.op, entry.requestName, entry.seq, modelMap, entry.root, defaultScheme, randomExamples);
|
|
115
|
+
const brunoExt = entry.op.pluginExtensions?.["bruno"];
|
|
116
|
+
const pluginOverride = brunoExt && typeof brunoExt === "object" && !Array.isArray(brunoExt) ? brunoExt["template"] : void 0;
|
|
117
|
+
if (typeof pluginOverride === "string") {
|
|
118
|
+
content = mergePluginFile(content, pluginOverride);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
relativePath: entry.relativePath,
|
|
122
|
+
content
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
__name(renderOpFile, "renderOpFile");
|
|
126
|
+
function computeOpFingerprint(entry, modelMap, defaultScheme, randomExamples) {
|
|
127
|
+
const referencedModels = collectTransitiveModelRefs(collectOpTypeNodes(entry.route, entry.op), modelMap);
|
|
128
|
+
const modelSnapshot = {};
|
|
129
|
+
for (const name of [
|
|
130
|
+
...referencedModels
|
|
131
|
+
].sort()) {
|
|
132
|
+
const m = modelMap.get(name);
|
|
133
|
+
if (m) modelSnapshot[name] = m;
|
|
134
|
+
}
|
|
135
|
+
const routeShape = {
|
|
136
|
+
path: entry.route.path,
|
|
137
|
+
params: entry.route.params ?? null,
|
|
138
|
+
modifiers: entry.route.modifiers ?? null,
|
|
139
|
+
security: entry.route.security ?? null,
|
|
140
|
+
description: entry.route.description ?? null
|
|
141
|
+
};
|
|
142
|
+
return hashFingerprint({
|
|
143
|
+
v: BRUNO_CODEGEN_VERSION,
|
|
144
|
+
opKey: entry.opKey,
|
|
145
|
+
relativePath: entry.relativePath,
|
|
146
|
+
requestName: entry.requestName,
|
|
147
|
+
seq: entry.seq,
|
|
148
|
+
route: routeShape,
|
|
149
|
+
op: entry.op,
|
|
150
|
+
rootMeta: entry.root.meta,
|
|
151
|
+
rootFile: entry.root.file,
|
|
152
|
+
defaultScheme: defaultScheme ?? null,
|
|
153
|
+
randomExamples,
|
|
154
|
+
models: modelSnapshot
|
|
90
155
|
});
|
|
91
|
-
return files;
|
|
92
156
|
}
|
|
93
|
-
__name(
|
|
157
|
+
__name(computeOpFingerprint, "computeOpFingerprint");
|
|
158
|
+
function collectOpTypeNodes(route, op) {
|
|
159
|
+
const out = [];
|
|
160
|
+
pushFromParamSource(route.params, out);
|
|
161
|
+
pushFromParamSource(op.query, out);
|
|
162
|
+
pushFromParamSource(op.headers, out);
|
|
163
|
+
if (op.request) {
|
|
164
|
+
for (const body of op.request.bodies) out.push(body.bodyType);
|
|
165
|
+
}
|
|
166
|
+
for (const resp of op.responses) {
|
|
167
|
+
if (resp.bodyType) out.push(resp.bodyType);
|
|
168
|
+
if (resp.headers) {
|
|
169
|
+
for (const h of resp.headers) out.push(h.type);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
__name(collectOpTypeNodes, "collectOpTypeNodes");
|
|
175
|
+
function pushFromParamSource(src, out) {
|
|
176
|
+
if (!src) return;
|
|
177
|
+
if (src.kind === "params") {
|
|
178
|
+
for (const n of src.nodes) out.push(n.type);
|
|
179
|
+
} else if (src.kind === "ref") {
|
|
180
|
+
out.push({
|
|
181
|
+
kind: "ref",
|
|
182
|
+
name: src.name
|
|
183
|
+
});
|
|
184
|
+
} else if (src.kind === "type") {
|
|
185
|
+
out.push(src.node);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
__name(pushFromParamSource, "pushFromParamSource");
|
|
189
|
+
function emptyManifest() {
|
|
190
|
+
return emptyIncrementalManifest(BRUNO_CODEGEN_VERSION);
|
|
191
|
+
}
|
|
192
|
+
__name(emptyManifest, "emptyManifest");
|
|
94
193
|
function parseManifest(content) {
|
|
194
|
+
const parsed = parseIncrementalManifest(content);
|
|
195
|
+
if (parsed.files.length > 0 || Object.keys(parsed.units).length > 0) return parsed;
|
|
95
196
|
try {
|
|
96
|
-
const
|
|
97
|
-
if (Array.isArray(
|
|
98
|
-
return
|
|
197
|
+
const raw = JSON.parse(content);
|
|
198
|
+
if (Array.isArray(raw?.files) && raw.files.every((f) => typeof f === "string")) {
|
|
199
|
+
return {
|
|
200
|
+
version: INCREMENTAL_MANIFEST_VERSION,
|
|
201
|
+
codegenVersion: "",
|
|
202
|
+
files: raw.files,
|
|
203
|
+
units: {}
|
|
204
|
+
};
|
|
99
205
|
}
|
|
100
206
|
} catch {
|
|
101
207
|
}
|
|
102
|
-
return
|
|
208
|
+
return emptyManifest();
|
|
103
209
|
}
|
|
104
210
|
__name(parseManifest, "parseManifest");
|
|
105
211
|
function deepMerge(base, override) {
|
|
@@ -610,64 +716,88 @@ function yamlString(value) {
|
|
|
610
716
|
__name(yamlString, "yamlString");
|
|
611
717
|
|
|
612
718
|
// src/index.ts
|
|
719
|
+
function validateBrunoExtension(value) {
|
|
720
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
721
|
+
return {
|
|
722
|
+
errors: [
|
|
723
|
+
`expected an object, got ${describe(value)}`
|
|
724
|
+
]
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
const errors = [];
|
|
728
|
+
for (const [key, val] of Object.entries(value)) {
|
|
729
|
+
if (key === "template") {
|
|
730
|
+
if (typeof val !== "string") errors.push(`'template' must be a string, got ${describe(val)}`);
|
|
731
|
+
} else {
|
|
732
|
+
errors.push(`unknown field '${key}' (allowed: template)`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return errors.length ? {
|
|
736
|
+
errors
|
|
737
|
+
} : void 0;
|
|
738
|
+
}
|
|
739
|
+
__name(validateBrunoExtension, "validateBrunoExtension");
|
|
740
|
+
function describe(value) {
|
|
741
|
+
if (value === null) return "null";
|
|
742
|
+
if (Array.isArray(value)) return "array";
|
|
743
|
+
return typeof value;
|
|
744
|
+
}
|
|
745
|
+
__name(describe, "describe");
|
|
613
746
|
var plugin = {
|
|
614
747
|
name: "bruno",
|
|
615
|
-
|
|
748
|
+
validateExtension: validateBrunoExtension,
|
|
616
749
|
async generateTargets({ opRoots, contractRoots }, ctx) {
|
|
617
750
|
const { auth, ...config } = ctx.options;
|
|
618
|
-
|
|
619
|
-
const outDir = resolve(base, config.output ?? "bruno-collection");
|
|
620
|
-
const collectionName = config.collectionName ?? basename2(ctx.rootDir);
|
|
621
|
-
cleanupTrackedFiles(outDir);
|
|
622
|
-
const files = generateOpenCollection(opRoots, {
|
|
623
|
-
collectionName,
|
|
624
|
-
contractRoots,
|
|
625
|
-
auth,
|
|
626
|
-
randomExamples: config.randomExamples ?? true,
|
|
627
|
-
includeInternal: config.includeInternal,
|
|
628
|
-
environments: config.environments
|
|
629
|
-
});
|
|
630
|
-
for (const { relativePath, content } of files) {
|
|
631
|
-
ctx.emitFile(resolve(outDir, relativePath), content);
|
|
632
|
-
}
|
|
751
|
+
await runBrunoCodegen(opRoots, contractRoots, ctx, config, auth);
|
|
633
752
|
}
|
|
634
753
|
};
|
|
635
754
|
var index_default = plugin;
|
|
636
755
|
function createBrunoPlugin(config, rootDir, auth) {
|
|
637
756
|
return {
|
|
638
757
|
name: "bruno",
|
|
639
|
-
|
|
758
|
+
validateExtension: validateBrunoExtension,
|
|
640
759
|
async generateTargets({ opRoots, contractRoots }, ctx) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const files = generateOpenCollection(opRoots, {
|
|
646
|
-
collectionName,
|
|
647
|
-
contractRoots,
|
|
648
|
-
auth,
|
|
649
|
-
randomExamples: config.randomExamples ?? true,
|
|
650
|
-
includeInternal: config.includeInternal,
|
|
651
|
-
environments: config.environments
|
|
652
|
-
});
|
|
653
|
-
for (const { relativePath, content } of files) {
|
|
654
|
-
ctx.emitFile(resolve(outDir, relativePath), content);
|
|
655
|
-
}
|
|
760
|
+
await runBrunoCodegen(opRoots, contractRoots, {
|
|
761
|
+
...ctx,
|
|
762
|
+
rootDir
|
|
763
|
+
}, config, auth);
|
|
656
764
|
}
|
|
657
765
|
};
|
|
658
766
|
}
|
|
659
767
|
__name(createBrunoPlugin, "createBrunoPlugin");
|
|
660
|
-
function
|
|
768
|
+
async function runBrunoCodegen(opRoots, contractRoots, ctx, config, auth) {
|
|
769
|
+
const base = config.baseDir ? resolve(ctx.rootDir, config.baseDir) : ctx.rootDir;
|
|
770
|
+
const outDir = resolve(base, config.output ?? "bruno-collection");
|
|
771
|
+
const collectionName = config.collectionName ?? basename2(ctx.rootDir);
|
|
772
|
+
const prevManifest = ctx.cacheEnabled ? readManifest(outDir) : emptyManifest();
|
|
773
|
+
const result = generateOpenCollectionIncremental(opRoots, {
|
|
774
|
+
collectionName,
|
|
775
|
+
contractRoots,
|
|
776
|
+
auth,
|
|
777
|
+
randomExamples: config.randomExamples ?? true,
|
|
778
|
+
includeInternal: config.includeInternal,
|
|
779
|
+
environments: config.environments
|
|
780
|
+
}, prevManifest, (relPath) => existsSync(resolve(outDir, relPath)));
|
|
781
|
+
deleteStalePaths(outDir, result.deletedPaths);
|
|
782
|
+
for (const { relativePath, content } of result.filesToWrite) {
|
|
783
|
+
ctx.emitFile(resolve(outDir, relativePath), content);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
__name(runBrunoCodegen, "runBrunoCodegen");
|
|
787
|
+
function readManifest(outDir) {
|
|
661
788
|
const manifestPath = resolve(outDir, MANIFEST_FILENAME);
|
|
662
|
-
if (!existsSync(manifestPath)) return;
|
|
663
|
-
let tracked;
|
|
789
|
+
if (!existsSync(manifestPath)) return emptyManifest();
|
|
664
790
|
try {
|
|
665
|
-
|
|
791
|
+
return parseManifest(readFileSync(manifestPath, "utf-8"));
|
|
666
792
|
} catch {
|
|
667
|
-
return;
|
|
793
|
+
return emptyManifest();
|
|
668
794
|
}
|
|
795
|
+
}
|
|
796
|
+
__name(readManifest, "readManifest");
|
|
797
|
+
function deleteStalePaths(outDir, relPaths) {
|
|
798
|
+
if (relPaths.length === 0) return;
|
|
669
799
|
const removedDirs = /* @__PURE__ */ new Set();
|
|
670
|
-
for (const rel of
|
|
800
|
+
for (const rel of relPaths) {
|
|
671
801
|
const abs = resolve(outDir, rel);
|
|
672
802
|
if (existsSync(abs)) {
|
|
673
803
|
rmSync(abs, {
|
|
@@ -692,9 +822,10 @@ function cleanupTrackedFiles(outDir) {
|
|
|
692
822
|
}
|
|
693
823
|
}
|
|
694
824
|
}
|
|
695
|
-
__name(
|
|
825
|
+
__name(deleteStalePaths, "deleteStalePaths");
|
|
696
826
|
export {
|
|
697
827
|
createBrunoPlugin,
|
|
698
|
-
index_default as default
|
|
828
|
+
index_default as default,
|
|
829
|
+
validateBrunoExtension
|
|
699
830
|
};
|
|
700
831
|
//# sourceMappingURL=index.js.map
|