diff --git a/packages/@n8n/cli/package.json b/packages/@n8n/cli/package.json index 84a3ffebf10..16a9427befd 100644 --- a/packages/@n8n/cli/package.json +++ b/packages/@n8n/cli/package.json @@ -66,6 +66,9 @@ "source-control": { "description": "Pull changes from the configured source control provider" }, + "git-connections": { + "description": "Create and manage Git repository connections" + }, "package": { "description": "Export and import workflows as n8n packages (beta)" }, diff --git a/packages/@n8n/cli/src/client.ts b/packages/@n8n/cli/src/client.ts index bd77fbae042..bfaf19ea7a1 100644 --- a/packages/@n8n/cli/src/client.ts +++ b/packages/@n8n/cli/src/client.ts @@ -214,6 +214,38 @@ export class N8nClient { return limit !== undefined ? results.slice(0, limit) : results; } + // ─── Git connections ─────────────────────────────────────────── + + async listGitConnections(limit?: number) { + return await this.paginate>('/git-connections', {}, limit); + } + + async getGitConnection(id: string) { + return await this.get>(`/git-connections/${id}`); + } + + async createGitConnection(body: unknown) { + return await this.post>('/git-connections', body); + } + + async updateGitConnection(id: string, body: unknown) { + return await this.put>(`/git-connections/${id}`, body); + } + + async cloneGitConnection(id: string, branchName?: string) { + return await this.post>(`/git-connections/${id}/clone`, { + ...(branchName ? { branchName } : {}), + }); + } + + async disconnectGitConnection(id: string) { + return await this.post>(`/git-connections/${id}/disconnect`); + } + + async deleteGitConnection(id: string) { + return await this.del(`/git-connections/${id}`); + } + // ─── Workflows ───────────────────────────────────────────────── async listWorkflows(query: Record = {}, limit?: number) { diff --git a/packages/@n8n/cli/src/commands/git-connections/clone.ts b/packages/@n8n/cli/src/commands/git-connections/clone.ts new file mode 100644 index 00000000000..ecea756da8d --- /dev/null +++ b/packages/@n8n/cli/src/commands/git-connections/clone.ts @@ -0,0 +1,19 @@ +import { Args, Flags } from '@oclif/core'; + +import { BaseCommand } from '../../base-command'; + +export default class GitConnectionsClone extends BaseCommand { + static override description = 'Clone a Git connection'; + static override args = { id: Args.string({ description: 'Git connection ID', required: true }) }; + static override flags = { + ...BaseCommand.baseFlags, + branchName: Flags.string({ description: 'Remote branch to clone', aliases: ['branch-name'] }), + }; + + async run() { + const { args, flags } = await this.parse(GitConnectionsClone); + await this.execute(async () => { + this.output(await this.getClient(flags).cloneGitConnection(args.id, flags.branchName), flags); + }); + } +} diff --git a/packages/@n8n/cli/src/commands/git-connections/create.ts b/packages/@n8n/cli/src/commands/git-connections/create.ts new file mode 100644 index 00000000000..98341f891df --- /dev/null +++ b/packages/@n8n/cli/src/commands/git-connections/create.ts @@ -0,0 +1,20 @@ +import { Flags } from '@oclif/core'; + +import { BaseCommand } from '../../base-command'; + +export default class GitConnectionsCreate extends BaseCommand { + static override description = 'Create a Git connection from JSON'; + static override flags = { + ...BaseCommand.baseFlags, + file: Flags.string({ description: 'Path to Git connection JSON file' }), + stdin: Flags.boolean({ description: 'Read Git connection JSON from stdin', default: false }), + }; + + async run() { + const { flags } = await this.parse(GitConnectionsCreate); + await this.execute(async () => { + const body = JSON.parse(this.readInput(flags)) as unknown; + this.output(await this.getClient(flags).createGitConnection(body), flags); + }); + } +} diff --git a/packages/@n8n/cli/src/commands/git-connections/delete.ts b/packages/@n8n/cli/src/commands/git-connections/delete.ts new file mode 100644 index 00000000000..be7805356e8 --- /dev/null +++ b/packages/@n8n/cli/src/commands/git-connections/delete.ts @@ -0,0 +1,17 @@ +import { Args } from '@oclif/core'; + +import { BaseCommand } from '../../base-command'; + +export default class GitConnectionsDelete extends BaseCommand { + static override description = 'Delete a Git connection'; + static override args = { id: Args.string({ description: 'Git connection ID', required: true }) }; + static override flags = { ...BaseCommand.baseFlags }; + + async run() { + const { args, flags } = await this.parse(GitConnectionsDelete); + await this.execute(async () => { + await this.getClient(flags).deleteGitConnection(args.id); + this.succeed(`Git connection ${args.id} deleted.`, flags, { id: args.id, deleted: true }); + }); + } +} diff --git a/packages/@n8n/cli/src/commands/git-connections/disconnect.ts b/packages/@n8n/cli/src/commands/git-connections/disconnect.ts new file mode 100644 index 00000000000..5e0d1429a0e --- /dev/null +++ b/packages/@n8n/cli/src/commands/git-connections/disconnect.ts @@ -0,0 +1,16 @@ +import { Args } from '@oclif/core'; + +import { BaseCommand } from '../../base-command'; + +export default class GitConnectionsDisconnect extends BaseCommand { + static override description = 'Disconnect a Git connection'; + static override args = { id: Args.string({ description: 'Git connection ID', required: true }) }; + static override flags = { ...BaseCommand.baseFlags }; + + async run() { + const { args, flags } = await this.parse(GitConnectionsDisconnect); + await this.execute(async () => { + this.output(await this.getClient(flags).disconnectGitConnection(args.id), flags); + }); + } +} diff --git a/packages/@n8n/cli/src/commands/git-connections/get.ts b/packages/@n8n/cli/src/commands/git-connections/get.ts new file mode 100644 index 00000000000..935ed8e97d1 --- /dev/null +++ b/packages/@n8n/cli/src/commands/git-connections/get.ts @@ -0,0 +1,16 @@ +import { Args } from '@oclif/core'; + +import { BaseCommand } from '../../base-command'; + +export default class GitConnectionsGet extends BaseCommand { + static override description = 'Get a Git connection by ID'; + static override args = { id: Args.string({ description: 'Git connection ID', required: true }) }; + static override flags = { ...BaseCommand.baseFlags }; + + async run() { + const { args, flags } = await this.parse(GitConnectionsGet); + await this.execute(async () => { + this.output(await this.getClient(flags).getGitConnection(args.id), flags); + }); + } +} diff --git a/packages/@n8n/cli/src/commands/git-connections/list.ts b/packages/@n8n/cli/src/commands/git-connections/list.ts new file mode 100644 index 00000000000..ed58d4146d5 --- /dev/null +++ b/packages/@n8n/cli/src/commands/git-connections/list.ts @@ -0,0 +1,21 @@ +import { Flags } from '@oclif/core'; + +import { BaseCommand } from '../../base-command'; + +export default class GitConnectionsList extends BaseCommand { + static override description = 'List Git connections'; + static override flags = { + ...BaseCommand.baseFlags, + limit: Flags.integer({ description: 'Maximum number of results' }), + }; + + async run() { + const { flags } = await this.parse(GitConnectionsList); + await this.execute(async () => { + const data = await this.getClient(flags).listGitConnections(flags.limit); + this.output(data, flags, { + columns: ['id', 'name', 'repositoryUrl', 'branchName', 'connectionType'], + }); + }); + } +} diff --git a/packages/@n8n/cli/src/commands/git-connections/update.ts b/packages/@n8n/cli/src/commands/git-connections/update.ts new file mode 100644 index 00000000000..f26c29a8d89 --- /dev/null +++ b/packages/@n8n/cli/src/commands/git-connections/update.ts @@ -0,0 +1,21 @@ +import { Args, Flags } from '@oclif/core'; + +import { BaseCommand } from '../../base-command'; + +export default class GitConnectionsUpdate extends BaseCommand { + static override description = 'Update a Git connection from JSON'; + static override args = { id: Args.string({ description: 'Git connection ID', required: true }) }; + static override flags = { + ...BaseCommand.baseFlags, + file: Flags.string({ description: 'Path to Git connection JSON file' }), + stdin: Flags.boolean({ description: 'Read Git connection JSON from stdin', default: false }), + }; + + async run() { + const { args, flags } = await this.parse(GitConnectionsUpdate); + await this.execute(async () => { + const body = JSON.parse(this.readInput(flags)) as unknown; + this.output(await this.getClient(flags).updateGitConnection(args.id, body), flags); + }); + } +} diff --git a/packages/@n8n/cli/src/index.ts b/packages/@n8n/cli/src/index.ts index 8c051bb1a90..ad221a6c919 100644 --- a/packages/@n8n/cli/src/index.ts +++ b/packages/@n8n/cli/src/index.ts @@ -22,6 +22,13 @@ import ExecutionGet from './commands/execution/get'; import ExecutionList from './commands/execution/list'; import ExecutionRetry from './commands/execution/retry'; import ExecutionStop from './commands/execution/stop'; +import GitConnectionsClone from './commands/git-connections/clone'; +import GitConnectionsCreate from './commands/git-connections/create'; +import GitConnectionsDelete from './commands/git-connections/delete'; +import GitConnectionsDisconnect from './commands/git-connections/disconnect'; +import GitConnectionsGet from './commands/git-connections/get'; +import GitConnectionsList from './commands/git-connections/list'; +import GitConnectionsUpdate from './commands/git-connections/update'; import Login from './commands/login'; import Logout from './commands/logout'; import PackageExport from './commands/package/export'; @@ -80,6 +87,14 @@ export const commands = { 'execution:stop': ExecutionStop, 'execution:delete': ExecutionDelete, + 'git-connections:list': GitConnectionsList, + 'git-connections:get': GitConnectionsGet, + 'git-connections:create': GitConnectionsCreate, + 'git-connections:update': GitConnectionsUpdate, + 'git-connections:clone': GitConnectionsClone, + 'git-connections:disconnect': GitConnectionsDisconnect, + 'git-connections:delete': GitConnectionsDelete, + 'credential:list': CredentialList, 'credential:get': CredentialGet, 'credential:schema': CredentialSchema,