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
72 changes: 72 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"node-fetch": "^3.3.2",
"open": "^10.2.0",
"ora": "^8.1.0",
"ts-morph": "^24.0.0",
"uuid": "^9.0.1",
"zod": "^4.1.12"
},
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { executePull, type PullOptions } from './pull.js';
export { executePack, type PackOptions } from './pack.js';
export { executePublish, type PublishOptions } from './publish.js';
export { executeDeploy, type DeployOptions } from './deploy.js';
export { executeScan, logDetectedBindings, type ScanOptions } from './scan.js';
28 changes: 28 additions & 0 deletions packages/cli/src/actions/push.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import chalk from 'chalk';
import * as fs from 'fs';

Check warning on line 2 in packages/cli/src/actions/push.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `node:fs` over `fs`.

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-typescript&issues=AZz7FYEjmDcGlvu5ivQR&open=AZz7FYEjmDcGlvu5ivQR&pullRequest=293
import * as path from 'path';
import inquirer from 'inquirer';
import fetch from 'node-fetch';
Expand All @@ -9,6 +10,8 @@
import { WebAppFileHandler } from '../core/webapp-file-handler/index.js';
import { Preconditions } from '../core/preconditions.js';
import { cliTelemetryClient } from '../telemetry/index.js';
import { scanProject, formatBindings, loadSDKResourceMethods } from '../core/resource-scanner/index.js';
import { logDetectedBindings } from './scan.js';
import type { EnvironmentConfig } from '../types/index.js';

export interface PushOptions {
Expand Down Expand Up @@ -77,7 +80,7 @@
return projectId;
}

export async function executePush(options: PushOptions): Promise<void> {

Check failure on line 83 in packages/cli/src/actions/push.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 27 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-typescript&issues=AZz7FYEjmDcGlvu5ivQS&open=AZz7FYEjmDcGlvu5ivQS&pullRequest=293
const logger = options.logger ?? { log: console.log };
cliTelemetryClient.track('Cli.Push');

Expand Down Expand Up @@ -134,6 +137,31 @@
);
}

const tsconfigPath = path.join(rootDir, 'tsconfig.json');
if (fs.existsSync(tsconfigPath)) {
try {
const registry = await loadSDKResourceMethods(rootDir);
// Silently skip scan if SDK is not installed — push should not be blocked
if (registry.size > 0) {
logger.log(chalk.gray('[scan] Scanning for UiPath resource references...'));
const scanResult = await scanProject(tsconfigPath, registry);
for (const warning of scanResult.warnings) {
const relPath = path.relative(rootDir, warning.sourceFile);
logger.log(chalk.yellow(`[scan] ${warning.message} (${relPath}:${warning.line})`));
}
if (scanResult.resources.length > 0) {
const bindings = formatBindings(scanResult.resources);
if (bindings.resources.length > 0) {
logDetectedBindings(bindings, logger);
}
}
}
} catch (scanError) {
const msg = scanError instanceof Error ? scanError.message : 'Unknown scan error';
logger.log(chalk.yellow(`[scan] Resource scanning skipped: ${msg}`));
}
}

