@contractkit/core
Advanced tools
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <title>Code coverage report for incremental.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> incremental.ts</h1> | ||
| <div class='clearfix'> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">98.48% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>65/66</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">92.85% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>39/42</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Functions</span> | ||
| <span class='fraction'>10/10</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>55/55</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> | ||
| <a name='L83'></a><a href='#L83'>83</a> | ||
| <a name='L84'></a><a href='#L84'>84</a> | ||
| <a name='L85'></a><a href='#L85'>85</a> | ||
| <a name='L86'></a><a href='#L86'>86</a> | ||
| <a name='L87'></a><a href='#L87'>87</a> | ||
| <a name='L88'></a><a href='#L88'>88</a> | ||
| <a name='L89'></a><a href='#L89'>89</a> | ||
| <a name='L90'></a><a href='#L90'>90</a> | ||
| <a name='L91'></a><a href='#L91'>91</a> | ||
| <a name='L92'></a><a href='#L92'>92</a> | ||
| <a name='L93'></a><a href='#L93'>93</a> | ||
| <a name='L94'></a><a href='#L94'>94</a> | ||
| <a name='L95'></a><a href='#L95'>95</a> | ||
| <a name='L96'></a><a href='#L96'>96</a> | ||
| <a name='L97'></a><a href='#L97'>97</a> | ||
| <a name='L98'></a><a href='#L98'>98</a> | ||
| <a name='L99'></a><a href='#L99'>99</a> | ||
| <a name='L100'></a><a href='#L100'>100</a> | ||
| <a name='L101'></a><a href='#L101'>101</a> | ||
| <a name='L102'></a><a href='#L102'>102</a> | ||
| <a name='L103'></a><a href='#L103'>103</a> | ||
| <a name='L104'></a><a href='#L104'>104</a> | ||
| <a name='L105'></a><a href='#L105'>105</a> | ||
| <a name='L106'></a><a href='#L106'>106</a> | ||
| <a name='L107'></a><a href='#L107'>107</a> | ||
| <a name='L108'></a><a href='#L108'>108</a> | ||
| <a name='L109'></a><a href='#L109'>109</a> | ||
| <a name='L110'></a><a href='#L110'>110</a> | ||
| <a name='L111'></a><a href='#L111'>111</a> | ||
| <a name='L112'></a><a href='#L112'>112</a> | ||
| <a name='L113'></a><a href='#L113'>113</a> | ||
| <a name='L114'></a><a href='#L114'>114</a> | ||
| <a name='L115'></a><a href='#L115'>115</a> | ||
| <a name='L116'></a><a href='#L116'>116</a> | ||
| <a name='L117'></a><a href='#L117'>117</a> | ||
| <a name='L118'></a><a href='#L118'>118</a> | ||
| <a name='L119'></a><a href='#L119'>119</a> | ||
| <a name='L120'></a><a href='#L120'>120</a> | ||
| <a name='L121'></a><a href='#L121'>121</a> | ||
| <a name='L122'></a><a href='#L122'>122</a> | ||
| <a name='L123'></a><a href='#L123'>123</a> | ||
| <a name='L124'></a><a href='#L124'>124</a> | ||
| <a name='L125'></a><a href='#L125'>125</a> | ||
| <a name='L126'></a><a href='#L126'>126</a> | ||
| <a name='L127'></a><a href='#L127'>127</a> | ||
| <a name='L128'></a><a href='#L128'>128</a> | ||
| <a name='L129'></a><a href='#L129'>129</a> | ||
| <a name='L130'></a><a href='#L130'>130</a> | ||
| <a name='L131'></a><a href='#L131'>131</a> | ||
| <a name='L132'></a><a href='#L132'>132</a> | ||
| <a name='L133'></a><a href='#L133'>133</a> | ||
| <a name='L134'></a><a href='#L134'>134</a> | ||
| <a name='L135'></a><a href='#L135'>135</a> | ||
| <a name='L136'></a><a href='#L136'>136</a> | ||
| <a name='L137'></a><a href='#L137'>137</a> | ||
| <a name='L138'></a><a href='#L138'>138</a> | ||
| <a name='L139'></a><a href='#L139'>139</a> | ||
| <a name='L140'></a><a href='#L140'>140</a> | ||
| <a name='L141'></a><a href='#L141'>141</a> | ||
| <a name='L142'></a><a href='#L142'>142</a> | ||
| <a name='L143'></a><a href='#L143'>143</a> | ||
| <a name='L144'></a><a href='#L144'>144</a> | ||
| <a name='L145'></a><a href='#L145'>145</a> | ||
| <a name='L146'></a><a href='#L146'>146</a> | ||
| <a name='L147'></a><a href='#L147'>147</a> | ||
| <a name='L148'></a><a href='#L148'>148</a> | ||
| <a name='L149'></a><a href='#L149'>149</a> | ||
| <a name='L150'></a><a href='#L150'>150</a> | ||
| <a name='L151'></a><a href='#L151'>151</a> | ||
| <a name='L152'></a><a href='#L152'>152</a> | ||
| <a name='L153'></a><a href='#L153'>153</a> | ||
| <a name='L154'></a><a href='#L154'>154</a> | ||
| <a name='L155'></a><a href='#L155'>155</a> | ||
| <a name='L156'></a><a href='#L156'>156</a> | ||
| <a name='L157'></a><a href='#L157'>157</a> | ||
| <a name='L158'></a><a href='#L158'>158</a> | ||
| <a name='L159'></a><a href='#L159'>159</a> | ||
| <a name='L160'></a><a href='#L160'>160</a> | ||
| <a name='L161'></a><a href='#L161'>161</a> | ||
| <a name='L162'></a><a href='#L162'>162</a> | ||
| <a name='L163'></a><a href='#L163'>163</a> | ||
| <a name='L164'></a><a href='#L164'>164</a> | ||
| <a name='L165'></a><a href='#L165'>165</a> | ||
| <a name='L166'></a><a href='#L166'>166</a> | ||
| <a name='L167'></a><a href='#L167'>167</a> | ||
| <a name='L168'></a><a href='#L168'>168</a> | ||
| <a name='L169'></a><a href='#L169'>169</a> | ||
| <a name='L170'></a><a href='#L170'>170</a> | ||
| <a name='L171'></a><a href='#L171'>171</a> | ||
| <a name='L172'></a><a href='#L172'>172</a> | ||
| <a name='L173'></a><a href='#L173'>173</a> | ||
| <a name='L174'></a><a href='#L174'>174</a> | ||
| <a name='L175'></a><a href='#L175'>175</a> | ||
| <a name='L176'></a><a href='#L176'>176</a> | ||
| <a name='L177'></a><a href='#L177'>177</a> | ||
| <a name='L178'></a><a href='#L178'>178</a> | ||
| <a name='L179'></a><a href='#L179'>179</a> | ||
| <a name='L180'></a><a href='#L180'>180</a> | ||
| <a name='L181'></a><a href='#L181'>181</a> | ||
| <a name='L182'></a><a href='#L182'>182</a> | ||
| <a name='L183'></a><a href='#L183'>183</a> | ||
| <a name='L184'></a><a href='#L184'>184</a> | ||
| <a name='L185'></a><a href='#L185'>185</a> | ||
| <a name='L186'></a><a href='#L186'>186</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">9x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </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">1x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">2x</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">2x</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">4x</span> | ||
| <span class="cline-any cline-yes">4x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">4x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">22x</span> | ||
| <span class="cline-any cline-yes">7x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">10x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import { createHash } from 'node:crypto'; | ||
| | ||
| /** | ||
| * Schema version for the on-disk manifest. Bumped if the manifest shape itself changes. | ||
| * Plugin codegen changes are tracked separately via `codegenVersion`. | ||
| */ | ||
| export const INCREMENTAL_MANIFEST_VERSION = 2; | ||
| | ||
| /** A single output file produced by a plugin: relative path within the plugin's output dir + content. */ | ||
| export interface IncrementalOutputFile { | ||
| relativePath: string; | ||
| content: string; | ||
| } | ||
| | ||
| /** Cache record for one plugin "unit" — the smallest thing a plugin can decide to regenerate or reuse. */ | ||
| export interface IncrementalUnitRecord { | ||
| /** Hash covering every input that affects this unit's output. */ | ||
| fingerprint: string; | ||
| /** Relative paths this unit emitted last time. Used to verify on-disk presence on cache hit, and to detect file removals. */ | ||
| files: string[]; | ||
| } | ||
| | ||
| /** | ||
| * Persistent on-disk record describing what a plugin produced last run. Plugins write | ||
| * one of these per output dir. The `version` and `codegenVersion` fields together | ||
| * decide whether the cache can be honored at all on the current run. | ||
| */ | ||
| export interface IncrementalManifest { | ||
| version: number; | ||
| /** Plugin-defined version string. Bump in the plugin code to force a full regen across all units. */ | ||
| codegenVersion: string; | ||
| /** Every relative path the plugin tracks (units' files plus global files plus the manifest itself). Used to compute deletions on the next run. */ | ||
| files: string[]; | ||
| /** Per-unit cache records keyed by stable unit ID. */ | ||
| units: Record<string, IncrementalUnitRecord>; | ||
| } | ||
| | ||
| /** A cacheable codegen unit — a stable key + the inputs to fingerprint + a deferred renderer that's only invoked on cache miss. */ | ||
| export interface IncrementalUnit { | ||
| /** Stable ID across runs (e.g. `<file>::<METHOD> <path>` for ops, `<file>` for per-file outputs). Renaming the source breaks the key, which is the correct behavior — old files get cleaned up, new ones are emitted. */ | ||
| key: string; | ||
| /** Pre-computed fingerprint covering every input that affects this unit's output. The caller is responsible for hashing in any cross-unit inputs (e.g. transitively-referenced models, plugin config). */ | ||
| fingerprint: string; | ||
| /** Renders the unit's output(s). Only called on cache miss. May produce zero, one, or many files (e.g. paired router + types files). */ | ||
| render: () => IncrementalOutputFile[]; | ||
| } | ||
| | ||
| /** What {@link runIncrementalCodegen} produces — files the caller must write, the new manifest, paths that should be deleted, and a count of skipped units (useful for logging). */ | ||
| export interface IncrementalResult { | ||
| /** Files the caller should write (changed/new units' files plus global files plus the new manifest). */ | ||
| filesToWrite: IncrementalOutputFile[]; | ||
| /** New manifest the caller should persist. Already included in `filesToWrite`. */ | ||
| manifest: IncrementalManifest; | ||
| /** Relative paths from the prior run that no longer appear in the new manifest. The caller should delete these from disk. */ | ||
| deletedPaths: string[]; | ||
| /** Number of units whose codegen was skipped because their fingerprint matched. */ | ||
| skippedUnitCount: number; | ||
| } | ||
| | ||
| /** Construct a no-op manifest, used when no prior run exists or when the cache is being intentionally bypassed (`--force`, `cacheEnabled=false`). */ | ||
| export function emptyIncrementalManifest(codegenVersion: string): IncrementalManifest { | ||
| return { version: INCREMENTAL_MANIFEST_VERSION, codegenVersion, files: [], units: {} }; | ||
| } | ||
| | ||
| /** | ||
| * Parse a previously-persisted manifest file. Returns an empty manifest on any | ||
| * shape error (malformed JSON, wrong version, missing fields). Stale files | ||
| * never block a build: the worst case is a full regen. | ||
| */ | ||
| export function parseIncrementalManifest(content: string): IncrementalManifest { | ||
| try { | ||
| const parsed = JSON.parse(content); | ||
| if (!parsed || typeof parsed !== 'object' || parsed.version !== INCREMENTAL_MANIFEST_VERSION) { | ||
| return emptyIncrementalManifest(''); | ||
| } | ||
| const codegenVersion = typeof parsed.codegenVersion === 'string' ? parsed.codegenVersion : <span class="branch-1 cbranch-no" title="branch not covered" >'';</span> | ||
| const files = Array.isArray(parsed.files) && parsed.files.every((f: unknown) => typeof f === 'string') ? (parsed.files as string[]) : <span class="branch-1 cbranch-no" title="branch not covered" >[];</span> | ||
| const units: Record<string, IncrementalUnitRecord> = {}; | ||
| if (parsed.units && typeof parsed.units === 'object' && !Array.isArray(parsed.units)) { | ||
| for (const [key, raw] of Object.entries(parsed.units as Record<string, unknown>)) { | ||
| <span class="missing-if-branch" title="if path not taken" >I</span>if (!raw || typeof raw !== 'object') <span class="cstat-no" title="statement not covered" >continue;</span> | ||
| const entry = raw as Record<string, unknown>; | ||
| const fp = entry['fingerprint']; | ||
| const fs = entry['files']; | ||
| if (typeof fp !== 'string') continue; | ||
| if (!Array.isArray(fs) || !fs.every(p => typeof p === 'string')) continue; | ||
| units[key] = { fingerprint: fp, files: fs as string[] }; | ||
| } | ||
| } | ||
| return { version: INCREMENTAL_MANIFEST_VERSION, codegenVersion, files, units }; | ||
| } catch { | ||
| return emptyIncrementalManifest(''); | ||
| } | ||
| } | ||
| | ||
| /** sha256 hex of `value` after stable JSON serialization. Two payloads with the same content always hash the same. */ | ||
| export function hashFingerprint(value: unknown): string { | ||
| return createHash('sha256').update(stableStringify(value)).digest('hex'); | ||
| } | ||
| | ||
| /** JSON.stringify variant that sorts object keys recursively, so structurally equivalent values always serialize identically. */ | ||
| export function stableStringify(value: unknown): string { | ||
| if (value === null || typeof value !== 'object') return JSON.stringify(value); | ||
| if (Array.isArray(value)) return '[' + value.map(stableStringify).join(',') + ']'; | ||
| const keys = Object.keys(value as Record<string, unknown>).sort(); | ||
| return '{' + keys.map(k => JSON.stringify(k) + ':' + stableStringify((value as Record<string, unknown>)[k])).join(',') + '}'; | ||
| } | ||
| | ||
| /** | ||
| * Run incremental codegen. | ||
| * | ||
| * For each unit, compares its current `fingerprint` against the prior manifest's | ||
| * record. On match (and provided every previously-emitted file is still on disk) | ||
| * the unit's `render()` is skipped and its prior output paths carry forward. | ||
| * On mismatch (or a missing file, or a `codegenVersion` bump) the unit re-renders | ||
| * and its files land in `filesToWrite`. | ||
| * | ||
| * Global files always land in `filesToWrite` — they're for outputs that always | ||
| * regenerate (aggregators, barrels, constants). | ||
| * | ||
| * The caller is responsible for: | ||
| * 1. Writing every `filesToWrite` entry. | ||
| * 2. Deleting every `deletedPaths` entry. | ||
| * 3. Persisting `manifest` to the manifest file path (already included in `filesToWrite`). | ||
| */ | ||
| export function runIncrementalCodegen(args: { | ||
| codegenVersion: string; | ||
| /** Relative path within the plugin's output dir where the manifest will be written. */ | ||
| manifestFilename: string; | ||
| prevManifest: IncrementalManifest; | ||
| /** Files always written, regardless of cache state — typically aggregators or constants. */ | ||
| globalFiles: IncrementalOutputFile[]; | ||
| /** Cacheable units, in deterministic order. */ | ||
| units: IncrementalUnit[]; | ||
| /** Returns `true` if the given relative path currently exists on disk. Used to invalidate cache entries when a previously-emitted file was deleted. */ | ||
| fileExists: (relativePath: string) => boolean; | ||
| }): IncrementalResult { | ||
| const { codegenVersion, manifestFilename, prevManifest, globalFiles, units, fileExists } = args; | ||
| const filesToWrite: IncrementalOutputFile[] = []; | ||
| const trackedPaths = new Set<string>(); | ||
| const newUnits: Record<string, IncrementalUnitRecord> = {}; | ||
| let skippedUnitCount = 0; | ||
| | ||
| const cacheUsable = prevManifest.version === INCREMENTAL_MANIFEST_VERSION && prevManifest.codegenVersion === codegenVersion; | ||
| | ||
| for (const file of globalFiles) { | ||
| filesToWrite.push(file); | ||
| trackedPaths.add(file.relativePath); | ||
| } | ||
| | ||
| for (const unit of units) { | ||
| const prev = cacheUsable ? prevManifest.units[unit.key] : undefined; | ||
| const cacheHit = | ||
| prev !== undefined && prev.fingerprint === unit.fingerprint && prev.files.length > 0 && prev.files.every(p => fileExists(p)); | ||
| | ||
| if (cacheHit) { | ||
| newUnits[unit.key] = { fingerprint: unit.fingerprint, files: prev!.files }; | ||
| for (const p of prev!.files) trackedPaths.add(p); | ||
| skippedUnitCount++; | ||
| continue; | ||
| } | ||
| | ||
| const rendered = unit.render(); | ||
| const renderedPaths: string[] = []; | ||
| for (const file of rendered) { | ||
| filesToWrite.push(file); | ||
| trackedPaths.add(file.relativePath); | ||
| renderedPaths.push(file.relativePath); | ||
| } | ||
| newUnits[unit.key] = { fingerprint: unit.fingerprint, files: renderedPaths }; | ||
| } | ||
| | ||
| trackedPaths.add(manifestFilename); | ||
| const sortedFiles = [...trackedPaths].sort(); | ||
| const manifest: IncrementalManifest = { | ||
| version: INCREMENTAL_MANIFEST_VERSION, | ||
| codegenVersion, | ||
| files: sortedFiles, | ||
| units: newUnits, | ||
| }; | ||
| filesToWrite.push({ relativePath: manifestFilename, content: JSON.stringify(manifest, null, 2) + '\n' }); | ||
| | ||
| const deletedPaths = prevManifest.files.filter(p => !trackedPaths.has(p)); | ||
| return { filesToWrite, manifest, deletedPaths, skippedUnitCount }; | ||
| } | ||
| </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-07T11:09:05.393Z | ||
| </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> | ||
| /** | ||
| * Schema version for the on-disk manifest. Bumped if the manifest shape itself changes. | ||
| * Plugin codegen changes are tracked separately via `codegenVersion`. | ||
| */ | ||
| export declare const INCREMENTAL_MANIFEST_VERSION = 2; | ||
| /** A single output file produced by a plugin: relative path within the plugin's output dir + content. */ | ||
| export interface IncrementalOutputFile { | ||
| relativePath: string; | ||
| content: string; | ||
| } | ||
| /** Cache record for one plugin "unit" — the smallest thing a plugin can decide to regenerate or reuse. */ | ||
| export interface IncrementalUnitRecord { | ||
| /** Hash covering every input that affects this unit's output. */ | ||
| fingerprint: string; | ||
| /** Relative paths this unit emitted last time. Used to verify on-disk presence on cache hit, and to detect file removals. */ | ||
| files: string[]; | ||
| } | ||
| /** | ||
| * Persistent on-disk record describing what a plugin produced last run. Plugins write | ||
| * one of these per output dir. The `version` and `codegenVersion` fields together | ||
| * decide whether the cache can be honored at all on the current run. | ||
| */ | ||
| export interface IncrementalManifest { | ||
| version: number; | ||
| /** Plugin-defined version string. Bump in the plugin code to force a full regen across all units. */ | ||
| codegenVersion: string; | ||
| /** Every relative path the plugin tracks (units' files plus global files plus the manifest itself). Used to compute deletions on the next run. */ | ||
| files: string[]; | ||
| /** Per-unit cache records keyed by stable unit ID. */ | ||
| units: Record<string, IncrementalUnitRecord>; | ||
| } | ||
| /** A cacheable codegen unit — a stable key + the inputs to fingerprint + a deferred renderer that's only invoked on cache miss. */ | ||
| export interface IncrementalUnit { | ||
| /** Stable ID across runs (e.g. `<file>::<METHOD> <path>` for ops, `<file>` for per-file outputs). Renaming the source breaks the key, which is the correct behavior — old files get cleaned up, new ones are emitted. */ | ||
| key: string; | ||
| /** Pre-computed fingerprint covering every input that affects this unit's output. The caller is responsible for hashing in any cross-unit inputs (e.g. transitively-referenced models, plugin config). */ | ||
| fingerprint: string; | ||
| /** Renders the unit's output(s). Only called on cache miss. May produce zero, one, or many files (e.g. paired router + types files). */ | ||
| render: () => IncrementalOutputFile[]; | ||
| } | ||
| /** What {@link runIncrementalCodegen} produces — files the caller must write, the new manifest, paths that should be deleted, and a count of skipped units (useful for logging). */ | ||
| export interface IncrementalResult { | ||
| /** Files the caller should write (changed/new units' files plus global files plus the new manifest). */ | ||
| filesToWrite: IncrementalOutputFile[]; | ||
| /** New manifest the caller should persist. Already included in `filesToWrite`. */ | ||
| manifest: IncrementalManifest; | ||
| /** Relative paths from the prior run that no longer appear in the new manifest. The caller should delete these from disk. */ | ||
| deletedPaths: string[]; | ||
| /** Number of units whose codegen was skipped because their fingerprint matched. */ | ||
| skippedUnitCount: number; | ||
| } | ||
| /** Construct a no-op manifest, used when no prior run exists or when the cache is being intentionally bypassed (`--force`, `cacheEnabled=false`). */ | ||
| export declare function emptyIncrementalManifest(codegenVersion: string): IncrementalManifest; | ||
| /** | ||
| * Parse a previously-persisted manifest file. Returns an empty manifest on any | ||
| * shape error (malformed JSON, wrong version, missing fields). Stale files | ||
| * never block a build: the worst case is a full regen. | ||
| */ | ||
| export declare function parseIncrementalManifest(content: string): IncrementalManifest; | ||
| /** sha256 hex of `value` after stable JSON serialization. Two payloads with the same content always hash the same. */ | ||
| export declare function hashFingerprint(value: unknown): string; | ||
| /** JSON.stringify variant that sorts object keys recursively, so structurally equivalent values always serialize identically. */ | ||
| export declare function stableStringify(value: unknown): string; | ||
| /** | ||
| * Run incremental codegen. | ||
| * | ||
| * For each unit, compares its current `fingerprint` against the prior manifest's | ||
| * record. On match (and provided every previously-emitted file is still on disk) | ||
| * the unit's `render()` is skipped and its prior output paths carry forward. | ||
| * On mismatch (or a missing file, or a `codegenVersion` bump) the unit re-renders | ||
| * and its files land in `filesToWrite`. | ||
| * | ||
| * Global files always land in `filesToWrite` — they're for outputs that always | ||
| * regenerate (aggregators, barrels, constants). | ||
| * | ||
| * The caller is responsible for: | ||
| * 1. Writing every `filesToWrite` entry. | ||
| * 2. Deleting every `deletedPaths` entry. | ||
| * 3. Persisting `manifest` to the manifest file path (already included in `filesToWrite`). | ||
| */ | ||
| export declare function runIncrementalCodegen(args: { | ||
| codegenVersion: string; | ||
| /** Relative path within the plugin's output dir where the manifest will be written. */ | ||
| manifestFilename: string; | ||
| prevManifest: IncrementalManifest; | ||
| /** Files always written, regardless of cache state — typically aggregators or constants. */ | ||
| globalFiles: IncrementalOutputFile[]; | ||
| /** Cacheable units, in deterministic order. */ | ||
| units: IncrementalUnit[]; | ||
| /** Returns `true` if the given relative path currently exists on disk. Used to invalidate cache entries when a previously-emitted file was deleted. */ | ||
| fileExists: (relativePath: string) => boolean; | ||
| }): IncrementalResult; | ||
| //# sourceMappingURL=incremental.d.ts.map |
| {"version":3,"file":"incremental.d.ts","sourceRoot":"","sources":["../src/incremental.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,yGAAyG;AACzG,MAAM,WAAW,qBAAqB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,0GAA0G;AAC1G,MAAM,WAAW,qBAAqB;IAClC,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,6HAA6H;IAC7H,KAAK,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,qGAAqG;IACrG,cAAc,EAAE,MAAM,CAAC;IACvB,kJAAkJ;IAClJ,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;CAChD;AAED,mIAAmI;AACnI,MAAM,WAAW,eAAe;IAC5B,yNAAyN;IACzN,GAAG,EAAE,MAAM,CAAC;IACZ,0MAA0M;IAC1M,WAAW,EAAE,MAAM,CAAC;IACpB,wIAAwI;IACxI,MAAM,EAAE,MAAM,qBAAqB,EAAE,CAAC;CACzC;AAED,oLAAoL;AACpL,MAAM,WAAW,iBAAiB;IAC9B,wGAAwG;IACxG,YAAY,EAAE,qBAAqB,EAAE,CAAC;IACtC,kFAAkF;IAClF,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,6HAA6H;IAC7H,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,mFAAmF;IACnF,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AAED,qJAAqJ;AACrJ,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,mBAAmB,CAEpF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CAwB7E;AAED,sHAAsH;AACtH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEtD;AAED,iIAAiI;AACjI,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKtD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,mBAAmB,CAAC;IAClC,4FAA4F;IAC5F,WAAW,EAAE,qBAAqB,EAAE,CAAC;IACrC,+CAA+C;IAC/C,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,uJAAuJ;IACvJ,UAAU,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;CACjD,GAAG,iBAAiB,CAgDpB"} |
| import { createHash } from 'node:crypto'; | ||
| /** | ||
| * Schema version for the on-disk manifest. Bumped if the manifest shape itself changes. | ||
| * Plugin codegen changes are tracked separately via `codegenVersion`. | ||
| */ | ||
| export const INCREMENTAL_MANIFEST_VERSION = 2; | ||
| /** A single output file produced by a plugin: relative path within the plugin's output dir + content. */ | ||
| export interface IncrementalOutputFile { | ||
| relativePath: string; | ||
| content: string; | ||
| } | ||
| /** Cache record for one plugin "unit" — the smallest thing a plugin can decide to regenerate or reuse. */ | ||
| export interface IncrementalUnitRecord { | ||
| /** Hash covering every input that affects this unit's output. */ | ||
| fingerprint: string; | ||
| /** Relative paths this unit emitted last time. Used to verify on-disk presence on cache hit, and to detect file removals. */ | ||
| files: string[]; | ||
| } | ||
| /** | ||
| * Persistent on-disk record describing what a plugin produced last run. Plugins write | ||
| * one of these per output dir. The `version` and `codegenVersion` fields together | ||
| * decide whether the cache can be honored at all on the current run. | ||
| */ | ||
| export interface IncrementalManifest { | ||
| version: number; | ||
| /** Plugin-defined version string. Bump in the plugin code to force a full regen across all units. */ | ||
| codegenVersion: string; | ||
| /** Every relative path the plugin tracks (units' files plus global files plus the manifest itself). Used to compute deletions on the next run. */ | ||
| files: string[]; | ||
| /** Per-unit cache records keyed by stable unit ID. */ | ||
| units: Record<string, IncrementalUnitRecord>; | ||
| } | ||
| /** A cacheable codegen unit — a stable key + the inputs to fingerprint + a deferred renderer that's only invoked on cache miss. */ | ||
| export interface IncrementalUnit { | ||
| /** Stable ID across runs (e.g. `<file>::<METHOD> <path>` for ops, `<file>` for per-file outputs). Renaming the source breaks the key, which is the correct behavior — old files get cleaned up, new ones are emitted. */ | ||
| key: string; | ||
| /** Pre-computed fingerprint covering every input that affects this unit's output. The caller is responsible for hashing in any cross-unit inputs (e.g. transitively-referenced models, plugin config). */ | ||
| fingerprint: string; | ||
| /** Renders the unit's output(s). Only called on cache miss. May produce zero, one, or many files (e.g. paired router + types files). */ | ||
| render: () => IncrementalOutputFile[]; | ||
| } | ||
| /** What {@link runIncrementalCodegen} produces — files the caller must write, the new manifest, paths that should be deleted, and a count of skipped units (useful for logging). */ | ||
| export interface IncrementalResult { | ||
| /** Files the caller should write (changed/new units' files plus global files plus the new manifest). */ | ||
| filesToWrite: IncrementalOutputFile[]; | ||
| /** New manifest the caller should persist. Already included in `filesToWrite`. */ | ||
| manifest: IncrementalManifest; | ||
| /** Relative paths from the prior run that no longer appear in the new manifest. The caller should delete these from disk. */ | ||
| deletedPaths: string[]; | ||
| /** Number of units whose codegen was skipped because their fingerprint matched. */ | ||
| skippedUnitCount: number; | ||
| } | ||
| /** Construct a no-op manifest, used when no prior run exists or when the cache is being intentionally bypassed (`--force`, `cacheEnabled=false`). */ | ||
| export function emptyIncrementalManifest(codegenVersion: string): IncrementalManifest { | ||
| return { version: INCREMENTAL_MANIFEST_VERSION, codegenVersion, files: [], units: {} }; | ||
| } | ||
| /** | ||
| * Parse a previously-persisted manifest file. Returns an empty manifest on any | ||
| * shape error (malformed JSON, wrong version, missing fields). Stale files | ||
| * never block a build: the worst case is a full regen. | ||
| */ | ||
| export function parseIncrementalManifest(content: string): IncrementalManifest { | ||
| try { | ||
| const parsed = JSON.parse(content); | ||
| if (!parsed || typeof parsed !== 'object' || parsed.version !== INCREMENTAL_MANIFEST_VERSION) { | ||
| return emptyIncrementalManifest(''); | ||
| } | ||
| const codegenVersion = typeof parsed.codegenVersion === 'string' ? parsed.codegenVersion : ''; | ||
| const files = Array.isArray(parsed.files) && parsed.files.every((f: unknown) => typeof f === 'string') ? (parsed.files as string[]) : []; | ||
| const units: Record<string, IncrementalUnitRecord> = {}; | ||
| if (parsed.units && typeof parsed.units === 'object' && !Array.isArray(parsed.units)) { | ||
| for (const [key, raw] of Object.entries(parsed.units as Record<string, unknown>)) { | ||
| if (!raw || typeof raw !== 'object') continue; | ||
| const entry = raw as Record<string, unknown>; | ||
| const fp = entry['fingerprint']; | ||
| const fs = entry['files']; | ||
| if (typeof fp !== 'string') continue; | ||
| if (!Array.isArray(fs) || !fs.every(p => typeof p === 'string')) continue; | ||
| units[key] = { fingerprint: fp, files: fs as string[] }; | ||
| } | ||
| } | ||
| return { version: INCREMENTAL_MANIFEST_VERSION, codegenVersion, files, units }; | ||
| } catch { | ||
| return emptyIncrementalManifest(''); | ||
| } | ||
| } | ||
| /** sha256 hex of `value` after stable JSON serialization. Two payloads with the same content always hash the same. */ | ||
| export function hashFingerprint(value: unknown): string { | ||
| return createHash('sha256').update(stableStringify(value)).digest('hex'); | ||
| } | ||
| /** JSON.stringify variant that sorts object keys recursively, so structurally equivalent values always serialize identically. */ | ||
| export function stableStringify(value: unknown): string { | ||
| if (value === null || typeof value !== 'object') return JSON.stringify(value); | ||
| if (Array.isArray(value)) return '[' + value.map(stableStringify).join(',') + ']'; | ||
| const keys = Object.keys(value as Record<string, unknown>).sort(); | ||
| return '{' + keys.map(k => JSON.stringify(k) + ':' + stableStringify((value as Record<string, unknown>)[k])).join(',') + '}'; | ||
| } | ||
| /** | ||
| * Run incremental codegen. | ||
| * | ||
| * For each unit, compares its current `fingerprint` against the prior manifest's | ||
| * record. On match (and provided every previously-emitted file is still on disk) | ||
| * the unit's `render()` is skipped and its prior output paths carry forward. | ||
| * On mismatch (or a missing file, or a `codegenVersion` bump) the unit re-renders | ||
| * and its files land in `filesToWrite`. | ||
| * | ||
| * Global files always land in `filesToWrite` — they're for outputs that always | ||
| * regenerate (aggregators, barrels, constants). | ||
| * | ||
| * The caller is responsible for: | ||
| * 1. Writing every `filesToWrite` entry. | ||
| * 2. Deleting every `deletedPaths` entry. | ||
| * 3. Persisting `manifest` to the manifest file path (already included in `filesToWrite`). | ||
| */ | ||
| export function runIncrementalCodegen(args: { | ||
| codegenVersion: string; | ||
| /** Relative path within the plugin's output dir where the manifest will be written. */ | ||
| manifestFilename: string; | ||
| prevManifest: IncrementalManifest; | ||
| /** Files always written, regardless of cache state — typically aggregators or constants. */ | ||
| globalFiles: IncrementalOutputFile[]; | ||
| /** Cacheable units, in deterministic order. */ | ||
| units: IncrementalUnit[]; | ||
| /** Returns `true` if the given relative path currently exists on disk. Used to invalidate cache entries when a previously-emitted file was deleted. */ | ||
| fileExists: (relativePath: string) => boolean; | ||
| }): IncrementalResult { | ||
| const { codegenVersion, manifestFilename, prevManifest, globalFiles, units, fileExists } = args; | ||
| const filesToWrite: IncrementalOutputFile[] = []; | ||
| const trackedPaths = new Set<string>(); | ||
| const newUnits: Record<string, IncrementalUnitRecord> = {}; | ||
| let skippedUnitCount = 0; | ||
| const cacheUsable = prevManifest.version === INCREMENTAL_MANIFEST_VERSION && prevManifest.codegenVersion === codegenVersion; | ||
| for (const file of globalFiles) { | ||
| filesToWrite.push(file); | ||
| trackedPaths.add(file.relativePath); | ||
| } | ||
| for (const unit of units) { | ||
| const prev = cacheUsable ? prevManifest.units[unit.key] : undefined; | ||
| const cacheHit = | ||
| prev !== undefined && prev.fingerprint === unit.fingerprint && prev.files.length > 0 && prev.files.every(p => fileExists(p)); | ||
| if (cacheHit) { | ||
| newUnits[unit.key] = { fingerprint: unit.fingerprint, files: prev!.files }; | ||
| for (const p of prev!.files) trackedPaths.add(p); | ||
| skippedUnitCount++; | ||
| continue; | ||
| } | ||
| const rendered = unit.render(); | ||
| const renderedPaths: string[] = []; | ||
| for (const file of rendered) { | ||
| filesToWrite.push(file); | ||
| trackedPaths.add(file.relativePath); | ||
| renderedPaths.push(file.relativePath); | ||
| } | ||
| newUnits[unit.key] = { fingerprint: unit.fingerprint, files: renderedPaths }; | ||
| } | ||
| trackedPaths.add(manifestFilename); | ||
| const sortedFiles = [...trackedPaths].sort(); | ||
| const manifest: IncrementalManifest = { | ||
| version: INCREMENTAL_MANIFEST_VERSION, | ||
| codegenVersion, | ||
| files: sortedFiles, | ||
| units: newUnits, | ||
| }; | ||
| filesToWrite.push({ relativePath: manifestFilename, content: JSON.stringify(manifest, null, 2) + '\n' }); | ||
| const deletedPaths = prevManifest.files.filter(p => !trackedPaths.has(p)); | ||
| return { filesToWrite, manifest, deletedPaths, skippedUnitCount }; | ||
| } |
| import { describe, it, expect } from 'vitest'; | ||
| import { | ||
| runIncrementalCodegen, | ||
| parseIncrementalManifest, | ||
| emptyIncrementalManifest, | ||
| hashFingerprint, | ||
| stableStringify, | ||
| INCREMENTAL_MANIFEST_VERSION, | ||
| type IncrementalUnit, | ||
| type IncrementalOutputFile, | ||
| } from '../src/incremental.js'; | ||
| describe('hashFingerprint', () => { | ||
| it('produces the same hash for structurally equivalent payloads', () => { | ||
| expect(hashFingerprint({ a: 1, b: 2 })).toBe(hashFingerprint({ b: 2, a: 1 })); | ||
| }); | ||
| it('produces different hashes for different payloads', () => { | ||
| expect(hashFingerprint({ a: 1 })).not.toBe(hashFingerprint({ a: 2 })); | ||
| }); | ||
| }); | ||
| describe('stableStringify', () => { | ||
| it('sorts object keys recursively', () => { | ||
| expect(stableStringify({ b: 1, a: { y: 2, x: 1 } })).toBe('{"a":{"x":1,"y":2},"b":1}'); | ||
| }); | ||
| it('preserves array order', () => { | ||
| expect(stableStringify([3, 1, 2])).toBe('[3,1,2]'); | ||
| }); | ||
| it('handles primitives and null', () => { | ||
| expect(stableStringify(null)).toBe('null'); | ||
| expect(stableStringify('s')).toBe('"s"'); | ||
| expect(stableStringify(42)).toBe('42'); | ||
| }); | ||
| }); | ||
| describe('parseIncrementalManifest', () => { | ||
| it('returns an empty manifest for malformed JSON', () => { | ||
| const m = parseIncrementalManifest('not json'); | ||
| expect(m.version).toBe(INCREMENTAL_MANIFEST_VERSION); | ||
| expect(m.codegenVersion).toBe(''); | ||
| expect(m.files).toEqual([]); | ||
| expect(m.units).toEqual({}); | ||
| }); | ||
| it('returns an empty manifest for a missing version', () => { | ||
| expect(parseIncrementalManifest('{"files":[]}').units).toEqual({}); | ||
| }); | ||
| it('round-trips a serialized manifest', () => { | ||
| const original = { | ||
| version: INCREMENTAL_MANIFEST_VERSION, | ||
| codegenVersion: '7', | ||
| files: ['a.ts', 'b.ts'], | ||
| units: { 'k': { fingerprint: 'fp1', files: ['a.ts'] } }, | ||
| }; | ||
| const parsed = parseIncrementalManifest(JSON.stringify(original)); | ||
| expect(parsed).toEqual(original); | ||
| }); | ||
| it('drops malformed unit entries but keeps well-formed ones', () => { | ||
| const raw = JSON.stringify({ | ||
| version: INCREMENTAL_MANIFEST_VERSION, | ||
| codegenVersion: '1', | ||
| files: [], | ||
| units: { | ||
| good: { fingerprint: 'a', files: ['x'] }, | ||
| bad1: { fingerprint: 123, files: ['x'] }, // fingerprint not string | ||
| bad2: { fingerprint: 'a', files: 'nope' }, // files not array | ||
| }, | ||
| }); | ||
| const parsed = parseIncrementalManifest(raw); | ||
| expect(Object.keys(parsed.units)).toEqual(['good']); | ||
| }); | ||
| }); | ||
| describe('runIncrementalCodegen', () => { | ||
| function unit(key: string, fingerprint: string, files: IncrementalOutputFile[]): IncrementalUnit { | ||
| return { key, fingerprint, render: () => files }; | ||
| } | ||
| it('renders every unit on first run (empty manifest)', () => { | ||
| const result = runIncrementalCodegen({ | ||
| codegenVersion: '1', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: emptyIncrementalManifest('1'), | ||
| globalFiles: [], | ||
| units: [unit('a', 'fp-a', [{ relativePath: 'a.ts', content: 'A' }])], | ||
| fileExists: () => true, | ||
| }); | ||
| expect(result.skippedUnitCount).toBe(0); | ||
| expect(result.filesToWrite.map(f => f.relativePath).sort()).toEqual(['a.ts', 'manifest.json']); | ||
| expect(result.deletedPaths).toEqual([]); | ||
| }); | ||
| it('skips units whose fingerprint matches and whose files still exist', () => { | ||
| const first = runIncrementalCodegen({ | ||
| codegenVersion: '1', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: emptyIncrementalManifest('1'), | ||
| globalFiles: [], | ||
| units: [unit('a', 'fp', [{ relativePath: 'a.ts', content: 'A' }])], | ||
| fileExists: () => true, | ||
| }); | ||
| let renderCalls = 0; | ||
| const second = runIncrementalCodegen({ | ||
| codegenVersion: '1', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: first.manifest, | ||
| globalFiles: [], | ||
| units: [ | ||
| { | ||
| key: 'a', | ||
| fingerprint: 'fp', | ||
| render: () => { | ||
| renderCalls++; | ||
| return [{ relativePath: 'a.ts', content: 'A' }]; | ||
| }, | ||
| }, | ||
| ], | ||
| fileExists: () => true, | ||
| }); | ||
| expect(second.skippedUnitCount).toBe(1); | ||
| expect(renderCalls).toBe(0); | ||
| expect(second.filesToWrite.map(f => f.relativePath)).toEqual(['manifest.json']); | ||
| }); | ||
| it('regenerates a unit whose fingerprint changed', () => { | ||
| const first = runIncrementalCodegen({ | ||
| codegenVersion: '1', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: emptyIncrementalManifest('1'), | ||
| globalFiles: [], | ||
| units: [unit('a', 'fp1', [{ relativePath: 'a.ts', content: 'old' }])], | ||
| fileExists: () => true, | ||
| }); | ||
| const second = runIncrementalCodegen({ | ||
| codegenVersion: '1', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: first.manifest, | ||
| globalFiles: [], | ||
| units: [unit('a', 'fp2', [{ relativePath: 'a.ts', content: 'new' }])], | ||
| fileExists: () => true, | ||
| }); | ||
| expect(second.skippedUnitCount).toBe(0); | ||
| expect(second.filesToWrite.find(f => f.relativePath === 'a.ts')?.content).toBe('new'); | ||
| }); | ||
| it('regenerates a unit whose previously-emitted file is missing on disk', () => { | ||
| const first = runIncrementalCodegen({ | ||
| codegenVersion: '1', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: emptyIncrementalManifest('1'), | ||
| globalFiles: [], | ||
| units: [unit('a', 'fp', [{ relativePath: 'a.ts', content: 'A' }])], | ||
| fileExists: () => true, | ||
| }); | ||
| const second = runIncrementalCodegen({ | ||
| codegenVersion: '1', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: first.manifest, | ||
| globalFiles: [], | ||
| units: [unit('a', 'fp', [{ relativePath: 'a.ts', content: 'A' }])], | ||
| fileExists: () => false, | ||
| }); | ||
| expect(second.skippedUnitCount).toBe(0); | ||
| expect(second.filesToWrite.map(f => f.relativePath)).toContain('a.ts'); | ||
| }); | ||
| it('treats every unit as a miss when codegenVersion changes', () => { | ||
| const first = runIncrementalCodegen({ | ||
| codegenVersion: '1', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: emptyIncrementalManifest('1'), | ||
| globalFiles: [], | ||
| units: [unit('a', 'fp', [{ relativePath: 'a.ts', content: 'A' }])], | ||
| fileExists: () => true, | ||
| }); | ||
| const second = runIncrementalCodegen({ | ||
| codegenVersion: '2', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: first.manifest, | ||
| globalFiles: [], | ||
| units: [unit('a', 'fp', [{ relativePath: 'a.ts', content: 'A' }])], | ||
| fileExists: () => true, | ||
| }); | ||
| expect(second.skippedUnitCount).toBe(0); | ||
| }); | ||
| it('reports paths in the prior manifest but not in the new run as deletedPaths', () => { | ||
| const first = runIncrementalCodegen({ | ||
| codegenVersion: '1', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: emptyIncrementalManifest('1'), | ||
| globalFiles: [], | ||
| units: [ | ||
| unit('a', 'fp-a', [{ relativePath: 'a.ts', content: 'A' }]), | ||
| unit('b', 'fp-b', [{ relativePath: 'b.ts', content: 'B' }]), | ||
| ], | ||
| fileExists: () => true, | ||
| }); | ||
| const second = runIncrementalCodegen({ | ||
| codegenVersion: '1', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: first.manifest, | ||
| globalFiles: [], | ||
| units: [unit('a', 'fp-a', [{ relativePath: 'a.ts', content: 'A' }])], | ||
| fileExists: () => true, | ||
| }); | ||
| expect(second.deletedPaths).toContain('b.ts'); | ||
| }); | ||
| it('always writes global files, even when every unit is cached', () => { | ||
| const first = runIncrementalCodegen({ | ||
| codegenVersion: '1', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: emptyIncrementalManifest('1'), | ||
| globalFiles: [{ relativePath: 'aggregator.ts', content: 'old' }], | ||
| units: [unit('a', 'fp', [{ relativePath: 'a.ts', content: 'A' }])], | ||
| fileExists: () => true, | ||
| }); | ||
| const second = runIncrementalCodegen({ | ||
| codegenVersion: '1', | ||
| manifestFilename: 'manifest.json', | ||
| prevManifest: first.manifest, | ||
| globalFiles: [{ relativePath: 'aggregator.ts', content: 'new' }], | ||
| units: [unit('a', 'fp', [{ relativePath: 'a.ts', content: 'A' }])], | ||
| fileExists: () => true, | ||
| }); | ||
| expect(second.filesToWrite.find(f => f.relativePath === 'aggregator.ts')?.content).toBe('new'); | ||
| }); | ||
| }); |
| > @contractkit/core@0.14.0 build:ci /home/runner/work/ContractKit/ContractKit/packages/contractkit | ||
| > @contractkit/core@0.15.0 build:ci /home/runner/work/ContractKit/ContractKit/packages/contractkit | ||
| > eslint --max-warnings=0 && pnpm run build | ||
| > @contractkit/core@0.14.0 build /home/runner/work/ContractKit/ContractKit/packages/contractkit | ||
| > @contractkit/core@0.15.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 @@ | ||
| [34mESM[39m Build start | ||
| [32mESM[39m [1mdist/index.js [22m[32m83.83 KB[39m | ||
| [32mESM[39m [1mdist/index.js.map [22m[32m195.34 KB[39m | ||
| [32mESM[39m [1mdist/index.js [22m[32m88.80 KB[39m | ||
| [32mESM[39m [1mdist/index.js.map [22m[32m210.61 KB[39m | ||
| [32mESM[39m ⚡️ Build success in 78ms | ||
| [34mDTS[39m Build start | ||
| [32mDTS[39m ⚡️ Build success in 1198ms | ||
| [32mDTS[39m [1mdist/index.d.ts [22m[32m24.81 KB[39m | ||
| [32mDTS[39m ⚡️ Build success in 1308ms | ||
| [32mDTS[39m [1mdist/index.d.ts [22m[32m31.44 KB[39m |
| > @contractkit/core@0.14.0 test:ci /home/runner/work/ContractKit/ContractKit/packages/contractkit | ||
| > @contractkit/core@0.15.0 test:ci /home/runner/work/ContractKit/ContractKit/packages/contractkit | ||
| > vitest run --coverage | ||
@@ -9,13 +9,14 @@ | ||
| [32m✓[39m tests/validate-inheritance.test.ts [2m([22m[2m16 tests[22m[2m)[22m[32m 72[2mms[22m[39m | ||
| [32m✓[39m tests/apply-options-defaults.test.ts [2m([22m[2m12 tests[22m[2m)[22m[32m 116[2mms[22m[39m | ||
| [32m✓[39m tests/diagnostics.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 16[2mms[22m[39m | ||
| [32m✓[39m tests/parser-ck.test.ts [2m([22m[2m170 tests[22m[2m)[22m[33m 650[2mms[22m[39m | ||
| [32m✓[39m tests/apply-variable-substitution.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 94[2mms[22m[39m | ||
| [32m✓[39m tests/validate-discriminated.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 26[2mms[22m[39m | ||
| [32m✓[39m tests/incremental.test.ts [2m([22m[2m16 tests[22m[2m)[22m[32m 16[2mms[22m[39m | ||
| [32m✓[39m tests/apply-options-defaults.test.ts [2m([22m[2m12 tests[22m[2m)[22m[32m 121[2mms[22m[39m | ||
| [32m✓[39m tests/validate-inheritance.test.ts [2m([22m[2m16 tests[22m[2m)[22m[32m 59[2mms[22m[39m | ||
| [32m✓[39m tests/parser-ck.test.ts [2m([22m[2m170 tests[22m[2m)[22m[33m 538[2mms[22m[39m | ||
| [32m✓[39m tests/diagnostics.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 17[2mms[22m[39m | ||
| [32m✓[39m tests/apply-variable-substitution.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 55[2mms[22m[39m | ||
| [32m✓[39m tests/validate-discriminated.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 30[2mms[22m[39m | ||
| [2m Test Files [22m [1m[32m6 passed[39m[22m[90m (6)[39m | ||
| [2m Tests [22m [1m[32m223 passed[39m[22m[90m (223)[39m | ||
| [2m Start at [22m 12:37:03 | ||
| [2m Duration [22m 2.27s[2m (transform 430ms, setup 0ms, import 3.30s, tests 975ms, environment 1ms)[22m | ||
| [2m Test Files [22m [1m[32m7 passed[39m[22m[90m (7)[39m | ||
| [2m Tests [22m [1m[32m239 passed[39m[22m[90m (239)[39m | ||
| [2m Start at [22m 11:09:03 | ||
| [2m Duration [22m 2.12s[2m (transform 488ms, setup 0ms, import 2.79s, tests 835ms, environment 1ms)[22m | ||
@@ -26,3 +27,3 @@ [34m % [39m[2mCoverage report from [22m[33mv8[39m | ||
| -------------------|---------|----------|---------|---------|------------------- | ||
| All files | 83.95 | 71.93 | 88.77 | 86.45 | | ||
| All files | 84.72 | 73.15 | 89.34 | 87.15 | | ||
| ...ns-defaults.ts | 98 | 95.34 | 100 | 100 | 67,93 | ||
@@ -34,2 +35,3 @@ ...ubstitution.ts | 97.56 | 94.87 | 100 | 100 | 26,55 | ||
| grammar.ts | 75 | 100 | 100 | 75 | 23-24 | ||
| incremental.ts | 98.48 | 92.85 | 100 | 100 | 76-77,81 | ||
| parser.ts | 100 | 66.66 | 100 | 100 | 25-26 | ||
@@ -36,0 +38,0 @@ semantics.ts | 92.55 | 79.37 | 92.52 | 95.5 | ...1200,1220-1224 |
+6
-0
| # @contractkit/core | ||
| ## 0.15.0 | ||
| ### Minor Changes | ||
| - 10ca07b: Add per-output incremental caching to the Bruno, Python, and TypeScript plugins. Editing a single contract or operation no longer regenerates every output file — only the units whose transitive inputs actually changed are re-rendered, with the rest reused from a per-plugin manifest. `@contractkit/core` exposes the shared utility (`runIncrementalCodegen`, `parseIncrementalManifest`, `hashFingerprint`, `collectTransitiveModelRefs`, manifest types) for plugin authors. `PluginContext` gains a `cacheEnabled` flag so plugins can honor `--force` / `cache: false`. | ||
| ## 0.14.0 | ||
@@ -4,0 +10,0 @@ |
@@ -358,3 +358,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-06T12:37:05.432Z | ||
| at 2026-05-07T11:09:05.393Z | ||
| </div> | ||
@@ -361,0 +361,0 @@ <script src="prettify.js"></script> |
@@ -316,3 +316,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-06T12:37:05.432Z | ||
| at 2026-05-07T11:09:05.393Z | ||
| </div> | ||
@@ -319,0 +319,0 @@ <script src="prettify.js"></script> |
@@ -1114,3 +1114,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-06T12:37:05.432Z | ||
| at 2026-05-07T11:09:05.393Z | ||
| </div> | ||
@@ -1117,0 +1117,0 @@ <script src="prettify.js"></script> |
+61
-3
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <coverage generated="1778071025448" clover="3.2.0"> | ||
| <project timestamp="1778071025448" name="All files"> | ||
| <metrics statements="1004" coveredstatements="868" conditionals="677" coveredconditionals="487" methods="187" coveredmethods="166" elements="1868" coveredelements="1521" complexity="0" loc="1004" ncloc="1004" packages="1" files="11" classes="11"/> | ||
| <coverage generated="1778152145409" clover="3.2.0"> | ||
| <project timestamp="1778152145409" name="All files"> | ||
| <metrics statements="1059" coveredstatements="923" conditionals="719" coveredconditionals="526" methods="197" coveredmethods="176" elements="1975" coveredelements="1625" complexity="0" loc="1059" ncloc="1059" packages="1" files="12" classes="12"/> | ||
| <file name="apply-options-defaults.ts" path="/home/runner/work/ContractKit/ContractKit/packages/contractkit/src/apply-options-defaults.ts"> | ||
@@ -124,2 +124,60 @@ <metrics statements="38" coveredstatements="38" conditionals="43" coveredconditionals="41" methods="9" coveredmethods="9"/> | ||
| </file> | ||
| <file name="incremental.ts" path="/home/runner/work/ContractKit/ContractKit/packages/contractkit/src/incremental.ts"> | ||
| <metrics statements="55" coveredstatements="55" conditionals="42" coveredconditionals="39" methods="10" coveredmethods="10"/> | ||
| <line num="7" count="1" type="stmt"/> | ||
| <line num="62" count="9" type="stmt"/> | ||
| <line num="71" count="4" type="stmt"/> | ||
| <line num="72" count="4" type="stmt"/> | ||
| <line num="73" count="4" type="cond" truecount="5" falsecount="0"/> | ||
| <line num="74" count="1" type="stmt"/> | ||
| <line num="76" count="2" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="77" count="4" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="78" count="4" type="stmt"/> | ||
| <line num="79" count="4" type="cond" truecount="5" falsecount="0"/> | ||
| <line num="80" count="2" type="stmt"/> | ||
| <line num="81" count="4" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="82" count="4" type="stmt"/> | ||
| <line num="83" count="4" type="stmt"/> | ||
| <line num="84" count="4" type="stmt"/> | ||
| <line num="85" count="4" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="86" count="3" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="87" count="2" type="stmt"/> | ||
| <line num="90" count="2" type="stmt"/> | ||
| <line num="92" count="1" type="stmt"/> | ||
| <line num="98" count="4" type="stmt"/> | ||
| <line num="103" count="22" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="104" count="7" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="105" count="6" type="stmt"/> | ||
| <line num="106" count="10" type="stmt"/> | ||
| <line num="138" count="13" type="stmt"/> | ||
| <line num="139" count="13" type="stmt"/> | ||
| <line num="140" count="13" type="stmt"/> | ||
| <line num="141" count="13" type="stmt"/> | ||
| <line num="142" count="13" type="stmt"/> | ||
| <line num="144" count="13" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="146" count="13" type="stmt"/> | ||
| <line num="147" count="2" type="stmt"/> | ||
| <line num="148" count="2" type="stmt"/> | ||
| <line num="151" count="13" type="stmt"/> | ||
| <line num="152" count="14" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="154" count="14" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="156" count="14" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="157" count="3" type="stmt"/> | ||
| <line num="158" count="3" type="stmt"/> | ||
| <line num="159" count="3" type="stmt"/> | ||
| <line num="160" count="3" type="stmt"/> | ||
| <line num="163" count="11" type="stmt"/> | ||
| <line num="164" count="11" type="stmt"/> | ||
| <line num="165" count="11" type="stmt"/> | ||
| <line num="166" count="11" type="stmt"/> | ||
| <line num="167" count="11" type="stmt"/> | ||
| <line num="168" count="11" type="stmt"/> | ||
| <line num="170" count="11" type="stmt"/> | ||
| <line num="173" count="13" type="stmt"/> | ||
| <line num="174" count="13" type="stmt"/> | ||
| <line num="175" count="13" type="stmt"/> | ||
| <line num="181" count="13" type="stmt"/> | ||
| <line num="183" count="14" type="stmt"/> | ||
| <line num="184" count="13" type="stmt"/> | ||
| </file> | ||
| <file name="parser.ts" path="/home/runner/work/ContractKit/ContractKit/packages/contractkit/src/parser.ts"> | ||
@@ -126,0 +184,0 @@ <metrics statements="9" coveredstatements="9" conditionals="6" coveredconditionals="4" methods="1" coveredmethods="1"/> |
@@ -145,3 +145,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-06T12:37:05.432Z | ||
| at 2026-05-07T11:09:05.393Z | ||
| </div> | ||
@@ -148,0 +148,0 @@ <script src="prettify.js"></script> |
@@ -187,3 +187,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-06T12:37:05.432Z | ||
| at 2026-05-07T11:09:05.393Z | ||
| </div> | ||
@@ -190,0 +190,0 @@ <script src="prettify.js"></script> |
@@ -154,3 +154,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-06T12:37:05.432Z | ||
| at 2026-05-07T11:09:05.393Z | ||
| </div> | ||
@@ -157,0 +157,0 @@ <script src="prettify.js"></script> |
+24
-9
@@ -26,5 +26,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">83.95% </span> | ||
| <span class="strong">84.72% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>989/1178</span> | ||
| <span class='fraction'>1054/1244</span> | ||
| </div> | ||
@@ -34,5 +34,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">71.93% </span> | ||
| <span class="strong">73.15% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>487/677</span> | ||
| <span class='fraction'>526/719</span> | ||
| </div> | ||
@@ -42,5 +42,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">88.77% </span> | ||
| <span class="strong">89.34% </span> | ||
| <span class="quiet">Functions</span> | ||
| <span class='fraction'>166/187</span> | ||
| <span class='fraction'>176/197</span> | ||
| </div> | ||
@@ -50,5 +50,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">86.45% </span> | ||
| <span class="strong">87.15% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>868/1004</span> | ||
| <span class='fraction'>923/1059</span> | ||
| </div> | ||
@@ -176,2 +176,17 @@ | ||
| <tr> | ||
| <td class="file high" data-value="incremental.ts"><a href="incremental.ts.html">incremental.ts</a></td> | ||
| <td data-value="98.48" class="pic high"> | ||
| <div class="chart"><div class="cover-fill" style="width: 98%"></div><div class="cover-empty" style="width: 2%"></div></div> | ||
| </td> | ||
| <td data-value="98.48" class="pct high">98.48%</td> | ||
| <td data-value="66" class="abs high">65/66</td> | ||
| <td data-value="92.85" class="pct high">92.85%</td> | ||
| <td data-value="42" class="abs high">39/42</td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="10" class="abs high">10/10</td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="55" class="abs high">55/55</td> | ||
| </tr> | ||
| <tr> | ||
| <td class="file high" data-value="parser.ts"><a href="parser.ts.html">parser.ts</a></td> | ||
@@ -259,3 +274,3 @@ <td data-value="100" class="pic high"> | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-06T12:37:05.432Z | ||
| at 2026-05-07T11:09:05.393Z | ||
| </div> | ||
@@ -262,0 +277,0 @@ <script src="prettify.js"></script> |
@@ -169,3 +169,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-06T12:37:05.432Z | ||
| at 2026-05-07T11:09:05.393Z | ||
| </div> | ||
@@ -172,0 +172,0 @@ <script src="prettify.js"></script> |
@@ -667,3 +667,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-06T12:37:05.432Z | ||
| at 2026-05-07T11:09:05.393Z | ||
| </div> | ||
@@ -670,0 +670,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-06T12:37:05.432Z | ||
| at 2026-05-07T11:09:05.393Z | ||
| </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-06T12:37:05.432Z | ||
| at 2026-05-07T11:09:05.393Z | ||
| </div> | ||
@@ -721,0 +721,0 @@ <script src="prettify.js"></script> |
+1
-0
@@ -15,2 +15,3 @@ export * from './ast.js'; | ||
| export * from './content-type.js'; | ||
| export * from './incremental.js'; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -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,kCAAkC,CAAC;AACjD,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;AAClC,cAAc,kBAAkB,CAAC"} |
+8
-0
@@ -7,2 +7,10 @@ import type { CkRootNode, ContractRootNode, OpRootNode, PluginValue } from './ast.js'; | ||
| options: Record<string, unknown>; | ||
| /** | ||
| * Whether plugin-internal caches should be honored. Set to `false` when the user | ||
| * passes `--force` or when `cache: false` is configured. Plugins that maintain | ||
| * their own incremental-build state (e.g. per-op manifests) should bypass it | ||
| * when this is `false`. The CLI-level plugin cache is governed separately by | ||
| * the plugin's `cacheKey`. | ||
| */ | ||
| cacheEnabled: boolean; | ||
| /** Register a file to be written to disk. Only available in generateTargets. */ | ||
@@ -9,0 +17,0 @@ emitFile(outPath: string, content: string): void; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEtF,MAAM,WAAW,aAAa;IAC1B,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACpD;AAED,oDAAoD;AACpD,MAAM,WAAW,cAAc;IAC3B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAC9B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IAEb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAEzE;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAElE;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK;QAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IAE9F;;;OAGG;IACH,eAAe,CAAC,EAAE,CACd,MAAM,EAAE;QACJ,aAAa,EAAE,gBAAgB,EAAE,CAAC;QAClC,OAAO,EAAE,UAAU,EAAE,CAAC;QACtB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QACrC,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;KACzC,EACD,GAAG,EAAE,aAAa,KACjB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnB;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACN,kDAAkD;QAClD,IAAI,EAAE,MAAM,CAAC;QACb,kEAAkE;QAClE,WAAW,EAAE,MAAM,CAAC;QACpB;;;WAGG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,6DAA6D;QAC7D,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAC/D,CAAC;CACL"} | ||
| {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEtF,MAAM,WAAW,aAAa;IAC1B,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC;;;;;;OAMG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACpD;AAED,oDAAoD;AACpD,MAAM,WAAW,cAAc;IAC3B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAC9B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IAEb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAEzE;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAElE;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK;QAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IAE9F;;;OAGG;IACH,eAAe,CAAC,EAAE,CACd,MAAM,EAAE;QACJ,aAAa,EAAE,gBAAgB,EAAE,CAAC;QAClC,OAAO,EAAE,UAAU,EAAE,CAAC;QACtB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QACrC,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;KACzC,EACD,GAAG,EAAE,aAAa,KACjB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnB;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACN,kDAAkD;QAClD,IAAI,EAAE,MAAM,CAAC;QACb,kEAAkE;QAClE,WAAW,EAAE,MAAM,CAAC;QACpB;;;WAGG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,6DAA6D;QAC7D,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAC/D,CAAC;CACL"} |
+11
-0
@@ -8,2 +8,13 @@ import type { ContractTypeNode, ContractRootNode, ModelNode, OpRootNode } from './ast.js'; | ||
| export declare function collectPublicTypeNames(root: OpRootNode, modelsWithInput?: Set<string>, modelsWithOutput?: Set<string>): Set<string>; | ||
| /** | ||
| * Walk the model graph from a set of seed types and return every transitively | ||
| * referenced model name. Bases are followed; aliased model `type` and field types | ||
| * are queued. Useful for cache fingerprinting — gives the slice of the model | ||
| * universe a particular op or contract root depends on. | ||
| * | ||
| * Models referenced by name but not present in `modelMap` are still included in | ||
| * the result (so a fingerprint that mentions them will detect when they appear | ||
| * later), but no further traversal happens through them. | ||
| */ | ||
| export declare function collectTransitiveModelRefs(seedTypes: ContractTypeNode[], modelMap: Map<string, ModelNode>): Set<string>; | ||
| export declare function collectTypeRefs(type: ContractTypeNode, out: Set<string>): void; | ||
@@ -10,0 +21,0 @@ /** |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"type-utils.d.ts","sourceRoot":"","sources":["../src/type-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU,EAAe,MAAM,UAAU,CAAC;AAKvG;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAEnI;AA4ID,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CA+B9E;AAID;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,uBAAuB,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAiCzH;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,wBAAwB,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAiC3H;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAcpE;AAED,4EAA4E;AAC5E,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAyBzG;AAmCD,0EAA0E;AAC1E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CA0BvG;AAmCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CA6C/D;AAED,gGAAgG;AAChG,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD"} | ||
| {"version":3,"file":"type-utils.d.ts","sourceRoot":"","sources":["../src/type-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU,EAAe,MAAM,UAAU,CAAC;AAKvG;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAEnI;AA4ID;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CA2BvH;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CA+B9E;AAID;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,uBAAuB,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAiCzH;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,wBAAwB,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAiC3H;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAcpE;AAED,4EAA4E;AAC5E,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAyBzG;AAmCD,0EAA0E;AAC1E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CA0BvG;AAmCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CA6C/D;AAED,gGAAgG;AAChG,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD"} |
+1
-1
| { | ||
| "name": "@contractkit/core", | ||
| "version": "0.14.0", | ||
| "version": "0.15.0", | ||
| "description": "Core DSL compiler library: grammar-driven parser, codegen, and validation", | ||
@@ -5,0 +5,0 @@ "author": { |
+36
-1
@@ -112,4 +112,38 @@ # @contractkit/core | ||
| `PluginContext` exposes `options`, `rootDir`, `emitFile`, `emitFileIfChanged`, and the `Diagnostics` collector. | ||
| `PluginContext` exposes `options`, `rootDir`, `emitFile`, and `cacheEnabled`. `cacheEnabled` is `false` when the user passes `--force` or sets `cache: false`; plugins that maintain incremental-build state should bypass it then. | ||
| ### Incremental codegen helper | ||
| Plugins that emit many files (one per operation, one per contract root, etc.) can opt into per-output caching via `runIncrementalCodegen`. The plugin defines cacheable "units" with a stable `key` and `fingerprint`; on subsequent runs, units whose fingerprint matches the persisted manifest skip their renderer entirely. Files in the prior manifest that aren't produced this run are reported in `deletedPaths` so the plugin can clean them up. | ||
| ```typescript | ||
| import { | ||
| runIncrementalCodegen, | ||
| parseIncrementalManifest, | ||
| emptyIncrementalManifest, | ||
| hashFingerprint, | ||
| type IncrementalManifest, | ||
| } from '@contractkit/core'; | ||
| const prev: IncrementalManifest = ctx.cacheEnabled ? readManifestFromDisk(outDir) : emptyIncrementalManifest('1'); | ||
| const result = runIncrementalCodegen({ | ||
| codegenVersion: '1', // bump to bust every per-unit cache | ||
| manifestFilename: '.my-plugin-manifest.json', | ||
| prevManifest: prev, | ||
| globalFiles: [/* always-regenerated outputs */], | ||
| units: roots.map(root => ({ | ||
| key: root.file, | ||
| fingerprint: hashFingerprint({ root, options: ctx.options }), | ||
| render: () => [{ relativePath: outPathFor(root), content: render(root) }], | ||
| })), | ||
| fileExists: relPath => existsSync(resolve(outDir, relPath)), | ||
| }); | ||
| deleteFromDisk(result.deletedPaths); | ||
| for (const f of result.filesToWrite) ctx.emitFile(resolve(outDir, f.relativePath), f.content); | ||
| ``` | ||
| Companion helpers: `hashFingerprint(value)` for stable sha256 hashing, `stableStringify(value)` for deterministic JSON, `collectTransitiveModelRefs(seedTypes, modelMap)` for slicing cross-file model dependencies into per-unit fingerprints. | ||
| ### Per-operation plugin files | ||
@@ -172,1 +206,2 @@ | ||
| | `src/plugin.ts` | `ContractKitPlugin` and `PluginContext` interface types | | ||
| | `src/incremental.ts` | Shared per-output caching helper (`runIncrementalCodegen`, manifest types) used by Bruno / Python / TypeScript plugins | |
+1
-0
@@ -15,1 +15,2 @@ export * from './ast.js'; | ||
| export * from './content-type.js'; | ||
| export * from './incremental.js'; |
+8
-0
@@ -8,2 +8,10 @@ import type { CkRootNode, ContractRootNode, OpRootNode, PluginValue } from './ast.js'; | ||
| options: Record<string, unknown>; | ||
| /** | ||
| * Whether plugin-internal caches should be honored. Set to `false` when the user | ||
| * passes `--force` or when `cache: false` is configured. Plugins that maintain | ||
| * their own incremental-build state (e.g. per-op manifests) should bypass it | ||
| * when this is `false`. The CLI-level plugin cache is governed separately by | ||
| * the plugin's `cacheKey`. | ||
| */ | ||
| cacheEnabled: boolean; | ||
| /** Register a file to be written to disk. Only available in generateTargets. */ | ||
@@ -10,0 +18,0 @@ emitFile(outPath: string, content: string): void; |
+39
-0
@@ -153,2 +153,41 @@ import type { ContractTypeNode, ContractRootNode, ModelNode, OpRootNode, ParamSource } from './ast.js'; | ||
| /** | ||
| * Walk the model graph from a set of seed types and return every transitively | ||
| * referenced model name. Bases are followed; aliased model `type` and field types | ||
| * are queued. Useful for cache fingerprinting — gives the slice of the model | ||
| * universe a particular op or contract root depends on. | ||
| * | ||
| * Models referenced by name but not present in `modelMap` are still included in | ||
| * the result (so a fingerprint that mentions them will detect when they appear | ||
| * later), but no further traversal happens through them. | ||
| */ | ||
| export function collectTransitiveModelRefs(seedTypes: ContractTypeNode[], modelMap: Map<string, ModelNode>): Set<string> { | ||
| const found = new Set<string>(); | ||
| const queue: ContractTypeNode[] = [...seedTypes]; | ||
| while (queue.length > 0) { | ||
| const t = queue.pop()!; | ||
| const refs = new Set<string>(); | ||
| collectTypeRefs(t, refs); | ||
| for (const ref of refs) { | ||
| if (found.has(ref)) continue; | ||
| found.add(ref); | ||
| const m = modelMap.get(ref); | ||
| if (!m) continue; | ||
| if (m.type) queue.push(m.type); | ||
| for (const f of m.fields) queue.push(f.type); | ||
| if (m.bases) { | ||
| for (const base of m.bases) { | ||
| if (found.has(base)) continue; | ||
| found.add(base); | ||
| const bm = modelMap.get(base); | ||
| if (!bm) continue; | ||
| if (bm.type) queue.push(bm.type); | ||
| for (const f of bm.fields) queue.push(f.type); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return found; | ||
| } | ||
| export function collectTypeRefs(type: ContractTypeNode, out: Set<string>): void { | ||
@@ -155,0 +194,0 @@ switch (type.kind) { |
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
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
1470234
7.4%94
5.62%10768
7.55%206
20.47%