Skip to content
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
npm run typecheck
npm run lint
npm run build
git add copilot-instructions.md
36 changes: 34 additions & 2 deletions copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,42 @@ pncli bitbucket get-build-status
# sonar — no subcommands implemented yet
```

### Artifactory
### Deps

```
# artifactory — no subcommands implemented yet
pncli deps frisk
--ecosystem <ecosystem> Filter to one ecosystem: npm, nuget, maven, all
(default: "all")
--direct-only Only scan direct dependencies (default: include
transitive) (default: false)
--include-dev Include dev/test dependencies (default: false)

pncli deps scan
--ecosystem <ecosystem> Filter to one ecosystem: npm, nuget, maven, all
(default: "all")
--include-transitive Include transitive dependencies (default: false)
--include-dev Include dev/test dependencies (default: false)

pncli deps diff
--from <ref> Base git ref (commit, tag, or branch)
--to <ref> Target git ref (default: working tree)
--ecosystem <ecosystem> Filter to one ecosystem: npm, nuget, maven, all
(default: "all")
--include-dev Include dev/test dependencies (default: false)

pncli deps outdated
--ecosystem <ecosystem> Filter to one ecosystem: npm, nuget, maven, all
(default: "all")
--major Only show major version bumps
--minor Only show minor version bumps or higher
--patch Only show patch version bumps or higher

pncli deps license-check
--ecosystem <ecosystem> Filter to one ecosystem: npm, nuget, maven, all
(default: "all")
--include-dev Include dev/test dependencies (default: false)

pncli deps connectivity
```

### Config
Expand Down
6 changes: 3 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { registerJiraCommands } from './services/jira/commands.js';
import { registerBitbucketCommands } from './services/bitbucket/commands.js';
import { registerConfluenceCommands } from './services/confluence/commands.js';
import { registerSonarCommands } from './services/sonar/commands.js';
import { registerArtifactoryCommands } from './services/artifactory/commands.js';
import { registerDepsCommands } from './services/deps/commands.js';
import { registerConfigCommands } from './services/config/commands.js';

const require = createRequire(import.meta.url);
Expand Down Expand Up @@ -48,17 +48,17 @@ registerJiraCommands(program);
registerBitbucketCommands(program);
registerConfluenceCommands(program);
registerSonarCommands(program);
registerArtifactoryCommands(program);
registerDepsCommands(program);
registerConfigCommands(program);

program.addHelpText('after', `
Services:
git Local git operations (status, diff, log, branch)
deps Dependency scanning, CVE detection, license auditing
jira Jira Data Cloud (coming soon)
bitbucket Bitbucket Server (coming soon)
confluence Confluence (coming soon)
sonar SonarQube (coming soon)
artifactory Artifactory (coming soon)
config Manage pncli configuration
`);

Expand Down
16 changes: 16 additions & 0 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ const ENV_KEYS = {
JIRA_API_TOKEN: 'PNCLI_JIRA_API_TOKEN',
BITBUCKET_BASE_URL: 'PNCLI_BITBUCKET_BASE_URL',
BITBUCKET_PAT: 'PNCLI_BITBUCKET_PAT',
ARTIFACTORY_BASE_URL: 'PNCLI_ARTIFACTORY_BASE_URL',
ARTIFACTORY_TOKEN: 'PNCLI_ARTIFACTORY_TOKEN',
ARTIFACTORY_REPO_NPM: 'PNCLI_ARTIFACTORY_REPO_NPM',
ARTIFACTORY_REPO_NUGET: 'PNCLI_ARTIFACTORY_REPO_NUGET',
ARTIFACTORY_REPO_MAVEN: 'PNCLI_ARTIFACTORY_REPO_MAVEN',
CONFIG_PATH: 'PNCLI_CONFIG_PATH'
} as const;

Expand Down Expand Up @@ -89,6 +94,13 @@ export function loadConfig(opts: LoadConfigOptions = {}): ResolvedConfig {
baseUrl: process.env[ENV_KEYS.BITBUCKET_BASE_URL] ?? globalConfig.bitbucket?.baseUrl,
pat: process.env[ENV_KEYS.BITBUCKET_PAT] ?? globalConfig.bitbucket?.pat
},
artifactory: {
baseUrl: process.env[ENV_KEYS.ARTIFACTORY_BASE_URL] ?? globalConfig.artifactory?.baseUrl,
token: process.env[ENV_KEYS.ARTIFACTORY_TOKEN] ?? globalConfig.artifactory?.token,
npmRepo: process.env[ENV_KEYS.ARTIFACTORY_REPO_NPM] ?? globalConfig.artifactory?.npmRepo,
nugetRepo: process.env[ENV_KEYS.ARTIFACTORY_REPO_NUGET] ?? globalConfig.artifactory?.nugetRepo,
mavenRepo: process.env[ENV_KEYS.ARTIFACTORY_REPO_MAVEN] ?? globalConfig.artifactory?.mavenRepo
},
defaults: mergedDefaults
};
}
Expand Down Expand Up @@ -138,6 +150,10 @@ export function maskConfig(config: ResolvedConfig): unknown {
bitbucket: {
...config.bitbucket,
pat: config.bitbucket.pat ? '***' : undefined
},
artifactory: {
...config.artifactory,
token: config.artifactory.token ? '***' : undefined
}
};
}
Expand Down
14 changes: 8 additions & 6 deletions src/lib/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,10 @@ export class HttpClient {

if (this.dryRun) {
const safeHeaders = { ...headers, Authorization: '[REDACTED]' };
process.stderr.write(`DRY RUN: ${init.method} ${url}\nHeaders: ${JSON.stringify(safeHeaders, null, 2)}\n`);
if (opts.body) process.stderr.write(`Body: ${JSON.stringify(opts.body, null, 2)}\n`);
process.exit(ExitCode.SUCCESS);
const msg = `DRY RUN: ${init.method} ${url}\nHeaders: ${JSON.stringify(safeHeaders, null, 2)}\n`
+ (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}\n` : '');
process.stderr.write(msg, () => process.exit(ExitCode.SUCCESS));
return new Promise<never>(() => { /* exit pending */ });
}

