@bonvoy/plugin-gitlab 0.2.0 → 0.2.2

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.
@@ -0,0 +1,46 @@
1
+ import { BonvoyPlugin } from "@bonvoy/core";
2
+
3
+ //#region src/operations.d.ts
4
+ interface GitLabReleaseParams {
5
+ projectId: string | number;
6
+ tagName: string;
7
+ name: string;
8
+ description: string;
9
+ }
10
+ interface GitLabMRParams {
11
+ projectId: string | number;
12
+ title: string;
13
+ description: string;
14
+ sourceBranch: string;
15
+ targetBranch: string;
16
+ }
17
+ interface GitLabMRResult {
18
+ url: string;
19
+ iid: number;
20
+ }
21
+ interface GitLabOperations {
22
+ createRelease(token: string, host: string, params: GitLabReleaseParams): Promise<void>;
23
+ createMR(token: string, host: string, params: GitLabMRParams): Promise<GitLabMRResult>;
24
+ releaseExists(token: string, host: string, projectId: string | number, tagName: string): Promise<boolean>;
25
+ }
26
+ declare const defaultGitLabOperations: GitLabOperations;
27
+ //#endregion
28
+ //#region src/gitlab.d.ts
29
+ interface GitLabPluginOptions {
30
+ token?: string;
31
+ host?: string;
32
+ projectId?: string | number;
33
+ tagFormat?: string;
34
+ }
35
+ declare class GitLabPlugin implements BonvoyPlugin {
36
+ name: string;
37
+ private options;
38
+ private ops;
39
+ constructor(options?: GitLabPluginOptions, ops?: GitLabOperations);
40
+ apply(bonvoy: any): void;
41
+ private validateReleases;
42
+ private getProjectId;
43
+ }
44
+ //#endregion
45
+ export { type GitLabOperations, type GitLabPluginOptions, type GitLabReleaseParams, GitLabPlugin as default, defaultGitLabOperations };
46
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,160 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { Gitlab } from "@gitbeaker/rest";
4
+
5
+ //#region src/operations.ts
6
+ const defaultGitLabOperations = {
7
+ async createRelease(token, host, params) {
8
+ await new Gitlab({
9
+ token,
10
+ host
11
+ }).ProjectReleases.create(params.projectId, {
12
+ tagName: params.tagName,
13
+ name: params.name,
14
+ description: params.description
15
+ });
16
+ },
17
+ async createMR(token, host, params) {
18
+ const mr = await new Gitlab({
19
+ token,
20
+ host
21
+ }).MergeRequests.create(params.projectId, params.sourceBranch, params.targetBranch, params.title, { description: params.description });
22
+ return {
23
+ url: mr.web_url,
24
+ iid: mr.iid
25
+ };
26
+ },
27
+ async releaseExists(token, host, projectId, tagName) {
28
+ const api = new Gitlab({
29
+ token,
30
+ host
31
+ });
32
+ try {
33
+ await api.ProjectReleases.show(projectId, tagName);
34
+ return true;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+ };
40
+
41
+ //#endregion
42
+ //#region src/gitlab.ts
43
+ var GitLabPlugin = class {
44
+ name = "gitlab";
45
+ options;
46
+ ops;
47
+ constructor(options = {}, ops) {
48
+ this.options = options;
49
+ this.ops = ops ?? defaultGitLabOperations;
50
+ }
51
+ apply(bonvoy) {
52
+ bonvoy.hooks.validateRepo.tapPromise(this.name, async (context) => {
53
+ await this.validateReleases(context);
54
+ });
55
+ bonvoy.hooks.makeRelease.tapPromise(this.name, async (context) => {
56
+ if (context.isDryRun) {
57
+ context.logger.info("🔍 [dry-run] Would create GitLab releases");
58
+ return;
59
+ }
60
+ const token = this.options.token || process.env.GITLAB_TOKEN;
61
+ if (!token) {
62
+ context.logger.warn("⚠️ GITLAB_TOKEN not found, skipping GitLab releases");
63
+ return;
64
+ }
65
+ const host = this.options.host || process.env.GITLAB_HOST || "https://gitlab.com";
66
+ const projectId = this.getProjectId(context.rootPath);
67
+ for (const pkg of context.changedPackages) {
68
+ const version = context.versions[pkg.name];
69
+ const changelog = context.changelogs[pkg.name] || "";
70
+ const tagName = (this.options.tagFormat ?? "{name}@{version}").replace("{name}", pkg.name).replace("{version}", version);
71
+ try {
72
+ await this.ops.createRelease(token, host, {
73
+ projectId,
74
+ tagName,
75
+ name: `${pkg.name} v${version}`,
76
+ description: changelog
77
+ });
78
+ context.logger.info(`✅ Created GitLab release: ${tagName}`);
79
+ } catch (error) {
80
+ const errorMessage = error instanceof Error ? error.message : String(error);
81
+ context.logger.error(`❌ Failed to create release for ${tagName}: ${errorMessage}`);
82
+ throw error;
83
+ }
84
+ }
85
+ });
86
+ bonvoy.hooks.createPR.tapPromise(this.name, async (context) => {
87
+ if (context.isDryRun) {
88
+ context.logger.info("🔍 [dry-run] Would create GitLab MR");
89
+ return;
90
+ }
91
+ const token = this.options.token || process.env.GITLAB_TOKEN;
92
+ if (!token) {
93
+ context.logger.warn("⚠️ GITLAB_TOKEN not found, skipping MR creation");
94
+ return;
95
+ }
96
+ const host = this.options.host || process.env.GITLAB_HOST || "https://gitlab.com";
97
+ const projectId = this.getProjectId(context.rootPath);
98
+ try {
99
+ const result = await this.ops.createMR(token, host, {
100
+ projectId,
101
+ title: context.title,
102
+ description: context.body,
103
+ sourceBranch: context.branchName,
104
+ targetBranch: context.baseBranch
105
+ });
106
+ context.prUrl = result.url;
107
+ context.prNumber = result.iid;
108
+ context.logger.info(`✅ Created GitLab MR: ${result.url}`);
109
+ } catch (error) {
110
+ const errorMessage = error instanceof Error ? error.message : String(error);
111
+ context.logger.error(`❌ Failed to create MR: ${errorMessage}`);
112
+ throw error;
113
+ }
114
+ });
115
+ }
116
+ async validateReleases(context) {
117
+ const { changedPackages, versions, logger } = context;
118
+ if (!versions) return;
119
+ const token = this.options.token || process.env.GITLAB_TOKEN;
120
+ if (!token) return;
121
+ const host = this.options.host || process.env.GITLAB_HOST || "https://gitlab.com";
122
+ let projectId;
123
+ try {
124
+ projectId = this.getProjectId(context.rootPath);
125
+ } catch {
126
+ return;
127
+ }
128
+ const tagFormat = this.options.tagFormat ?? "{name}@{version}";
129
+ const existing = [];
130
+ for (const pkg of changedPackages) {
131
+ const version = versions[pkg.name];
132
+ /* c8 ignore start -- defensive: version always present for changedPackages */
133
+ if (!version) continue;
134
+ /* c8 ignore stop */
135
+ const tag = tagFormat.replace("{name}", pkg.name).replace("{version}", version);
136
+ if (await this.ops.releaseExists(token, host, projectId, tag)) existing.push(tag);
137
+ }
138
+ if (existing.length > 0) {
139
+ logger.error(`❌ GitLab releases already exist: ${existing.join(", ")}`);
140
+ throw new Error(`Cannot release: GitLab releases already exist (${existing.join(", ")}). Delete them first or bump to a new version.`);
141
+ }
142
+ }
143
+ getProjectId(rootPath) {
144
+ if (this.options.projectId) return this.options.projectId;
145
+ try {
146
+ const pkgPath = join(rootPath, "package.json");
147
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
148
+ const repoUrl = typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url;
149
+ if (repoUrl) {
150
+ const match = repoUrl.match(/gitlab\.com[:/](.+?)(?:\.git)?$/);
151
+ if (match) return encodeURIComponent(match[1]);
152
+ }
153
+ } catch {}
154
+ throw new Error("Could not determine GitLab project. Please set \"repository\" in package.json or provide projectId in plugin options.");
155
+ }
156
+ };
157
+
158
+ //#endregion
159
+ export { GitLabPlugin as default, defaultGitLabOperations };
160
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/operations.ts","../src/gitlab.ts"],"sourcesContent":["import { Gitlab } from '@gitbeaker/rest';\n\nexport interface GitLabReleaseParams {\n projectId: string | number;\n tagName: string;\n name: string;\n description: string;\n}\n\nexport interface GitLabMRParams {\n projectId: string | number;\n title: string;\n description: string;\n sourceBranch: string;\n targetBranch: string;\n}\n\nexport interface GitLabMRResult {\n url: string;\n iid: number;\n}\n\nexport interface GitLabOperations {\n createRelease(token: string, host: string, params: GitLabReleaseParams): Promise<void>;\n createMR(token: string, host: string, params: GitLabMRParams): Promise<GitLabMRResult>;\n releaseExists(\n token: string,\n host: string,\n projectId: string | number,\n tagName: string,\n ): Promise<boolean>;\n}\n\nexport const defaultGitLabOperations: GitLabOperations = {\n /* v8 ignore start */\n async createRelease(token, host, params) {\n const api = new Gitlab({ token, host });\n await api.ProjectReleases.create(params.projectId, {\n tagName: params.tagName,\n name: params.name,\n description: params.description,\n });\n },\n\n async createMR(token, host, params) {\n const api = new Gitlab({ token, host });\n const mr = await api.MergeRequests.create(\n params.projectId,\n params.sourceBranch,\n params.targetBranch,\n params.title,\n {\n description: params.description,\n },\n );\n return { url: mr.web_url, iid: mr.iid };\n },\n\n async releaseExists(token, host, projectId, tagName) {\n const api = new Gitlab({ token, host });\n try {\n await api.ProjectReleases.show(projectId, tagName);\n return true;\n } catch {\n return false;\n }\n },\n /* v8 ignore stop */\n};\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport type { BonvoyPlugin, Context, PRContext, ReleaseContext } from '@bonvoy/core';\n\nimport { defaultGitLabOperations, type GitLabOperations } from './operations.js';\n\nexport interface GitLabPluginOptions {\n token?: string;\n host?: string;\n projectId?: string | number;\n tagFormat?: string;\n}\n\nexport default class GitLabPlugin implements BonvoyPlugin {\n name = 'gitlab';\n private options: GitLabPluginOptions;\n private ops: GitLabOperations;\n\n constructor(options: GitLabPluginOptions = {}, ops?: GitLabOperations) {\n this.options = options;\n this.ops = ops ?? defaultGitLabOperations;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Bonvoy type causes circular dependency\n apply(bonvoy: any): void {\n bonvoy.hooks.validateRepo.tapPromise(this.name, async (context: Context) => {\n await this.validateReleases(context);\n });\n\n bonvoy.hooks.makeRelease.tapPromise(this.name, async (context: ReleaseContext) => {\n if (context.isDryRun) {\n context.logger.info('🔍 [dry-run] Would create GitLab releases');\n return;\n }\n\n const token = this.options.token || process.env.GITLAB_TOKEN;\n if (!token) {\n context.logger.warn('⚠️ GITLAB_TOKEN not found, skipping GitLab releases');\n return;\n }\n\n const host = this.options.host || process.env.GITLAB_HOST || 'https://gitlab.com';\n const projectId = this.getProjectId(context.rootPath);\n\n for (const pkg of context.changedPackages) {\n const version = context.versions[pkg.name];\n const changelog = context.changelogs[pkg.name] || '';\n const tagFormat = this.options.tagFormat ?? '{name}@{version}';\n const tagName = tagFormat.replace('{name}', pkg.name).replace('{version}', version);\n\n try {\n await this.ops.createRelease(token, host, {\n projectId,\n tagName,\n name: `${pkg.name} v${version}`,\n description: changelog,\n });\n\n context.logger.info(`✅ Created GitLab release: ${tagName}`);\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n context.logger.error(`❌ Failed to create release for ${tagName}: ${errorMessage}`);\n throw error;\n }\n }\n });\n\n // MR creation hook\n bonvoy.hooks.createPR.tapPromise(this.name, async (context: PRContext) => {\n if (context.isDryRun) {\n context.logger.info('🔍 [dry-run] Would create GitLab MR');\n return;\n }\n\n const token = this.options.token || process.env.GITLAB_TOKEN;\n if (!token) {\n context.logger.warn('⚠️ GITLAB_TOKEN not found, skipping MR creation');\n return;\n }\n\n const host = this.options.host || process.env.GITLAB_HOST || 'https://gitlab.com';\n const projectId = this.getProjectId(context.rootPath);\n\n try {\n const result = await this.ops.createMR(token, host, {\n projectId,\n title: context.title,\n description: context.body,\n sourceBranch: context.branchName,\n targetBranch: context.baseBranch,\n });\n\n context.prUrl = result.url;\n context.prNumber = result.iid;\n context.logger.info(`✅ Created GitLab MR: ${result.url}`);\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n context.logger.error(`❌ Failed to create MR: ${errorMessage}`);\n throw error;\n }\n });\n }\n\n private async validateReleases(context: Context): Promise<void> {\n const { changedPackages, versions, logger } = context;\n if (!versions) return;\n\n const token = this.options.token || process.env.GITLAB_TOKEN;\n if (!token) return;\n\n const host = this.options.host || process.env.GITLAB_HOST || 'https://gitlab.com';\n let projectId: string | number;\n try {\n projectId = this.getProjectId(context.rootPath);\n } catch {\n return;\n }\n\n const tagFormat = this.options.tagFormat ?? '{name}@{version}';\n const existing: string[] = [];\n\n for (const pkg of changedPackages) {\n const version = versions[pkg.name];\n /* c8 ignore start -- defensive: version always present for changedPackages */\n if (!version) continue;\n /* c8 ignore stop */\n const tag = tagFormat.replace('{name}', pkg.name).replace('{version}', version);\n if (await this.ops.releaseExists(token, host, projectId, tag)) {\n existing.push(tag);\n }\n }\n\n if (existing.length > 0) {\n logger.error(`❌ GitLab releases already exist: ${existing.join(', ')}`);\n throw new Error(\n `Cannot release: GitLab releases already exist (${existing.join(', ')}). Delete them first or bump to a new version.`,\n );\n }\n }\n\n private getProjectId(rootPath: string): string | number {\n if (this.options.projectId) {\n return this.options.projectId;\n }\n\n // Try to read from package.json repository field\n try {\n const pkgPath = join(rootPath, 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n const repoUrl = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url;\n\n if (repoUrl) {\n // Match gitlab.com/group/project or gitlab.com/group/subgroup/project\n const match = repoUrl.match(/gitlab\\.com[:/](.+?)(?:\\.git)?$/);\n if (match) {\n return encodeURIComponent(match[1]);\n }\n }\n } catch {\n // Ignore error\n }\n\n throw new Error(\n 'Could not determine GitLab project. Please set \"repository\" in package.json or provide projectId in plugin options.',\n );\n }\n}\n\nexport {\n defaultGitLabOperations,\n type GitLabMRParams,\n type GitLabMRResult,\n type GitLabOperations,\n type GitLabReleaseParams,\n} from './operations.js';\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonvoy/plugin-gitlab",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "🚢 GitLab releases plugin for bonvoy",
5
5
  "keywords": [
6
6
  "bonvoy",
@@ -35,7 +35,7 @@
35
35
  "test": "vitest"
36
36
  },
37
37
  "dependencies": {
38
- "@bonvoy/core": "^0.1.0",
38
+ "@bonvoy/core": "^0.4.0",
39
39
  "@gitbeaker/rest": "^42.5.0"
40
40
  },
41
41
  "engines": {