1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-09-16 08:56:40 +02:00

Desktop: Resolves #7934: Add Simple Backup as a default plugin (#9360)

This commit is contained in:
Henry Heino
2023-12-11 05:58:45 -08:00
committed by GitHub
parent 6306a0f371
commit 4fc786cf0b
42 changed files with 754 additions and 378 deletions

View File

@@ -0,0 +1,2 @@
plugin-base-repo/
plugin-sources/*

2
packages/default-plugins/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
built-plugins/
plugin-sources/*

View File

@@ -0,0 +1,31 @@
import buildAll from './commands/buildAll';
import editPatch from './commands/editPatch';
const yargs = require('yargs');
const build = () => {
yargs
.usage('$0 <cmd> [args]')
.command('build <outputDir>', 'build all', (yargs: any) => {
yargs.positional('outputDir', {
type: 'string',
describe: 'Path to the parent directory for built output',
});
}, async (args: any) => {
await buildAll(args.outputDir);
process.exit(0);
})
.command('patch <plugin>', 'Edit the patch file for the given plugin ID', (yargs: any) => {
yargs.positional('plugin', {
type: 'string',
describe: 'ID of the plugin to patch',
});
}, async (args: any) => {
await editPatch(args.plugin, null);
process.exit(0);
})
.help()
.argv;
};
build();

View File

@@ -0,0 +1,124 @@
/* eslint-disable no-console */
import { copy, exists, remove, mkdirp, readdir, mkdtemp, readFile, writeFile } from 'fs-extra';
import { join, resolve, basename } from 'path';
import { tmpdir } from 'os';
import { chdir, cwd } from 'process';
import { execCommand } from '@joplin/utils';
import { glob } from 'glob';
import readRepositoryJson from './utils/readRepositoryJson';
import waitForCliInput from './utils/waitForCliInput';
import getPathToPatchFileFor from './utils/getPathToPatchFileFor';
type BeforeEachInstallCallback = (buildDir: string, pluginName: string)=> Promise<void>;
const buildDefaultPlugins = async (outputParentDir: string|null, beforeInstall: BeforeEachInstallCallback) => {
const pluginSourcesDir = resolve(join(__dirname, 'plugin-sources'));
const pluginRepositoryData = await readRepositoryJson(join(__dirname, 'pluginRepositories.json'));
const originalDirectory = cwd();
const logStatus = (...message: string[]) => {
const blue = '\x1b[96m';
const reset = '\x1b[0m';
console.log(blue, ...message, reset);
};
for (const pluginId in pluginRepositoryData) {
const repositoryData = pluginRepositoryData[pluginId];
const buildDir = await mkdtemp(join(tmpdir(), 'default-plugin-build'));
try {
logStatus('Building plugin', pluginId, 'at', buildDir);
const pluginDir = resolve(join(pluginSourcesDir, pluginId));
// Clone the repository if not done yet
if (!(await exists(pluginDir)) || (await readdir(pluginDir)).length === 0) {
logStatus(`Cloning from repository ${repositoryData.cloneUrl}`);
await execCommand(['git', 'clone', '--', repositoryData.cloneUrl, pluginDir]);
chdir(pluginDir);
}
chdir(pluginDir);
const currentCommitHash = (await execCommand(['git', 'rev-parse', 'HEAD~'])).trim();
const expectedCommitHash = repositoryData.commit;
if (currentCommitHash !== expectedCommitHash) {
logStatus(`Switching to commit ${expectedCommitHash}`);
await execCommand(['git', 'switch', repositoryData.branch]);
await execCommand(['git', 'checkout', expectedCommitHash]);
}
logStatus('Copying repository files...');
await copy(pluginDir, buildDir, {
filter: fileName => {
return basename(fileName) !== '.git';
},
});
chdir(buildDir);
logStatus('Initializing repository.');
await execCommand('git init . -b main');
logStatus('Marking manifest as built-in');
const manifestFile = './src/manifest.json';
const manifest = JSON.parse(await readFile(manifestFile, 'utf8'));
manifest._built_in = true;
await writeFile(manifestFile, JSON.stringify(manifest, undefined, '\t'));
logStatus('Creating initial commit.');
await execCommand('git add .');
await execCommand(['git', 'config', 'user.name', 'Build script']);
await execCommand(['git', 'config', 'user.email', '']);
await execCommand(['git', 'commit', '-m', 'Initial commit']);
const patchFile = getPathToPatchFileFor(pluginId);
if (await exists(patchFile)) {
logStatus('Applying patch.');
await execCommand(['git', 'apply', patchFile]);
}
await beforeInstall(buildDir, pluginId);
logStatus('Installing dependencies.');
await execCommand('npm install');
const jplFiles = await glob('publish/*.jpl');
logStatus(`Found built .jpl files: ${JSON.stringify(jplFiles)}`);
if (jplFiles.length === 0) {
throw new Error(`No published files found in ${buildDir}/publish`);
}
if (outputParentDir !== null) {
logStatus(`Checking output directory in ${outputParentDir}`);
const outputDirectory = join(outputParentDir, pluginId);
if (await exists(outputDirectory)) {
await remove(outputDirectory);
}
await mkdirp(outputDirectory);
const sourceFile = jplFiles[0];
const destFile = join(outputDirectory, 'plugin.jpl');
logStatus(`Copying built file from ${sourceFile} to ${destFile}`);
await copy(sourceFile, destFile);
} else {
console.warn('No output directory specified. Not copying built .jpl files.');
}
} catch (error) {
console.error(error);
console.log('Build directory', buildDir);
await waitForCliInput();
throw error;
} finally {
chdir(originalDirectory);
await remove(buildDir);
logStatus('Removed build directory');
}
}
};
export default buildDefaultPlugins;