return request<T>(url, init, opts.timeoutMs ?? 30000);
Expand All @@ -185,9 +186,10 @@ export class HttpClient {

if (this.dryRun) {
const safeHeaders = { ...headers, Authorization: '[REDACTED]' };
process.stderr.write(`DRY RUN: ${init.method} ${url}\nHeaders: ${JSON.stringify(safeHeaders, null, 2)}\n`);
if (opts.body) process.stderr.write(`Body: ${JSON.stringify(opts.body, null, 2)}\n`);
process.exit(ExitCode.SUCCESS);
const msg = `DRY RUN: ${init.method} ${url}\nHeaders: ${JSON.stringify(safeHeaders, null, 2)}\n`
+ (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}\n` : '');
process.stderr.write(msg, () => process.exit(ExitCode.SUCCESS));
return new Promise<never>(() => { /* exit pending */ });
}

return request<T>(url, init, opts.timeoutMs ?? 30000);
Expand Down
8 changes: 3 additions & 5 deletions src/lib/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,10 @@ export function fail(

if (msg) process.stderr.write(msg + '\n');

process.stdout.write(
(globalOptions.pretty ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope)) + '\n'
);

const output = (globalOptions.pretty ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope)) + '\n';
const exitCode = err instanceof PncliError ? exitCodeFromStatus(err.status) : ExitCode.GENERAL_ERROR;
process.exit(exitCode);
process.stdout.write(output, () => process.exit(exitCode));
throw new PncliError(errorDetail.message, errorDetail.status);
}

export function log(message: string): void {
Expand Down
16 changes: 0 additions & 16 deletions src/services/artifactory/commands.ts

This file was deleted.

49 changes: 49 additions & 0 deletions src/services/config/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,46 @@ async function initGlobalConfig(start: number): Promise<void> {
message: 'Bitbucket personal access token:'
});

process.stderr.write('\n── Artifactory ───────────────────────────────────\n');
const useArtifactory = await confirm({
message: 'Configure Artifactory for dependency commands (deps outdated, deps license-check)?',
default: false
});

let artifactoryBaseUrl = '';
let artifactoryToken = '';
let npmRepo = '';
let nugetRepo = '';
let mavenRepo = '';

if (useArtifactory) {
artifactoryBaseUrl = await input({
message: 'Artifactory base URL (e.g. https://artifactory.company.com):',
default: ''
});

artifactoryToken = await password({
message: 'Artifactory API token:'
});

process.stderr.write('\nConfigure which ecosystems you use (skip any that don\'t apply):\n');

const useNpm = await confirm({ message: ' Use npm packages from Artifactory?', default: true });
if (useNpm) {
npmRepo = await input({ message: ' npm repository name:', default: 'npm-remote' });
}

const useNuget = await confirm({ message: ' Use NuGet packages from Artifactory?', default: false });
if (useNuget) {
nugetRepo = await input({ message: ' NuGet repository name:', default: 'nuget-remote' });
}

const useMaven = await confirm({ message: ' Use Maven packages from Artifactory?', default: false });
if (useMaven) {
mavenRepo = await input({ message: ' Maven repository name:', default: 'libs-release' });
}
}

process.stderr.write('\n── Defaults ──────────────────────────────────────\n');
const jiraProject = await input({
message: 'Default Jira project key (optional):',
Expand Down Expand Up @@ -144,6 +184,15 @@ async function initGlobalConfig(start: number): Promise<void> {
baseUrl: bitbucketBaseUrl || undefined,
pat: bitbucketPat || undefined
},
...(useArtifactory ? {
artifactory: {
baseUrl: artifactoryBaseUrl || undefined,
token: artifactoryToken || undefined,
npmRepo: npmRepo || undefined,
nugetRepo: nugetRepo || undefined,
mavenRepo: mavenRepo || undefined
}
} : {}),
defaults: {
jira: {
project: jiraProject || undefined
Expand Down
Loading
Loading