chrome-devtools-mcp-for-extension 0.6.2 → 0.6.4

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.
@@ -1,332 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- import fs from 'node:fs';
7
- import path from 'node:path';
8
- import archiver from 'archiver';
9
- import z from 'zod';
10
- import { ToolCategories } from './categories.js';
11
- import { defineTool } from './ToolDefinition.js';
12
- // Main submission tool - now with browser automation!
13
- export const submitToWebStore = defineTool({
14
- name: 'submit_to_webstore',
15
- description: `Automatically submit a Chrome extension to the Web Store using browser automation`,
16
- annotations: {
17
- category: ToolCategories.EXTENSION_DEVELOPMENT,
18
- readOnlyHint: false,
19
- },
20
- schema: {
21
- extensionPath: z.string().describe('Path to the extension directory'),
22
- autoSubmit: z.boolean().optional().default(false).describe('Automatically submit via browser (requires login)'),
23
- },
24
- handler: async (request, response, context) => {
25
- const { extensionPath } = request.params;
26
- const outputPath = path.join(path.dirname(extensionPath), `${path.basename(extensionPath)}-submission.zip`);
27
- response.appendResponseLine('🚀 **Chrome Web Store Submission Process**');
28
- response.appendResponseLine('='.repeat(40));
29
- response.appendResponseLine('');
30
- // Step 1: Validate manifest
31
- response.appendResponseLine('**Step 1: Validating manifest.json...**');
32
- const manifestPath = path.join(extensionPath, 'manifest.json');
33
- let manifest;
34
- let manifestValid = true;
35
- try {
36
- if (!fs.existsSync(manifestPath)) {
37
- response.appendResponseLine('❌ manifest.json not found');
38
- return;
39
- }
40
- const manifestContent = fs.readFileSync(manifestPath, 'utf-8');
41
- manifest = JSON.parse(manifestContent);
42
- // Validation checks
43
- const errors = [];
44
- const warnings = [];
45
- const suggestions = [];
46
- // Required fields
47
- if (manifest.manifest_version !== 3) {
48
- errors.push('Must use Manifest V3 (manifest_version: 3)');
49
- manifestValid = false;
50
- }
51
- if (!manifest.name || manifest.name.length > 45) {
52
- errors.push('Name is required and must be <= 45 characters');
53
- manifestValid = false;
54
- }
55
- if (!manifest.version || !/^\d+\.\d+\.\d+(\.\d+)?$/.test(manifest.version)) {
56
- errors.push('Version must follow format: 1.0.0 or 1.0.0.0');
57
- manifestValid = false;
58
- }
59
- if (!manifest.description || manifest.description.length > 132) {
60
- warnings.push('Description should be provided and <= 132 characters');
61
- }
62
- // Icons
63
- if (!manifest.icons || !manifest.icons['128']) {
64
- warnings.push('Should include 128x128 icon');
65
- }
66
- // Permissions check
67
- const dangerousPermissions = [
68
- 'debugger',
69
- 'devtools',
70
- 'management',
71
- 'privacy',
72
- 'proxy',
73
- 'system.cpu',
74
- 'system.memory',
75
- 'vpnProvider',
76
- ];
77
- const usedDangerousPerms = manifest.permissions?.filter(p => dangerousPermissions.includes(p)) || [];
78
- if (usedDangerousPerms.length > 0) {
79
- warnings.push(`Uses sensitive permissions: ${usedDangerousPerms.join(', ')}`);
80
- }
81
- // Host permissions
82
- if (manifest.host_permissions?.includes('<all_urls>')) {
83
- warnings.push('Using <all_urls> requires strong justification');
84
- }
85
- // Service worker check
86
- if (manifest.background?.service_worker) {
87
- const swPath = path.join(extensionPath, manifest.background.service_worker);
88
- if (!fs.existsSync(swPath)) {
89
- errors.push(`Service worker file not found: ${manifest.background.service_worker}`);
90
- manifestValid = false;
91
- }
92
- }
93
- // Suggestions
94
- if (!manifest.icons?.['16'] || !manifest.icons?.['48']) {
95
- suggestions.push('Consider adding 16x16 and 48x48 icons');
96
- }
97
- // Display results
98
- if (manifestValid) {
99
- response.appendResponseLine('✅ Manifest is valid');
100
- }
101
- else {
102
- response.appendResponseLine('❌ Manifest has errors');
103
- }
104
- if (errors.length > 0) {
105
- response.appendResponseLine('\n**Errors:**');
106
- errors.forEach(err => response.appendResponseLine(`- ❌ ${err}`));
107
- }
108
- if (warnings.length > 0) {
109
- response.appendResponseLine('\n**Warnings:**');
110
- warnings.forEach(warn => response.appendResponseLine(`- ⚠️ ${warn}`));
111
- }
112
- if (suggestions.length > 0) {
113
- response.appendResponseLine('\n**Suggestions:**');
114
- suggestions.forEach(sug => response.appendResponseLine(`- 💡 ${sug}`));
115
- }
116
- }
117
- catch (error) {
118
- response.appendResponseLine(`❌ Failed to parse manifest: ${error}`);
119
- return;
120
- }
121
- if (!manifestValid) {
122
- response.appendResponseLine('');
123
- response.appendResponseLine('❌ **Submission blocked:** Fix manifest errors first');
124
- return;
125
- }
126
- response.appendResponseLine('');
127
- // Step 2: Generate store listing
128
- response.appendResponseLine('**Step 2: Generating store listing...**');
129
- let description = `${manifest.name} is a Chrome extension that ${manifest.description || 'enhances your browsing experience'}.\n\n`;
130
- description += '## Features\n\n';
131
- // Infer features from permissions
132
- if (manifest.permissions?.includes('tabs')) {
133
- description += '• Manage and organize your browser tabs\n';
134
- }
135
- if (manifest.permissions?.includes('storage')) {
136
- description += '• Save your preferences and settings\n';
137
- }
138
- if (manifest.permissions?.includes('notifications')) {
139
- description += '• Receive helpful notifications\n';
140
- }
141
- if (manifest.content_scripts) {
142
- description += '• Enhance website functionality\n';
143
- }
144
- if (manifest.action?.default_popup) {
145
- description += '• Quick access from toolbar\n';
146
- }
147
- // Guess category
148
- const { permissions = [], host_permissions = [] } = manifest;
149
- let category = 'Productivity'; // Default
150
- if (permissions.includes('tabs') || permissions.includes('bookmarks')) {
151
- category = 'Productivity';
152
- }
153
- else if (permissions.includes('downloads')) {
154
- category = 'Tools';
155
- }
156
- else if (host_permissions.some(h => h.includes('youtube') || h.includes('video'))) {
157
- category = 'Entertainment';
158
- }
159
- else if (host_permissions.some(h => h.includes('facebook') || h.includes('twitter'))) {
160
- category = 'Social & Communication';
161
- }
162
- response.appendResponseLine(`**Name:** ${manifest.name}`);
163
- response.appendResponseLine(`**Summary:** ${manifest.description || `${manifest.name} - Chrome Extension`}`);
164
- response.appendResponseLine(`**Category:** ${category}`);
165
- response.appendResponseLine('');
166
- response.appendResponseLine('**Generated Description Preview:**');
167
- response.appendResponseLine(description.substring(0, 200) + '...');
168
- response.appendResponseLine('');
169
- // Step 3: Create submission package
170
- response.appendResponseLine('**Step 3: Creating submission package...**');
171
- try {
172
- await new Promise((resolve, reject) => {
173
- const output = fs.createWriteStream(outputPath);
174
- const archive = archiver('zip', {
175
- zlib: { level: 9 } // Maximum compression
176
- });
177
- output.on('close', () => {
178
- const sizeKB = (archive.pointer() / 1024).toFixed(2);
179
- response.appendResponseLine(`📦 Package created: ${outputPath}`);
180
- response.appendResponseLine(` Size: ${sizeKB} KB`);
181
- resolve();
182
- });
183
- archive.on('error', (err) => {
184
- response.appendResponseLine(`❌ Failed to create package: ${err}`);
185
- reject(err);
186
- });
187
- archive.pipe(output);
188
- // Add extension files, excluding unnecessary ones
189
- archive.glob('**/*', {
190
- cwd: extensionPath,
191
- ignore: [
192
- 'node_modules/**',
193
- '.git/**',
194
- '.gitignore',
195
- '*.map',
196
- '.DS_Store',
197
- 'Thumbs.db',
198
- '*.log',
199
- 'test/**',
200
- 'tests/**',
201
- 'docs/**',
202
- ],
203
- });
204
- archive.finalize();
205
- });
206
- response.appendResponseLine('✅ Package created successfully!');
207
- }
208
- catch (error) {
209
- response.appendResponseLine(`❌ Failed to create package: ${error}`);
210
- return;
211
- }
212
- response.appendResponseLine('');
213
- response.appendResponseLine('='.repeat(40));
214
- response.appendResponseLine('**📋 Final Checklist:**');
215
- response.appendResponseLine('');
216
- response.appendResponseLine('Before submitting to Chrome Web Store:');
217
- response.appendResponseLine('1. ✅ Manifest validated');
218
- response.appendResponseLine('2. ✅ ZIP package created');
219
- response.appendResponseLine('3. ⬜ Add screenshots (1280x800 recommended)');
220
- response.appendResponseLine('4. ⬜ Add promotional images');
221
- response.appendResponseLine('5. ⬜ Write privacy policy (if needed)');
222
- response.appendResponseLine('6. ⬜ Pay $5 developer registration fee (first time only)');
223
- response.appendResponseLine('');
224
- response.appendResponseLine('**Submit at:** https://chrome.google.com/webstore/devconsole');
225
- // Step 4: Auto-submit via browser automation
226
- if (request.params.autoSubmit) {
227
- response.appendResponseLine('');
228
- response.appendResponseLine('='.repeat(40));
229
- response.appendResponseLine('**🤖 Step 4: Automated Browser Submission**');
230
- response.appendResponseLine('');
231
- const page = context.getSelectedPage();
232
- try {
233
- // Navigate to Chrome Web Store Developer Dashboard
234
- response.appendResponseLine('Navigating to Developer Dashboard...');
235
- await page.goto('https://chrome.google.com/webstore/devconsole', {
236
- waitUntil: 'networkidle0',
237
- });
238
- // Check if user is logged in
239
- await new Promise(resolve => setTimeout(resolve, 2000));
240
- const currentUrl = page.url();
241
- if (currentUrl.includes('accounts.google.com')) {
242
- response.appendResponseLine('⚠️ Login required. Please log in manually.');
243
- response.appendResponseLine('After logging in, run this command again.');
244
- return;
245
- }
246
- // Check if this is a new submission or update
247
- response.appendResponseLine('Checking for existing extensions...');
248
- // Look for "Add new item" button
249
- const addNewButton = await page.$('button[aria-label="Add new item"], a[href*="register"]');
250
- if (addNewButton) {
251
- response.appendResponseLine('Creating new extension submission...');
252
- await addNewButton.click();
253
- await page.waitForNavigation({ waitUntil: 'networkidle0' });
254
- }
255
- else {
256
- response.appendResponseLine('❌ Could not find "Add new item" button');
257
- response.appendResponseLine('Please ensure you are on the Developer Dashboard');
258
- return;
259
- }
260
- // Upload the ZIP file
261
- response.appendResponseLine('Uploading extension package...');
262
- const fileInput = await page.$('input[type="file"]');
263
- if (fileInput) {
264
- await fileInput.uploadFile(outputPath);
265
- response.appendResponseLine('✅ Package uploaded');
266
- // Wait for processing
267
- await new Promise(resolve => setTimeout(resolve, 3000));
268
- // Look for any errors
269
- const errorElements = await page.$$('.error-message, [role="alert"]');
270
- if (errorElements.length > 0) {
271
- const errorText = await page.evaluate(() => {
272
- const errors = document.querySelectorAll('.error-message, [role="alert"]');
273
- return Array.from(errors).map(e => e.textContent).join('\n');
274
- });
275
- response.appendResponseLine(`⚠️ Upload errors detected: ${errorText}`);
276
- }
277
- }
278
- else {
279
- response.appendResponseLine('❌ Could not find file upload input');
280
- return;
281
- }
282
- // Fill in store listing information
283
- response.appendResponseLine('Filling in store listing...');
284
- // Title field (usually pre-filled from manifest)
285
- const titleInput = await page.$('input[name="title"], input[aria-label*="title"]');
286
- if (titleInput) {
287
- const currentTitle = await titleInput.evaluate(el => el.value);
288
- if (!currentTitle) {
289
- await titleInput.type(manifest.name);
290
- }
291
- }
292
- // Summary/Short description
293
- const summaryInput = await page.$('textarea[name="summary"], textarea[aria-label*="summary"]');
294
- if (summaryInput) {
295
- await summaryInput.click({ clickCount: 3 }); // Select all
296
- await summaryInput.type(manifest.description || `${manifest.name} - Chrome Extension`);
297
- }
298
- // Detailed description
299
- const descriptionInput = await page.$('textarea[name="description"], textarea[aria-label*="description"]');
300
- if (descriptionInput) {
301
- await descriptionInput.click({ clickCount: 3 });
302
- await descriptionInput.type(description);
303
- }
304
- // Category selection
305
- const categorySelect = await page.$('select[name="category"], select[aria-label*="category"]');
306
- if (categorySelect) {
307
- await categorySelect.select(category.toLowerCase().replace(/\s+/g, '_'));
308
- }
309
- // Language
310
- const languageSelect = await page.$('select[name="language"], select[aria-label*="language"]');
311
- if (languageSelect) {
312
- await languageSelect.select('en');
313
- }
314
- response.appendResponseLine('✅ Store listing filled');
315
- // Screenshots reminder
316
- response.appendResponseLine('');
317
- response.appendResponseLine('⚠️ **Manual steps required:**');
318
- response.appendResponseLine('1. Add at least 1 screenshot (1280x800 or 640x400)');
319
- response.appendResponseLine('2. Add promotional images if needed');
320
- response.appendResponseLine('3. Review all information');
321
- response.appendResponseLine('4. Click "Save draft" or "Submit for review"');
322
- response.appendResponseLine('');
323
- response.appendResponseLine('The browser is now on the submission page.');
324
- response.appendResponseLine('Complete the remaining steps manually.');
325
- }
326
- catch (error) {
327
- response.appendResponseLine(`❌ Automation error: ${error}`);
328
- response.appendResponseLine('You may need to complete the submission manually.');
329
- }
330
- }
331
- },
332
- });