mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-27 08:21:03 +02:00
Clipper: Improve support for future versions of Chrome (upgrade to manifest version 3) (#10109)
This commit is contained in:
parent
78b8839ae3
commit
e72cce0d07
@ -1229,6 +1229,7 @@ packages/tools/packageJsonLint.js
|
|||||||
packages/tools/postPreReleasesToForum.js
|
packages/tools/postPreReleasesToForum.js
|
||||||
packages/tools/release-android.js
|
packages/tools/release-android.js
|
||||||
packages/tools/release-cli.js
|
packages/tools/release-cli.js
|
||||||
|
packages/tools/release-clipper.js
|
||||||
packages/tools/release-electron.js
|
packages/tools/release-electron.js
|
||||||
packages/tools/release-ios.js
|
packages/tools/release-ios.js
|
||||||
packages/tools/release-plugin-repo-cli.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/postPreReleasesToForum.js
|
||||||
packages/tools/release-android.js
|
packages/tools/release-android.js
|
||||||
packages/tools/release-cli.js
|
packages/tools/release-cli.js
|
||||||
|
packages/tools/release-clipper.js
|
||||||
packages/tools/release-electron.js
|
packages/tools/release-electron.js
|
||||||
packages/tools/release-ios.js
|
packages/tools/release-ios.js
|
||||||
packages/tools/release-plugin-repo-cli.js
|
packages/tools/release-plugin-repo-cli.js
|
||||||
|
@ -11,13 +11,9 @@
|
|||||||
if (typeof browser !== 'undefined') {
|
if (typeof browser !== 'undefined') {
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
browser_ = browser;
|
browser_ = browser;
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
browserSupportsPromises_ = true;
|
|
||||||
} else if (typeof chrome !== 'undefined') {
|
} else if (typeof chrome !== 'undefined') {
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
browser_ = chrome;
|
browser_ = chrome;
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
browserSupportsPromises_ = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(s) {
|
function escapeHtml(s) {
|
||||||
@ -461,6 +457,7 @@
|
|||||||
tags: command.tags,
|
tags: command.tags,
|
||||||
windowInnerWidth: window.innerWidth,
|
windowInnerWidth: window.innerWidth,
|
||||||
windowInnerHeight: window.innerHeight,
|
windowInnerHeight: window.innerHeight,
|
||||||
|
devicePixelRatio: window.devicePixelRatio,
|
||||||
};
|
};
|
||||||
|
|
||||||
browser_.runtime.sendMessage({
|
browser_.runtime.sendMessage({
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 3,
|
||||||
"name": "Joplin Web Clipper [DEV]",
|
"name": "Joplin Web Clipper [DEV]",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
|
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
|
||||||
"homepage_url": "https://joplinapp.org",
|
"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": {
|
"icons": {
|
||||||
"32": "icons/32.png",
|
"32": "icons/32.png",
|
||||||
"48": "icons/48.png",
|
"48": "icons/48.png",
|
||||||
@ -13,18 +15,21 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
"activeTab",
|
"activeTab",
|
||||||
"tabs",
|
"tabs",
|
||||||
"http://*/",
|
"scripting",
|
||||||
"https://*/",
|
|
||||||
"<all_urls>",
|
|
||||||
"storage"
|
"storage"
|
||||||
],
|
],
|
||||||
"browser_action": {
|
"host_permissions": [
|
||||||
|
"http://*/",
|
||||||
|
"https://*/",
|
||||||
|
"<all_urls>"
|
||||||
|
],
|
||||||
|
"action": {
|
||||||
"default_icon": "icons/32.png",
|
"default_icon": "icons/32.png",
|
||||||
"default_title": "Joplin Web Clipper",
|
"default_title": "Joplin Web Clipper",
|
||||||
"default_popup": "popup/build/index.html"
|
"default_popup": "popup/build/index.html"
|
||||||
},
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
"_execute_browser_action": {
|
"_execute_action": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Alt+Shift+J"
|
"default": "Alt+Shift+J"
|
||||||
}
|
}
|
||||||
@ -49,12 +54,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": [
|
"scripts": ["service_worker.mjs"],
|
||||||
"background.js"
|
|
||||||
],
|
"service_worker": "service_worker.mjs",
|
||||||
"persistent": false
|
"type": "module"
|
||||||
},
|
},
|
||||||
"applications": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "{8419486a-54e9-11e8-9401-ac9e17909436}"
|
"id": "{8419486a-54e9-11e8-9401-ac9e17909436}"
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,13 @@ class AppComponent extends Component {
|
|||||||
|
|
||||||
this.clipScreenshot_click = async () => {
|
this.clipScreenshot_click = async () => {
|
||||||
try {
|
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();
|
const baseUrl = await bridge().clipperServerBaseUrl();
|
||||||
|
|
||||||
await bridge().sendCommandToActiveTab({
|
await bridge().sendCommandToActiveTab({
|
||||||
@ -179,12 +186,14 @@ class AppComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadContentScripts() {
|
async loadContentScripts() {
|
||||||
await bridge().tabsExecuteScript({ file: '/content_scripts/setUpEnvironment.js' });
|
await bridge().tabsExecuteScript([
|
||||||
await bridge().tabsExecuteScript({ file: '/content_scripts/JSDOMParser.js' });
|
'/content_scripts/setUpEnvironment.js',
|
||||||
await bridge().tabsExecuteScript({ file: '/content_scripts/Readability.js' });
|
'/content_scripts/JSDOMParser.js',
|
||||||
await bridge().tabsExecuteScript({ file: '/content_scripts/Readability-readerable.js' });
|
'/content_scripts/Readability.js',
|
||||||
await bridge().tabsExecuteScript({ file: '/content_scripts/clipperUtils.js' });
|
'/content_scripts/Readability-readerable.js',
|
||||||
await bridge().tabsExecuteScript({ file: '/content_scripts/index.js' });
|
'/content_scripts/clipperUtils.js',
|
||||||
|
'/content_scripts/index.js',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
import getActiveTabs from '../../util/getActiveTabs.mjs';
|
||||||
|
import joplinEnv from '../../util/joplinEnv.mjs';
|
||||||
const { randomClipperPort } = require('./randomClipperPort');
|
const { randomClipperPort } = require('./randomClipperPort');
|
||||||
|
|
||||||
function msleep(ms) {
|
function msleep(ms) {
|
||||||
@ -17,13 +19,12 @@ class Bridge {
|
|||||||
this.token_ = null;
|
this.token_ = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async init(browser, browserSupportsPromises, store) {
|
async init(browser, store) {
|
||||||
console.info('Popup: Init bridge');
|
console.info('Popup: Init bridge');
|
||||||
|
|
||||||
this.browser_ = browser;
|
this.browser_ = browser;
|
||||||
this.dispatch_ = store.dispatch;
|
this.dispatch_ = store.dispatch;
|
||||||
this.store_ = store;
|
this.store_ = store;
|
||||||
this.browserSupportsPromises_ = browserSupportsPromises;
|
|
||||||
this.clipperServerPort_ = null;
|
this.clipperServerPort_ = null;
|
||||||
this.clipperServerPortStatus_ = 'searching';
|
this.clipperServerPortStatus_ = 'searching';
|
||||||
|
|
||||||
@ -74,12 +75,7 @@ class Bridge {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.browser_.runtime.onMessage.addListener(this.browser_notify);
|
this.browser_.runtime.onMessage.addListener(this.browser_notify);
|
||||||
const backgroundPage = await this.backgroundPage(this.browser_);
|
this.env_ = joplinEnv();
|
||||||
|
|
||||||
// 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';
|
|
||||||
|
|
||||||
console.info('Popup: Env:', this.env());
|
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() {
|
env() {
|
||||||
return this.env_;
|
return this.env_;
|
||||||
}
|
}
|
||||||
@ -305,50 +290,26 @@ class Bridge {
|
|||||||
return `http://127.0.0.1:${port}`;
|
return `http://127.0.0.1:${port}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async tabsExecuteScript(options) {
|
async tabsExecuteScript(files) {
|
||||||
if (this.browserSupportsPromises_) return this.browser().tabs.executeScript(options);
|
const activeTabs = await getActiveTabs(this.browser());
|
||||||
|
await this.browser().scripting.executeScript({
|
||||||
return new Promise((resolve, reject) => {
|
target: {
|
||||||
this.browser().tabs.executeScript(options, () => {
|
tabId: activeTabs[0].id,
|
||||||
const e = this.browser().runtime.lastError;
|
},
|
||||||
if (e) {
|
files,
|
||||||
const msg = [`tabsExecuteScript: Cannot load ${JSON.stringify(options)}`];
|
|
||||||
if (e.message) msg.push(e.message);
|
|
||||||
reject(new Error(msg.join(': ')));
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async tabsQuery(options) {
|
async tabsQuery(options) {
|
||||||
if (this.browserSupportsPromises_) return this.browser().tabs.query(options);
|
return this.browser().tabs.query(options);
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.browser().tabs.query(options, (tabs) => {
|
|
||||||
resolve(tabs);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async tabsSendMessage(tabId, command) {
|
async tabsSendMessage(tabId, command) {
|
||||||
if (this.browserSupportsPromises_) return this.browser().tabs.sendMessage(tabId, command);
|
return this.browser().tabs.sendMessage(tabId, command);
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.browser().tabs.sendMessage(tabId, command, (result) => {
|
|
||||||
resolve(result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async tabsCreate(options) {
|
async tabsCreate(options) {
|
||||||
if (this.browserSupportsPromises_) return this.browser().tabs.create(options);
|
return this.browser().tabs.create(options);
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.browser().tabs.create(options, () => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async folderTree() {
|
async folderTree() {
|
||||||
@ -356,30 +317,16 @@ class Bridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async storageSet(keys) {
|
async storageSet(keys) {
|
||||||
if (this.browserSupportsPromises_) return this.browser().storage.local.set(keys);
|
return this.browser().storage.local.set(keys);
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.browser().storage.local.set(keys, () => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async storageGet(keys, defaultValue = null) {
|
async storageGet(keys, defaultValue = null) {
|
||||||
if (this.browserSupportsPromises_) {
|
|
||||||
try {
|
try {
|
||||||
const r = await this.browser().storage.local.get(keys);
|
const r = await this.browser().storage.local.get(keys);
|
||||||
return r;
|
return r;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.browser().storage.local.get(keys, (result) => {
|
|
||||||
resolve(result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendCommandToActiveTab(command) {
|
async sendCommandToActiveTab(command) {
|
||||||
|
@ -112,7 +112,7 @@ async function main() {
|
|||||||
|
|
||||||
console.info('Popup: Init bridge and restore state...');
|
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...');
|
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;
|
let browser_ = null;
|
||||||
if (typeof browser !== 'undefined') {
|
if (typeof browser !== 'undefined') {
|
||||||
browser_ = browser;
|
browser_ = browser;
|
||||||
browserSupportsPromises_ = true;
|
|
||||||
} else if (typeof chrome !== 'undefined') {
|
} else if (typeof chrome !== 'undefined') {
|
||||||
browser_ = chrome;
|
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) {
|
async function browserCaptureVisibleTabs(windowId) {
|
||||||
const options = {
|
const options = {
|
||||||
format: 'jpeg',
|
format: 'jpeg',
|
||||||
@ -31,53 +19,19 @@ async function browserCaptureVisibleTabs(windowId) {
|
|||||||
// https://discourse.joplinapp.org/t/clip-screenshot-image-quality/12302/4
|
// https://discourse.joplinapp.org/t/clip-screenshot-image-quality/12302/4
|
||||||
quality: 92,
|
quality: 92,
|
||||||
};
|
};
|
||||||
if (browserSupportsPromises_) return browser_.tabs.captureVisibleTab(windowId, options);
|
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
browser_.runtime.onInstalled.addListener(() => {
|
browser_.runtime.onInstalled.addListener(() => {
|
||||||
if (window.joplinEnv() === 'dev') {
|
if (joplinEnv() === 'dev') {
|
||||||
browser_.browserAction.setIcon({
|
browser_.action.setIcon({
|
||||||
path: 'icons/32-dev.png',
|
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) => {
|
browser_.runtime.onMessage.addListener(async (command) => {
|
||||||
if (command.name === 'screenshotArea') {
|
if (command.name === 'screenshotArea') {
|
||||||
const browserZoom = await browserGetZoom();
|
|
||||||
|
|
||||||
// The dimensions of the image returned by Firefox are the regular ones,
|
// 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
|
// 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
|
// 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
|
// The crop rectangle is always in real pixels, so we need to multiply
|
||||||
// it by the ratio we've calculated.
|
// 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 imageDataUrl = await browserCaptureVisibleTabs(null);
|
||||||
const imageSize = await getImageSize(imageDataUrl);
|
|
||||||
const imagePixelRatio = imageSize.width / command.content.windowInnerWidth;
|
|
||||||
|
|
||||||
const content = { ...command.content };
|
const content = { ...command.content };
|
||||||
content.image_data_url = imageDataUrl;
|
content.image_data_url = imageDataUrl;
|
||||||
if ('url' in content) content.source_url = content.url;
|
if ('url' in content) content.source_url = content.url;
|
||||||
|
|
||||||
const ratio = browserZoom * imagePixelRatio;
|
const ratio = content.devicePixelRatio;
|
||||||
const newArea = { ...command.content.crop_rect };
|
const newArea = { ...command.content.crop_rect };
|
||||||
newArea.x *= ratio;
|
newArea.x *= ratio;
|
||||||
newArea.y *= 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) {
|
async function sendClipMessage(clipType) {
|
||||||
const tabs = await getActiveTabs();
|
const tabs = await getActiveTabs(browser_);
|
||||||
if (!tabs || !tabs.length) {
|
if (!tabs || !tabs.length) {
|
||||||
console.error('No active tabs');
|
console.error('No active tabs');
|
||||||
return;
|
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');
|
import * as fs from 'fs-extra';
|
||||||
const { execCommand, rootDir } = require('./tool-utils.js');
|
import { execCommand, rootDir } from './tool-utils';
|
||||||
const md5File = require('md5-file');
|
const md5File = require('md5-file');
|
||||||
const glob = require('glob');
|
import * as glob from 'glob';
|
||||||
|
|
||||||
const clipperDir = `${rootDir}/packages/app-clipper`;
|
const clipperDir = `${rootDir}/packages/app-clipper`;
|
||||||
const tmpSourceDirName = 'Clipper-source';
|
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.mkdirp(`${baseDestDir}/${sourcePath}`);
|
||||||
await fs.copy(`${baseSourceDir}/${sourcePath}`, `${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, 'popup/build', distDir);
|
||||||
await copyDir(clipperDir, 'content_scripts', distDir);
|
await copyDir(clipperDir, 'content_scripts', distDir);
|
||||||
await copyDir(clipperDir, 'icons', 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.copy(`${clipperDir}/manifest.json`, `${distDir}/manifest.json`);
|
||||||
await fs.remove(`${distDir}/popup/build/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 manifestText = await fs.readFile(manifestPath, 'utf-8');
|
||||||
const manifest = JSON.parse(manifestText);
|
const manifest = JSON.parse(manifestText);
|
||||||
const v = manifest.version.split('.');
|
const v = manifest.version.split('.');
|
||||||
@ -47,11 +48,11 @@ async function createSourceZip() {
|
|||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function compareFiles(path1, path2) {
|
async function compareFiles(path1: string, path2: string) {
|
||||||
return await md5File(path1) === await md5File(path2);
|
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}`);
|
console.info(`Comparing directories ${dir1} to ${dir2}`);
|
||||||
|
|
||||||
const globOptions = {
|
const globOptions = {
|
||||||
@ -61,7 +62,7 @@ async function compareDir(dir1, dir2) {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterFiles = (f) => {
|
const filterFiles = (f: string) => {
|
||||||
const stat = fs.statSync(f);
|
const stat = fs.statSync(f);
|
||||||
return !stat.isDirectory();
|
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 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 files2 = glob.sync(`${dir2}/**/*`, globOptions).filter(filterFiles).map(f => f.substr(dir2.length + 1));
|
||||||
|
|
||||||
const missingFiles1 = [];
|
const missingFiles1: string[] = [];
|
||||||
const missingFiles2 = [];
|
const missingFiles2: string[] = [];
|
||||||
const canBeMissing1 = [];
|
const canBeMissing1: string[] = [];
|
||||||
const canBeMissing2 = ['manifest.json'];
|
const canBeMissing2: string[] = ['manifest.json'];
|
||||||
const differentFiles = [];
|
const differentFiles: string[] = [];
|
||||||
for (const f of files1) {
|
for (const f of files1) {
|
||||||
if (!files2.includes(f)) {
|
if (!files2.includes(f)) {
|
||||||
if (canBeMissing2.includes(f)) continue;
|
if (canBeMissing2.includes(f)) continue;
|
||||||
@ -104,7 +105,7 @@ async function compareDir(dir1, dir2) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkSourceZip(sourceZip, compiledZip) {
|
async function checkSourceZip(sourceZip: string, compiledZip: string) {
|
||||||
const tmpDir = `${require('os').tmpdir()}/${Date.now()}`;
|
const tmpDir = `${require('os').tmpdir()}/${Date.now()}`;
|
||||||
|
|
||||||
console.info(`Checking source ZIP in ${tmpDir}`);
|
console.info(`Checking source ZIP in ${tmpDir}`);
|
||||||
@ -119,6 +120,7 @@ async function checkSourceZip(sourceZip, compiledZip) {
|
|||||||
console.info(await execCommand(`unzip "${sourceZip}"`));
|
console.info(await execCommand(`unzip "${sourceZip}"`));
|
||||||
process.chdir(`${sourceDir}/Clipper-source/popup`);
|
process.chdir(`${sourceDir}/Clipper-source/popup`);
|
||||||
console.info(await execCommand('npm install'));
|
console.info(await execCommand('npm install'));
|
||||||
|
console.info(await execCommand('npm run build'));
|
||||||
|
|
||||||
process.chdir(compiledDir);
|
process.chdir(compiledDir);
|
||||||
console.info(await execCommand(`cp "${compiledZip}" .`));
|
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() {
|
async function main() {
|
||||||
console.info(await execCommand('git pull'));
|
console.info(await execCommand('git pull'));
|
||||||
|
|
||||||
@ -139,24 +146,34 @@ async function main() {
|
|||||||
|
|
||||||
console.info('Building extension...');
|
console.info('Building extension...');
|
||||||
process.chdir(`${clipperDir}/popup`);
|
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
|
await setReleaseMode(true);
|
||||||
// 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.
|
|
||||||
console.info(await execCommand(`rm -rf ${clipperDir}/popup/build`));
|
console.info(await execCommand(`rm -rf ${clipperDir}/popup/build`));
|
||||||
console.info(await execCommand('npm run 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: {
|
chrome: {
|
||||||
removeManifestKeys: (manifest) => {
|
removeManifestKeys: (manifest) => {
|
||||||
manifest = { ...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;
|
return manifest;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
firefox: {
|
firefox: {
|
||||||
removeManifestKeys: (manifest) => {
|
removeManifestKeys: (manifest) => {
|
||||||
manifest = { ...manifest };
|
manifest = { ...manifest };
|
||||||
delete manifest.background.persistent;
|
|
||||||
|
manifest.background = { ...manifest.background };
|
||||||
|
delete manifest.background.service_worker;
|
||||||
|
|
||||||
return manifest;
|
return manifest;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -186,13 +203,17 @@ async function main() {
|
|||||||
const sourceZip = await createSourceZip();
|
const sourceZip = await createSourceZip();
|
||||||
await checkSourceZip(sourceZip, dists.firefox.outputPath);
|
await checkSourceZip(sourceZip, dists.firefox.outputPath);
|
||||||
|
|
||||||
|
await setReleaseMode(false);
|
||||||
|
|
||||||
process.chdir(clipperDir);
|
process.chdir(clipperDir);
|
||||||
|
if (!process.argv.includes('--no-publish')) {
|
||||||
console.info(await execCommand('git add -A'));
|
console.info(await execCommand('git add -A'));
|
||||||
console.info(await execCommand(`git commit -m "Clipper release v${newVersion}"`));
|
console.info(await execCommand(`git commit -m "Clipper release v${newVersion}"`));
|
||||||
console.info(await execCommand(`git tag clipper-${newVersion}`));
|
console.info(await execCommand(`git tag clipper-${newVersion}`));
|
||||||
console.info(await execCommand('git push'));
|
console.info(await execCommand('git push'));
|
||||||
console.info(await execCommand('git push --tags'));
|
console.info(await execCommand('git push --tags'));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
main().catch((error) => {
|
main().catch((error) => {
|
||||||
console.error('Fatal error');
|
console.error('Fatal error');
|
Loading…
Reference in New Issue
Block a user