const handler = new WebAppFileHandler({
projectId,
rootDir,
Expand Down
90 changes: 90 additions & 0 deletions packages/cli/src/actions/scan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import chalk from 'chalk';
import * as fs from 'fs';

Check warning on line 2 in packages/cli/src/actions/scan.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `node:fs` over `fs`.

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-typescript&issues=AZz7FYELmDcGlvu5ivQO&open=AZz7FYELmDcGlvu5ivQO&pullRequest=293
import * as path from 'path';

Check warning on line 3 in packages/cli/src/actions/scan.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `node:path` over `path`.

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-typescript&issues=AZz7FYELmDcGlvu5ivQP&open=AZz7FYELmDcGlvu5ivQP&pullRequest=293
import { scanProject, formatBindings, loadSDKResourceMethods } from '../core/resource-scanner/index.js';
import { MESSAGES } from '../constants/index.js';
import { cliTelemetryClient } from '../telemetry/index.js';
import type { Bindings } from '../core/webapp-file-handler/types.js';

export interface ScanOptions {
rootDir?: string;
tsconfig?: string;
logger?: { log: (message: string) => void };
}

function logDetectedBindings(
bindings: Bindings,
logger: { log: (message: string) => void },
): void {
logger.log('');
logger.log(chalk.cyan('┌──────────────────────────────────────────────────────'));
logger.log(chalk.cyan('│') + chalk.bold.white(` Detected ${bindings.resources.length} resource binding(s) in your code`));
logger.log(chalk.cyan('├──────────────────────────────────────────────────────'));

for (const r of bindings.resources) {
const type = r.resource.toUpperCase();
const name = r.value.name?.defaultValue ?? r.value.ConnectionId?.defaultValue ?? '';
const folder = r.value.folderPath?.defaultValue;
const folderInfo = folder ? chalk.gray(` → folder: ${folder}`) : '';
logger.log(
chalk.cyan('│') +
` ${chalk.yellow(`[${type}]`)} ${chalk.bold.white(name)}${folderInfo}` +

Check warning on line 31 in packages/cli/src/actions/scan.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not use nested template literals.

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-typescript&issues=AZz7FYELmDcGlvu5ivQQ&open=AZz7FYELmDcGlvu5ivQQ&pullRequest=293
chalk.gray(` (key: ${r.key})`),
);
}

logger.log(chalk.cyan('│'));
logger.log(chalk.cyan('│') + chalk.bold.yellow(' Action required: ') + 'Please ensure these resources are declared in your');
logger.log(chalk.cyan('│') + ` ${chalk.bold('bindings.json')}. Missing entries may cause runtime failures after deployment.`);
logger.log(chalk.cyan('└──────────────────────────────────────────────────────'));
logger.log('');
}

export { logDetectedBindings };

export async function executeScan(options: ScanOptions): Promise<void> {
const logger = options.logger ?? { log: console.log };
cliTelemetryClient.track('Cli.Scan');

const rootDir = options.rootDir ?? process.cwd();
const tsconfigRelative = options.tsconfig ?? 'tsconfig.json';
const tsconfigPath = path.resolve(rootDir, tsconfigRelative);

logger.log(chalk.blue(MESSAGES.INFO.SCAN_HEADER));
logger.log('');

if (!fs.existsSync(tsconfigPath)) {
throw new Error(`${MESSAGES.ERRORS.SCAN_TSCONFIG_NOT_FOUND} (${tsconfigPath})`);
}

logger.log(chalk.gray(`[scan] Loading SDK resource metadata...`));
const registry = await loadSDKResourceMethods(rootDir);

if (registry.size === 0) {
logger.log(chalk.yellow('[scan] No resource metadata found in SDK. Ensure @uipath/uipath-typescript is installed.'));
return;
}

logger.log(chalk.gray(`[scan] Scanning project...`));

const result = await scanProject(tsconfigPath, registry);

for (const warning of result.warnings) {
const relPath = path.relative(rootDir, warning.sourceFile);
logger.log(chalk.yellow(`[scan] ⚠ ${warning.message} (${relPath}:${warning.line})`));
}

if (result.resources.length === 0) {
logger.log(chalk.gray('[scan] No UiPath resource references detected.'));
return;
}

const bindings = formatBindings(result.resources);

if (bindings.resources.length === 0) {
logger.log(chalk.gray('[scan] No resources with resolvable literal values found.'));
return;
}

logDetectedBindings(bindings, logger);
}
38 changes: 38 additions & 0 deletions packages/cli/src/commands/scan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Command, Flags } from '@oclif/core';
import chalk from 'chalk';
import { MESSAGES } from '../constants/index.js';
import { track } from '../telemetry/index.js';
import { executeScan } from '../actions/scan.js';

export default class Scan extends Command {
static override description = 'Scan TypeScript project for UiPath resource usage and display detected bindings';

Check warning on line 8 in packages/cli/src/commands/scan.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-typescript&issues=AZz7FYEzmDcGlvu5ivQT&open=AZz7FYEzmDcGlvu5ivQT&pullRequest=293

static override examples = [

Check warning on line 10 in packages/cli/src/commands/scan.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-typescript&issues=AZz7FYEzmDcGlvu5ivQU&open=AZz7FYEzmDcGlvu5ivQU&pullRequest=293
'<%= config.bin %> <%= command.id %>',
'<%= config.bin %> <%= command.id %> --tsconfig tsconfig.build.json',
];

static override flags = {

Check warning on line 15 in packages/cli/src/commands/scan.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=UiPath_uipath-typescript&issues=AZz7FYEzmDcGlvu5ivQV&open=AZz7FYEzmDcGlvu5ivQV&pullRequest=293
help: Flags.help({ char: 'h' }),
tsconfig: Flags.string({
description: 'Path to tsconfig.json (relative to project root). Default: tsconfig.json',
default: 'tsconfig.json',
}),
};

@track('Scan')
public async run(): Promise<void> {
const { flags } = await this.parse(Scan);
try {
await executeScan({
tsconfig: flags.tsconfig,
logger: this,
});
process.exit(0);
} catch (error) {
const msg = error instanceof Error ? error.message : MESSAGES.ERRORS.UNKNOWN_ERROR;
this.log(chalk.red(`${MESSAGES.ERRORS.SCAN_FAILED_PREFIX}${msg}`));
process.exit(1);
}
}
}
Loading
Loading