Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
/* eslint-disable prefer-const */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config({
|
||||
path: './assemblyai.env',
|
||||
});
|
||||
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import OpenAPIParser from '@readme/openapi-parser';
|
||||
import { mergician } from 'mergician';
|
||||
import { titleCase } from 'title-case';
|
||||
|
||||
const generatedPath = './src/lib/generated/';
|
||||
|
||||
type Generators = {
|
||||
props?: (schemas: any) => any;
|
||||
};
|
||||
const generateMap: Record<string, Generators> = {
|
||||
transcribe: {
|
||||
props: (schemas) => schemas.TranscriptParams,
|
||||
},
|
||||
'list-transcript': {
|
||||
props: (schemas) => schemas.ListTranscriptParams,
|
||||
},
|
||||
'lemur-task': {
|
||||
props: (schemas) => schemas.LemurTaskParams,
|
||||
},
|
||||
};
|
||||
|
||||
const merge = mergician({ appendArrays: true, dedupArrays: true });
|
||||
(async function () {
|
||||
const specLocation = process.env.OPENAPI_SPEC_LOCATION;
|
||||
if (!specLocation)
|
||||
throw new Error('OPENAPI_SPEC_LOCATION env variable is required');
|
||||
|
||||
let spec: any = merge(
|
||||
await OpenAPIParser.parse(specLocation),
|
||||
await OpenAPIParser.parse('./scripts/openapi.overrides.yml', {
|
||||
validate: {
|
||||
schema: false,
|
||||
spec: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
spec = await OpenAPIParser.dereference(spec);
|
||||
|
||||
Object.entries(generateMap).forEach(([paramsName, { props }]) => {
|
||||
const parametersPath = join(generatedPath, paramsName, 'props.ts');
|
||||
if (props) {
|
||||
let propsJson = createPropsFromSchema(props(spec.components.schemas));
|
||||
let propsTs = createTs(propsJson);
|
||||
|
||||
const dir = dirname(parametersPath);
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(parametersPath, propsTs, 'utf-8');
|
||||
} else if (existsSync(parametersPath)) {
|
||||
unlinkSync(parametersPath);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
function createPropsFromSchema(schema: any): Record<string, any> {
|
||||
if (!schema) return {};
|
||||
schema = structuredClone(schema);
|
||||
if (schema.allOf) {
|
||||
const obj = {};
|
||||
(schema.allOf as any[]).forEach((schema) =>
|
||||
Object.assign(obj, createPropsFromSchema(schema))
|
||||
);
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (!schema.properties) return {};
|
||||
|
||||
const properties: Record<string, unknown> = {};
|
||||
const requiredProperties = schema.required ?? [];
|
||||
for (let [key, value] of Object.entries(schema.properties) as [
|
||||
key: string,
|
||||
value: any
|
||||
]) {
|
||||
if (value['x-ap-ignore']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let nullable = false;
|
||||
let label: string;
|
||||
if (value['x-label']) {
|
||||
label = titleCase(value['x-label']);
|
||||
} else {
|
||||
label = titleCase(key);
|
||||
console.warn(`No x-label found for property ${key}`);
|
||||
}
|
||||
// grab the value of oneOf with null
|
||||
if (value.oneOf) {
|
||||
if (value.oneOf.findIndex((item: any) => item.type === 'null') > -1) {
|
||||
nullable = true;
|
||||
}
|
||||
|
||||
const options = value.oneOf.filter((item: any) => item.type !== 'null');
|
||||
// take first one and hope for the best
|
||||
value.oneOf = undefined;
|
||||
const option = {
|
||||
type: options[0].type,
|
||||
enum: options[0].enum,
|
||||
format: options[0].format,
|
||||
items: options[0].items,
|
||||
'x-aai-enum': options[0]['x-aai-enum'],
|
||||
anyOf: options[0].anyOf,
|
||||
};
|
||||
value = { ...value, ...option };
|
||||
if (options[0].properties) {
|
||||
value.properties = { ...value.properties, ...options[0].properties };
|
||||
}
|
||||
if (options[0].required) {
|
||||
value.required = (value.required || []).concat(options[0].required);
|
||||
}
|
||||
}
|
||||
if (value.anyOf) {
|
||||
const enumAnyOfIndex = value.anyOf.findIndex((item: any) => item.enum);
|
||||
// if any string or an enum, use the enum
|
||||
if (
|
||||
value.anyOf.findIndex((item: any) => item.type === 'string') > -1 &&
|
||||
enumAnyOfIndex > -1
|
||||
) {
|
||||
value.type = value.anyOf[enumAnyOfIndex].type;
|
||||
value.enum = value.anyOf[enumAnyOfIndex].enum;
|
||||
if ('x-aai-enum' in value.anyOf[enumAnyOfIndex]) {
|
||||
value['x-aai-enum'] = value.anyOf[enumAnyOfIndex]['x-aai-enum'];
|
||||
}
|
||||
value.anyOf = undefined;
|
||||
} else {
|
||||
throw new Error(`Unsupported AnyOf found for ${key}`);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(value.type)) {
|
||||
if (value.type.indexOf('null') > -1) {
|
||||
nullable = true;
|
||||
}
|
||||
const types = value.type.filter((type: string) => type !== 'null');
|
||||
if (types.length === 1) {
|
||||
value.type = types[0];
|
||||
} else {
|
||||
throw new Error(`Multiple types found for ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
const required = requiredProperties.indexOf(key) > -1 && !nullable;
|
||||
|
||||
// handleArray
|
||||
if (value.type === 'array' && value.items.type === 'object') {
|
||||
properties[key] = {
|
||||
displayName: label,
|
||||
description: value.description,
|
||||
type: 'Array',
|
||||
required,
|
||||
properties: { ...createPropsFromSchema(value.items) },
|
||||
};
|
||||
} else if (
|
||||
value.items &&
|
||||
value.items.type === 'string' &&
|
||||
value.items.enum
|
||||
) {
|
||||
properties[key] = {
|
||||
...createField(key, label, value.items, required),
|
||||
displayName: label,
|
||||
description: value.description,
|
||||
type: 'StaticMultiSelectDropdown',
|
||||
required,
|
||||
};
|
||||
} else {
|
||||
// default field
|
||||
properties[key] = createField(key, label, value, required);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
function createField(
|
||||
key: string,
|
||||
label: string,
|
||||
value: any,
|
||||
required: boolean
|
||||
) {
|
||||
const field: any = {
|
||||
displayName: label,
|
||||
type: mapType(value),
|
||||
required,
|
||||
description: value.description,
|
||||
};
|
||||
|
||||
if (value.type === 'boolean' && 'default' in value) {
|
||||
field.defaultValue = value.default;
|
||||
}
|
||||
|
||||
if (value.enum) {
|
||||
field.type = 'StaticDropdown';
|
||||
field.options = {
|
||||
options: (value.enum as string[]).map((item) => {
|
||||
const option = { label: titleCase(item), value: item };
|
||||
if (
|
||||
value['x-aai-enum'] &&
|
||||
value['x-aai-enum'][item] &&
|
||||
value['x-aai-enum'][item]['label']
|
||||
) {
|
||||
option.label = titleCase(value['x-aai-enum'][item]['label']);
|
||||
return option;
|
||||
} else {
|
||||
console.warn(`No x-aai-enum value found for property ${key} ${item}`);
|
||||
}
|
||||
return option;
|
||||
}),
|
||||
};
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
const typeMap: Record<string, string> = {
|
||||
date: 'DateTime',
|
||||
'date-time': 'DateTime',
|
||||
url: 'ShortText',
|
||||
string: 'ShortText',
|
||||
uuid: 'ShortText',
|
||||
object: 'Object',
|
||||
number: 'Number',
|
||||
integer: 'Number',
|
||||
float: 'Number',
|
||||
double: 'Number',
|
||||
boolean: 'Checkbox',
|
||||
array: 'Array',
|
||||
json: 'Json',
|
||||
};
|
||||
|
||||
function mapType(schema: any) {
|
||||
if (schema['x-ap-type']) {
|
||||
return schema['x-ap-type'];
|
||||
}
|
||||
if (schema.format && schema.format in typeMap) {
|
||||
return typeMap[schema.format];
|
||||
}
|
||||
if (schema.type in typeMap) {
|
||||
return typeMap[schema.type];
|
||||
}
|
||||
throw new Error(`Unsupported type found ${schema.type}`);
|
||||
}
|
||||
function createTs(
|
||||
propsJson: Record<string, { type: string } & object>
|
||||
): string {
|
||||
let result = `import { Property } from "@activepieces/pieces-framework";
|
||||
export const props = `;
|
||||
result += createTsProps(propsJson);
|
||||
result += ';\n';
|
||||
return result;
|
||||
}
|
||||
function createTsProps(
|
||||
propsJson: Record<string, { type: string } & object>
|
||||
): string {
|
||||
let result = '{\n';
|
||||
for (const key in propsJson) {
|
||||
const { type, ...prop } = propsJson[key];
|
||||
let innerProps: null | string = null;
|
||||
if ('properties' in prop) {
|
||||
innerProps = createTsProps(
|
||||
prop['properties'] as Record<string, { type: string } & object>
|
||||
);
|
||||
prop.properties = '[REPLACE_WITH_PROPS]';
|
||||
}
|
||||
result +=
|
||||
` ${key}: Property.${type}(\n` +
|
||||
JSON.stringify(prop, null, ' ') +
|
||||
'),\n';
|
||||
if (innerProps) {
|
||||
result = result.replace('"[REPLACE_WITH_PROPS]"', innerProps);
|
||||
}
|
||||
}
|
||||
result += '\n}';
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user