@celonis/content-cli 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/README.md +68 -18
  2. package/commands/configuration-management/api/package-version-api.js +9 -0
  3. package/commands/configuration-management/batch-import-export.service.js +10 -2
  4. package/commands/configuration-management/interfaces/package-version.interfaces.js +6 -0
  5. package/commands/configuration-management/module.js +22 -0
  6. package/commands/configuration-management/package-version-command.service.js +5 -0
  7. package/commands/configuration-management/package-version.service.js +32 -0
  8. package/commands/profile/module.js +9 -0
  9. package/commands/profile/profile-command.service.js +18 -1
  10. package/core/profile/profile.service.js +45 -11
  11. package/core/profile/secret-storage.service.js +115 -0
  12. package/lib/commands/configuration-management/api/package-version-api.d.ts +2 -1
  13. package/lib/commands/configuration-management/batch-import-export.service.d.ts +1 -0
  14. package/lib/commands/configuration-management/interfaces/package-version.interfaces.d.ts +21 -0
  15. package/lib/commands/configuration-management/module.d.ts +1 -0
  16. package/lib/commands/configuration-management/package-version-command.service.d.ts +1 -0
  17. package/lib/commands/configuration-management/package-version.service.d.ts +2 -0
  18. package/lib/commands/profile/module.d.ts +1 -0
  19. package/lib/commands/profile/profile-command.service.d.ts +1 -0
  20. package/lib/core/profile/profile.interface.d.ts +7 -4
  21. package/lib/core/profile/profile.service.d.ts +2 -2
  22. package/lib/core/profile/secret-storage.service.d.ts +7 -0
  23. package/package.json +4 -2
package/README.md CHANGED
@@ -9,10 +9,11 @@ teams and execute commands in a profile's context.
9
9
 
