Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@contractkit/core

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@contractkit/core - npm Package Compare versions

Comparing version
0.12.0
to
0.13.0
+328
coverage/apply-variable-substitution.ts.html
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for apply-variable-substitution.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> apply-variable-substitution.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">97.56% </span>
<span class="quiet">Statements</span>
<span class='fraction'>40/41</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">94.87% </span>
<span class="quiet">Branches</span>
<span class='fraction'>37/39</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>6/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>33/33</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">97x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">97x</span>
<span class="cline-any cline-yes">40x</span>
<span class="cline-any cline-yes">40x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">97x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">349x</span>
<span class="cline-any cline-yes">319x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">309x</span>
<span class="cline-any cline-yes">309x</span>
<span class="cline-any cline-yes">39x</span>
<span class="cline-any cline-yes">270x</span>
<span class="cline-any cline-yes">57x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* Normalization pass — substitutes `{{name}}` references in every string-bearing field
* of the AST with values from `root.meta` (the file's `options { keys }` block) or, when
* absent, from a workspace-wide `fallbackKeys` map (typically merged from each plugin's
* `options.keys` in `contractkit.config.json`).
*
* Runs in the CLI between `parseCk` and `decomposeCk`, after `applyOptionsDefaults`. It
* deliberately does NOT run inside `parseCk` so the prettier plugin sees the un-substituted
* source form and can round-trip the file.
*
* Substitution rules:
* - `{{name}}` → value lookup; warns and emits the literal string `undefined` when missing.
* - `\{{name}}` → literal `{{name}}` (no substitution, no warning).
*
* `root.meta` is itself excluded from the walk — keys are not recursively substituted.
*/
import type { CkRootNode, OpRootNode, SourceLocation } from './ast.js';
import type { DiagnosticCollector } from './diagnostics.js';
&nbsp;
const SUBSTITUTION_RE = /\\\{\{(\w+)\}\}|\{\{(\w+)\}\}/g;
&nbsp;
type Root = CkRootNode | OpRootNode;
&nbsp;
export function applyVariableSubstitution(root: Root, diag: DiagnosticCollector, fallbackKeys: Record&lt;string, string&gt; = {}): void {
const file = root.file;
const meta = root.meta ?? <span class="branch-1 cbranch-no" title="branch not covered" >{};</span>
&nbsp;
const lookup = (name: string): string | undefined =&gt; {
if (Object.prototype.hasOwnProperty.call(meta, name)) return meta[name];
if (Object.prototype.hasOwnProperty.call(fallbackKeys, name)) return fallbackKeys[name];
return undefined;
};
&nbsp;
const substitute = (input: string, line: number): string =&gt; {
if (!input.includes('{{')) return input;
return input.replace(SUBSTITUTION_RE, (_match, escapedName: string | undefined, varName: string | undefined) =&gt; {
if (escapedName !== undefined) return `{{${escapedName}}}`;
const value = lookup(varName!);
if (value === undefined) {
diag.warn(file, line, `Unknown variable '{{${varName}}}'`);
return 'undefined';
}
return value;
});
};
&nbsp;
walk(root, substitute, 0, /* isRoot */ true);
}
&nbsp;
function isLoc(value: unknown): value is SourceLocation {
return typeof value === 'object' &amp;&amp; value !== null &amp;&amp; typeof (value as SourceLocation).line === 'number' &amp;&amp; typeof (value as SourceLocation).file === 'string';
}
&nbsp;
function walk(node: unknown, substitute: (s: string, line: number) =&gt; string, currentLine: number, isRoot: boolean): void {
<span class="missing-if-branch" title="if path not taken" >I</span>if (node === null || typeof node !== 'object') <span class="cstat-no" title="statement not covered" >return;</span>
&nbsp;
if (Array.isArray(node)) {
for (const item of node) walk(item, substitute, currentLine, false);
return;
}
&nbsp;
const obj = node as Record&lt;string, unknown&gt;;
&nbsp;
// Promote `loc.line` to the running context so warnings emitted while walking
// this node's descendants can attribute themselves accurately.
const ownLoc = obj['loc'];
const lineHere = isLoc(ownLoc) ? ownLoc.line : currentLine;
&nbsp;
for (const key of Object.keys(obj)) {
// Skip book-keeping fields and the substitution source itself.
if (key === 'loc' || key === 'file') continue;
if (isRoot &amp;&amp; key === 'meta') continue;
&nbsp;
const value = obj[key];
if (typeof value === 'string') {
obj[key] = substitute(value, lineHere);
} else if (value !== null &amp;&amp; typeof value === 'object') {
walk(value, substitute, lineHere, false);
}
}
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-01T18:49:05.829Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
/**
* Normalization pass — substitutes `{{name}}` references in every string-bearing field
* of the AST with values from `root.meta` (the file's `options { keys }` block) or, when
* absent, from a workspace-wide `fallbackKeys` map (typically merged from each plugin's
* `options.keys` in `contractkit.config.json`).
*
* Runs in the CLI between `parseCk` and `decomposeCk`, after `applyOptionsDefaults`. It
* deliberately does NOT run inside `parseCk` so the prettier plugin sees the un-substituted
* source form and can round-trip the file.
*
* Substitution rules:
* - `{{name}}` → value lookup; warns and emits the literal string `undefined` when missing.
* - `\{{name}}` → literal `{{name}}` (no substitution, no warning).
*
* `root.meta` is itself excluded from the walk — keys are not recursively substituted.
*/
import type { CkRootNode, OpRootNode } from './ast.js';
import type { DiagnosticCollector } from './diagnostics.js';
type Root = CkRootNode | OpRootNode;
export declare function applyVariableSubstitution(root: Root, diag: DiagnosticCollector, fallbackKeys?: Record<string, string>): void;
export {};
//# sourceMappingURL=apply-variable-substitution.d.ts.map
{"version":3,"file":"apply-variable-substitution.d.ts","sourceRoot":"","sources":["../src/apply-variable-substitution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAkB,MAAM,UAAU,CAAC;AACvE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAI5D,KAAK,IAAI,GAAG,UAAU,GAAG,UAAU,CAAC;AAEpC,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,mBAAmB,EAAE,YAAY,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,IAAI,CAwBhI"}
/**
* Normalization pass — substitutes `{{name}}` references in every string-bearing field
* of the AST with values from `root.meta` (the file's `options { keys }` block) or, when
* absent, from a workspace-wide `fallbackKeys` map (typically merged from each plugin's
* `options.keys` in `contractkit.config.json`).
*
* Runs in the CLI between `parseCk` and `decomposeCk`, after `applyOptionsDefaults`. It
* deliberately does NOT run inside `parseCk` so the prettier plugin sees the un-substituted
* source form and can round-trip the file.
*
* Substitution rules:
* - `{{name}}` → value lookup; warns and emits the literal string `undefined` when missing.
* - `\{{name}}` → literal `{{name}}` (no substitution, no warning).
*
* `root.meta` is itself excluded from the walk — keys are not recursively substituted.
*/
import type { CkRootNode, OpRootNode, SourceLocation } from './ast.js';
import type { DiagnosticCollector } from './diagnostics.js';
const SUBSTITUTION_RE = /\\\{\{(\w+)\}\}|\{\{(\w+)\}\}/g;
type Root = CkRootNode | OpRootNode;
export function applyVariableSubstitution(root: Root, diag: DiagnosticCollector, fallbackKeys: Record<string, string> = {}): void {
const file = root.file;
const meta = root.meta ?? {};
const lookup = (name: string): string | undefined => {
if (Object.prototype.hasOwnProperty.call(meta, name)) return meta[name];
if (Object.prototype.hasOwnProperty.call(fallbackKeys, name)) return fallbackKeys[name];
return undefined;
};
const substitute = (input: string, line: number): string => {
if (!input.includes('{{')) return input;
return input.replace(SUBSTITUTION_RE, (_match, escapedName: string | undefined, varName: string | undefined) => {
if (escapedName !== undefined) return `{{${escapedName}}}`;
const value = lookup(varName!);
if (value === undefined) {
diag.warn(file, line, `Unknown variable '{{${varName}}}'`);
return 'undefined';
}
return value;
});
};
walk(root, substitute, 0, /* isRoot */ true);
}
function isLoc(value: unknown): value is SourceLocation {
return typeof value === 'object' && value !== null && typeof (value as SourceLocation).line === 'number' && typeof (value as SourceLocation).file === 'string';
}
function walk(node: unknown, substitute: (s: string, line: number) => string, currentLine: number, isRoot: boolean): void {
if (node === null || typeof node !== 'object') return;
if (Array.isArray(node)) {
for (const item of node) walk(item, substitute, currentLine, false);
return;
}
const obj = node as Record<string, unknown>;
// Promote `loc.line` to the running context so warnings emitted while walking
// this node's descendants can attribute themselves accurately.
const ownLoc = obj['loc'];
const lineHere = isLoc(ownLoc) ? ownLoc.line : currentLine;
for (const key of Object.keys(obj)) {
// Skip book-keeping fields and the substitution source itself.
if (key === 'loc' || key === 'file') continue;
if (isRoot && key === 'meta') continue;
const value = obj[key];
if (typeof value === 'string') {
obj[key] = substitute(value, lineHere);
} else if (value !== null && typeof value === 'object') {
walk(value, substitute, lineHere, false);
}
}
}
import { describe, it, expect } from 'vitest';
import { parseCk } from '../src/parser.js';
import { applyVariableSubstitution } from '../src/apply-variable-substitution.js';
import { DiagnosticCollector } from '../src/diagnostics.js';
import type { CkRootNode } from '../src/ast.js';
function substitute(source: string, fallbackKeys?: Record<string, string>): { root: CkRootNode; diag: DiagnosticCollector } {
const diag = new DiagnosticCollector();
const root = parseCk(source, 'test.ck', diag);
applyVariableSubstitution(root, diag, fallbackKeys);
return { root, diag };
}
describe('applyVariableSubstitution', () => {
it('substitutes a variable in a plugin value (the canonical case)', () => {
const { root, diag } = substitute(`options {
keys: { bruno: "../../bruno" }
}
operation /auth/token: {
post: {
plugins: { bruno: "{{bruno}}/authentication/request.token.yml" }
response: { 201: }
}
}
`);
expect(diag.hasErrors()).toBe(false);
expect(diag.getAll()).toHaveLength(0);
const op = root.routes[0]!.operations[0]!;
expect(op.plugins).toEqual({ bruno: '../../bruno/authentication/request.token.yml' });
});
it('substitutes in options.services values', () => {
const { root } = substitute(`options {
keys: { mod: "src/modules" }
services: {
AuthService: "#{{mod}}/authentication/auth.service.js"
}
}
operation /x: { get: { response: { 200: } } }
`);
expect(root.services).toEqual({ AuthService: '#src/modules/authentication/auth.service.js' });
});
it('substitutes inside multiple plugin values within one operation', () => {
const { root } = substitute(`options {
keys: {
bruno: "../../bruno"
area: "auth"
}
}
operation /auth/token: {
post: {
plugins: {
bruno: "{{bruno}}/{{area}}/request.yml"
other: "static"
}
response: { 201: }
}
}
`);
const op = root.routes[0]!.operations[0]!;
expect(op.plugins).toEqual({
bruno: '../../bruno/auth/request.yml',
other: 'static',
});
});
it('treats `\\{{name}}` as a literal `{{name}}` and emits no warning', () => {
const { root, diag } = substitute(String.raw`options {
keys: { bruno: "../../bruno" }
}
operation /auth/token: {
post: {
plugins: { bruno: "\{{bruno}}/literal.yml" }
response: { 201: }
}
}
`);
expect(diag.getAll()).toHaveLength(0);
const op = root.routes[0]!.operations[0]!;
expect(op.plugins).toEqual({ bruno: '{{bruno}}/literal.yml' });
});
it('emits literal `undefined` and a warning for an unknown variable', () => {
const { root, diag } = substitute(`operation /auth/token: {
post: {
plugins: { bruno: "{{bruno}}/missing.yml" }
response: { 201: }
}
}
`);
const op = root.routes[0]!.operations[0]!;
expect(op.plugins).toEqual({ bruno: 'undefined/missing.yml' });
const warns = diag.getAll().filter(d => d.severity === 'warning');
expect(warns).toHaveLength(1);
expect(warns[0]!.file).toBe('test.ck');
expect(warns[0]!.message).toBe(`Unknown variable '{{bruno}}'`);
});
it('does not recursively substitute within `options.keys` values themselves', () => {
const { root } = substitute(`options {
keys: {
a: "{{b}}"
b: "x"
}
}
operation /x: { get: { response: { 200: } } }
`);
// `a` keeps its literal value — no recursive expansion
expect(root.meta).toEqual({ a: '{{b}}', b: 'x' });
});
it('substitutes multiple occurrences in a single string', () => {
const { root } = substitute(`options {
keys: {
a: "X"
b: "Y"
}
}
operation /x: {
get: {
plugins: { bruno: "{{a}}-{{b}}-{{a}}" }
response: { 200: }
}
}
`);
expect(root.routes[0]!.operations[0]!.plugins).toEqual({ bruno: 'X-Y-X' });
});
it('is a no-op when no variables and no keys are defined', () => {
const { diag } = substitute(`operation /x: { get: { response: { 200: } } }
`);
expect(diag.getAll()).toHaveLength(0);
});
it('uses fallbackKeys when options.keys does not define the name', () => {
const { root, diag } = substitute(
`operation /auth/token: {
post: {
plugins: { bruno: "{{bruno}}/from-fallback.yml" }
response: { 201: }
}
}
`,
{ bruno: '../../bruno' },
);
expect(diag.getAll()).toHaveLength(0);
const op = root.routes[0]!.operations[0]!;
expect(op.plugins).toEqual({ bruno: '../../bruno/from-fallback.yml' });
});
it('prefers options.keys over fallbackKeys when both define a name', () => {
const { root } = substitute(
`options {
keys: { bruno: "file-local" }
}
operation /auth/token: {
post: {
plugins: { bruno: "{{bruno}}/x.yml" }
response: { 201: }
}
}
`,
{ bruno: 'fallback' },
);
const op = root.routes[0]!.operations[0]!;
expect(op.plugins).toEqual({ bruno: 'file-local/x.yml' });
});
});
+7
-7
> @contractkit/core@0.12.0 build:ci /home/runner/work/ContractKit/ContractKit/packages/contractkit
> @contractkit/core@0.13.0 build:ci /home/runner/work/ContractKit/ContractKit/packages/contractkit
> eslint --max-warnings=0 && pnpm run build
> @contractkit/core@0.12.0 build /home/runner/work/ContractKit/ContractKit/packages/contractkit
> @contractkit/core@0.13.0 build /home/runner/work/ContractKit/ContractKit/packages/contractkit
> tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration && cp src/contractkit.ohm dist/

@@ -14,7 +14,7 @@

ESM Build start
ESM dist/index.js 80.33 KB
ESM dist/index.js.map 186.34 KB
ESM ⚡️ Build success in 107ms
ESM dist/index.js 82.30 KB
ESM dist/index.js.map 191.58 KB
ESM ⚡️ Build success in 84ms
DTS Build start
DTS ⚡️ Build success in 1278ms
DTS dist/index.d.ts 22.96 KB
DTS ⚡️ Build success in 1127ms
DTS dist/index.d.ts 23.97 KB
> @contractkit/core@0.12.0 test:ci /home/runner/work/ContractKit/ContractKit/packages/contractkit
> @contractkit/core@0.13.0 test:ci /home/runner/work/ContractKit/ContractKit/packages/contractkit
> vitest run --coverage

@@ -9,12 +9,13 @@

✓ tests/apply-options-defaults.test.ts (12 tests) 83ms
✓ tests/validate-inheritance.test.ts (16 tests) 72ms
✓ tests/validate-inheritance.test.ts (16 tests) 55ms
✓ tests/apply-options-defaults.test.ts (12 tests) 84ms
✓ tests/diagnostics.test.ts (10 tests) 14ms
✓ tests/parser-ck.test.ts (167 tests) 480ms
✓ tests/validate-discriminated.test.ts (5 tests) 30ms
✓ tests/parser-ck.test.ts (167 tests) 537ms
✓ tests/apply-variable-substitution.test.ts (10 tests) 48ms
✓ tests/validate-discriminated.test.ts (5 tests) 26ms
 Test Files  5 passed (5)
 Tests  210 passed (210)
 Start at  17:30:27
 Duration  1.66s (transform 406ms, setup 0ms, import 2.35s, tests 678ms, environment 1ms)
 Test Files  6 passed (6)
 Tests  220 passed (220)
 Start at  18:49:03
 Duration  1.89s (transform 383ms, setup 0ms, import 2.77s, tests 764ms, environment 1ms)

@@ -25,4 +26,5 @@  % Coverage report from v8

-------------------|---------|----------|---------|---------|-------------------
All files | 83.03 | 69.93 | 87.93 | 85.33 |
All files | 83.55 | 71.38 | 88.33 | 85.83 |
...ns-defaults.ts | 98 | 95.34 | 100 | 100 | 67,93
...ubstitution.ts | 97.56 | 94.87 | 100 | 100 | 26,55
ast.ts | 90 | 57.14 | 100 | 85.71 | 309

@@ -29,0 +31,0 @@ decompose.ts | 100 | 100 | 100 | 100 |

# @contractkit/core
## 0.13.0
### Minor Changes
- 7555412: Add `{{var}}` variable substitution in `.ck` files.
Variables declared in a file's `options { keys: { ... } }` block can now be referenced from any string in the file as `{{name}}`. The CLI also collects a workspace-wide fallback map from each plugin entry's `options.keys` in `contractkit.config.json`, so an author can define a key once and use it across every `.ck` file.
- `{{name}}` → resolved from `options.keys` first, then the plugin-config fallback. Unknown variables emit the literal string `undefined` and a warning (`Unknown variable '{{name}}'`).
- `\{{name}}` → escapes the substitution; the literal characters `{{name}}` are emitted with no warning.
Substitution runs as a post-parse normalization pass (after `applyOptionsDefaults`), so the prettier plugin still round-trips the source form.
Example:
```
options {
keys: { bruno: "../../bruno" }
}
operation /auth/token: {
post: {
plugins: { bruno: "{{bruno}}/authentication/request.token.yml" }
response: { 201: { application/json: AuthenticationToken } }
}
}
```
## 0.12.0

@@ -4,0 +31,0 @@

@@ -358,3 +358,3 @@

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-01T17:30:28.704Z
at 2026-05-01T18:49:05.829Z
</div>

@@ -361,0 +361,0 @@ <script src="prettify.js"></script>

@@ -417,3 +417,3 @@

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -598,3 +598,3 @@ <span class="cline-any cline-neutral">&nbsp;</span>

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -1107,3 +1107,3 @@ <span class="cline-any cline-neutral">&nbsp;</span>

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-01T17:30:28.704Z
at 2026-05-01T18:49:05.829Z
</div>

@@ -1110,0 +1110,0 @@ <script src="prettify.js"></script>

<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1777656628725" clover="3.2.0">
<project timestamp="1777656628725" name="All files">
<metrics statements="941" coveredstatements="803" conditionals="632" coveredconditionals="442" methods="174" coveredmethods="153" elements="1747" coveredelements="1398" complexity="0" loc="941" ncloc="941" packages="1" files="10" classes="10"/>
<coverage generated="1777661345847" clover="3.2.0">
<project timestamp="1777661345847" name="All files">
<metrics statements="974" coveredstatements="836" conditionals="671" coveredconditionals="479" methods="180" coveredmethods="159" elements="1825" coveredelements="1474" complexity="0" loc="974" ncloc="974" packages="1" files="11" classes="11"/>
<file name="apply-options-defaults.ts" path="/home/runner/work/ContractKit/ContractKit/packages/contractkit/src/apply-options-defaults.ts">

@@ -46,6 +46,42 @@ <metrics statements="38" coveredstatements="38" conditionals="43" coveredconditionals="41" methods="9" coveredmethods="9"/>

</file>
<file name="apply-variable-substitution.ts" path="/home/runner/work/ContractKit/ContractKit/packages/contractkit/src/apply-variable-substitution.ts">
<metrics statements="33" coveredstatements="33" conditionals="39" coveredconditionals="37" methods="6" coveredmethods="6"/>
<line num="20" count="1" type="stmt"/>
<line num="25" count="10" type="stmt"/>
<line num="26" count="10" type="cond" truecount="1" falsecount="1"/>
<line num="28" count="10" type="stmt"/>
<line num="29" count="10" type="cond" truecount="2" falsecount="0"/>
<line num="30" count="2" type="cond" truecount="2" falsecount="0"/>
<line num="31" count="1" type="stmt"/>
<line num="34" count="10" type="stmt"/>
<line num="35" count="39" type="cond" truecount="2" falsecount="0"/>
<line num="36" count="8" type="stmt"/>
<line num="37" count="11" type="cond" truecount="2" falsecount="0"/>
<line num="38" count="10" type="stmt"/>
<line num="39" count="10" type="cond" truecount="2" falsecount="0"/>
<line num="40" count="1" type="stmt"/>
<line num="41" count="1" type="stmt"/>
<line num="43" count="9" type="stmt"/>
<line num="47" count="10" type="stmt"/>
<line num="51" count="57" type="cond" truecount="4" falsecount="0"/>
<line num="55" count="97" type="cond" truecount="3" falsecount="1"/>
<line num="57" count="97" type="cond" truecount="2" falsecount="0"/>
<line num="58" count="40" type="stmt"/>
<line num="59" count="40" type="stmt"/>
<line num="62" count="57" type="stmt"/>
<line num="66" count="57" type="stmt"/>
<line num="67" count="57" type="cond" truecount="2" falsecount="0"/>
<line num="69" count="97" type="stmt"/>
<line num="71" count="349" type="cond" truecount="4" falsecount="0"/>
<line num="72" count="319" type="cond" truecount="4" falsecount="0"/>
<line num="74" count="309" type="stmt"/>
<line num="75" count="309" type="cond" truecount="2" falsecount="0"/>
<line num="76" count="39" type="stmt"/>
<line num="77" count="270" type="cond" truecount="4" falsecount="0"/>
<line num="78" count="57" type="stmt"/>
</file>
<file name="ast.ts" path="/home/runner/work/ContractKit/ContractKit/packages/contractkit/src/ast.ts">
<metrics statements="7" coveredstatements="6" conditionals="7" coveredconditionals="4" methods="3" coveredmethods="3"/>
<line num="8" count="4" type="stmt"/>
<line num="188" count="4" type="stmt"/>
<line num="8" count="5" type="stmt"/>
<line num="188" count="5" type="stmt"/>
<line num="298" count="1" type="cond" truecount="1" falsecount="2"/>

@@ -65,7 +101,7 @@ <line num="299" count="1" type="stmt"/>

<metrics statements="12" coveredstatements="12" conditionals="6" coveredconditionals="6" methods="8" coveredmethods="8"/>
<line num="9" count="226" type="stmt"/>
<line num="9" count="236" type="stmt"/>
<line num="12" count="21" type="stmt"/>
<line num="16" count="14" type="stmt"/>
<line num="20" count="42" type="stmt"/>
<line num="24" count="32" type="stmt"/>
<line num="16" count="15" type="stmt"/>
<line num="20" count="43" type="stmt"/>
<line num="24" count="37" type="stmt"/>
<line num="28" count="2" type="stmt"/>

@@ -81,16 +117,16 @@ <line num="29" count="2" type="cond" truecount="2" falsecount="0"/>

<metrics statements="8" coveredstatements="6" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
<line num="11" count="4" type="stmt"/>
<line num="12" count="4" type="stmt"/>
<line num="16" count="4" type="stmt"/>
<line num="19" count="4" type="stmt"/>
<line num="20" count="4" type="stmt"/>
<line num="11" count="5" type="stmt"/>
<line num="12" count="5" type="stmt"/>
<line num="16" count="5" type="stmt"/>
<line num="19" count="5" type="stmt"/>
<line num="20" count="5" type="stmt"/>
<line num="23" count="0" type="stmt"/>
<line num="24" count="0" type="stmt"/>
<line num="27" count="4" type="stmt"/>
<line num="27" count="5" type="stmt"/>
</file>
<file name="parser.ts" path="/home/runner/work/ContractKit/ContractKit/packages/contractkit/src/parser.ts">
<metrics statements="9" coveredstatements="9" conditionals="6" coveredconditionals="4" methods="1" coveredmethods="1"/>
<line num="10" count="4" type="stmt"/>
<line num="21" count="216" type="stmt"/>
<line num="23" count="216" type="cond" truecount="2" falsecount="0"/>
<line num="10" count="5" type="stmt"/>
<line num="21" count="226" type="stmt"/>
<line num="23" count="226" type="cond" truecount="2" falsecount="0"/>
<line num="24" count="4" type="stmt"/>

@@ -100,4 +136,4 @@ <line num="25" count="4" type="cond" truecount="1" falsecount="1"/>

<line num="27" count="4" type="stmt"/>
<line num="30" count="212" type="stmt"/>
<line num="31" count="212" type="stmt"/>
<line num="30" count="222" type="stmt"/>
<line num="31" count="222" type="stmt"/>
</file>

@@ -107,7 +143,7 @@ <file name="semantics.ts" path="/home/runner/work/ContractKit/ContractKit/packages/contractkit/src/semantics.ts">

<line num="35" count="49" type="stmt"/>
<line num="42" count="636" type="stmt"/>
<line num="43" count="636" type="stmt"/>
<line num="44" count="636" type="stmt"/>
<line num="45" count="217498" type="cond" truecount="2" falsecount="0"/>
<line num="47" count="636" type="stmt"/>
<line num="42" count="656" type="stmt"/>
<line num="43" count="656" type="stmt"/>
<line num="44" count="656" type="stmt"/>
<line num="45" count="218788" type="cond" truecount="2" falsecount="0"/>
<line num="47" count="656" type="stmt"/>
<line num="60" count="11" type="stmt"/>

@@ -121,42 +157,42 @@ <line num="61" count="11" type="stmt"/>

<line num="69" count="11" type="cond" truecount="1" falsecount="1"/>
<line num="73" count="4" type="stmt"/>
<line num="76" count="4" type="stmt"/>
<line num="80" count="212" type="stmt"/>
<line num="81" count="212" type="stmt"/>
<line num="82" count="212" type="stmt"/>
<line num="87" count="212" type="cond" truecount="2" falsecount="0"/>
<line num="88" count="21" type="stmt"/>
<line num="89" count="21" type="cond" truecount="1" falsecount="1"/>
<line num="90" count="21" type="cond" truecount="1" falsecount="1"/>
<line num="91" count="21" type="stmt"/>
<line num="92" count="21" type="stmt"/>
<line num="93" count="21" type="stmt"/>
<line num="96" count="212" type="stmt"/>
<line num="97" count="212" type="stmt"/>
<line num="99" count="212" type="stmt"/>
<line num="100" count="273" type="stmt"/>
<line num="101" count="273" type="cond" truecount="2" falsecount="0"/>
<line num="102" count="118" type="stmt"/>
<line num="73" count="5" type="stmt"/>
<line num="76" count="5" type="stmt"/>
<line num="80" count="222" type="stmt"/>
<line num="81" count="222" type="stmt"/>
<line num="82" count="222" type="stmt"/>
<line num="87" count="222" type="cond" truecount="2" falsecount="0"/>
<line num="88" count="28" type="stmt"/>
<line num="89" count="28" type="cond" truecount="1" falsecount="1"/>
<line num="90" count="28" type="cond" truecount="1" falsecount="1"/>
<line num="91" count="28" type="stmt"/>
<line num="92" count="28" type="stmt"/>
<line num="93" count="28" type="stmt"/>
<line num="96" count="222" type="stmt"/>
<line num="97" count="222" type="stmt"/>
<line num="99" count="222" type="stmt"/>
<line num="100" count="283" type="stmt"/>
<line num="101" count="283" type="cond" truecount="2" falsecount="0"/>
<line num="102" count="128" type="stmt"/>
<line num="103" count="155" type="cond" truecount="1" falsecount="1"/>
<line num="104" count="155" type="stmt"/>
<line num="108" count="212" type="stmt"/>
<line num="109" count="212" type="cond" truecount="2" falsecount="0"/>
<line num="110" count="212" type="cond" truecount="2" falsecount="0"/>
<line num="111" count="212" type="stmt"/>
<line num="115" count="273" type="stmt"/>
<line num="116" count="273" type="stmt"/>
<line num="117" count="273" type="stmt"/>
<line num="118" count="273" type="cond" truecount="2" falsecount="0"/>
<line num="119" count="118" type="stmt"/>
<line num="108" count="222" type="stmt"/>
<line num="109" count="222" type="cond" truecount="2" falsecount="0"/>
<line num="110" count="222" type="cond" truecount="2" falsecount="0"/>
<line num="111" count="222" type="stmt"/>
<line num="115" count="283" type="stmt"/>
<line num="116" count="283" type="stmt"/>
<line num="117" count="283" type="stmt"/>
<line num="118" count="283" type="cond" truecount="2" falsecount="0"/>
<line num="119" count="128" type="stmt"/>
<line num="122" count="155" type="stmt"/>
<line num="129" count="21" type="stmt"/>
<line num="130" count="21" type="stmt"/>
<line num="131" count="21" type="stmt"/>
<line num="132" count="21" type="stmt"/>
<line num="136" count="21" type="stmt"/>
<line num="137" count="21" type="stmt"/>
<line num="138" count="21" type="stmt"/>
<line num="139" count="21" type="cond" truecount="2" falsecount="0"/>
<line num="129" count="28" type="stmt"/>
<line num="130" count="28" type="stmt"/>
<line num="131" count="28" type="stmt"/>
<line num="132" count="28" type="stmt"/>
<line num="136" count="28" type="stmt"/>
<line num="137" count="29" type="stmt"/>
<line num="138" count="29" type="stmt"/>
<line num="139" count="29" type="cond" truecount="2" falsecount="0"/>
<line num="140" count="1" type="stmt"/>
<line num="141" count="20" type="cond" truecount="2" falsecount="0"/>
<line num="141" count="28" type="cond" truecount="2" falsecount="0"/>
<line num="142" count="7" type="cond" truecount="1" falsecount="1"/>

@@ -166,3 +202,3 @@ <line num="143" count="0" type="stmt"/>

<line num="146" count="7" type="stmt"/>
<line num="147" count="13" type="cond" truecount="2" falsecount="0"/>
<line num="147" count="21" type="cond" truecount="2" falsecount="0"/>
<line num="148" count="4" type="cond" truecount="1" falsecount="1"/>

@@ -172,20 +208,20 @@ <line num="149" count="0" type="stmt"/>

<line num="152" count="4" type="stmt"/>
<line num="153" count="9" type="cond" truecount="1" falsecount="1"/>
<line num="154" count="9" type="stmt"/>
<line num="155" count="15" type="cond" truecount="2" falsecount="0"/>
<line num="156" count="7" type="cond" truecount="1" falsecount="1"/>
<line num="160" count="21" type="stmt"/>
<line num="164" count="21" type="stmt"/>
<line num="168" count="5" type="stmt"/>
<line num="169" count="5" type="stmt"/>
<line num="170" count="9" type="stmt"/>
<line num="171" count="9" type="cond" truecount="2" falsecount="0"/>
<line num="172" count="8" type="stmt"/>
<line num="174" count="5" type="stmt"/>
<line num="178" count="4" type="stmt"/>
<line num="179" count="4" type="stmt"/>
<line num="180" count="7" type="stmt"/>
<line num="181" count="7" type="cond" truecount="1" falsecount="1"/>
<line num="182" count="7" type="stmt"/>
<line num="184" count="4" type="stmt"/>
<line num="153" count="17" type="cond" truecount="1" falsecount="1"/>
<line num="154" count="17" type="stmt"/>
<line num="155" count="26" type="cond" truecount="2" falsecount="0"/>
<line num="156" count="8" type="cond" truecount="1" falsecount="1"/>
<line num="160" count="28" type="stmt"/>
<line num="164" count="29" type="stmt"/>
<line num="168" count="12" type="stmt"/>
<line num="169" count="12" type="stmt"/>
<line num="170" count="19" type="stmt"/>
<line num="171" count="19" type="cond" truecount="2" falsecount="0"/>
<line num="172" count="18" type="stmt"/>
<line num="174" count="12" type="stmt"/>
<line num="178" count="5" type="stmt"/>
<line num="179" count="5" type="stmt"/>
<line num="180" count="8" type="stmt"/>
<line num="181" count="8" type="cond" truecount="1" falsecount="1"/>
<line num="182" count="8" type="stmt"/>
<line num="184" count="5" type="stmt"/>
<line num="188" count="7" type="stmt"/>

@@ -210,6 +246,6 @@ <line num="189" count="7" type="stmt"/>

<line num="214" count="11" type="stmt"/>
<line num="218" count="15" type="stmt"/>
<line num="219" count="15" type="stmt"/>
<line num="220" count="15" type="stmt"/>
<line num="224" count="6" type="stmt"/>
<line num="218" count="26" type="stmt"/>
<line num="219" count="26" type="stmt"/>
<line num="220" count="26" type="stmt"/>
<line num="224" count="17" type="stmt"/>
<line num="228" count="9" type="stmt"/>

@@ -396,47 +432,47 @@ <line num="234" count="155" type="stmt"/>

<line num="604" count="14" type="stmt"/>
<line num="611" count="118" type="stmt"/>
<line num="612" count="118" type="stmt"/>
<line num="614" count="118" type="stmt"/>
<line num="615" count="118" type="stmt"/>
<line num="611" count="128" type="stmt"/>
<line num="612" count="128" type="stmt"/>
<line num="614" count="128" type="stmt"/>
<line num="615" count="128" type="stmt"/>
<line num="616" count="4" type="stmt"/>
<line num="618" count="118" type="cond" truecount="2" falsecount="0"/>
<line num="620" count="118" type="stmt"/>
<line num="621" count="118" type="stmt"/>
<line num="623" count="118" type="stmt"/>
<line num="624" count="118" type="stmt"/>
<line num="625" count="118" type="cond" truecount="2" falsecount="0"/>
<line num="627" count="118" type="stmt"/>
<line num="634" count="118" type="stmt"/>
<line num="647" count="118" type="stmt"/>
<line num="648" count="118" type="stmt"/>
<line num="649" count="158" type="stmt"/>
<line num="651" count="118" type="stmt"/>
<line num="618" count="128" type="cond" truecount="2" falsecount="0"/>
<line num="620" count="128" type="stmt"/>
<line num="621" count="128" type="stmt"/>
<line num="623" count="128" type="stmt"/>
<line num="624" count="128" type="stmt"/>
<line num="625" count="128" type="cond" truecount="2" falsecount="0"/>
<line num="627" count="128" type="stmt"/>
<line num="634" count="128" type="stmt"/>
<line num="647" count="128" type="stmt"/>
<line num="648" count="128" type="stmt"/>
<line num="649" count="174" type="stmt"/>
<line num="651" count="128" type="stmt"/>
<line num="655" count="23" type="stmt"/>
<line num="659" count="135" type="stmt"/>
<line num="665" count="118" type="stmt"/>
<line num="666" count="118" type="stmt"/>
<line num="670" count="118" type="stmt"/>
<line num="673" count="118" type="stmt"/>
<line num="674" count="145" type="stmt"/>
<line num="675" count="145" type="stmt"/>
<line num="676" count="145" type="cond" truecount="3" falsecount="1"/>
<line num="677" count="145" type="cond" truecount="2" falsecount="0"/>
<line num="659" count="151" type="stmt"/>
<line num="665" count="128" type="stmt"/>
<line num="666" count="128" type="stmt"/>
<line num="670" count="128" type="stmt"/>
<line num="673" count="128" type="stmt"/>
<line num="674" count="155" type="stmt"/>
<line num="675" count="155" type="stmt"/>
<line num="676" count="155" type="cond" truecount="3" falsecount="1"/>
<line num="677" count="155" type="cond" truecount="2" falsecount="0"/>
<line num="678" count="1" type="stmt"/>
<line num="679" count="1" type="cond" truecount="1" falsecount="1"/>
<line num="680" count="1" type="stmt"/>
<line num="682" count="144" type="cond" truecount="2" falsecount="0"/>
<line num="682" count="154" type="cond" truecount="2" falsecount="0"/>
<line num="683" count="14" type="stmt"/>
<line num="684" count="14" type="stmt"/>
<line num="685" count="130" type="cond" truecount="2" falsecount="0"/>
<line num="685" count="140" type="cond" truecount="2" falsecount="0"/>
<line num="686" count="4" type="stmt"/>
<line num="687" count="126" type="cond" truecount="1" falsecount="1"/>
<line num="688" count="126" type="stmt"/>
<line num="689" count="126" type="cond" truecount="4" falsecount="0"/>
<line num="687" count="136" type="cond" truecount="1" falsecount="1"/>
<line num="688" count="136" type="stmt"/>
<line num="689" count="136" type="cond" truecount="4" falsecount="0"/>
<line num="690" count="1" type="stmt"/>
<line num="692" count="126" type="stmt"/>
<line num="694" count="144" type="stmt"/>
<line num="697" count="118" type="stmt"/>
<line num="701" count="145" type="cond" truecount="2" falsecount="0"/>
<line num="692" count="136" type="stmt"/>
<line num="694" count="154" type="stmt"/>
<line num="697" count="128" type="stmt"/>
<line num="701" count="155" type="cond" truecount="2" falsecount="0"/>
<line num="702" count="1" type="stmt"/>
<line num="704" count="144" type="stmt"/>
<line num="704" count="154" type="stmt"/>
<line num="711" count="14" type="cond" truecount="2" falsecount="0"/>

@@ -466,24 +502,24 @@ <line num="712" count="1" type="stmt"/>

<line num="750" count="14" type="stmt"/>
<line num="763" count="126" type="stmt"/>
<line num="764" count="126" type="stmt"/>
<line num="766" count="126" type="stmt"/>
<line num="767" count="126" type="stmt"/>
<line num="763" count="136" type="stmt"/>
<line num="764" count="136" type="stmt"/>
<line num="766" count="136" type="stmt"/>
<line num="767" count="136" type="stmt"/>
<line num="768" count="0" type="stmt"/>
<line num="771" count="126" type="cond" truecount="2" falsecount="0"/>
<line num="775" count="126" type="cond" truecount="3" falsecount="1"/>
<line num="777" count="126" type="stmt"/>
<line num="778" count="126" type="stmt"/>
<line num="779" count="126" type="stmt"/>
<line num="780" count="126" type="stmt"/>
<line num="782" count="126" type="cond" truecount="2" falsecount="0"/>
<line num="784" count="126" type="stmt"/>
<line num="801" count="126" type="stmt"/>
<line num="809" count="126" type="stmt"/>
<line num="815" count="126" type="stmt"/>
<line num="816" count="126" type="stmt"/>
<line num="828" count="126" type="stmt"/>
<line num="832" count="126" type="stmt"/>
<line num="833" count="141" type="stmt"/>
<line num="834" count="141" type="cond" truecount="1" falsecount="1"/>
<line num="835" count="141" type="cond" truecount="10" falsecount="0"/>
<line num="771" count="136" type="cond" truecount="2" falsecount="0"/>
<line num="775" count="136" type="cond" truecount="3" falsecount="1"/>
<line num="777" count="136" type="stmt"/>
<line num="778" count="136" type="stmt"/>
<line num="779" count="136" type="stmt"/>
<line num="780" count="136" type="stmt"/>
<line num="782" count="136" type="cond" truecount="2" falsecount="0"/>
<line num="784" count="136" type="stmt"/>
<line num="801" count="136" type="stmt"/>
<line num="809" count="136" type="stmt"/>
<line num="815" count="136" type="stmt"/>
<line num="816" count="136" type="stmt"/>
<line num="828" count="136" type="stmt"/>
<line num="832" count="136" type="stmt"/>
<line num="833" count="158" type="stmt"/>
<line num="834" count="158" type="cond" truecount="1" falsecount="1"/>
<line num="835" count="158" type="cond" truecount="10" falsecount="0"/>
<line num="837" count="3" type="stmt"/>

@@ -508,11 +544,11 @@ <line num="838" count="3" type="stmt"/>

<line num="863" count="15" type="stmt"/>
<line num="865" count="45" type="stmt"/>
<line num="866" count="45" type="stmt"/>
<line num="865" count="55" type="stmt"/>
<line num="866" count="55" type="stmt"/>
<line num="868" count="8" type="stmt"/>
<line num="869" count="8" type="stmt"/>
<line num="871" count="4" type="stmt"/>
<line num="872" count="4" type="stmt"/>
<line num="876" count="126" type="stmt"/>
<line num="880" count="141" type="cond" truecount="1" falsecount="1"/>
<line num="881" count="141" type="stmt"/>
<line num="871" count="11" type="stmt"/>
<line num="872" count="11" type="stmt"/>
<line num="876" count="136" type="stmt"/>
<line num="880" count="158" type="cond" truecount="1" falsecount="1"/>
<line num="881" count="158" type="stmt"/>
<line num="887" count="3" type="stmt"/>

@@ -552,15 +588,15 @@ <line num="891" count="24" type="stmt"/>

<line num="952" count="15" type="stmt"/>
<line num="956" count="45" type="stmt"/>
<line num="957" count="45" type="stmt"/>
<line num="958" count="69" type="stmt"/>
<line num="959" count="69" type="cond" truecount="1" falsecount="1"/>
<line num="960" count="69" type="stmt"/>
<line num="962" count="45" type="stmt"/>
<line num="969" count="69" type="stmt"/>
<line num="970" count="69" type="stmt"/>
<line num="971" count="69" type="stmt"/>
<line num="976" count="69" type="stmt"/>
<line num="980" count="69" type="stmt"/>
<line num="981" count="69" type="cond" truecount="2" falsecount="0"/>
<line num="982" count="69" type="cond" truecount="2" falsecount="0"/>
<line num="956" count="55" type="stmt"/>
<line num="957" count="55" type="stmt"/>
<line num="958" count="79" type="stmt"/>
<line num="959" count="79" type="cond" truecount="1" falsecount="1"/>
<line num="960" count="79" type="stmt"/>
<line num="962" count="55" type="stmt"/>
<line num="969" count="79" type="stmt"/>
<line num="970" count="79" type="stmt"/>
<line num="971" count="79" type="stmt"/>
<line num="976" count="79" type="stmt"/>
<line num="980" count="79" type="stmt"/>
<line num="981" count="79" type="cond" truecount="2" falsecount="0"/>
<line num="982" count="79" type="cond" truecount="2" falsecount="0"/>
<line num="983" count="40" type="stmt"/>

@@ -583,8 +619,8 @@ <line num="984" count="40" type="stmt"/>

<line num="1003" count="32" type="stmt"/>
<line num="1007" count="69" type="stmt"/>
<line num="1008" count="69" type="cond" truecount="2" falsecount="0"/>
<line num="1009" count="69" type="cond" truecount="2" falsecount="0"/>
<line num="1010" count="69" type="cond" truecount="2" falsecount="0"/>
<line num="1011" count="69" type="cond" truecount="2" falsecount="0"/>
<line num="1012" count="69" type="stmt"/>
<line num="1007" count="79" type="stmt"/>
<line num="1008" count="79" type="cond" truecount="2" falsecount="0"/>
<line num="1009" count="79" type="cond" truecount="2" falsecount="0"/>
<line num="1010" count="79" type="cond" truecount="2" falsecount="0"/>
<line num="1011" count="79" type="cond" truecount="2" falsecount="0"/>
<line num="1012" count="79" type="stmt"/>
<line num="1016" count="40" type="cond" truecount="1" falsecount="1"/>

@@ -642,11 +678,11 @@ <line num="1017" count="40" type="stmt"/>

<line num="1112" count="1" type="stmt"/>
<line num="1118" count="4" type="stmt"/>
<line num="1119" count="4" type="stmt"/>
<line num="1120" count="4" type="stmt"/>
<line num="1121" count="4" type="stmt"/>
<line num="1123" count="4" type="stmt"/>
<line num="1127" count="4" type="stmt"/>
<line num="1128" count="4" type="stmt"/>
<line num="1129" count="4" type="cond" truecount="2" falsecount="2"/>
<line num="1130" count="4" type="stmt"/>
<line num="1118" count="11" type="stmt"/>
<line num="1119" count="11" type="stmt"/>
<line num="1120" count="12" type="stmt"/>
<line num="1121" count="12" type="stmt"/>
<line num="1123" count="11" type="stmt"/>
<line num="1127" count="12" type="stmt"/>
<line num="1128" count="12" type="stmt"/>
<line num="1129" count="12" type="cond" truecount="2" falsecount="2"/>
<line num="1130" count="12" type="stmt"/>
<line num="1136" count="49" type="stmt"/>

@@ -665,9 +701,9 @@ <line num="1137" count="49" type="stmt"/>

<line num="1170" count="0" type="stmt"/>
<line num="1174" count="4" type="stmt"/>
<line num="1174" count="5" type="stmt"/>
</file>
<file name="type-builders.ts" path="/home/runner/work/ContractKit/ContractKit/packages/contractkit/src/type-builders.ts">
<metrics statements="91" coveredstatements="78" conditionals="129" coveredconditionals="84" methods="20" coveredmethods="20"/>
<line num="8" count="4" type="stmt"/>
<line num="9" count="4" type="stmt"/>
<line num="10" count="4" type="stmt"/>
<line num="8" count="5" type="stmt"/>
<line num="9" count="5" type="stmt"/>
<line num="10" count="5" type="stmt"/>
<line num="22" count="310" type="cond" truecount="2" falsecount="0"/>

@@ -674,0 +710,0 @@ <line num="23" count="212" type="stmt"/>

@@ -145,3 +145,3 @@

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-01T17:30:28.704Z
at 2026-05-01T18:49:05.829Z
</div>

@@ -148,0 +148,0 @@ <script src="prettify.js"></script>

@@ -112,3 +112,3 @@

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">226x</span>
<span class="cline-any cline-yes">236x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -120,11 +120,11 @@ <span class="cline-any cline-neutral">&nbsp;</span>

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">42x</span>
<span class="cline-any cline-yes">43x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">32x</span>
<span class="cline-any cline-yes">37x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -189,3 +189,3 @@ <span class="cline-any cline-neutral">&nbsp;</span>

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-01T17:30:28.704Z
at 2026-05-01T18:49:05.829Z
</div>

@@ -192,0 +192,0 @@ <script src="prettify.js"></script>

@@ -103,12 +103,12 @@

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -120,3 +120,3 @@ <span class="cline-any cline-neutral">&nbsp;</span>

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**

@@ -156,3 +156,3 @@ * Grammar loader — compiles the Ohm grammar and exports the grammar object.

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-01T17:30:28.704Z
at 2026-05-01T18:49:05.829Z
</div>

@@ -159,0 +159,0 @@ <script src="prettify.js"></script>

@@ -26,5 +26,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">83.03% </span>
<span class="strong">83.55% </span>
<span class="quiet">Statements</span>
<span class='fraction'>915/1102</span>
<span class='fraction'>955/1143</span>
</div>

@@ -34,5 +34,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">69.93% </span>
<span class="strong">71.38% </span>
<span class="quiet">Branches</span>
<span class='fraction'>442/632</span>
<span class='fraction'>479/671</span>
</div>

@@ -42,5 +42,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">87.93% </span>
<span class="strong">88.33% </span>
<span class="quiet">Functions</span>
<span class='fraction'>153/174</span>
<span class='fraction'>159/180</span>
</div>

@@ -50,5 +50,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">85.33% </span>
<span class="strong">85.83% </span>
<span class="quiet">Lines</span>
<span class='fraction'>803/941</span>
<span class='fraction'>836/974</span>
</div>

@@ -101,2 +101,17 @@

<tr>
<td class="file high" data-value="apply-variable-substitution.ts"><a href="apply-variable-substitution.ts.html">apply-variable-substitution.ts</a></td>
<td data-value="97.56" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 97%"></div><div class="cover-empty" style="width: 3%"></div></div>
</td>
<td data-value="97.56" class="pct high">97.56%</td>
<td data-value="41" class="abs high">40/41</td>
<td data-value="94.87" class="pct high">94.87%</td>
<td data-value="39" class="abs high">37/39</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="6" class="abs high">6/6</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="33" class="abs high">33/33</td>
</tr>
<tr>
<td class="file high" data-value="ast.ts"><a href="ast.ts.html">ast.ts</a></td>

@@ -244,3 +259,3 @@ <td data-value="90" class="pic high">

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-01T17:30:28.704Z
at 2026-05-01T18:49:05.829Z
</div>

@@ -247,0 +262,0 @@ <script src="prettify.js"></script>

@@ -107,3 +107,3 @@

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -119,5 +119,5 @@ <span class="cline-any cline-neutral">&nbsp;</span>

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">216x</span>
<span class="cline-any cline-yes">226x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">216x</span>
<span class="cline-any cline-yes">226x</span>
<span class="cline-any cline-yes">4x</span>

@@ -129,4 +129,4 @@ <span class="cline-any cline-yes">4x</span>

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">212x</span>
<span class="cline-any cline-yes">212x</span>
<span class="cline-any cline-yes">222x</span>
<span class="cline-any cline-yes">222x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -172,3 +172,3 @@ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-01T17:30:28.704Z
at 2026-05-01T18:49:05.829Z
</div>

@@ -175,0 +175,0 @@ <script src="prettify.js"></script>

@@ -271,5 +271,5 @@

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -668,3 +668,3 @@ <span class="cline-any cline-neutral">&nbsp;</span>

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-01T17:30:28.704Z
at 2026-05-01T18:49:05.829Z
</div>

@@ -671,0 +671,0 @@ <script src="prettify.js"></script>

@@ -763,3 +763,3 @@

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-01T17:30:28.704Z
at 2026-05-01T18:49:05.829Z
</div>

@@ -766,0 +766,0 @@ <script src="prettify.js"></script>

@@ -718,3 +718,3 @@

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-01T17:30:28.704Z
at 2026-05-01T18:49:05.829Z
</div>

@@ -721,0 +721,0 @@ <script src="prettify.js"></script>

@@ -11,2 +11,3 @@ export * from './ast.js';

export * from './apply-options-defaults.js';
export * from './apply-variable-substitution.js';
export * from './validate-refs.js';

@@ -13,0 +14,0 @@ export * from './plugin.js';

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC"}
{
"name": "@contractkit/core",
"version": "0.12.0",
"version": "0.13.0",
"description": "Core DSL compiler library: grammar-driven parser, codegen, and validation",

@@ -5,0 +5,0 @@ "author": {

@@ -40,2 +40,3 @@ # @contractkit/core

| `applyOptionsDefaults(root)` | Normalization pass that merges file-level `options { request/response: { headers } }` into each operation. Run after `parseCk`, before downstream consumers. |
| `applyVariableSubstitution(root, diag, fallbackKeys?)` | Normalization pass that expands `{{name}}` references in every string field of the AST using `root.meta` first, then the optional `fallbackKeys` map. Run after `applyOptionsDefaults`. |
| `validateRefs(roots)` | Cross-file type-reference validation. Warns when a model is referenced but not declared anywhere. |

@@ -42,0 +43,0 @@ | `validateInheritance(roots)` | Multi-base inheritance validation — cross-base conflicts, `override` requirement, cycle detection. |

@@ -11,4 +11,5 @@ export * from './ast.js';

export * from './apply-options-defaults.js';
export * from './apply-variable-substitution.js';
export * from './validate-refs.js';
export * from './plugin.js';
export * from './content-type.js';

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display