openpkg-sdk
Version:
TypeScript package specification SDK
961 lines (953 loc) • 32.4 kB
JavaScript
// src/extractor.ts
import * as ts4 from "typescript";
import * as path from "path";
import * as fs from "fs";
// src/utils/type-utils.ts
import * as ts from "typescript";
function collectReferencedTypes(type, typeChecker, referencedTypes2, visitedTypes = new Set) {
if (visitedTypes.has(type))
return;
visitedTypes.add(type);
const symbol = type.getSymbol();
if (symbol) {
const symbolName = symbol.getName();
if (!symbolName.startsWith("__") && !isBuiltInType(symbolName)) {
referencedTypes2.add(symbolName);
}
}
if (type.isIntersection()) {
for (const intersectionType of type.types) {
collectReferencedTypes(intersectionType, typeChecker, referencedTypes2, visitedTypes);
}
}
if (type.isUnion()) {
for (const unionType of type.types) {
collectReferencedTypes(unionType, typeChecker, referencedTypes2, visitedTypes);
}
}
if (type.flags & ts.TypeFlags.Object) {
const objectType = type;
if (objectType.objectFlags & ts.ObjectFlags.Reference) {
const typeRef = objectType;
if (typeRef.typeArguments) {
for (const typeArg of typeRef.typeArguments) {
collectReferencedTypes(typeArg, typeChecker, referencedTypes2, visitedTypes);
}
}
}
}
}
function isBuiltInType(name) {
const builtIns = [
"string",
"number",
"boolean",
"bigint",
"symbol",
"undefined",
"null",
"any",
"unknown",
"never",
"void",
"object",
"Array",
"Promise",
"Map",
"Set",
"WeakMap",
"WeakSet",
"Date",
"RegExp",
"Error",
"Function",
"Object",
"String",
"Number",
"Boolean",
"BigInt",
"Symbol",
"Uint8Array",
"Int8Array",
"Uint16Array",
"Int16Array",
"Uint32Array",
"Int32Array",
"Float32Array",
"Float64Array",
"BigInt64Array",
"BigUint64Array",
"Uint8ClampedArray",
"ArrayBuffer",
"ArrayBufferLike",
"DataView",
"Uint8ArrayConstructor",
"ArrayBufferConstructor",
"JSON",
"Math",
"Reflect",
"Proxy",
"Intl",
"globalThis",
"__type"
];
return builtIns.includes(name);
}
// src/utils/tsdoc-utils.ts
import * as ts2 from "typescript";
function parseJSDocComment(symbol, typeChecker, sourceFileOverride) {
const node = symbol.valueDeclaration || symbol.declarations?.[0];
if (!node)
return null;
const sourceFile = sourceFileOverride || node.getSourceFile();
const commentRanges = ts2.getLeadingCommentRanges(sourceFile.text, node.pos);
if (!commentRanges || commentRanges.length === 0) {
return null;
}
const lastComment = commentRanges[commentRanges.length - 1];
const commentText = sourceFile.text.substring(lastComment.pos, lastComment.end);
return parseJSDocText(commentText);
}
function parseJSDocText(commentText) {
const result = {
description: "",
params: [],
examples: []
};
const cleanedText = commentText.replace(/^\/\*\*\s*/, "").replace(/\s*\*\/$/, "").replace(/^\s*\* ?/gm, "");
const lines = cleanedText.split(`
`);
let currentTag = "";
let currentContent = [];
for (const line of lines) {
const tagMatch = line.match(/^@(\w+)(?:\s+(.*))?$/);
if (tagMatch) {
if (currentTag) {
processTag(result, currentTag, currentContent.join(`
`));
}
currentTag = tagMatch[1];
currentContent = tagMatch[2] ? [tagMatch[2]] : [];
} else if (currentTag) {
currentContent.push(line);
} else {
if (line.trim()) {
result.description += (result.description ? `
` : "") + line;
}
}
}
if (currentTag) {
processTag(result, currentTag, currentContent.join(`
`));
}
return result;
}
function processTag(result, tag, content) {
switch (tag) {
case "param":
case "parameter": {
const paramMatch = content.match(/^(?:\{([^}]+)\}\s+)?(\S+)(?:\s+-\s+)?(.*)$/);
if (paramMatch) {
const [, type, name, description] = paramMatch;
result.params.push({
name: name || "",
description: description || "",
type
});
}
break;
}
case "returns":
case "return": {
result.returns = content;
break;
}
case "example": {
result.examples?.push(content);
break;
}
}
}
function extractDestructuredParams(parsedDoc, paramName) {
const destructuredParams = new Map;
const paramPrefix = paramName + ".";
for (const param of parsedDoc.params) {
if (param.name.startsWith(paramPrefix)) {
const propertyName = param.name.substring(paramPrefix.length);
destructuredParams.set(propertyName, param.description);
} else if (param.name.includes(".") && paramName === "__0") {
const [prefix, propertyName] = param.name.split(".", 2);
if (propertyName) {
destructuredParams.set(propertyName, param.description);
}
}
}
return destructuredParams;
}
function getParameterDocumentation(param, paramDecl, typeChecker) {
const result = {
description: ""
};
const funcNode = paramDecl.parent;
if (ts2.isFunctionDeclaration(funcNode) || ts2.isFunctionExpression(funcNode)) {
const funcSymbol = typeChecker.getSymbolAtLocation(funcNode.name || funcNode);
if (funcSymbol) {
const parsedDoc = parseJSDocComment(funcSymbol, typeChecker);
if (parsedDoc) {
const paramName = param.getName();
const paramDoc = parsedDoc.params.find((p) => p.name === paramName || p.name.split(".")[0] === paramName);
if (paramDoc) {
result.description = paramDoc.description;
}
const destructuredProps = extractDestructuredParams(parsedDoc, paramName);
if (destructuredProps.size > 0) {
result.destructuredProperties = Array.from(destructuredProps.entries()).map(([name, description]) => ({
name,
description
}));
}
}
}
}
return result;
}
// src/utils/parameter-utils.ts
import * as ts3 from "typescript";
function propertiesToSchema(properties, description) {
const schema = {
type: "object",
properties: {}
};
const required = [];
for (const prop of properties) {
const propType = prop.type;
let propSchema;
if (typeof propType === "string") {
if (["string", "number", "boolean", "bigint", "null"].includes(propType)) {
propSchema = { type: propType === "bigint" ? "string" : propType };
} else {
propSchema = { type: propType };
}
} else if (propType && typeof propType === "object") {
propSchema = propType;
} else {
propSchema = { type: "any" };
}
if (prop.description) {
propSchema.description = prop.description;
}
schema.properties[prop.name] = propSchema;
if (!prop.optional) {
required.push(prop.name);
}
}
if (required.length > 0) {
schema.required = required;
}
if (description) {
schema.description = description;
}
return schema;
}
function formatTypeReference(type, typeChecker, typeRefs, referencedTypes2) {
const typeString = typeChecker.typeToString(type);
const primitives = ["string", "number", "boolean", "bigint", "symbol", "any", "unknown", "void", "undefined", "null", "never"];
if (primitives.includes(typeString)) {
if (typeString === "bigint") {
return { type: "string", format: "bigint" };
} else if (typeString === "undefined" || typeString === "null") {
return { type: "null" };
} else if (typeString === "void" || typeString === "never") {
return { type: "null" };
} else {
return { type: typeString };
}
}
if (type.isUnion()) {
const unionType = type;
const parts = unionType.types.map((t) => formatTypeReference(t, typeChecker, typeRefs, referencedTypes2));
return {
anyOf: parts
};
}
const symbol = type.getSymbol();
if (symbol) {
const symbolName = symbol.getName();
if (symbolName.startsWith("__")) {
if (type.getFlags() & ts3.TypeFlags.Object) {
const properties = type.getProperties();
if (properties.length > 0) {
const objSchema = {
type: "object",
properties: {}
};
const required = [];
for (const prop of properties) {
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
const propName = prop.getName();
objSchema.properties[propName] = formatTypeReference(propType, typeChecker, typeRefs, referencedTypes2);
if (!(prop.flags & ts3.SymbolFlags.Optional)) {
required.push(propName);
}
}
if (required.length > 0) {
objSchema.required = required;
}
return objSchema;
}
}
return { type: "object" };
} else {
if (typeRefs.has(symbolName)) {
return { $ref: `#/types/${symbolName}` };
}
if (referencedTypes2 && !isBuiltInType(symbolName)) {
referencedTypes2.add(symbolName);
return { $ref: `#/types/${symbolName}` };
}
if (symbolName === "Array") {
return { type: "array" };
} else if (symbolName === "Promise" || symbolName === "Uint8Array") {
return { type: symbolName };
}
return { $ref: `#/types/${symbolName}` };
}
}
if (type.isLiteral()) {
if (typeString.startsWith('"') && typeString.endsWith('"')) {
const literalValue = typeString.slice(1, -1);
return { enum: [literalValue] };
}
return { enum: [Number(typeString)] };
}
const typePattern = /^(\w+)(\s*\|\s*undefined)?$/;
const match = typeString.match(typePattern);
if (match) {
const [, typeName, hasUndefined] = match;
if (typeRefs.has(typeName) || !isBuiltInType(typeName)) {
if (hasUndefined) {
return {
anyOf: [
{ $ref: `#/types/${typeName}` },
{ type: "null" }
]
};
}
return { $ref: `#/types/${typeName}` };
}
}
return { type: typeString };
}
function structureParameter(param, paramDecl, paramType, typeChecker, typeRefs, functionDoc, paramDoc, referencedTypes2) {
const paramName = param.getName();
if (paramType.isIntersection()) {
const properties = [];
const intersectionType = paramType;
for (const subType of intersectionType.types) {
const symbol2 = subType.getSymbol();
const typeString = typeChecker.typeToString(subType);
if (!symbol2 || symbol2.getName().startsWith("__")) {
for (const prop of subType.getProperties()) {
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
let description = "";
if (functionDoc) {
let docParam = functionDoc.params.find((p) => p.name === `${paramName}.${prop.getName()}`);
if (!docParam && paramName === "__0") {
docParam = functionDoc.params.find((p) => p.name.endsWith(`.${prop.getName()}`));
}
if (docParam) {
description = docParam.description;
}
}
properties.push({
name: prop.getName(),
type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes2),
description,
optional: !!(prop.flags & ts3.SymbolFlags.Optional)
});
}
} else {
const symbolName = symbol2.getName();
for (const prop of subType.getProperties()) {
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
properties.push({
name: prop.getName(),
type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes2),
description: "",
optional: !!(prop.flags & ts3.SymbolFlags.Optional)
});
}
}
}
const actualName = paramName === "__0" && functionDoc ? getActualParamName(functionDoc) : paramName;
return {
name: actualName,
required: !typeChecker.isOptionalParameter(paramDecl),
description: paramDoc?.description || "",
schema: propertiesToSchema(properties)
};
}
if (paramType.isUnion()) {
const unionType = paramType;
const objectOptions = [];
let hasNonObjectTypes = false;
for (const subType of unionType.types) {
const symbol2 = subType.getSymbol();
if (!symbol2 || symbol2.getName().startsWith("__")) {
const properties = [];
for (const prop of subType.getProperties()) {
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
properties.push({
name: prop.getName(),
type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes2),
description: "",
optional: !!(prop.flags & ts3.SymbolFlags.Optional)
});
}
if (properties.length > 0) {
objectOptions.push({ properties });
}
} else {
hasNonObjectTypes = true;
}
}
if (objectOptions.length > 0 && !hasNonObjectTypes) {
return {
name: paramName,
required: !typeChecker.isOptionalParameter(paramDecl),
description: paramDoc?.description || "",
schema: {
oneOf: objectOptions.map((opt) => propertiesToSchema(opt.properties))
}
};
}
}
const symbol = paramType.getSymbol();
if (symbol && symbol.getName().startsWith("__") && paramType.getProperties().length > 0) {
const properties = [];
for (const prop of paramType.getProperties()) {
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
properties.push({
name: prop.getName(),
type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes2),
description: "",
optional: !!(prop.flags & ts3.SymbolFlags.Optional)
});
}
return {
name: paramName,
required: !typeChecker.isOptionalParameter(paramDecl),
description: paramDoc?.description || "",
schema: propertiesToSchema(properties)
};
}
const typeRef = formatTypeReference(paramType, typeChecker, typeRefs, referencedTypes2);
let schema;
if (typeof typeRef === "string") {
if (["string", "number", "boolean", "null", "undefined", "any", "unknown", "never", "void"].includes(typeRef)) {
schema = { type: typeRef };
} else {
schema = { type: typeRef };
}
} else {
schema = typeRef;
}
return {
name: paramName,
required: !typeChecker.isOptionalParameter(paramDecl),
description: paramDoc?.description || "",
schema
};
}
function getActualParamName(functionDoc) {
const docParam = functionDoc.params.find((p) => p.name.includes("."));
if (docParam) {
return docParam.name.split(".")[0];
}
return "__0";
}
// src/extractor.ts
async function extractPackageSpec(entryFile, packageDir, content, options) {
const baseDir = packageDir || path.dirname(entryFile);
const nodeModulesPath = path.join(baseDir, "node_modules");
const hasNodeModules = fs.existsSync(nodeModulesPath);
const resolveExternalTypes = options?.resolveExternalTypes ?? hasNodeModules;
if (hasNodeModules && resolveExternalTypes) {
console.log("node_modules detected, resolving external types");
}
const configPath = ts4.findConfigFile(baseDir, ts4.sys.fileExists, "tsconfig.json");
let compilerOptions = {
target: ts4.ScriptTarget.Latest,
module: ts4.ModuleKind.CommonJS,
lib: ["lib.es2021.d.ts"],
allowJs: true,
declaration: true,
moduleResolution: ts4.ModuleResolutionKind.NodeJs
};
if (configPath) {
const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile);
const parsedConfig = ts4.parseJsonConfigFileContent(configFile.config, ts4.sys, path.dirname(configPath));
compilerOptions = { ...compilerOptions, ...parsedConfig.options };
}
let program;
if (content !== undefined) {
const sourceFile2 = ts4.createSourceFile(entryFile, content, ts4.ScriptTarget.Latest, true);
const compilerHost = ts4.createCompilerHost(compilerOptions);
const originalGetSourceFile = compilerHost.getSourceFile;
compilerHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
if (fileName === entryFile) {
return sourceFile2;
}
return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
};
program = ts4.createProgram([entryFile], compilerOptions, compilerHost);
} else {
program = ts4.createProgram([entryFile], compilerOptions);
}
const typeChecker = program.getTypeChecker();
const sourceFile = program.getSourceFile(entryFile);
if (!sourceFile) {
throw new Error(`Could not load ${entryFile}`);
}
const packageJsonPath = path.join(baseDir, "package.json");
const packageJson = fs.existsSync(packageJsonPath) ? JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) : {};
const spec = {
$schema: "https://raw.githubusercontent.com/ryanwaits/openpkg/main/schemas/v0.1.0/openpkg.schema.json",
openpkg: "0.1.0",
meta: {
name: packageJson.name || "unknown",
version: packageJson.version || "1.0.0",
description: packageJson.description || "",
license: packageJson.license || "",
repository: packageJson.repository?.url || packageJson.repository || "",
ecosystem: "js/ts"
},
exports: [],
types: []
};
const typeRefs = new Map;
const typeDefinitions = new Map;
const referencedTypes2 = new Set;
let typeCounter = 0;
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
if (moduleSymbol) {
const exports = typeChecker.getExportsOfModule(moduleSymbol);
for (const symbol of exports) {
const declaration = symbol.valueDeclaration || symbol.declarations?.[0];
if (!declaration)
continue;
const name = symbol.getName();
if (ts4.isClassDeclaration(declaration) || ts4.isInterfaceDeclaration(declaration) || ts4.isTypeAliasDeclaration(declaration) || ts4.isEnumDeclaration(declaration)) {
typeRefs.set(name, name);
}
}
for (const symbol of exports) {
const declaration = symbol.valueDeclaration || symbol.declarations?.[0];
if (!declaration)
continue;
const name = symbol.getName();
const id = name;
if (ts4.isFunctionDeclaration(declaration)) {
spec.exports.push({
id,
name,
kind: "function",
signatures: getFunctionSignatures(declaration, typeChecker, typeRefs, referencedTypes2),
description: getJSDocComment(symbol, typeChecker),
source: getSourceLocation(declaration)
});
} else if (ts4.isClassDeclaration(declaration)) {
spec.exports.push({
id,
name,
kind: "class",
description: getJSDocComment(symbol, typeChecker),
source: getSourceLocation(declaration)
});
if (!typeDefinitions.has(name)) {
const typeDef = {
id,
name,
kind: "class",
description: getJSDocComment(symbol, typeChecker),
source: getSourceLocation(declaration)
};
typeDefinitions.set(name, typeDef);
spec.types?.push(typeDef);
typeRefs.set(name, id);
}
} else if (ts4.isInterfaceDeclaration(declaration)) {
spec.exports.push({
id,
name,
kind: "interface",
description: getJSDocComment(symbol, typeChecker),
source: getSourceLocation(declaration)
});
if (!typeDefinitions.has(name)) {
const typeDef = {
id,
name,
kind: "interface",
schema: interfaceToSchema(declaration, typeChecker, typeRefs, referencedTypes2),
description: getJSDocComment(symbol, typeChecker),
source: getSourceLocation(declaration)
};
typeDefinitions.set(name, typeDef);
spec.types?.push(typeDef);
typeRefs.set(name, id);
}
} else if (ts4.isTypeAliasDeclaration(declaration)) {
spec.exports.push({
id,
name,
kind: "type",
type: typeToRef(declaration.type, typeChecker, typeRefs),
description: getJSDocComment(symbol, typeChecker),
source: getSourceLocation(declaration)
});
if (!typeDefinitions.has(name)) {
const typeDef = {
id,
name,
kind: "type",
type: typeChecker.typeToString(typeChecker.getTypeAtLocation(declaration)),
description: getJSDocComment(symbol, typeChecker),
source: getSourceLocation(declaration)
};
typeDefinitions.set(name, typeDef);
spec.types?.push(typeDef);
typeRefs.set(name, id);
}
} else if (ts4.isEnumDeclaration(declaration)) {
spec.exports.push({
id,
name,
kind: "enum",
description: getJSDocComment(symbol, typeChecker),
source: getSourceLocation(declaration)
});
if (!typeDefinitions.has(name)) {
const typeDef = {
id,
name,
kind: "enum",
members: getEnumMembers(declaration),
description: getJSDocComment(symbol, typeChecker),
source: getSourceLocation(declaration)
};
typeDefinitions.set(name, typeDef);
spec.types?.push(typeDef);
typeRefs.set(name, id);
}
} else if (ts4.isVariableDeclaration(declaration)) {
const type = typeChecker.getTypeAtLocation(declaration);
spec.exports.push({
id,
name,
kind: "variable",
type: typeToRef(declaration, typeChecker, typeRefs),
description: getJSDocComment(symbol, typeChecker),
source: getSourceLocation(declaration)
});
}
}
for (const typeName of referencedTypes2) {
if (!typeRefs.has(typeName) && !typeDefinitions.has(typeName)) {
const allSourceFiles = program.getSourceFiles();
for (const file of allSourceFiles) {
if (!resolveExternalTypes && (file.fileName.includes("node_modules") || file.fileName.endsWith(".d.ts") && !file.fileName.startsWith(baseDir))) {
continue;
}
const fileSymbol = typeChecker.getSymbolAtLocation(file);
if (fileSymbol) {
const exports2 = typeChecker.getExportsOfModule(fileSymbol);
for (const exportSymbol of exports2) {
if (exportSymbol.getName() === typeName && !typeDefinitions.has(typeName)) {
const declaration = exportSymbol.valueDeclaration || exportSymbol.declarations?.[0];
if (!declaration)
continue;
if (ts4.isInterfaceDeclaration(declaration)) {
const typeDef = {
id: typeName,
name: typeName,
kind: "interface",
schema: interfaceToSchema(declaration, typeChecker, typeRefs, referencedTypes2),
description: getJSDocComment(exportSymbol, typeChecker),
source: getSourceLocation(declaration)
};
typeDefinitions.set(typeName, typeDef);
spec.types?.push(typeDef);
typeRefs.set(typeName, typeName);
} else if (ts4.isTypeAliasDeclaration(declaration)) {
const typeDef = {
id: typeName,
name: typeName,
kind: "type",
type: typeChecker.typeToString(typeChecker.getTypeAtLocation(declaration)),
description: getJSDocComment(exportSymbol, typeChecker),
source: getSourceLocation(declaration)
};
typeDefinitions.set(typeName, typeDef);
spec.types?.push(typeDef);
typeRefs.set(typeName, typeName);
}
}
}
}
}
}
}
}
return spec;
}
function getJSDocComment(symbol, typeChecker) {
const comments = symbol.getDocumentationComment(typeChecker);
return ts4.displayPartsToString(comments);
}
function getSourceLocation(node) {
const sourceFile = node.getSourceFile();
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
return {
file: sourceFile.fileName,
line: line + 1
};
}
function typeToRef(node, typeChecker, typeRefs, referencedTypes2) {
const type = typeChecker.getTypeAtLocation(node);
if (referencedTypes2) {
collectReferencedTypes(type, typeChecker, referencedTypes2);
}
return formatTypeReference(type, typeChecker, typeRefs, referencedTypes2);
}
function getFunctionSignatures(func, typeChecker, typeRefs, referencedTypes2) {
const signature = typeChecker.getSignatureFromDeclaration(func);
if (!signature)
return [];
const funcSymbol = typeChecker.getSymbolAtLocation(func.name || func);
const functionDoc = funcSymbol ? parseJSDocComment(funcSymbol, typeChecker) : null;
return [{
parameters: signature.getParameters().map((param) => {
const paramDecl = param.valueDeclaration;
const paramType = typeChecker.getTypeAtLocation(paramDecl);
if (referencedTypes2) {
collectReferencedTypes(paramType, typeChecker, referencedTypes2);
}
const paramDoc = getParameterDocumentation(param, paramDecl, typeChecker);
return structureParameter(param, paramDecl, paramType, typeChecker, typeRefs, functionDoc, paramDoc, referencedTypes2);
}),
returns: {
schema: signature.getReturnType() ? formatTypeReference(signature.getReturnType(), typeChecker, typeRefs, referencedTypes2) : { type: "void" },
description: functionDoc?.returns || ""
}
}];
}
function interfaceToSchema(iface, typeChecker, typeRefs, referencedTypes2) {
const schema = {
type: "object",
properties: {}
};
const required = [];
iface.members.filter(ts4.isPropertySignature).forEach((prop) => {
const propName = prop.name?.getText() || "";
if (prop.type && referencedTypes2) {
const propType = typeChecker.getTypeAtLocation(prop.type);
collectReferencedTypes(propType, typeChecker, referencedTypes2);
}
schema.properties[propName] = prop.type ? formatTypeReference(typeChecker.getTypeAtLocation(prop.type), typeChecker, typeRefs, referencedTypes2) : { type: "any" };
if (!prop.questionToken) {
required.push(propName);
}
});
if (required.length > 0) {
schema.required = required;
}
return schema;
}
function getEnumMembers(enumDecl) {
return enumDecl.members.map((member) => ({
name: member.name?.getText() || "",
value: member.initializer ? member.initializer.getText() : undefined,
description: ""
}));
}
// src/index.ts
import * as fs2 from "fs/promises";
import * as path2 from "path";
// src/types/openpkg.ts
import { z } from "zod";
var schemaSchema = z.lazy(() => z.union([
z.object({
type: z.enum(["string", "number", "boolean", "integer", "null", "array", "object"])
}),
z.object({
$ref: z.string()
}),
z.object({
type: z.literal("array"),
items: schemaSchema.optional(),
description: z.string().optional()
}),
z.object({
type: z.literal("object"),
properties: z.record(z.string(), schemaSchema).optional(),
required: z.array(z.string()).optional(),
description: z.string().optional(),
additionalProperties: z.union([z.boolean(), schemaSchema]).optional()
}),
z.object({
oneOf: z.array(schemaSchema),
description: z.string().optional()
}),
z.object({
anyOf: z.array(schemaSchema),
description: z.string().optional()
}),
z.object({
allOf: z.array(schemaSchema),
description: z.string().optional()
}),
z.object({
enum: z.array(z.union([z.string(), z.number(), z.null()])),
description: z.string().optional()
})
]));
var parameterSchema = z.object({
name: z.string(),
in: z.literal("query").optional(),
required: z.boolean().optional(),
description: z.string().optional(),
schema: schemaSchema
});
var returnTypeSchema = z.object({
schema: schemaSchema,
description: z.string().optional()
});
var classMemberSchema = z.object({
id: z.string(),
name: z.string(),
kind: z.enum(["method", "property", "constructor", "accessor"]),
visibility: z.enum(["public", "private", "protected"]).optional(),
signatures: z.array(z.object({
parameters: z.array(parameterSchema).optional(),
returns: returnTypeSchema.optional(),
description: z.string().optional()
})).optional(),
schema: schemaSchema.optional(),
description: z.string().optional(),
examples: z.array(z.string()).optional(),
flags: z.record(z.string(), z.boolean()).optional()
});
var enumMemberSchema = z.object({
id: z.string(),
name: z.string(),
value: z.union([z.string(), z.number()]).optional(),
description: z.string().optional()
});
var memberSchema = z.union([classMemberSchema, enumMemberSchema]);
var openPkgSchema = z.object({
$schema: z.string().optional(),
openpkg: z.literal("0.1.0"),
meta: z.object({
name: z.string(),
version: z.string(),
description: z.string().optional(),
license: z.string().optional(),
repository: z.string().optional(),
ecosystem: z.string().default("js/ts")
}),
exports: z.array(z.object({
id: z.string(),
name: z.string(),
kind: z.enum(["function", "class", "variable", "interface", "type", "enum", "module", "namespace", "reference"]),
signatures: z.array(z.object({
parameters: z.array(parameterSchema).optional(),
returns: returnTypeSchema.optional(),
description: z.string().optional()
})).optional(),
members: z.array(memberSchema).optional(),
schema: schemaSchema.optional(),
description: z.string().optional(),
examples: z.array(z.string()).optional(),
source: z.object({
file: z.string().optional(),
line: z.number().optional(),
url: z.string().optional()
}).optional(),
flags: z.record(z.string(), z.unknown()).optional(),
tags: z.array(z.object({
name: z.string(),
text: z.string()
})).optional()
})),
types: z.array(z.object({
id: z.string(),
name: z.string(),
kind: z.enum(["class", "interface", "type", "enum"]),
description: z.string().optional(),
schema: schemaSchema.optional(),
members: z.array(z.object({
name: z.string(),
value: z.union([z.string(), z.number()]).optional(),
description: z.string().optional()
})).optional(),
source: z.object({
file: z.string().optional(),
line: z.number().optional(),
url: z.string().optional()
}).optional(),
tags: z.array(z.object({
name: z.string(),
text: z.string()
})).optional(),
rawComments: z.string().optional()
})).optional(),
examples: z.array(z.object({})).optional(),
extensions: z.record(z.string(), z.unknown()).optional()
});
// src/index.ts
class OpenPkg {
options;
constructor(options = {}) {
this.options = {
includePrivate: false,
followImports: true,
...options
};
}
async analyze(code, fileName = "temp.ts") {
const tempDir = path2.dirname(fileName);
const result = await extractPackageSpec(fileName, tempDir, code, this.options);
return result;
}
async analyzeFile(filePath) {
const content = await fs2.readFile(filePath, "utf-8");
const dir = path2.dirname(filePath);
const result = await extractPackageSpec(filePath, dir, content, this.options);
return result;
}
async analyzeProject(entryPath) {
return this.analyzeFile(entryPath);
}
async analyzeWithDiagnostics(code, fileName) {
const spec = await this.analyze(code, fileName);
return {
spec,
diagnostics: []
};
}
}
async function analyze(code) {
return new OpenPkg().analyze(code);
}
async function analyzeFile(filePath) {
return new OpenPkg().analyzeFile(filePath);
}
export {
openPkgSchema,
extractPackageSpec,
analyzeFile,
analyze,
OpenPkg
};