View File

@@ -0,0 +1,7 @@
import buildDefaultPlugins from '../buildDefaultPlugins';
const buildAll = (outputDirectory: string) => {
return buildDefaultPlugins(outputDirectory, async () => { });
};
export default buildAll;

View File

@@ -0,0 +1,31 @@
import { execCommand } from '@joplin/utils';
import waitForCliInput from '../utils/waitForCliInput';
import { copy } from 'fs-extra';
import { join } from 'path';
import buildDefaultPlugins from '../buildDefaultPlugins';
import getPathToPatchFileFor from '../utils/getPathToPatchFileFor';
const editPatch = async (targetPluginId: string, outputParentDir: string|null) => {
let patchedPlugin = false;
await buildDefaultPlugins(outputParentDir, async (buildDir, pluginId) => {
if (pluginId !== targetPluginId) {
return;
}
// eslint-disable-next-line no-console
console.log('Make changes to', buildDir, 'to create a patch.');
await waitForCliInput();
await execCommand(['sh', '-c', 'git diff -p > diff.diff']);
await copy(join(buildDir, './diff.diff'), getPathToPatchFileFor(pluginId));
patchedPlugin = true;
});
if (!patchedPlugin) {
throw new Error(`No default plugin with ID ${targetPluginId} found!`);
}
};
export default editPatch;

View File

@@ -0,0 +1,25 @@
{
"name": "@joplin/default-plugins",
"version": "2.13.0",
"description": "Default plugins bundler",
"private": true,
"scripts": {
"tsc": "tsc --project tsconfig.json",
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
"patch": "ts-node build.ts patch"
},
"repository": {
"type": "git",
"url": "git+https://github.com/laurent22/joplin.git"
},
"devDependencies": {
"@types/yargs": "17.0.31",
"ts-node": "10.9.1",
"typescript": "5.2.2"
},
"dependencies": {
"@joplin/utils": "~2.13",
"fs-extra": "11.1.1",
"yargs": "17.7.2"
}
}

View File

