Socket
Socket
Sign inDemoInstall

@airtasker/spot

Package Overview
Dependencies
85
Maintainers
1
Versions
49
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.1.27 to 0.1.28

build/lib/src/parsing/properties/endpoint-description.d.ts

18

build/cli/src/commands/generate.js

@@ -20,2 +20,3 @@ "use strict";

let { api: apiPath, language, generator, out: outDir } = flags;
const apiFileName = path.basename(apiPath, ".ts");
const api = await file_parser_1.parsePath(apiPath);

@@ -61,3 +62,6 @@ if (!generator) {

const generatedFiles = generators[generator][language](api);
for (const [relativePath, content] of Object.entries(generatedFiles)) {
for (let [relativePath, content] of Object.entries(generatedFiles)) {
if (relativePath.indexOf("*") !== -1) {
relativePath = relativePath.replace(/\*/g, apiFileName);
}
output_1.outputFile(outDir, relativePath, content);

@@ -107,6 +111,6 @@ }

json: api => ({
"types.json": json_schema_1.generateJsonSchema(api, "json")
"*.json": json_schema_1.generateJsonSchema(api, "json")
}),
yaml: api => ({
"types.yml": json_schema_1.generateJsonSchema(api, "yaml")
"*.yml": json_schema_1.generateJsonSchema(api, "yaml")
})

@@ -116,6 +120,6 @@ },

json: api => ({
"api.json": openapi2_1.generateOpenApiV2(api, "json")
"*.json": openapi2_1.generateOpenApiV2(api, "json")
}),
yaml: api => ({
"api.yml": openapi2_1.generateOpenApiV2(api, "yaml")
"*.yml": openapi2_1.generateOpenApiV2(api, "yaml")
})

@@ -125,6 +129,6 @@ },

json: api => ({
"api.json": openapi3_1.generateOpenApiV3(api, "json")
"*.json": openapi3_1.generateOpenApiV3(api, "json")
}),
yaml: api => ({
"api.yml": openapi3_1.generateOpenApiV3(api, "yaml")
"*.yml": openapi3_1.generateOpenApiV3(api, "yaml")
})

@@ -131,0 +135,0 @@ },

@@ -20,2 +20,3 @@ "use strict";

let { api: apiPath, language, generator, out: outDir } = flags;
const apiFileName = path.basename(apiPath, ".ts");
const api = await file_parser_1.parsePath(apiPath);

@@ -61,3 +62,6 @@ if (!generator) {

const generatedFiles = generators[generator][language](api);
for (const [relativePath, content] of Object.entries(generatedFiles)) {
for (let [relativePath, content] of Object.entries(generatedFiles)) {
if (relativePath.indexOf("*") !== -1) {
relativePath = relativePath.replace(/\*/g, apiFileName);
}
output_1.outputFile(outDir, relativePath, content);

@@ -107,6 +111,6 @@ }

json: api => ({
"types.json": json_schema_1.generateJsonSchema(api, "json")
"*.json": json_schema_1.generateJsonSchema(api, "json")
}),
yaml: api => ({
"types.yml": json_schema_1.generateJsonSchema(api, "yaml")
"*.yml": json_schema_1.generateJsonSchema(api, "yaml")
})

@@ -116,6 +120,6 @@ },

json: api => ({
"api.json": openapi2_1.generateOpenApiV2(api, "json")
"*.json": openapi2_1.generateOpenApiV2(api, "json")
}),
yaml: api => ({
"api.yml": openapi2_1.generateOpenApiV2(api, "yaml")
"*.yml": openapi2_1.generateOpenApiV2(api, "yaml")
})

@@ -125,6 +129,6 @@ },

json: api => ({
"api.json": openapi3_1.generateOpenApiV3(api, "json")
"*.json": openapi3_1.generateOpenApiV3(api, "json")
}),
yaml: api => ({
"api.yml": openapi3_1.generateOpenApiV3(api, "yaml")
"*.yml": openapi3_1.generateOpenApiV3(api, "yaml")
})

@@ -131,0 +135,0 @@ },

@@ -8,2 +8,3 @@ "use strict";

const compact = require("lodash/compact");
const uniqBy = require("lodash/uniqBy");
const defaultTo = require("lodash/defaultTo");

