Compare commits

...

5 Commits

Author SHA1 Message Date
ee387d9637 feat: add extension packing script and update build script
All checks were successful
Deploy to Production / deploy (push) Successful in 14s
2026-03-03 11:03:43 +00:00
f2203e896e chore: bump version to 0.6.16
All checks were successful
Deploy to Production / deploy (push) Successful in 13s
2026-03-03 10:03:44 +00:00
cb8d3d01ec fix: update extension link to Chrome Web Store 2026-03-03 10:03:41 +00:00
5b31171964 0.6.15
All checks were successful
Deploy to Production / deploy (push) Successful in 13s
2026-03-03 00:02:34 +00:00
e98761a18e feat: make header title a link to home page 2026-03-03 00:02:17 +00:00
7 changed files with 141 additions and 11 deletions

6
.gitignore vendored
View File

@@ -27,3 +27,9 @@ dev-dist
extension-release.zip extension-release.zip
*.zip *.zip
tools-app-extension-*.zip tools-app-extension-*.zip
tools-app-extension-*.crx
# Extension keys and builds
*.pem
*.crx
scripts/*.pub

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "tools-app", "name": "tools-app",
"version": "0.6.14", "version": "0.6.16",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "tools-app", "name": "tools-app",
"version": "0.6.14", "version": "0.6.16",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"barcode-detector": "^3.1.0", "barcode-detector": "^3.1.0",

View File

@@ -1,12 +1,13 @@
{ {
"name": "tools-app", "name": "tools-app",
"private": true, "private": true,
"version": "0.6.14", "version": "0.6.17",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"pack-extension": "node scripts/pack_crx.js",
"postinstall": "mkdir -p public/wasm && cp node_modules/zxing-wasm/dist/reader/zxing_reader.wasm public/wasm/" "postinstall": "mkdir -p public/wasm && cp node_modules/zxing-wasm/dist/reader/zxing_reader.wasm public/wasm/"
}, },
"dependencies": { "dependencies": {

View File

@@ -6,9 +6,14 @@ import zipfile
# Configuration # Configuration
SOURCE_DIR = "extension" SOURCE_DIR = "extension"
BUILD_DIR = "dist-extension" BUILD_DIR = "dist-extension"
OUTPUT_ZIP = "extension-release.zip"
MANIFEST_FILE = "manifest.json" MANIFEST_FILE = "manifest.json"
# Read version to create dynamic zip name
with open(os.path.join(SOURCE_DIR, MANIFEST_FILE), "r") as f:
source_manifest = json.load(f)
version = source_manifest.get("version", "unknown")
OUTPUT_ZIP = f"tools-app-extension-v{version}.zip"
# Remove build directory if exists # Remove build directory if exists
if os.path.exists(BUILD_DIR): if os.path.exists(BUILD_DIR):
shutil.rmtree(BUILD_DIR) shutil.rmtree(BUILD_DIR)
@@ -27,7 +32,7 @@ print("Removing localhost from manifest...")
# Filter host_permissions # Filter host_permissions
if "host_permissions" in manifest: if "host_permissions" in manifest:
manifest["host_permissions"] = [ manifest["host_permissions"] = [
perm for perm in manifest["host_permissions"] perm for perm in manifest["host_permissions"]
if "localhost" not in perm if "localhost" not in perm
] ]
@@ -36,7 +41,7 @@ if "content_scripts" in manifest:
for script in manifest["content_scripts"]: for script in manifest["content_scripts"]:
if "matches" in script: if "matches" in script:
script["matches"] = [ script["matches"] = [
match for match in script["matches"] match for match in script["matches"]
if "localhost" not in match if "localhost" not in match
] ]
@@ -55,4 +60,4 @@ with zipfile.ZipFile(OUTPUT_ZIP, "w", zipfile.ZIP_DEFLATED) as zipf:
# Cleanup # Cleanup
shutil.rmtree(BUILD_DIR) shutil.rmtree(BUILD_DIR)
print(f"Done! {OUTPUT_ZIP} is ready for upload to Chrome Web Store.") print(f"Done! {OUTPUT_ZIP} is ready for upload to Chrome Web Store.")

110
scripts/pack_crx.js Normal file
View File

@@ -0,0 +1,110 @@
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
function findKey() {
// 1. Check if key provided via CLI
if (process.argv[3]) return process.argv[3];
// 2. Check local project directory (gitignored)
const localKey = path.join(process.cwd(), 'scripts', 'key.pem');
if (fs.existsSync(localKey)) return localKey;
// 3. Check common SSH key locations in ~/.ssh
const sshDir = path.join(process.env.HOME, '.ssh');
const commonKeys = ['id_rsa', 'id_ecdsa', 'id_ed25519'];
for (const keyName of commonKeys) {
const fullPath = path.join(sshDir, keyName);
if (fs.existsSync(fullPath)) return fullPath;
}
return null;
}
const KEY_PATH = findKey();
const INPUT_SOURCE = process.argv[2] || path.join(process.cwd(), 'extension');
const TEMP_DIR = path.join(process.cwd(), 'temp_extension_build');
function pack() {
console.log('📦 Packing extension to CRX...');
if (!KEY_PATH) {
console.error('❌ Error: No private key found.');
console.log('Tried local scripts/key.pem and common ~/.ssh/id_* keys.');
console.log('You can provide a path manually: npm run pack-extension <source> <path/to/key>');
process.exit(1);
}
let extensionDir = INPUT_SOURCE;
let isTemp = false;
try {
// If input is a zip file, unzip it first
if (INPUT_SOURCE.endsWith('.zip')) {
console.log('🤐 Unzipping extension...');
if (fs.existsSync(TEMP_DIR)) fs.rmSync(TEMP_DIR, { recursive: true, force: true });
fs.mkdirSync(TEMP_DIR);
execSync(`unzip -o "${INPUT_SOURCE}" -d "${TEMP_DIR}"`, { stdio: 'pipe' });
extensionDir = TEMP_DIR;
isTemp = true;
}
// Resolve actual extension directory (handle subdirs in zip)
if (!fs.existsSync(path.join(extensionDir, 'manifest.json'))) {
const subdirs = fs.readdirSync(extensionDir).filter(f => fs.statSync(path.join(extensionDir, f)).isDirectory());
if (subdirs.length === 1 && fs.existsSync(path.join(extensionDir, subdirs[0], 'manifest.json'))) {
extensionDir = path.join(extensionDir, subdirs[0]);
console.log(`📂 Found manifest in subdirectory: ${extensionDir}`);
} else {
console.error(`❌ Error: manifest.json not found in ${extensionDir}`);
process.exit(1);
}
}
// Determine output filename
let outputName;
if (INPUT_SOURCE.endsWith('.zip')) {
outputName = path.basename(INPUT_SOURCE).replace('.zip', '.crx');
} else {
const manifest = JSON.parse(fs.readFileSync(path.join(extensionDir, 'manifest.json'), 'utf8'));
outputName = `tools-app-extension-v${manifest.version}.crx`;
}
const outputFull = path.join(process.cwd(), outputName);
// Get version for logging
const manifest = JSON.parse(fs.readFileSync(path.join(extensionDir, 'manifest.json'), 'utf8'));
const version = manifest.version;
console.log(`🔑 Using key: ${KEY_PATH}`);
console.log(`📂 Source: ${extensionDir} (v${version})`);
console.log(`🚀 Running crx3 via npx...`);
// Command: npx -y crx3 --key <key> --crx <output> <dir>
execSync(`npx -y crx3 --key "${KEY_PATH}" --crx "${outputFull}" "${extensionDir}"`, {
stdio: 'inherit'
});
// Cleanup any file that crx3 might have created automatically based on temp dir name
const unintendedFile = extensionDir + '.crx';
if (fs.existsSync(unintendedFile) && unintendedFile !== outputFull) {
fs.unlinkSync(unintendedFile);
}
console.log(`\n✅ Success! Extension packed to: ${outputFull}`);
} catch (error) {
console.error('\n❌ Failed to pack extension.');
if (error.message.includes('algorithm')) {
console.error('⚠️ Note: Chrome CRX format requires RSA or ECDSA (P-256) keys.');
} else {
console.error('Error details:', error.message);
}
process.exit(1);
} finally {
if (isTemp && fs.existsSync(TEMP_DIR)) {
fs.rmSync(TEMP_DIR, { recursive: true, force: true });
}
}
}
pack();

View File

@@ -44,7 +44,7 @@ onMounted(() => {
<line x1="3" y1="18" x2="21" y2="18"></line> <line x1="3" y1="18" x2="21" y2="18"></line>
</svg> </svg>
</button> </button>
<h1 class="app-title">Tools App</h1> <router-link to="/" class="app-title">Tools App</router-link>
</div> </div>
<button <button
@@ -115,10 +115,11 @@ onMounted(() => {
.app-title { .app-title {
margin: 0; margin: 0;
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 600;
background: var(--title-gradient); background: var(--title-gradient);
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;
color: transparent;
text-decoration: none;
font-weight: bold;
} }
</style> </style>

View File

@@ -56,7 +56,14 @@ onUnmounted(() => {
With the extension, you can capture and process content even when you're working in other apps. With the extension, you can capture and process content even when you're working in other apps.
</p> </p>
<div class="modal-actions"> <div class="modal-actions">
<a href="#" class="btn-neon" @click.prevent>Extension Coming Soon</a> <a
href="https://chromewebstore.google.com/detail/tools-app-extension/bhcpbmfncohogehbhebiffcgjcndnneg"
target="_blank"
rel="noopener noreferrer"
class="btn-neon"
>
Install Extension
</a>
</div> </div>
</div> </div>