@contractkit/plugin-bruno 0.9.1
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 +35 -0
- package/.turbo/turbo-build.log +15 -0
- package/.turbo/turbo-test$colon$ci.log +128 -0
- package/.turbo/turbo-test.log +14 -0
- package/CHANGELOG.md +110 -0
- package/README.md +80 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +313 -0
- package/coverage/coverage-final.json +3 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +131 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/src/codegen-bruno.ts.html +1903 -0
- package/coverage/src/index.html +116 -0
- package/coverage/tests/helpers.ts.html +604 -0
- package/coverage/tests/index.html +116 -0
- package/dist/codegen-bruno.d.ts +50 -0
- package/dist/codegen-bruno.d.ts.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +631 -0
- package/dist/index.js.map +1 -0
- package/eslint.config.js +6 -0
- package/package.json +43 -0
- package/src/codegen-bruno.ts +606 -0
- package/src/index.ts +130 -0
- package/tests/codegen-bruno.test.ts +1008 -0
- package/tests/helpers.ts +173 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +14 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { resolve, basename, dirname } from 'node:path';
|
|
2
|
+
import { existsSync, readFileSync, rmSync, readdirSync, rmdirSync } from 'node:fs';
|
|
3
|
+
import { generateOpenCollection, MANIFEST_FILENAME, parseManifest } from './codegen-bruno.js';
|
|
4
|
+
import type { BrunoSecurityScheme } from './codegen-bruno.js';
|
|
5
|
+
import type { ContractKitPlugin } from '@contractkit/core';
|
|
6
|
+
|
|
7
|
+
export interface BrunoPluginConfig {
|
|
8
|
+
baseDir?: string;
|
|
9
|
+
output?: string;
|
|
10
|
+
collectionName?: string;
|
|
11
|
+
/**
|
|
12
|
+
* When true (default), example values use Bruno's faker templates
|
|
13
|
+
* (`{{$randomUUID}}`, `{{$randomEmail}}`, etc.) so each send produces
|
|
14
|
+
* fresh data. Set to false for deterministic placeholders.
|
|
15
|
+
*/
|
|
16
|
+
randomExamples?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Whether to generate request files for operations marked `internal`. Defaults to
|
|
19
|
+
* `true` — Bruno collections are typically used by the team that owns the API and
|
|
20
|
+
* benefit from full coverage. Set to `false` to omit internal ops.
|
|
21
|
+
*/
|
|
22
|
+
includeInternal?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface BrunoPluginOptions extends BrunoPluginConfig {
|
|
26
|
+
auth?: { defaultScheme: string; schemes?: Record<string, BrunoSecurityScheme> };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ─── Default export: loaded via plugins array, reads config from ctx.options ─
|
|
30
|
+
|
|
31
|
+
const plugin: ContractKitPlugin = {
|
|
32
|
+
name: 'bruno',
|
|
33
|
+
cacheKey: 'bruno',
|
|
34
|
+
async generateTargets({ opRoots, contractRoots }, ctx) {
|
|
35
|
+
const { auth, ...config } = ctx.options as BrunoPluginOptions;
|
|
36
|
+
const base = config.baseDir ? resolve(ctx.rootDir, config.baseDir) : ctx.rootDir;
|
|
37
|
+
const outDir = resolve(base, config.output ?? 'bruno-collection');
|
|
38
|
+
const collectionName = config.collectionName ?? basename(ctx.rootDir);
|
|
39
|
+
|
|
40
|
+
cleanupTrackedFiles(outDir);
|
|
41
|
+
|
|
42
|
+
const files = generateOpenCollection(opRoots, {
|
|
43
|
+
collectionName,
|
|
44
|
+
contractRoots,
|
|
45
|
+
auth,
|
|
46
|
+
randomExamples: config.randomExamples ?? true,
|
|
47
|
+
includeInternal: config.includeInternal,
|
|
48
|
+
});
|
|
49
|
+
for (const { relativePath, content } of files) {
|
|
50
|
+
ctx.emitFile(resolve(outDir, relativePath), content);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default plugin;
|
|
56
|
+
|
|
57
|
+
// ─── Factory: for programmatic use with explicit config ────────────────────
|
|
58
|
+
|
|
59
|
+
export function createBrunoPlugin(
|
|
60
|
+
config: BrunoPluginConfig,
|
|
61
|
+
rootDir: string,
|
|
62
|
+
auth?: { defaultScheme: string; schemes?: Record<string, BrunoSecurityScheme> },
|
|
63
|
+
): ContractKitPlugin {
|
|
64
|
+
return {
|
|
65
|
+
name: 'bruno',
|
|
66
|
+
cacheKey: `bruno:${JSON.stringify(config)}`,
|
|
67
|
+
async generateTargets({ opRoots, contractRoots }, ctx) {
|
|
68
|
+
const base = config.baseDir ? resolve(rootDir, config.baseDir) : rootDir;
|
|
69
|
+
const outDir = resolve(base, config.output ?? 'bruno-collection');
|
|
70
|
+
const collectionName = config.collectionName ?? basename(rootDir);
|
|
71
|
+
|
|
72
|
+
cleanupTrackedFiles(outDir);
|
|
73
|
+
|
|
74
|
+
const files = generateOpenCollection(opRoots, {
|
|
75
|
+
collectionName,
|
|
76
|
+
contractRoots,
|
|
77
|
+
auth,
|
|
78
|
+
randomExamples: config.randomExamples ?? true,
|
|
79
|
+
});
|
|
80
|
+
for (const { relativePath, content } of files) {
|
|
81
|
+
ctx.emitFile(resolve(outDir, relativePath), content);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Delete files this plugin generated on the previous run, leaving anything
|
|
89
|
+
* the user added (custom .bru files, scripts, secrets, etc.) untouched.
|
|
90
|
+
*
|
|
91
|
+
* On first run — or after manual deletion of the manifest — nothing is
|
|
92
|
+
* removed; stale files from prior versions linger until manually cleaned.
|
|
93
|
+
*/
|
|
94
|
+
function cleanupTrackedFiles(outDir: string): void {
|
|
95
|
+
const manifestPath = resolve(outDir, MANIFEST_FILENAME);
|
|
96
|
+
if (!existsSync(manifestPath)) return;
|
|
97
|
+
|
|
98
|
+
let tracked: string[];
|
|
99
|
+
try {
|
|
100
|
+
tracked = parseManifest(readFileSync(manifestPath, 'utf-8'));
|
|
101
|
+
} catch {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const removedDirs = new Set<string>();
|
|
106
|
+
for (const rel of tracked) {
|
|
107
|
+
const abs = resolve(outDir, rel);
|
|
108
|
+
if (existsSync(abs)) {
|
|
109
|
+
rmSync(abs, { force: true });
|
|
110
|
+
removedDirs.add(dirname(abs));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Walk up from each affected directory and remove it if empty, stopping at outDir.
|
|
115
|
+
for (const dir of removedDirs) {
|
|
116
|
+
let current = dir;
|
|
117
|
+
while (current.startsWith(outDir) && current !== outDir) {
|
|
118
|
+
try {
|
|
119
|
+
if (readdirSync(current).length === 0) {
|
|
120
|
+
rmdirSync(current);
|
|
121
|
+
current = dirname(current);
|
|
122
|
+
} else {
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|