2022-08-01 14:27:11 +10:00
|
|
|
import fsp from 'node:fs/promises';
|
|
|
|
|
import { OpenAPIV3 } from 'openapi-types';
|
2022-08-12 19:25:16 +10:00
|
|
|
import { format } from 'prettier';
|
2022-12-12 15:20:04 +11:00
|
|
|
import pkg from 'fast-json-patch';
|
|
|
|
|
const { observe, generate } = pkg;
|
2022-08-01 14:27:11 +10:00
|
|
|
|
|
|
|
|
export default async function apiGen(lookup: any) {
|
|
|
|
|
const spec: OpenAPIV3.Document = JSON.parse(
|
|
|
|
|
await fsp.readFile('./spec.json', 'utf8'),
|
|
|
|
|
);
|
2022-08-12 19:25:16 +10:00
|
|
|
const observer = observe(spec);
|
2022-08-01 14:27:11 +10:00
|
|
|
const tags = spec.tags;
|
2022-08-12 19:25:16 +10:00
|
|
|
|
|
|
|
|
await fsp.rmdir('./src/api', { recursive: true });
|
|
|
|
|
await fsp.rmdir('./__tests__/gen', { recursive: true });
|
|
|
|
|
|
|
|
|
|
await fsp.mkdir(`./src/api`);
|
|
|
|
|
await fsp.mkdir(`./__tests__/gen`);
|
2022-08-01 14:27:11 +10:00
|
|
|
await Promise.allSettled(
|
|
|
|
|
tags.map(({ name }) => fsp.mkdir(`./src/api/${name}`)),
|
|
|
|
|
);
|
|
|
|
|
const operationIds: string[] = [];
|
|
|
|
|
const operations: {
|
|
|
|
|
[key: string]: {
|
|
|
|
|
specSection: OpenAPIV3.PathItemObject['post' | 'get' | 'put' | 'delete'];
|
|
|
|
|
path: string;
|
|
|
|
|
method: string;
|
|
|
|
|
};
|
|
|
|
|
} = {};
|
|
|
|
|
Object.entries(spec.paths).forEach(([path, pathValue]) => {
|
|
|
|
|
Object.entries(pathValue).forEach(([method, _methodValue]) => {
|
|
|
|
|
const methodValue = _methodValue as OpenAPIV3.PathItemObject[
|
|
|
|
|
| 'post'
|
|
|
|
|
| 'get'
|
|
|
|
|
| 'put'
|
|
|
|
|
| 'delete'];
|
|
|
|
|
const operationId = (methodValue as any).operationId;
|
|
|
|
|
if (!operationId) {
|
|
|
|
|
throw `no operationId for ${path} ${method}`;
|
|
|
|
|
}
|
|
|
|
|
operations[operationId] = {
|
|
|
|
|
path,
|
|
|
|
|
method,
|
|
|
|
|
specSection: methodValue,
|
|
|
|
|
};
|
|
|
|
|
operationIds.push(operationId);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
const indexFile: {
|
|
|
|
|
[tag: string]: {
|
|
|
|
|
importsStr: string[];
|
|
|
|
|
exportsStr: string[];
|
|
|
|
|
};
|
|
|
|
|
} = {};
|
2022-08-02 05:55:20 +10:00
|
|
|
const writePromises = Object.entries(operations).map(
|
|
|
|
|
async ([operationId, operation]) => {
|
|
|
|
|
if ('hidden' === (operation.specSection as any).tags[0]) {
|
2022-08-12 19:25:16 +10:00
|
|
|
return [];
|
2022-08-01 14:27:11 +10:00
|
|
|
}
|
2022-08-12 19:25:16 +10:00
|
|
|
let template: string = (await fsp.readFile('./src/template.md', 'utf8'))
|
|
|
|
|
.replaceAll('```typescript', '')
|
|
|
|
|
.replaceAll('```', '');
|
|
|
|
|
let exampleTemplate: string = (
|
|
|
|
|
await fsp.readFile('./src/exampleAndGenTestTemplate.md', 'utf8')
|
|
|
|
|
)
|
|
|
|
|
.replaceAll('```typescript', '')
|
|
|
|
|
.replaceAll('```', '');
|
|
|
|
|
|
|
|
|
|
const importedParamTypes: string[] = [];
|
2022-08-02 05:55:20 +10:00
|
|
|
const path = operation.path;
|
|
|
|
|
const params = operation.specSection
|
|
|
|
|
.parameters as OpenAPIV3.ParameterObject[];
|
|
|
|
|
template = template.replaceAll(',', ',');
|
2022-08-12 19:25:16 +10:00
|
|
|
const inputTypes: string[] = ['client?: Client'];
|
|
|
|
|
const inputParams: string[] = ['client'];
|
|
|
|
|
const inputParamsExamples: string[] = [];
|
2022-08-02 05:55:20 +10:00
|
|
|
let urlPathParams: string[] = path.split('/');
|
|
|
|
|
const urlQueryParams: string[] = [];
|
2022-08-12 19:25:16 +10:00
|
|
|
(params || []).forEach(({ name, in: _in, schema }) => {
|
|
|
|
|
let _type = 'any';
|
|
|
|
|
if ('$ref' in schema) {
|
|
|
|
|
const ref = schema.$ref;
|
|
|
|
|
_type = lookup[ref];
|
|
|
|
|
importedParamTypes.push(_type);
|
|
|
|
|
const refName = ref.split('/').pop();
|
|
|
|
|
const reffedSchema = spec.components.schemas[refName];
|
|
|
|
|
if ('$ref' in reffedSchema) {
|
|
|
|
|
throw 'bad';
|
|
|
|
|
}
|
|
|
|
|
if (reffedSchema.type === 'string' && reffedSchema.enum) {
|
2022-12-11 16:09:58 -08:00
|
|
|
if (operationId.includes('file') && name === 'src_format') {
|
|
|
|
|
const input =
|
|
|
|
|
reffedSchema.enum.find((fmt) => fmt === 'obj') ||
|
|
|
|
|
reffedSchema.enum.find((fmt) => fmt === 'svg');
|
|
|
|
|
inputParamsExamples.push(`${name}: '${input}'`);
|
2022-08-12 19:25:16 +10:00
|
|
|
} else if (name === 'output_format') {
|
|
|
|
|
inputParamsExamples.push(`${name}: '${reffedSchema.enum[0]}'`);
|
|
|
|
|
} else {
|
|
|
|
|
inputParamsExamples.push(`${name}: '${reffedSchema.enum[1]}'`);
|
|
|
|
|
}
|
2023-06-01 10:07:07 +10:00
|
|
|
} else if ('oneOf' in reffedSchema) {
|
|
|
|
|
const isOutput = ['output_unit', 'output_format'].includes(name);
|
|
|
|
|
const input = (reffedSchema.oneOf?.find(
|
|
|
|
|
(_input: OpenAPIV3.SchemaObject) =>
|
|
|
|
|
(_input?.enum[0] === 'obj' && !isOutput) ||
|
|
|
|
|
_input?.enum[0] === 'svg' ||
|
|
|
|
|
_input?.enum[0] === 'stl',
|
|
|
|
|
) ||
|
|
|
|
|
reffedSchema.oneOf?.[isOutput ? 1 : 0]) as OpenAPIV3.SchemaObject;
|
|
|
|
|
inputParamsExamples.push(`${name}: '${input?.enum?.[0]}'`);
|
2022-08-12 19:25:16 +10:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (schema.type === 'number' || schema.type === 'integer') {
|
|
|
|
|
_type = 'number';
|
|
|
|
|
inputParamsExamples.push(`${name}: ${7}`);
|
|
|
|
|
} else if (schema.type === 'string' || schema.type === 'boolean') {
|
|
|
|
|
inputParamsExamples.push(
|
|
|
|
|
`${name}: ${schema.type === 'string' ? "'string'" : 'true'}`,
|
|
|
|
|
);
|
|
|
|
|
_type = schema.type;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
inputTypes.push(`${name}: ${_type}`);
|
2022-08-02 05:55:20 +10:00
|
|
|
if (!name) {
|
|
|
|
|
throw 'no name for param';
|
|
|
|
|
}
|
|
|
|
|
inputParams.push(name);
|
|
|
|
|
if (_in === 'path') {
|
|
|
|
|
urlPathParams = urlPathParams.map((p) => {
|
|
|
|
|
if (p === `{${name}}`) {
|
|
|
|
|
return `\${${name}}`;
|
|
|
|
|
}
|
|
|
|
|
return p;
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
urlQueryParams.push(`${name}=\${${name}}`);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
const templateUrlPath = wrapInBacktics(
|
|
|
|
|
`${urlPathParams.join('/')}${
|
|
|
|
|
urlQueryParams.length ? `?${urlQueryParams.join('&')}` : ''
|
|
|
|
|
}`,
|
|
|
|
|
);
|
|
|
|
|
const requestBody = operation.specSection
|
|
|
|
|
?.requestBody as OpenAPIV3.ResponseObject;
|
|
|
|
|
|
|
|
|
|
if (requestBody?.content?.['application/octet-stream']) {
|
|
|
|
|
const schema = requestBody.content['application/octet-stream']
|
|
|
|
|
.schema as OpenAPIV3.SchemaObject;
|
|
|
|
|
if (schema?.type !== 'string') {
|
|
|
|
|
throw 'requestBody type not implemented';
|
|
|
|
|
}
|
|
|
|
|
inputTypes.push('body: string');
|
|
|
|
|
inputParams.push('body');
|
2023-06-01 10:07:07 +10:00
|
|
|
const srcFmts = inputParamsExamples.find((str) => {
|
|
|
|
|
return str.startsWith('src_format:');
|
|
|
|
|
});
|
|
|
|
|
const exampleFile = !srcFmts
|
|
|
|
|
? 'example'
|
|
|
|
|
: srcFmts.includes('obj')
|
2022-12-11 16:09:58 -08:00
|
|
|
? 'example.obj'
|
|
|
|
|
: 'example.svg';
|
2022-08-12 19:25:16 +10:00
|
|
|
inputParamsExamples.push(
|
2022-12-11 16:09:58 -08:00
|
|
|
`body: await fsp.readFile('./${exampleFile}', 'base64')`,
|
2022-08-12 19:25:16 +10:00
|
|
|
);
|
|
|
|
|
exampleTemplate = `import fsp from 'fs/promises';` + exampleTemplate;
|
2022-08-02 05:55:20 +10:00
|
|
|
template = template.replaceAll("body: 'BODY'", 'body');
|
2022-08-01 14:27:11 +10:00
|
|
|
} else {
|
2022-08-02 05:55:20 +10:00
|
|
|
template = template.replaceAll(/body: 'BODY'.+/g, '');
|
2022-08-01 14:27:11 +10:00
|
|
|
}
|
|
|
|
|
|
2022-08-12 19:25:16 +10:00
|
|
|
if (inputParams.length === 1) {
|
2022-08-02 05:55:20 +10:00
|
|
|
template = replacer(template, [
|
2022-08-12 19:25:16 +10:00
|
|
|
[
|
|
|
|
|
'functionNameParams: FunctionNameParams,',
|
|
|
|
|
'functionNameParams: FunctionNameParams = {},',
|
|
|
|
|
],
|
|
|
|
|
]);
|
|
|
|
|
exampleTemplate = replacer(exampleTemplate, [
|
|
|
|
|
[`{ param: 'param' }`, ''],
|
2022-08-02 05:55:20 +10:00
|
|
|
]);
|
2022-08-01 14:27:11 +10:00
|
|
|
}
|
2022-08-02 05:55:20 +10:00
|
|
|
const importedTypes: string[] = [];
|
|
|
|
|
Object.values(operation.specSection?.responses).forEach((response) => {
|
|
|
|
|
const schema = (response as any)?.content?.['application/json']
|
|
|
|
|
?.schema as OpenAPIV3.SchemaObject;
|
|
|
|
|
if (!schema) {
|
|
|
|
|
let ref = (response as any)?.$ref || '';
|
|
|
|
|
ref = ref.replace('responses', 'schemas');
|
|
|
|
|
const typeReference = lookup[ref];
|
2022-08-01 14:27:11 +10:00
|
|
|
|
2022-08-02 05:55:20 +10:00
|
|
|
if (!importedTypes.includes(typeReference) && ref) {
|
|
|
|
|
importedTypes.push(typeReference);
|
|
|
|
|
}
|
|
|
|
|
} else if (
|
|
|
|
|
(response as any)?.content['application/json']?.schema?.$ref
|
|
|
|
|
) {
|
|
|
|
|
const ref = (response as any)?.content['application/json']?.schema
|
|
|
|
|
?.$ref;
|
|
|
|
|
const typeReference = lookup[ref];
|
|
|
|
|
if (!importedTypes.includes(typeReference)) {
|
|
|
|
|
importedTypes.push(typeReference);
|
|
|
|
|
}
|
2023-06-01 10:07:07 +10:00
|
|
|
} else if (
|
|
|
|
|
Object.keys(schema).length === 0 ||
|
|
|
|
|
schema.type === 'string'
|
|
|
|
|
) {
|
2022-08-02 05:55:20 +10:00
|
|
|
// do nothing
|
|
|
|
|
} else if (schema.type === 'array') {
|
|
|
|
|
const items = schema.items as OpenAPIV3.SchemaObject;
|
|
|
|
|
if ((items as any).$ref) {
|
|
|
|
|
const typeReference = lookup[(items as any).$ref];
|
|
|
|
|
if (!importedTypes.includes(typeReference + '[]')) {
|
|
|
|
|
importedTypes.push(typeReference + '[]');
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-06-01 10:07:07 +10:00
|
|
|
throw 'only ref arrays implemented';
|
2022-08-01 14:27:11 +10:00
|
|
|
}
|
|
|
|
|
} else {
|
2022-08-12 19:25:16 +10:00
|
|
|
console.log('apiGen', schema);
|
2022-08-01 14:27:11 +10:00
|
|
|
throw 'not implemented';
|
|
|
|
|
}
|
2022-08-02 05:55:20 +10:00
|
|
|
});
|
2022-08-01 14:27:11 +10:00
|
|
|
|
2022-08-02 05:55:20 +10:00
|
|
|
const returnTyping = `type ${FC(operationId)}_return = ${
|
|
|
|
|
importedTypes.length ? importedTypes.join(' | ') : 'any'
|
|
|
|
|
}`;
|
2022-08-01 14:27:11 +10:00
|
|
|
|
2022-08-02 05:55:20 +10:00
|
|
|
template = replacer(template, [
|
|
|
|
|
[/interface FunctionNameReturn(.|\n)+?}/g, returnTyping],
|
|
|
|
|
[`'string' + functionNameParams.exampleParam`, templateUrlPath],
|
|
|
|
|
[
|
|
|
|
|
`functionNameParams:`,
|
|
|
|
|
`{${inputParams.filter((a) => a).join(', ')}}:`,
|
|
|
|
|
],
|
|
|
|
|
[`exampleParam: string`, inputTypes.join('; ')],
|
2022-08-12 19:25:16 +10:00
|
|
|
["method: 'METHOD'", `method: '${operation.method.toUpperCase()}'`],
|
2022-08-02 05:55:20 +10:00
|
|
|
['function functionName', `function ${operationId}`],
|
|
|
|
|
['FunctionNameReturn', `${FC(operationId)}_return`],
|
|
|
|
|
['FunctionNameParams', `${FC(operationId)}_params`],
|
|
|
|
|
[
|
|
|
|
|
"import * as types from './src/models.ts';",
|
2022-08-12 19:25:16 +10:00
|
|
|
`import {${[...new Set([...importedTypes, ...importedParamTypes])]
|
2022-08-02 05:55:20 +10:00
|
|
|
.map((a) => (a || '').replaceAll('[', '').replaceAll(']', ''))
|
|
|
|
|
.join(', ')}} from '../../models.js';`,
|
|
|
|
|
],
|
|
|
|
|
]);
|
2022-08-01 14:27:11 +10:00
|
|
|
|
2022-08-02 05:55:20 +10:00
|
|
|
const tag = operation.specSection?.tags?.[0] || 'err';
|
|
|
|
|
const safeTag = tag.replaceAll('-', '_');
|
2022-08-12 19:25:16 +10:00
|
|
|
exampleTemplate = replacer(exampleTemplate, [
|
|
|
|
|
[`param: 'param'`, inputParamsExamples.filter((a) => a).join(', ')],
|
|
|
|
|
['{ api }', `{ ${safeTag} }`],
|
|
|
|
|
['api.section', `${safeTag}.${operationId}`],
|
|
|
|
|
]);
|
|
|
|
|
if (
|
2023-02-04 08:42:34 +11:00
|
|
|
// definitely a bit of a hack, these should probably be fixed,
|
|
|
|
|
// or at the very least checked periodically.
|
2023-06-01 10:07:07 +10:00
|
|
|
// underscores before the period should be replaced with hyphens
|
2022-08-12 19:25:16 +10:00
|
|
|
[
|
|
|
|
|
'payments.delete_payment_information_for_user',
|
|
|
|
|
'users.delete_user_self',
|
|
|
|
|
'api-calls.get_api_call_for_user',
|
|
|
|
|
'api-calls.get_async_operation',
|
2023-04-06 19:55:39 -07:00
|
|
|
'payments.validate_customer_tax_information_for_user',
|
2023-06-01 10:07:07 +10:00
|
|
|
'api-calls.get_api_call',
|
|
|
|
|
'users.get_user_extended',
|
|
|
|
|
'payments.delete_payment_method_for_user',
|
|
|
|
|
'users.get_user',
|
|
|
|
|
'oauth2.device_auth_verify',
|
|
|
|
|
'users.get_user_front_hash_self',
|
|
|
|
|
'oauth2.oauth2_provider_callback',
|
|
|
|
|
'apps.apps_github_webhook',
|
2022-08-12 19:25:16 +10:00
|
|
|
].includes(`${tag.trim()}.${operationId.trim()}`)
|
|
|
|
|
) {
|
|
|
|
|
// these test are expected to fail
|
|
|
|
|
exampleTemplate = replacer(exampleTemplate, [
|
|
|
|
|
['expect(await example()).toBeTruthy();', ''],
|
2023-06-01 10:07:07 +10:00
|
|
|
[/const examplePromise = example(.|\n)+?.toBe\('timeout'\)/g, ''],
|
|
|
|
|
]);
|
|
|
|
|
} else if (
|
2023-08-01 15:11:46 -07:00
|
|
|
['ai.create_text_to_3d', 'ai.create_image_to_3d'].includes(
|
|
|
|
|
`${tag.trim()}.${operationId.trim()}`,
|
|
|
|
|
)
|
2023-06-01 10:07:07 +10:00
|
|
|
) {
|
|
|
|
|
exampleTemplate = replacer(exampleTemplate, [
|
|
|
|
|
['expect(await example()).toBeTruthy();', ''],
|
|
|
|
|
[/try {(.|\n)+?}(.|\n)+?}/g, ''],
|
2022-08-12 19:25:16 +10:00
|
|
|
]);
|
|
|
|
|
} else {
|
|
|
|
|
exampleTemplate = replacer(exampleTemplate, [
|
|
|
|
|
[/try {(.|\n)+?}(.|\n)+?}/g, ''],
|
2023-06-01 10:07:07 +10:00
|
|
|
[/const examplePromise = example(.|\n)+?.toBe\('timeout'\)/g, ''],
|
2022-08-12 19:25:16 +10:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
let genTest = exampleTemplate;
|
|
|
|
|
|
|
|
|
|
genTest = replacer(genTest, [
|
|
|
|
|
['console.log(JSON.stringify(response, null, 2));', ''],
|
|
|
|
|
]);
|
|
|
|
|
const genTestsWritePromise = fsp.writeFile(
|
|
|
|
|
`./__tests__/gen/${tag}-${operationId}.test.ts`,
|
|
|
|
|
genTest,
|
|
|
|
|
'utf8',
|
|
|
|
|
);
|
|
|
|
|
exampleTemplate = replacer(exampleTemplate, [
|
|
|
|
|
["from '../../src/index.js'", "from '@kittycad/lib'"],
|
|
|
|
|
[/describe\('Testing(.|\n)+?(}\);)(.|\n)+?(}\);)/g, ''],
|
|
|
|
|
[/.+return response;\n/g, ''],
|
|
|
|
|
]);
|
|
|
|
|
spec.paths[operation.path][operation.method]['x-typescript'] = {
|
|
|
|
|
example: format(exampleTemplate, {
|
|
|
|
|
parser: 'babel',
|
|
|
|
|
tabWidth: 4,
|
|
|
|
|
semi: false,
|
|
|
|
|
singleQuote: true,
|
|
|
|
|
arrowParens: 'avoid',
|
|
|
|
|
trailingComma: 'es5',
|
|
|
|
|
}),
|
|
|
|
|
libDocsLink: '',
|
|
|
|
|
};
|
|
|
|
|
|
2022-08-02 05:55:20 +10:00
|
|
|
if (!indexFile[safeTag]) {
|
|
|
|
|
indexFile[safeTag] = {
|
|
|
|
|
importsStr: [],
|
|
|
|
|
exportsStr: [],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
indexFile[safeTag].importsStr.push(
|
|
|
|
|
`import ${operationId} from './api/${tag}/${operationId}.js';`,
|
2022-08-01 14:27:11 +10:00
|
|
|
);
|
2022-08-02 05:55:20 +10:00
|
|
|
indexFile[safeTag].exportsStr.push(operationId);
|
2022-08-12 19:25:16 +10:00
|
|
|
const libWritePromise = fsp.writeFile(
|
2022-08-02 05:55:20 +10:00
|
|
|
`./src/api/${tag}/${operationId}.ts`,
|
|
|
|
|
template,
|
|
|
|
|
'utf8',
|
|
|
|
|
);
|
2022-08-12 19:25:16 +10:00
|
|
|
return [genTestsWritePromise, libWritePromise];
|
2022-08-02 05:55:20 +10:00
|
|
|
},
|
|
|
|
|
);
|
2022-08-12 19:25:16 +10:00
|
|
|
await Promise.all(writePromises.flat());
|
2022-08-02 05:55:20 +10:00
|
|
|
let indexFileString = '';
|
2022-08-02 09:59:24 +10:00
|
|
|
// sorts are added to keep a consistent order since awaiting the promises has non-deterministic order
|
|
|
|
|
Object.entries(indexFile)
|
|
|
|
|
.sort(([a], [b]) => (a > b ? 1 : -1))
|
|
|
|
|
.forEach(([tag, { importsStr: imports, exportsStr: exports }]) => {
|
|
|
|
|
indexFileString += imports.sort().join('\n') + '\n';
|
|
|
|
|
indexFileString += `export const ${tag} = { ${exports
|
|
|
|
|
.sort()
|
|
|
|
|
.join(', ')} };\n\n`;
|
|
|
|
|
});
|
2022-08-12 19:25:16 +10:00
|
|
|
indexFileString += `export type { Models } from './models.js';\n`;
|
|
|
|
|
indexFileString += `export { Client} from './client.js';\n`;
|
2022-08-02 05:55:20 +10:00
|
|
|
await fsp.writeFile(`./src/index.ts`, indexFileString, 'utf8');
|
2022-08-12 19:25:16 +10:00
|
|
|
spec.info['x-typescript'] = {
|
|
|
|
|
client: [
|
|
|
|
|
`// Create a client with your token.`,
|
|
|
|
|
`async function ExampleWithClient() {`,
|
2022-08-15 08:49:14 +10:00
|
|
|
` const client = new Client('your-token');`,
|
2022-08-12 19:25:16 +10:00
|
|
|
` const response = await meta.ping({ client });`,
|
|
|
|
|
` if ('error_code' in response) throw 'error';`,
|
|
|
|
|
` console.log(response.message); // 'pong'`,
|
|
|
|
|
`}`,
|
|
|
|
|
``,
|
|
|
|
|
`// - OR -`,
|
|
|
|
|
``,
|
|
|
|
|
`// Your token will be parsed from the environment`,
|
|
|
|
|
`// variable: 'KITTYCAD_TOKEN'.`,
|
|
|
|
|
`async function ExampleWithOutClient() {`,
|
|
|
|
|
` const response = await meta.ping();`,
|
|
|
|
|
` if ('error_code' in response) throw 'error';`,
|
|
|
|
|
` console.log(response.message); // 'pong'`,
|
|
|
|
|
`}`,
|
|
|
|
|
].join('\n'),
|
|
|
|
|
install: 'npm install @kittycad/lib\n# or \n$ yarn add @kittycad/lib',
|
|
|
|
|
};
|
|
|
|
|
const patch = generate(observer);
|
|
|
|
|
await fsp.writeFile(
|
|
|
|
|
`./kittycad.ts.patch.json`,
|
|
|
|
|
JSON.stringify(patch, null, 2),
|
|
|
|
|
'utf8',
|
|
|
|
|
);
|
2022-08-01 14:27:11 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function wrapInBacktics(str: string) {
|
|
|
|
|
return `\`${str}\``;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function replacer(
|
|
|
|
|
template: string,
|
|
|
|
|
replaceList: (
|
|
|
|
|
| [string | RegExp, string]
|
|
|
|
|
| [string | RegExp, string, boolean]
|
|
|
|
|
)[],
|
|
|
|
|
): string {
|
|
|
|
|
let result = template;
|
|
|
|
|
replaceList.forEach(([search, newVal, debug]) => {
|
|
|
|
|
if (debug) {
|
|
|
|
|
const newTemplate = result.replace(search, newVal);
|
|
|
|
|
console.log({ old: result, newTemplate });
|
|
|
|
|
}
|
|
|
|
|
result = result.replaceAll(search, newVal);
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isObj(obj: any) {
|
|
|
|
|
return (
|
|
|
|
|
typeof obj === 'object' &&
|
|
|
|
|
obj !== null &&
|
|
|
|
|
!Array.isArray(obj) &&
|
|
|
|
|
Object.keys(obj).length
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function FC(str: string): string {
|
|
|
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
|
|
|
}
|