Files
Momento/memento-note/extension/scripts/build-chrome-store.mjs
Antigravity 86505659cf
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 5m47s
CI / Deploy production (on server) (push) Successful in 23s
fix(extension): masquer le diagnostic en builds Store v0.4.6
- Ajout du flag ALLOW_DIAGNOSTICS dans sidepanel.js.
- En prod (builds Chrome Store / Firefox) ALLOW_DIAGNOSTICS = false :
  le bouton ? et le panneau diagnostic sont masqués, logDiag reste silencieux.
- Mise à jour des scripts de build pour appliquer ALLOW_DIAGNOSTICS = false.
- Bump version 0.4.6.
2026-07-18 17:21:40 +00:00

276 lines
8.3 KiB
JavaScript
Executable File

#!/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.
// `exclude` accepts RegExp (matched against the relative path) or a function
// returning true when the entry (relative path, isDirectory flag) should be skipped.
function copyFiles(src, dest, exclude = []) {
ensureDir(dest)
const entries = fs.readdirSync(src, { withFileTypes: true })
const isExcluded = (relPath, isDir) =>
exclude.some(rule => {
if (typeof rule === 'function') return rule(relPath, isDir)
return relPath.match(rule) !== null
})
for (const entry of entries) {
const srcPath = path.join(src, entry.name)
const relPath = path.relative(extRoot, srcPath)
if (isExcluded(relPath, entry.isDirectory())) {
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'
)
.replace(
/const ALLOW_DIAGNOSTICS = true/,
'const ALLOW_DIAGNOSTICS = false'
)
}
// Read and modify manifest.json for production
function processManifestJson(content) {
const manifest = JSON.parse(content)
// Un Web Clipper doit pouvoir s'exécuter sur n'importe quel site http(s).
// Le Chrome Web Store accepte ces permissions quand elles sont justifiées
// par la fonctionnalité (extraction de pages web).
manifest.host_permissions = [
'http://*/*',
'https://*/*',
]
if (manifest.content_scripts?.[0]) {
manifest.content_scripts[0].matches = ['http://*/*', 'https://*/*']
}
// Strip Firefox-only fields (Chrome Store rejects unknown keys on strict review)
delete manifest.sidebar_action
delete manifest.browser_specific_settings
delete manifest.optional_host_permissions
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, internal sources and dist)
log('📋 Copying extension files...', 'blue')
copyFiles(extRoot, distDir, [
// Top-level directories to skip entirely (do not even create empty folders)
(rel, isDir) => isDir && /^dist-/i.test(rel),
(rel, isDir) => isDir && (rel === 'scripts' || rel === 'i18n' || rel === 'store-assets'),
// File-level patterns
/\.md$/,
/^node_modules$/,
/^diagnose\.js$/, // dev diagnostic script
/^test-sidepanel\.html$/, // dev test page
/^memento-web-clipper-chrome-store\.zip$/, // avoid recursive zip-in-zip
/^memento-web-clipper-firefox\.xpi$/, // exclude sibling Firefox build artifacts
])
// 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()