Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 40 additions & 0 deletions src/commands/sdk/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Commands for building custom apps with the Celonis SDK.
*/

import { Command, OptionValues } from "commander";
import { Configurator, IModule } from "../../core/command/module-handler";
import { Context } from "../../core/command/cli-context";
import { FatalError } from "../../core/utils/logger";

class Module extends IModule {

public register(context: Context, configurator: Configurator): void {
const command = configurator.command("sdk")
.description("Commands for building custom apps with the Celonis SDK.");

command.command("token")
.description("Print an access token for the active profile, refreshing it if needed")
.option("--json", "Print team url, access token and expiry as JSON")
.action(this.token);
}

private async token(context: Context, command: Command, options: OptionValues): Promise<void> {

Check warning on line 22 in src/commands/sdk/module.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Async method 'token' has no 'await' expression.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AaFADBrk2ObLi0Gph8kQ&open=AaFADBrk2ObLi0Gph8kQ&pullRequest=432
const profile = context.profile;
if (!profile?.apiToken) {
throw new FatalError("No access token available. Run 'content-cli profile create' to log in.");
}
// Write to stdout directly so the output can be piped or captured by other tools.
if (options.json) {
process.stdout.write(JSON.stringify({
team: profile.team,
accessToken: profile.apiToken,
expiresAt: profile.expiresAt,
}) + "\n");
} else {
process.stdout.write(profile.apiToken + "\n");
}
}
}

export = Module;
23 changes: 20 additions & 3 deletions src/core/profile/profile.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,11 +214,25 @@
break;
case ProfileType.DEVICE_CODE:
const deviceCodeIssuer = await Issuer.discover(profile.team);
// Spike (DISCO-68): CONTENT_CLI_CLIENT_ID overrides the OAuth client; it is stored on the profile
// so that refreshes use the same client.
if (process.env.CONTENT_CLI_CLIENT_ID) {
profile.clientId = process.env.CONTENT_CLI_CLIENT_ID;
}
const deviceCodeOAuthClient = new deviceCodeIssuer.Client({
client_id: "content-cli",
client_id: profile.clientId ?? "content-cli",
token_endpoint_auth_method: "none",
});
const deviceCodeScopeAttempts: string[][] = [OAUTH_SCOPES, DEVICE_CODE_SCOPES_WITHOUT_ACTION_ENGINE];
// Spike (DISCO-68): CONTENT_CLI_EXTRA_SCOPES="a b" adds scopes, CONTENT_CLI_SCOPES="a b" replaces them.
// Either one makes a single attempt without fallback, so a rejected scope fails visibly
// instead of being silently dropped.
const parseScopes = (value?: string) => (value ?? "").split(/[\s,]+/).filter(Boolean);

Check warning on line 229 in src/core/profile/profile.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected lexical declaration in case block.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AaFADBvk2ObLi0Gph8kR&open=AaFADBvk2ObLi0Gph8kR&pullRequest=432
const overrideScopes = parseScopes(process.env.CONTENT_CLI_SCOPES);

Check warning on line 230 in src/core/profile/profile.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected lexical declaration in case block.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AaFADBvk2ObLi0Gph8kS&open=AaFADBvk2ObLi0Gph8kS&pullRequest=432
const extraScopes = parseScopes(process.env.CONTENT_CLI_EXTRA_SCOPES);

Check warning on line 231 in src/core/profile/profile.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected lexical declaration in case block.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AaFADBvk2ObLi0Gph8kT&open=AaFADBvk2ObLi0Gph8kT&pullRequest=432
const scopesOverridden = overrideScopes.length > 0 || extraScopes.length > 0;

Check warning on line 232 in src/core/profile/profile.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected lexical declaration in case block.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AaFADBvk2ObLi0Gph8kU&open=AaFADBvk2ObLi0Gph8kU&pullRequest=432
const deviceCodeScopeAttempts: string[][] = scopesOverridden
? [[...(overrideScopes.length ? overrideScopes : OAUTH_SCOPES), ...extraScopes]]
: [OAUTH_SCOPES, DEVICE_CODE_SCOPES_WITHOUT_ACTION_ENGINE];

Check warning on line 235 in src/core/profile/profile.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected lexical declaration in case block.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AaFADBvk2ObLi0Gph8kV&open=AaFADBvk2ObLi0Gph8kV&pullRequest=432
let deviceCodeSuccess = false;
for (const scopeList of deviceCodeScopeAttempts) {
try {
Expand All @@ -234,6 +248,9 @@
break;
} catch (err) {
// This scope set failed; try next or fail below
if (scopesOverridden) {
logger.warn(`Device code authorization with scopes "${scopeList.join(" ")}" failed: ${err}`);
}
}
}
if (!deviceCodeSuccess) {
Expand Down Expand Up @@ -287,7 +304,7 @@
const issuer = await Issuer.discover(profile.team);
if (profile.type === ProfileType.DEVICE_CODE) {
const oauthClient = new issuer.Client({
client_id: "content-cli",
client_id: profile.clientId ?? "content-cli",
token_endpoint_auth_method: "none",
});
const tokenSet = await oauthClient.refresh(profile.refreshToken);
Expand Down
Loading