10
10
  1. [Getting Started](#getting-started)
11
11
  2. [About the Project](#about-the-project)
12
- 3. [Building the Project](#building-the-project)
13
- 4. [Release Process](#release-process)
14
- 5. [Contributing](#contributing)
15
- 6. [License](#license)
12
+ 3. [Building the Project](#building-and-using-the-project-locally)
13
+ 4. [Troubleshooting](#troubleshooting)
14
+ 5. [Release Process](#release-process)
15
+ 6. [Contributing](#contributing)
16
+ 7. [License](#license)
16
17
 
17
18
  ## Getting Started
18
19
 
@@ -71,8 +72,8 @@ Example, if you want to push a package from zipped file "interesting-package.zip
71
72
  content-cli push package -p my-dev-profile --spaceKey my-cool-space -f interesting-package.zip
72
73
  ```
73
74
 
74
- A more comprehensive list of Content CLI capabilities can be found on the following
75
- [documentation](https://github.com/celonis/content-cli/blob/master/DOCUMENTATION.md).
75
+ A comprehensive list of Content CLI capabilities can be found in the
76
+ [documentation](https://github.com/celonis/content-cli/tree/main/docs).
76
77
 
77
78
  You can still explore the full capabilities of Content CLI and the list of options for the different commands
78
79
  by using the `-h` option in your command.
@@ -85,16 +86,65 @@ content-cli pull package -h
85
86
  ## Building and Using the Project locally
86
87
 
87
88
  This tool is tightly connected with the Celonis Platform and all capabilities require to have access to a Celonis Platform Team.
88
- After cloning the project, the next step is to install the project dependencies. We use `yarn` as our package manager,
89
- so running `yarn install` on the project root folder should install all the necessary dependencies. After installing
90
- the project dependencies, you can run `yarn build` to build the project artifact. To use the built artifact, you can
91
- run `node content-cli.js` in the generated `dist` folder.
92
89
 
93
- If we want to use a specific local build of the tool globally, we can do this by:
94
- - Move to the root directory of the project.
95
- - Execute `cd dist`, to move to the `dist` directory.
96
- - Execute the `npm link` command.
97
- This will create a symbolic link in the global `node_modules` directory, allowing you to run the CLI from anywhere on your machine.
90
+ ### Prerequisites
91
+
92
+ - **Node.js** `>=18.20.5` (LTS recommended). Check with `node -v`.
93
+ - **yarn** package manager. Install with `npm install -g yarn` if not already available.
94
+
95
+ ### Build steps
96
+
97
+ ```bash
98
+ git clone https://github.com/celonis/content-cli.git
99
+ cd content-cli
100
+ yarn install
101
+ yarn build
102
+ ```
103
+
104
+ The build compiles TypeScript into the `dist/` folder and copies `package.json` there.
105
+
106
+ ### Running the built CLI
107
+
108
+ You can run the built artifact directly from the `dist` folder:
109
+
110
+ ```bash
111
+ node dist/content-cli.js -h
112
+ ```
113
+
114
+ ### Installing the local build globally
115
+
116
+ To use your local build as the global `content-cli` command:
117
+
118
+ ```bash
119
+ cd dist
120
+ npm link
121
+ ```
122
+
123
+ This creates a symbolic link in the global `node_modules` directory, allowing you to run `content-cli` from anywhere on your machine. To unlink later, run `npm unlink -g @celonis/content-cli`.
124
+
125
+ ## Troubleshooting
126
+
127
+ ### `Error: Cannot find module '...'`
128
+
129
+ If you see a `Cannot find module` error when running `content-cli`, dependencies may not be installed correctly:
130
+
131
+ - **Global install:** Try updating to the latest version: `npm install -g @celonis/content-cli@latest`.
132
+ - **Local development:** Run `yarn install` from the project root before building.
133
+
134
+ ### `content-cli: command not found`
135
+
136
+ - Ensure Node.js is installed and on your `PATH` (`node -v`).
137
+ - If installed via `npm install -g`, check that npm's global `bin` directory is in your `PATH`. You can find it with `npm bin -g`.
138
+ - If using `npm link`, make sure you ran it from the `dist/` directory (not the project root).
139
+
140
+ ### Node.js version errors
141
+
142
+ Content CLI requires Node.js `>=18.20.5`. If you see compatibility errors, upgrade your Node.js installation. We recommend using [nvm](https://github.com/nvm-sh/nvm) to manage Node.js versions:
143
+
144
+ ```bash
145
+ nvm install --lts
146
+ nvm use --lts
147
+ ```
98
148
 
99
149
  ## Release Process
100
150
 
@@ -109,14 +159,14 @@ npm i -g @celonis/content-cli
109
159
  ## Contributing
110
160
 
111
161
  We encourage public contributions! Please check
112
- [CONTRIBUTING.md](https://github.com/celonis/content-cli/blob/master/CONTRIBUTING.md) for details on our
162
+ [CONTRIBUTING.md](https://github.com/celonis/content-cli/blob/main/CONTRIBUTING.md) for details on our
113
163
  code of conduct.
114
164
 
115
- For details on command development, refer to the [How to Add a Command](https://github.com/celonis/content-cli/blob/master/docs/how-to-add-command.md) guide.
165
+ For details on command development, refer to the [How to Add a Command](https://github.com/celonis/content-cli/blob/main/docs/how-to-add-command.md) guide.
116
166
 
117
167
  ## License
118
168
 
119
169
  Copyright (c) 2021 Celonis SE
120
170
 
121
171
  This project is licensed under the MIT License -
122
- see [LICENSE](https://github.com/celonis/content-cli/blob/master/LICENSE) file for details.
172
+ see [LICENSE](https://github.com/celonis/content-cli/blob/main/LICENSE) file for details.
@@ -33,6 +33,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
33
33
  });
34
34
  });
35
35
  }
36
+ createVersion(packageKey, request) {
37
+ return __awaiter(this, void 0, void 0, function* () {
38
+ return this.httpClient()
39
+ .post(`/pacman/api/core/packages/${packageKey}/versions`, request)
40
+ .catch(e => {
41
+ throw new logger_1.FatalError(`Problem creating version for package '${packageKey}': ${e}`);
42
+ });
43
+ });
44
+ }
36
45
  }
37
46
  exports.PackageVersionApi = PackageVersionApi;
38
47
  });
@@ -141,7 +141,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
141
141
  const variablesManifests = this.parseEntryData(configs, batch_export_import_constants_1.BatchExportImportConstants.VARIABLES_FILE_NAME);
142
142
  configs = yield this.studioService.mapSpaces(configs, studioManifests);
143
143
  const existingStudioPackages = yield this.studioPackageApi.findAllPackages();
144
- const formData = this.buildBodyForImport(configs, variablesManifests);
144
+ const formData = this.buildBodyForImport(configs, sourceToBeImported, variablesManifests);
145
145
  const postPackageImportData = yield this.batchImportExportApi.importPackages(formData, overwrite);
146
146
  yield this.studioService.processImportedPackages(configs, existingStudioPackages, studioManifests);
147
147
  if (gitBranch) {
@@ -196,8 +196,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
196
196
  file_service_1.fileService.writeToFileWithGivenName(JSON.stringify(packagesMetadata), filename);
197
197
  logger_1.logger.info(file_service_1.FileService.fileDownloadedMessage + filename);
198
198
  }
199
- buildBodyForImport(configs, variablesManifests) {
199
+ buildBodyForImport(configs, sourcePath, variablesManifests) {
200
200
  const formData = new FormData();
201
+ this.assertUncompressedSizeWithinLimit(configs, sourcePath);
201
202
  const readableStream = this.getReadableStream(configs);
202
203
  formData.append("file", readableStream, { filename: "configs.zip" });
203
204
  if (variablesManifests) {
@@ -207,6 +208,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
207
208
  }
208
209
  return formData;
209
210
  }
211
+ assertUncompressedSizeWithinLimit(configs, sourcePath) {
212
+ const MAX_ZIP_SIZE = 4 * 1024 * 1024 * 1024;
213
+ const totalUncompressedBytes = configs.getEntries().reduce((sum, entry) => sum + entry.header.size, 0);
214
+ if (totalUncompressedBytes > MAX_ZIP_SIZE) {
215
+ throw new Error(`Failed to handle zip file "${sourcePath}": uncompressed size ${(totalUncompressedBytes / (Math.pow(1024, 3))).toFixed(2)} GB exceeds the 4 GB limit.`);
216
+ }
217
+ }
210
218
  getReadableStream(configs) {
211
219
  return new stream_1.Readable({
212
220
  read() {
@@ -9,4 +9,10 @@
9
9
  })(function (require, exports) {
10
10
  "use strict";
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.VersionBumpOption = void 0;
13
+ var VersionBumpOption;
14
+ (function (VersionBumpOption) {
15
+ VersionBumpOption["NONE"] = "NONE";
16
+ VersionBumpOption["PATCH"] = "PATCH";
17
+ })(VersionBumpOption || (exports.VersionBumpOption = VersionBumpOption = {}));
12
18
  });
@@ -79,6 +79,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
79
79
  .requiredOption("--packageVersion <packageVersion>", "Version of the package. Can be a specific version or \"LATEST\" keyword, which will return the most recently created version.")
80
80
  .option("--json", "Return the response as a JSON file")
81
81
  .action(this.getPackageVersion);
82
+ configVersionCommand.command("create")
83
+ .description("Create a new version for a package")
84
+ .requiredOption("--packageKey <packageKey>", "Identifier of the package")
85
+ .option("--packageVersion <packageVersion>", "Version string (required if versionBumpOption is NONE)")
86
+ .option("--versionBumpOption <versionBumpOption>", "Version bump option: NONE or PATCH", "NONE")
87
+ .option("--summaryOfChanges <summaryOfChanges>", "Summary of changes for this version")
88
+ .option("--nodeFilterKeys <nodeFilterKeys...>", "Node keys to include in the version. If omitted, all nodes of the package are included.")
89
+ .option("--json", "Return the response as a JSON file")
90
+ .action(this.createPackageVersion);
82
91
  const variablesCommand = configCommand.command("variables")
83
92
  .description("Commands related to variable configs");
84
93
  variablesCommand.command("list")
@@ -162,6 +171,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
162
171
  yield new package_version_command_service_1.PackageVersionCommandService(context).getPackageVersion(options.packageKey, options.packageVersion, options.json);
163
172
  });
164
173
  }
174
+ createPackageVersion(context, command, options) {
175
+ return __awaiter(this, void 0, void 0, function* () {
176
+ const hasExplicitVersion = !!options.packageVersion;
177
+ const hasVersionBump = options.versionBumpOption && options.versionBumpOption !== "NONE";
178
+ if (hasExplicitVersion && hasVersionBump) {
179
+ throw new Error("Please provide either --packageVersion or --versionBumpOption, but not both.");
180
+ }
181
+ if (!hasExplicitVersion && !hasVersionBump) {
182
+ throw new Error("Please provide either --packageVersion or --versionBumpOption PATCH.");
183
+ }
184
+ yield new package_version_command_service_1.PackageVersionCommandService(context).createPackageVersion(options.packageKey, options.packageVersion, options.versionBumpOption, options.summaryOfChanges, options.nodeFilterKeys, options.json);
185
+ });
186
+ }
165
187
  batchImportPackages(context, command, options) {
166
188
  return __awaiter(this, void 0, void 0, function* () {
167
189
  if (options.gitProfile && !options.gitBranch) {
@@ -29,6 +29,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
29
29
  yield this.packageVersionService.findPackageVersion(packageKey, version, jsonResponse);
30
30
  });
31
31
  }
32
+ createPackageVersion(packageKey, version, versionBumpOption, summaryOfChanges, nodeFilterKeys, jsonResponse) {
33
+ return __awaiter(this, void 0, void 0, function* () {
34
+ yield this.packageVersionService.createPackageVersion(packageKey, version, versionBumpOption, summaryOfChanges, nodeFilterKeys, jsonResponse);
35
+ });
36
+ }
32
37
  }
33
38
  exports.PackageVersionCommandService = PackageVersionCommandService;
34
39
  });
@@ -40,6 +40,30 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
40
40
  }
41
41
  });
42
42
  }
43
+ createPackageVersion(packageKey, version, versionBumpOption, summaryOfChanges, nodeFilterKeys, jsonResponse) {
44
+ return __awaiter(this, void 0, void 0, function* () {
45
+ const request = {
46
+ version: version,
47
+ versionBumpOption: versionBumpOption,
48
+ summaryOfChanges: summaryOfChanges,
49
+ };
50
+ if (nodeFilterKeys && nodeFilterKeys.length > 0) {
51
+ request.nodeFilter = {
52
+ filterType: "INCLUDE",
53
+ keys: nodeFilterKeys,
54
+ };
55
+ }
56
+ const created = yield this.packageVersionApi.createVersion(packageKey, request);
57
+ if (jsonResponse) {
58
+ const filename = (0, uuid_1.v4)() + ".json";
59
+ file_service_1.fileService.writeToFileWithGivenName(JSON.stringify(created, null, 2), filename);
60
+ logger_1.logger.info(file_service_1.FileService.fileDownloadedMessage + filename);
61
+ }
62
+ else {
63
+ this.printPackageVersionCreatedTransport(created);
64
+ }
65
+ });
66
+ }
43
67
  printPackageVersionTransport(packageVersionTransport) {
44
68
  logger_1.logger.info(`Package Key: ${packageVersionTransport.packageKey}`);
45
69
  logger_1.logger.info(`Version: ${packageVersionTransport.version}`);
@@ -50,6 +74,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
50
74
  logger_1.logger.info(`Deployed: ${packageVersionTransport.deployed}`);
51
75
  logger_1.logger.info(`Published By: ${packageVersionTransport.publishedBy}`);
52
76
  }
77
+ printPackageVersionCreatedTransport(transport) {
78
+ logger_1.logger.info(`Successfully created version ${transport.version} for package ${transport.packageKey}`);
79
+ logger_1.logger.info(`Version: ${transport.version}`);
80
+ logger_1.logger.info(`Package Key: ${transport.packageKey}`);
81
+ logger_1.logger.info(`Summary of Changes: ${transport.summaryOfChanges}`);
82
+ logger_1.logger.info(`Creation Date: ${new Date(transport.creationDate).toISOString()}`);
83
+ logger_1.logger.info(`Created By: ${transport.createdBy}`);
84
+ }
53
85
  }
54
86
  exports.PackageVersionService = PackageVersionService;
55
87
  });
@@ -37,6 +37,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
37
37
  command.command("default <profile>")
38
38
  .description("Command to set a profile as default")
39
39
  .action(this.defaultProfile);
40
+ command.command("secure <profile>")
41
+ .description("Migrate a profile's secrets to secure system keychain storage")
42
+ .action(this.secureProfile);
40
43
  }
41
44
  defaultProfile(context, command) {
42
45
  return __awaiter(this, void 0, void 0, function* () {
@@ -55,6 +58,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
55
58
  yield new profile_command_service_1.ProfileCommandService().listProfiles();
56
59
  });
57
60
  }
61
+ secureProfile(context, command) {
62
+ return __awaiter(this, void 0, void 0, function* () {
63
+ const profile = command.args[0];
64
+ yield new profile_command_service_1.ProfileCommandService().secureProfile(profile);
65
+ });
66
+ }
58
67
  }
59
68
  return Module;
60
69
  });
@@ -55,7 +55,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
55
55
  }
56
56
  profile.authenticationType = yield profile_validator_1.ProfileValidator.validateProfile(profile);
57
57
  yield this.profileService.authorizeProfile(profile);
58
- this.profileService.storeProfile(profile);
58
+ yield this.profileService.storeProfile(profile);
59
59
  if (setAsDefault) {
60
60
  yield this.makeDefaultProfile(profile.name);
61
61
  }
@@ -89,6 +89,23 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
89
89
  logger_1.logger.info("Default profile: " + profile);
90
90
  });
91
91
  }
92
+ secureProfile(profileName) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ const profile = yield this.profileService.findProfile(profileName);
95
+ if (profile.secretsStoredSecurely) {
96
+ logger_1.logger.info(`Profile "${profileName}" is already using secure storage.`);
97
+ return;
98
+ }
99
+ yield this.profileService.storeProfile(profile);
100
+ const updatedProfile = yield this.profileService.findProfile(profileName);
101
+ if (updatedProfile.secretsStoredSecurely) {
102
+ logger_1.logger.info(`Profile "${profileName}" secrets have been migrated to secure storage.`);
103
+ }
104
+ else {
105
+ logger_1.logger.warn(`Failed to migrate profile "${profileName}" to secure storage. Secrets remain in plaintext.`);
106
+ }
107
+ });
108
+ }
92
109
  }
93
110
  exports.ProfileCommandService = ProfileCommandService;
94
111
  });
@@ -13,12 +13,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
13
13
  if (v !== undefined) module.exports = v;
14
14
  }
15
15
  else if (typeof define === "function" && define.amd) {
16
- define(["require", "exports", "./profile.interface", "./profile.validator", "path", "fs", "../utils/logger", "openid-client", "axios", "os"], factory);
16
+ define(["require", "exports", "./profile.interface", "./profile.validator", "path", "fs", "../utils/logger", "openid-client", "axios", "os", "./secret-storage.service"], factory);
17
17
  }
18
18
  })(function (require, exports) {
19
19
  "use strict";
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.profileService = exports.ProfileService = void 0;
21
+ exports.ProfileService = void 0;
22
22
  const profile_interface_1 = require("./profile.interface");
23
23
  const profile_validator_1 = require("./profile.validator");
24
24
  const path = require("path");
@@ -27,6 +27,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
27
27
  const openid_client_1 = require("openid-client");
28
28
  const axios_1 = require("axios");
29
29
  const os = require("os");
30
+ const secret_storage_service_1 = require("./secret-storage.service");
30
31
  const homedir = os.homedir();
31
32
  // use 5 seconds buffer to avoid rare cases when accessToken is just about to expire before the command is sent
32
33
  const expiryBuffer = 5000;
@@ -38,6 +39,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
38
39
  constructor() {
39
40
  this.profileContainerPath = path.resolve(homedir, ".celonis-content-cli-profiles");
40
41
  this.configContainer = path.resolve(this.profileContainerPath, "config.json");
42
+ this.secureSecretStorageService = new secret_storage_service_1.SecureSecretStorageService();
41
43
  }
42
44
  findProfile(profileName) {
43
45
  return __awaiter(this, void 0, void 0, function* () {
@@ -46,8 +48,28 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
46
48
  if (!this.checkIfMissingProfile(profileName)) {
47
49
  const file = fs.readFileSync(path.resolve(this.profileContainerPath, this.constructProfileFileName(profileName)), { encoding: "utf-8" });
48
50
  const profile = JSON.parse(file);
49
- this.refreshProfile(profile)
50
- .then(() => resolve(profile));
51
+ if (profile.secretsStoredSecurely) {
52
+ this.secureSecretStorageService.getSecrets(profileName)
53
+ .then(profileSecureSecrets => {
54
+ if (profileSecureSecrets) {
55
+ profile.apiToken = profileSecureSecrets.apiToken;
56
+ profile.refreshToken = profileSecureSecrets.refreshToken;
57
+ profile.clientSecret = profileSecureSecrets.clientSecret;
58
+ this.refreshProfile(profile)
59
+ .then(() => resolve(profile))
60
+ .catch(() => reject(`The profile ${profileName} couldn't be resolved.`));
61
+ }
62
+ else {
63
+ reject("Failed to read secrets from system keychain.");
64
+ }
65
+ })
66
+ .catch(() => reject(`The profile ${profileName} couldn't be resolved.`));
67
+ }
68
+ else {
69
+ this.refreshProfile(profile)
70
+ .then(() => resolve(profile))
71
+ .catch(() => reject(`The profile ${profileName} couldn't be resolved.`));
72
+ }
51
73
  }
52
74
  else if (process.env.TEAM_URL && process.env.API_TOKEN) {
53
75
  resolve(this.buildProfileFromEnvVariables());
@@ -92,11 +114,24 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
92
114
  }
93
115
  }
94
116
  storeProfile(profile) {
95
- this.createProfileContainerIfNotExists();
96
- const newProfileFileName = this.constructProfileFileName(profile.name);
97
- profile.team = this.getBaseTeamUrl(profile.team);
98
- fs.writeFileSync(path.resolve(this.profileContainerPath, newProfileFileName), JSON.stringify(profile), {
99
- encoding: "utf-8",
117
+ return __awaiter(this, void 0, void 0, function* () {
118
+ this.createProfileContainerIfNotExists();
119
+ const newProfileFileName = this.constructProfileFileName(profile.name);
120
+ profile.team = this.getBaseTeamUrl(profile.team);
121
+ const secretsStoredInKeychain = yield this.secureSecretStorageService.storeSecrets(profile);
122
+ const profileToStore = Object.assign({}, profile);
123
+ if (secretsStoredInKeychain) {
124
+ profileToStore.secretsStoredSecurely = true;
125
+ delete profileToStore.apiToken;
126
+ delete profileToStore.refreshToken;
127
+ delete profileToStore.clientSecret;
128
+ }
129
+ else {
130
+ profileToStore.secretsStoredSecurely = false;
131
+ }
132
+ fs.writeFileSync(path.resolve(this.profileContainerPath, newProfileFileName), JSON.stringify(profileToStore), {
133
+ encoding: "utf-8",
134
+ });
100
135
  });
101
136
  }
102
137
  buildProfileFromEnvVariables() {
@@ -263,7 +298,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
263
298
  logger_1.logger.error(new logger_1.FatalError("The profile cannot be refreshed. Please retry or recreate profile."));
264
299
  }
265
300
  }
266
- this.storeProfile(profile);
301
+ yield this.storeProfile(profile);
267
302
  });
268
303
  }
269
304
  getProfileEnvVariables() {
@@ -394,5 +429,4 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
394
429
  }
395
430
  }
396
431
  exports.ProfileService = ProfileService;
397
- exports.profileService = new ProfileService();
398
432
  });
