You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Clipper: Improve support for future versions of Chrome (upgrade to manifest version 3) (#10109)
This commit is contained in:
		| @@ -1229,6 +1229,7 @@ packages/tools/packageJsonLint.js | ||||
| packages/tools/postPreReleasesToForum.js | ||||
| packages/tools/release-android.js | ||||
| packages/tools/release-cli.js | ||||
| packages/tools/release-clipper.js | ||||
| packages/tools/release-electron.js | ||||
| packages/tools/release-ios.js | ||||
| packages/tools/release-plugin-repo-cli.js | ||||
|   | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1209,6 +1209,7 @@ packages/tools/packageJsonLint.js | ||||
| packages/tools/postPreReleasesToForum.js | ||||
| packages/tools/release-android.js | ||||
| packages/tools/release-cli.js | ||||
| packages/tools/release-clipper.js | ||||
| packages/tools/release-electron.js | ||||
| packages/tools/release-ios.js | ||||
| packages/tools/release-plugin-repo-cli.js | ||||
|   | ||||
| @@ -11,13 +11,9 @@ | ||||
| 	if (typeof browser !== 'undefined') { | ||||
| 		// eslint-disable-next-line no-undef | ||||
| 		browser_ = browser; | ||||
| 		// eslint-disable-next-line no-undef | ||||
| 		browserSupportsPromises_ = true; | ||||
| 	} else if (typeof chrome !== 'undefined') { | ||||
| 		// eslint-disable-next-line no-undef | ||||
| 		browser_ = chrome; | ||||
| 		// eslint-disable-next-line no-undef | ||||
| 		browserSupportsPromises_ = false; | ||||
| 	} | ||||
|  | ||||
| 	function escapeHtml(s) { | ||||
| @@ -461,6 +457,7 @@ | ||||
| 						tags: command.tags, | ||||
| 						windowInnerWidth: window.innerWidth, | ||||
| 						windowInnerHeight: window.innerHeight, | ||||
| 						devicePixelRatio: window.devicePixelRatio, | ||||
| 					}; | ||||
|  | ||||
| 					browser_.runtime.sendMessage({ | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| { | ||||
|     "manifest_version": 2, | ||||
|     "manifest_version": 3, | ||||
|     "name": "Joplin Web Clipper [DEV]", | ||||
|     "version": "3.0.0", | ||||
|     "description": "Capture and save web pages and screenshots from your browser to Joplin.", | ||||
|     "homepage_url": "https://joplinapp.org", | ||||
|     "content_security_policy": "script-src 'self'; object-src 'self'", | ||||
|     "content_security_policy": { | ||||
|         "extension_pages": "script-src 'self'; object-src 'self'" | ||||
|     }, | ||||
|     "icons": { | ||||
|         "32": "icons/32.png", | ||||
|         "48": "icons/48.png", | ||||
| @@ -13,18 +15,21 @@ | ||||
|     "permissions": [ | ||||
|         "activeTab", | ||||
|         "tabs", | ||||
|         "http://*/", | ||||
|         "https://*/", | ||||
|         "<all_urls>", | ||||
|         "scripting", | ||||
|         "storage" | ||||
|     ], | ||||
|     "browser_action": { | ||||
|     "host_permissions": [ | ||||
|         "http://*/", | ||||
|         "https://*/", | ||||
|         "<all_urls>" | ||||
|     ], | ||||
|     "action": { | ||||
|         "default_icon": "icons/32.png", | ||||
|         "default_title": "Joplin Web Clipper", | ||||
|         "default_popup": "popup/build/index.html" | ||||
|     }, | ||||
|     "commands": { | ||||
|         "_execute_browser_action": { | ||||
|         "_execute_action": { | ||||
|             "suggested_key": { | ||||
|                 "default": "Alt+Shift+J" | ||||
|             } | ||||
| @@ -49,12 +54,12 @@ | ||||
|         } | ||||
|     }, | ||||
|     "background": { | ||||
|         "scripts": [ | ||||
|             "background.js" | ||||
|         ], | ||||
|         "persistent": false | ||||
|         "scripts": ["service_worker.mjs"], | ||||
|  | ||||
|         "service_worker": "service_worker.mjs", | ||||
|         "type": "module" | ||||
|     }, | ||||
|     "applications": { | ||||
|     "browser_specific_settings": { | ||||
|         "gecko": { | ||||
|             "id": "{8419486a-54e9-11e8-9401-ac9e17909436}" | ||||
|         } | ||||
|   | ||||
| @@ -115,6 +115,13 @@ class AppComponent extends Component { | ||||
|  | ||||
| 		this.clipScreenshot_click = async () => { | ||||
| 			try { | ||||
| 				// Firefox requires the <all_urls> host permission to take a | ||||
| 				// screenshot of the current page, however, this may change | ||||
| 				// in the future. Note that Firefox also forces this permission | ||||
| 				// to be optional. | ||||
| 				// See https://discourse.mozilla.org/t/browser-tabs-capturevisibletab-not-working-in-firefox-for-mv3/122965/3 | ||||
| 				await bridge().browser().permissions.request({ origins: ['<all_urls>'] }); | ||||
|  | ||||
| 				const baseUrl = await bridge().clipperServerBaseUrl(); | ||||
|  | ||||
| 				await bridge().sendCommandToActiveTab({ | ||||
| @@ -179,12 +186,14 @@ class AppComponent extends Component { | ||||
| 	} | ||||
|  | ||||
| 	async loadContentScripts() { | ||||
| 		await bridge().tabsExecuteScript({ file: '/content_scripts/setUpEnvironment.js' }); | ||||
| 		await bridge().tabsExecuteScript({ file: '/content_scripts/JSDOMParser.js' }); | ||||
| 		await bridge().tabsExecuteScript({ file: '/content_scripts/Readability.js' }); | ||||
| 		await bridge().tabsExecuteScript({ file: '/content_scripts/Readability-readerable.js' }); | ||||
| 		await bridge().tabsExecuteScript({ file: '/content_scripts/clipperUtils.js' }); | ||||
| 		await bridge().tabsExecuteScript({ file: '/content_scripts/index.js' }); | ||||
| 		await bridge().tabsExecuteScript([ | ||||
| 			'/content_scripts/setUpEnvironment.js', | ||||
| 			'/content_scripts/JSDOMParser.js', | ||||
| 			'/content_scripts/Readability.js', | ||||
| 			'/content_scripts/Readability-readerable.js', | ||||
| 			'/content_scripts/clipperUtils.js', | ||||
| 			'/content_scripts/index.js', | ||||
| 		]); | ||||
| 	} | ||||
|  | ||||
| 	async componentDidMount() { | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| /* eslint-disable no-console */ | ||||
|  | ||||
| import getActiveTabs from '../../util/getActiveTabs.mjs'; | ||||
| import joplinEnv from '../../util/joplinEnv.mjs'; | ||||
| const { randomClipperPort } = require('./randomClipperPort'); | ||||
|  | ||||
| function msleep(ms) { | ||||
| @@ -17,13 +19,12 @@ class Bridge { | ||||
| 		this.token_ = null; | ||||
| 	} | ||||
|  | ||||
| 	async init(browser, browserSupportsPromises, store) { | ||||
| 	async init(browser, store) { | ||||
| 		console.info('Popup: Init bridge'); | ||||
|  | ||||
| 		this.browser_ = browser; | ||||
| 		this.dispatch_ = store.dispatch; | ||||
| 		this.store_ = store; | ||||
| 		this.browserSupportsPromises_ = browserSupportsPromises; | ||||
| 		this.clipperServerPort_ = null; | ||||
| 		this.clipperServerPortStatus_ = 'searching'; | ||||
|  | ||||
| @@ -74,12 +75,7 @@ class Bridge { | ||||
| 			} | ||||
| 		}; | ||||
| 		this.browser_.runtime.onMessage.addListener(this.browser_notify); | ||||
| 		const backgroundPage = await this.backgroundPage(this.browser_); | ||||
|  | ||||
| 		// Not sure why the getBackgroundPage() sometimes returns null, so | ||||
| 		// in that case default to "prod" environment, which means the live | ||||
| 		// extension won't be affected by this bug. | ||||
| 		this.env_ = backgroundPage ? backgroundPage.joplinEnv() : 'prod'; | ||||
| 		this.env_ = joplinEnv(); | ||||
|  | ||||
| 		console.info('Popup: Env:', this.env()); | ||||
|  | ||||
| @@ -197,17 +193,6 @@ class Bridge { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async backgroundPage(browser) { | ||||
| 		const bgp = browser.extension.getBackgroundPage(); | ||||
| 		if (bgp) return bgp; | ||||
|  | ||||
| 		return new Promise((resolve) => { | ||||
| 			browser.runtime.getBackgroundPage((bgp) => { | ||||
| 				resolve(bgp); | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	env() { | ||||
| 		return this.env_; | ||||
| 	} | ||||
| @@ -305,50 +290,26 @@ class Bridge { | ||||
| 		return `http://127.0.0.1:${port}`; | ||||
| 	} | ||||
|  | ||||
| 	async tabsExecuteScript(options) { | ||||
| 		if (this.browserSupportsPromises_) return this.browser().tabs.executeScript(options); | ||||
|  | ||||
| 		return new Promise((resolve, reject) => { | ||||
| 			this.browser().tabs.executeScript(options, () => { | ||||
| 				const e = this.browser().runtime.lastError; | ||||
| 				if (e) { | ||||
| 					const msg = [`tabsExecuteScript: Cannot load ${JSON.stringify(options)}`]; | ||||
| 					if (e.message) msg.push(e.message); | ||||
| 					reject(new Error(msg.join(': '))); | ||||
| 				} | ||||
| 				resolve(); | ||||
| 			}); | ||||
| 	async tabsExecuteScript(files) { | ||||
| 		const activeTabs = await getActiveTabs(this.browser()); | ||||
| 		await this.browser().scripting.executeScript({ | ||||
| 			target: { | ||||
| 				tabId: activeTabs[0].id, | ||||
| 			}, | ||||
| 			files, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	async tabsQuery(options) { | ||||
| 		if (this.browserSupportsPromises_) return this.browser().tabs.query(options); | ||||
|  | ||||
| 		return new Promise((resolve) => { | ||||
| 			this.browser().tabs.query(options, (tabs) => { | ||||
| 				resolve(tabs); | ||||
| 			}); | ||||
| 		}); | ||||
| 		return this.browser().tabs.query(options); | ||||
| 	} | ||||
|  | ||||
| 	async tabsSendMessage(tabId, command) { | ||||
| 		if (this.browserSupportsPromises_) return this.browser().tabs.sendMessage(tabId, command); | ||||
|  | ||||
| 		return new Promise((resolve) => { | ||||
| 			this.browser().tabs.sendMessage(tabId, command, (result) => { | ||||
| 				resolve(result); | ||||
| 			}); | ||||
| 		}); | ||||
| 		return this.browser().tabs.sendMessage(tabId, command); | ||||
| 	} | ||||
|  | ||||
| 	async tabsCreate(options) { | ||||
| 		if (this.browserSupportsPromises_) return this.browser().tabs.create(options); | ||||
|  | ||||
| 		return new Promise((resolve) => { | ||||
| 			this.browser().tabs.create(options, () => { | ||||
| 				resolve(); | ||||
| 			}); | ||||
| 		}); | ||||
| 		return this.browser().tabs.create(options); | ||||
| 	} | ||||
|  | ||||
| 	async folderTree() { | ||||
| @@ -356,29 +317,15 @@ class Bridge { | ||||
| 	} | ||||
|  | ||||
| 	async storageSet(keys) { | ||||
| 		if (this.browserSupportsPromises_) return this.browser().storage.local.set(keys); | ||||
|  | ||||
| 		return new Promise((resolve) => { | ||||
| 			this.browser().storage.local.set(keys, () => { | ||||
| 				resolve(); | ||||
| 			}); | ||||
| 		}); | ||||
| 		return this.browser().storage.local.set(keys); | ||||
| 	} | ||||
|  | ||||
| 	async storageGet(keys, defaultValue = null) { | ||||
| 		if (this.browserSupportsPromises_) { | ||||
| 			try { | ||||
| 				const r = await this.browser().storage.local.get(keys); | ||||
| 				return r; | ||||
| 			} catch (error) { | ||||
| 				return defaultValue; | ||||
| 			} | ||||
| 		} else { | ||||
| 			return new Promise((resolve) => { | ||||
| 				this.browser().storage.local.get(keys, (result) => { | ||||
| 					resolve(result); | ||||
| 				}); | ||||
| 			}); | ||||
| 		try { | ||||
| 			const r = await this.browser().storage.local.get(keys); | ||||
| 			return r; | ||||
| 		} catch (error) { | ||||
| 			return defaultValue; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -112,7 +112,7 @@ async function main() { | ||||
|  | ||||
| 	console.info('Popup: Init bridge and restore state...'); | ||||
|  | ||||
| 	await bridge().init(window.browser ? window.browser : window.chrome, !!window.browser, store); | ||||
| 	await bridge().init(window.browser ? window.browser : window.chrome, store); | ||||
|  | ||||
| 	console.info('Popup: Creating React app...'); | ||||
|  | ||||
|   | ||||
| @@ -1,25 +1,13 @@ | ||||
| import joplinEnv from './util/joplinEnv.mjs'; | ||||
| import getActiveTabs from './util/getActiveTabs.mjs'; | ||||
| 
 | ||||
| let browser_ = null; | ||||
| if (typeof browser !== 'undefined') { | ||||
| 	browser_ = browser; | ||||
| 	browserSupportsPromises_ = true; | ||||
| } else if (typeof chrome !== 'undefined') { | ||||
| 	browser_ = chrome; | ||||
| 	browserSupportsPromises_ = false; | ||||
| } | ||||
| 
 | ||||
| let env_ = null; | ||||
| 
 | ||||
| // Make this function global so that it can be accessed
 | ||||
| // from the popup too.
 | ||||
| // https://stackoverflow.com/questions/6323184/communication-between-background-page-and-popup-page-in-a-chrome-extension
 | ||||
| window.joplinEnv = function() { | ||||
| 	if (env_) return env_; | ||||
| 
 | ||||
| 	const manifest = browser_.runtime.getManifest(); | ||||
| 	env_ = manifest.name.indexOf('[DEV]') >= 0 ? 'dev' : 'prod'; | ||||
| 	return env_; | ||||
| }; | ||||
| 
 | ||||
| async function browserCaptureVisibleTabs(windowId) { | ||||
| 	const options = { | ||||
| 		format: 'jpeg', | ||||
| @@ -31,53 +19,19 @@ async function browserCaptureVisibleTabs(windowId) { | ||||
| 		// https://discourse.joplinapp.org/t/clip-screenshot-image-quality/12302/4
 | ||||
| 		quality: 92, | ||||
| 	}; | ||||
| 	if (browserSupportsPromises_) return browser_.tabs.captureVisibleTab(windowId, options); | ||||
| 
 | ||||
| 	return new Promise((resolve) => { | ||||
| 		browser_.tabs.captureVisibleTab(windowId, options, (image) => { | ||||
| 			resolve(image); | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| async function browserGetZoom(tabId) { | ||||
| 	if (browserSupportsPromises_) return browser_.tabs.getZoom(tabId); | ||||
| 
 | ||||
| 	return new Promise((resolve) => { | ||||
| 		browser_.tabs.getZoom(tabId, (zoom) => { | ||||
| 			resolve(zoom); | ||||
| 		}); | ||||
| 	}); | ||||
| 	return browser_.tabs.captureVisibleTab(windowId, options); | ||||
| } | ||||
| 
 | ||||
| browser_.runtime.onInstalled.addListener(() => { | ||||
| 	if (window.joplinEnv() === 'dev') { | ||||
| 		browser_.browserAction.setIcon({ | ||||
| 	if (joplinEnv() === 'dev') { | ||||
| 		browser_.action.setIcon({ | ||||
| 			path: 'icons/32-dev.png', | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| async function getImageSize(dataUrl) { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		const image = new Image(); | ||||
| 
 | ||||
| 		image.onload = function() { | ||||
| 			resolve({ width: image.width, height: image.height }); | ||||
| 		}; | ||||
| 
 | ||||
| 		image.onerror = function(event) { | ||||
| 			reject(event); | ||||
| 		}; | ||||
| 
 | ||||
| 		image.src = dataUrl; | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| browser_.runtime.onMessage.addListener(async (command) => { | ||||
| 	if (command.name === 'screenshotArea') { | ||||
| 		const browserZoom = await browserGetZoom(); | ||||
| 
 | ||||
| 		// The dimensions of the image returned by Firefox are the regular ones,
 | ||||
| 		// while the one returned by Chrome depend on the screen pixel ratio. So
 | ||||
| 		// it would return a 600*400 image if the window dimensions are 300x200
 | ||||
| @@ -90,15 +44,18 @@ browser_.runtime.onMessage.addListener(async (command) => { | ||||
| 		//
 | ||||
| 		// The crop rectangle is always in real pixels, so we need to multiply
 | ||||
| 		// it by the ratio we've calculated.
 | ||||
| 		//
 | ||||
| 		// 8/3/2024: With manifest v3, we don't have access to DOM APIs in Chrome.
 | ||||
| 		// As a result, we can't easily calculate the size of the captured image.
 | ||||
| 		// We instead base the crop region exclusively on window.devicePixelRatio,
 | ||||
| 		// which seems to work in modern Firefox and Chrome.
 | ||||
| 		const imageDataUrl = await browserCaptureVisibleTabs(null); | ||||
| 		const imageSize = await getImageSize(imageDataUrl); | ||||
| 		const imagePixelRatio = imageSize.width / command.content.windowInnerWidth; | ||||
| 
 | ||||
| 		const content = { ...command.content }; | ||||
| 		content.image_data_url = imageDataUrl; | ||||
| 		if ('url' in content) content.source_url = content.url; | ||||
| 
 | ||||
| 		const ratio = browserZoom * imagePixelRatio; | ||||
| 		const ratio = content.devicePixelRatio; | ||||
| 		const newArea = { ...command.content.crop_rect }; | ||||
| 		newArea.x *= ratio; | ||||
| 		newArea.y *= ratio; | ||||
| @@ -117,19 +74,8 @@ browser_.runtime.onMessage.addListener(async (command) => { | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| async function getActiveTabs() { | ||||
| 	const options = { active: true, currentWindow: true }; | ||||
| 	if (browserSupportsPromises_) return browser_.tabs.query(options); | ||||
| 
 | ||||
| 	return new Promise((resolve) => { | ||||
| 		browser_.tabs.query(options, (tabs) => { | ||||
| 			resolve(tabs); | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| async function sendClipMessage(clipType) { | ||||
| 	const tabs = await getActiveTabs(); | ||||
| 	const tabs = await getActiveTabs(browser_); | ||||
| 	if (!tabs || !tabs.length) { | ||||
| 		console.error('No active tabs'); | ||||
| 		return; | ||||
							
								
								
									
										7
									
								
								packages/app-clipper/util/getActiveTabs.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/app-clipper/util/getActiveTabs.mjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
|  | ||||
| const getActiveTabs = async (browser) => { | ||||
| 	const options = { active: true, currentWindow: true }; | ||||
| 	return await browser.tabs.query(options); | ||||
| } | ||||
|  | ||||
| export default getActiveTabs; | ||||
							
								
								
									
										3
									
								
								packages/app-clipper/util/joplinEnv.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/app-clipper/util/joplinEnv.mjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| // AUTOGENERATED by release-clipper | ||||
|  | ||||
| export default () => 'dev'; | ||||
| @@ -1,26 +1,27 @@ | ||||
| const fs = require('fs-extra'); | ||||
| const { execCommand, rootDir } = require('./tool-utils.js'); | ||||
| import * as fs from 'fs-extra'; | ||||
| import { execCommand, rootDir } from './tool-utils'; | ||||
| const md5File = require('md5-file'); | ||||
| const glob = require('glob'); | ||||
| import * as glob from 'glob'; | ||||
| 
 | ||||
| const clipperDir = `${rootDir}/packages/app-clipper`; | ||||
| const tmpSourceDirName = 'Clipper-source'; | ||||
| 
 | ||||
| async function copyDir(baseSourceDir, sourcePath, baseDestDir) { | ||||
| async function copyDir(baseSourceDir: string, sourcePath: string, baseDestDir: string) { | ||||
| 	await fs.mkdirp(`${baseDestDir}/${sourcePath}`); | ||||
| 	await fs.copy(`${baseSourceDir}/${sourcePath}`, `${baseDestDir}/${sourcePath}`); | ||||
| } | ||||
| 
 | ||||
| async function copyToDist(distDir) { | ||||
| async function copyToDist(distDir: string) { | ||||
| 	await copyDir(clipperDir, 'popup/build', distDir); | ||||
| 	await copyDir(clipperDir, 'content_scripts', distDir); | ||||
| 	await copyDir(clipperDir, 'icons', distDir); | ||||
| 	await fs.copy(`${clipperDir}/background.js`, `${distDir}/background.js`); | ||||
| 	await copyDir(clipperDir, 'util', distDir); | ||||
| 	await fs.copy(`${clipperDir}/service_worker.mjs`, `${distDir}/service_worker.mjs`); | ||||
| 	await fs.copy(`${clipperDir}/manifest.json`, `${distDir}/manifest.json`); | ||||
| 	await fs.remove(`${distDir}/popup/build/manifest.json`); | ||||
| } | ||||
| 
 | ||||
| async function updateManifestVersionNumber(manifestPath) { | ||||
| async function updateManifestVersionNumber(manifestPath: string) { | ||||
| 	const manifestText = await fs.readFile(manifestPath, 'utf-8'); | ||||
| 	const manifest = JSON.parse(manifestText); | ||||
| 	const v = manifest.version.split('.'); | ||||
| @@ -47,11 +48,11 @@ async function createSourceZip() { | ||||
| 	return filePath; | ||||
| } | ||||
| 
 | ||||
| async function compareFiles(path1, path2) { | ||||
| async function compareFiles(path1: string, path2: string) { | ||||
| 	return await md5File(path1) === await md5File(path2); | ||||
| } | ||||
| 
 | ||||
| async function compareDir(dir1, dir2) { | ||||
| async function compareDir(dir1: string, dir2: string) { | ||||
| 	console.info(`Comparing directories ${dir1} to ${dir2}`); | ||||
| 
 | ||||
| 	const globOptions = { | ||||
| @@ -61,7 +62,7 @@ async function compareDir(dir1, dir2) { | ||||
| 		], | ||||
| 	}; | ||||
| 
 | ||||
| 	const filterFiles = (f) => { | ||||
| 	const filterFiles = (f: string) => { | ||||
| 		const stat = fs.statSync(f); | ||||
| 		return !stat.isDirectory(); | ||||
| 	}; | ||||
| @@ -69,11 +70,11 @@ async function compareDir(dir1, dir2) { | ||||
| 	const files1 = glob.sync(`${dir1}/**/*`, globOptions).filter(filterFiles).map(f => f.substr(dir1.length + 1)); | ||||
| 	const files2 = glob.sync(`${dir2}/**/*`, globOptions).filter(filterFiles).map(f => f.substr(dir2.length + 1)); | ||||
| 
 | ||||
| 	const missingFiles1 = []; | ||||
| 	const missingFiles2 = []; | ||||
| 	const canBeMissing1 = []; | ||||
| 	const canBeMissing2 = ['manifest.json']; | ||||
| 	const differentFiles = []; | ||||
| 	const missingFiles1: string[] = []; | ||||
| 	const missingFiles2: string[] = []; | ||||
| 	const canBeMissing1: string[] = []; | ||||
| 	const canBeMissing2: string[] = ['manifest.json']; | ||||
| 	const differentFiles: string[] = []; | ||||
| 	for (const f of files1) { | ||||
| 		if (!files2.includes(f)) { | ||||
| 			if (canBeMissing2.includes(f)) continue; | ||||
| @@ -104,7 +105,7 @@ async function compareDir(dir1, dir2) { | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| async function checkSourceZip(sourceZip, compiledZip) { | ||||
| async function checkSourceZip(sourceZip: string, compiledZip: string) { | ||||
| 	const tmpDir = `${require('os').tmpdir()}/${Date.now()}`; | ||||
| 
 | ||||
| 	console.info(`Checking source ZIP in ${tmpDir}`); | ||||
| @@ -119,6 +120,7 @@ async function checkSourceZip(sourceZip, compiledZip) { | ||||
| 	console.info(await execCommand(`unzip "${sourceZip}"`)); | ||||
| 	process.chdir(`${sourceDir}/Clipper-source/popup`); | ||||
| 	console.info(await execCommand('npm install')); | ||||
| 	console.info(await execCommand('npm run build')); | ||||
| 
 | ||||
| 	process.chdir(compiledDir); | ||||
| 	console.info(await execCommand(`cp "${compiledZip}" .`)); | ||||
| @@ -132,6 +134,11 @@ async function checkSourceZip(sourceZip, compiledZip) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| async function setReleaseMode(isReleaseMode: boolean) { | ||||
| 	const joplinEnvPath = `${clipperDir}/util/joplinEnv.mjs`; | ||||
| 	await fs.writeFile(joplinEnvPath, `// AUTOGENERATED by release-clipper\n\nexport default () => '${isReleaseMode ? 'prod' : 'dev'}';`); | ||||
| } | ||||
| 
 | ||||
| async function main() { | ||||
| 	console.info(await execCommand('git pull')); | ||||
| 
 | ||||
| @@ -139,24 +146,34 @@ async function main() { | ||||
| 
 | ||||
| 	console.info('Building extension...'); | ||||
| 	process.chdir(`${clipperDir}/popup`); | ||||
| 	// SKIP_PREFLIGHT_CHECK avoids the error "There might be a problem with the project dependency tree." due to eslint 5.12.0 being
 | ||||
| 	// installed by CRA and 6.1.0 by us. It doesn't affect anything though, and the behaviour of the preflight
 | ||||
| 	// check is buggy so we can ignore it.
 | ||||
| 	await setReleaseMode(true); | ||||
| 	console.info(await execCommand(`rm -rf ${clipperDir}/popup/build`)); | ||||
| 	console.info(await execCommand('npm run build')); | ||||
| 
 | ||||
| 	const dists = { | ||||
| 	type PlatformDistOptions = { | ||||
| 		removeManifestKeys(manifest: Record<string, any>): Record<string, any>; | ||||
| 		outputPath?: string; | ||||
| 	}; | ||||
| 	const dists: Record<string, PlatformDistOptions> = { | ||||
| 		chrome: { | ||||
| 			removeManifestKeys: (manifest) => { | ||||
| 				manifest = { ...manifest }; | ||||
| 				delete manifest.applications; | ||||
| 				delete manifest.browser_specific_settings; | ||||
| 
 | ||||
| 				manifest.background = { ...manifest.background }; | ||||
| 				delete manifest.background.scripts; | ||||
| 				delete manifest.background.persistent; | ||||
| 
 | ||||
| 				return manifest; | ||||
| 			}, | ||||
| 		}, | ||||
| 		firefox: { | ||||
| 			removeManifestKeys: (manifest) => { | ||||
| 				manifest = { ...manifest }; | ||||
| 				delete manifest.background.persistent; | ||||
| 
 | ||||
| 				manifest.background = { ...manifest.background }; | ||||
| 				delete manifest.background.service_worker; | ||||
| 
 | ||||
| 				return manifest; | ||||
| 			}, | ||||
| 		}, | ||||
| @@ -186,12 +203,16 @@ async function main() { | ||||
| 	const sourceZip = await createSourceZip(); | ||||
| 	await checkSourceZip(sourceZip, dists.firefox.outputPath); | ||||
| 
 | ||||
| 	await setReleaseMode(false); | ||||
| 
 | ||||
| 	process.chdir(clipperDir); | ||||
| 	console.info(await execCommand('git add -A')); | ||||
| 	console.info(await execCommand(`git commit -m "Clipper release v${newVersion}"`)); | ||||
| 	console.info(await execCommand(`git tag clipper-${newVersion}`)); | ||||
| 	console.info(await execCommand('git push')); | ||||
| 	console.info(await execCommand('git push --tags')); | ||||
| 	if (!process.argv.includes('--no-publish')) { | ||||
| 		console.info(await execCommand('git add -A')); | ||||
| 		console.info(await execCommand(`git commit -m "Clipper release v${newVersion}"`)); | ||||
| 		console.info(await execCommand(`git tag clipper-${newVersion}`)); | ||||
| 		console.info(await execCommand('git push')); | ||||
| 		console.info(await execCommand('git push --tags')); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| main().catch((error) => { | ||||
		Reference in New Issue
	
	Block a user