@@ -25,10 +26,7 @@ function generateOpenApiV2(api, format) {

swagger: "2.0",
tags: [
{
name: "TODO"
}
],
tags: getTags(api),
info: {
version: "0.0.0",
title: "TODO",
title: api.description.name,
description: api.description.description,
contact: {

@@ -47,5 +45,5 @@ name: "TODO"

operationId: endpointName,
description: "TODO",
description: endpoint.description,
consumes: consumes(api, endpoint),
tags: ["TODO"],
tags: endpoint.tags,
parameters: getParameters(api, endpoint),

@@ -70,2 +68,12 @@ responses: {

exports.openApiV2 = openApiV2;
function getTags(api) {
return uniqBy(Object.entries(api.endpoints).reduce((acc, [endpointName, endpoint]) => {
if (endpoint.tags) {
acc = acc.concat(endpoint.tags.map(tag => {
return { name: tag };
}));
}
return acc;
}, []), "name");
}
function getParameters(api, endpoint) {

@@ -72,0 +80,0 @@ const parameters = endpoint.path

@@ -9,2 +9,3 @@ "use strict";

const compact = require("lodash/compact");
const uniqBy = require("lodash/uniqBy");
const pickBy = require("lodash/pickBy");

@@ -27,10 +28,7 @@ const defaultTo = require("lodash/defaultTo");

openapi: "3.0.0",
tags: [
{
name: "TODO"
}
],
tags: getTags(api),
info: {
version: "0.0.0",
title: "TODO",
title: api.description.name,
description: api.description.description,
contact: {

@@ -49,4 +47,4 @@ name: "TODO"

operationId: endpointName,
description: "TODO",
tags: ["TODO"],
description: endpoint.description,
tags: endpoint.tags,
parameters: getParameters(api, endpoint),

@@ -78,2 +76,12 @@ ...pickBy({

exports.openApiV3 = openApiV3;
function getTags(api) {
return uniqBy(Object.entries(api.endpoints).reduce((acc, [endpointName, endpoint]) => {
if (endpoint.tags) {
acc = acc.concat(endpoint.tags.map(tag => {
return { name: tag };
}));
}
return acc;
}, []), "name");
}
function getParameters(api, endpoint) {

@@ -80,0 +88,0 @@ const parameters = endpoint.path

@@ -126,3 +126,3 @@ "use strict";

ts.createPropertyAssignment("responseType", ts.createStringLiteral("json")),
ts.createPropertyAssignment("headers", ts.createObjectLiteral(Object.entries(endpoint.headers).map(([headerName, header]) => ts.createPropertyAssignment(header.headerFieldName, ts.createIdentifier(headerName))))),
ts.createPropertyAssignment("headers", ts.createObjectLiteral(Object.entries(endpoint.headers).map(([headerName, header]) => ts.createPropertyAssignment(ts.createStringLiteral(header.headerFieldName), ts.createIdentifier(headerName))))),
...(includeRequest

@@ -129,0 +129,0 @@ ? [

@@ -1,3 +0,5 @@

export declare function api(description?: ApiDescription): (constructor: Function) => void;
export declare function api(description: ApiDescription): (constructor: Function) => void;
export interface ApiDescription {
name: string;
description: string;
}

@@ -8,4 +10,6 @@ export declare function endpoint(description: EndpointDescription): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;

path: string;
description?: string;
requestContentType?: HttpContentType;
successStatusCode?: number;
tags?: string[];
}

@@ -12,0 +16,0 @@ export declare function genericError<T>(): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function api(description = {}) {
function api(description) {
return (constructor) => { };

@@ -5,0 +5,0 @@ }

@@ -8,2 +8,3 @@ "use strict";

const compact = require("lodash/compact");
const uniqBy = require("lodash/uniqBy");
const defaultTo = require("lodash/defaultTo");

@@ -25,10 +26,7 @@ function generateOpenApiV2(api, format) {

swagger: "2.0",
tags: [
{
name: "TODO"
}
],
tags: getTags(api),
info: {
version: "0.0.0",
title: "TODO",
title: api.description.name,
description: api.description.description,
contact: {

@@ -47,5 +45,5 @@ name: "TODO"

operationId: endpointName,
description: "TODO",
description: endpoint.description,
consumes: consumes(api, endpoint),
tags: ["TODO"],
tags: endpoint.tags,
parameters: getParameters(api, endpoint),

@@ -70,2 +68,12 @@ responses: {

exports.openApiV2 = openApiV2;
function getTags(api) {
return uniqBy(Object.entries(api.endpoints).reduce((acc, [endpointName, endpoint]) => {
if (endpoint.tags) {
acc = acc.concat(endpoint.tags.map(tag => {
return { name: tag };
}));
}
return acc;
}, []), "name");
}
function getParameters(api, endpoint) {

@@ -72,0 +80,0 @@ const parameters = endpoint.path

@@ -9,2 +9,3 @@ "use strict";

const compact = require("lodash/compact");
const uniqBy = require("lodash/uniqBy");
const pickBy = require("lodash/pickBy");

@@ -27,10 +28,7 @@ const defaultTo = require("lodash/defaultTo");

openapi: "3.0.0",
tags: [
{
name: "TODO"
}
],
tags: getTags(api),
info: {
version: "0.0.0",
title: "TODO",
title: api.description.name,
description: api.description.description,
contact: {

@@ -49,4 +47,4 @@ name: "TODO"

operationId: endpointName,
description: "TODO",
tags: ["TODO"],
description: endpoint.description,
tags: endpoint.tags,
parameters: getParameters(api, endpoint),

@@ -78,2 +76,12 @@ ...pickBy({

exports.openApiV3 = openApiV3;
function getTags(api) {
return uniqBy(Object.entries(api.endpoints).reduce((acc, [endpointName, endpoint]) => {
if (endpoint.tags) {
acc = acc.concat(endpoint.tags.map(tag => {
return { name: tag };
}));
}
return acc;
}, []), "name");
}
function getParameters(api, endpoint) {

@@ -80,0 +88,0 @@ const parameters = endpoint.path

@@ -126,3 +126,3 @@ "use strict";

ts.createPropertyAssignment("responseType", ts.createStringLiteral("json")),
ts.createPropertyAssignment("headers", ts.createObjectLiteral(Object.entries(endpoint.headers).map(([headerName, header]) => ts.createPropertyAssignment(header.headerFieldName, ts.createIdentifier(headerName))))),
ts.createPropertyAssignment("headers", ts.createObjectLiteral(Object.entries(endpoint.headers).map(([headerName, header]) => ts.createPropertyAssignment(ts.createStringLiteral(header.headerFieldName), ts.createIdentifier(headerName))))),
...(includeRequest

@@ -129,0 +129,0 @@ ? [

@@ -1,3 +0,5 @@

export declare function api(description?: ApiDescription): (constructor: Function) => void;
export declare function api(description: ApiDescription): (constructor: Function) => void;
export interface ApiDescription {
name: string;
description: string;
}

@@ -8,4 +10,6 @@ export declare function endpoint(description: EndpointDescription): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;

path: string;
description?: string;
requestContentType?: HttpContentType;
successStatusCode?: number;
tags?: string[];
}

@@ -12,0 +16,0 @@ export declare function genericError<T>(): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function api(description = {}) {
function api(description) {
return (constructor) => { };

@@ -5,0 +5,0 @@ }

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

import { HttpContentType, HttpMethod } from "./lib";
import { ApiDescription, HttpContentType, HttpMethod } from "./lib";
export interface Api {

@@ -9,2 +9,3 @@ endpoints: {

};
description: ApiDescription;
}

@@ -14,4 +15,6 @@ export interface Endpoint {

path: PathComponent[];
description: string;
headers: Headers;
queryParams: QueryParamComponent[];
tags?: string[];
requestContentType?: HttpContentType;

@@ -18,0 +21,0 @@ requestType: Type;

@@ -11,2 +11,4 @@ "use strict";

const type_parser_1 = require("./type-parser");
const endpoint_method_1 = require("./nodes/endpoint-method");
const merge = require("lodash/merge");
/**

@@ -18,7 +20,3 @@ * Parses a TypeScript source file, as well as any other TypeScript files it imports recursively.

async function parsePath(sourcePath) {
const api = {
endpoints: {},
types: {}
};
await parseFileRecursively(api, new Set(), sourcePath);
const api = parseRootFile(sourcePath);
const errors = validator_1.validate(api);

@@ -31,5 +29,5 @@ if (errors.length > 0) {

exports.parsePath = parsePath;
async function parseFileRecursively(api, visitedPaths, sourcePath) {
if (!(await fs.existsSync(sourcePath))) {
if (await fs.existsSync(sourcePath + ".ts")) {
function extractSourceFile(sourcePath) {
if (!fs.existsSync(sourcePath)) {
if (fs.existsSync(sourcePath + ".ts")) {
sourcePath += ".ts";

@@ -41,11 +39,9 @@ }

}
if (visitedPaths.has(sourcePath) ||
path.resolve(sourcePath).startsWith(__dirname)) {
return;
}
else {
visitedPaths.add(sourcePath);
}
const fileContent = await fs.readFile(sourcePath, "utf8");
const fileContent = fs.readFileSync(sourcePath, "utf8");
const sourceFile = ts.createSourceFile(path.basename(sourcePath), fileContent, ts.ScriptTarget.Latest);
return sourceFile;
}
function getPathsRecursively(sourcePath) {
const importPaths = new Set();
const sourceFile = extractSourceFile(sourcePath);
for (const statement of sourceFile.statements) {

@@ -61,9 +57,79 @@ if (ts.isImportDeclaration(statement)) {

}
await parseFileRecursively(api, visitedPaths, path.join(sourcePath, "..", importPath));
if (!(importPaths.has(importPath) ||
path.resolve(importPath).startsWith(__dirname))) {
importPaths.add(path.join(sourcePath, "..", importPath));
}
}
else if (ts.isClassDeclaration(statement)) {
}
return new Set([
...importPaths,
...[...importPaths].reduce((acc, importPath) => {
return new Set([...acc, ...getPathsRecursively(importPath)]);
}, new Set())
]);
}
function parseRootFile(sourcePath) {
const importPaths = getPathsRecursively(sourcePath);
const api = {
endpoints: {},
types: {},
description: {
name: "",
description: ""
}
};
if (containsApiDeclaration(sourcePath)) {
const sourceFile = extractSourceFile(sourcePath);
for (const statement of sourceFile.statements) {
if (ts.isClassDeclaration(statement)) {
const apiDescription = api_class_1.parseApiClass(sourceFile, statement);
if (apiDescription) {
api.description = apiDescription;
}
api.endpoints = parseEndpoints(statement, sourceFile);
}
else if (ts.isTypeAliasDeclaration(statement)) {
const name = statement.name.getText(sourceFile);
api.types[name] = type_parser_1.extractType(sourceFile, statement.type);
}
else if (ts.isInterfaceDeclaration(statement)) {
const name = statement.name.getText(sourceFile);
api.types[name] = type_parser_1.extractObjectType(sourceFile, statement);
}
}
return [...importPaths].reduce((acc, path) => {
return merge(acc, parseFile(path));
}, api);
}
else {
throw panic_1.panic(`No @api declaration found at ${sourcePath}`);
}
}
function parseEndpoints(statement, sourceFile) {
const endpoints = {};
for (const member of statement.members) {
if (ts.isMethodDeclaration(member)) {
// Each endpoint must be defined only once.
const endpointName = member.name.getText(sourceFile);
if (endpoints[endpointName]) {
throw panic_1.panic(`Found multiple definitions of the same endpoint ${endpointName}`);
}
endpoints[endpointName] = endpoint_method_1.parseEndpointMethod(sourceFile, member);
}
}
return endpoints;
}
function parseFile(sourcePath) {
const api = {
endpoints: {},
types: {}
};
const sourceFile = extractSourceFile(sourcePath);
for (const statement of sourceFile.statements) {
if (ts.isClassDeclaration(statement)) {
const apiDecorator = decorators_1.extractSingleDecorator(sourceFile, statement, "api");
if (apiDecorator) {
api_class_1.parseApiClass(sourceFile, statement, api);
throw `@api cannot be defined more than once at ${sourcePath}`;
}
api.endpoints = parseEndpoints(statement, sourceFile);
}

@@ -79,2 +145,15 @@ else if (ts.isTypeAliasDeclaration(statement)) {

}
return api;
}
function containsApiDeclaration(sourcePath) {
const sourceFile = extractSourceFile(sourcePath);
for (const statement of sourceFile.statements) {
if (ts.isClassDeclaration(statement)) {
const apiDecorator = decorators_1.extractSingleDecorator(sourceFile, statement, "api");
if (apiDecorator) {
return true;
}
}
}
return false;
}
import * as ts from "typescript";
import { Api } from "../../models";
import { ApiDescription } from "@airtasker/spot";
/**
* Parses a top-level API class definition and the endpoints it defines, such as:
* ```
* @api()
* @api({
* name: "My API",
* description: "A really cool API"
* })
* class Api {

@@ -18,2 +21,2 @@ * @endpoint({

*/
export declare function parseApiClass(sourceFile: ts.SourceFile, classDeclaration: ts.ClassDeclaration, api: Api): void;
export declare function parseApiClass(sourceFile: ts.SourceFile, classDeclaration: ts.ClassDeclaration): ApiDescription;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const endpoint_method_1 = require("./endpoint-method");
const decorators_1 = require("../decorators");
const panic_1 = require("../panic");
const literal_parser_1 = require("../literal-parser");
/**
* Parses a top-level API class definition and the endpoints it defines, such as:
* ```
* @api()
* @api({
* name: "My API",
* description: "A really cool API"
* })
* class Api {

@@ -20,9 +24,30 @@ * @endpoint({

*/
function parseApiClass(sourceFile, classDeclaration, api) {
for (const member of classDeclaration.members) {
if (ts.isMethodDeclaration(member)) {
endpoint_method_1.parseEndpointMethod(sourceFile, member, api);
}
function parseApiClass(sourceFile, classDeclaration) {
const apiDecorator = decorators_1.extractSingleDecorator(sourceFile, classDeclaration, "api");
if (!apiDecorator) {
throw panic_1.panic("@api() decorator not found");
}
if (apiDecorator.arguments.length !== 1) {
throw panic_1.panic(`Expected exactly one argument for @api(), got ${apiDecorator.arguments.length}`);
}
const apiDescription = apiDecorator.arguments[0];
if (!literal_parser_1.isObjectLiteral(apiDescription)) {
throw panic_1.panic(`@api() expects an object literal, got this instead: ${classDeclaration.getText(sourceFile)}`);
}
return extractApiInfo(sourceFile, classDeclaration, apiDescription);
}
exports.parseApiClass = parseApiClass;
function extractApiInfo(sourceFile, classDeclaration, apiDescription) {
const nameLiteral = apiDescription.properties["name"];
if (!literal_parser_1.isStringLiteral(nameLiteral)) {
throw panic_1.panic(`Invalid name in api description: ${classDeclaration.getText(sourceFile)}`);
}
const descriptionLiteral = apiDescription.properties["description"];
if (!literal_parser_1.isStringLiteral(descriptionLiteral)) {
throw panic_1.panic(`Invalid name in api description: ${classDeclaration.getText(sourceFile)}`);
}
return {
name: nameLiteral.text,
description: descriptionLiteral.text
};
}
import * as ts from "typescript";
import { Api } from "../../models";
import { Endpoint } from "../../models";
/**

@@ -17,2 +17,2 @@ * Parses a method of an API class definition, such as:

*/
export declare function parseEndpointMethod(sourceFile: ts.SourceFile, methodDeclaration: ts.MethodDeclaration, api: Api): void;
export declare function parseEndpointMethod(sourceFile: ts.SourceFile, methodDeclaration: ts.MethodDeclaration): Endpoint;

@@ -16,2 +16,4 @@ "use strict";

const success_status_code_1 = require("../properties/success-status-code");
const endpoint_description_1 = require("../properties/endpoint-description");
const tags_1 = require("../properties/tags");
/**

@@ -31,13 +33,8 @@ * Parses a method of an API class definition, such as:

*/
function parseEndpointMethod(sourceFile, methodDeclaration, api) {
function parseEndpointMethod(sourceFile, methodDeclaration) {
// A method must have an @endpoint() decorator to qualify as an endpoint definition.
const endpointDecorator = decorators_1.extractSingleDecorator(sourceFile, methodDeclaration, "endpoint");
if (!endpointDecorator) {
return;
throw panic_1.panic("Expected to have @endpoint() for the method");
}
// Each endpoint must be defined only once.
const endpointName = methodDeclaration.name.getText(sourceFile);
if (api.endpoints[endpointName]) {
throw panic_1.panic(`Found multiple definitions of the same endpoint ${endpointName}`);
}
if (endpointDecorator.arguments.length !== 1) {

@@ -51,6 +48,8 @@ throw panic_1.panic(`Expected exactly one argument for @endpoint(), got ${endpointDecorator.arguments.length}`);

}
const endpoint = {
return {
method: method_1.extractMethod(sourceFile, methodDeclaration, endpointDescription),
path: path_1.extractPath(sourceFile, methodDeclaration, endpointDescription),
description: endpoint_description_1.extractEndpointDescription(sourceFile, methodDeclaration, endpointDescription),
requestContentType: request_content_type_1.extractRequestContentType(sourceFile, methodDeclaration, endpointDescription),
tags: tags_1.extractTags(sourceFile, methodDeclaration, endpointDescription),
headers: headers_1.extractHeaders(sourceFile, methodDeclaration),

@@ -64,4 +63,3 @@ queryParams: query_parameters_1.extractQueryParams(sourceFile, methodDeclaration),

};
api.endpoints[endpointName] = endpoint;
}
exports.parseEndpointMethod = parseEndpointMethod;

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

import { HttpContentType, HttpMethod } from "./lib";
import { ApiDescription, HttpContentType, HttpMethod } from "./lib";
export interface Api {

@@ -9,2 +9,3 @@ endpoints: {

};
description: ApiDescription;
}

@@ -14,4 +15,6 @@ export interface Endpoint {

path: PathComponent[];
description: string;
headers: Headers;
queryParams: QueryParamComponent[];
tags?: string[];
requestContentType?: HttpContentType;

@@ -18,0 +21,0 @@ requestType: Type;

@@ -11,2 +11,4 @@ "use strict";

const type_parser_1 = require("./type-parser");
const endpoint_method_1 = require("./nodes/endpoint-method");
const merge = require("lodash/merge");
/**

@@ -18,7 +20,3 @@ * Parses a TypeScript source file, as well as any other TypeScript files it imports recursively.

async function parsePath(sourcePath) {
const api = {
endpoints: {},
types: {}
};
await parseFileRecursively(api, new Set(), sourcePath);
const api = parseRootFile(sourcePath);
const errors = validator_1.validate(api);

@@ -31,5 +29,5 @@ if (errors.length > 0) {

exports.parsePath = parsePath;
async function parseFileRecursively(api, visitedPaths, sourcePath) {
if (!(await fs.existsSync(sourcePath))) {
if (await fs.existsSync(sourcePath + ".ts")) {
function extractSourceFile(sourcePath) {
if (!fs.existsSync(sourcePath)) {
if (fs.existsSync(sourcePath + ".ts")) {
sourcePath += ".ts";

@@ -41,11 +39,9 @@ }

}
if (visitedPaths.has(sourcePath) ||
path.resolve(sourcePath).startsWith(__dirname)) {
return;
}
else {
visitedPaths.add(sourcePath);
}
const fileContent = await fs.readFile(sourcePath, "utf8");
const fileContent = fs.readFileSync(sourcePath, "utf8");
const sourceFile = ts.createSourceFile(path.basename(sourcePath), fileContent, ts.ScriptTarget.Latest);
return sourceFile;
}
function getPathsRecursively(sourcePath) {
const importPaths = new Set();
const sourceFile = extractSourceFile(sourcePath);
for (const statement of sourceFile.statements) {

@@ -61,9 +57,79 @@ if (ts.isImportDeclaration(statement)) {

}
await parseFileRecursively(api, visitedPaths, path.join(sourcePath, "..", importPath));
if (!(importPaths.has(importPath) ||
path.resolve(importPath).startsWith(__dirname))) {
importPaths.add(path.join(sourcePath, "..", importPath));
}
}
else if (ts.isClassDeclaration(statement)) {
}
return new Set([
...importPaths,
...[...importPaths].reduce((acc, importPath) => {
return new Set([...acc, ...getPathsRecursively(importPath)]);
}, new Set())
]);
}
function parseRootFile(sourcePath) {
const importPaths = getPathsRecursively(sourcePath);
const api = {
endpoints: {},
types: {},
description: {
name: "",
description: ""
}
};
if (containsApiDeclaration(sourcePath)) {
const sourceFile = extractSourceFile(sourcePath);
for (const statement of sourceFile.statements) {
if (ts.isClassDeclaration(statement)) {
const apiDescription = api_class_1.parseApiClass(sourceFile, statement);
if (apiDescription) {
api.description = apiDescription;
}
api.endpoints = parseEndpoints(statement, sourceFile);
}
else if (ts.isTypeAliasDeclaration(statement)) {
const name = statement.name.getText(sourceFile);
api.types[name] = type_parser_1.extractType(sourceFile, statement.type);
}
else if (ts.isInterfaceDeclaration(statement)) {
const name = statement.name.getText(sourceFile);
api.types[name] = type_parser_1.extractObjectType(sourceFile, statement);
}
}
return [...importPaths].reduce((acc, path) => {
return merge(acc, parseFile(path));
}, api);
}
else {
throw panic_1.panic(`No @api declaration found at ${sourcePath}`);
}
}
function parseEndpoints(statement, sourceFile) {
const endpoints = {};
for (const member of statement.members) {
if (ts.isMethodDeclaration(member)) {
// Each endpoint must be defined only once.
const endpointName = member.name.getText(sourceFile);
if (endpoints[endpointName]) {
throw panic_1.panic(`Found multiple definitions of the same endpoint ${endpointName}`);
}
endpoints[endpointName] = endpoint_method_1.parseEndpointMethod(sourceFile, member);
}
}
return endpoints;
}
function parseFile(sourcePath) {
const api = {
endpoints: {},
types: {}
};
const sourceFile = extractSourceFile(sourcePath);
for (const statement of sourceFile.statements) {
if (ts.isClassDeclaration(statement)) {
const apiDecorator = decorators_1.extractSingleDecorator(sourceFile, statement, "api");
if (apiDecorator) {
api_class_1.parseApiClass(sourceFile, statement, api);
throw `@api cannot be defined more than once at ${sourcePath}`;
}
api.endpoints = parseEndpoints(statement, sourceFile);
}

@@ -79,2 +145,15 @@ else if (ts.isTypeAliasDeclaration(statement)) {

}
return api;
}
function containsApiDeclaration(sourcePath) {
const sourceFile = extractSourceFile(sourcePath);
for (const statement of sourceFile.statements) {
if (ts.isClassDeclaration(statement)) {
const apiDecorator = decorators_1.extractSingleDecorator(sourceFile, statement, "api");
if (apiDecorator) {
return true;
}
}
}
return false;
}
import * as ts from "typescript";
import { Api } from "../../models";
import { ApiDescription } from "@airtasker/spot";
/**
* Parses a top-level API class definition and the endpoints it defines, such as:
* ```
* @api()
* @api({
* name: "My API",
* description: "A really cool API"
* })
* class Api {

@@ -18,2 +21,2 @@ * @endpoint({

*/
export declare function parseApiClass(sourceFile: ts.SourceFile, classDeclaration: ts.ClassDeclaration, api: Api): void;
export declare function parseApiClass(sourceFile: ts.SourceFile, classDeclaration: ts.ClassDeclaration): ApiDescription;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const endpoint_method_1 = require("./endpoint-method");
const decorators_1 = require("../decorators");
const panic_1 = require("../panic");
const literal_parser_1 = require("../literal-parser");
/**
* Parses a top-level API class definition and the endpoints it defines, such as:
* ```
* @api()
* @api({
* name: "My API",
* description: "A really cool API"
* })
* class Api {

@@ -20,9 +24,30 @@ * @endpoint({

*/
function parseApiClass(sourceFile, classDeclaration, api) {
for (const member of classDeclaration.members) {
if (ts.isMethodDeclaration(member)) {
endpoint_method_1.parseEndpointMethod(sourceFile, member, api);
}
function parseApiClass(sourceFile, classDeclaration) {
const apiDecorator = decorators_1.extractSingleDecorator(sourceFile, classDeclaration, "api");
if (!apiDecorator) {
throw panic_1.panic("@api() decorator not found");
}
if (apiDecorator.arguments.length !== 1) {
throw panic_1.panic(`Expected exactly one argument for @api(), got ${apiDecorator.arguments.length}`);
}
const apiDescription = apiDecorator.arguments[0];
if (!literal_parser_1.isObjectLiteral(apiDescription)) {
throw panic_1.panic(`@api() expects an object literal, got this instead: ${classDeclaration.getText(sourceFile)}`);
}
return extractApiInfo(sourceFile, classDeclaration, apiDescription);
}
exports.parseApiClass = parseApiClass;
function extractApiInfo(sourceFile, classDeclaration, apiDescription) {
const nameLiteral = apiDescription.properties["name"];
if (!literal_parser_1.isStringLiteral(nameLiteral)) {
throw panic_1.panic(`Invalid name in api description: ${classDeclaration.getText(sourceFile)}`);
}
const descriptionLiteral = apiDescription.properties["description"];
if (!literal_parser_1.isStringLiteral(descriptionLiteral)) {
throw panic_1.panic(`Invalid name in api description: ${classDeclaration.getText(sourceFile)}`);
}
return {
name: nameLiteral.text,
description: descriptionLiteral.text
};
}
import * as ts from "typescript";
import { Api } from "../../models";
import { Endpoint } from "../../models";
/**

@@ -17,2 +17,2 @@ * Parses a method of an API class definition, such as:

*/
export declare function parseEndpointMethod(sourceFile: ts.SourceFile, methodDeclaration: ts.MethodDeclaration, api: Api): void;
export declare function parseEndpointMethod(sourceFile: ts.SourceFile, methodDeclaration: ts.MethodDeclaration): Endpoint;

@@ -16,2 +16,4 @@ "use strict";

const success_status_code_1 = require("../properties/success-status-code");
const endpoint_description_1 = require("../properties/endpoint-description");
const tags_1 = require("../properties/tags");
/**

@@ -31,13 +33,8 @@ * Parses a method of an API class definition, such as:

*/
function parseEndpointMethod(sourceFile, methodDeclaration, api) {
function parseEndpointMethod(sourceFile, methodDeclaration) {
// A method must have an @endpoint() decorator to qualify as an endpoint definition.
const endpointDecorator = decorators_1.extractSingleDecorator(sourceFile, methodDeclaration, "endpoint");
if (!endpointDecorator) {
return;
throw panic_1.panic("Expected to have @endpoint() for the method");
}
// Each endpoint must be defined only once.
const endpointName = methodDeclaration.name.getText(sourceFile);
if (api.endpoints[endpointName]) {
throw panic_1.panic(`Found multiple definitions of the same endpoint ${endpointName}`);
}
if (endpointDecorator.arguments.length !== 1) {

@@ -51,6 +48,8 @@ throw panic_1.panic(`Expected exactly one argument for @endpoint(), got ${endpointDecorator.arguments.length}`);

}
const endpoint = {
return {
method: method_1.extractMethod(sourceFile, methodDeclaration, endpointDescription),
path: path_1.extractPath(sourceFile, methodDeclaration, endpointDescription),
description: endpoint_description_1.extractEndpointDescription(sourceFile, methodDeclaration, endpointDescription),
requestContentType: request_content_type_1.extractRequestContentType(sourceFile, methodDeclaration, endpointDescription),
tags: tags_1.extractTags(sourceFile, methodDeclaration, endpointDescription),
headers: headers_1.extractHeaders(sourceFile, methodDeclaration),

@@ -64,4 +63,3 @@ queryParams: query_parameters_1.extractQueryParams(sourceFile, methodDeclaration),

};
api.endpoints[endpointName] = endpoint;
}
exports.parseEndpointMethod = parseEndpointMethod;

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

{"version":"0.1.27","commands":{"generate":{"id":"generate","description":"Runs a generator on an API. Used to produce client libraries, server boilerplates and well-known API contract formats such as OpenAPI.","pluginName":"@airtasker/spot","pluginType":"core","aliases":[],"examples":["$ api generate --language typescript --generator axios-client --out src/\nGenerated the following files:\n- src/types.ts\n- src/validators.ts\n- src/client.ts\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"api":{"name":"api","type":"option","char":"a","description":"Path to a TypeScript API definition","required":true},"language":{"name":"language","type":"option","char":"l","description":"Language to generate"},"generator":{"name":"generator","type":"option","char":"g","description":"Generator to run"},"out":{"name":"out","type":"option","char":"o","description":"Directory in which to output generated files"}},"args":[]},"init":{"id":"init","description":"Generates the boilerplate for an API.","pluginName":"@airtasker/spot","pluginType":"core","aliases":[],"examples":["$ api init\nGenerated the following files:\n- api.ts\n- tsconfig.json\n- package.json\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]}}}
{"version":"0.1.28","commands":{"generate":{"id":"generate","description":"Runs a generator on an API. Used to produce client libraries, server boilerplates and well-known API contract formats such as OpenAPI.","pluginName":"@airtasker/spot","pluginType":"core","aliases":[],"examples":["$ api generate --language typescript --generator axios-client --out src/\nGenerated the following files:\n- src/types.ts\n- src/validators.ts\n- src/client.ts\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"api":{"name":"api","type":"option","char":"a","description":"Path to a TypeScript API definition","required":true},"language":{"name":"language","type":"option","char":"l","description":"Language to generate"},"generator":{"name":"generator","type":"option","char":"g","description":"Generator to run"},"out":{"name":"out","type":"option","char":"o","description":"Directory in which to output generated files"}},"args":[]},"init":{"id":"init","description":"Generates the boilerplate for an API.","pluginName":"@airtasker/spot","pluginType":"core","aliases":[],"examples":["$ api init\nGenerated the following files:\n- api.ts\n- tsconfig.json\n- package.json\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]}}}
{
"name": "@airtasker/spot",
"version": "0.1.27",
"version": "0.1.28",
"author": "Francois Wouts",

@@ -62,4 +62,6 @@ "bin": {

"test": "jest",
"prettier:list": "prettier --list-different \"**/*.js\" \"**/*.jsx\" \"**/*.ts\" \"**/*.tsx\"",
"prettier:write": "prettier --write \"**/*.js\" \"**/*.jsx\" \"**/*.ts\" \"**/*.tsx\"",
"release": "yarn version && oclif-dev readme && git add README.md && git commit README.md -m \"Update README\" && git push && npm publish --access=public"
}
}

@@ -104,3 +104,3 @@ Spot

_See code: [build/cli/src/commands/generate.js](https://github.com/airtasker/spot/blob/v0.1.27/build/cli/src/commands/generate.js)_
_See code: [build/cli/src/commands/generate.js](https://github.com/airtasker/spot/blob/v0.1.28/build/cli/src/commands/generate.js)_

@@ -143,3 +143,3 @@ ## `spot help [COMMAND]`

_See code: [build/cli/src/commands/init.js](https://github.com/airtasker/spot/blob/v0.1.27/build/cli/src/commands/init.js)_
_See code: [build/cli/src/commands/init.js](https://github.com/airtasker/spot/blob/v0.1.28/build/cli/src/commands/init.js)_
<!-- commandsstop -->
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc