import RequestOptions = Cypress.RequestOptions; // Load type definitions that come with Cypress module /// declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface Chainable { runProvisioningScript(params: RunProvisioningScriptParams): Chainable; } } } export interface RunProvisioningScriptParams { script: FormFile | StringDictionary[]; files?: FormFile[]; /** Optional, defaults to serverDefaults */ jahiaServer?: JahiaServer; options?: Cypress.Loggable; // Optional, defaults to { log: true } requestOptions?: Partial; // Optional, defaults to {} } export type StringDictionary = { [key: string]: string } export type FormFile = { fileName?: string, fileContent?: string, type?: string, encoding?: Cypress.Encodings replacements?: StringDictionary } export type JahiaServer = { url: string; username: string; password: string } function processContent(formFile: FormFile) { let content = formFile.fileContent; if (formFile.replacements) { Object.keys(formFile.replacements).forEach(k => { content = content.replaceAll(k, formFile.replacements[k]); }); } formFile.fileContent = content; // If the file has an encoding, create a Blob with the new Blob constructor. It handle correctly the encoding // for the groovy scripts // Did not change for all files because jar files can be added as well, which are binary files // In that case the function binaryStringToBlob will be used if (formFile.encoding) { return new Blob([content], {type: formFile.type}); } // Default to binary string to blob conversion return Cypress.Blob.binaryStringToBlob(content, formFile.type); } function append(formFile: FormFile, formData: FormData, key: string) { if (formFile.fileContent) { formData.append(key, processContent(formFile), formFile.fileName); } else if (formFile.fileName) { cy.fixture(formFile.fileName, (formFile.encoding ? formFile.encoding : 'binary')).then(content => { if (typeof content === 'object') { formFile.fileContent = JSON.stringify(content); } else { formFile.fileContent = content; } formData.append(key, processContent(formFile), formFile.fileName); }); } } const serverDefaults: JahiaServer = { url: Cypress.config().baseUrl, username: 'root', password: Cypress.env('SUPER_USER_PASSWORD') }; function isFormFile(script: FormFile | StringDictionary[]): script is FormFile { return Boolean((script as FormFile)?.fileContent || (script as FormFile)?.fileName); } function getScriptSummary(script: FormFile | StringDictionary[]): string { if (isFormFile(script)) { if (script.fileName) { return script.fileName; } if (script.fileContent) { // Parse first operation and its value from YAML list: "- operationName: value" const yamlMatch = script.fileContent.match(/^\s*-\s+(\w+)\s*:\s*"?([^"\n]+)"?/m); if (yamlMatch) { return `${yamlMatch[1]}: ${yamlMatch[2].trim()}`; } // Parse first operation name from JSON array: [{"operationName": ...}] try { const parsed = JSON.parse(script.fileContent); if (Array.isArray(parsed) && parsed.length > 0) { const ops = parsed.map((op: Record) => Object.keys(op)[0] ?? 'unknown'); return ops.length === 1 ? ops[0] : `[${ops.join(', ')}]`; } } catch { // Not valid JSON, fall through } } return 'inline script'; } if (!script || script.length === 0) { return 'empty script'; } const ops = script.map(op => Object.keys(op)[0] ?? 'unknown'); return ops.length === 1 ? ops[0] : `[${ops.join(', ')}]`; } // eslint-disable-next-line @typescript-eslint/no-explicit-any export const runProvisioningScript = (paramsOrScript: RunProvisioningScriptParams | FormFile | StringDictionary[], ...rest: any[]): void => { // Backward-compatible: support old positional signature // runProvisioningScript(script, files, jahiaServer, options, timeout) let script: FormFile | StringDictionary[]; let files: FormFile[]; let jahiaServer: JahiaServer; let options: Cypress.Loggable; let requestOptions: Partial; const isLegacyCall = Array.isArray(paramsOrScript) || (paramsOrScript as FormFile).fileContent !== undefined || (paramsOrScript as FormFile).fileName !== undefined; if (isLegacyCall) { script = paramsOrScript as FormFile | StringDictionary[]; files = rest[0]; jahiaServer = rest[1] ?? serverDefaults; options = rest[2] ?? {log: true}; requestOptions = {}; } else { const params = paramsOrScript as RunProvisioningScriptParams; script = params.script; files = params.files; jahiaServer = params.jahiaServer ?? serverDefaults; options = params.options ?? {log: true}; requestOptions = params.requestOptions ?? {}; } const formData = new FormData(); if (isFormFile(script)) { append(script, formData, 'script'); } else { append({ fileContent: JSON.stringify(script), type: 'application/json' }, formData, 'script'); } if (files) { files.forEach(f => { append(f, formData, 'file'); }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any let response: Cypress.Response; // eslint-disable-next-line @typescript-eslint/no-explicit-any let result: any; let logger: Cypress.Log; const scriptSummary = getScriptSummary(script); const replacementsFromFiles = files ?.filter(f => f.replacements && Object.keys(f.replacements).length > 0) .map(f => `${f.fileName}: ${JSON.stringify(f.replacements)}`); if (options.log) { logger = Cypress.log({ autoEnd: false, name: 'runProvisioningScript', displayName: 'provScript', message: `${scriptSummary} @ ${jahiaServer.url}`, consoleProps: () => { return { Script: script, Operations: isFormFile(script) ? undefined : script?.map(op => `${Object.keys(op)[0]}: ${Object.values(op)[0]}`), Files: files?.map(f => f.fileName ?? 'inline file') ?? [], Replacements: replacementsFromFiles?.length > 0 ? replacementsFromFiles : undefined, Server: jahiaServer.url, 'HTTP Status': response ? `${response.status} ${response.statusText}` : 'pending', Duration: response ? `${response.duration}ms` : 'pending', Result: result, Response: response }; } }); } const request: Partial = { url: `${jahiaServer.url}/modules/api/provisioning`, method: 'POST', auth: { user: jahiaServer.username, pass: jahiaServer.password, sendImmediately: true }, body: formData, log: false, ...requestOptions }; cy.request(request).then(res => { response = res; // If the response status is 200, decode the response, otherwise return the response as is if (res.status === 200) { try { const decoder = new TextDecoder(); result = JSON.parse(decoder.decode(response.body)); } catch (e) { result = e; } } else { result = res; } logger?.end(); if (logger) { const hasFailed = res.status !== 200 || (Array.isArray(result) && result.some((r: any) => typeof r === 'string' && r.includes('.failed'))); // eslint-disable-line @typescript-eslint/no-explicit-any const prefix = hasFailed ? '❌ ' : '✅ '; logger.set('message', `${prefix}${scriptSummary} @ ${jahiaServer.url}`); } return result; }); };