From 2ae5a2e53aae0a633fee01a4724ba29a7c4f5ba0 Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Tue, 31 Mar 2026 15:32:17 +0200 Subject: [PATCH 1/6] refactoring process import --- lib/processImport.ts | 831 ---------------------- lib/processImport/csnBuilder.ts | 215 ++++++ lib/processImport/index.ts | 131 ++++ lib/processImport/rawWorkflowConverter.ts | 212 ++++++ lib/processImport/schemaMapper.ts | 221 ++++++ lib/processImport/utils.ts | 31 + 6 files changed, 810 insertions(+), 831 deletions(-) delete mode 100644 lib/processImport.ts create mode 100644 lib/processImport/csnBuilder.ts create mode 100644 lib/processImport/index.ts create mode 100644 lib/processImport/rawWorkflowConverter.ts create mode 100644 lib/processImport/schemaMapper.ts create mode 100644 lib/processImport/utils.ts diff --git a/lib/processImport.ts b/lib/processImport.ts deleted file mode 100644 index 3a362e8..0000000 --- a/lib/processImport.ts +++ /dev/null @@ -1,831 +0,0 @@ -import * as path from 'node:path'; -import * as fs from 'node:fs'; -import cds from '@sap/cds'; -import * as csn from './types/csn-extensions'; -import { getServiceCredentials, CachingTokenProvider, createXsuaaTokenProvider } from './auth'; -import { - createProcessApiClient, - IProcessApiClient, - ProcessHeader, - DataType, - JsonSchema, -} from './api'; -import { PROCESS_LOGGER_PREFIX, PROCESS_SERVICE } from './constants'; - -const LOG = cds.log(PROCESS_LOGGER_PREFIX); -const CLASS_DEFINITION = 'com.sap.bpm.wfs.Model'; - -// ============================================================================ -// TYPES -// ============================================================================ - -interface ImportOptions { - name?: string; - file?: string; -} - -interface SchemaMapContext { - parentTypeName: string; - serviceName: string; - definitions: Record; -} - -// --- Raw SBPA workflow JSON types --- - -/** Top-level structure of a raw SBPA workflow JSON file. */ -interface RawWorkflowJson { - contents: Record; -} - -/** Base shape for any entry inside `contents`. */ -interface RawWorkflowEntry { - classDefinition?: string; - [key: string]: unknown; -} - -/** The `com.sap.bpm.wfs.Model` entry. */ -interface RawWorkflowModelEntry extends RawWorkflowEntry { - classDefinition: typeof CLASS_DEFINITION; - projectId: string; - processIdentifier: string; - artifactId: string; - name: string; - /** UUID key pointing to the Schemas entry. */ - schemas: string; -} - -/** The `com.sap.bpm.wfs.Schemas` entry. */ -interface RawWorkflowSchemasEntry extends RawWorkflowEntry { - classDefinition: 'com.sap.bpm.wfs.Schemas'; - schemas: Record; -} - -/** A single schema item inside the Schemas entry. */ -interface RawWorkflowSchemaItem { - schemaRef: string; - content: JsonSchema & RawProcessSchemaContent; -} - -/** - * Additional structure on the process schema content (the one matching `$.{artifactId}`). - * Data type schemas don't have `definitions.in` / `definitions.out`. - */ -interface RawProcessSchemaContent { - definitions?: { - out?: JsonSchema; - in?: JsonSchema; - }; -} - -// ============================================================================ -// MAIN ENTRY POINT -// ============================================================================ - -/** - * Module-level cache for data types, populated during loadProcessHeader / fetchAndSaveProcessDefinition - * and consumed by buildCsnModel → resolveTypeReference. Reset at the start of each importProcess call. - * Call order: importProcess resets → loadProcessHeader populates → buildCsnModel reads. - */ -let dataTypeCache = new Map(); - -export async function importProcess( - jsonFile: string, - options: ImportOptions = {}, -): Promise { - dataTypeCache = new Map(); - - if (options.name) { - const { filePath, processHeader } = await fetchAndSaveProcessDefinition(options.name); - options.file = filePath; - return buildCsnModel(processHeader); - } - return generateCsnModel(jsonFile); -} - -// ============================================================================ -// STEP 1: FETCH PROCESS DEFINITION FROM SBPA -// ============================================================================ - -interface FetchResult { - filePath: string; - processHeader: ProcessHeader; -} - -async function fetchAndSaveProcessDefinition(processName: string): Promise { - const [projectId, processId] = splitAtLastDot(processName); - const apiClient = await createApiClient(); - - LOG.debug('Retrieving process header...'); - const processHeader = await apiClient.fetchProcessHeader(projectId, processId); - processHeader.projectId = projectId; - - if (processHeader.dependencies?.length) { - LOG.debug(`Fetching ${processHeader.dependencies.length} dependent data types...`); - processHeader.dataTypes = await apiClient.fetchAllDataTypes( - projectId, - processHeader.dependencies, - ); - processHeader.dataTypes.forEach((dt) => dataTypeCache.set(dt.uid, dt)); - } - - const outputPath = path.join(cds.root, 'srv', 'workflows', `${processName}.json`); - await fs.promises.mkdir(path.dirname(outputPath), { recursive: true }); - await fs.promises.writeFile(outputPath, JSON.stringify(processHeader, null, 2), 'utf8'); - - return { filePath: outputPath, processHeader }; -} - -async function createApiClient(): Promise { - let credentials = getServiceCredentials(PROCESS_SERVICE); - - if (!credentials) { - // Try to resolve cloud bindings automatically (same as cds bind --exec does) - // REVISIT: once merged in core - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const cdsDk = cds as any; - const resolve = cdsDk._localOrGlobal ?? cdsDk._local ?? require; - const { env: bindingEnv } = resolve('@sap/cds-dk/lib/bind/shared'); - process.env.CDS_ENV ??= 'hybrid'; - cdsDk.env = cds.env.for('cds'); - Object.assign(process.env, await bindingEnv()); - cdsDk.env = cds.env.for('cds'); - credentials = getServiceCredentials(PROCESS_SERVICE); - } catch (e) { - LOG.debug('Auto-resolve bindings failed:', e); - } - } - - if (!credentials) { - throw new Error( - 'No ProcessService credentials found. Ensure you have bound a process service instance (e.g., via cds bind process -2 ).', - ); - } - - const apiUrl = credentials.endpoints?.api; - if (!apiUrl) { - throw new Error('No API URL found in ProcessService credentials.'); - } - - LOG.debug('Creating API client...'); - const tokenProvider = createXsuaaTokenProvider(credentials); - const cachingTokenProvider = new CachingTokenProvider(tokenProvider); - - return createProcessApiClient(apiUrl, () => cachingTokenProvider.getToken()); -} - -// ============================================================================ -// STEP 2: GENERATE CSN MODEL -// ============================================================================ - -async function generateCsnModel(jsonFilePath: string): Promise { - const processHeader = loadProcessHeader(jsonFilePath); - const csnModel = buildCsnModel(processHeader); - - return csnModel; -} - -// ============================================================================ -// RAW SBPA WORKFLOW JSON → PROCESS HEADER CONVERSION -// ============================================================================ - -/** - * Raw SBPA workflow JSON is the format exported from the SAP Build Process Automation - * design-time. It uses a `{ "contents": { ... } }` structure with entries keyed by - * UUID, containing classDefinitions like "com.sap.bpm.wfs.Model", "com.sap.bpm.wfs.Schemas", etc. - * - * This is different from the ProcessHeader format returned by the unified API. - */ - -function isRawWorkflowJson(parsed: unknown): boolean { - const obj = parsed as RawWorkflowJson; - if (!obj?.contents || typeof obj.contents !== 'object') return false; - for (const key in obj.contents) { - if (obj.contents[key]?.classDefinition === CLASS_DEFINITION) return true; - } - return false; -} - -function convertWorkflowToProcessHeader(workflow: RawWorkflowJson): ProcessHeader { - const contents = workflow.contents; - - // 1. Find the Model entry - let modelEntry: RawWorkflowModelEntry | undefined; - for (const key in contents) { - if (contents[key]?.classDefinition === CLASS_DEFINITION) { - modelEntry = contents[key] as RawWorkflowModelEntry; - break; - } - } - - if (!modelEntry) { - throw new Error('Raw workflow JSON does not contain a CLASSDEFINITION entry.'); - } - - const projectId: string = modelEntry.projectId; - const identifier: string = modelEntry.processIdentifier; - const artifactId: string = modelEntry.artifactId; - const name: string = modelEntry.name; - - // 2. Find the Schemas entry - const schemasUid: string = modelEntry.schemas; - const schemasEntry = contents[schemasUid] as RawWorkflowSchemasEntry | undefined; - if (!schemasEntry?.schemas) { - throw new Error('Raw workflow JSON does not contain a valid Schemas entry.'); - } - - // 3. Find the process schema (the one whose schemaRef matches $.{artifactId}) - const processSchemaRef = `$.${artifactId}`; - let processSchemaContent: RawProcessSchemaContent | null = null; - const dataTypeSchemas: Array<{ schemaRef: string; content: JsonSchema }> = []; - - for (const schemaKey of Object.keys(schemasEntry.schemas)) { - const schema = schemasEntry.schemas[schemaKey]; - if (schema.schemaRef === processSchemaRef) { - processSchemaContent = schema.content; - } else { - dataTypeSchemas.push({ schemaRef: schema.schemaRef, content: schema.content }); - } - } - - if (!processSchemaContent) { - throw new Error( - `Raw workflow JSON does not contain a schema entry with schemaRef "${processSchemaRef}".`, - ); - } - - // 4. Extract inputs (definitions.out) and outputs (definitions.in) from the process schema - const inputs: JsonSchema = processSchemaContent?.definitions?.out ?? { - type: 'object', - properties: {}, - required: [], - }; - const outputs: JsonSchema = processSchemaContent?.definitions?.in ?? { - type: 'object', - properties: {}, - required: [], - }; - - // 5. Build data types from non-process schemas - const dataTypes: DataType[] = dataTypeSchemas.map((ds) => { - const uid = ds.schemaRef.startsWith('$.') ? ds.schemaRef.substring(2) : ds.schemaRef; - const dtName = ds.content?.title ?? uid; - return { - uid, - name: dtName, - identifier: dtName, - type: 'datatype', - header: ds.content, - }; - }); - - // 6. Build dependencies from data types - const dependencies = dataTypes.map((dt) => ({ - artifactUid: dt.uid, - type: 'both' as const, - })); - - // 7. Reconstruct $ref patterns in inputs/outputs for complex type properties. - // In the raw format, complex types are inlined. We need to replace them with - // $ref: "$.{uid}" references so buildCsnModel can resolve them via dataTypeCache. - for (const dt of dataTypes) { - restoreRefs(inputs, dt); - restoreRefs(outputs, dt); - } - - return { - uid: artifactId, - name, - identifier, - type: 'bpi.process', - projectId, - header: { - inputs, - outputs, - processAttributes: { type: 'object', properties: {}, required: [] }, - }, - dependencies: dependencies.length > 0 ? dependencies : undefined, - dataTypes: dataTypes.length > 0 ? dataTypes : undefined, - }; -} - -/** - * Recursively walk properties of a schema and replace inlined complex types with - * $ref references when they match a known data type (by refName or title). - */ -function restoreRefs(schema: JsonSchema, dataType: DataType): void { - if (!schema?.properties) return; - - for (const propName of Object.keys(schema.properties)) { - const prop = schema.properties[propName]; - if (!prop) continue; - - // Check if this is an array whose items match the data type (check arrays first - // because the array itself may also carry a refName) - if (prop.type === 'array' && prop.items && matchesDataType(prop.items, dataType)) { - schema.properties[propName] = { - type: 'array', - items: { - $ref: `$.${dataType.uid}`, - refName: dataType.name, - }, - ...(prop.title ? { title: prop.title } : {}), - ...(prop.description !== undefined ? { description: prop.description } : {}), - ...(prop.refName ? { refName: prop.refName } : {}), - }; - continue; - } - - // Check if this property is a direct inline of the data type - if (matchesDataType(prop, dataType)) { - schema.properties[propName] = { - $ref: `$.${dataType.uid}`, - refName: dataType.name, - ...(prop.title ? { title: prop.title } : {}), - ...(prop.description !== undefined ? { description: prop.description } : {}), - }; - continue; - } - } -} - -/** - * Check if a schema property matches a data type by comparing refName or title. - */ -function matchesDataType(prop: JsonSchema, dataType: DataType): boolean { - if (prop.refName && prop.refName === dataType.name) return true; - // For inlined objects, the title on the data type content matches the data type name - if (prop.type === 'object' && prop.title === dataType.header?.title) return true; - return false; -} - -function loadProcessHeader(filePath: string): ProcessHeader { - const content = fs.readFileSync(path.resolve(filePath), 'utf-8'); - const parsed = JSON.parse(content); - - // Detect raw SBPA workflow JSON format (has "contents" with "CLASSDEFINITION") - if (isRawWorkflowJson(parsed)) { - LOG.debug('Detected raw SBPA workflow JSON format, converting to ProcessHeader...'); - const header = convertWorkflowToProcessHeader(parsed); - header.dataTypes?.forEach((dt) => dataTypeCache.set(dt.uid, dt)); - return header; - } - - const header = parsed as ProcessHeader; - header.dataTypes?.forEach((dt) => dataTypeCache.set(dt.uid, dt)); - return header; -} - -function buildCsnModel(process: ProcessHeader): csn.CsnModel { - const serviceName = `${process.projectId}.${capitalize(process.identifier)}Service`; - LOG.debug(`Service name: ${serviceName}`); - - const definitions: Record = {}; - - definitions[serviceName] = createServiceDefinition(serviceName, process); - for (const dataType of dataTypeCache.values()) { - addDataTypeDefinition(dataType, serviceName, definitions); - } - - addProcessTypes(process, serviceName, definitions); - - const normalizedInputSchema = ensureObjectSchema(process.header?.inputs); - const inputProperties = normalizedInputSchema.properties ?? {}; - const hasInputProperties = Object.keys(inputProperties).length > 0; - const hasRequiredInputs = hasInputProperties && (normalizedInputSchema.required?.length ?? 0) > 0; - addProcessActions(serviceName, definitions, hasInputProperties, hasRequiredInputs); - - return { - $version: '2.0', - definitions, - meta: { creator: '@cap-js/process' }, - }; -} - -// ============================================================================ -// CSN BUILDERS: SERVICE & TYPES -// ============================================================================ - -function createServiceDefinition(serviceName: string, process: ProcessHeader): csn.CsnDefinition { - return { - kind: 'service', - name: serviceName, - doc: 'DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT.', - '@protocol': 'none', - '@bpm.process': `${process.projectId}.${process.identifier}`, - }; -} - -function addDataTypeDefinition( - dataType: DataType, - serviceName: string, - definitions: Record, -): void { - const typeName = fqn(serviceName, sanitizeName(dataType.name)); - - if (definitions[typeName]) return; - - if (!dataType.header || dataType.header.type !== 'object') { - LOG.warn(`Data type ${dataType.name} has no valid schema, creating empty type`); - definitions[typeName] = { kind: 'type', name: typeName, elements: {} }; - return; - } - - definitions[typeName] = buildTypeFromSchema(typeName, dataType.header, serviceName, definitions); - LOG.debug(`Generated type: ${typeName}`); -} - -function addProcessTypes( - process: ProcessHeader, - serviceName: string, - definitions: Record, -): void { - const inputsName = fqn(serviceName, 'ProcessInputs'); - const outputsName = fqn(serviceName, 'ProcessOutputs'); - const attributeName = fqn(serviceName, 'ProcessAttribute'); - const attributesName = fqn(serviceName, 'ProcessAttributes'); - const instanceName = fqn(serviceName, 'ProcessInstance'); - const instancesName = fqn(serviceName, 'ProcessInstances'); - - definitions[inputsName] = buildTypeFromSchema( - inputsName, - ensureObjectSchema(process.header?.inputs), - serviceName, - definitions, - ); - definitions[outputsName] = buildTypeFromSchema( - outputsName, - ensureObjectSchema(process.header?.outputs), - serviceName, - definitions, - ); - - definitions[attributeName] = { - kind: 'type', - name: attributeName, - elements: { - id: { type: csn.CdsBuiltinType.String, notNull: true }, - label: { type: csn.CdsBuiltinType.String, notNull: true }, - value: { type: csn.CdsBuiltinType.String }, - type: { type: csn.CdsBuiltinType.String, notNull: true }, - }, - }; - - definitions[attributesName] = { - kind: 'type', - name: attributesName, - items: { - type: attributeName, - }, - }; - - definitions[instanceName] = { - kind: 'type', - name: instanceName, - elements: { - definitionId: { type: csn.CdsBuiltinType.String }, - definitionVersion: { type: csn.CdsBuiltinType.String }, - id: { type: csn.CdsBuiltinType.String }, - status: { type: csn.CdsBuiltinType.String }, - startedAt: { type: csn.CdsBuiltinType.String }, - startedBy: { type: csn.CdsBuiltinType.String }, - }, - }; - - definitions[instancesName] = { - kind: 'type', - name: instancesName, - items: { type: instanceName }, - }; -} - -// ============================================================================ -// CSN BUILDERS: ACTIONS -// ============================================================================ - -function addProcessActions( - serviceName: string, - definitions: Record, - hasInputProperties: boolean, - hasRequiredInputs: boolean, -): void { - const inputsType = fqn(serviceName, 'ProcessInputs'); - const outputsType = fqn(serviceName, 'ProcessOutputs'); - const attributesType = fqn(serviceName, 'ProcessAttributes'); - const instancesType = fqn(serviceName, 'ProcessInstances'); - - // Start action — three tiers: - // 1. No input properties: start() with no params - // 2. All inputs optional: start(inputs: ProcessInputs) — inputs param is optional - // 3. Some/all inputs required: start(inputs: ProcessInputs not null) — inputs param is required - if (!hasInputProperties) { - definitions[fqn(serviceName, 'start')] = { - kind: 'action', - name: fqn(serviceName, 'start'), - }; - } else { - definitions[fqn(serviceName, 'start')] = { - kind: 'action', - name: fqn(serviceName, 'start'), - params: { - inputs: { - type: inputsType, - notNull: hasRequiredInputs ? true : undefined, - }, - }, - }; - } - - // Query functions - definitions[fqn(serviceName, 'getAttributes')] = { - kind: 'function', - name: fqn(serviceName, 'getAttributes'), - params: { - processInstanceId: { type: csn.CdsBuiltinType.String, notNull: true }, - }, - returns: { type: attributesType }, - }; - - definitions[fqn(serviceName, 'getOutputs')] = { - kind: 'function', - name: fqn(serviceName, 'getOutputs'), - params: { - processInstanceId: { type: csn.CdsBuiltinType.String, notNull: true }, - }, - returns: { type: outputsType }, - }; - - definitions[fqn(serviceName, 'getInstancesByBusinessKey')] = { - kind: 'function', - name: fqn(serviceName, 'getInstancesByBusinessKey'), - params: { - businessKey: { type: csn.CdsBuiltinType.String, notNull: true }, - status: { items: { type: csn.CdsBuiltinType.String } }, - }, - returns: { type: instancesType }, - }; - - // Lifecycle actions - for (const action of ['suspend', 'resume', 'cancel']) { - definitions[fqn(serviceName, action)] = { - kind: 'action', - name: fqn(serviceName, action), - params: { - businessKey: { type: csn.CdsBuiltinType.String, notNull: true }, - cascade: { type: csn.CdsBuiltinType.Boolean }, - }, - }; - } -} - -// ============================================================================ -// JSON SCHEMA → CSN CONVERSION -// ============================================================================ - -function buildTypeFromSchema( - typeName: string, - schema: JsonSchema, - serviceName: string, - definitions: Record, -): csn.CsnType { - const required = new Set(schema.required ?? []); - const elements: Record = {}; - - const properties = schema.properties ?? {}; - for (const propName in properties) { - if (Object.hasOwn(properties, propName)) { - const propSchema = properties[propName]; - const safeName = sanitizeName(propName); - elements[safeName] = mapSchemaPropertyToElement( - safeName, - propSchema, - required.has(propName), - { - parentTypeName: typeName, - serviceName, - definitions, - }, - ); - } - } - - return { kind: 'type', name: typeName, elements }; -} - -function mapSchemaPropertyToElement( - propName: string, - schema: JsonSchema, - isRequired: boolean, - ctx: SchemaMapContext, -): csn.CsnElement { - const notNull = isRequired || undefined; - - // Reference to another type - if (schema?.$ref) { - return { type: resolveTypeReference(schema, ctx.serviceName), notNull }; - } - - // Primitives - switch (schema.type) { - case 'string': - return { type: mapStringFormat(schema), notNull }; - case 'boolean': - return { type: csn.CdsBuiltinType.Boolean, notNull }; - case 'number': - return { type: csn.CdsBuiltinType.DecimalFloat, notNull }; - case 'integer': - return { type: csn.CdsBuiltinType.Integer, notNull }; - } - - // Nested object - if (schema.type === 'object') { - const nestedName = fqn( - ctx.serviceName, - sanitizeName(`${baseName(ctx.parentTypeName)}_${propName}`), - ); - ctx.definitions[nestedName] = buildTypeFromSchema( - nestedName, - ensureObjectSchema(schema), - ctx.serviceName, - ctx.definitions, - ); - return { type: nestedName, notNull }; - } - - // Array - if (schema.type === 'array') { - const arrayName = fqn( - ctx.serviceName, - sanitizeName(`${baseName(ctx.parentTypeName)}_${propName}_Array`), - ); - if (!schema.items) throw new Error(`Array ${ctx.parentTypeName}.${propName} has no 'items'.`); - - ctx.definitions[arrayName] = { - kind: 'type', - name: arrayName, - items: buildArrayItemsSpec(schema.items, { ...ctx, parentTypeName: arrayName }), - }; - return { type: arrayName, notNull }; - } - - // Fallback - return { type: csn.CdsBuiltinType.String, notNull }; -} - -function buildArrayItemsSpec(itemsSchema: JsonSchema, ctx: SchemaMapContext): csn.CsnTypeSpec { - // Reference - if (itemsSchema?.$ref) { - return { type: resolveTypeReference(itemsSchema, ctx.serviceName) }; - } - - // Primitives - switch (itemsSchema.type) { - case 'string': - return { type: mapStringFormat(itemsSchema) }; - case 'boolean': - return { type: csn.CdsBuiltinType.Boolean }; - case 'number': - return { type: csn.CdsBuiltinType.DecimalFloat }; - case 'integer': - return { type: csn.CdsBuiltinType.Integer }; - } - - // Object items (inline) - if (itemsSchema.type === 'object') { - const required = new Set(itemsSchema.required ?? []); - const elements: Record = {}; - - const properties = itemsSchema.properties ?? {}; - - for (const name in properties) { - if (Object.hasOwn(properties, name)) { - const schema = properties[name]; - const safeName = sanitizeName(name); - elements[safeName] = mapSchemaPropertyToElement(safeName, schema, required.has(name), { - parentTypeName: fqn(ctx.serviceName, `${baseName(ctx.parentTypeName)}_Item`), - serviceName: ctx.serviceName, - definitions: ctx.definitions, - }); - } - } - return { elements }; - } - - // Nested array - if (itemsSchema.type === 'array') { - if (!itemsSchema.items) - throw new Error(`Nested array under ${ctx.parentTypeName} missing 'items'.`); - return { items: buildArrayItemsSpec(itemsSchema.items, ctx) }; - } - - return { type: csn.CdsBuiltinType.String }; -} - -function resolveTypeReference( - schema: { $ref?: string; refName?: string }, - serviceName: string, -): string { - const ref = schema.$ref; - if (!ref) - throw new Error(`Invalid reference in ${serviceName} for ${schema.refName ?? 'unknown'}`); - - // Internal reference: #/definitions/date - if (ref.startsWith('#/')) { - const format = ref.split('/').pop(); - return mapDateFormatToCdsType(format); - } - - // External artifact: $.uuid - if (ref.startsWith('$.')) { - const uid = ref.substring(2); - const dataType = dataTypeCache.get(uid); - - if (dataType) { - return fqn(serviceName, sanitizeName(dataType.name)); - } - - if (schema.refName) { - LOG.warn(`Data type ${schema.refName} (${uid}) not in cache, using name`); - return fqn(serviceName, sanitizeName(schema.refName)); - } - - LOG.warn(`Unknown artifact reference: ${ref}`); - return fqn(serviceName, sanitizeName(uid)); - } - - // Fallback - return fqn(serviceName, sanitizeName(schema.refName ?? ref)); -} - -function mapDateFormatToCdsType(format?: string): csn.CdsBuiltinType { - switch (format) { - case 'date': - return csn.CdsBuiltinType.Date; - case 'time': - return csn.CdsBuiltinType.Time; - case 'dateTime': - return csn.CdsBuiltinType.Timestamp; - default: - return csn.CdsBuiltinType.String; - } -} - -/** - * Map a string-typed JSON schema property to the correct CDS type, - * taking into account the `format` property (used in raw workflow JSON). - * Note: password-typed strings have no dedicated CDS type and map to String. - */ -function mapStringFormat(schema: JsonSchema): csn.CdsBuiltinType { - if (schema.format) { - switch (schema.format) { - case 'date': - return csn.CdsBuiltinType.Date; - case 'date-time': - return csn.CdsBuiltinType.Timestamp; - case 'time': - return csn.CdsBuiltinType.Time; - default: - return csn.CdsBuiltinType.String; - } - } - return csn.CdsBuiltinType.String; -} - -function ensureObjectSchema(schema?: JsonSchema): JsonSchema { - if (schema?.type === 'object' && schema.properties) { - return schema; - } - return { type: 'object', properties: {}, required: [] }; -} - -// ============================================================================ -// UTILITIES -// ============================================================================ - -/** Create fully qualified name: "Service.TypeName" */ -function fqn(qualifier: string, name: string): string { - return `${qualifier}.${name}`; -} - -/** Get the part after the last dot: "a.b.c" → "c" */ -function baseName(name: string): string { - return splitAtLastDot(name)[1]; -} - -/** Split at last dot: "a.b.c" → ["a.b", "c"] */ -function splitAtLastDot(name: string): [string, string] { - const i = name.lastIndexOf('.'); - return i === -1 ? ['', name] : [name.slice(0, i), name.slice(i + 1)]; -} - -/** Capitalize first letter */ -function capitalize(s: string): string { - return s.charAt(0).toUpperCase() + s.slice(1); -} - -/** Convert to safe identifier: "foo-bar" → "foo_bar", "123x" → "_123x" */ -function sanitizeName(name: string): string { - return String(name) - .replace(/[^A-Za-z0-9_]/g, '_') - .replace(/^(\d)/, '_$1'); -} diff --git a/lib/processImport/csnBuilder.ts b/lib/processImport/csnBuilder.ts new file mode 100644 index 0000000..a700aab --- /dev/null +++ b/lib/processImport/csnBuilder.ts @@ -0,0 +1,215 @@ +import cds from '@sap/cds'; +import * as csn from '../types/csn-extensions'; +import { ProcessHeader, DataType } from '../api'; +import { PROCESS_LOGGER_PREFIX } from '../constants'; +import { buildTypeFromSchema, ensureObjectSchema } from './schemaMapper'; +import { fqn, sanitizeName, capitalize } from './utils'; + +const LOG = cds.log(PROCESS_LOGGER_PREFIX); + +export function buildCsnModel( + process: ProcessHeader, + dataTypeCache: Map, +): csn.CsnModel { + const serviceName = `${process.projectId}.${capitalize(process.identifier)}Service`; + LOG.debug(`Service name: ${serviceName}`); + + const definitions: Record = {}; + + definitions[serviceName] = createServiceDefinition(serviceName, process); + for (const dataType of dataTypeCache.values()) { + addDataTypeDefinition(dataType, serviceName, definitions, dataTypeCache); + } + + addProcessTypes(process, serviceName, definitions, dataTypeCache); + + const normalizedInputSchema = ensureObjectSchema(process.header?.inputs); + const inputProperties = normalizedInputSchema.properties ?? {}; + const hasInputProperties = Object.keys(inputProperties).length > 0; + const hasRequiredInputs = hasInputProperties && (normalizedInputSchema.required?.length ?? 0) > 0; + addProcessActions(serviceName, definitions, hasInputProperties, hasRequiredInputs); + + return { + $version: '2.0', + definitions, + meta: { creator: '@cap-js/process' }, + }; +} + +function createServiceDefinition(serviceName: string, process: ProcessHeader): csn.CsnDefinition { + return { + kind: 'service', + name: serviceName, + doc: 'DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT.', + '@protocol': 'none', + '@bpm.process': `${process.projectId}.${process.identifier}`, + }; +} + +function addDataTypeDefinition( + dataType: DataType, + serviceName: string, + definitions: Record, + dataTypeCache: Map, +): void { + const typeName = fqn(serviceName, sanitizeName(dataType.name)); + + if (definitions[typeName]) return; + + if (!dataType.header || dataType.header.type !== 'object') { + LOG.warn(`Data type ${dataType.name} has no valid schema, creating empty type`); + definitions[typeName] = { kind: 'type', name: typeName, elements: {} }; + return; + } + + definitions[typeName] = buildTypeFromSchema( + typeName, + dataType.header, + serviceName, + definitions, + dataTypeCache, + ); + LOG.debug(`Generated type: ${typeName}`); +} + +function addProcessTypes( + process: ProcessHeader, + serviceName: string, + definitions: Record, + dataTypeCache: Map, +): void { + const inputsName = fqn(serviceName, 'ProcessInputs'); + const outputsName = fqn(serviceName, 'ProcessOutputs'); + const attributeName = fqn(serviceName, 'ProcessAttribute'); + const attributesName = fqn(serviceName, 'ProcessAttributes'); + const instanceName = fqn(serviceName, 'ProcessInstance'); + const instancesName = fqn(serviceName, 'ProcessInstances'); + + definitions[inputsName] = buildTypeFromSchema( + inputsName, + ensureObjectSchema(process.header?.inputs), + serviceName, + definitions, + dataTypeCache, + ); + definitions[outputsName] = buildTypeFromSchema( + outputsName, + ensureObjectSchema(process.header?.outputs), + serviceName, + definitions, + dataTypeCache, + ); + + definitions[attributeName] = { + kind: 'type', + name: attributeName, + elements: { + id: { type: csn.CdsBuiltinType.String, notNull: true }, + label: { type: csn.CdsBuiltinType.String, notNull: true }, + value: { type: csn.CdsBuiltinType.String }, + type: { type: csn.CdsBuiltinType.String, notNull: true }, + }, + }; + + definitions[attributesName] = { + kind: 'type', + name: attributesName, + items: { + type: attributeName, + }, + }; + + definitions[instanceName] = { + kind: 'type', + name: instanceName, + elements: { + definitionId: { type: csn.CdsBuiltinType.String }, + definitionVersion: { type: csn.CdsBuiltinType.String }, + id: { type: csn.CdsBuiltinType.String }, + status: { type: csn.CdsBuiltinType.String }, + startedAt: { type: csn.CdsBuiltinType.String }, + startedBy: { type: csn.CdsBuiltinType.String }, + }, + }; + + definitions[instancesName] = { + kind: 'type', + name: instancesName, + items: { type: instanceName }, + }; +} + +function addProcessActions( + serviceName: string, + definitions: Record, + hasInputProperties: boolean, + hasRequiredInputs: boolean, +): void { + const inputsType = fqn(serviceName, 'ProcessInputs'); + const outputsType = fqn(serviceName, 'ProcessOutputs'); + const attributesType = fqn(serviceName, 'ProcessAttributes'); + const instancesType = fqn(serviceName, 'ProcessInstances'); + + // Start action — three tiers: + // 1. No input properties: start() with no params + // 2. All inputs optional: start(inputs: ProcessInputs) — inputs param is optional + // 3. Some/all inputs required: start(inputs: ProcessInputs not null) — inputs param is required + if (!hasInputProperties) { + definitions[fqn(serviceName, 'start')] = { + kind: 'action', + name: fqn(serviceName, 'start'), + }; + } else { + definitions[fqn(serviceName, 'start')] = { + kind: 'action', + name: fqn(serviceName, 'start'), + params: { + inputs: { + type: inputsType, + notNull: hasRequiredInputs ? true : undefined, + }, + }, + }; + } + + // Query functions + definitions[fqn(serviceName, 'getAttributes')] = { + kind: 'function', + name: fqn(serviceName, 'getAttributes'), + params: { + processInstanceId: { type: csn.CdsBuiltinType.String, notNull: true }, + }, + returns: { type: attributesType }, + }; + + definitions[fqn(serviceName, 'getOutputs')] = { + kind: 'function', + name: fqn(serviceName, 'getOutputs'), + params: { + processInstanceId: { type: csn.CdsBuiltinType.String, notNull: true }, + }, + returns: { type: outputsType }, + }; + + definitions[fqn(serviceName, 'getInstancesByBusinessKey')] = { + kind: 'function', + name: fqn(serviceName, 'getInstancesByBusinessKey'), + params: { + businessKey: { type: csn.CdsBuiltinType.String, notNull: true }, + status: { items: { type: csn.CdsBuiltinType.String } }, + }, + returns: { type: instancesType }, + }; + + // Lifecycle actions + for (const action of ['suspend', 'resume', 'cancel']) { + definitions[fqn(serviceName, action)] = { + kind: 'action', + name: fqn(serviceName, action), + params: { + businessKey: { type: csn.CdsBuiltinType.String, notNull: true }, + cascade: { type: csn.CdsBuiltinType.Boolean }, + }, + }; + } +} diff --git a/lib/processImport/index.ts b/lib/processImport/index.ts new file mode 100644 index 0000000..bb758ad --- /dev/null +++ b/lib/processImport/index.ts @@ -0,0 +1,131 @@ +import * as path from 'node:path'; +import * as fs from 'node:fs'; +import cds from '@sap/cds'; +import * as csn from '../types/csn-extensions'; +import { getServiceCredentials, CachingTokenProvider, createXsuaaTokenProvider } from '../auth'; +import { createProcessApiClient, IProcessApiClient, ProcessHeader, DataType } from '../api'; +import { PROCESS_LOGGER_PREFIX, PROCESS_SERVICE } from '../constants'; +import { buildCsnModel } from './csnBuilder'; +import { isRawWorkflowJson, convertWorkflowToProcessHeader } from './rawWorkflowConverter'; +import { splitAtLastDot } from './utils'; + +const LOG = cds.log(PROCESS_LOGGER_PREFIX); + +interface ImportOptions { + name?: string; + file?: string; +} + +/** + * Module-level cache for data types, populated during loadProcessHeader / fetchAndSaveProcessDefinition + * and consumed by buildCsnModel -> resolveTypeReference. Reset at the start of each importProcess call. + * Call order: importProcess resets -> loadProcessHeader populates -> buildCsnModel reads. + */ +let dataTypeCache = new Map(); + +export async function importProcess( + jsonFile: string, + options: ImportOptions = {}, +): Promise { + dataTypeCache = new Map(); + + if (options.name) { + const { filePath, processHeader } = await fetchAndSaveProcessDefinition(options.name); + options.file = filePath; + return buildCsnModel(processHeader, dataTypeCache); + } + const processHeader = loadProcessHeader(jsonFile); + return buildCsnModel(processHeader, dataTypeCache); +} + +// ============================================================================ +// Fetch process definition from SBPA +// ============================================================================ + +interface FetchResult { + filePath: string; + processHeader: ProcessHeader; +} + +async function fetchAndSaveProcessDefinition(processName: string): Promise { + const [projectId, processId] = splitAtLastDot(processName); + const apiClient = await createApiClient(); + + LOG.debug('Retrieving process header...'); + const processHeader = await apiClient.fetchProcessHeader(projectId, processId); + processHeader.projectId = projectId; + + if (processHeader.dependencies?.length) { + LOG.debug(`Fetching ${processHeader.dependencies.length} dependent data types...`); + processHeader.dataTypes = await apiClient.fetchAllDataTypes( + projectId, + processHeader.dependencies, + ); + processHeader.dataTypes.forEach((dt) => dataTypeCache.set(dt.uid, dt)); + } + + const outputPath = path.join(cds.root, 'srv', 'workflows', `${processName}.json`); + await fs.promises.mkdir(path.dirname(outputPath), { recursive: true }); + await fs.promises.writeFile(outputPath, JSON.stringify(processHeader, null, 2), 'utf8'); + + return { filePath: outputPath, processHeader }; +} + +async function createApiClient(): Promise { + let credentials = getServiceCredentials(PROCESS_SERVICE); + + if (!credentials) { + // Try to resolve cloud bindings automatically (same as cds bind --exec does) + // REVISIT: once merged in core + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const cdsDk = cds as any; + const resolve = cdsDk._localOrGlobal ?? cdsDk._local ?? require; + const { env: bindingEnv } = resolve('@sap/cds-dk/lib/bind/shared'); + process.env.CDS_ENV ??= 'hybrid'; + cdsDk.env = cds.env.for('cds'); + Object.assign(process.env, await bindingEnv()); + cdsDk.env = cds.env.for('cds'); + credentials = getServiceCredentials(PROCESS_SERVICE); + } catch (e) { + LOG.debug('Auto-resolve bindings failed:', e); + } + } + + if (!credentials) { + throw new Error( + 'No ProcessService credentials found. Ensure you have bound a process service instance (e.g., via cds bind process -2 ).', + ); + } + + const apiUrl = credentials.endpoints?.api; + if (!apiUrl) { + throw new Error('No API URL found in ProcessService credentials.'); + } + + LOG.debug('Creating API client...'); + const tokenProvider = createXsuaaTokenProvider(credentials); + const cachingTokenProvider = new CachingTokenProvider(tokenProvider); + + return createProcessApiClient(apiUrl, () => cachingTokenProvider.getToken()); +} + +// ============================================================================ +// Load process header from file +// ============================================================================ + +function loadProcessHeader(filePath: string): ProcessHeader { + const content = fs.readFileSync(path.resolve(filePath), 'utf-8'); + const parsed = JSON.parse(content); + + let header: ProcessHeader; + if (isRawWorkflowJson(parsed)) { + LOG.debug('Detected raw SBPA workflow JSON format, converting to ProcessHeader...'); + header = convertWorkflowToProcessHeader(parsed); + } else { + header = parsed as ProcessHeader; + } + + header.dataTypes?.forEach((dt) => dataTypeCache.set(dt.uid, dt)); + return header; +} diff --git a/lib/processImport/rawWorkflowConverter.ts b/lib/processImport/rawWorkflowConverter.ts new file mode 100644 index 0000000..91969d1 --- /dev/null +++ b/lib/processImport/rawWorkflowConverter.ts @@ -0,0 +1,212 @@ +import cds from '@sap/cds'; +import { ProcessHeader, DataType, JsonSchema } from '../api'; +import { PROCESS_LOGGER_PREFIX } from '../constants'; +import { EMPTY_OBJECT_SCHEMA } from './utils'; + +const LOG = cds.log(PROCESS_LOGGER_PREFIX); + +const CLASS_DEFINITION = 'com.sap.bpm.wfs.Model'; + +// ============================================================================ +// Raw SBPA workflow JSON types +// ============================================================================ + +/** Top-level structure of a raw SBPA workflow JSON file. */ +interface RawWorkflowJson { + contents: Record; +} + +/** Base shape for any entry inside `contents`. */ +interface RawWorkflowEntry { + classDefinition?: string; + [key: string]: unknown; +} + +/** The `com.sap.bpm.wfs.Model` entry. */ +interface RawWorkflowModelEntry extends RawWorkflowEntry { + classDefinition: typeof CLASS_DEFINITION; + projectId: string; + processIdentifier: string; + artifactId: string; + name: string; + /** UUID key pointing to the Schemas entry. */ + schemas: string; +} + +/** The `com.sap.bpm.wfs.Schemas` entry. */ +interface RawWorkflowSchemasEntry extends RawWorkflowEntry { + classDefinition: 'com.sap.bpm.wfs.Schemas'; + schemas: Record; +} + +/** A single schema item inside the Schemas entry. */ +interface RawWorkflowSchemaItem { + schemaRef: string; + content: JsonSchema & RawProcessSchemaContent; +} + +/** + * Additional structure on the process schema content (the one matching `$.{artifactId}`). + * Data type schemas don't have `definitions.in` / `definitions.out`. + */ +interface RawProcessSchemaContent { + definitions?: { + out?: JsonSchema; + in?: JsonSchema; + }; +} + +// ============================================================================ +// Public API +// ============================================================================ + +/** + * Detect whether a parsed JSON object is a raw SBPA workflow JSON file + * (has "contents" with a "com.sap.bpm.wfs.Model" entry). + */ +export function isRawWorkflowJson(parsed: unknown): boolean { + const obj = parsed as RawWorkflowJson; + if (!obj?.contents || typeof obj.contents !== 'object') return false; + for (const key in obj.contents) { + if (obj.contents[key]?.classDefinition === CLASS_DEFINITION) return true; + } + return false; +} + +/** + * Convert a raw SBPA workflow JSON file into the unified ProcessHeader format + * that the CSN builder expects. + */ +export function convertWorkflowToProcessHeader(workflow: unknown): ProcessHeader { + const contents = (workflow as RawWorkflowJson).contents; + + // Find the Model entry + let modelEntry: RawWorkflowModelEntry | undefined; + for (const key in contents) { + if (contents[key]?.classDefinition === CLASS_DEFINITION) { + modelEntry = contents[key] as RawWorkflowModelEntry; + break; + } + } + if (!modelEntry) { + throw new Error('Raw workflow JSON does not contain a CLASSDEFINITION entry.'); + } + + const { + projectId, + processIdentifier: identifier, + artifactId, + name, + schemas: schemasUid, + } = modelEntry; + + // Find the Schemas entry + const schemasEntry = contents[schemasUid] as RawWorkflowSchemasEntry | undefined; + if (!schemasEntry?.schemas) { + throw new Error('Raw workflow JSON does not contain a valid Schemas entry.'); + } + + // Separate process schema from data type schemas + const processSchemaRef = `$.${artifactId}`; + let processSchemaContent: RawProcessSchemaContent | null = null; + const dataTypes: DataType[] = []; + + for (const schemaKey in schemasEntry.schemas) { + const schema = schemasEntry.schemas[schemaKey]; + if (schema.schemaRef === processSchemaRef) { + processSchemaContent = schema.content; + } else { + const uid = schema.schemaRef.startsWith('$.') + ? schema.schemaRef.substring(2) + : schema.schemaRef; + const dtName = schema.content?.title ?? uid; + dataTypes.push({ + uid, + name: dtName, + identifier: dtName, + type: 'datatype', + header: schema.content, + }); + } + } + + if (!processSchemaContent) { + throw new Error( + `Raw workflow JSON does not contain a schema entry with schemaRef "${processSchemaRef}".`, + ); + } + + // Extract inputs (definitions.out) and outputs (definitions.in) from the process schema + const inputs = processSchemaContent.definitions?.out ?? EMPTY_OBJECT_SCHEMA; + const outputs = processSchemaContent.definitions?.in ?? EMPTY_OBJECT_SCHEMA; + + // Reconstruct $ref patterns in inputs/outputs for complex type properties. + // In the raw format, complex types are inlined. We replace them with + // $ref: "$.{uid}" references so buildCsnModel can resolve them via dataTypeCache. + for (const dt of dataTypes) { + restoreRefs(inputs, dt); + restoreRefs(outputs, dt); + } + + LOG.debug('Converted raw workflow JSON to ProcessHeader format.'); + + return { + uid: artifactId, + name, + identifier, + type: 'bpi.process', + projectId, + header: { inputs, outputs, processAttributes: EMPTY_OBJECT_SCHEMA }, + dependencies: + dataTypes.length > 0 + ? dataTypes.map((dt) => ({ artifactUid: dt.uid, type: 'both' as const })) + : undefined, + dataTypes: dataTypes.length > 0 ? dataTypes : undefined, + }; +} + +// ============================================================================ +// Internal helpers +// ============================================================================ + +/** + * Walk properties of a schema and replace inlined complex types with + * $ref references when they match a known data type (by refName or title). + */ +function restoreRefs(schema: JsonSchema, dataType: DataType): void { + if (!schema?.properties) return; + + const ref: JsonSchema = { $ref: `$.${dataType.uid}`, refName: dataType.name }; + + for (const propName in schema.properties) { + const prop = schema.properties[propName]; + if (!prop) continue; + + // Check arrays first because the array itself may also carry a refName + if (prop.type === 'array' && prop.items && matchesDataType(prop.items, dataType)) { + schema.properties[propName] = { + type: 'array', + items: ref, + title: prop.title, + description: prop.description, + refName: prop.refName, + }; + } else if (matchesDataType(prop, dataType)) { + schema.properties[propName] = { + ...ref, + title: prop.title, + description: prop.description, + }; + } + } +} + +/** + * Check if a schema property matches a data type by comparing refName or title. + */ +function matchesDataType(prop: JsonSchema, dataType: DataType): boolean { + if (prop.refName && prop.refName === dataType.name) return true; + // For inlined objects, the title on the data type content matches the data type name + if (prop.type === 'object' && prop.title === dataType.header?.title) return true; + return false; +} diff --git a/lib/processImport/schemaMapper.ts b/lib/processImport/schemaMapper.ts new file mode 100644 index 0000000..54b9247 --- /dev/null +++ b/lib/processImport/schemaMapper.ts @@ -0,0 +1,221 @@ +import cds from '@sap/cds'; +import * as csn from '../types/csn-extensions'; +import { DataType, JsonSchema } from '../api'; +import { PROCESS_LOGGER_PREFIX } from '../constants'; +import { fqn, baseName, sanitizeName, EMPTY_OBJECT_SCHEMA } from './utils'; + +const LOG = cds.log(PROCESS_LOGGER_PREFIX); + +interface SchemaMapContext { + parentTypeName: string; + serviceName: string; + definitions: Record; + dataTypeCache: Map; +} + +export function buildTypeFromSchema( + typeName: string, + schema: JsonSchema, + serviceName: string, + definitions: Record, + dataTypeCache: Map, +): csn.CsnType { + const required = new Set(schema.required ?? []); + const elements: Record = {}; + + const properties = schema.properties ?? {}; + for (const propName in properties) { + const propSchema = properties[propName]; + const safeName = sanitizeName(propName); + elements[safeName] = mapSchemaPropertyToElement(safeName, propSchema, required.has(propName), { + parentTypeName: typeName, + serviceName, + definitions, + dataTypeCache, + }); + } + + return { kind: 'type', name: typeName, elements }; +} + +export function ensureObjectSchema(schema?: JsonSchema): JsonSchema { + if (schema?.type === 'object' && schema.properties) { + return schema; + } + return EMPTY_OBJECT_SCHEMA; +} + +/** + * Resolve a JSON schema to a CDS type reference for $ref and primitive types. + * Returns undefined for object, array, or unrecognized types (caller must handle). + */ +function resolvePrimitiveOrRef( + schema: JsonSchema, + serviceName: string, + dataTypeCache: Map, +): string | undefined { + if (schema.$ref) return resolveTypeReference(schema, serviceName, dataTypeCache); + switch (schema.type) { + case 'string': + return mapStringFormat(schema); + case 'boolean': + return csn.CdsBuiltinType.Boolean; + case 'number': + return csn.CdsBuiltinType.DecimalFloat; + case 'integer': + return csn.CdsBuiltinType.Integer; + default: + return undefined; + } +} + +function mapSchemaPropertyToElement( + propName: string, + schema: JsonSchema, + isRequired: boolean, + ctx: SchemaMapContext, +): csn.CsnElement { + const notNull = isRequired || undefined; + + // Reference or primitive + const primitiveType = resolvePrimitiveOrRef(schema, ctx.serviceName, ctx.dataTypeCache); + if (primitiveType) return { type: primitiveType, notNull }; + + // Nested object + if (schema.type === 'object') { + const nestedName = fqn( + ctx.serviceName, + sanitizeName(`${baseName(ctx.parentTypeName)}_${propName}`), + ); + ctx.definitions[nestedName] = buildTypeFromSchema( + nestedName, + ensureObjectSchema(schema), + ctx.serviceName, + ctx.definitions, + ctx.dataTypeCache, + ); + return { type: nestedName, notNull }; + } + + // Array + if (schema.type === 'array') { + const arrayName = fqn( + ctx.serviceName, + sanitizeName(`${baseName(ctx.parentTypeName)}_${propName}_Array`), + ); + if (!schema.items) throw new Error(`Array ${ctx.parentTypeName}.${propName} has no 'items'.`); + + ctx.definitions[arrayName] = { + kind: 'type', + name: arrayName, + items: buildArrayItemsSpec(schema.items, { ...ctx, parentTypeName: arrayName }), + }; + return { type: arrayName, notNull }; + } + + // Fallback + return { type: csn.CdsBuiltinType.String, notNull }; +} + +function buildArrayItemsSpec(itemsSchema: JsonSchema, ctx: SchemaMapContext): csn.CsnTypeSpec { + // Reference or primitive + const primitiveType = resolvePrimitiveOrRef(itemsSchema, ctx.serviceName, ctx.dataTypeCache); + if (primitiveType) return { type: primitiveType }; + + // Object items (inline) + if (itemsSchema.type === 'object') { + const required = new Set(itemsSchema.required ?? []); + const elements: Record = {}; + + const properties = itemsSchema.properties ?? {}; + + for (const name in properties) { + const schema = properties[name]; + const safeName = sanitizeName(name); + elements[safeName] = mapSchemaPropertyToElement(safeName, schema, required.has(name), { + parentTypeName: fqn(ctx.serviceName, `${baseName(ctx.parentTypeName)}_Item`), + serviceName: ctx.serviceName, + definitions: ctx.definitions, + dataTypeCache: ctx.dataTypeCache, + }); + } + return { elements }; + } + + // Nested array + if (itemsSchema.type === 'array') { + if (!itemsSchema.items) + throw new Error(`Nested array under ${ctx.parentTypeName} missing 'items'.`); + return { items: buildArrayItemsSpec(itemsSchema.items, ctx) }; + } + + return { type: csn.CdsBuiltinType.String }; +} + +function resolveTypeReference( + schema: { $ref?: string; refName?: string }, + serviceName: string, + dataTypeCache: Map, +): string { + const ref = schema.$ref; + if (!ref) + throw new Error(`Invalid reference in ${serviceName} for ${schema.refName ?? 'unknown'}`); + + // Internal reference: #/definitions/date + if (ref.startsWith('#/')) { + const format = ref.split('/').pop(); + return mapDateFormatToCdsType(format); + } + + // External artifact: $.uuid + if (ref.startsWith('$.')) { + const uid = ref.substring(2); + const dataType = dataTypeCache.get(uid); + + if (dataType) { + return fqn(serviceName, sanitizeName(dataType.name)); + } + + if (schema.refName) { + LOG.warn(`Data type ${schema.refName} (${uid}) not in cache, using name`); + return fqn(serviceName, sanitizeName(schema.refName)); + } + + LOG.warn(`Unknown artifact reference: ${ref}`); + return fqn(serviceName, sanitizeName(uid)); + } + + // Fallback + return fqn(serviceName, sanitizeName(schema.refName ?? ref)); +} + +function mapDateFormatToCdsType(format?: string): csn.CdsBuiltinType { + switch (format) { + case 'date': + return csn.CdsBuiltinType.Date; + case 'time': + return csn.CdsBuiltinType.Time; + case 'dateTime': + return csn.CdsBuiltinType.Timestamp; + default: + return csn.CdsBuiltinType.String; + } +} + +/** + * Map a string-typed JSON schema property to the correct CDS type, + * taking into account the `format` property (used in raw workflow JSON). + * Note: password-typed strings have no dedicated CDS type and map to String. + */ +function mapStringFormat(schema: JsonSchema): csn.CdsBuiltinType { + switch (schema.format) { + case 'date': + return csn.CdsBuiltinType.Date; + case 'date-time': + return csn.CdsBuiltinType.Timestamp; + case 'time': + return csn.CdsBuiltinType.Time; + default: + return csn.CdsBuiltinType.String; + } +} diff --git a/lib/processImport/utils.ts b/lib/processImport/utils.ts new file mode 100644 index 0000000..312612b --- /dev/null +++ b/lib/processImport/utils.ts @@ -0,0 +1,31 @@ +import { JsonSchema } from '../api'; + +export const EMPTY_OBJECT_SCHEMA: JsonSchema = { type: 'object', properties: {}, required: [] }; + +/** Create fully qualified name: "Service.TypeName" */ +export function fqn(qualifier: string, name: string): string { + return `${qualifier}.${name}`; +} + +/** Get the part after the last dot: "a.b.c" → "c" */ +export function baseName(name: string): string { + return splitAtLastDot(name)[1]; +} + +/** Split at last dot: "a.b.c" → ["a.b", "c"] */ +export function splitAtLastDot(name: string): [string, string] { + const i = name.lastIndexOf('.'); + return i === -1 ? ['', name] : [name.slice(0, i), name.slice(i + 1)]; +} + +/** Capitalize first letter */ +export function capitalize(s: string): string { + return s.charAt(0).toUpperCase() + s.slice(1); +} + +/** Convert to safe identifier: "foo-bar" → "foo_bar", "123x" → "_123x" */ +export function sanitizeName(name: string): string { + return String(name) + .replace(/[^A-Za-z0-9_]/g, '_') + .replace(/^(\d)/, '_$1'); +} From 0fd15bfe827f571887a9e25cc21a06a372341f9a Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Wed, 1 Apr 2026 14:21:00 +0200 Subject: [PATCH 2/6] moved imported files --- .../downloadedModels}/ImportProcess_Attributes_And_Outputs.json | 0 .../downloadedModels}/ImportProcess_Complex_Inputs.json | 0 .../downloadedModels}/ImportProcess_Simple_Inputs.json | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tests/{bookshop/srv/workflows => hybrid/downloadedModels}/ImportProcess_Attributes_And_Outputs.json (100%) rename tests/{bookshop/srv/workflows => hybrid/downloadedModels}/ImportProcess_Complex_Inputs.json (100%) rename tests/{bookshop/srv/workflows => hybrid/downloadedModels}/ImportProcess_Simple_Inputs.json (100%) diff --git a/tests/bookshop/srv/workflows/ImportProcess_Attributes_And_Outputs.json b/tests/hybrid/downloadedModels/ImportProcess_Attributes_And_Outputs.json similarity index 100% rename from tests/bookshop/srv/workflows/ImportProcess_Attributes_And_Outputs.json rename to tests/hybrid/downloadedModels/ImportProcess_Attributes_And_Outputs.json diff --git a/tests/bookshop/srv/workflows/ImportProcess_Complex_Inputs.json b/tests/hybrid/downloadedModels/ImportProcess_Complex_Inputs.json similarity index 100% rename from tests/bookshop/srv/workflows/ImportProcess_Complex_Inputs.json rename to tests/hybrid/downloadedModels/ImportProcess_Complex_Inputs.json diff --git a/tests/bookshop/srv/workflows/ImportProcess_Simple_Inputs.json b/tests/hybrid/downloadedModels/ImportProcess_Simple_Inputs.json similarity index 100% rename from tests/bookshop/srv/workflows/ImportProcess_Simple_Inputs.json rename to tests/hybrid/downloadedModels/ImportProcess_Simple_Inputs.json From c41ab9548cb9860e082e2c28841737b971023e0f Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Wed, 1 Apr 2026 14:45:50 +0200 Subject: [PATCH 3/6] fixed the naming --- README.md | 12 ++- lib/processImport/index.ts | 26 ++++- ....importProcess_Attributes_And_Outputs.json | 94 +++++++++++-------- ...bridtest.importProcess_Complex_Inputs.json | 73 +++++++------- ...ybridtest.importProcess_Simple_Inputs.json | 36 +++---- 5 files changed, 144 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 6340d6f..390efeb 100644 --- a/README.md +++ b/README.md @@ -412,10 +412,12 @@ To import a process, you have two different options to import: First via the dow Go to your SBPA instance > Control Tower > Environments > Select your environment where the process is deployed > Processes and Workflows > Select your process > Click on the "Download Model" button. ```bash -cds import --from process ~./Downloads/.json +cds import --from process ~/Downloads/.json ``` -This will create a new CDS service in `./srv/external/.cds` with the process definition that is used for the build-time validation and the typed programmatic API. +This will create: +- A new CDS service in `./srv/external/{projectId}.{processIdentifier}.cds` with the process definition used for build-time validation and the typed programmatic API +- A converted ProcessHeader JSON file in `./srv/workflows/{projectId}.{processIdentifier}.json` for future re-imports ##### From SBPA (Remote Import) @@ -427,9 +429,11 @@ Import your SBPA process directly from SBPA. cds import --from process --name eu12.myorg.myproject.myProcess ``` -This will create also a new CDS service in `./srv/external/eu12.myorg.myproject.myProcess.cds` with the process definition as well as a JSON file in `./srv/workflows/eu12.myorg.myproject.myProcess.json` with the process definition in JSON-format. +This will create: +- A new CDS service in `./srv/external/eu12.myorg.myproject.myProcess.cds` with the process definition +- A ProcessHeader JSON file in `./srv/workflows/eu12.myorg.myproject.myProcess.json` with the process definition in JSON format -In case your external services are corrupted, you can import from the created file in `./srv/workflows/` as well: +In case your external services are corrupted, you can re-import from the saved file in `./srv/workflows/`: ```bash cds import --from process ./srv/workflows/eu12.myorg.myproject.myProcess.json diff --git a/lib/processImport/index.ts b/lib/processImport/index.ts index bb758ad..5aa2b3c 100644 --- a/lib/processImport/index.ts +++ b/lib/processImport/index.ts @@ -34,7 +34,11 @@ export async function importProcess( options.file = filePath; return buildCsnModel(processHeader, dataTypeCache); } - const processHeader = loadProcessHeader(jsonFile); + + const { processHeader, savedFilePath } = await loadProcessHeader(jsonFile); + if (savedFilePath) { + options.file = savedFilePath; + } return buildCsnModel(processHeader, dataTypeCache); } @@ -114,18 +118,34 @@ async function createApiClient(): Promise { // Load process header from file // ============================================================================ -function loadProcessHeader(filePath: string): ProcessHeader { +async function loadProcessHeader( + filePath: string, +): Promise<{ processHeader: ProcessHeader; savedFilePath?: string }> { const content = fs.readFileSync(path.resolve(filePath), 'utf-8'); const parsed = JSON.parse(content); let header: ProcessHeader; + let savedFilePath: string | undefined; + if (isRawWorkflowJson(parsed)) { LOG.debug('Detected raw SBPA workflow JSON format, converting to ProcessHeader...'); header = convertWorkflowToProcessHeader(parsed); + LOG.debug('Converted ProcessHeader:', JSON.stringify(header, null, 2)); + + savedFilePath = path.join( + cds.root, + 'srv', + 'workflows', + `${header.projectId}.${header.identifier}.json`, + ); + await fs.promises.mkdir(path.dirname(savedFilePath), { recursive: true }); + await fs.promises.writeFile(savedFilePath, JSON.stringify(header, null, 2), 'utf8'); } else { header = parsed as ProcessHeader; + savedFilePath = undefined; } header.dataTypes?.forEach((dt) => dataTypeCache.set(dt.uid, dt)); - return header; + + return { processHeader: header, savedFilePath }; } diff --git a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.json b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.json index 339d47f..72264a9 100644 --- a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.json +++ b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.json @@ -1,10 +1,9 @@ { "uid": "5edc02ff-ce9a-4aaf-94bb-440b0c981a20", "name": "ImportProcess_Attributes_And_Outputs", - "description": "", + "identifier": "importProcess_Attributes_And_Outputs", "type": "bpi.process", - "createdAt": "2026-03-18T10:23:31.648909Z", - "updatedAt": "2026-03-18T12:48:22.484258Z", + "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", "header": { "inputs": { "title": "inputs", @@ -13,23 +12,28 @@ "definitions": { "date": { "type": "string", - "format": "date" + "format": "date", + "title": "date" }, "dateTime": { "type": "string", - "format": "date-time" + "format": "date-time", + "title": "dateTime" }, "password": { "type": "string", - "password": true + "password": true, + "title": "password" }, "time": { "type": "string", - "format": "time" + "format": "time", + "title": "time" }, "documentFolder": { "type": "string", - "format": "document-folder" + "format": "document-folder", + "title": "documentFolder" } }, "properties": { @@ -50,7 +54,8 @@ "refName": "ImportProcess_Complex_DataType" }, "title": "Complexe", - "description": "" + "description": "", + "refName": "ImportProcess_Complex_DataType" }, "optionalcomplexe": { "$ref": "$.11cbfe86-2e73-4198-868a-d7a2115d82c1", @@ -72,23 +77,28 @@ "definitions": { "date": { "type": "string", - "format": "date" + "format": "date", + "title": "date" }, "dateTime": { "type": "string", - "format": "date-time" + "format": "date-time", + "title": "dateTime" }, "password": { "type": "string", - "password": true + "password": true, + "title": "password" }, "time": { "type": "string", - "format": "time" + "format": "time", + "title": "time" }, "documentFolder": { "type": "string", - "format": "document-folder" + "format": "document-folder", + "title": "documentFolder" } }, "properties": { @@ -115,7 +125,8 @@ "refName": "ImportProcess_Complex_DataType" }, "title": "Complexe", - "description": "" + "description": "", + "refName": "ImportProcess_Complex_DataType" } }, "required": [ @@ -124,8 +135,6 @@ ] }, "processAttributes": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "processAttributes", "type": "object", "properties": {}, "required": [] @@ -137,17 +146,12 @@ "type": "both" } ], - "identifier": "importProcess_Attributes_And_Outputs", - "valid": true, - "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", "dataTypes": [ { "uid": "11cbfe86-2e73-4198-868a-d7a2115d82c1", "name": "ImportProcess_Complex_DataType", - "description": "", + "identifier": "ImportProcess_Complex_DataType", "type": "datatype", - "createdAt": "2026-03-18T10:28:54.794097Z", - "updatedAt": "2026-03-18T10:31:37.975015Z", "header": { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", @@ -160,11 +164,13 @@ "properties": { "SubString1": { "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6" + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", + "title": "SubString1" }, "Substring2": { "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94" + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", + "title": "Substring2" } }, "required": [ @@ -172,7 +178,8 @@ "Substring2" ] }, - "type": "array" + "type": "array", + "title": "StringList" }, "StringType": { "type": "object", @@ -187,21 +194,27 @@ "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", "properties": { "SubSubSubDate": { - "uid": "361a2e90-c32d-421d-8077-6a412436f031", - "$ref": "#/definitions/date" + "type": "string", + "format": "date", + "title": "SubSubSubDate" }, "SubSubSubPassword": { - "uid": "9901ea3d-4e0f-4421-981e-22fbc0d35cc6", - "$ref": "#/definitions/password" + "type": "string", + "password": true, + "title": "SubSubSubPassword" }, "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365" + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", + "title": "SubSubSubAny" } - } + }, + "title": "SubSubStringType" } - } + }, + "title": "SubStringType" } - } + }, + "title": "StringType" } }, "required": [ @@ -210,17 +223,18 @@ "definitions": { "password": { "type": "string", - "password": true + "password": true, + "title": "password" }, "date": { "type": "string", - "format": "date" + "format": "date", + "title": "date" } }, - "version": 1 - }, - "identifier": "importProcess_Complex_DataType", - "valid": true + "version": 1, + "description": "" + } } ] } \ No newline at end of file diff --git a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.json b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.json index 155ebcc..1d011fa 100644 --- a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.json +++ b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.json @@ -1,10 +1,9 @@ { "uid": "edb7b7fd-8a1c-4c51-a19f-b357aece8428", "name": "ImportProcess_Complex_Inputs", - "description": "", + "identifier": "importProcess_Complex_Inputs", "type": "bpi.process", - "createdAt": "2026-03-18T10:23:06.672697Z", - "updatedAt": "2026-03-19T11:43:58.132360Z", + "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", "header": { "inputs": { "title": "inputs", @@ -13,23 +12,28 @@ "definitions": { "date": { "type": "string", - "format": "date" + "format": "date", + "title": "date" }, "dateTime": { "type": "string", - "format": "date-time" + "format": "date-time", + "title": "dateTime" }, "password": { "type": "string", - "password": true + "password": true, + "title": "password" }, "time": { "type": "string", - "format": "time" + "format": "time", + "title": "time" }, "documentFolder": { "type": "string", - "format": "document-folder" + "format": "document-folder", + "title": "documentFolder" } }, "properties": { @@ -66,8 +70,6 @@ "properties": {} }, "processAttributes": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "processAttributes", "type": "object", "properties": {}, "required": [] @@ -79,17 +81,12 @@ "type": "both" } ], - "identifier": "importProcess_Complex_Inputs", - "valid": true, - "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", "dataTypes": [ { "uid": "11cbfe86-2e73-4198-868a-d7a2115d82c1", "name": "ImportProcess_Complex_DataType", - "description": "", + "identifier": "ImportProcess_Complex_DataType", "type": "datatype", - "createdAt": "2026-03-18T10:28:54.794097Z", - "updatedAt": "2026-03-18T10:31:37.975015Z", "header": { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", @@ -102,11 +99,13 @@ "properties": { "SubString1": { "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6" + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", + "title": "SubString1" }, "Substring2": { "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94" + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", + "title": "Substring2" } }, "required": [ @@ -114,7 +113,8 @@ "Substring2" ] }, - "type": "array" + "type": "array", + "title": "StringList" }, "StringType": { "type": "object", @@ -129,21 +129,27 @@ "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", "properties": { "SubSubSubDate": { - "uid": "361a2e90-c32d-421d-8077-6a412436f031", - "$ref": "#/definitions/date" + "type": "string", + "format": "date", + "title": "SubSubSubDate" }, "SubSubSubPassword": { - "uid": "9901ea3d-4e0f-4421-981e-22fbc0d35cc6", - "$ref": "#/definitions/password" + "type": "string", + "password": true, + "title": "SubSubSubPassword" }, "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365" + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", + "title": "SubSubSubAny" } - } + }, + "title": "SubSubStringType" } - } + }, + "title": "SubStringType" } - } + }, + "title": "StringType" } }, "required": [ @@ -152,17 +158,18 @@ "definitions": { "password": { "type": "string", - "password": true + "password": true, + "title": "password" }, "date": { "type": "string", - "format": "date" + "format": "date", + "title": "date" } }, - "version": 1 - }, - "identifier": "importProcess_Complex_DataType", - "valid": true + "version": 1, + "description": "" + } } ] } \ No newline at end of file diff --git a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.json b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.json index 19d8798..13169e3 100644 --- a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.json +++ b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.json @@ -1,10 +1,9 @@ { "uid": "0fc541c5-5266-458e-801a-9fe7fbfc32e5", "name": "ImportProcess_Simple_Inputs", - "description": "", + "identifier": "importProcess_Simple_Inputs", "type": "bpi.process", - "createdAt": "2026-03-18T10:22:42.550371Z", - "updatedAt": "2026-03-18T10:25:09.164955Z", + "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", "header": { "inputs": { "title": "inputs", @@ -13,23 +12,28 @@ "definitions": { "date": { "type": "string", - "format": "date" + "format": "date", + "title": "date" }, "dateTime": { "type": "string", - "format": "date-time" + "format": "date-time", + "title": "dateTime" }, "password": { "type": "string", - "password": true + "password": true, + "title": "password" }, "time": { "type": "string", - "format": "time" + "format": "time", + "title": "time" }, "documentFolder": { "type": "string", - "format": "document-folder" + "format": "document-folder", + "title": "documentFolder" } }, "properties": { @@ -49,17 +53,20 @@ "description": "" }, "date": { - "$ref": "#/definitions/date", + "type": "string", + "format": "date", "title": "Date", "description": "" }, "datetime": { - "$ref": "#/definitions/dateTime", + "type": "string", + "format": "date-time", "title": "DateTime", "description": "" }, "documentfolder": { - "$ref": "#/definitions/documentFolder", + "type": "string", + "format": "document-folder", "title": "DocumentFolder", "description": "" } @@ -80,14 +87,9 @@ "properties": {} }, "processAttributes": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "processAttributes", "type": "object", "properties": {}, "required": [] } - }, - "identifier": "importProcess_Simple_Inputs", - "valid": true, - "projectId": "eu12.cdsmunich.capprocesspluginhybridtest" + } } \ No newline at end of file From 2f6da2022260b2d91a4ead670af5645f574e3ddf Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Wed, 1 Apr 2026 15:16:30 +0200 Subject: [PATCH 4/6] fixed tests path --- lib/processImport/index.ts | 30 ++++++++++++++++++++---------- lib/processImportRegistration.ts | 6 +++++- tests/hybrid/processImport.test.ts | 7 ++++--- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/lib/processImport/index.ts b/lib/processImport/index.ts index 5aa2b3c..0c6a516 100644 --- a/lib/processImport/index.ts +++ b/lib/processImport/index.ts @@ -14,6 +14,7 @@ const LOG = cds.log(PROCESS_LOGGER_PREFIX); interface ImportOptions { name?: string; file?: string; + saveProcessHeader?: boolean; } /** @@ -35,9 +36,12 @@ export async function importProcess( return buildCsnModel(processHeader, dataTypeCache); } - const { processHeader, savedFilePath } = await loadProcessHeader(jsonFile); - if (savedFilePath) { - options.file = savedFilePath; + const { processHeader, targetFilePath } = await loadProcessHeader( + jsonFile, + options.saveProcessHeader ?? false, + ); + if (targetFilePath) { + options.file = targetFilePath; } return buildCsnModel(processHeader, dataTypeCache); } @@ -120,32 +124,38 @@ async function createApiClient(): Promise { async function loadProcessHeader( filePath: string, -): Promise<{ processHeader: ProcessHeader; savedFilePath?: string }> { + saveProcessHeader: boolean = false, +): Promise<{ processHeader: ProcessHeader; targetFilePath?: string }> { const content = fs.readFileSync(path.resolve(filePath), 'utf-8'); const parsed = JSON.parse(content); let header: ProcessHeader; - let savedFilePath: string | undefined; + let targetFilePath: string | undefined; if (isRawWorkflowJson(parsed)) { LOG.debug('Detected raw SBPA workflow JSON format, converting to ProcessHeader...'); header = convertWorkflowToProcessHeader(parsed); LOG.debug('Converted ProcessHeader:', JSON.stringify(header, null, 2)); - savedFilePath = path.join( + // Always compute the target file path (for options.file) + targetFilePath = path.join( cds.root, 'srv', 'workflows', `${header.projectId}.${header.identifier}.json`, ); - await fs.promises.mkdir(path.dirname(savedFilePath), { recursive: true }); - await fs.promises.writeFile(savedFilePath, JSON.stringify(header, null, 2), 'utf8'); + + // Only save if requested (CLI import) + if (saveProcessHeader) { + await fs.promises.mkdir(path.dirname(targetFilePath), { recursive: true}); + await fs.promises.writeFile(targetFilePath, JSON.stringify(header, null, 2), 'utf8'); + } } else { header = parsed as ProcessHeader; - savedFilePath = undefined; + targetFilePath = undefined; } header.dataTypes?.forEach((dt) => dataTypeCache.set(dt.uid, dt)); - return { processHeader: header, savedFilePath }; + return { processHeader: header, targetFilePath }; } diff --git a/lib/processImportRegistration.ts b/lib/processImportRegistration.ts index d4b777f..320dfb4 100644 --- a/lib/processImportRegistration.ts +++ b/lib/processImportRegistration.ts @@ -17,5 +17,9 @@ export function registerProcessImport() { // @ts-expect-error: process does not exist on cds.import type cds.import.from ??= {}; // @ts-expect-error: from does not exist on cds.import type - cds.import.from.process = importProcess; + cds.import.from.process = (jsonFile: string, options: any = {}) => { + // When called via CLI, always save the ProcessHeader JSON + options.saveProcessHeader = true; + return importProcess(jsonFile, options); + }; } diff --git a/tests/hybrid/processImport.test.ts b/tests/hybrid/processImport.test.ts index 1baedf7..6187d79 100644 --- a/tests/hybrid/processImport.test.ts +++ b/tests/hybrid/processImport.test.ts @@ -6,6 +6,7 @@ const NAMESPACE = 'eu12.cdsmunich.capprocesspluginhybridtest'; const IMPORTED_CDS_DIR = path.resolve(__dirname, 'importedCDS'); const EXTERNAL_CDS_DIR = path.resolve(__dirname, '..', 'bookshop', 'srv', 'external'); const WORKFLOWS_DIR = path.resolve(__dirname, '..', 'bookshop', 'srv', 'workflows'); +const DOWNLOADED_MODELS_DIR = path.resolve(__dirname, 'downloadedModels'); function readCdsFile(dir: string, processName: string): string { const filePath = path.join(dir, `${NAMESPACE}.${processName}.cds`); @@ -38,7 +39,7 @@ describe('Process Import: imported CDS files match external definitions', () => describe('Process Import: raw SBPA workflow JSON produces same CSN as ProcessHeader JSON', () => { it('should produce identical CSN for importProcess_Simple_Inputs', async () => { const rawCsn = await importProcess( - path.join(WORKFLOWS_DIR, 'ImportProcess_Simple_Inputs.json'), + path.join(DOWNLOADED_MODELS_DIR, 'ImportProcess_Simple_Inputs.json'), ); const headerCsn = await importProcess( path.join(WORKFLOWS_DIR, `${NAMESPACE}.importProcess_Simple_Inputs.json`), @@ -49,7 +50,7 @@ describe('Process Import: raw SBPA workflow JSON produces same CSN as ProcessHea it('should produce identical CSN for importProcess_Complex_Inputs', async () => { const rawCsn = await importProcess( - path.join(WORKFLOWS_DIR, 'ImportProcess_Complex_Inputs.json'), + path.join(DOWNLOADED_MODELS_DIR, 'ImportProcess_Complex_Inputs.json'), ); const headerCsn = await importProcess( path.join(WORKFLOWS_DIR, `${NAMESPACE}.importProcess_Complex_Inputs.json`), @@ -60,7 +61,7 @@ describe('Process Import: raw SBPA workflow JSON produces same CSN as ProcessHea it('should produce identical CSN for importProcess_Attributes_And_Outputs', async () => { const rawCsn = await importProcess( - path.join(WORKFLOWS_DIR, 'ImportProcess_Attributes_And_Outputs.json'), + path.join(DOWNLOADED_MODELS_DIR, 'ImportProcess_Attributes_And_Outputs.json'), ); const headerCsn = await importProcess( path.join(WORKFLOWS_DIR, `${NAMESPACE}.importProcess_Attributes_And_Outputs.json`), From a28f94e0401e0781fcbd26d1169c77c9232b79ab Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Wed, 1 Apr 2026 15:37:06 +0200 Subject: [PATCH 5/6] fix lint --- README.md | 2 + lib/processImport/index.ts | 2 +- .../ImportProcess_Attributes_And_Outputs.json | 1614 ++++++++--------- .../ImportProcess_Complex_Inputs.json | 634 ++++--- .../ImportProcess_Simple_Inputs.json | 365 ++-- 5 files changed, 1283 insertions(+), 1334 deletions(-) diff --git a/README.md b/README.md index 390efeb..0fb2695 100644 --- a/README.md +++ b/README.md @@ -416,6 +416,7 @@ cds import --from process ~/Downloads/.json ``` This will create: + - A new CDS service in `./srv/external/{projectId}.{processIdentifier}.cds` with the process definition used for build-time validation and the typed programmatic API - A converted ProcessHeader JSON file in `./srv/workflows/{projectId}.{processIdentifier}.json` for future re-imports @@ -430,6 +431,7 @@ cds import --from process --name eu12.myorg.myproject.myProcess ``` This will create: + - A new CDS service in `./srv/external/eu12.myorg.myproject.myProcess.cds` with the process definition - A ProcessHeader JSON file in `./srv/workflows/eu12.myorg.myproject.myProcess.json` with the process definition in JSON format diff --git a/lib/processImport/index.ts b/lib/processImport/index.ts index 0c6a516..f93bb29 100644 --- a/lib/processImport/index.ts +++ b/lib/processImport/index.ts @@ -147,7 +147,7 @@ async function loadProcessHeader( // Only save if requested (CLI import) if (saveProcessHeader) { - await fs.promises.mkdir(path.dirname(targetFilePath), { recursive: true}); + await fs.promises.mkdir(path.dirname(targetFilePath), { recursive: true }); await fs.promises.writeFile(targetFilePath, JSON.stringify(header, null, 2), 'utf8'); } } else { diff --git a/tests/hybrid/downloadedModels/ImportProcess_Attributes_And_Outputs.json b/tests/hybrid/downloadedModels/ImportProcess_Attributes_And_Outputs.json index 8958e9f..ec43993 100644 --- a/tests/hybrid/downloadedModels/ImportProcess_Attributes_And_Outputs.json +++ b/tests/hybrid/downloadedModels/ImportProcess_Attributes_And_Outputs.json @@ -1,867 +1,835 @@ { - "contents": { - "9d2f0a4b-1765-4ebf-8266-e03de9207cdf": { - "classDefinition": "com.sap.bpm.wfs.Model", - "id": "eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs", - "subject": "ImportProcess_Attributes_And_Outputs", - "businessKey": "", - "customAttributes": [ + "contents": { + "9d2f0a4b-1765-4ebf-8266-e03de9207cdf": { + "classDefinition": "com.sap.bpm.wfs.Model", + "id": "eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs", + "subject": "ImportProcess_Attributes_And_Outputs", + "businessKey": "", + "customAttributes": [ + { + "id": "intattribute", + "label": "IntAttribute", + "value": "${context.startEvent.intattribute}", + "type": "number" + }, + { + "id": "stringattribute", + "label": "StringAttribute", + "value": "${context.startEvent.stringattribute}", + "type": "string" + } + ], + "propagateChildErrors": false, + "processIdentifier": "importProcess_Attributes_And_Outputs", + "artifactId": "5edc02ff-ce9a-4aaf-94bb-440b0c981a20", + "name": "ImportProcess_Attributes_And_Outputs", + "events": { + "5ab063dc-8539-4b00-804c-10cbeb292a87": { + "name": "StartEvent" + }, + "345c87ea-0bc9-491c-927d-d1c11feeba19": { + "name": "EndEvent" + } + }, + "sequenceFlows": { + "87e0ae9f-6c83-4a88-a13c-a3c63c271acf": { + "name": "sequenceFlow" + } + }, + "schemas": "a753c266-5585-49b6-99a4-0cbbb6d5edd1", + "technicalDeploymentId": "dd8daef0-23f3-4411-9c80-96d6f513db98", + "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", + "projectVersion": "1.0.11" + }, + "5ab063dc-8539-4b00-804c-10cbeb292a87": { + "classDefinition": "com.sap.bpm.wfs.StartEvent", + "mappings": { + "out": { + "['custom']": { + "expression": { + "type": "ObjectExpression", + "properties": [] + }, + "type": "expression" + }, + "['startEvent']": { + "expression": { + "type": "CallExpression", + "callee": { + "type": "Identifier", + "name": "SAP_SAFE_PARSE" + }, + "arguments": [ { - "id": "intattribute", - "label": "IntAttribute", - "value": "${context.startEvent.intattribute}", - "type": "number" + "type": "Identifier", + "name": "output" }, { - "id": "stringattribute", - "label": "StringAttribute", - "value": "${context.startEvent.stringattribute}", - "type": "string" + "type": "ArrayExpression", + "elements": [] } - ], - "propagateChildErrors": false, - "processIdentifier": "importProcess_Attributes_And_Outputs", - "artifactId": "5edc02ff-ce9a-4aaf-94bb-440b0c981a20", - "name": "ImportProcess_Attributes_And_Outputs", - "events": { - "5ab063dc-8539-4b00-804c-10cbeb292a87": { - "name": "StartEvent" + ] + }, + "type": "expression" + } + } + }, + "interfaces": { + "out": { + "schema": "pb3e7XuqdebhSrN_5564uNc", + "definition": "#/definitions/out" + } + }, + "id": "startEvent", + "name": "StartEvent" + }, + "345c87ea-0bc9-491c-927d-d1c11feeba19": { + "classDefinition": "com.sap.bpm.wfs.EndEvent", + "mappings": { + "in": { + "['complexe']": { + "expression": { + "type": "CallExpression", + "callee": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "LogicalExpression", + "operator": "||", + "left": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "Identifier", + "name": "context" + }, + "property": { + "type": "Identifier", + "name": "startEvent" + } + }, + "property": { + "type": "Identifier", + "name": "complexe" + } + }, + "right": { + "type": "ArrayExpression", + "elements": [] + } }, - "345c87ea-0bc9-491c-927d-d1c11feeba19": { - "name": "EndEvent" + "property": { + "type": "Identifier", + "name": "map" } + }, + "arguments": [ + { + "type": "FunctionExpression", + "params": [ + { + "type": "Identifier", + "name": "listItem" + } + ], + "body": { + "type": "BlockStatement", + "body": [ + { + "type": "ReturnStatement", + "argument": { + "type": "ObjectExpression", + "properties": [ + { + "type": "Property", + "key": { + "type": "Identifier", + "name": "StringList" + }, + "computed": false, + "value": { + "type": "CallExpression", + "callee": { + "type": "Identifier", + "name": "SAP_SAFE_PARSE" + }, + "arguments": [ + { + "type": "Identifier", + "name": "listItem" + }, + { + "type": "ArrayExpression", + "elements": [ + { + "type": "Literal", + "value": "StringList", + "raw": "'StringList'" + } + ] + } + ] + }, + "kind": "init", + "method": false, + "shorthand": false + } + ] + } + } + ] + }, + "generator": false, + "expression": false, + "async": false + } + ] }, - "sequenceFlows": { - "87e0ae9f-6c83-4a88-a13c-a3c63c271acf": { - "name": "sequenceFlow" + "type": "expression" + }, + "['optionalcomplexe']": { + "expression": { + "type": "CallExpression", + "callee": { + "type": "Identifier", + "name": "SAP_SAFE_PARSE" + }, + "arguments": [ + { + "type": "Identifier", + "name": "context" + }, + { + "type": "ArrayExpression", + "elements": [ + { + "type": "Literal", + "value": "startEvent", + "raw": "'startEvent'" + }, + { + "type": "Literal", + "value": "optionalcomplexe", + "raw": "'optionalcomplexe'" + } + ] } + ] }, - "schemas": "a753c266-5585-49b6-99a4-0cbbb6d5edd1", - "technicalDeploymentId": "dd8daef0-23f3-4411-9c80-96d6f513db98", - "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", - "projectVersion": "1.0.11" - }, - "5ab063dc-8539-4b00-804c-10cbeb292a87": { - "classDefinition": "com.sap.bpm.wfs.StartEvent", - "mappings": { - "out": { - "['custom']": { - "expression": { - "type": "ObjectExpression", - "properties": [] - }, - "type": "expression" + "type": "expression" + }, + "['optionalstring']": { + "expression": { + "type": "CallExpression", + "callee": { + "type": "Identifier", + "name": "SAP_SAFE_PARSE" + }, + "arguments": [ + { + "type": "Identifier", + "name": "context" + }, + { + "type": "ArrayExpression", + "elements": [ + { + "type": "Literal", + "value": "startEvent", + "raw": "'startEvent'" }, - "['startEvent']": { - "expression": { - "type": "CallExpression", - "callee": { - "type": "Identifier", - "name": "SAP_SAFE_PARSE" - }, - "arguments": [ - { - "type": "Identifier", - "name": "output" - }, - { - "type": "ArrayExpression", - "elements": [] - } - ] - }, - "type": "expression" + { + "type": "Literal", + "value": "stringattribute", + "raw": "'stringattribute'" } + ] } + ] }, - "interfaces": { - "out": { - "schema": "pb3e7XuqdebhSrN_5564uNc", - "definition": "#/definitions/out" + "type": "expression" + }, + "['string']": { + "expression": { + "type": "CallExpression", + "callee": { + "type": "Identifier", + "name": "SAP_SAFE_PARSE" + }, + "arguments": [ + { + "type": "Identifier", + "name": "context" + }, + { + "type": "ArrayExpression", + "elements": [ + { + "type": "Literal", + "value": "startEvent", + "raw": "'startEvent'" + }, + { + "type": "Literal", + "value": "stringattribute", + "raw": "'stringattribute'" + } + ] } + ] }, - "id": "startEvent", - "name": "StartEvent" - }, - "345c87ea-0bc9-491c-927d-d1c11feeba19": { - "classDefinition": "com.sap.bpm.wfs.EndEvent", - "mappings": { - "in": { - "['complexe']": { - "expression": { - "type": "CallExpression", - "callee": { - "type": "MemberExpression", - "computed": false, - "object": { - "type": "LogicalExpression", - "operator": "||", - "left": { - "type": "MemberExpression", - "computed": false, - "object": { - "type": "MemberExpression", - "computed": false, - "object": { - "type": "Identifier", - "name": "context" - }, - "property": { - "type": "Identifier", - "name": "startEvent" - } - }, - "property": { - "type": "Identifier", - "name": "complexe" - } - }, - "right": { - "type": "ArrayExpression", - "elements": [] - } - }, - "property": { - "type": "Identifier", - "name": "map" - } + "type": "expression" + } + } + }, + "interfaces": { + "in": { + "schema": "pb3e7XuqdebhSrN_5564uNc", + "definition": "#/definitions/in" + } + }, + "id": "endEvent_1", + "name": "EndEvent" + }, + "87e0ae9f-6c83-4a88-a13c-a3c63c271acf": { + "classDefinition": "com.sap.bpm.wfs.SequenceFlow", + "id": "pbtnevEtrats", + "name": "sequenceFlow", + "sourceRef": "5ab063dc-8539-4b00-804c-10cbeb292a87", + "targetRef": "345c87ea-0bc9-491c-927d-d1c11feeba19" + }, + "a753c266-5585-49b6-99a4-0cbbb6d5edd1": { + "classDefinition": "com.sap.bpm.wfs.Schemas", + "schemas": { + "pb3e7XuqdebhSrN_5564uNc": { + "origin": "local", + "version": "1.0.0", + "schemaRef": "$.5edc02ff-ce9a-4aaf-94bb-440b0c981a20", + "content": { + "definitions": { + "out": { + "title": "inputs", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "date": { + "type": "string", + "format": "date", + "title": "date" + }, + "dateTime": { + "type": "string", + "format": "date-time", + "title": "dateTime" + }, + "password": { + "type": "string", + "password": true, + "title": "password" + }, + "time": { + "type": "string", + "format": "time", + "title": "time" + }, + "documentFolder": { + "type": "string", + "format": "document-folder", + "title": "documentFolder" + } + }, + "properties": { + "stringattribute": { + "type": "string", + "title": "StringAttribute", + "description": "" + }, + "intattribute": { + "type": "number", + "title": "IntAttribute", + "description": "" + }, + "complexe": { + "type": "array", + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Complexe", + "properties": { + "StringList": { + "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", + "items": { + "type": "object", + "properties": { + "SubString1": { + "type": "string", + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", + "title": "SubString1" + }, + "Substring2": { + "type": "string", + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", + "title": "Substring2" + } }, - "arguments": [ - { - "type": "FunctionExpression", - "params": [ - { - "type": "Identifier", - "name": "listItem" - } - ], - "body": { - "type": "BlockStatement", - "body": [ - { - "type": "ReturnStatement", - "argument": { - "type": "ObjectExpression", - "properties": [ - { - "type": "Property", - "key": { - "type": "Identifier", - "name": "StringList" - }, - "computed": false, - "value": { - "type": "CallExpression", - "callee": { - "type": "Identifier", - "name": "SAP_SAFE_PARSE" - }, - "arguments": [ - { - "type": "Identifier", - "name": "listItem" - }, - { - "type": "ArrayExpression", - "elements": [ - { - "type": "Literal", - "value": "StringList", - "raw": "'StringList'" - } - ] - } - ] - }, - "kind": "init", - "method": false, - "shorthand": false - } - ] - } - } - ] + "required": ["SubString1", "Substring2"] + }, + "type": "array", + "title": "StringList" + }, + "StringType": { + "type": "object", + "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", + "properties": { + "SubStringType": { + "type": "object", + "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", + "properties": { + "SubSubStringType": { + "type": "object", + "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", + "properties": { + "SubSubSubDate": { + "type": "string", + "format": "date", + "title": "SubSubSubDate" + }, + "SubSubSubPassword": { + "type": "string", + "password": true, + "title": "SubSubSubPassword" }, - "generator": false, - "expression": false, - "async": false + "SubSubSubAny": { + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", + "title": "SubSubSubAny" + } + }, + "title": "SubSubStringType" } - ] + }, + "title": "SubStringType" + } + }, + "title": "StringType" + } + }, + "required": ["StringList"], + "definitions": { + "password": { + "type": "string", + "password": true, + "title": "password" }, - "type": "expression" + "date": { + "type": "string", + "format": "date", + "title": "date" + } + }, + "version": 1, + "description": "", + "refName": "ImportProcess_Complex_DataType" }, - "['optionalcomplexe']": { - "expression": { - "type": "CallExpression", - "callee": { - "type": "Identifier", - "name": "SAP_SAFE_PARSE" + "title": "Complexe", + "description": "", + "refName": "ImportProcess_Complex_DataType" + }, + "optionalcomplexe": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "OptionalComplexe", + "properties": { + "StringList": { + "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", + "items": { + "type": "object", + "properties": { + "SubString1": { + "type": "string", + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", + "title": "SubString1" }, - "arguments": [ - { - "type": "Identifier", - "name": "context" - }, - { - "type": "ArrayExpression", - "elements": [ - { - "type": "Literal", - "value": "startEvent", - "raw": "'startEvent'" - }, - { - "type": "Literal", - "value": "optionalcomplexe", - "raw": "'optionalcomplexe'" - } - ] - } - ] + "Substring2": { + "type": "string", + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", + "title": "Substring2" + } + }, + "required": ["SubString1", "Substring2"] }, - "type": "expression" - }, - "['optionalstring']": { - "expression": { - "type": "CallExpression", - "callee": { - "type": "Identifier", - "name": "SAP_SAFE_PARSE" - }, - "arguments": [ - { - "type": "Identifier", - "name": "context" + "type": "array", + "title": "StringList" + }, + "StringType": { + "type": "object", + "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", + "properties": { + "SubStringType": { + "type": "object", + "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", + "properties": { + "SubSubStringType": { + "type": "object", + "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", + "properties": { + "SubSubSubDate": { + "type": "string", + "format": "date", + "title": "SubSubSubDate" + }, + "SubSubSubPassword": { + "type": "string", + "password": true, + "title": "SubSubSubPassword" + }, + "SubSubSubAny": { + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", + "title": "SubSubSubAny" + } }, - { - "type": "ArrayExpression", - "elements": [ - { - "type": "Literal", - "value": "startEvent", - "raw": "'startEvent'" - }, - { - "type": "Literal", - "value": "stringattribute", - "raw": "'stringattribute'" - } - ] - } - ] + "title": "SubSubStringType" + } + }, + "title": "SubStringType" + } }, - "type": "expression" + "title": "StringType" + } }, - "['string']": { - "expression": { - "type": "CallExpression", - "callee": { - "type": "Identifier", - "name": "SAP_SAFE_PARSE" + "required": ["StringList"], + "definitions": { + "password": { + "type": "string", + "password": true, + "title": "password" + }, + "date": { + "type": "string", + "format": "date", + "title": "date" + } + }, + "version": 1, + "description": "", + "refName": "ImportProcess_Complex_DataType" + } + }, + "required": ["stringattribute", "intattribute", "complexe"] + }, + "in": { + "title": "outputs", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "date": { + "type": "string", + "format": "date", + "title": "date" + }, + "dateTime": { + "type": "string", + "format": "date-time", + "title": "dateTime" + }, + "password": { + "type": "string", + "password": true, + "title": "password" + }, + "time": { + "type": "string", + "format": "time", + "title": "time" + }, + "documentFolder": { + "type": "string", + "format": "document-folder", + "title": "documentFolder" + } + }, + "properties": { + "string": { + "type": "string", + "title": "String", + "description": "" + }, + "optionalstring": { + "type": "string", + "title": "OptionalString", + "description": "" + }, + "optionalcomplexe": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "OptionalComplexe", + "properties": { + "StringList": { + "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", + "items": { + "type": "object", + "properties": { + "SubString1": { + "type": "string", + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", + "title": "SubString1" }, - "arguments": [ - { - "type": "Identifier", - "name": "context" - }, - { - "type": "ArrayExpression", - "elements": [ - { - "type": "Literal", - "value": "startEvent", - "raw": "'startEvent'" - }, - { - "type": "Literal", - "value": "stringattribute", - "raw": "'stringattribute'" - } - ] - } - ] + "Substring2": { + "type": "string", + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", + "title": "Substring2" + } + }, + "required": ["SubString1", "Substring2"] }, - "type": "expression" - } - } - }, - "interfaces": { - "in": { - "schema": "pb3e7XuqdebhSrN_5564uNc", - "definition": "#/definitions/in" - } - }, - "id": "endEvent_1", - "name": "EndEvent" - }, - "87e0ae9f-6c83-4a88-a13c-a3c63c271acf": { - "classDefinition": "com.sap.bpm.wfs.SequenceFlow", - "id": "pbtnevEtrats", - "name": "sequenceFlow", - "sourceRef": "5ab063dc-8539-4b00-804c-10cbeb292a87", - "targetRef": "345c87ea-0bc9-491c-927d-d1c11feeba19" - }, - "a753c266-5585-49b6-99a4-0cbbb6d5edd1": { - "classDefinition": "com.sap.bpm.wfs.Schemas", - "schemas": { - "pb3e7XuqdebhSrN_5564uNc": { - "origin": "local", - "version": "1.0.0", - "schemaRef": "$.5edc02ff-ce9a-4aaf-94bb-440b0c981a20", - "content": { - "definitions": { - "out": { - "title": "inputs", + "type": "array", + "title": "StringList" + }, + "StringType": { + "type": "object", + "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", + "properties": { + "SubStringType": { + "type": "object", + "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", + "properties": { + "SubSubStringType": { "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "date": { - "type": "string", - "format": "date", - "title": "date" - }, - "dateTime": { - "type": "string", - "format": "date-time", - "title": "dateTime" - }, - "password": { - "type": "string", - "password": true, - "title": "password" - }, - "time": { - "type": "string", - "format": "time", - "title": "time" - }, - "documentFolder": { - "type": "string", - "format": "document-folder", - "title": "documentFolder" - } - }, + "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", "properties": { - "stringattribute": { - "type": "string", - "title": "StringAttribute", - "description": "" - }, - "intattribute": { - "type": "number", - "title": "IntAttribute", - "description": "" - }, - "complexe": { - "type": "array", - "items": { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "title": "Complexe", - "properties": { - "StringList": { - "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", - "items": { - "type": "object", - "properties": { - "SubString1": { - "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", - "title": "SubString1" - }, - "Substring2": { - "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", - "title": "Substring2" - } - }, - "required": [ - "SubString1", - "Substring2" - ] - }, - "type": "array", - "title": "StringList" - }, - "StringType": { - "type": "object", - "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", - "properties": { - "SubStringType": { - "type": "object", - "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", - "properties": { - "SubSubStringType": { - "type": "object", - "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", - "properties": { - "SubSubSubDate": { - "type": "string", - "format": "date", - "title": "SubSubSubDate" - }, - "SubSubSubPassword": { - "type": "string", - "password": true, - "title": "SubSubSubPassword" - }, - "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", - "title": "SubSubSubAny" - } - }, - "title": "SubSubStringType" - } - }, - "title": "SubStringType" - } - }, - "title": "StringType" - } - }, - "required": [ - "StringList" - ], - "definitions": { - "password": { - "type": "string", - "password": true, - "title": "password" - }, - "date": { - "type": "string", - "format": "date", - "title": "date" - } - }, - "version": 1, - "description": "", - "refName": "ImportProcess_Complex_DataType" - }, - "title": "Complexe", - "description": "", - "refName": "ImportProcess_Complex_DataType" - }, - "optionalcomplexe": { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "title": "OptionalComplexe", - "properties": { - "StringList": { - "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", - "items": { - "type": "object", - "properties": { - "SubString1": { - "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", - "title": "SubString1" - }, - "Substring2": { - "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", - "title": "Substring2" - } - }, - "required": [ - "SubString1", - "Substring2" - ] - }, - "type": "array", - "title": "StringList" - }, - "StringType": { - "type": "object", - "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", - "properties": { - "SubStringType": { - "type": "object", - "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", - "properties": { - "SubSubStringType": { - "type": "object", - "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", - "properties": { - "SubSubSubDate": { - "type": "string", - "format": "date", - "title": "SubSubSubDate" - }, - "SubSubSubPassword": { - "type": "string", - "password": true, - "title": "SubSubSubPassword" - }, - "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", - "title": "SubSubSubAny" - } - }, - "title": "SubSubStringType" - } - }, - "title": "SubStringType" - } - }, - "title": "StringType" - } - }, - "required": [ - "StringList" - ], - "definitions": { - "password": { - "type": "string", - "password": true, - "title": "password" - }, - "date": { - "type": "string", - "format": "date", - "title": "date" - } - }, - "version": 1, - "description": "", - "refName": "ImportProcess_Complex_DataType" - } + "SubSubSubDate": { + "type": "string", + "format": "date", + "title": "SubSubSubDate" + }, + "SubSubSubPassword": { + "type": "string", + "password": true, + "title": "SubSubSubPassword" + }, + "SubSubSubAny": { + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", + "title": "SubSubSubAny" + } }, - "required": [ - "stringattribute", - "intattribute", - "complexe" - ] + "title": "SubSubStringType" + } }, - "in": { - "title": "outputs", - "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "date": { - "type": "string", - "format": "date", - "title": "date" - }, - "dateTime": { - "type": "string", - "format": "date-time", - "title": "dateTime" - }, - "password": { - "type": "string", - "password": true, - "title": "password" - }, - "time": { - "type": "string", - "format": "time", - "title": "time" - }, - "documentFolder": { - "type": "string", - "format": "document-folder", - "title": "documentFolder" - } - }, - "properties": { - "string": { - "type": "string", - "title": "String", - "description": "" - }, - "optionalstring": { - "type": "string", - "title": "OptionalString", - "description": "" + "title": "SubStringType" + } + }, + "title": "StringType" + } + }, + "required": ["StringList"], + "definitions": { + "password": { + "type": "string", + "password": true, + "title": "password" + }, + "date": { + "type": "string", + "format": "date", + "title": "date" + } + }, + "version": 1, + "description": "", + "refName": "ImportProcess_Complex_DataType" + }, + "complexe": { + "type": "array", + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Complexe", + "properties": { + "StringList": { + "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", + "items": { + "type": "object", + "properties": { + "SubString1": { + "type": "string", + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", + "title": "SubString1" + }, + "Substring2": { + "type": "string", + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", + "title": "Substring2" + } + }, + "required": ["SubString1", "Substring2"] + }, + "type": "array", + "title": "StringList" + }, + "StringType": { + "type": "object", + "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", + "properties": { + "SubStringType": { + "type": "object", + "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", + "properties": { + "SubSubStringType": { + "type": "object", + "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", + "properties": { + "SubSubSubDate": { + "type": "string", + "format": "date", + "title": "SubSubSubDate" }, - "optionalcomplexe": { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "title": "OptionalComplexe", - "properties": { - "StringList": { - "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", - "items": { - "type": "object", - "properties": { - "SubString1": { - "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", - "title": "SubString1" - }, - "Substring2": { - "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", - "title": "Substring2" - } - }, - "required": [ - "SubString1", - "Substring2" - ] - }, - "type": "array", - "title": "StringList" - }, - "StringType": { - "type": "object", - "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", - "properties": { - "SubStringType": { - "type": "object", - "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", - "properties": { - "SubSubStringType": { - "type": "object", - "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", - "properties": { - "SubSubSubDate": { - "type": "string", - "format": "date", - "title": "SubSubSubDate" - }, - "SubSubSubPassword": { - "type": "string", - "password": true, - "title": "SubSubSubPassword" - }, - "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", - "title": "SubSubSubAny" - } - }, - "title": "SubSubStringType" - } - }, - "title": "SubStringType" - } - }, - "title": "StringType" - } - }, - "required": [ - "StringList" - ], - "definitions": { - "password": { - "type": "string", - "password": true, - "title": "password" - }, - "date": { - "type": "string", - "format": "date", - "title": "date" - } - }, - "version": 1, - "description": "", - "refName": "ImportProcess_Complex_DataType" + "SubSubSubPassword": { + "type": "string", + "password": true, + "title": "SubSubSubPassword" }, - "complexe": { - "type": "array", - "items": { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "title": "Complexe", - "properties": { - "StringList": { - "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", - "items": { - "type": "object", - "properties": { - "SubString1": { - "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", - "title": "SubString1" - }, - "Substring2": { - "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", - "title": "Substring2" - } - }, - "required": [ - "SubString1", - "Substring2" - ] - }, - "type": "array", - "title": "StringList" - }, - "StringType": { - "type": "object", - "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", - "properties": { - "SubStringType": { - "type": "object", - "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", - "properties": { - "SubSubStringType": { - "type": "object", - "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", - "properties": { - "SubSubSubDate": { - "type": "string", - "format": "date", - "title": "SubSubSubDate" - }, - "SubSubSubPassword": { - "type": "string", - "password": true, - "title": "SubSubSubPassword" - }, - "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", - "title": "SubSubSubAny" - } - }, - "title": "SubSubStringType" - } - }, - "title": "SubStringType" - } - }, - "title": "StringType" - } - }, - "required": [ - "StringList" - ], - "definitions": { - "password": { - "type": "string", - "password": true, - "title": "password" - }, - "date": { - "type": "string", - "format": "date", - "title": "date" - } - }, - "version": 1, - "description": "", - "refName": "ImportProcess_Complex_DataType" - }, - "title": "Complexe", - "description": "", - "refName": "ImportProcess_Complex_DataType" + "SubSubSubAny": { + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", + "title": "SubSubSubAny" } - }, - "required": [ - "string", - "complexe" - ] + }, + "title": "SubSubStringType" + } + }, + "title": "SubStringType" } + }, + "title": "StringType" + } + }, + "required": ["StringList"], + "definitions": { + "password": { + "type": "string", + "password": true, + "title": "password" }, - "title": "pb3e7XuqdebhSrN_5564uNc" + "date": { + "type": "string", + "format": "date", + "title": "date" + } + }, + "version": 1, + "description": "", + "refName": "ImportProcess_Complex_DataType" + }, + "title": "Complexe", + "description": "", + "refName": "ImportProcess_Complex_DataType" + } + }, + "required": ["string", "complexe"] + } + }, + "title": "pb3e7XuqdebhSrN_5564uNc" + } + }, + "pbRw1egG6lgFqcxejg57tbi": { + "origin": "local", + "version": "1.0.0", + "schemaRef": "$.11cbfe86-2e73-4198-868a-d7a2115d82c1", + "content": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "ImportProcess_Complex_DataType", + "properties": { + "StringList": { + "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", + "items": { + "type": "object", + "properties": { + "SubString1": { + "type": "string", + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", + "title": "SubString1" + }, + "Substring2": { + "type": "string", + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", + "title": "Substring2" } + }, + "required": ["SubString1", "Substring2"] }, - "pbRw1egG6lgFqcxejg57tbi": { - "origin": "local", - "version": "1.0.0", - "schemaRef": "$.11cbfe86-2e73-4198-868a-d7a2115d82c1", - "content": { - "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "title": "StringList" + }, + "StringType": { + "type": "object", + "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", + "properties": { + "SubStringType": { + "type": "object", + "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", + "properties": { + "SubSubStringType": { "type": "object", - "title": "ImportProcess_Complex_DataType", + "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", "properties": { - "StringList": { - "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", - "items": { - "type": "object", - "properties": { - "SubString1": { - "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", - "title": "SubString1" - }, - "Substring2": { - "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", - "title": "Substring2" - } - }, - "required": [ - "SubString1", - "Substring2" - ] - }, - "type": "array", - "title": "StringList" - }, - "StringType": { - "type": "object", - "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", - "properties": { - "SubStringType": { - "type": "object", - "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", - "properties": { - "SubSubStringType": { - "type": "object", - "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", - "properties": { - "SubSubSubDate": { - "type": "string", - "format": "date", - "title": "SubSubSubDate" - }, - "SubSubSubPassword": { - "type": "string", - "password": true, - "title": "SubSubSubPassword" - }, - "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", - "title": "SubSubSubAny" - } - }, - "title": "SubSubStringType" - } - }, - "title": "SubStringType" - } - }, - "title": "StringType" - } - }, - "required": [ - "StringList" - ], - "definitions": { - "password": { - "type": "string", - "password": true, - "title": "password" - }, - "date": { - "type": "string", - "format": "date", - "title": "date" - } + "SubSubSubDate": { + "type": "string", + "format": "date", + "title": "SubSubSubDate" + }, + "SubSubSubPassword": { + "type": "string", + "password": true, + "title": "SubSubSubPassword" + }, + "SubSubSubAny": { + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", + "title": "SubSubSubAny" + } }, - "version": 1, - "description": "" - } - } - } - }, - "06b0ed72-8891-4aac-a8d1-4ac105cd5960": { - "classDefinition": "com.sap.bpm.wfs.Translation", - "bundles": { - "": { - "endEvent_1.name": "EndEvent", - "workflow.intattribute.label": "IntAttribute", - "workflow.stringattribute.label": "StringAttribute", - "workflow.name": "ImportProcess_Attributes_And_Outputs", - "workflow.subject": "ImportProcess_Attributes_And_Outputs", - "pbtnevEtrats.name": "sequenceFlow", - "startEvent.name": "StartEvent" - } - } + "title": "SubSubStringType" + } + }, + "title": "SubStringType" + } + }, + "title": "StringType" + } + }, + "required": ["StringList"], + "definitions": { + "password": { + "type": "string", + "password": true, + "title": "password" + }, + "date": { + "type": "string", + "format": "date", + "title": "date" + } + }, + "version": 1, + "description": "" + } + } + } + }, + "06b0ed72-8891-4aac-a8d1-4ac105cd5960": { + "classDefinition": "com.sap.bpm.wfs.Translation", + "bundles": { + "": { + "endEvent_1.name": "EndEvent", + "workflow.intattribute.label": "IntAttribute", + "workflow.stringattribute.label": "StringAttribute", + "workflow.name": "ImportProcess_Attributes_And_Outputs", + "workflow.subject": "ImportProcess_Attributes_And_Outputs", + "pbtnevEtrats.name": "sequenceFlow", + "startEvent.name": "StartEvent" } + } } -} \ No newline at end of file + } +} diff --git a/tests/hybrid/downloadedModels/ImportProcess_Complex_Inputs.json b/tests/hybrid/downloadedModels/ImportProcess_Complex_Inputs.json index 6c0215c..2238cff 100644 --- a/tests/hybrid/downloadedModels/ImportProcess_Complex_Inputs.json +++ b/tests/hybrid/downloadedModels/ImportProcess_Complex_Inputs.json @@ -1,343 +1,329 @@ { - "contents": { - "fef9b790-3fcd-4707-87c9-32808dcf2cb2": { - "classDefinition": "com.sap.bpm.wfs.Model", - "id": "eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs", - "subject": "ImportProcess_Complex_Inputs", - "businessKey": "", - "customAttributes": [], - "propagateChildErrors": false, - "processIdentifier": "importProcess_Complex_Inputs", - "artifactId": "edb7b7fd-8a1c-4c51-a19f-b357aece8428", - "name": "ImportProcess_Complex_Inputs", - "events": { - "ca7736c9-69e0-4a8e-9be2-a31a33ac0a83": { - "name": "StartEvent" - }, - "f68b8b77-663d-4106-8c40-f9548caa76e6": { - "name": "EndEvent" - } + "contents": { + "fef9b790-3fcd-4707-87c9-32808dcf2cb2": { + "classDefinition": "com.sap.bpm.wfs.Model", + "id": "eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs", + "subject": "ImportProcess_Complex_Inputs", + "businessKey": "", + "customAttributes": [], + "propagateChildErrors": false, + "processIdentifier": "importProcess_Complex_Inputs", + "artifactId": "edb7b7fd-8a1c-4c51-a19f-b357aece8428", + "name": "ImportProcess_Complex_Inputs", + "events": { + "ca7736c9-69e0-4a8e-9be2-a31a33ac0a83": { + "name": "StartEvent" + }, + "f68b8b77-663d-4106-8c40-f9548caa76e6": { + "name": "EndEvent" + } + }, + "sequenceFlows": { + "29987b52-df5e-40ad-a6d1-32b0f983216d": { + "name": "sequenceFlow" + } + }, + "schemas": "a87c890f-99a7-49e4-a2c1-b4fcf7433730", + "technicalDeploymentId": "dd8daef0-23f3-4411-9c80-96d6f513db98", + "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", + "projectVersion": "1.0.11" + }, + "ca7736c9-69e0-4a8e-9be2-a31a33ac0a83": { + "classDefinition": "com.sap.bpm.wfs.StartEvent", + "mappings": { + "out": { + "['custom']": { + "expression": { + "type": "ObjectExpression", + "properties": [] }, - "sequenceFlows": { - "29987b52-df5e-40ad-a6d1-32b0f983216d": { - "name": "sequenceFlow" + "type": "expression" + }, + "['startEvent']": { + "expression": { + "type": "CallExpression", + "callee": { + "type": "Identifier", + "name": "SAP_SAFE_PARSE" + }, + "arguments": [ + { + "type": "Identifier", + "name": "output" + }, + { + "type": "ArrayExpression", + "elements": [] } + ] }, - "schemas": "a87c890f-99a7-49e4-a2c1-b4fcf7433730", - "technicalDeploymentId": "dd8daef0-23f3-4411-9c80-96d6f513db98", - "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", - "projectVersion": "1.0.11" - }, - "ca7736c9-69e0-4a8e-9be2-a31a33ac0a83": { - "classDefinition": "com.sap.bpm.wfs.StartEvent", - "mappings": { - "out": { - "['custom']": { - "expression": { - "type": "ObjectExpression", - "properties": [] - }, - "type": "expression" + "type": "expression" + } + } + }, + "interfaces": { + "out": { + "schema": "pbWe9j4XrKA3mXEEeuVfK0L", + "definition": "#/definitions/out" + } + }, + "id": "startEvent", + "name": "StartEvent" + }, + "f68b8b77-663d-4106-8c40-f9548caa76e6": { + "classDefinition": "com.sap.bpm.wfs.EndEvent", + "id": "endEvent_1", + "name": "EndEvent" + }, + "29987b52-df5e-40ad-a6d1-32b0f983216d": { + "classDefinition": "com.sap.bpm.wfs.SequenceFlow", + "id": "pbtnevEtrats", + "name": "sequenceFlow", + "sourceRef": "ca7736c9-69e0-4a8e-9be2-a31a33ac0a83", + "targetRef": "f68b8b77-663d-4106-8c40-f9548caa76e6" + }, + "a87c890f-99a7-49e4-a2c1-b4fcf7433730": { + "classDefinition": "com.sap.bpm.wfs.Schemas", + "schemas": { + "pbPEEvyLH5Sdohv0J7O1kJN": { + "origin": "local", + "version": "1.0.0", + "schemaRef": "$.11cbfe86-2e73-4198-868a-d7a2115d82c1", + "content": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "ImportProcess_Complex_DataType", + "properties": { + "StringList": { + "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", + "items": { + "type": "object", + "properties": { + "SubString1": { + "type": "string", + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", + "title": "SubString1" }, - "['startEvent']": { - "expression": { - "type": "CallExpression", - "callee": { - "type": "Identifier", - "name": "SAP_SAFE_PARSE" - }, - "arguments": [ - { - "type": "Identifier", - "name": "output" - }, - { - "type": "ArrayExpression", - "elements": [] - } - ] - }, - "type": "expression" + "Substring2": { + "type": "string", + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", + "title": "Substring2" } - } - }, - "interfaces": { - "out": { - "schema": "pbWe9j4XrKA3mXEEeuVfK0L", - "definition": "#/definitions/out" - } - }, - "id": "startEvent", - "name": "StartEvent" - }, - "f68b8b77-663d-4106-8c40-f9548caa76e6": { - "classDefinition": "com.sap.bpm.wfs.EndEvent", - "id": "endEvent_1", - "name": "EndEvent" - }, - "29987b52-df5e-40ad-a6d1-32b0f983216d": { - "classDefinition": "com.sap.bpm.wfs.SequenceFlow", - "id": "pbtnevEtrats", - "name": "sequenceFlow", - "sourceRef": "ca7736c9-69e0-4a8e-9be2-a31a33ac0a83", - "targetRef": "f68b8b77-663d-4106-8c40-f9548caa76e6" - }, - "a87c890f-99a7-49e4-a2c1-b4fcf7433730": { - "classDefinition": "com.sap.bpm.wfs.Schemas", - "schemas": { - "pbPEEvyLH5Sdohv0J7O1kJN": { - "origin": "local", - "version": "1.0.0", - "schemaRef": "$.11cbfe86-2e73-4198-868a-d7a2115d82c1", - "content": { - "$schema": "http://json-schema.org/draft-07/schema#", + }, + "required": ["SubString1", "Substring2"] + }, + "type": "array", + "title": "StringList" + }, + "StringType": { + "type": "object", + "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", + "properties": { + "SubStringType": { + "type": "object", + "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", + "properties": { + "SubSubStringType": { "type": "object", - "title": "ImportProcess_Complex_DataType", + "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", "properties": { - "StringList": { - "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", - "items": { - "type": "object", - "properties": { - "SubString1": { - "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", - "title": "SubString1" - }, - "Substring2": { - "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", - "title": "Substring2" - } - }, - "required": [ - "SubString1", - "Substring2" - ] - }, - "type": "array", - "title": "StringList" - }, - "StringType": { - "type": "object", - "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", - "properties": { - "SubStringType": { - "type": "object", - "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", - "properties": { - "SubSubStringType": { - "type": "object", - "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", - "properties": { - "SubSubSubDate": { - "type": "string", - "format": "date", - "title": "SubSubSubDate" - }, - "SubSubSubPassword": { - "type": "string", - "password": true, - "title": "SubSubSubPassword" - }, - "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", - "title": "SubSubSubAny" - } - }, - "title": "SubSubStringType" - } - }, - "title": "SubStringType" - } - }, - "title": "StringType" - } + "SubSubSubDate": { + "type": "string", + "format": "date", + "title": "SubSubSubDate" + }, + "SubSubSubPassword": { + "type": "string", + "password": true, + "title": "SubSubSubPassword" + }, + "SubSubSubAny": { + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", + "title": "SubSubSubAny" + } }, - "required": [ - "StringList" - ], - "definitions": { - "password": { - "type": "string", - "password": true, - "title": "password" + "title": "SubSubStringType" + } + }, + "title": "SubStringType" + } + }, + "title": "StringType" + } + }, + "required": ["StringList"], + "definitions": { + "password": { + "type": "string", + "password": true, + "title": "password" + }, + "date": { + "type": "string", + "format": "date", + "title": "date" + } + }, + "version": 1, + "description": "" + } + }, + "pbWe9j4XrKA3mXEEeuVfK0L": { + "origin": "local", + "version": "1.0.0", + "schemaRef": "$.edb7b7fd-8a1c-4c51-a19f-b357aece8428", + "content": { + "definitions": { + "out": { + "title": "inputs", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "date": { + "type": "string", + "format": "date", + "title": "date" + }, + "dateTime": { + "type": "string", + "format": "date-time", + "title": "dateTime" + }, + "password": { + "type": "string", + "password": true, + "title": "password" + }, + "time": { + "type": "string", + "format": "time", + "title": "time" + }, + "documentFolder": { + "type": "string", + "format": "document-folder", + "title": "documentFolder" + } + }, + "properties": { + "complextype": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "complexType", + "properties": { + "StringList": { + "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", + "items": { + "type": "object", + "properties": { + "SubString1": { + "type": "string", + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", + "title": "SubString1" }, - "date": { - "type": "string", - "format": "date", - "title": "date" + "Substring2": { + "type": "string", + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", + "title": "Substring2" } + }, + "required": ["SubString1", "Substring2"] }, - "version": 1, - "description": "" - } - }, - "pbWe9j4XrKA3mXEEeuVfK0L": { - "origin": "local", - "version": "1.0.0", - "schemaRef": "$.edb7b7fd-8a1c-4c51-a19f-b357aece8428", - "content": { - "definitions": { - "out": { - "title": "inputs", + "type": "array", + "title": "StringList" + }, + "StringType": { + "type": "object", + "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", + "properties": { + "SubStringType": { + "type": "object", + "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", + "properties": { + "SubSubStringType": { "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "date": { - "type": "string", - "format": "date", - "title": "date" - }, - "dateTime": { - "type": "string", - "format": "date-time", - "title": "dateTime" - }, - "password": { - "type": "string", - "password": true, - "title": "password" - }, - "time": { - "type": "string", - "format": "time", - "title": "time" - }, - "documentFolder": { - "type": "string", - "format": "document-folder", - "title": "documentFolder" - } - }, + "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", "properties": { - "complextype": { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "title": "complexType", - "properties": { - "StringList": { - "uid": "4ed37465-be1c-44f1-b15b-94cba0cc04f4", - "items": { - "type": "object", - "properties": { - "SubString1": { - "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", - "title": "SubString1" - }, - "Substring2": { - "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", - "title": "Substring2" - } - }, - "required": [ - "SubString1", - "Substring2" - ] - }, - "type": "array", - "title": "StringList" - }, - "StringType": { - "type": "object", - "uid": "23add1b4-d9e0-4955-8b24-2196d210a43d", - "properties": { - "SubStringType": { - "type": "object", - "uid": "3ca99589-d0b0-462a-92bf-bc872d81a0b3", - "properties": { - "SubSubStringType": { - "type": "object", - "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", - "properties": { - "SubSubSubDate": { - "type": "string", - "format": "date", - "title": "SubSubSubDate" - }, - "SubSubSubPassword": { - "type": "string", - "password": true, - "title": "SubSubSubPassword" - }, - "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", - "title": "SubSubSubAny" - } - }, - "title": "SubSubStringType" - } - }, - "title": "SubStringType" - } - }, - "title": "StringType" - } - }, - "required": [ - "StringList" - ], - "definitions": { - "password": { - "type": "string", - "password": true, - "title": "password" - }, - "date": { - "type": "string", - "format": "date", - "title": "date" - } - }, - "version": 1, - "description": "", - "refName": "ImportProcess_Complex_DataType" - }, - "stringlist": { - "type": "string", - "title": "StringList", - "description": "" - }, - "complexlist": { - "type": "array", - "items": { - "type": "string" - }, - "title": "complexList", - "description": "" - } + "SubSubSubDate": { + "type": "string", + "format": "date", + "title": "SubSubSubDate" + }, + "SubSubSubPassword": { + "type": "string", + "password": true, + "title": "SubSubSubPassword" + }, + "SubSubSubAny": { + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", + "title": "SubSubSubAny" + } }, - "required": [ - "complextype", - "stringlist", - "complexlist" - ] + "title": "SubSubStringType" + } }, - "in": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "outputs", - "type": "object", - "properties": {} - } + "title": "SubStringType" + } }, - "title": "pbWe9j4XrKA3mXEEeuVfK0L" - } - } - } - }, - "b9935dbe-56de-4caa-9576-893d4dce1858": { - "classDefinition": "com.sap.bpm.wfs.Translation", - "bundles": { - "": { - "endEvent_1.name": "EndEvent", - "workflow.name": "ImportProcess_Complex_Inputs", - "workflow.subject": "ImportProcess_Complex_Inputs", - "pbtnevEtrats.name": "sequenceFlow", - "startEvent.name": "StartEvent" - } - } + "title": "StringType" + } + }, + "required": ["StringList"], + "definitions": { + "password": { + "type": "string", + "password": true, + "title": "password" + }, + "date": { + "type": "string", + "format": "date", + "title": "date" + } + }, + "version": 1, + "description": "", + "refName": "ImportProcess_Complex_DataType" + }, + "stringlist": { + "type": "string", + "title": "StringList", + "description": "" + }, + "complexlist": { + "type": "array", + "items": { + "type": "string" + }, + "title": "complexList", + "description": "" + } + }, + "required": ["complextype", "stringlist", "complexlist"] + }, + "in": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "outputs", + "type": "object", + "properties": {} + } + }, + "title": "pbWe9j4XrKA3mXEEeuVfK0L" + } + } + } + }, + "b9935dbe-56de-4caa-9576-893d4dce1858": { + "classDefinition": "com.sap.bpm.wfs.Translation", + "bundles": { + "": { + "endEvent_1.name": "EndEvent", + "workflow.name": "ImportProcess_Complex_Inputs", + "workflow.subject": "ImportProcess_Complex_Inputs", + "pbtnevEtrats.name": "sequenceFlow", + "startEvent.name": "StartEvent" } + } } -} \ No newline at end of file + } +} diff --git a/tests/hybrid/downloadedModels/ImportProcess_Simple_Inputs.json b/tests/hybrid/downloadedModels/ImportProcess_Simple_Inputs.json index 2080a22..ef8ad4c 100644 --- a/tests/hybrid/downloadedModels/ImportProcess_Simple_Inputs.json +++ b/tests/hybrid/downloadedModels/ImportProcess_Simple_Inputs.json @@ -1,194 +1,187 @@ { - "contents": { - "dffc57a3-8926-4f40-bd3c-06d0a6b044f2": { - "classDefinition": "com.sap.bpm.wfs.Model", - "id": "eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs", - "subject": "ImportProcess_Simple_Inputs", - "businessKey": "", - "customAttributes": [], - "propagateChildErrors": false, - "processIdentifier": "importProcess_Simple_Inputs", - "artifactId": "0fc541c5-5266-458e-801a-9fe7fbfc32e5", - "name": "ImportProcess_Simple_Inputs", - "events": { - "615d8e80-273a-4761-a68d-d30d18a9df86": { - "name": "StartEvent" - }, - "15e4b060-a1e9-494c-8963-9f0730b149f5": { - "name": "EndEvent" - } - }, - "sequenceFlows": { - "d03d0d14-638e-407c-a266-f33ad71cebda": { - "name": "sequenceFlow" - } - }, - "schemas": "1305aa5f-fa54-4b92-8602-459d311780e8", - "technicalDeploymentId": "dd8daef0-23f3-4411-9c80-96d6f513db98", - "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", - "projectVersion": "1.0.11" - }, + "contents": { + "dffc57a3-8926-4f40-bd3c-06d0a6b044f2": { + "classDefinition": "com.sap.bpm.wfs.Model", + "id": "eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs", + "subject": "ImportProcess_Simple_Inputs", + "businessKey": "", + "customAttributes": [], + "propagateChildErrors": false, + "processIdentifier": "importProcess_Simple_Inputs", + "artifactId": "0fc541c5-5266-458e-801a-9fe7fbfc32e5", + "name": "ImportProcess_Simple_Inputs", + "events": { "615d8e80-273a-4761-a68d-d30d18a9df86": { - "classDefinition": "com.sap.bpm.wfs.StartEvent", - "mappings": { - "out": { - "['custom']": { - "expression": { - "type": "ObjectExpression", - "properties": [] - }, - "type": "expression" - }, - "['startEvent']": { - "expression": { - "type": "CallExpression", - "callee": { - "type": "Identifier", - "name": "SAP_SAFE_PARSE" - }, - "arguments": [ - { - "type": "Identifier", - "name": "output" - }, - { - "type": "ArrayExpression", - "elements": [] - } - ] - }, - "type": "expression" - } - } - }, - "interfaces": { - "out": { - "schema": "pbsYfOXZVsx0Cr6VrMDMaOE", - "definition": "#/definitions/out" - } - }, - "id": "startEvent", - "name": "StartEvent" + "name": "StartEvent" }, "15e4b060-a1e9-494c-8963-9f0730b149f5": { - "classDefinition": "com.sap.bpm.wfs.EndEvent", - "id": "endEvent_1", - "name": "EndEvent" - }, + "name": "EndEvent" + } + }, + "sequenceFlows": { "d03d0d14-638e-407c-a266-f33ad71cebda": { - "classDefinition": "com.sap.bpm.wfs.SequenceFlow", - "id": "pbtnevEtrats", - "name": "sequenceFlow", - "sourceRef": "615d8e80-273a-4761-a68d-d30d18a9df86", - "targetRef": "15e4b060-a1e9-494c-8963-9f0730b149f5" - }, - "1305aa5f-fa54-4b92-8602-459d311780e8": { - "classDefinition": "com.sap.bpm.wfs.Schemas", - "schemas": { - "pbsYfOXZVsx0Cr6VrMDMaOE": { - "origin": "local", - "version": "1.0.0", - "schemaRef": "$.0fc541c5-5266-458e-801a-9fe7fbfc32e5", - "content": { - "definitions": { - "out": { - "title": "inputs", - "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "date": { - "type": "string", - "format": "date", - "title": "date" - }, - "dateTime": { - "type": "string", - "format": "date-time", - "title": "dateTime" - }, - "password": { - "type": "string", - "password": true, - "title": "password" - }, - "time": { - "type": "string", - "format": "time", - "title": "time" - }, - "documentFolder": { - "type": "string", - "format": "document-folder", - "title": "documentFolder" - } - }, - "properties": { - "string": { - "type": "string", - "title": "String", - "description": "" - }, - "number": { - "type": "number", - "title": "Number", - "description": "" - }, - "_boolean": { - "type": "boolean", - "title": "Boolean", - "description": "" - }, - "date": { - "type": "string", - "format": "date", - "title": "Date", - "description": "" - }, - "datetime": { - "type": "string", - "format": "date-time", - "title": "DateTime", - "description": "" - }, - "documentfolder": { - "type": "string", - "format": "document-folder", - "title": "DocumentFolder", - "description": "" - } - }, - "required": [ - "string", - "number", - "_boolean", - "date", - "datetime", - "documentfolder" - ] - }, - "in": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "outputs", - "type": "object", - "properties": {} - } - }, - "title": "pbsYfOXZVsx0Cr6VrMDMaOE" - } - } - } - }, - "59fad70f-9b00-4f7d-aa81-849c413aa848": { - "classDefinition": "com.sap.bpm.wfs.Translation", - "bundles": { - "": { - "endEvent_1.name": "EndEvent", - "workflow.name": "ImportProcess_Simple_Inputs", - "workflow.subject": "ImportProcess_Simple_Inputs", - "pbtnevEtrats.name": "sequenceFlow", - "startEvent.name": "StartEvent" + "name": "sequenceFlow" + } + }, + "schemas": "1305aa5f-fa54-4b92-8602-459d311780e8", + "technicalDeploymentId": "dd8daef0-23f3-4411-9c80-96d6f513db98", + "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", + "projectVersion": "1.0.11" + }, + "615d8e80-273a-4761-a68d-d30d18a9df86": { + "classDefinition": "com.sap.bpm.wfs.StartEvent", + "mappings": { + "out": { + "['custom']": { + "expression": { + "type": "ObjectExpression", + "properties": [] + }, + "type": "expression" + }, + "['startEvent']": { + "expression": { + "type": "CallExpression", + "callee": { + "type": "Identifier", + "name": "SAP_SAFE_PARSE" + }, + "arguments": [ + { + "type": "Identifier", + "name": "output" + }, + { + "type": "ArrayExpression", + "elements": [] } - } + ] + }, + "type": "expression" + } + } + }, + "interfaces": { + "out": { + "schema": "pbsYfOXZVsx0Cr6VrMDMaOE", + "definition": "#/definitions/out" + } + }, + "id": "startEvent", + "name": "StartEvent" + }, + "15e4b060-a1e9-494c-8963-9f0730b149f5": { + "classDefinition": "com.sap.bpm.wfs.EndEvent", + "id": "endEvent_1", + "name": "EndEvent" + }, + "d03d0d14-638e-407c-a266-f33ad71cebda": { + "classDefinition": "com.sap.bpm.wfs.SequenceFlow", + "id": "pbtnevEtrats", + "name": "sequenceFlow", + "sourceRef": "615d8e80-273a-4761-a68d-d30d18a9df86", + "targetRef": "15e4b060-a1e9-494c-8963-9f0730b149f5" + }, + "1305aa5f-fa54-4b92-8602-459d311780e8": { + "classDefinition": "com.sap.bpm.wfs.Schemas", + "schemas": { + "pbsYfOXZVsx0Cr6VrMDMaOE": { + "origin": "local", + "version": "1.0.0", + "schemaRef": "$.0fc541c5-5266-458e-801a-9fe7fbfc32e5", + "content": { + "definitions": { + "out": { + "title": "inputs", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "date": { + "type": "string", + "format": "date", + "title": "date" + }, + "dateTime": { + "type": "string", + "format": "date-time", + "title": "dateTime" + }, + "password": { + "type": "string", + "password": true, + "title": "password" + }, + "time": { + "type": "string", + "format": "time", + "title": "time" + }, + "documentFolder": { + "type": "string", + "format": "document-folder", + "title": "documentFolder" + } + }, + "properties": { + "string": { + "type": "string", + "title": "String", + "description": "" + }, + "number": { + "type": "number", + "title": "Number", + "description": "" + }, + "_boolean": { + "type": "boolean", + "title": "Boolean", + "description": "" + }, + "date": { + "type": "string", + "format": "date", + "title": "Date", + "description": "" + }, + "datetime": { + "type": "string", + "format": "date-time", + "title": "DateTime", + "description": "" + }, + "documentfolder": { + "type": "string", + "format": "document-folder", + "title": "DocumentFolder", + "description": "" + } + }, + "required": ["string", "number", "_boolean", "date", "datetime", "documentfolder"] + }, + "in": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "outputs", + "type": "object", + "properties": {} + } + }, + "title": "pbsYfOXZVsx0Cr6VrMDMaOE" + } + } + } + }, + "59fad70f-9b00-4f7d-aa81-849c413aa848": { + "classDefinition": "com.sap.bpm.wfs.Translation", + "bundles": { + "": { + "endEvent_1.name": "EndEvent", + "workflow.name": "ImportProcess_Simple_Inputs", + "workflow.subject": "ImportProcess_Simple_Inputs", + "pbtnevEtrats.name": "sequenceFlow", + "startEvent.name": "StartEvent" } + } } -} \ No newline at end of file + } +} From 9a4dfa2c5c62f9b1c045edd654b13c8d3e0e4c54 Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Wed, 1 Apr 2026 16:47:50 +0200 Subject: [PATCH 6/6] fix lint --- lib/processImportRegistration.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/processImportRegistration.ts b/lib/processImportRegistration.ts index 320dfb4..cc4ba90 100644 --- a/lib/processImportRegistration.ts +++ b/lib/processImportRegistration.ts @@ -1,6 +1,12 @@ import cds from '@sap/cds'; import { importProcess } from './processImport'; +interface ImportOptions { + name?: string; + file?: string; + saveProcessHeader?: boolean; +} + export function registerProcessImport() { // @ts-expect-error: import does not exist on cds type cds.import ??= {}; @@ -17,7 +23,7 @@ export function registerProcessImport() { // @ts-expect-error: process does not exist on cds.import type cds.import.from ??= {}; // @ts-expect-error: from does not exist on cds.import type - cds.import.from.process = (jsonFile: string, options: any = {}) => { + cds.import.from.process = (jsonFile: string, options: ImportOptions = {}) => { // When called via CLI, always save the ProcessHeader JSON options.saveProcessHeader = true; return importProcess(jsonFile, options);