perf: memo GridCard, fuse save fns, fix slash tab active color
This commit is contained in:
247
memento-note/extension/scripts/build-chrome-store.mjs
Executable file
247
memento-note/extension/scripts/build-chrome-store.mjs
Executable file
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Build script for Chrome Web Store production package
|
||||
* Usage: node scripts/build-chrome-store.mjs
|
||||
*
|
||||
* This script:
|
||||
* 1. Sets ALLOW_INSTANCE_CONFIG = false in sidepanel.js
|
||||
* 2. Removes localhost permissions from manifest.json
|
||||
* 3. Copies and generates icons from public/icons/
|
||||
* 4. Creates a production-ready .zip package
|
||||
*/
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import AdmZip from 'adm-zip'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const extRoot = path.resolve(__dirname, '..')
|
||||
const projectRoot = path.resolve(extRoot, '..')
|
||||
const publicIconsDir = path.join(projectRoot, 'public', 'icons')
|
||||
const distDir = path.join(extRoot, 'dist-chrome-store')
|
||||
|
||||
// Colors for terminal output
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
red: '\x1b[31m'
|
||||
}
|
||||
|
||||
function log(message, color = 'reset') {
|
||||
console.log(`${colors[color]}${message}${colors.reset}`)
|
||||
}
|
||||
|
||||
function ensureDir(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
// Copy all files from source to destination, excluding specified patterns
|
||||
function copyFiles(src, dest, exclude = []) {
|
||||
ensureDir(dest)
|
||||
const entries = fs.readdirSync(src, { withFileTypes: true })
|
||||
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(src, entry.name)
|
||||
const relPath = path.relative(extRoot, srcPath)
|
||||
|
||||
// Skip excluded files/directories
|
||||
if (exclude.some(pattern => relPath.match(pattern))) {
|
||||
continue
|
||||
}
|
||||
|
||||
const destPath = path.join(dest, entry.name)
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
copyFiles(srcPath, destPath, exclude)
|
||||
} else {
|
||||
fs.copyFileSync(srcPath, destPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read and modify sidepanel.js for production
|
||||
function processSidepanelJs(content) {
|
||||
return content.replace(
|
||||
/const ALLOW_INSTANCE_CONFIG = true/,
|
||||
'const ALLOW_INSTANCE_CONFIG = false'
|
||||
)
|
||||
}
|
||||
|
||||
// Read and modify manifest.json for production
|
||||
function processManifestJson(content) {
|
||||
const manifest = JSON.parse(content)
|
||||
|
||||
// Remove localhost from host_permissions
|
||||
if (manifest.host_permissions) {
|
||||
manifest.host_permissions = manifest.host_permissions.filter(
|
||||
perm => !perm.includes('localhost:3000') && !perm.includes('127.0.0.1:3000')
|
||||
)
|
||||
}
|
||||
|
||||
return JSON.stringify(manifest, null, 2)
|
||||
}
|
||||
|
||||
// Generate PNG icons from SVG using sharp
|
||||
async function generateIcons() {
|
||||
log('📦 Generating PNG icons from SVG...', 'blue')
|
||||
|
||||
const sharp = (await import('sharp')).default
|
||||
const sizes = [16, 48, 128]
|
||||
|
||||
// Source SVG files
|
||||
const icon512Svg = path.join(publicIconsDir, 'icon-512.svg')
|
||||
const icon192Svg = path.join(publicIconsDir, 'icon-192.svg')
|
||||
|
||||
if (!fs.existsSync(icon512Svg) || !fs.existsSync(icon192Svg)) {
|
||||
log('⚠️ Source SVG icons not found. Copying SVG files only.', 'yellow')
|
||||
// Copy SVG files as fallback
|
||||
fs.copyFileSync(icon512Svg, path.join(distDir, 'icon-512.svg'))
|
||||
fs.copyFileSync(icon192Svg, path.join(distDir, 'icon-192.svg'))
|
||||
return
|
||||
}
|
||||
|
||||
// Generate PNG icons
|
||||
for (const size of sizes) {
|
||||
const sourceSvg = size >= 128 ? icon512Svg : icon192Svg
|
||||
const outputPath = path.join(distDir, `icon-${size}.png`)
|
||||
|
||||
await sharp(sourceSvg)
|
||||
.resize(size, size)
|
||||
.png()
|
||||
.toFile(outputPath)
|
||||
|
||||
log(` ✓ Generated icon-${size}.png`, 'green')
|
||||
}
|
||||
|
||||
// Also copy SVG files for reference
|
||||
fs.copyFileSync(icon512Svg, path.join(distDir, 'icon-512.svg'))
|
||||
fs.copyFileSync(icon192Svg, path.join(distDir, 'icon-192.svg'))
|
||||
}
|
||||
|
||||
// Create ZIP package using AdmZip
|
||||
async function createZipPackage() {
|
||||
log('📦 Creating ZIP package...', 'blue')
|
||||
|
||||
const zipPath = path.join(extRoot, 'memento-web-clipper-chrome-store.zip')
|
||||
|
||||
try {
|
||||
const zip = new AdmZip()
|
||||
|
||||
// Add all files from dist directory
|
||||
const addFiles = (dir, base = '') => {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name)
|
||||
const relativePath = path.join(base, entry.name)
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
addFiles(fullPath, relativePath)
|
||||
} else {
|
||||
zip.addLocalFile(fullPath, base)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addFiles(distDir)
|
||||
|
||||
// Write the zip file
|
||||
zip.writeZip(zipPath)
|
||||
|
||||
// Get file size
|
||||
const stats = fs.statSync(zipPath)
|
||||
log(`✓ ZIP package created: ${zipPath}`, 'green')
|
||||
log(` Size: ${(stats.size / 1024).toFixed(2)} KB`, 'green')
|
||||
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to create ZIP: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Main build process
|
||||
async function build() {
|
||||
log('🚀 Starting Chrome Web Store build...', 'blue')
|
||||
log('')
|
||||
|
||||
try {
|
||||
// Clean dist directory
|
||||
log('🧹 Cleaning dist directory...', 'blue')
|
||||
if (fs.existsSync(distDir)) {
|
||||
fs.rmSync(distDir, { recursive: true, force: true })
|
||||
}
|
||||
ensureDir(distDir)
|
||||
|
||||
// Copy extension files (excluding build scripts and dist)
|
||||
log('📋 Copying extension files...', 'blue')
|
||||
copyFiles(extRoot, distDir, [
|
||||
/^dist-/,
|
||||
/^scripts\//,
|
||||
/\.md$/,
|
||||
/^node_modules$/
|
||||
])
|
||||
|
||||
// Process sidepanel.js
|
||||
log('⚙️ Processing sidepanel.js...', 'blue')
|
||||
const sidepanelPath = path.join(distDir, 'sidepanel.js')
|
||||
let sidepanelContent = fs.readFileSync(sidepanelPath, 'utf8')
|
||||
sidepanelContent = processSidepanelJs(sidepanelContent)
|
||||
fs.writeFileSync(sidepanelPath, sidepanelContent)
|
||||
log(' ✓ Set ALLOW_INSTANCE_CONFIG = false', 'green')
|
||||
|
||||
// Process manifest.json
|
||||
log('⚙️ Processing manifest.json...', 'blue')
|
||||
const manifestPath = path.join(distDir, 'manifest.json')
|
||||
let manifestContent = fs.readFileSync(manifestPath, 'utf8')
|
||||
manifestContent = processManifestJson(manifestContent)
|
||||
|
||||
// Add icons to manifest
|
||||
const manifest = JSON.parse(manifestContent)
|
||||
manifest.icons = {
|
||||
"16": "icon-16.png",
|
||||
"48": "icon-48.png",
|
||||
"128": "icon-128.png"
|
||||
}
|
||||
manifest.action = {
|
||||
...manifest.action,
|
||||
"default_icon": {
|
||||
"16": "icon-16.png",
|
||||
"48": "icon-48.png",
|
||||
"128": "icon-128.png"
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
|
||||
log(' ✓ Removed localhost permissions', 'green')
|
||||
log(' ✓ Added icon definitions', 'green')
|
||||
|
||||
// Generate icons
|
||||
await generateIcons()
|
||||
|
||||
// Create ZIP package
|
||||
await createZipPackage()
|
||||
|
||||
log('')
|
||||
log('✅ Build completed successfully!', 'green')
|
||||
log('')
|
||||
log('📦 Output files:', 'blue')
|
||||
log(` • Package: ${path.join(extRoot, 'memento-web-clipper-chrome-store.zip')}`, 'reset')
|
||||
log(` • Dist dir: ${distDir}`, 'reset')
|
||||
log('')
|
||||
log('📝 Next steps:', 'blue')
|
||||
log(' 1. Test the extension by loading the dist-chrome-store folder in Chrome (chrome://extensions)', 'reset')
|
||||
log(' 2. Upload the .zip file to Chrome Web Store Developer Dashboard', 'reset')
|
||||
log('')
|
||||
|
||||
} catch (error) {
|
||||
log('')
|
||||
log('❌ Build failed!', 'red')
|
||||
log(` Error: ${error.message}`, 'red')
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Run the build
|
||||
build()
|
||||
Reference in New Issue
Block a user