typescript-scaffolder
Version:
 ### Unit Test Coverage: 97.12%
199 lines (198 loc) • 8.02 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateWebhookFixture = generateWebhookFixture;
const path = __importStar(require("path"));
const ts_morph_1 = require("ts-morph");
/**
* Generate a fixture file for a given interface by introspecting the interface's properties
* and assigning reasonable faker-generated defaults based on type (and sometimes name).
*
* The file is written to `${outputDir}/${interfaceName}.fixture.ts` and exports
* `simulatedWebhookPayload` typed as the given interface.
*/
function generateWebhookFixture(interfaceName, interfaceImportPath, outputDir, project, exportConstName = 'simulatedWebhookPayload') {
const normalizedImport = interfaceImportPath.startsWith('.')
? interfaceImportPath
: `./${interfaceImportPath}`;
const fixtureFilePath = path.join(outputDir, `${interfaceName}.fixture.ts`);
const fixtureFile = project.createSourceFile(fixtureFilePath, '', { overwrite: true });
// Type-only import of the interface
fixtureFile.addImportDeclaration({
isTypeOnly: true,
namedImports: [interfaceName],
moduleSpecifier: normalizedImport,
});
// Also import faker in the generated file
fixtureFile.addImportDeclaration({
moduleSpecifier: '@faker-js/faker',
namedImports: ['faker'],
});
// Try to load and parse the interface source to build a dynamic payload
const absInterfacePath = path.resolve(outputDir, normalizedImport.endsWith('.ts') ? normalizedImport : `${normalizedImport}.ts`);
const tmpProject = new ts_morph_1.Project({ useInMemoryFileSystem: false });
const ifaceSource = tmpProject.addSourceFileAtPathIfExists(absInterfacePath);
let objectLines = null;
if (ifaceSource) {
const iface = ifaceSource.getInterface(interfaceName);
if (iface) {
objectLines = iface.getProperties().map((prop) => {
const name = prop.getName();
const type = prop.getType();
const valueExpr = fakerExprFor(name, type, 2, 2);
return ` ${name}: ${valueExpr},`;
});
}
}
if (!objectLines) {
// Fallback stub when parsing failed
fixtureFile.addStatements([
`// TODO: Populate with faker-generated fields that satisfy ${interfaceName}`,
`export const ${exportConstName}: ${interfaceName} = {} as ${interfaceName};`,
]);
return;
}
fixtureFile.addStatements([
`// Auto-generated from interface ${interfaceName}`,
`export const ${exportConstName}: ${interfaceName} = {`,
...objectLines,
`} as ${interfaceName};`,
]);
}
function fakerExprFor(name, type, depth = 1, indent = 0) {
// Handle unions of string literals: pick the first literal value
if (type.isUnion()) {
const parts = type.getUnionTypes();
if (parts.length > 0 && parts.every((t) => t.isStringLiteral())) {
const v = parts[0].getLiteralValue();
return JSON.stringify(v);
}
// If union includes null/undefined with a primary type, take the primary
const nonNullable = parts.filter((t) => !t.isNull() && !t.isUndefined());
if (nonNullable.length === 1) {
return fakerExprFor(name, nonNullable[0], depth, indent);
}
}
// Primitive types
if (type.isString()) {
return fakerStringByName(name);
}
if (type.isNumber()) {
return 'faker.number.int({ min: 1, max: 10000 })';
}
if (type.isBoolean()) {
return 'faker.datatype.boolean()';
}
// Date type (as object)
const text = type.getText();
if (text === 'Date') {
return 'faker.date.past() as any as Date';
}
// Arrays
if (type.isArray()) {
const elem = type.getArrayElementType();
if (elem) {
const inner = fakerExprFor(name.replace(/s$/, '') || 'item', elem, Math.max(0, depth - 1), indent + 2);
return inner.includes('\n') ? `[\n${inner}\n${' '.repeat(indent)}]` : `[${inner}]`;
}
return '[]';
}
// String literal type
if (type.isStringLiteral()) {
return JSON.stringify(type.getLiteralValue());
}
// Enum-like: try to detect enum declaration and pick first member
const sym = type.getSymbol();
const decl = sym?.getDeclarations()[0];
if (decl && decl.getKindName && decl.getKindName() === 'EnumDeclaration') {
const enumName = sym.getName();
// Best-effort: use first member via Object.values
return `(Object.values(${enumName}) as any)[0]`;
}
// Object or anything else: shallow placeholder or nested object
return buildObjectLiteralForType(type, depth, indent);
}
function buildObjectLiteralForType(type, depth, indent) {
if (depth <= 0) {
return '{} as any';
}
const props = type.getProperties();
if (props.length === 0) {
return '{} as any';
}
const pad = ' '.repeat(indent);
const pad2 = ' '.repeat(indent + 2);
const lines = [`${pad}{`];
for (const sym of props) {
const name = sym.getName();
const decls = sym.getDeclarations();
if (decls.length === 0) {
continue;
}
const propType = decls[0].getType();
const valueExpr = fakerExprFor(name, propType, depth - 1, indent + 2);
lines.push(`${pad2}${name}: ${valueExpr},`);
}
lines.push(`${pad}}`);
return lines.join('\n');
}
function fakerStringByName(name) {
const lower = name.toLowerCase();
if (/(^|_)id$/.test(lower) || lower.endsWith('id'))
return 'faker.string.uuid()';
if (lower.includes('email'))
return 'faker.internet.email()';
if (lower.includes('name'))
return 'faker.person.fullName()';
if (lower.includes('phone'))
return 'faker.phone.number()';
if (lower.includes('url'))
return 'faker.internet.url()';
if (lower.includes('ip'))
return 'faker.internet.ip()';
if (lower.includes('city'))
return 'faker.location.city()';
if (lower.includes('country'))
return 'faker.location.country()';
if (lower.includes('postcode') || lower.includes('zip'))
return 'faker.location.zipCode()';
if (lower.includes('address'))
return 'faker.location.streetAddress()';
if (lower.includes('message') || lower.includes('desc'))
return 'faker.lorem.sentence()';
if (lower.includes('date') || lower.endsWith('at') || lower.endsWith('_at'))
return 'new Date().toISOString()';
return 'faker.lorem.word()';
}
;