create-vextro 0.0.3 → 0.1.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.
Files changed (36) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +94 -24
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +514 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +71 -41
  7. package/src/templates/chrome/src/background/background.ts +30 -0
  8. package/src/templates/chrome/src/content/content.ts +13 -0
  9. package/src/templates/chrome/src/manifest.ts +28 -0
  10. package/{extension-structure → src/templates/chrome}/vite.config.ts +27 -28
  11. package/src/templates/firefox/src/background/background.ts +29 -0
  12. package/src/templates/firefox/src/content/content.ts +13 -0
  13. package/src/templates/firefox/src/manifest.json +35 -0
  14. package/src/templates/firefox/vite.config.ts +22 -0
  15. package/src/templates/shared/src/options/App.tsx +70 -0
  16. package/{extension-structure → src/templates/shared}/src/options/index.tsx +9 -9
  17. package/src/templates/shared/src/options/options.html +12 -0
  18. package/src/templates/shared/src/popup/App.tsx +61 -0
  19. package/{extension-structure → src/templates/shared}/src/popup/index.tsx +9 -9
  20. package/src/templates/shared/src/popup/popup.html +12 -0
  21. package/src/templates/shared/src/styles.css +1 -0
  22. package/src/templates/shared/src/utils/storage.ts +34 -0
  23. package/bin/cli.js +0 -124
  24. package/extension-structure/src/background/background.ts +0 -2
  25. package/extension-structure/src/content/content.ts +0 -2
  26. package/extension-structure/src/manifest.ts +0 -28
  27. package/extension-structure/src/options/App.tsx +0 -49
  28. package/extension-structure/src/options/options.html +0 -12
  29. package/extension-structure/src/popup/App.tsx +0 -72
  30. package/extension-structure/src/popup/popup.html +0 -12
  31. package/extension-structure/src/styles.css +0 -1
  32. package/extension-structure/src/utils/apiClient.ts +0 -5
  33. /package/{extension-structure → src/templates/shared}/public/icon.png +0 -0
  34. /package/{extension-structure → src/templates/shared}/public/icons/icon128.png +0 -0
  35. /package/{extension-structure → src/templates/shared}/public/icons/icon16.png +0 -0
  36. /package/{extension-structure → src/templates/shared}/public/icons/icon48.png +0 -0
