@bonvoy/plugin-git 0.1.1 → 0.4.0
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.
- package/README.md +76 -0
- package/dist/index.d.mts +6 -1
- package/dist/index.mjs +59 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -5
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @bonvoy/plugin-git 🚢
|
|
2
|
+
|
|
3
|
+
> Git operations plugin for bonvoy
|
|
4
|
+
|
|
5
|
+
Handles git commit, tag, push, and branch operations during the release process.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @bonvoy/plugin-git
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- ✅ Commits version bumps and changelog updates
|
|
16
|
+
- ✅ Creates git tags for each released package
|
|
17
|
+
- ✅ Pushes commits and tags to remote
|
|
18
|
+
- ✅ Branch creation and checkout for PR workflow
|
|
19
|
+
- ✅ Configurable commit message and tag format
|
|
20
|
+
- ✅ Dry-run support
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
// bonvoy.config.js
|
|
26
|
+
export default {
|
|
27
|
+
git: {
|
|
28
|
+
commitMessage: 'chore(release): :bookmark: {packages} [skip ci]', // default
|
|
29
|
+
tagFormat: '{name}@{version}', // default
|
|
30
|
+
push: true, // default
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Placeholders
|
|
36
|
+
|
|
37
|
+
| Placeholder | Description |
|
|
38
|
+
|-------------|-------------|
|
|
39
|
+
| `{packages}` | Comma-separated list of released package names |
|
|
40
|
+
| `{name}` | Package name (for tag format) |
|
|
41
|
+
| `{version}` | Package version (for tag format) |
|
|
42
|
+
|
|
43
|
+
## Hooks
|
|
44
|
+
|
|
45
|
+
This plugin taps into the following hooks:
|
|
46
|
+
|
|
47
|
+
| Hook | Action |
|
|
48
|
+
|------|--------|
|
|
49
|
+
| `beforePublish` | Commits changes, creates tags, pushes to remote |
|
|
50
|
+
|
|
51
|
+
## Operations
|
|
52
|
+
|
|
53
|
+
The plugin provides these git operations:
|
|
54
|
+
|
|
55
|
+
- `add(files, cwd)` - Stage files
|
|
56
|
+
- `commit(message, cwd)` - Create commit
|
|
57
|
+
- `tag(name, cwd)` - Create tag
|
|
58
|
+
- `push(cwd, branch?)` - Push to remote
|
|
59
|
+
- `pushTags(tags, cwd)` - Push tags
|
|
60
|
+
- `checkout(branch, cwd, create?)` - Checkout or create branch
|
|
61
|
+
- `getCurrentBranch(cwd)` - Get current branch name
|
|
62
|
+
- `getLastTag(cwd)` - Get most recent tag
|
|
63
|
+
- `getCommitsSinceTag(tag, cwd)` - Get commits since tag
|
|
64
|
+
|
|
65
|
+
## Default Behavior
|
|
66
|
+
|
|
67
|
+
This plugin is loaded automatically by bonvoy. During `beforePublish`:
|
|
68
|
+
|
|
69
|
+
1. Stage all changes (`git add .`)
|
|
70
|
+
2. Commit with the configured message
|
|
71
|
+
3. Create tags for each released package
|
|
72
|
+
4. Push commits and tags to the remote
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -5,8 +5,11 @@ interface GitOperations {
|
|
|
5
5
|
add(files: string, cwd: string): Promise<void>;
|
|
6
6
|
commit(message: string, cwd: string): Promise<void>;
|
|
7
7
|
tag(name: string, cwd: string): Promise<void>;
|
|
8
|
-
push(cwd: string): Promise<void>;
|
|
8
|
+
push(cwd: string, branch?: string): Promise<void>;
|
|
9
9
|
pushTags(tags: string[], cwd: string): Promise<void>;
|
|
10
|
+
checkout(branch: string, cwd: string, create?: boolean): Promise<void>;
|
|
11
|
+
getCurrentBranch(cwd: string): Promise<string>;
|
|
12
|
+
tagExists(name: string, cwd: string): Promise<boolean>;
|
|
10
13
|
getCommitsSinceTag(tag: string | null, cwd: string): Promise<Array<{
|
|
11
14
|
hash: string;
|
|
12
15
|
message: string;
|
|
@@ -31,12 +34,14 @@ declare class GitPlugin implements BonvoyPlugin {
|
|
|
31
34
|
constructor(config?: GitPluginConfig, ops?: GitOperations);
|
|
32
35
|
apply(bonvoy: {
|
|
33
36
|
hooks: {
|
|
37
|
+
validateRepo: any;
|
|
34
38
|
beforePublish: any;
|
|
35
39
|
};
|
|
36
40
|
}): void;
|
|
37
41
|
private commitChanges;
|
|
38
42
|
private createTags;
|
|
39
43
|
private pushChanges;
|
|
44
|
+
private validateTags;
|
|
40
45
|
}
|
|
41
46
|
//#endregion
|
|
42
47
|
export { type GitOperations, type GitPluginConfig, GitPlugin as default, defaultGitOperations };
|
package/dist/index.mjs
CHANGED
|
@@ -15,8 +15,14 @@ const defaultGitOperations = {
|
|
|
15
15
|
async tag(name, cwd) {
|
|
16
16
|
await execa("git", ["tag", name], { cwd });
|
|
17
17
|
},
|
|
18
|
-
async push(cwd) {
|
|
19
|
-
await execa("git", [
|
|
18
|
+
async push(cwd, branch) {
|
|
19
|
+
if (branch) await execa("git", [
|
|
20
|
+
"push",
|
|
21
|
+
"-u",
|
|
22
|
+
"origin",
|
|
23
|
+
branch
|
|
24
|
+
], { cwd });
|
|
25
|
+
else await execa("git", ["push"], { cwd });
|
|
20
26
|
},
|
|
21
27
|
async pushTags(tags, cwd) {
|
|
22
28
|
await execa("git", [
|
|
@@ -25,6 +31,29 @@ const defaultGitOperations = {
|
|
|
25
31
|
...tags
|
|
26
32
|
], { cwd });
|
|
27
33
|
},
|
|
34
|
+
async checkout(branch, cwd, create = false) {
|
|
35
|
+
await execa("git", create ? [
|
|
36
|
+
"checkout",
|
|
37
|
+
"-b",
|
|
38
|
+
branch
|
|
39
|
+
] : ["checkout", branch], { cwd });
|
|
40
|
+
},
|
|
41
|
+
async getCurrentBranch(cwd) {
|
|
42
|
+
const { stdout } = await execa("git", [
|
|
43
|
+
"rev-parse",
|
|
44
|
+
"--abbrev-ref",
|
|
45
|
+
"HEAD"
|
|
46
|
+
], { cwd });
|
|
47
|
+
return stdout.trim();
|
|
48
|
+
},
|
|
49
|
+
async tagExists(name, cwd) {
|
|
50
|
+
try {
|
|
51
|
+
await execa("git", ["rev-parse", `refs/tags/${name}`], { cwd });
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
28
57
|
async getLastTag(cwd) {
|
|
29
58
|
try {
|
|
30
59
|
const { stdout } = await execa("git", [
|
|
@@ -70,52 +99,70 @@ var GitPlugin = class {
|
|
|
70
99
|
ops;
|
|
71
100
|
constructor(config = {}, ops) {
|
|
72
101
|
this.config = {
|
|
73
|
-
commitMessage: config.commitMessage ?? "chore
|
|
102
|
+
commitMessage: config.commitMessage ?? "chore: release {packages}",
|
|
74
103
|
tagFormat: config.tagFormat ?? "{name}@{version}",
|
|
75
104
|
push: config.push ?? true
|
|
76
105
|
};
|
|
77
106
|
this.ops = ops ?? defaultGitOperations;
|
|
78
107
|
}
|
|
79
108
|
apply(bonvoy) {
|
|
109
|
+
bonvoy.hooks.validateRepo.tapPromise(this.name, async (context) => {
|
|
110
|
+
await this.validateTags(context);
|
|
111
|
+
});
|
|
80
112
|
bonvoy.hooks.beforePublish.tapPromise(this.name, async (context) => {
|
|
81
|
-
|
|
113
|
+
context.logger.info("📝 Committing changes...");
|
|
82
114
|
await this.commitChanges(context);
|
|
83
|
-
|
|
115
|
+
context.logger.info("🏷️ Creating git tags...");
|
|
84
116
|
await this.createTags(context);
|
|
85
117
|
if (this.config.push) {
|
|
86
|
-
|
|
118
|
+
context.logger.info("⬆️ Pushing to remote...");
|
|
87
119
|
await this.pushChanges(context);
|
|
88
120
|
}
|
|
89
121
|
});
|
|
90
122
|
}
|
|
91
123
|
async commitChanges(context) {
|
|
92
|
-
const { packages, isDryRun, rootPath } = context;
|
|
124
|
+
const { packages, isDryRun, rootPath, logger } = context;
|
|
93
125
|
if (packages.length === 0) return;
|
|
94
126
|
const packageNames = packages.map((pkg) => pkg.name).join(", ");
|
|
95
127
|
const message = this.config.commitMessage.replace("{packages}", packageNames);
|
|
96
|
-
|
|
128
|
+
logger.info(` Commit message: "${message}"`);
|
|
97
129
|
if (!isDryRun) {
|
|
98
130
|
await this.ops.add(".", rootPath);
|
|
99
131
|
await this.ops.commit(message, rootPath);
|
|
100
132
|
}
|
|
101
133
|
}
|
|
102
134
|
async createTags(context) {
|
|
103
|
-
const { packages, isDryRun, rootPath } = context;
|
|
135
|
+
const { packages, isDryRun, rootPath, logger } = context;
|
|
104
136
|
for (const pkg of packages) {
|
|
105
137
|
const tag = this.config.tagFormat.replace("{name}", pkg.name).replace("{version}", pkg.version);
|
|
106
|
-
|
|
138
|
+
logger.info(` Tag: ${tag}`);
|
|
107
139
|
if (!isDryRun) await this.ops.tag(tag, rootPath);
|
|
108
140
|
}
|
|
109
141
|
}
|
|
110
142
|
async pushChanges(context) {
|
|
111
|
-
const { packages, isDryRun, rootPath } = context;
|
|
112
|
-
|
|
143
|
+
const { packages, isDryRun, rootPath, logger } = context;
|
|
144
|
+
logger.info(" Pushing commits and tags...");
|
|
113
145
|
if (!isDryRun) {
|
|
114
146
|
await this.ops.push(rootPath);
|
|
115
147
|
const tags = packages.map((pkg) => this.config.tagFormat.replace("{name}", pkg.name).replace("{version}", pkg.version));
|
|
116
148
|
await this.ops.pushTags(tags, rootPath);
|
|
117
149
|
}
|
|
118
150
|
}
|
|
151
|
+
async validateTags(context) {
|
|
152
|
+
const { changedPackages, versions, rootPath, logger } = context;
|
|
153
|
+
if (!versions) return;
|
|
154
|
+
const existingTags = [];
|
|
155
|
+
for (const pkg of changedPackages) {
|
|
156
|
+
const version = versions[pkg.name];
|
|
157
|
+
if (!version) continue;
|
|
158
|
+
const tag = this.config.tagFormat.replace("{name}", pkg.name).replace("{version}", version);
|
|
159
|
+
if (await this.ops.tagExists(tag, rootPath)) existingTags.push(tag);
|
|
160
|
+
}
|
|
161
|
+
if (existingTags.length > 0) {
|
|
162
|
+
logger.error(`❌ Git tags already exist: ${existingTags.join(", ")}`);
|
|
163
|
+
throw new Error(`Cannot release: git tags already exist (${existingTags.join(", ")}). Delete them first or bump to a new version.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
119
166
|
};
|
|
120
167
|
|
|
121
168
|
//#endregion
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/operations.ts","../src/git.ts"],"sourcesContent":["import { execa } from 'execa';\n\nexport interface GitOperations {\n add(files: string, cwd: string): Promise<void>;\n commit(message: string, cwd: string): Promise<void>;\n tag(name: string, cwd: string): Promise<void>;\n push(cwd: string): Promise<void>;\n pushTags(tags: string[], cwd: string): Promise<void>;\n getCommitsSinceTag(\n tag: string | null,\n cwd: string,\n ): Promise<\n Array<{ hash: string; message: string; author: string; date: string; files: string[] }>\n >;\n getLastTag(cwd: string): Promise<string | null>;\n}\n\nexport const defaultGitOperations: GitOperations = {\n async add(files, cwd) {\n await execa('git', ['add', files], { cwd });\n },\n\n async commit(message, cwd) {\n await execa('git', ['commit', '-m', message], { cwd });\n },\n\n async tag(name, cwd) {\n await execa('git', ['tag', name], { cwd });\n },\n\n async push(cwd) {\n await execa('git', ['push'], { cwd });\n },\n\n async pushTags(tags, cwd) {\n await execa('git', ['push', 'origin', ...tags], { cwd });\n },\n\n async getLastTag(cwd) {\n try {\n const { stdout } = await execa('git', ['describe', '--tags', '--abbrev=0'], { cwd });\n return stdout.trim() || null;\n } catch {\n return null;\n }\n },\n\n async getCommitsSinceTag(tag, cwd) {\n const range = tag ? `${tag}..HEAD` : 'HEAD';\n const { stdout } = await execa(\n 'git',\n ['log', '--pretty=format:%H|%s|%an|%aI', '--name-only', range],\n { cwd },\n );\n\n if (!stdout.trim()) return [];\n\n const commits: Array<{\n hash: string;\n message: string;\n author: string;\n date: string;\n files: string[];\n }> = [];\n const entries = stdout.split('\\n\\n');\n\n for (const entry of entries) {\n const lines = entry.split('\\n');\n const [firstLine, ...fileLines] = lines;\n const [hash, message, author, date] = firstLine.split('|');\n if (hash && message) {\n commits.push({\n hash,\n message,\n author: author || '',\n date: date || '',\n files: fileLines.filter((f) => f.trim()),\n });\n }\n }\n\n return commits;\n },\n};\n","import type { BonvoyPlugin, PublishContext } from '@bonvoy/core';\n\nimport { defaultGitOperations, type GitOperations } from './operations.js';\n\nexport interface GitPluginConfig {\n commitMessage?: string;\n tagFormat?: string;\n push?: boolean;\n}\n\nexport default class GitPlugin implements BonvoyPlugin {\n name = 'git';\n\n private config: Required<GitPluginConfig>;\n private ops: GitOperations;\n\n constructor(config: GitPluginConfig = {}, ops?: GitOperations) {\n this.config = {\n commitMessage: config.commitMessage ?? 'chore(release): :bookmark: {packages} [skip ci]',\n tagFormat: config.tagFormat ?? '{name}@{version}',\n push: config.push ?? true,\n };\n this.ops = ops ?? defaultGitOperations;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Hook types are complex and vary by implementation\n apply(bonvoy: { hooks: { beforePublish: any } }): void {\n bonvoy.hooks.beforePublish.tapPromise(this.name, async (context: PublishContext) => {\n console.log('📝 Committing changes...');\n await this.commitChanges(context);\n console.log('🏷️ Creating git tags...');\n await this.createTags(context);\n\n if (this.config.push) {\n console.log('⬆️ Pushing to remote...');\n await this.pushChanges(context);\n }\n });\n }\n\n private async commitChanges(context: PublishContext): Promise<void> {\n const { packages, isDryRun, rootPath } = context;\n\n if (packages.length === 0) return;\n\n const packageNames = packages.map((pkg) => pkg.name).join(', ');\n const message = this.config.commitMessage.replace('{packages}', packageNames);\n\n console.log(` Commit message: \"${message}\"`);\n\n if (!isDryRun) {\n await this.ops.add('.', rootPath);\n await this.ops.commit(message, rootPath);\n }\n }\n\n private async createTags(context: PublishContext): Promise<void> {\n const { packages, isDryRun, rootPath } = context;\n\n for (const pkg of packages) {\n const tag = this.config.tagFormat\n .replace('{name}', pkg.name)\n .replace('{version}', pkg.version);\n\n console.log(` Tag: ${tag}`);\n\n if (!isDryRun) {\n await this.ops.tag(tag, rootPath);\n }\n }\n }\n\n private async pushChanges(context: PublishContext): Promise<void> {\n const { packages, isDryRun, rootPath } = context;\n\n console.log(' Pushing commits and tags...');\n\n if (!isDryRun) {\n await this.ops.push(rootPath);\n\n const tags = packages.map((pkg) =>\n this.config.tagFormat.replace('{name}', pkg.name).replace('{version}', pkg.version),\n );\n await this.ops.pushTags(tags, rootPath);\n }\n }\n}\n\nexport { defaultGitOperations, type GitOperations } from './operations.js';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/operations.ts","../src/git.ts"],"sourcesContent":["import { execa } from 'execa';\n\nexport interface GitOperations {\n add(files: string, cwd: string): Promise<void>;\n commit(message: string, cwd: string): Promise<void>;\n tag(name: string, cwd: string): Promise<void>;\n push(cwd: string, branch?: string): Promise<void>;\n pushTags(tags: string[], cwd: string): Promise<void>;\n checkout(branch: string, cwd: string, create?: boolean): Promise<void>;\n getCurrentBranch(cwd: string): Promise<string>;\n tagExists(name: string, cwd: string): Promise<boolean>;\n getCommitsSinceTag(\n tag: string | null,\n cwd: string,\n ): Promise<\n Array<{ hash: string; message: string; author: string; date: string; files: string[] }>\n >;\n getLastTag(cwd: string): Promise<string | null>;\n}\n\nexport const defaultGitOperations: GitOperations = {\n async add(files, cwd) {\n await execa('git', ['add', files], { cwd });\n },\n\n async commit(message, cwd) {\n await execa('git', ['commit', '-m', message], { cwd });\n },\n\n async tag(name, cwd) {\n await execa('git', ['tag', name], { cwd });\n },\n\n /* c8 ignore start - real git operations */\n async push(cwd, branch?) {\n if (branch) {\n await execa('git', ['push', '-u', 'origin', branch], { cwd });\n } else {\n await execa('git', ['push'], { cwd });\n }\n },\n\n async pushTags(tags, cwd) {\n await execa('git', ['push', 'origin', ...tags], { cwd });\n },\n\n async checkout(branch, cwd, create = false) {\n const args = create ? ['checkout', '-b', branch] : ['checkout', branch];\n await execa('git', args, { cwd });\n },\n\n async getCurrentBranch(cwd) {\n const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });\n return stdout.trim();\n },\n\n async tagExists(name, cwd) {\n try {\n await execa('git', ['rev-parse', `refs/tags/${name}`], { cwd });\n return true;\n } catch {\n return false;\n }\n },\n /* c8 ignore stop */\n\n async getLastTag(cwd) {\n try {\n const { stdout } = await execa('git', ['describe', '--tags', '--abbrev=0'], { cwd });\n return stdout.trim() || null;\n } catch {\n return null;\n }\n },\n\n async getCommitsSinceTag(tag, cwd) {\n const range = tag ? `${tag}..HEAD` : 'HEAD';\n const { stdout } = await execa(\n 'git',\n ['log', '--pretty=format:%H|%s|%an|%aI', '--name-only', range],\n { cwd },\n );\n\n if (!stdout.trim()) return [];\n\n const commits: Array<{\n hash: string;\n message: string;\n author: string;\n date: string;\n files: string[];\n }> = [];\n const entries = stdout.split('\\n\\n');\n\n for (const entry of entries) {\n const lines = entry.split('\\n');\n const [firstLine, ...fileLines] = lines;\n const [hash, message, author, date] = firstLine.split('|');\n if (hash && message) {\n commits.push({\n hash,\n message,\n author: author || '',\n date: date || '',\n files: fileLines.filter((f) => f.trim()),\n });\n }\n }\n\n return commits;\n },\n};\n","import type { BonvoyPlugin, Context, PublishContext } from '@bonvoy/core';\n\nimport { defaultGitOperations, type GitOperations } from './operations.js';\n\nexport interface GitPluginConfig {\n commitMessage?: string;\n tagFormat?: string;\n push?: boolean;\n}\n\nexport default class GitPlugin implements BonvoyPlugin {\n name = 'git';\n\n private config: Required<GitPluginConfig>;\n private ops: GitOperations;\n\n constructor(config: GitPluginConfig = {}, ops?: GitOperations) {\n this.config = {\n commitMessage: config.commitMessage ?? 'chore: release {packages}',\n tagFormat: config.tagFormat ?? '{name}@{version}',\n push: config.push ?? true,\n };\n this.ops = ops ?? defaultGitOperations;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Hook types are complex and vary by implementation\n apply(bonvoy: { hooks: { validateRepo: any; beforePublish: any } }): void {\n bonvoy.hooks.validateRepo.tapPromise(this.name, async (context: Context) => {\n await this.validateTags(context);\n });\n\n bonvoy.hooks.beforePublish.tapPromise(this.name, async (context: PublishContext) => {\n context.logger.info('📝 Committing changes...');\n await this.commitChanges(context);\n context.logger.info('🏷️ Creating git tags...');\n await this.createTags(context);\n\n if (this.config.push) {\n context.logger.info('⬆️ Pushing to remote...');\n await this.pushChanges(context);\n }\n });\n }\n\n private async commitChanges(context: PublishContext): Promise<void> {\n const { packages, isDryRun, rootPath, logger } = context;\n\n if (packages.length === 0) return;\n\n const packageNames = packages.map((pkg) => pkg.name).join(', ');\n const message = this.config.commitMessage.replace('{packages}', packageNames);\n\n logger.info(` Commit message: \"${message}\"`);\n\n if (!isDryRun) {\n await this.ops.add('.', rootPath);\n await this.ops.commit(message, rootPath);\n }\n }\n\n private async createTags(context: PublishContext): Promise<void> {\n const { packages, isDryRun, rootPath, logger } = context;\n\n for (const pkg of packages) {\n const tag = this.config.tagFormat\n .replace('{name}', pkg.name)\n .replace('{version}', pkg.version);\n\n logger.info(` Tag: ${tag}`);\n\n if (!isDryRun) {\n await this.ops.tag(tag, rootPath);\n }\n }\n }\n\n private async pushChanges(context: PublishContext): Promise<void> {\n const { packages, isDryRun, rootPath, logger } = context;\n\n logger.info(' Pushing commits and tags...');\n\n if (!isDryRun) {\n await this.ops.push(rootPath);\n\n const tags = packages.map((pkg) =>\n this.config.tagFormat.replace('{name}', pkg.name).replace('{version}', pkg.version),\n );\n await this.ops.pushTags(tags, rootPath);\n }\n }\n\n private async validateTags(context: Context): Promise<void> {\n const { changedPackages, versions, rootPath, logger } = context;\n if (!versions) return;\n\n const existingTags: string[] = [];\n\n for (const pkg of changedPackages) {\n const version = versions[pkg.name];\n if (!version) continue;\n\n const tag = this.config.tagFormat.replace('{name}', pkg.name).replace('{version}', version);\n\n if (await this.ops.tagExists(tag, rootPath)) {\n existingTags.push(tag);\n }\n }\n\n if (existingTags.length > 0) {\n logger.error(`❌ Git tags already exist: ${existingTags.join(', ')}`);\n throw new Error(\n `Cannot release: git tags already exist (${existingTags.join(', ')}). Delete them first or bump to a new version.`,\n );\n }\n }\n}\n\nexport { defaultGitOperations, type GitOperations } from './operations.js';\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bonvoy/plugin-git",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Git operations plugin for bonvoy",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "🚢 Git operations plugin for bonvoy",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bonvoy",
|
|
7
7
|
"plugin",
|
|
@@ -37,9 +37,6 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"execa": "^9.6.1"
|
|
39
39
|
},
|
|
40
|
-
"devDependencies": {
|
|
41
|
-
"vitest": "^4.0.16"
|
|
42
|
-
},
|
|
43
40
|
"engines": {
|
|
44
41
|
"node": ">= 20.5"
|
|
45
42
|
}
|