@@ -0,0 +1,62 @@
diff --git a/src/sevenZip.ts b/src/sevenZip.ts
index ef2a527..d98c777 100644
--- a/src/sevenZip.ts
+++ b/src/sevenZip.ts
@@ -1,21 +1,21 @@
// https://sevenzip.osdn.jp/chm/cmdline/exit_codes.htm
// https://sevenzip.osdn.jp/chm/cmdline/commands/index.htm
import * as _7z from "node-7z";
-import * as sevenBin from "7zip-bin";
-import * as path from "path";
import { exec } from "child_process";
import joplin from "api";
-
-export let pathTo7zip = sevenBin.path7za;
-
-export namespace sevenZip {
- export async function updateBinPath() {
- pathTo7zip = path.join(
- await joplin.plugins.installationDir(),
- "7zip-bin",
- pathTo7zip
- );
- }
+const sevenBin = joplin.require("7zip-bin");
+
+ export let pathTo7zip = sevenBin.path7za;
+
+ export namespace sevenZip {
+ export async function updateBinPath() {
+ // Not necessary with 7zip required from Joplin
+ // pathTo7zip = path.join(
+ // await joplin.plugins.installationDir(),
+ // "7zip-bin",
+ // pathTo7zip
+ // );
+ }
export async function setExecutionFlag() {
if (process.platform !== "win32") {
diff --git a/webpack.config.js b/webpack.config.js
index 34a1797..7b2a480 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -200,15 +200,9 @@ const pluginConfig = { ...baseConfig, entry: './src/index.ts',
path: distDir,
},
plugins: [
- new CopyPlugin({
- patterns: [
- {
- from: '**/*',
- context: path.resolve(__dirname, 'node_modules','7zip-bin'),
- to: path.resolve(__dirname, 'dist/7zip-bin/'),
- },
- ]
- }),
+ // Removed a CopyPlugin (added by Simple Backup, not necessary when using
+ // Joplin's built-in 7zip)
+
new CopyPlugin({
patterns: [
{

View File

@@ -0,0 +1,7 @@
{
"io.github.jackgruber.backup": {
"cloneUrl": "https://github.com/JackGruber/joplin-plugin-backup.git",
"branch": "master",
"commit": "021085cc37ed83a91a7950744e462782e27c04a6"
}
}

View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"**/node_modules",
"plugin-sources/",
"plugin-base-repo/"
]
}

View File

@@ -0,0 +1,9 @@
import { join, dirname } from 'path';
const getPathToPatchFileFor = (pluginName: string) => {
const rootDir = dirname(__dirname);
return join(rootDir, 'plugin-patches', `${pluginName}.diff`);
};
export default getPathToPatchFileFor;

View File

@@ -0,0 +1,37 @@
import { readFile } from 'fs-extra';
export interface RepositoryData {
cloneUrl: string;
branch: string;
commit: string;
}
export interface AllRepositoryData {
[pluginId: string]: RepositoryData;
}
const readRepositoryJson = async (repositoryDataFilepath: string): Promise<AllRepositoryData> => {
const fileContent = await readFile(repositoryDataFilepath, 'utf8');
const parsedJson = JSON.parse(fileContent);
// Validate
for (const pluginId in parsedJson) {
if (typeof parsedJson[pluginId] !== 'object') {
throw new Error('pluginRepositories should map from plugin IDs to objects.');
}
const assertPropertyIsString = (propertyName: string) => {
if (typeof parsedJson[pluginId][propertyName] !== 'string') {
throw new Error(`Plugin ${pluginId} should have field '${propertyName}' of type string.`);
}
};
assertPropertyIsString('cloneUrl');
assertPropertyIsString('branch');
assertPropertyIsString('commit');
}
return parsedJson;
};
export default readRepositoryJson;

View File

@@ -0,0 +1,23 @@
const readline = require('readline/promises');
/* eslint-disable no-console */
let readlineInterface: any = null;
const waitForCliInput = async () => {
readlineInterface ??= readline.createInterface({
input: process.stdin,
output: process.stdout,
});
if (process.stdin.isTTY) {
const green = '\x1b[92m';
const reset = '\x1b[0m';
await readlineInterface.question(`${green}[Press enter to continue]${reset}`);
console.log('Continuing...');
} else {
console.warn('Input is not from a TTY -- not waiting for input.');
}
};
export default waitForCliInput;