@@ -0,0 +1,115 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ (function (factory) {
11
+ if (typeof module === "object" && typeof module.exports === "object") {
12
+ var v = factory(require, exports);
13
+ if (v !== undefined) module.exports = v;
14
+ }
15
+ else if (typeof define === "function" && define.amd) {
16
+ define(["require", "exports", "../utils/logger"], factory);
17
+ }
18
+ })(function (require, exports) {
19
+ "use strict";
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.SecureSecretStorageService = void 0;
22
+ const logger_1 = require("../utils/logger");
23
+ var PROFILE_SECRET_TYPE;
24
+ (function (PROFILE_SECRET_TYPE) {
25
+ PROFILE_SECRET_TYPE["API_TOKEN"] = "apiToken";
26
+ PROFILE_SECRET_TYPE["CLIENT_SECRET"] = "clientSecret";
27
+ PROFILE_SECRET_TYPE["REFRESH_TOKEN"] = "refreshToken";
28
+ })(PROFILE_SECRET_TYPE || (PROFILE_SECRET_TYPE = {}));
29
+ const CONTENT_CLI_SERVICE_NAME = "celonis-content-cli";
30
+ // Lazy load keytar to handle cases where native dependencies are not available
31
+ let keytar = null;
32
+ let keytarLoadError = null;
33
+ function getKeytar() {
34
+ if (keytar !== null) {
35
+ return keytar;
36
+ }
37
+ if (keytarLoadError !== null) {
38
+ return null;
39
+ }
40
+ try {
41
+ keytar = require("@github/keytar");
42
+ return keytar;
43
+ }
44
+ catch (error) {
45
+ keytarLoadError = error;
46
+ return null;
47
+ }
48
+ }
49
+ class SecureSecretStorageService {
50
+ storeSecrets(profile) {
51
+ return __awaiter(this, void 0, void 0, function* () {
52
+ let secretsStoredInKeychain = true;
53
+ const secretEntries = [
54
+ { type: PROFILE_SECRET_TYPE.API_TOKEN, value: profile.apiToken },
55
+ { type: PROFILE_SECRET_TYPE.CLIENT_SECRET, value: profile.clientSecret },
56
+ { type: PROFILE_SECRET_TYPE.REFRESH_TOKEN, value: profile.refreshToken },
57
+ ];
58
+ for (const secretEntry of secretEntries) {
59
+ if (secretEntry.value) {
60
+ const stored = yield this.storeSecret(this.getSecretServiceName(profile.name), secretEntry.type, secretEntry.value);
61
+ if (!stored) {
62
+ secretsStoredInKeychain = false;
63
+ }
64
+ }
65
+ }
66
+ if (!secretsStoredInKeychain) {
67
+ logger_1.logger.warn("⚠️ Failed to store secrets securely. They will be stored in plain text file.");
68
+ return false;
69
+ }
70
+ return true;
71
+ });
72
+ }
73
+ getSecrets(profileName) {
74
+ return __awaiter(this, void 0, void 0, function* () {
75
+ const keytarModule = getKeytar();
76
+ if (!keytarModule) {
77
+ return undefined;
78
+ }
79
+ try {
80
+ const secrets = yield keytarModule.findCredentials(this.getSecretServiceName(profileName));
81
+ if (!secrets.length) {
82
+ return undefined;
83
+ }
84
+ const profileSecrets = {};
85
+ for (const secret of secrets) {
86
+ profileSecrets[secret.account] = secret.password;
87
+ }
88
+ return profileSecrets;
89
+ }
90
+ catch (err) {
91
+ return undefined;
92
+ }
93
+ });
94
+ }
95
+ getSecretServiceName(profileName) {
96
+ return `${CONTENT_CLI_SERVICE_NAME}:${profileName}`;
97
+ }
98
+ storeSecret(service, account, secret) {
99
+ return __awaiter(this, void 0, void 0, function* () {
100
+ const keytarModule = getKeytar();
101
+ if (!keytarModule) {
102
+ return false;
103
+ }
104
+ try {
105
+ yield keytarModule.setPassword(service, account, secret);
106
+ return true;
107
+ }
108
+ catch (err) {
109
+ return false;
110
+ }
111
+ });
112
+ }
113
+ }
114
+ exports.SecureSecretStorageService = SecureSecretStorageService;
115
+ });
@@ -1,7 +1,8 @@
1
1
  import { Context } from "../../../core/command/cli-context";
2
- import { PackageVersionTransport } from "../interfaces/package-version.interfaces";
2
+ import { PackageVersionCreatedTransport, PackageVersionTransport, SavePackageVersionTransport } from "../interfaces/package-version.interfaces";
3
3
  export declare class PackageVersionApi {
4
4
  private httpClient;
5
5
  constructor(context: Context);
6
6
  findOne(packageKey: string, version: string): Promise<PackageVersionTransport>;
7
+ createVersion(packageKey: string, request: SavePackageVersionTransport): Promise<PackageVersionCreatedTransport>;
7
8
  }
@@ -18,6 +18,7 @@ export declare class BatchImportExportService {
18
18
  private getVersionedVariablesForPackagesWithKeys;
19
19
  private exportListOfPackagesMetadata;
20
20
  private buildBodyForImport;
21
+ private assertUncompressedSizeWithinLimit;
21
22
  private getReadableStream;
22
23
  private parseEntryData;
23
24
  private downloadZip;
@@ -8,3 +8,24 @@ export interface PackageVersionTransport {
8
8
  deployed: boolean;
9
9
  publishedBy: string;
10
10
  }
11
+ export interface SavePackageVersionTransport {
12
+ version?: string;
13
+ versionBumpOption?: VersionBumpOption;
14
+ summaryOfChanges?: string;
15
+ nodeFilter?: NodeFilterTransport;
16
+ }
17
+ export declare enum VersionBumpOption {
18
+ NONE = "NONE",
19
+ PATCH = "PATCH"
20
+ }
21
+ export interface NodeFilterTransport {
22
+ filterType: "INCLUDE";
23
+ keys: string[];
24
+ }
25
+ export interface PackageVersionCreatedTransport {
26
+ packageKey: string;
27
+ version: string;
28
+ summaryOfChanges: string;
29
+ creationDate: string;
30
+ createdBy: string;
31
+ }
@@ -9,6 +9,7 @@ declare class Module extends IModule {
9
9
  private batchExportPackages;
10
10
  private batchExportPackagesMetadata;
11
11
  private getPackageVersion;
12
+ private createPackageVersion;
12
13
  private batchImportPackages;
13
14
  private diffPackages;
14
15
  private listVariables;
@@ -3,4 +3,5 @@ export declare class PackageVersionCommandService {
3
3
  private packageVersionService;
4
4
  constructor(context: Context);
5
5
  getPackageVersion(packageKey: string, version: string, jsonResponse: boolean): Promise<void>;
6
+ createPackageVersion(packageKey: string, version: string | undefined, versionBumpOption: string, summaryOfChanges: string | undefined, nodeFilterKeys: string[] | undefined, jsonResponse: boolean): Promise<void>;
6
7
  }
@@ -3,5 +3,7 @@ export declare class PackageVersionService {
3
3
  private packageVersionApi;
4
4
  constructor(context: Context);
5
5
  findPackageVersion(packageKey: string, version: string, jsonResponse: boolean): Promise<void>;
6
+ createPackageVersion(packageKey: string, version: string | undefined, versionBumpOption: string, summaryOfChanges: string | undefined, nodeFilterKeys: string[] | undefined, jsonResponse: boolean): Promise<void>;
6
7
  private printPackageVersionTransport;
8
+ private printPackageVersionCreatedTransport;
7
9
  }
@@ -8,5 +8,6 @@ declare class Module extends IModule {
8
8
  private defaultProfile;
9
9
  private createProfile;
10
10
  private listProfiles;
11
+ private secureProfile;
11
12
  }
12
13
  export = Module;
@@ -3,4 +3,5 @@ export declare class ProfileCommandService {
3
3
  createProfile(setAsDefault: boolean): Promise<void>;
4
4
  listProfiles(): Promise<void>;
5
5
  makeDefaultProfile(profile: string): Promise<void>;
6
+ secureProfile(profileName: string): Promise<void>;
6
7
  }
@@ -1,14 +1,17 @@
1
- export interface Profile {
1
+ export interface ProfileSecrets {
2
+ apiToken: string;
3
+ clientSecret?: string;
4
+ refreshToken?: string;
5
+ secretsStoredSecurely?: boolean;
6
+ }
7
+ export interface Profile extends ProfileSecrets {
2
8
  name: string;
3
9
  team: string;
4
10
  type: ProfileType;
5
- apiToken: string;
6
11
  authenticationType: AuthenticationType;
7
12
  clientId?: string;
8
- clientSecret?: string;
9
13
  scopes?: string[];
10
14
  clientAuthenticationMethod?: ClientAuthenticationMethod;
11
- refreshToken?: string;
12
15
  expiresAt?: number;
13
16
  }
14
17
  export type AuthenticationType = "Bearer" | "AppKey";
@@ -5,10 +5,11 @@ export interface Config {
5
5
  export declare class ProfileService {
6
6
  private profileContainerPath;
7
7
  private configContainer;
8
+ private secureSecretStorageService;
8
9
  findProfile(profileName: string): Promise<Profile>;
9
10
  makeDefaultProfile(profileName: string): Promise<Profile>;
10
11
  getDefaultProfile(): string;
11
- storeProfile(profile: Profile): void;
12
+ storeProfile(profile: Profile): Promise<void>;
12
13
  private buildProfileFromEnvVariables;
13
14
  private storeConfig;
14
15
  private createProfileContainerIfNotExists;
@@ -40,4 +41,3 @@ export declare class ProfileService {
40
41
  */
41
42
  private extractScopesFromTokenSet;
42
43
  }
43
- export declare const profileService: ProfileService;
@@ -0,0 +1,7 @@
1
+ import { Profile, ProfileSecrets } from "./profile.interface";
2
+ export declare class SecureSecretStorageService {
3
+ storeSecrets(profile: Profile): Promise<boolean>;
4
+ getSecrets(profileName: string): Promise<ProfileSecrets | undefined>;
5
+ private getSecretServiceName;
6
+ private storeSecret;
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celonis/content-cli",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "CLI Tool to help manage content in Celonis Platform",
5
5
  "main": "content-cli.js",
6
6
  "bin": {
@@ -19,14 +19,16 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "adm-zip": "0.5.14",
22
+ "chalk": "4.1.2",
22
23
  "uuid": "9.0.1",
23
24
  "axios": "1.13.5",
24
25
  "commander": "13.1.0",
25
26
  "form-data": "4.0.4",
26
27
  "openid-client": "5.6.1",
27
28
  "hpagent": "1.2.0",
29
+ "@github/keytar": "7.10.6",
28
30
  "semver": "7.6.3",
29
- "simple-git": "3.28.0",
31
+ "simple-git": "3.32.3",
30
32
  "valid-url": "1.0.9",
31
33
  "winston": "3.17.0",
32
34
  "yaml": "2.7.0"