package/package.json CHANGED
@@ -1,41 +1,71 @@
1
- {
2
- "name": "create-vextro",
3
- "version": "0.0.3",
4
- "description": "Scaffold modern Chrome extensions with Vite + React + Tailwind",
5
- "bin": {
6
- "create-vextro": "bin/cli.js"
7
- },
8
- "type": "module",
9
- "files": [
10
- "bin",
11
- "extension-structure/"
12
- ],
13
- "keywords": [
14
- "vite",
15
- "chrome-extension",
16
- "vite-plugin",
17
- "react",
18
- "tailwind",
19
- "crxjs",
20
- "manifest-v3",
21
- "scaffold"
22
- ],
23
- "repository": {
24
- "type": "git",
25
- "url": "https://github.com/lasalasa/vextro"
26
- },
27
- "author": "Lasantha <lasanthaslakmal@gmail.com>",
28
- "license": "MIT",
29
- "bugs": {
30
- "url": "https://github.com/lasalasa/vextro/issues"
31
- },
32
- "homepage": "https://github.com/lasalasa/vextro#readme",
33
- "dependencies": {
34
- "chalk": "^5.3.0",
35
- "fs-extra": "^11.2.0",
36
- "inquirer": "^9.2.8"
37
- },
38
- "engines": {
39
- "node": ">=16"
40
- }
41
- }
1
+ {
2
+ "name": "create-vextro",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold modern browser extensions (Chrome, Edge, Firefox) with Vite + React + Tailwind",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-vextro": "./dist/index.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=20.0.0"
11
+ },
12
+ "scripts": {
13
+ "build": "tsup",
14
+ "dev": "tsup --watch",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest",
17
+ "lint": "eslint src/",
18
+ "format": "prettier --write .",
19
+ "format:check": "prettier --check .",
20
+ "prepublishOnly": "npm run build && npm test",
21
+ "typecheck": "tsc --noEmit"
22
+ },
23
+ "dependencies": {
24
+ "chalk": "^5.3.0",
25
+ "commander": "^12.1.0",
26
+ "fs-extra": "^11.2.0",
27
+ "inquirer": "^9.2.8",
28
+ "ora": "^8.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@changesets/cli": "^2.27.0",
32
+ "@types/fs-extra": "^11.0.4",
33
+ "@types/inquirer": "^9.0.7",
34
+ "@types/node": "^20.0.0",
35
+ "eslint": "^9.0.0",
36
+ "prettier": "^3.3.0",
37
+ "tsup": "^8.0.0",
38
+ "typescript": "^5.5.0",
39
+ "typescript-eslint": "^8.0.0",
40
+ "vitest": "^2.0.0"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "src/templates"
45
+ ],
46
+ "keywords": [
47
+ "vite",
48
+ "chrome-extension",
49
+ "firefox-extension",
50
+ "browser-extension",
51
+ "vite-plugin",
52
+ "react",
53
+ "tailwind",
54
+ "crxjs",
55
+ "manifest-v3",
56
+ "scaffold",
57
+ "cli",
58
+ "generator",
59
+ "starter-kit"
60
+ ],
61
+ "repository": {
62
+ "type": "git",
63
+ "url": "https://github.com/CodeCanvasCollective/vextro.git"
64
+ },
65
+ "author": "Lasantha <lasanthaslakmal@gmail.com>",
66
+ "license": "MIT",
67
+ "bugs": {
68
+ "url": "https://github.com/CodeCanvasCollective/vextro/issues"
69
+ },
70
+ "homepage": "https://github.com/CodeCanvasCollective/vextro#readme"
71
+ }
@@ -0,0 +1,30 @@
1
+ // Background service worker
2
+ // This runs in the background and handles events for your extension.
3
+
4
+ // Runs when the extension is first installed or updated
5
+ chrome.runtime.onInstalled.addListener((details) => {
6
+ console.log('[Vextro] Extension installed:', details.reason);
7
+
8
+ // Set default storage values on install
9
+ if (details.reason === 'install') {
10
+ chrome.storage.sync.set({
11
+ greeting: 'Hello from Vextro!',
12
+ clickCount: 0,
13
+ });
14
+ }
15
+ });
16
+
17
+ // Listen for messages from popup, options, or content scripts
18
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
19
+ console.log('[Vextro] Message received:', message, 'from:', sender.tab?.url);
20
+
21
+ if (message.type === 'GET_TAB_INFO') {
22
+ // Example: respond with the current active tab info
23
+ chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
24
+ sendResponse({ tab: tabs[0] ?? null });
25
+ });
26
+ return true; // Keep the message channel open for async response
27
+ }
28
+
29
+ sendResponse({ status: 'unknown message type' });
30
+ });
@@ -0,0 +1,13 @@
1
+ // Content script
2
+ // This runs in the context of web pages matched by manifest.ts content_scripts.
3
+
4
+ console.log('[Vextro] Content script loaded on:', window.location.href);
5
+
6
+ // Example: send a message to the background service worker
7
+ chrome.runtime.sendMessage({ type: 'GET_TAB_INFO' }, (response) => {
8
+ if (chrome.runtime.lastError) {
9
+ console.warn('[Vextro] Message error:', chrome.runtime.lastError.message);
10
+ return;
11
+ }
12
+ console.log('[Vextro] Tab info received:', response);
13
+ });
@@ -0,0 +1,28 @@
1
+ import { defineManifest } from '@crxjs/vite-plugin';
2
+
3
+ export default defineManifest({
4
+ manifest_version: 3,
5
+ name: '__EXT_NAME__',
6
+ description: 'A modern Chrome extension built with Vextro.',
7
+ version: '1.0.0',
8
+ icons: {
9
+ '16': 'icons/icon16.png',
10
+ '48': 'icons/icon48.png',
11
+ '128': 'icons/icon128.png',
12
+ },
13
+ permissions: ['storage', 'tabs', 'scripting'],
14
+ action: {
15
+ default_popup: 'src/popup/popup.html',
16
+ },
17
+ background: {
18
+ service_worker: 'src/background/background.ts',
19
+ type: 'module',
20
+ },
21
+ content_scripts: [
22
+ {
23
+ matches: ['<all_urls>'],
24
+ js: ['src/content/content.ts'],
25
+ },
26
+ ],
27
+ options_page: 'src/options/options.html',
28
+ });
@@ -1,28 +1,27 @@
1
- import path from 'path';
2
- import { defineConfig } from 'vite';
3
- import react from '@vitejs/plugin-react';
4
- import { crx } from '@crxjs/vite-plugin';
5
- import manifest from './src/manifest';
6
- import tailwindcss from '@tailwindcss/vite'
7
-
8
- export default defineConfig({
9
- plugins: [
10
- react(),
11
- crx({ manifest }),
12
- tailwindcss()
13
- ],
14
- build: {
15
- outDir: 'dist',
16
- rollupOptions: {
17
- input: {
18
- popup: path.resolve(__dirname, 'src/popup/popup.html'),
19
- options: path.resolve(__dirname, 'src/options/options.html')
20
- },
21
- output: {
22
- assetFileNames: 'assets/[name].[ext]',
23
- chunkFileNames: 'assets/[name].js',
24
- entryFileNames: 'assets/[name].js'
25
- }
26
- }
27
- }
28
- });
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { defineConfig } from 'vite';
4
+ import react from '@vitejs/plugin-react';
5
+ import { crx } from '@crxjs/vite-plugin';
6
+ import manifest from './src/manifest';
7
+ import tailwindcss from '@tailwindcss/vite';
8
+
9
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
10
+
11
+ export default defineConfig({
12
+ plugins: [react(), crx({ manifest }), tailwindcss()],
13
+ build: {
14
+ outDir: 'dist',
15
+ rollupOptions: {
16
+ input: {
17
+ popup: path.resolve(__dirname, 'src/popup/popup.html'),
18
+ options: path.resolve(__dirname, 'src/options/options.html'),
19
+ },
20
+ output: {
21
+ assetFileNames: 'assets/[name].[ext]',
22
+ chunkFileNames: 'assets/[name].js',
23
+ entryFileNames: 'assets/[name].js',
24
+ },
25
+ },
26
+ },
27
+ });
@@ -0,0 +1,29 @@
1
+ // Background service worker
2
+ // This runs in the background and handles events for your extension.
3
+
4
+ // Runs when the extension is first installed or updated
5
+ chrome.runtime.onInstalled.addListener((details) => {
6
+ console.log('[Vextro] Extension installed:', details.reason);
7
+
8
+ // Set default storage values on install
9
+ if (details.reason === 'install') {
10
+ chrome.storage.sync.set({
11
+ greeting: 'Hello from Vextro!',
12
+ clickCount: 0,
13
+ });
14
+ }
15
+ });
16
+
17
+ // Listen for messages from popup, options, or content scripts
18
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
19
+ console.log('[Vextro] Message received:', message, 'from:', sender.tab?.url);
20
+
21
+ if (message.type === 'GET_TAB_INFO') {
22
+ chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
23
+ sendResponse({ tab: tabs[0] ?? null });
24
+ });
25
+ return true; // Keep the message channel open for async response
26
+ }
27
+
28
+ sendResponse({ status: 'unknown message type' });
29
+ });
@@ -0,0 +1,13 @@
1
+ // Content script
2
+ // This runs in the context of web pages matched by manifest.json content_scripts.
3
+
4
+ console.log('[Vextro] Content script loaded on:', window.location.href);
5
+
6
+ // Example: send a message to the background script
7
+ chrome.runtime.sendMessage({ type: 'GET_TAB_INFO' }, (response) => {
8
+ if (chrome.runtime.lastError) {
9
+ console.warn('[Vextro] Message error:', chrome.runtime.lastError.message);
10
+ return;
11
+ }
12
+ console.log('[Vextro] Tab info received:', response);
13
+ });
@@ -0,0 +1,35 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "__EXT_NAME__",
4
+ "description": "A modern browser extension built with Vextro.",
5
+ "version": "1.0.0",
6
+ "icons": {
7
+ "16": "icons/icon16.png",
8
+ "48": "icons/icon48.png",
9
+ "128": "icons/icon128.png"
10
+ },
11
+ "permissions": ["storage", "tabs", "scripting"],
12
+ "action": {
13
+ "default_popup": "src/popup/popup.html"
14
+ },
15
+ "background": {
16
+ "scripts": ["src/background/background.ts"],
17
+ "type": "module"
18
+ },
19
+ "content_scripts": [
20
+ {
21
+ "matches": ["<all_urls>"],
22
+ "js": ["src/content/content.ts"]
23
+ }
24
+ ],
25
+ "options_ui": {
26
+ "page": "src/options/options.html",
27
+ "open_in_tab": true
28
+ },
29
+ "browser_specific_settings": {
30
+ "gecko": {
31
+ "id": "__EXT_NAME__@vextro.dev",
32
+ "strict_min_version": "109.0"
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,22 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { defineConfig } from 'vite';
4
+ import react from '@vitejs/plugin-react';
5
+ import webExtension from 'vite-plugin-web-extension';
6
+ import tailwindcss from '@tailwindcss/vite';
7
+
8
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
9
+
10
+ export default defineConfig({
11
+ plugins: [
12
+ react(),
13
+ webExtension({
14
+ manifest: path.resolve(__dirname, 'src/manifest.json'),
15
+ additionalInputs: ['src/popup/popup.html', 'src/options/options.html'],
16
+ }),
17
+ tailwindcss(),
18
+ ],
19
+ build: {
20
+ outDir: 'dist',
21
+ },
22
+ });
@@ -0,0 +1,70 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { getStorageValue, setStorageValue } from '../utils/storage';
3
+
4
+ export default function App() {
5
+ const [greeting, setGreeting] = useState('Hello from Vextro!');
6
+ const [saved, setSaved] = useState(false);
7
+
8
+ useEffect(() => {
9
+ getStorageValue<string>('greeting', 'Hello from Vextro!').then(setGreeting);
10
+ }, []);
11
+
12
+ const handleSave = async () => {
13
+ await setStorageValue('greeting', greeting);
14
+ setSaved(true);
15
+ setTimeout(() => setSaved(false), 2000);
16
+ };
17
+
18
+ return (
19
+ <div className="min-h-screen bg-gray-100 p-8 text-gray-900 font-sans">
20
+ <div className="max-w-xl mx-auto">
21
+ <h1 className="text-2xl font-bold mb-6">Vextro Extension Settings</h1>
22
+
23
+ {/* Greeting Setting */}
24
+ <div className="bg-white rounded-lg shadow p-6 mb-4">
25
+ <label className="block text-sm font-medium text-gray-700 mb-2">Greeting Message</label>
26
+ <input
27
+ type="text"
28
+ value={greeting}
29
+ onChange={(e) => setGreeting(e.target.value)}
30
+ className="w-full p-2.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none"
31
+ placeholder="Enter your greeting..."
32
+ />
33
+ <p className="text-xs text-gray-500 mt-1">
34
+ This greeting appears in the popup. It's stored using chrome.storage.sync.
35
+ </p>
36
+ </div>
37
+
38
+ {/* Save Button */}
39
+ <button
40
+ onClick={handleSave}
41
+ className="bg-blue-600 hover:bg-blue-700 active:bg-blue-800 transition-colors text-white px-6 py-2.5 rounded-md font-medium"
42
+ >
43
+ Save Settings
44
+ </button>
45
+
46
+ {saved && (
47
+ <span className="ml-3 text-green-600 text-sm font-medium">✔ Saved successfully!</span>
48
+ )}
49
+
50
+ {/* Info */}
51
+ <div className="mt-8 bg-blue-50 border border-blue-200 rounded-lg p-4">
52
+ <h2 className="text-sm font-semibold text-blue-800 mb-1">About Vextro</h2>
53
+ <p className="text-sm text-blue-700">
54
+ This extension was scaffolded with{' '}
55
+ <a
56
+ href="https://github.com/lasalasa/vextro"
57
+ target="_blank"
58
+ rel="noopener noreferrer"
59
+ className="underline"
60
+ >
61
+ create-vextro
62
+ </a>
63
+ . Edit this page in{' '}
64
+ <code className="bg-blue-100 px-1 rounded">src/options/App.tsx</code>.
65
+ </p>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ );
70
+ }
@@ -1,9 +1,9 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom/client';
3
- import App from './App';
4
-
5
- ReactDOM.createRoot(document.getElementById('root')!).render(
6
- <React.StrictMode>
7
- <App />
8
- </React.StrictMode>
9
- );
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App';
4
+
5
+ ReactDOM.createRoot(document.getElementById('root')!).render(
6
+ <React.StrictMode>
7
+ <App />
8
+ </React.StrictMode>,
9
+ );
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link href="/src/styles.css" rel="stylesheet" />
6
+ <title>Vextro Options</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./index.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,61 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { getStorageValue, setStorageValue } from '../utils/storage';
3
+
4
+ export default function App() {
5
+ const [count, setCount] = useState(0);
6
+ const [greeting, setGreeting] = useState('');
7
+
8
+ useEffect(() => {
9
+ // Load saved count from chrome.storage
10
+ getStorageValue<number>('clickCount', 0).then(setCount);
11
+ getStorageValue<string>('greeting', 'Hello from Vextro!').then(setGreeting);
12
+ }, []);
13
+
14
+ const handleClick = async () => {
15
+ const newCount = count + 1;
16
+ setCount(newCount);
17
+ await setStorageValue('clickCount', newCount);
18
+ };
19
+
20
+ const openOptionsPage = () => {
21
+ chrome.runtime.openOptionsPage();
22
+ };
23
+
24
+ return (
25
+ <div className="w-[320px] min-h-[280px] p-5 bg-gray-900 text-white font-sans">
26
+ {/* Header */}
27
+ <div className="flex items-center justify-between mb-5">
28
+ <div className="flex items-center gap-2">
29
+ <img src="/icon.png" alt="Vextro Logo" className="w-6 h-6" />
30
+ <h1 className="text-lg font-semibold">Vextro</h1>
31
+ </div>
32
+ <button
33
+ onClick={openOptionsPage}
34
+ className="text-sm text-blue-400 hover:text-blue-300 transition-colors"
35
+ >
36
+ ⚙️ Settings
37
+ </button>
38
+ </div>
39
+
40
+ {/* Greeting */}
41
+ <p className="text-sm text-gray-400 mb-4">{greeting}</p>
42
+
43
+ {/* Counter Demo */}
44
+ <div className="bg-gray-800 p-4 rounded-lg text-center">
45
+ <p className="text-sm text-gray-400 mb-2">Clicks (synced via chrome.storage)</p>
46
+ <p className="text-3xl font-bold text-blue-400 mb-3">{count}</p>
47
+ <button
48
+ onClick={handleClick}
49
+ className="w-full bg-blue-600 hover:bg-blue-700 active:bg-blue-800 transition-colors py-2 px-4 rounded-md font-medium"
50
+ >
51
+ Click Me
52
+ </button>
53
+ </div>
54
+
55
+ {/* Footer */}
56
+ <p className="text-xs text-gray-600 text-center mt-4">
57
+ Built with Vextro — Vite + React + Tailwind
58
+ </p>
59
+ </div>
60
+ );
61
+ }
@@ -1,9 +1,9 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom/client';
3
- import App from './App';
4
-
5
- ReactDOM.createRoot(document.getElementById('root')!).render(
6
- <React.StrictMode>
7
- <App />
8
- </React.StrictMode>
9
- );
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App';
4
+
5
+ ReactDOM.createRoot(document.getElementById('root')!).render(
6
+ <React.StrictMode>
7
+ <App />
8
+ </React.StrictMode>,
9
+ );
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link href="/src/styles.css" rel="stylesheet" />
6
+ <title>Vextro Popup</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./index.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1 @@
1
+ @import 'tailwindcss';
@@ -0,0 +1,34 @@
1
+ // Typed wrapper around chrome.storage.sync
2
+
3
+ /**
4
+ * Get a value from chrome.storage.sync with a default fallback.
5
+ */
6
+ export async function getStorageValue<T>(key: string, defaultValue: T): Promise<T> {
7
+ return new Promise((resolve) => {
8
+ chrome.storage.sync.get({ [key]: defaultValue }, (result) => {
9
+ resolve(result[key] as T);
10
+ });
11
+ });
12
+ }
13
+
14
+ /**
15
+ * Set a value in chrome.storage.sync.
16
+ */
17
+ export async function setStorageValue<T>(key: string, value: T): Promise<void> {
18
+ return new Promise((resolve) => {
19
+ chrome.storage.sync.set({ [key]: value }, () => {
20
+ resolve();
21
+ });
22
+ });
23
+ }
24
+
25
+ /**
26
+ * Remove a value from chrome.storage.sync.
27
+ */
28
+ export async function removeStorageValue(key: string): Promise<void> {
29
+ return new Promise((resolve) => {
30
+ chrome.storage.sync.remove(key, () => {
31
+ resolve();
32
+ });
33
+ });
34
+ }