Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
6b2d53b
Adding minor comments
lippisch Apr 17, 2025
2145ce2
Profile is now loaded as part of the context
lippisch Apr 17, 2025
97a056d
Logger now writes logs into the config directory
lippisch Apr 21, 2025
1b0bbde
fix for --debug
lippisch Apr 21, 2025
101a96c
Migrated list command as an example implementation
lippisch Apr 22, 2025
0ab31e0
modules can extend any root level command
lippisch Apr 23, 2025
7c23b97
entry point file can be of 3 variants now, supporting foldername as p…
lippisch Apr 24, 2025
9fba40f
Merge remote-tracking branch 'origin/master' into fli-contribution-mo…
lippisch Apr 29, 2025
5ab1e34
Modules are now structured by owning team, not command structure
lippisch Apr 29, 2025
5f107c1
Base API with some refactored error handling
lippisch May 2, 2025
601623d
minor tweaks to align with documentation
lippisch May 2, 2025
0b60d45
TA-3749: Define foundation of the CLI
ZgjimHaziri May 13, 2025
38f86e7
TA-3749: Revert unnecessary change
ZgjimHaziri May 13, 2025
6f4f515
TA-3749: Revert unnecessary change
ZgjimHaziri May 13, 2025
128fcab
TA-3749: Revert unnecessary change
ZgjimHaziri May 13, 2025
b624e74
TA-3749: Revert unnecessary change
ZgjimHaziri May 13, 2025
f5e25b8
TA-3749: yarn-lock update
ZgjimHaziri May 13, 2025
5b4a283
TA-3749: Rever package.json update
ZgjimHaziri May 13, 2025
7f6cc1b
TA-3749: Move code to top level
ZgjimHaziri May 14, 2025
0726a23
Merge base branch
ZgjimHaziri May 14, 2025
4351014
TA-3749: Trigger build
ZgjimHaziri May 14, 2025
843f7b5
TA-3749: Comment test run
ZgjimHaziri May 14, 2025
d88b9a4
TA-3750: Setup command modules
ZgjimHaziri May 14, 2025
e3cb10f
TA-3750: Merge base branch insto TA-3750-setup-modules
ZgjimHaziri May 16, 2025
375f56c
TA-3750: Rename Pacman module to Config
ZgjimHaziri May 16, 2025
ba51325
TA-3750: Remove non-Astro codeowners
ZgjimHaziri May 16, 2025
d2ff658
TA-3750: Update Pacman module description
ZgjimHaziri May 16, 2025
633f4fd
TA-3750: Fix merge conflicts
ZgjimHaziri May 16, 2025
715fe90
TA-3750: Rename config module
ZgjimHaziri May 16, 2025
14be28d
TA-3750: Rename config module in codeowners file
ZgjimHaziri May 16, 2025
f9033a3
TA-3771: Migrate Data Pipeline commands
ZgjimHaziri May 19, 2025
b11c573
Merge branch 'content-cli-v2-refactoring' into TA-3771-migrate-data-p…
ZgjimHaziri May 19, 2025
3a2f976
TA-3767: Migrate Studio commands
ZgjimHaziri May 20, 2025
4165bd8
TA-3767: Add Navi as codeowners in Studio commands
ZgjimHaziri May 20, 2025
7b9c231
TA-3769: Migrate Analysis commands
ZgjimHaziri May 20, 2025
7b8e58a
TA-3769: Move Pacman related commands out of Studio module
ZgjimHaziri May 20, 2025
bcaa9bf
TA-3769: Remove Pacman interfaces from this PR
ZgjimHaziri May 20, 2025
b2a35bb
TA-3770: Migrate CPM4 commands
ZgjimHaziri May 20, 2025
4c022ce
TA-3770: Add individual codeowners
ZgjimHaziri May 21, 2025
26260f3
Merge branch 'TA-3771-migrate-data-pipeline' into TA-3767-migrate-stu…
ZgjimHaziri May 21, 2025
7ad84ae
Merge branch 'TA-3769-migrate-studio-commands' of github.com:celonis/…
ZgjimHaziri May 21, 2025
f1c6527
Merge branch 'TA-3769-migrate-existing-analysis-commands' of github.c…
ZgjimHaziri May 21, 2025
4a94347
TA-3770: Add cpm4 codeowners
ZgjimHaziri May 21, 2025
367a73f
TA-3769: Add process-analytics team as owners
ZgjimHaziri May 21, 2025
edbed17
TA-3770: Merge base branch
ZgjimHaziri May 21, 2025
cc86dff
TA-3768: Setup test config
ZgjimHaziri May 22, 2025
ed95597
TA-3768: Renames and uncommenting
ZgjimHaziri May 22, 2025
1b38ca9
TA-3768: Comment out `yarn test` in build.yml
ZgjimHaziri May 22, 2025
1b4221c
TA-3768: Rename variable used in logging transport
ZgjimHaziri May 22, 2025
1099500
Merge branch 'content-cli-v2-refactoring' of github.com:celonis/conte…
ZgjimHaziri May 22, 2025
5771e93
TA-3768: Use test token
ZgjimHaziri May 22, 2025
c69d030
TA-3768: Make fs import consistent across projects
ZgjimHaziri May 22, 2025
acebee5
TA-3768: Migrate Action Flows commands (#206)
ZgjimHaziri May 23, 2025
4950af9
TA-3751: Migrate Pacman commands (#207)
ZgjimHaziri May 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
#/src/core @celonis/astro
#/src/commands/configuration-management/ @celonis/astro
#/src/commands/profile/ @celonis/astro
#/src/commands/action-flows/ @celonis/process-automation
/src/commands/action-flows/ @celonis/process-automation
/tests/commands/action-flows/ @celonis/process-automation
/src/commands/analysis/ @celonis/process-analytics
/src/commands/cpm4/ @celonis/cpm4
/src/commands/data-pipeline/ @Dusan-r @IvanGandacov @EktaCelonis @gorasoCelonis
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ jobs:
run: yarn install
- name: Yarn Build
run: yarn build
# - name: Yarn Test
# run: yarn test
- name: Yarn Test
run: yarn test
46 changes: 23 additions & 23 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
// import type { Config } from "@jest/types"
// const config: Config.InitialOptions = {
// verbose: true,
// transform: {
// "^.+\\.tsx?$": "ts-jest",
// },
// testMatch: ["<rootDir>/tests/**/*.spec.ts"],
// moduleNameMapper: {
// "^\\./../package.json$": "<rootDir>/tests/mocks/package.json",
// },
// setupFilesAfterEnv: [
// "<rootDir>/tests/jest.setup.ts",
// ],
// globals: {
// "ts-jest": {
// tsconfig: {
// sourceMap: true
// }
// }
// }
// }
//
// export default config
import type { Config } from "@jest/types"
const config: Config.InitialOptions = {
verbose: true,
transform: {
"^.+\\.tsx?$": "ts-jest",
},
testMatch: ["<rootDir>/tests/**/*.spec.ts"],
moduleNameMapper: {
"^\\./../package.json$": "<rootDir>/tests/mocks/package.json",
},
setupFilesAfterEnv: [
"<rootDir>/tests/jest.setup.ts",
],
globals: {
"ts-jest": {
tsconfig: {
sourceMap: true
}
}
}
}

export default config
35 changes: 35 additions & 0 deletions src/commands/action-flows/action-flow/action-flow-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as FormData from "form-data";
import { HttpClient } from "../../../core/http/http-client";
import { Context } from "../../../core/command/cli-context";
import { FatalError } from "../../../core/utils/logger";

export class ActionFlowApi {

private httpClient: HttpClient;

constructor(context: Context) {
this.httpClient = context.httpClient;
}

public async exportRawAssets(packageId: string): Promise<Buffer> {
return this.httpClient.getFile(`/ems-automation/api/root/${packageId}/export/assets`).catch(e => {
throw new FatalError(`Problem getting Action Flow assets: ${e}`);
});
}

public async analyzeAssets(packageId: string): Promise<any> {
return this.httpClient.get(`/ems-automation/api/root/${packageId}/export/assets/analyze`).catch(e => {
throw new FatalError(`Problem analyzing Action Flow assets: ${e}`);
});
}

public async importAssets(packageId: string, data: FormData, dryRun: boolean): Promise<any> {
const params = {
dryRun: dryRun,
};

return this.httpClient.postFile(`/ems-automation/api/root/${packageId}/import/assets`, data, params).catch(e => {
throw new FatalError(`Problem importing Action Flow assets: ${e}`);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Context } from "../../../core/command/cli-context";
import { ActionFlowService } from "./action-flow.service";

export class ActionFlowCommandService {

private actionFlowService: ActionFlowService;

constructor(context: Context) {
this.actionFlowService = new ActionFlowService(context);
}

public async exportActionFlows(packageId: string, metadataFile: string): Promise<void> {
await this.actionFlowService.exportActionFlows(packageId, metadataFile);
}

public async analyzeActionFlows(packageId: string, outputToJsonFile: boolean): Promise<void> {
await this.actionFlowService.analyzeActionFlows(packageId, outputToJsonFile);
}

public async importActionFlows(packageId: string, filePath: string, dryRun: boolean, outputToJsonFile: boolean): Promise<void> {
await this.actionFlowService.importActionFlows(packageId, filePath, dryRun, outputToJsonFile);
}
}
79 changes: 79 additions & 0 deletions src/commands/action-flows/action-flow/action-flow.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { v4 as uuidv4 } from "uuid";
import * as AdmZip from "adm-zip";
import * as FormData from "form-data";
import * as fs from "fs";
import { Context } from "../../../core/command/cli-context";
import { ActionFlowApi } from "./action-flow-api";
import { fileService, FileService } from "../../../core/utils/file-service";
import { logger } from "../../../core/utils/logger";

export class ActionFlowService {
public static readonly METADATA_FILE_NAME = "metadata.json";

private actionFlowApi: ActionFlowApi;

constructor(context: Context) {
this.actionFlowApi = new ActionFlowApi(context);
}

public async exportActionFlows(packageId: string, metadataFilePath: string): Promise<void> {
const exportedActionFlowsData = await this.actionFlowApi.exportRawAssets(packageId);
const tmpZip: AdmZip = new AdmZip(exportedActionFlowsData);

const zip = new AdmZip();
tmpZip.getEntries().forEach(entry => {
zip.addFile(entry.entryName, entry.getData());
});

if (metadataFilePath) {
this.attachMetadataFile(metadataFilePath, zip);
}

const fileName = "action-flows_export_" + uuidv4() + ".zip";
zip.writeZip(fileName);
logger.info(FileService.fileDownloadedMessage + fileName);
}

public async analyzeActionFlows(packageId: string, outputToJsonFile: boolean): Promise<void> {
const actionFlowsMetadata = await this.actionFlowApi.analyzeAssets(packageId);
const actionFlowsMetadataString = JSON.stringify(actionFlowsMetadata, null, 4);

if (outputToJsonFile) {
const metadataFileName = "action-flows_metadata_" + uuidv4() + ".json";
fileService.writeToFileWithGivenName(actionFlowsMetadataString, metadataFileName);
logger.info(FileService.fileDownloadedMessage + metadataFileName);
} else {
logger.info("Action flows analyze metadata: \n" + actionFlowsMetadataString);
}
}

public async importActionFlows(packageId: string, filePath: string, dryRun: boolean, outputToJsonFile: boolean): Promise<void> {
const actionFlowsZip = this.createBodyForImport(filePath);
const eventLog = await this.actionFlowApi.importAssets(packageId, actionFlowsZip, dryRun);
const eventLogString = JSON.stringify(eventLog, null, 4);

if (outputToJsonFile) {
const eventLogFileName = "action-flows_import_event_log_" + uuidv4() + ".json";
fileService.writeToFileWithGivenName(eventLogString, eventLogFileName);
logger.info(FileService.fileDownloadedMessage + eventLogFileName);
} else {
logger.info("Action flows import event log: \n" + eventLogString);
}
}

private createBodyForImport(fileName: string): FormData {
fileName = fileName + (fileName.endsWith(".zip") ? "" : ".zip");

const formData = new FormData();
formData.append("file", fs.createReadStream(fileName, { encoding: null }), { filename: fileName });

return formData;
}

private attachMetadataFile(fileName: string, zip: AdmZip): void {
fileName = fileName + (fileName.endsWith(".json") ? "" : ".json");
const metadata = fileService.readFile(fileName);

zip.addFile(ActionFlowService.METADATA_FILE_NAME, Buffer.from(metadata));
}
}
63 changes: 63 additions & 0 deletions src/commands/action-flows/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,75 @@

import { Configurator, IModule } from "../../core/command/module-handler";
import { Context } from "../../core/command/cli-context";
import { Command, OptionValues } from "commander";
import { ActionFlowCommandService } from "./action-flow/action-flow-command.service";
import { SkillCommandService } from "./skill/skill-command.service";

class Module extends IModule {

register(context: Context, configurator: Configurator) {
const analyzeCommand = configurator.command("analyze");
analyzeCommand.command("action-flows")
.description("Analyze Action Flows dependencies for a certain package")
.option("-p, --profile <profile>", "Profile which you want to use to analyze Action Flows")
.requiredOption("--packageId <packageId>", "ID of the package from which you want to export Action Flows")
.option("-o, --outputToJsonFile", "Output the analyze result in a JSON file")
.action(this.analyzeActionFlows);

const exportCommand = configurator.command("export");
exportCommand.command("action-flows")
.description("Command to export all Action Flows in a package with their objects and dependencies")
.option("-p, --profile <profile>", "Profile which you want to use to export Action Flows")
.requiredOption("--packageId <packageId>", "ID of the package from which you want to export Action Flows")
.option("-f, --file <file>", "Action flows metadata file (relative path)")
.action(this.exportActionFlows);

const importCommand = configurator.command("import");
importCommand.command("action-flows")
.description("Command to import all Action Flows in a package with their objects and dependencies")
.option("-p, --profile <profile>", "Profile which you want to use to import Action Flows")
.requiredOption("--packageId <packageId>", "ID of the package to which you want to export Action Flows")
.requiredOption("-f, --file <file>", "Exported Action Flows file (relative path)")
.requiredOption("-d, --dryRun <dryRun>", "Execute the import on dry run mode")
.option("-o, --outputToJsonFile", "Output the import result in a JSON file")
.action(this.importActionFlows);

const pullCommand = configurator.command("pull");
pullCommand.command("skill")
.description("Command to pull a skill")
.option("-p, --profile <profile>", "Profile which you want to use to pull the skill")
.requiredOption("--projectId <projectId>", "Id of the project you want to pull")
.requiredOption("--skillId <skillId>", "Id of the skill you want to pull")
.action(this.pullSkill);

const pushCommand = configurator.command("push");
pushCommand.command("skill")
.description("Command to push a skill to a project")
.option("-p, --profile <profile>", "Profile which you want to use to push the skill")
.requiredOption("--projectId <projectId>", "Id of the project you want to push")
.requiredOption("-f, --file <file>", "The file you want to push")
.action(this.pushSkill);
}

private async analyzeActionFlows(context: Context, command: Command, options: OptionValues): Promise<void> {
await new ActionFlowCommandService(context).analyzeActionFlows(options.packageId, options.outputToJsonFile);
}

private async exportActionFlows(context: Context, command: Command, options: OptionValues): Promise<void> {
await new ActionFlowCommandService(context).exportActionFlows(options.packageId, options.file);
}

private async importActionFlows(context: Context, command: Command, options: OptionValues): Promise<void> {
await new ActionFlowCommandService(context).importActionFlows(options.packageId, options.file, options.dryRun, options.outputToJsonFile);
}

private async pullSkill(context: Context, command: Command, options: OptionValues): Promise<void> {
await new SkillCommandService(context).pullSkill(options.profile, options.projectId, options.skillId);
}

private async pushSkill(context: Context, command: Command, options: OptionValues): Promise<void> {
await new SkillCommandService(context).pushSkill(options.profile, options.projectId, options.file);
}
}

export = Module;
20 changes: 20 additions & 0 deletions src/commands/action-flows/skill/skill-command.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ContentService } from "../../../core/http/http-shared/content.service";
import { Context } from "../../../core/command/cli-context";
import { SkillManagerFactory } from "./skill.manager-factory";

export class SkillCommandService {
private contentService = new ContentService();
private skillManagerFactory: SkillManagerFactory;

constructor(context: Context) {
this.skillManagerFactory = new SkillManagerFactory(context);
}

public async pullSkill(profile: string, projectId: string, skillId: string): Promise<void> {
await this.contentService.pull(this.skillManagerFactory.createManager(projectId, skillId, null));
}

public async pushSkill(profile: string, projectId: string, filename: string): Promise<void> {
await this.contentService.push(this.skillManagerFactory.createManager(projectId, null, filename));
}
}
32 changes: 32 additions & 0 deletions src/commands/action-flows/skill/skill.manager-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as fs from "fs";
import * as path from "path";
import { Stream } from "stream";
import { Context } from "../../../core/command/cli-context";
import { FatalError, logger } from "../../../core/utils/logger";
import { SkillManager } from "./skill.manager";

export class SkillManagerFactory {

private readonly context: Context;

constructor(context: Context) {
this.context = context;
}

public createManager(projectId: string, skillId: string, filename: string): SkillManager {
const skillManager = new SkillManager(this.context);
skillManager.skillId = skillId;
skillManager.projectId = projectId;
if (filename !== null) {
skillManager.content = this.readFile(filename);
}
return skillManager;
}

private readFile(filename: string): Stream {
if (!fs.existsSync(path.resolve(process.cwd(), filename))) {
logger.error(new FatalError("The provided file does not exit"));
}
return fs.createReadStream(path.resolve(process.cwd(), filename), { encoding: "binary" });
}
}
59 changes: 59 additions & 0 deletions src/commands/action-flows/skill/skill.manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as FormData from "form-data";
import { Context } from "../../../core/command/cli-context";
import { BaseManager } from "../../../core/http/http-shared/base.manager";
import { ManagerConfig } from "../../../core/http/http-shared/manager-config.interface";

export class SkillManager extends BaseManager {
private static BASE_URL = "/action-engine/api/projects";
private _skillId: string;
private _projectId: string;
private _content: any;

constructor(context: Context) {
super(context);
}

public get content(): any {
return this._content;
}

public set content(value: any) {
this._content = value;
}
public get skillId(): string {
return this._skillId;
}

public set skillId(value: string) {
this._skillId = value;
}

public get projectId(): string {
return this._projectId;
}

public set projectId(value: string) {
this._projectId = value;
}

public getConfig(): ManagerConfig {
return {
pushUrl: `${SkillManager.BASE_URL}/${this.projectId}/skills/import-file`,
pullUrl: `${SkillManager.BASE_URL}/${this.projectId}/skills/${this.skillId}/export`,
exportFileName: "skill_" + this.skillId + ".json",
onPushSuccessMessage: (skill: any): string => {
return "Skill was pushed successfully. New ID: " + skill.id;
},
};
}

public getBody(): any {
const formData = new FormData();
formData.append("file", this.content);
return formData;
}

protected getSerializedFileContent(data: any): string {
return JSON.stringify(data);
}
}
Loading