chrome-ext-ci-cd 1.0.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 +198 -0
- package/bin/cli.js +67 -0
- package/lib/activate.js +98 -0
- package/lib/bump.js +299 -0
- package/lib/init.js +232 -0
- package/lib/keygen.js +107 -0
- package/lib/license.js +199 -0
- package/lib/package.js +248 -0
- package/lib/release.js +214 -0
- package/lib/validate.js +215 -0
- package/package.json +37 -0
- package/templates/extensionignore +29 -0
- package/templates/nvmrc +1 -0
- package/templates/release.yml +179 -0
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# chrome-ext-ci-cd
|
|
2
|
+
|
|
3
|
+
CLI toolkit for Chrome extension CI/CD: validate, version, package, and release.
|
|
4
|
+
|
|
5
|
+
Zero runtime dependencies. Works with any Chrome extension project using Manifest V3.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -D chrome-ext-ci-cd
|
|
11
|
+
npx chrome-ext-ci-cd init
|
|
12
|
+
npx chrome-ext-ci-cd release patch
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`init` scaffolds a GitHub Actions workflow, `.extensionignore`, `.nvmrc`, and npm scripts into your project. `release` validates your manifest, bumps the version, packages a `.zip`, commits, tags, and pushes -- triggering CI.
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
### validate
|
|
20
|
+
|
|
21
|
+
Validate `manifest.json` for Chrome Web Store compatibility.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
chrome-ext-ci-cd validate [options]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Options:**
|
|
28
|
+
|
|
29
|
+
| Flag | Description |
|
|
30
|
+
|------|-------------|
|
|
31
|
+
| `--manifest, -m <path>` | Path to manifest.json (default: `manifest.json`) |
|
|
32
|
+
| `--help, -h` | Show help |
|
|
33
|
+
|
|
34
|
+
**Checks:** MV3 compliance, version format, icon file existence, permissions audit (flags dangerous permissions), CSP safety (blocks `unsafe-eval`).
|
|
35
|
+
|
|
36
|
+
**Exit codes:** `0` on success, `1` on validation errors. Warnings do not affect the exit code.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx chrome-ext-ci-cd validate
|
|
40
|
+
npx chrome-ext-ci-cd validate --manifest src/manifest.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### package
|
|
44
|
+
|
|
45
|
+
Package the extension directory into a `.zip` for distribution.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
chrome-ext-ci-cd package [options]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Options:**
|
|
52
|
+
|
|
53
|
+
| Flag | Description |
|
|
54
|
+
|------|-------------|
|
|
55
|
+
| `--source <dir>` | Extension source directory (default: `.`) |
|
|
56
|
+
| `--output <path>` | Output zip file path (default: `dist/extension.zip`) |
|
|
57
|
+
| `--help, -h` | Show help |
|
|
58
|
+
|
|
59
|
+
Respects `.extensionignore` patterns plus built-in default exclusions (node_modules, .git, tests, etc.). Reports file count and size. Warns above 50 MB (Chrome Web Store soft limit).
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx chrome-ext-ci-cd package
|
|
63
|
+
npx chrome-ext-ci-cd package --source . --output dist/my-ext.zip
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### bump
|
|
67
|
+
|
|
68
|
+
Bump the version in `manifest.json`, create a git commit and annotated tag, and push.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
chrome-ext-ci-cd bump <patch|minor|major|x.y.z> [options]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Options:**
|
|
75
|
+
|
|
76
|
+
| Flag | Description |
|
|
77
|
+
|------|-------------|
|
|
78
|
+
| `--dry-run` | Preview version change without making changes |
|
|
79
|
+
| `--manifest, -m <path>` | Path to manifest.json (default: `manifest.json`) |
|
|
80
|
+
| `--help, -h` | Show help |
|
|
81
|
+
|
|
82
|
+
Updates both `manifest.json` and `package.json` (if present). Refuses to run if the working tree has uncommitted changes. `--dry-run` shows what would happen without modifying any files.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npx chrome-ext-ci-cd bump patch
|
|
86
|
+
npx chrome-ext-ci-cd bump minor --dry-run
|
|
87
|
+
npx chrome-ext-ci-cd bump 2.0.0
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### release
|
|
91
|
+
|
|
92
|
+
Run the full release pipeline: validate, bump, and package in sequence.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
chrome-ext-ci-cd release <patch|minor|major> [options]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Options:**
|
|
99
|
+
|
|
100
|
+
| Flag | Description |
|
|
101
|
+
|------|-------------|
|
|
102
|
+
| `--dry-run` | Validate (real), bump (preview), skip package |
|
|
103
|
+
| `--manifest, -m <path>` | Path to manifest.json (default: `manifest.json`) |
|
|
104
|
+
| `--source <dir>` | Extension source directory (default: `.`) |
|
|
105
|
+
| `--output <path>` | Output zip file path (default: `dist/extension.zip`) |
|
|
106
|
+
| `--help, -h` | Show help |
|
|
107
|
+
|
|
108
|
+
Stops on first failure with a descriptive error naming which step failed. `--dry-run` runs validation normally, shows the version bump preview, and skips packaging.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx chrome-ext-ci-cd release patch
|
|
112
|
+
npx chrome-ext-ci-cd release minor --dry-run
|
|
113
|
+
npx chrome-ext-ci-cd release major --source src --output dist/ext.zip
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### init
|
|
117
|
+
|
|
118
|
+
Scaffold CI/CD files into your Chrome extension project.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
chrome-ext-ci-cd init [options]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Options:**
|
|
125
|
+
|
|
126
|
+
| Flag | Description |
|
|
127
|
+
|------|-------------|
|
|
128
|
+
| `--force, -f` | Overwrite existing files |
|
|
129
|
+
| `--help, -h` | Show help |
|
|
130
|
+
|
|
131
|
+
**Creates:**
|
|
132
|
+
- `.github/workflows/release.yml` -- GitHub Actions workflow for CI/CD
|
|
133
|
+
- `.extensionignore` -- Patterns for files to exclude from the zip
|
|
134
|
+
- `.nvmrc` -- Node version file
|
|
135
|
+
|
|
136
|
+
**Injects npm scripts** into `package.json`:
|
|
137
|
+
- `ext:validate` -- `chrome-ext-ci-cd validate`
|
|
138
|
+
- `ext:package` -- `chrome-ext-ci-cd package`
|
|
139
|
+
- `ext:release:patch` -- `chrome-ext-ci-cd release patch`
|
|
140
|
+
- `ext:release:minor` -- `chrome-ext-ci-cd release minor`
|
|
141
|
+
- `ext:release:major` -- `chrome-ext-ci-cd release major`
|
|
142
|
+
|
|
143
|
+
Warns before overwriting existing files. Use `--force` to overwrite.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npx chrome-ext-ci-cd init
|
|
147
|
+
npx chrome-ext-ci-cd init --force
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Manual Setup
|
|
151
|
+
|
|
152
|
+
If you prefer not to use `init`, create these files manually:
|
|
153
|
+
|
|
154
|
+
### 1. GitHub Actions Workflow
|
|
155
|
+
|
|
156
|
+
Create `.github/workflows/release.yml`. The workflow should trigger on version tags (`v*`) and PRs to `main`, with jobs for validation, packaging, GitHub Release creation, and optional Chrome Web Store publishing. See the [template](templates/release.yml) for a complete example.
|
|
157
|
+
|
|
158
|
+
### 2. Extension Ignore
|
|
159
|
+
|
|
160
|
+
Create `.extensionignore` in your project root with patterns for files to exclude from the extension zip:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
.vscode/
|
|
164
|
+
.idea/
|
|
165
|
+
docs/
|
|
166
|
+
*.md
|
|
167
|
+
webpack.config.*
|
|
168
|
+
*.psd
|
|
169
|
+
*.sketch
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Built-in defaults (node_modules, .git, tests, etc.) are always excluded regardless of this file.
|
|
173
|
+
|
|
174
|
+
### 3. npm Scripts
|
|
175
|
+
|
|
176
|
+
Add these scripts to your `package.json`:
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"scripts": {
|
|
181
|
+
"ext:validate": "chrome-ext-ci-cd validate",
|
|
182
|
+
"ext:package": "chrome-ext-ci-cd package",
|
|
183
|
+
"ext:release:patch": "chrome-ext-ci-cd release patch",
|
|
184
|
+
"ext:release:minor": "chrome-ext-ci-cd release minor",
|
|
185
|
+
"ext:release:major": "chrome-ext-ci-cd release major"
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Requirements
|
|
191
|
+
|
|
192
|
+
- **Node.js** 20 or later
|
|
193
|
+
- **git** (for bump and release commands)
|
|
194
|
+
- **zip** (for packaging -- pre-installed on most systems)
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
Proprietary. All rights reserved.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { version } = require('../package.json');
|
|
5
|
+
const license = require('../lib/license');
|
|
6
|
+
|
|
7
|
+
const COMMANDS = {
|
|
8
|
+
validate: { module: '../lib/validate', description: 'Validate manifest.json' },
|
|
9
|
+
package: { module: '../lib/package', description: 'Package extension as .zip' },
|
|
10
|
+
bump: { module: '../lib/bump', description: 'Bump version in manifest.json' },
|
|
11
|
+
release: { module: '../lib/release', description: 'Full release pipeline (validate + bump + package)' },
|
|
12
|
+
init: { module: '../lib/init', description: 'Scaffold CI/CD into your project' },
|
|
13
|
+
activate: { module: '../lib/activate', description: 'Activate a license key' },
|
|
14
|
+
keygen: { module: '../lib/keygen', description: 'Generate a license key (owner-only)' },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function printHelp() {
|
|
18
|
+
console.log(`chrome-ext-ci-cd v${version}`);
|
|
19
|
+
console.log('');
|
|
20
|
+
console.log('Usage: chrome-ext-ci-cd <command> [options]');
|
|
21
|
+
console.log('');
|
|
22
|
+
console.log('Commands:');
|
|
23
|
+
for (const [name, cmd] of Object.entries(COMMANDS)) {
|
|
24
|
+
console.log(` ${name.padEnd(12)}${cmd.description}`);
|
|
25
|
+
}
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log('Options:');
|
|
28
|
+
console.log(' --help, -h Show this help message');
|
|
29
|
+
console.log(' --version, -v Show version number');
|
|
30
|
+
console.log('');
|
|
31
|
+
console.log("Run 'chrome-ext-ci-cd <command> --help' for command-specific help.");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const arg = process.argv[2];
|
|
35
|
+
|
|
36
|
+
if (!arg || arg === '--help' || arg === '-h') {
|
|
37
|
+
printHelp();
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (arg === '--version' || arg === '-v') {
|
|
42
|
+
console.log(version);
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!COMMANDS[arg]) {
|
|
47
|
+
console.error(`Unknown command: ${arg}`);
|
|
48
|
+
console.error('');
|
|
49
|
+
printHelp();
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- License Gate ---
|
|
54
|
+
if (arg !== 'activate' && arg !== 'keygen') {
|
|
55
|
+
var licenseResult = license.checkLicense();
|
|
56
|
+
if (!licenseResult.valid) {
|
|
57
|
+
console.error(licenseResult.message);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
if (licenseResult.trial) {
|
|
61
|
+
console.error('Trial: ' + licenseResult.daysLeft + ' day' + (licenseResult.daysLeft !== 1 ? 's' : '') + ' remaining. Activate a license: chrome-ext-ci-cd activate <key>');
|
|
62
|
+
console.error('');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const cmd = require(COMMANDS[arg].module);
|
|
67
|
+
process.exitCode = cmd.run(process.argv.slice(3));
|
package/lib/activate.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { parseArgs } = require('node:util');
|
|
6
|
+
const license = require('./license');
|
|
7
|
+
|
|
8
|
+
// --- Help ---
|
|
9
|
+
|
|
10
|
+
function printHelp() {
|
|
11
|
+
console.log('Usage: chrome-ext-ci-cd activate <key>');
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log('Activate a license key for this machine.');
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log('Arguments:');
|
|
16
|
+
console.log(' key The license key (starts with CECICD-)');
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log('Options:');
|
|
19
|
+
console.log(' --help, -h Show this help message');
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log('Examples:');
|
|
22
|
+
console.log(' chrome-ext-ci-cd activate CECICD-eyJ0aWV....-a1b2c3d4...');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// --- CLI Runner ---
|
|
26
|
+
|
|
27
|
+
function run(args) {
|
|
28
|
+
var values;
|
|
29
|
+
var positionals;
|
|
30
|
+
try {
|
|
31
|
+
var parsed = parseArgs({
|
|
32
|
+
args: args,
|
|
33
|
+
options: {
|
|
34
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
35
|
+
},
|
|
36
|
+
allowPositionals: true,
|
|
37
|
+
});
|
|
38
|
+
values = parsed.values;
|
|
39
|
+
positionals = parsed.positionals;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (err.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') {
|
|
42
|
+
console.error('Unknown option: ' + err.message);
|
|
43
|
+
console.error('');
|
|
44
|
+
printHelp();
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (values.help) {
|
|
51
|
+
printHelp();
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Get key from positionals
|
|
56
|
+
var key = positionals[0];
|
|
57
|
+
if (!key) {
|
|
58
|
+
console.error('Missing license key. Usage: chrome-ext-ci-cd activate <key>');
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Validate key format and signature
|
|
63
|
+
var result = license.verifyKey(key, license.HMAC_SECRET);
|
|
64
|
+
|
|
65
|
+
if (!result.valid && result.expired) {
|
|
66
|
+
console.error('License key is expired (valid until ' + result.payload.exp + ').');
|
|
67
|
+
console.error('Contact the vendor for a renewal key.');
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!result.valid) {
|
|
72
|
+
console.error('Invalid license key: ' + result.error);
|
|
73
|
+
console.error('');
|
|
74
|
+
console.error('Check the key and try again:');
|
|
75
|
+
console.error(' chrome-ext-ci-cd activate <key>');
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Store the key
|
|
80
|
+
var configDir = license.getConfigDir();
|
|
81
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
82
|
+
var licensePath = path.join(configDir, 'license');
|
|
83
|
+
fs.writeFileSync(licensePath, key + '\n');
|
|
84
|
+
|
|
85
|
+
// Print activation summary
|
|
86
|
+
console.log('License activated successfully.');
|
|
87
|
+
console.log('');
|
|
88
|
+
console.log(' Customer: ' + result.payload.customer);
|
|
89
|
+
console.log(' Tier: ' + result.payload.tier);
|
|
90
|
+
console.log(' Expires: ' + result.payload.exp);
|
|
91
|
+
console.log(' Stored at: ' + licensePath);
|
|
92
|
+
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- Exports ---
|
|
97
|
+
|
|
98
|
+
module.exports = { run: run };
|
package/lib/bump.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { execFileSync } = require('node:child_process');
|
|
6
|
+
const { parseArgs } = require('node:util');
|
|
7
|
+
|
|
8
|
+
// --- Helpers ---
|
|
9
|
+
|
|
10
|
+
function calculateNewVersion(currentVersion, bumpType) {
|
|
11
|
+
if (['patch', 'minor', 'major'].includes(bumpType)) {
|
|
12
|
+
var parts = currentVersion.split('.').map(Number);
|
|
13
|
+
// Pad to 3 parts if needed
|
|
14
|
+
while (parts.length < 3) parts.push(0);
|
|
15
|
+
|
|
16
|
+
switch (bumpType) {
|
|
17
|
+
case 'major':
|
|
18
|
+
parts[0]++;
|
|
19
|
+
parts[1] = 0;
|
|
20
|
+
parts[2] = 0;
|
|
21
|
+
break;
|
|
22
|
+
case 'minor':
|
|
23
|
+
parts[1]++;
|
|
24
|
+
parts[2] = 0;
|
|
25
|
+
break;
|
|
26
|
+
case 'patch':
|
|
27
|
+
parts[2]++;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
return parts.join('.');
|
|
31
|
+
}
|
|
32
|
+
// Explicit version provided
|
|
33
|
+
return bumpType;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function checkDirtyTree() {
|
|
37
|
+
try {
|
|
38
|
+
execFileSync('git', ['diff', '--quiet'], { stdio: 'pipe' });
|
|
39
|
+
execFileSync('git', ['diff', '--cached', '--quiet'], { stdio: 'pipe' });
|
|
40
|
+
return { clean: true };
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return {
|
|
43
|
+
clean: false,
|
|
44
|
+
message: 'Working tree has uncommitted changes. Commit or stash changes first.'
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// --- Pure Bump Function ---
|
|
50
|
+
|
|
51
|
+
function bump(bumpType, options) {
|
|
52
|
+
var opts = options || {};
|
|
53
|
+
|
|
54
|
+
// Validate bumpType
|
|
55
|
+
if (!bumpType) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
errors: ['Bump type required. Use: patch, minor, major, or an explicit version (e.g., 2.1.0)'],
|
|
59
|
+
warnings: [],
|
|
60
|
+
data: {}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Resolve manifest path
|
|
65
|
+
var manifestPath = path.resolve(
|
|
66
|
+
opts.manifestPath || opts.manifest || process.env.MANIFEST_PATH || path.join(process.cwd(), 'manifest.json')
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
var data = { manifestPath: manifestPath };
|
|
70
|
+
|
|
71
|
+
// Check manifest exists
|
|
72
|
+
if (!fs.existsSync(manifestPath)) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
errors: ['Manifest not found: ' + manifestPath + '. Check the file path or use --manifest to specify location.'],
|
|
76
|
+
warnings: [],
|
|
77
|
+
data: data
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Read and parse manifest
|
|
82
|
+
var manifest;
|
|
83
|
+
try {
|
|
84
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
errors: ['Invalid JSON in ' + manifestPath + ': ' + e.message],
|
|
89
|
+
warnings: [],
|
|
90
|
+
data: data
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Get current version
|
|
95
|
+
var currentVersion = manifest.version;
|
|
96
|
+
if (!currentVersion) {
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
errors: ['manifest.json is missing "version" field.'],
|
|
100
|
+
warnings: [],
|
|
101
|
+
data: data
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Calculate new version
|
|
106
|
+
var newVersion = calculateNewVersion(currentVersion, bumpType);
|
|
107
|
+
var tag = 'v' + newVersion;
|
|
108
|
+
|
|
109
|
+
data.previousVersion = currentVersion;
|
|
110
|
+
data.newVersion = newVersion;
|
|
111
|
+
data.tag = tag;
|
|
112
|
+
|
|
113
|
+
// Dry run -- return early without side effects
|
|
114
|
+
if (opts.dryRun) {
|
|
115
|
+
data.dryRun = true;
|
|
116
|
+
return {
|
|
117
|
+
success: true,
|
|
118
|
+
errors: [],
|
|
119
|
+
warnings: [],
|
|
120
|
+
data: data
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check dirty tree
|
|
125
|
+
var treeStatus = checkDirtyTree();
|
|
126
|
+
if (!treeStatus.clean) {
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
errors: [treeStatus.message],
|
|
130
|
+
warnings: [],
|
|
131
|
+
data: data
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Update manifest.json
|
|
136
|
+
manifest.version = newVersion;
|
|
137
|
+
if (manifest.version_name !== undefined) {
|
|
138
|
+
manifest.version_name = newVersion;
|
|
139
|
+
}
|
|
140
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
141
|
+
|
|
142
|
+
// Update package.json in the same directory (D-05: bump always syncs both)
|
|
143
|
+
var packageJsonPath = path.join(path.dirname(manifestPath), 'package.json');
|
|
144
|
+
var updatedPackageJson = false;
|
|
145
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
146
|
+
try {
|
|
147
|
+
var pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
148
|
+
pkg.version = newVersion;
|
|
149
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
150
|
+
updatedPackageJson = true;
|
|
151
|
+
} catch (e) {
|
|
152
|
+
// Skip silently if package.json can't be parsed
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Git operations
|
|
157
|
+
try {
|
|
158
|
+
// Stage manifest.json (always)
|
|
159
|
+
execFileSync('git', ['add', manifestPath], { stdio: 'pipe' });
|
|
160
|
+
|
|
161
|
+
// Stage package.json only if it was updated
|
|
162
|
+
if (updatedPackageJson) {
|
|
163
|
+
execFileSync('git', ['add', packageJsonPath], { stdio: 'pipe' });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Commit
|
|
167
|
+
execFileSync('git', ['commit', '-m', 'chore: bump version to ' + newVersion], { stdio: 'pipe' });
|
|
168
|
+
|
|
169
|
+
// Tag (annotated)
|
|
170
|
+
execFileSync('git', ['tag', '-a', tag, '-m', 'Release ' + tag], { stdio: 'pipe' });
|
|
171
|
+
} catch (e) {
|
|
172
|
+
var errMsg = e.stderr ? e.stderr.toString() : e.message;
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
errors: ['Git operations failed: ' + errMsg + '. Commit and tag were not created.'],
|
|
176
|
+
warnings: [],
|
|
177
|
+
data: data
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Push
|
|
182
|
+
try {
|
|
183
|
+
var branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
184
|
+
encoding: 'utf-8'
|
|
185
|
+
}).trim();
|
|
186
|
+
|
|
187
|
+
execFileSync('git', ['push', 'origin', branch, '--follow-tags'], { stdio: 'inherit' });
|
|
188
|
+
data.branch = branch;
|
|
189
|
+
} catch (e) {
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
errors: ['Push failed. You can push manually: git push origin --follow-tags'],
|
|
193
|
+
warnings: [],
|
|
194
|
+
data: data
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
errors: [],
|
|
201
|
+
warnings: [],
|
|
202
|
+
data: data
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --- Help ---
|
|
207
|
+
|
|
208
|
+
function printHelp() {
|
|
209
|
+
console.log('Usage: chrome-ext-ci-cd bump <patch|minor|major|x.y.z> [options]');
|
|
210
|
+
console.log('');
|
|
211
|
+
console.log('Bump version in manifest.json, commit, tag, and push.');
|
|
212
|
+
console.log('');
|
|
213
|
+
console.log('Arguments:');
|
|
214
|
+
console.log(' patch Increment patch version (1.0.0 -> 1.0.1)');
|
|
215
|
+
console.log(' minor Increment minor version (1.0.0 -> 1.1.0)');
|
|
216
|
+
console.log(' major Increment major version (1.0.0 -> 2.0.0)');
|
|
217
|
+
console.log(' x.y.z Set explicit version');
|
|
218
|
+
console.log('');
|
|
219
|
+
console.log('Options:');
|
|
220
|
+
console.log(' --dry-run Preview version change without making changes');
|
|
221
|
+
console.log(' --manifest, -m Path to manifest.json (default: manifest.json)');
|
|
222
|
+
console.log(' --help, -h Show this help message');
|
|
223
|
+
console.log('');
|
|
224
|
+
console.log('Examples:');
|
|
225
|
+
console.log(' chrome-ext-ci-cd bump patch');
|
|
226
|
+
console.log(' chrome-ext-ci-cd bump minor --dry-run');
|
|
227
|
+
console.log(' chrome-ext-ci-cd bump 2.0.0');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// --- CLI Runner ---
|
|
231
|
+
|
|
232
|
+
function run(args) {
|
|
233
|
+
var values, positionals;
|
|
234
|
+
try {
|
|
235
|
+
var parsed = parseArgs({
|
|
236
|
+
args: args,
|
|
237
|
+
options: {
|
|
238
|
+
'dry-run': { type: 'boolean', default: false },
|
|
239
|
+
manifest: { type: 'string', short: 'm', default: process.env.MANIFEST_PATH || 'manifest.json' },
|
|
240
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
241
|
+
},
|
|
242
|
+
allowPositionals: true,
|
|
243
|
+
});
|
|
244
|
+
values = parsed.values;
|
|
245
|
+
positionals = parsed.positionals;
|
|
246
|
+
} catch (err) {
|
|
247
|
+
if (err.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') {
|
|
248
|
+
console.error('Unknown option: ' + err.message);
|
|
249
|
+
console.error('');
|
|
250
|
+
printHelp();
|
|
251
|
+
return 1;
|
|
252
|
+
}
|
|
253
|
+
throw err;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (values.help) {
|
|
257
|
+
printHelp();
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
var bumpType = positionals[0];
|
|
262
|
+
if (!bumpType) {
|
|
263
|
+
console.error('Bump type required. Usage: chrome-ext-ci-cd bump <patch|minor|major> [--dry-run]');
|
|
264
|
+
return 1;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
var result = bump(bumpType, {
|
|
268
|
+
manifestPath: path.resolve(values.manifest),
|
|
269
|
+
dryRun: values['dry-run']
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Format output
|
|
273
|
+
if (result.data.previousVersion) {
|
|
274
|
+
console.log('Current version: ' + result.data.previousVersion);
|
|
275
|
+
}
|
|
276
|
+
if (result.data.newVersion) {
|
|
277
|
+
console.log('New version: ' + result.data.newVersion);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (result.data.dryRun) {
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log('[DRY RUN] No changes made.');
|
|
283
|
+
} else if (result.success) {
|
|
284
|
+
console.log('');
|
|
285
|
+
console.log('Created commit and tag: ' + result.data.tag);
|
|
286
|
+
if (result.data.branch) {
|
|
287
|
+
console.log('Pushed ' + result.data.branch + ' + ' + result.data.tag + ' to origin.');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (result.errors.length > 0) {
|
|
292
|
+
console.log('');
|
|
293
|
+
result.errors.forEach(function(e) { console.error('ERROR: ' + e); });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return result.success ? 0 : 1;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
module.exports = { bump, run };
|