Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | 1x 16x 12x 12x 18x 21x 18x 18x 18x 21x 63x 18x 16x 16x 16x 16x 16x 16x 7x 7x 9x 9x 3x 5x 5x 5x 5x 5x 5x 5x 5x 5x 4x 4x 4x 4x 4x | import { resolve, join, relative, dirname } from 'node:path';
import type { ContractRootNode, OpRootNode } from '@contractkit/core';
import { collectTypeRefs, collectPublicTypeNames } from '@contractkit/core';
export const TEMPLATE_VAR_RE = /\{\w+\}/;
export function resolveTemplate(template: string, vars: Record<string, string>): string {
return template.replace(/\{(\w+)\}/g, (_, key) => vars[key] ?? `{${key}}`);
}
export function includesFilename(p: string): boolean {
const last = p.split('/').pop() ?? '';
return last.includes('.');
}
export function commonDir(files: string[], rootDir: string): string {
Iif (files.length === 0) return resolve(rootDir);
const parts = files.map(f => dirname(f).split('/'));
const first = parts[0]!;
let depth = first.length;
for (const p of parts) {
for (let i = 0; i < depth; i++) {
Iif (p[i] !== first[i]) {
depth = i;
break;
}
}
}
return first.slice(0, depth).join('/') || '/';
}
// ─── Server / Zod output paths ─────────────────────────────────────────────
export function computeOpOutPath(
filePath: string,
baseDir: string,
output: string | undefined,
defaultSuffix: string,
commonRoot: string,
meta: Record<string, string> = {},
): string {
const baseName = filePath.split('/').pop()!;
const relDir = relative(commonRoot, dirname(filePath));
const filename = baseName.replace(/\.ck$/, '');
const defaultName = `${filename}${defaultSuffix}`;
const baseOutDir = resolve(baseDir);
if (output && TEMPLATE_VAR_RE.test(output)) {
const resolved = resolveTemplate(output, { filename, dir: relDir, ext: 'ck', ...meta });
Eif (includesFilename(resolved)) return join(baseOutDir, resolved);
return join(baseOutDir, resolved, defaultName);
}
Iif (output) {
if (includesFilename(output)) return join(baseOutDir, output);
return join(baseOutDir, output, relDir, defaultName);
}
return join(baseOutDir, relDir, defaultName);
}
export function computeContractOutPath(
filePath: string,
baseDir: string,
output: string | undefined,
defaultSuffix: string,
commonRoot: string,
meta: Record<string, string> = {},
): string {
return computeOpOutPath(filePath, baseDir, output, defaultSuffix, commonRoot, meta);
}
// ─── SDK output paths ──────────────────────────────────────────────────────
export function computeSdkOutPath(
filePath: string,
rootDir: string,
clientOutput: string | undefined,
commonRoot: string,
meta: Record<string, string> = {},
): string | null {
Iif (!filePath.endsWith('.ck')) return null;
const baseName = filePath.split('/').pop()!;
const defaultOutName = baseName.replace(/\.ck$/, '.client.ts');
const baseOutDir = resolve(rootDir);
const relDir = relative(commonRoot, dirname(filePath));
const filename = baseName.replace(/\.ck$/, '');
Eif (clientOutput && TEMPLATE_VAR_RE.test(clientOutput)) {
const resolved = resolveTemplate(clientOutput, { filename, dir: relDir, ext: 'ck', ...meta });
Eif (includesFilename(resolved)) return join(baseOutDir, resolved);
return join(baseOutDir, resolved, defaultOutName);
}
if (clientOutput) {
if (includesFilename(clientOutput)) return join(baseOutDir, clientOutput);
return join(baseOutDir, clientOutput, relDir, defaultOutName);
}
return join(baseOutDir, relDir, defaultOutName);
}
export function computeSdkTypeOutPath(
filePath: string,
rootDir: string,
typeOutput: string,
commonRoot: string,
meta: Record<string, string> = {},
): string | null {
if (!filePath.endsWith('.ck')) return null;
const baseName = filePath.split('/').pop()!;
const defaultOutName = baseName.replace(/\.ck$/, '.ts');
const baseOutDir = resolve(rootDir);
const relDir = relative(commonRoot, dirname(filePath));
const filename = baseName.replace(/\.ck$/, '');
if (TEMPLATE_VAR_RE.test(typeOutput)) {
const resolved = resolveTemplate(typeOutput, { filename, dir: relDir, ext: 'ck', ...meta });
if (includesFilename(resolved)) return join(baseOutDir, resolved);
return join(baseOutDir, resolved, defaultOutName);
}
if (includesFilename(typeOutput)) return join(baseOutDir, typeOutput);
return join(baseOutDir, typeOutput, relDir, defaultOutName);
}
export function generateBarrelFiles(contractPaths: string[]): { outPath: string; content: string }[] {
const byDir = new Map<string, string[]>();
for (const outPath of contractPaths) {
const dir = dirname(outPath);
const group = byDir.get(dir) ?? [];
group.push(outPath);
byDir.set(dir, group);
}
const results: { outPath: string; content: string }[] = [];
for (const [dir, files] of byDir) {
const exports = files
.map(f => `export * from './${f.split('/').pop()!.replace(/\.ts$/, '.js')}';`)
.sort()
.join('\n');
results.push({ outPath: join(dir, 'index.ts'), content: `// Auto-generated barrel file\n${exports}\n` });
}
return results;
}
export function computePubliclyReachableTypes(
opAsts: OpRootNode[],
contractAsts: ContractRootNode[],
modelsWithInput: Set<string>,
modelsWithOutput: Set<string> = new Set(),
): Set<string> | null {
if (opAsts.length === 0) return null;
const reachable = new Set<string>();
for (const opAst of opAsts) {
for (const name of collectPublicTypeNames(opAst, modelsWithInput, modelsWithOutput)) reachable.add(name);
}
const modelDeps = new Map<string, Set<string>>();
for (const contractAst of contractAsts) {
for (const model of contractAst.models) {
const deps = new Set<string>();
if (model.bases) for (const b of model.bases) deps.add(b);
if (model.type) collectTypeRefs(model.type, deps);
for (const field of model.fields) collectTypeRefs(field.type, deps);
modelDeps.set(model.name, deps);
}
}
const frontier = [...reachable];
while (frontier.length > 0) {
const name = frontier.pop()!;
const baseName = name.endsWith('Input') ? name.slice(0, -5) : name.endsWith('Output') ? name.slice(0, -6) : name;
for (const dep of modelDeps.get(baseName) ?? []) {
if (!reachable.has(dep)) {
reachable.add(dep);
frontier.push(dep);
}
if (modelsWithInput.has(dep)) {
const inputDep = `${dep}Input`;
if (!reachable.has(inputDep)) {
reachable.add(inputDep);
frontier.push(inputDep);
}
}
if (modelsWithOutput.has(dep)) {
const outputDep = `${dep}Output`;
if (!reachable.has(outputDep)) {
reachable.add(outputDep);
frontier.push(outputDep);
}
}
}
}
return reachable;
}
|