Tools: Moved some utility functions to @joplin/utils to reduce dependencies between packages

pull/7928/head
Laurent Cozic 2023-03-19 15:37:07 +00:00
parent 1548ea18e1
commit 3e52411bc4
37 changed files with 308 additions and 229 deletions

View File

@ -14,7 +14,8 @@
"@joplin/turndown-plugin-gfm",
"@joplin/tools",
"@joplin/react-native-saf-x",
"@joplin/react-native-alarm-notification"
"@joplin/react-native-alarm-notification",
"@joplin/utils"
]
}
]

View File

@ -329,6 +329,7 @@
"packages/renderer/MdToHtml/rules/sanitize_html.js": true,
"packages/server/db-*.sqlite": true,
"packages/server/dist/": true,
"packages/utils/dist/": true,
"packages/server/temp": true,
"packages/server/test.pid": true,
"phpunit.xml": true,

View File

@ -8,7 +8,7 @@ const Resource = require('@joplin/lib/models/Resource').default;
const Setting = require('@joplin/lib/models/Setting').default;
const reducer = require('@joplin/lib/reducer').default;
const { defaultState } = require('@joplin/lib/reducer');
const { splitCommandString } = require('@joplin/lib/string-utils.js');
const { splitCommandString } = require('@joplin/utils');
const { reg } = require('@joplin/lib/registry.js');
const { _ } = require('@joplin/lib/locale');
const shim = require('@joplin/lib/shim').default;

View File

@ -9,7 +9,8 @@ const Tag = require('@joplin/lib/models/Tag').default;
const Setting = require('@joplin/lib/models/Setting').default;
const { reg } = require('@joplin/lib/registry.js');
const { fileExtension } = require('@joplin/lib/path-utils');
const { splitCommandString, splitCommandBatch } = require('@joplin/lib/string-utils');
const { splitCommandString } = require('@joplin/utils');
const { splitCommandBatch } = require('@joplin/lib/string-utils');
const { _ } = require('@joplin/lib/locale');
const fs = require('fs-extra');
const { cliUtils } = require('./cli-utils.js');

View File

@ -1,6 +1,6 @@
const fs = require('fs-extra');
const BaseCommand = require('./base-command').default;
const { splitCommandString } = require('@joplin/lib/string-utils.js');
const { splitCommandString } = require('@joplin/utils');
const uuid = require('@joplin/lib/uuid').default;
const { app } = require('./app.js');
const { _ } = require('@joplin/lib/locale');

View File

@ -30,7 +30,8 @@
2019,
2020,
2021,
2022
2022,
2023
],
"owner": "Laurent Cozic"
},
@ -42,6 +43,7 @@
"dependencies": {
"@joplin/lib": "~2.10",
"@joplin/renderer": "~2.10",
"@joplin/utils": "~2.10",
"aws-sdk": "2.1290.0",
"chalk": "4.1.2",
"compare-version": "0.1.2",

View File

@ -19,7 +19,7 @@
import * as fs from 'fs-extra';
import { homedir } from 'os';
import { execCommand2 } from '@joplin/tools/tool-utils';
import { execCommand } from '@joplin/utils';
import { chdir } from 'process';
const minUserNum = 1;
@ -66,7 +66,7 @@ const processUser = async (userNum: number) => {
await chdir(cliDir);
await execCommand2(['yarn', 'run', 'start-no-build', '--', '--profile', profileDir, 'batch', commandFile]);
await execCommand(['yarn', 'run', 'start-no-build', '--', '--profile', profileDir, 'batch', commandFile]);
} catch (error) {
console.error(`Could not process user ${userNum}:`, error);
} finally {
@ -90,7 +90,7 @@ const main = async () => {
// Build the app once before starting, because we'll use start-no-build to
// run the scripts (faster)
await execCommand2(['yarn', 'run', 'build']);
await execCommand(['yarn', 'run', 'build']);
const focusUserNum = 0;

View File

@ -20,7 +20,7 @@ import Folder from './models/Folder';
import BaseItem from './models/BaseItem';
import Note from './models/Note';
import Tag from './models/Tag';
const { splitCommandString } = require('./string-utils.js');
import { splitCommandString } from '@joplin/utils';
import { reg } from './registry';
import time from './time';
import BaseSyncTarget from './BaseSyncTarget';
@ -706,7 +706,7 @@ export default class BaseApplication {
flagContent = flagContent.trim();
let flags = splitCommandString(flagContent);
let flags: any = splitCommandString(flagContent);
flags.splice(0, 0, 'cmd');
flags.splice(0, 0, 'node');

View File

@ -38,6 +38,7 @@
"@joplin/renderer": "^2.10.2",
"@joplin/turndown": "^4.0.65",
"@joplin/turndown-plugin-gfm": "^1.0.47",
"@joplin/utils": "~2.10",
"@types/nanoid": "3.0.0",
"async-mutex": "0.4.0",
"base-64": "1.0.0",

View File

@ -1,6 +1,6 @@
/* eslint-disable import/prefer-default-export */
const { splitCommandString } = require('../../string-utils');
import { splitCommandString } from '@joplin/utils';
import { spawn } from 'child_process';
import Logger from '../../Logger';
import Setting from '../../models/Setting';

View File

@ -138,80 +138,6 @@ function commandArgumentsToString(args) {
return output.join(' ');
}
function splitCommandString(command, options = null) {
options = options || {};
if (!('handleEscape' in options)) {
options.handleEscape = true;
}
const args = [];
let state = 'start';
let current = '';
let quote = '"';
let escapeNext = false;
for (let i = 0; i < command.length; i++) {
const c = command[i];
if (state === 'quotes') {
if (c !== quote) {
current += c;
} else {
args.push(current);
current = '';
state = 'start';
}
continue;
}
if (escapeNext) {
current += c;
escapeNext = false;
continue;
}
if (c === '\\' && options.handleEscape) {
escapeNext = true;
continue;
}
if (c === '"' || c === '\'') {
state = 'quotes';
quote = c;
continue;
}
if (state === 'arg') {
if (c === ' ' || c === '\t') {
args.push(current);
current = '';
state = 'start';
} else {
current += c;
}
continue;
}
if (c !== ' ' && c !== '\t') {
state = 'arg';
current += c;
}
}
if (state === 'quotes') {
throw new Error(`Unclosed quote in command line: ${command}`);
}
if (current !== '') {
args.push(current);
}
if (args.length <= 0) {
throw new Error('Empty command line');
}
return args;
}
function splitCommandBatch(commandBatch) {
const commandLines = [];
const eol = '\n';
@ -368,4 +294,4 @@ function scriptType(s) {
return 'en';
}
module.exports = Object.assign({ formatCssSize, camelCaseToDash, removeDiacritics, substrWithEllipsis, nextWhitespaceIndex, escapeFilename, wrap, splitCommandString, splitCommandBatch, padLeft, toTitleCase, urlDecode, escapeHtml, surroundKeywords, scriptType, commandArgumentsToString }, stringUtilsCommon);
module.exports = Object.assign({ formatCssSize, camelCaseToDash, removeDiacritics, substrWithEllipsis, nextWhitespaceIndex, escapeFilename, wrap, splitCommandBatch, padLeft, toTitleCase, urlDecode, escapeHtml, surroundKeywords, scriptType, commandArgumentsToString }, stringUtilsCommon);

View File

@ -9,7 +9,7 @@ import * as path from 'path';
import * as process from 'process';
import validatePluginId from '@joplin/lib/services/plugins/utils/validatePluginId';
import validatePluginVersion from '@joplin/lib/services/plugins/utils/validatePluginVersion';
import { execCommand2, resolveRelativePathWithinDir, gitPullTry, gitRepoCleanTry, gitRepoClean } from '@joplin/tools/tool-utils.js';
import { resolveRelativePathWithinDir, gitPullTry, gitRepoCleanTry, gitRepoClean } from '@joplin/tools/tool-utils.js';
import checkIfPluginCanBeAdded from './lib/checkIfPluginCanBeAdded';
import updateReadme from './lib/updateReadme';
import { NpmPackage } from './lib/types';
@ -17,6 +17,7 @@ import gitCompareUrl from './lib/gitCompareUrl';
import commandUpdateRelease from './commands/updateRelease';
import { isJoplinPluginPackage, readJsonFile } from './lib/utils';
import { applyManifestOverrides, getObsoleteManifests, readManifestOverrides } from './lib/overrideUtils';
import { execCommand } from '@joplin/utils';
function pluginInfoFromSearchResults(results: any[]): NpmPackage[] {
const output: NpmPackage[] = [];
@ -49,7 +50,7 @@ async function checkPluginRepository(dirPath: string, dryRun: boolean) {
async function extractPluginFilesFromPackage(existingManifests: any, workDir: string, packageName: string, destDir: string): Promise<any> {
const previousDir = chdir(workDir);
await execCommand2(`npm install ${packageName} --save --ignore-scripts`, { showStderr: false, showStdout: false });
await execCommand(`npm install ${packageName} --save --ignore-scripts`, { showStderr: false, showStdout: false });
const pluginDir = resolveRelativePathWithinDir(workDir, 'node_modules', packageName, 'publish');
@ -154,7 +155,7 @@ async function processNpmPackage(npmPackage: NpmPackage, repoDir: string, dryRun
await fs.mkdirp(packageTempDir);
chdir(packageTempDir);
await execCommand2('npm init --yes --loglevel silent', { quiet: true });
await execCommand('npm init --yes --loglevel silent', { quiet: true });
let actionType: ProcessingActionType = ProcessingActionType.Update;
let manifests: any = {};
@ -200,8 +201,8 @@ async function processNpmPackage(npmPackage: NpmPackage, repoDir: string, dryRun
if (!dryRun) {
if (!(await gitRepoClean())) {
await execCommand2('git add -A', { showStdout: false });
await execCommand2(['git', 'commit', '-m', commitMessage(actionType, manifest, previousManifest, npmPackage, error)], { showStdout: false });
await execCommand('git add -A', { showStdout: false });
await execCommand(['git', 'commit', '-m', commitMessage(actionType, manifest, previousManifest, npmPackage, error)], { showStdout: false });
} else {
console.info('Nothing to commit');
}
@ -227,14 +228,14 @@ async function commandBuild(args: CommandBuildArgs) {
if (!dryRun) {
if (!(await gitRepoClean())) {
console.info('Updating README...');
await execCommand2('git add -A');
await execCommand2('git commit -m "Update README"');
await execCommand('git add -A');
await execCommand('git commit -m "Update README"');
}
}
chdir(previousDir);
const searchResults = (await execCommand2('npm search joplin-plugin --searchlimit 5000 --json', { showStdout: false, showStderr: false })).trim();
const searchResults = (await execCommand('npm search joplin-plugin --searchlimit 5000 --json', { showStdout: false, showStderr: false })).trim();
const npmPackages = pluginInfoFromSearchResults(JSON.parse(searchResults));
for (const npmPackage of npmPackages) {
@ -245,11 +246,11 @@ async function commandBuild(args: CommandBuildArgs) {
await commandUpdateRelease(args);
if (!(await gitRepoClean())) {
await execCommand2('git add -A');
await execCommand2('git commit -m "Update stats"');
await execCommand('git add -A');
await execCommand('git commit -m "Update stats"');
}
await execCommand2('git push');
await execCommand('git push');
}
}

View File

@ -20,6 +20,7 @@
"dependencies": {
"@joplin/lib": "^2.10.2",
"@joplin/tools": "^2.10.2",
"@joplin/utils": "~2.10",
"fs-extra": "11.1.0",
"gh-release-assets": "2.0.1",
"node-fetch": "2.6.7",

View File

@ -1,5 +1,6 @@
import { execCommand2, rootDir } from './tool-utils';
import { rootDir } from './tool-utils';
import * as moment from 'moment';
import { execCommand } from '@joplin/utils';
interface Argv {
dryRun?: boolean;
@ -35,7 +36,7 @@ async function main() {
const buildDate = moment(new Date().getTime()).format('YYYY-MM-DDTHH:mm:ssZ');
let revision = '';
try {
revision = await execCommand2('git rev-parse --short HEAD', { showStdout: false });
revision = await execCommand('git rev-parse --short HEAD', { showStdout: false });
} catch (error) {
console.info('Could not get git commit: metadata revision field will be empty');
}
@ -62,11 +63,11 @@ async function main() {
return;
}
await execCommand2(dockerCommand);
await execCommand(dockerCommand);
for (const tag of dockerTags) {
await execCommand2(`docker tag "${repository}:${imageVersion}" "${repository}:${tag}"`);
if (pushImages) await execCommand2(`docker push ${repository}:${tag}`);
await execCommand(`docker tag "${repository}:${imageVersion}" "${repository}:${tag}"`);
if (pushImages) await execCommand(`docker push ${repository}:${tag}`);
}
}

View File

@ -1,8 +1,8 @@
import { join } from 'path';
import { execCommand2 } from './tool-utils';
import { pathExists, mkdir, readFile, move, remove, writeFile } from 'fs-extra';
import { DefaultPluginsInfo } from '@joplin/lib/services/plugins/PluginService';
import getDefaultPluginsInfo from '@joplin/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo';
import { execCommand } from '@joplin/utils';
const fetch = require('node-fetch');
interface PluginAndVersion {
@ -41,7 +41,7 @@ async function downloadFile(url: string, outputPath: string) {
export async function extractPlugins(currentDir: string, defaultPluginDir: string, downloadedPluginsNames: PluginIdAndName): Promise<void> {
for (const pluginId of Object.keys(downloadedPluginsNames)) {
await execCommand2(`tar xzf ${currentDir}/${downloadedPluginsNames[pluginId]}`, { quiet: true });
await execCommand(`tar xzf ${currentDir}/${downloadedPluginsNames[pluginId]}`, { quiet: true });
await move(`package/publish/${pluginId}.jpl`, `${defaultPluginDir}/${pluginId}/plugin.jpl`, { overwrite: true });
await move(`package/publish/${pluginId}.json`, `${defaultPluginDir}/${pluginId}/manifest.json`, { overwrite: true });
await remove(`${downloadedPluginsNames[pluginId]}`);

View File

@ -1,4 +1,5 @@
import { execCommand2, rootDir } from './tool-utils';
import { execCommand } from '@joplin/utils';
import { rootDir } from './tool-utils';
const sqlts = require('@rmp135/sql-ts').default;
const fs = require('fs-extra');
@ -6,7 +7,7 @@ const fs = require('fs-extra');
async function main() {
// Run the CLI app once so as to generate the database file
process.chdir(`${rootDir}/packages/app-cli`);
await execCommand2('yarn start version');
await execCommand('yarn start version');
const sqlTsConfig = {
'client': 'sqlite3',

View File

@ -36,6 +36,7 @@ module.exports = {
'packages/fork-sax/**',
'packages/lib/plugin_types/**',
'packages/server/**',
'packages/utils/**',
],
}).filter(f => !f.endsWith('.d.ts'));

View File

@ -1,6 +1,7 @@
import { readdir, stat, writeFile } from 'fs-extra';
import { chdir, cwd } from 'process';
import { execCommand2, rootDir } from './tool-utils';
import { rootDir } from './tool-utils';
import { execCommand } from '@joplin/utils';
import yargs = require('yargs');
import { rtrimSlashes } from '@joplin/lib/path-utils';
@ -13,7 +14,7 @@ interface LicenseInfo {
const getLicenses = async (directory: string): Promise<Record<string, LicenseInfo>> => {
const previousDir = cwd();
await chdir(directory);
const result = await execCommand2(['license-checker-rseidelsohn', '--production', '--json'], { quiet: true });
const result = await execCommand(['license-checker-rseidelsohn', '--production', '--json'], { quiet: true });
const info: Record<string, LicenseInfo> = JSON.parse(result);
if (!info) throw new Error(`Could not parse JSON: ${directory}`);
await chdir(previousDir);

View File

@ -22,6 +22,7 @@
"dependencies": {
"@joplin/lib": "^2.10.2",
"@joplin/renderer": "^2.10.2",
"@joplin/utils": "~2.10",
"@types/node-fetch": "2.6.2",
"@types/yargs": "17.0.20",
"dayjs": "1.11.7",

View File

@ -1,5 +1,6 @@
import { execCommand } from '@joplin/utils';
import * as fs from 'fs-extra';
import { execCommandVerbose, execCommandWithPipes, githubRelease, githubOauthToken, fileExists, gitPullTry, completeReleaseWithChangelog, execCommand2 } from './tool-utils';
import { execCommandVerbose, execCommandWithPipes, githubRelease, githubOauthToken, fileExists, gitPullTry, completeReleaseWithChangelog } from './tool-utils';
const path = require('path');
const fetch = require('node-fetch');
const uriTemplate = require('uri-template');
@ -150,7 +151,7 @@ async function main() {
const isPreRelease = !('type' in argv) || argv.type === 'prerelease';
process.chdir(rnDir);
await execCommand2('yarn run build', { showStdout: false });
await execCommand('yarn run build', { showStdout: false });
if (isPreRelease) console.info('Creating pre-release');
console.info('Updating version numbers in build.gradle...');

View File

@ -1,4 +1,5 @@
import { execCommand2, rootDir, completeReleaseWithChangelog } from './tool-utils';
import { execCommand } from '@joplin/utils';
import { rootDir, completeReleaseWithChangelog } from './tool-utils';
const appDir = `${rootDir}/packages/app-cli`;
const changelogPath = `${rootDir}/readme/changelog_cli.md`;
@ -8,19 +9,19 @@ const changelogPath = `${rootDir}/readme/changelog_cli.md`;
async function main() {
process.chdir(appDir);
await execCommand2('git pull');
await execCommand('git pull');
const newVersion = (await execCommand2('npm version patch')).trim();
const newVersion = (await execCommand('npm version patch')).trim();
console.info(`Building ${newVersion}...`);
const newTag = `cli-${newVersion}`;
await execCommand2('touch app/main.js');
await execCommand2('yarn run build');
await execCommand2('cp ../../README.md build/');
await execCommand('touch app/main.js');
await execCommand('yarn run build');
await execCommand('cp ../../README.md build/');
process.chdir(`${appDir}/build`);
await execCommand2('npm publish');
await execCommand('npm publish');
await completeReleaseWithChangelog(changelogPath, newVersion, newTag, 'CLI', false);
}

View File

@ -1,4 +1,5 @@
import { execCommand2, gitCurrentBranch, githubRelease, gitPullTry, rootDir } from './tool-utils';
import { execCommand } from '@joplin/utils';
import { gitCurrentBranch, githubRelease, gitPullTry, rootDir } from './tool-utils';
const appDir = `${rootDir}/packages/app-desktop`;
@ -11,16 +12,16 @@ async function main() {
console.info(`Running from: ${process.cwd()}`);
const version = (await execCommand2('npm version patch')).trim();
const version = (await execCommand('npm version patch')).trim();
const tagName = version;
console.info(`New version number: ${version}`);
await execCommand2('git add -A');
await execCommand2(`git commit -m "Desktop release ${version}"`);
await execCommand2(`git tag ${tagName}`);
await execCommand2('git push');
await execCommand2('git push --tags');
await execCommand('git add -A');
await execCommand(`git commit -m "Desktop release ${version}"`);
await execCommand(`git tag ${tagName}`);
await execCommand('git push');
await execCommand('git push --tags');
const releaseOptions = { isDraft: true, isPreRelease: !!argv.beta };

View File

@ -1,5 +1,6 @@
import { execCommand } from '@joplin/utils';
import { chdir } from 'process';
import { rootDir, gitPullTry, execCommand2, releaseFinalGitCommands } from './tool-utils';
import { rootDir, gitPullTry, releaseFinalGitCommands } from './tool-utils';
const workDir = `${rootDir}/packages/plugin-repo-cli`;
@ -7,18 +8,18 @@ async function main() {
await gitPullTry();
chdir(rootDir);
await execCommand2('yarn run tsc');
await execCommand('yarn run tsc');
chdir(workDir);
await execCommand2('yarn run dist');
await execCommand('yarn run dist');
const newVersion = (await execCommand2('npm version patch')).trim();
const newVersion = (await execCommand('npm version patch')).trim();
console.info(`New version: ${newVersion}`);
const tagName = `plugin-repo-cli-${newVersion}`;
console.info(`Tag name: ${tagName}`);
await execCommand2('npm publish');
await execCommand('npm publish');
console.info(releaseFinalGitCommands('Plugin Repo CLI', newVersion, tagName));
}

View File

@ -1,4 +1,5 @@
import { execCommand2, rootDir, gitPullTry, completeReleaseWithChangelog } from './tool-utils';
import { execCommand } from '@joplin/utils';
import { rootDir, gitPullTry, completeReleaseWithChangelog } from './tool-utils';
const serverDir = `${rootDir}/packages/server`;
@ -12,7 +13,7 @@ async function main() {
await gitPullTry();
process.chdir(serverDir);
const version = (await execCommand2('npm version patch')).trim();
const version = (await execCommand('npm version patch')).trim();
const versionSuffix = ''; // isPreRelease ? '-beta' : '';
const tagName = `server-${version}${versionSuffix}`;

View File

@ -1,6 +1,7 @@
import yargs = require('yargs');
import { chdir } from 'process';
import { execCommand2, rootDir } from './tool-utils';
import { rootDir } from './tool-utils';
import { execCommand } from '@joplin/utils';
const main = async () => {
const argv = await yargs.argv;
@ -10,7 +11,7 @@ const main = async () => {
chdir(rootDir);
try {
await execCommand2(['yarn', 'run', 'cspell'].concat(filePaths), { showStderr: false, showStdout: false });
await execCommand(['yarn', 'run', 'cspell'].concat(filePaths), { showStderr: false, showStdout: false });
} catch (error) {
if (!error.stdout.trim()) return;

View File

@ -1,4 +1,4 @@
import { execCommand2 } from './tool-utils';
import { execCommand } from '@joplin/utils';
async function main() {
const argv = require('yargs').argv;
@ -6,9 +6,9 @@ async function main() {
const version = argv._[0];
await execCommand2(`docker pull "joplin/server:${version}"`);
await execCommand2(`docker tag "joplin/server:${version}" "joplin/server:latest"`);
await execCommand2('docker push joplin/server:latest');
await execCommand(`docker pull "joplin/server:${version}"`);
await execCommand(`docker tag "joplin/server:${version}" "joplin/server:latest"`);
await execCommand('docker push joplin/server:latest');
}
if (require.main === module) {

View File

@ -1,9 +1,9 @@
import * as fs from 'fs-extra';
import { readCredentialFile } from '@joplin/lib/utils/credentialFiles';
import { execCommand as execCommand2, commandToString } from '@joplin/utils';
const fetch = require('node-fetch');
const execa = require('execa');
const { splitCommandString } = require('@joplin/lib/string-utils');
const moment = require('moment');
export interface GitHubReleaseAsset {
@ -20,23 +20,6 @@ export interface GitHubRelease {
draft: boolean;
}
function quotePath(path: string) {
if (!path) return '';
if (path.indexOf('"') < 0 && path.indexOf(' ') < 0) return path;
path = path.replace(/"/, '\\"');
return `"${path}"`;
}
function commandToString(commandName: string, args: string[] = []) {
const output = [quotePath(commandName)];
for (const arg of args) {
output.push(quotePath(arg));
}
return output.join(' ');
}
async function insertChangelog(tag: string, changelogPath: string, changelog: string, isPrerelease: boolean, repoTagUrl: string = '') {
repoTagUrl = repoTagUrl || 'https://github.com/laurent22/joplin/releases/tag';
@ -163,52 +146,6 @@ export function execCommandVerbose(commandName: string, args: string[] = []) {
return promise;
}
interface ExecCommandOptions {
showInput?: boolean;
showStdout?: boolean;
showStderr?: boolean;
quiet?: boolean;
}
// There's lot of execCommandXXX functions, but eventually all scripts should
// use the one below, which supports:
//
// - Printing the command being executed
// - Printing the output in real time (piping to stdout)
// - Returning the command result as string
export async function execCommand2(command: string | string[], options: ExecCommandOptions = null): Promise<string> {
options = {
showInput: true,
showStdout: true,
showStderr: true,
quiet: false,
...options,
};
if (options.quiet) {
options.showInput = false;
options.showStdout = false;
options.showStderr = false;
}
if (options.showInput) {
if (typeof command === 'string') {
console.info(`> ${command}`);
} else {
console.info(`> ${commandToString(command[0], command.slice(1))}`);
}
}
const args: string[] = typeof command === 'string' ? splitCommandString(command) : command as string[];
const executableName = args[0];
args.splice(0, 1);
const promise = execa(executableName, args);
if (options.showStdout) promise.stdout.pipe(process.stdout);
if (options.showStderr) promise.stdout.pipe(process.stderr);
const result = await promise;
return result.stdout.trim();
}
export function execCommandWithPipes(executable: string, args: string[]) {
const spawn = require('child_process').spawn;

View File

@ -1,5 +1,6 @@
import { execCommand } from '@joplin/utils';
import { chdir } from 'process';
import { execCommand2, rootDir, gitRepoCleanTry } from './tool-utils';
import { rootDir, gitRepoCleanTry } from './tool-utils';
import updateDownloadPage from './website/updateDownloadPage';
async function main() {
@ -7,23 +8,23 @@ async function main() {
if (doGitOperations) {
await gitRepoCleanTry();
await execCommand2(['git', 'pull', '--rebase']);
await execCommand(['git', 'pull', '--rebase']);
}
await execCommand2(['node', `${rootDir}/packages/tools/update-readme-download.js`]);
await execCommand2(['node', `${rootDir}/packages/tools/build-release-stats.js`, '--types=changelog']);
await execCommand2(['node', `${rootDir}/packages/tools/build-release-stats.js`, '--types=stats', '--update-interval=30']);
await execCommand2(['node', `${rootDir}/packages/tools/update-readme-sponsors.js`]);
await execCommand2(['node', `${rootDir}/packages/tools/build-welcome.js`]);
await execCommand(['node', `${rootDir}/packages/tools/update-readme-download.js`]);
await execCommand(['node', `${rootDir}/packages/tools/build-release-stats.js`, '--types=changelog']);
await execCommand(['node', `${rootDir}/packages/tools/build-release-stats.js`, '--types=stats', '--update-interval=30']);
await execCommand(['node', `${rootDir}/packages/tools/update-readme-sponsors.js`]);
await execCommand(['node', `${rootDir}/packages/tools/build-welcome.js`]);
chdir(rootDir);
await execCommand2(['yarn', 'run', 'buildApiDoc']);
await execCommand(['yarn', 'run', 'buildApiDoc']);
await updateDownloadPage();
if (doGitOperations) {
await execCommand2(['git', 'add', '-A']);
await execCommand2(['git', 'commit', '-m', 'Update Markdown doc']);
await execCommand2(['git', 'pull', '--rebase']);
await execCommand2(['git', 'push']);
await execCommand(['git', 'add', '-A']);
await execCommand(['git', 'commit', '-m', 'Update Markdown doc']);
await execCommand(['git', 'pull', '--rebase']);
await execCommand(['git', 'push']);
}
}

2
packages/utils/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
dist/
node_modules/

3
packages/utils/README.md Normal file
View File

@ -0,0 +1,3 @@
# Utility package
Those are generic utility functions that can be imported from any other packages. This package however shouldn't have any dependency to any other Joplin package, so that it can be imported without having to import "lib", "renderer" and so on. This is particulary important for Docker images, so that they can be trimmed down to only what's needed.

View File

@ -0,0 +1,16 @@
const quotePath = (path: string) => {
if (!path) return '';
if (path.indexOf('"') < 0 && path.indexOf(' ') < 0) return path;
path = path.replace(/"/, '\\"');
return `"${path}"`;
};
export default (commandName: string, args: string[] = []) => {
const output = [quotePath(commandName)];
for (const arg of args) {
output.push(quotePath(arg));
}
return output.join(' ');
};

View File

@ -0,0 +1,44 @@
import * as execa from 'execa';
import commandToString from './commandToString';
import splitCommandString from './splitCommandString';
import { stdout } from 'process';
interface ExecCommandOptions {
showInput?: boolean;
showStdout?: boolean;
showStderr?: boolean;
quiet?: boolean;
}
export default async (command: string | string[], options: ExecCommandOptions | null = null): Promise<string> => {
options = {
showInput: true,
showStdout: true,
showStderr: true,
quiet: false,
...options,
};
if (options.quiet) {
options.showInput = false;
options.showStdout = false;
options.showStderr = false;
}
if (options.showInput) {
if (typeof command === 'string') {
stdout.write(`> ${command}\n`);
} else {
stdout.write(`> ${commandToString(command[0], command.slice(1))}\n`);
}
}
const args: string[] = typeof command === 'string' ? splitCommandString(command) : command as string[];
const executableName = args[0];
args.splice(0, 1);
const promise = execa(executableName, args);
if (options.showStdout && promise.stdout) promise.stdout.pipe(process.stdout);
if (options.showStderr && promise.stdout) promise.stdout.pipe(process.stderr);
const result = await promise;
return result.stdout.trim();
};

9
packages/utils/index.ts Normal file
View File

@ -0,0 +1,9 @@
import execCommand from './execCommand';
import commandToString from './commandToString';
import splitCommandString from './splitCommandString';
export {
execCommand,
commandToString,
splitCommandString,
};

View File

@ -0,0 +1,21 @@
{
"name": "@joplin/utils",
"version": "2.10.0",
"description": "Utilities for Joplin",
"repository": "https://github.com/laurent22/joplin/tree/dev/packages/utils",
"main": "dist/index.js",
"publishConfig": {
"access": "public"
},
"scripts": {
"tsc": "tsc --project tsconfig.json",
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
"test": "jest --verbose=false",
"test-ci": "yarn test"
},
"author": "",
"license": "AGPL-3.0-or-later",
"dependencies": {
"execa": "5.1.1"
}
}

View File

@ -0,0 +1,73 @@
export default (command: string, options: any = null) => {
options = options || {};
if (!('handleEscape' in options)) {
options.handleEscape = true;
}
const args = [];
let state = 'start';
let current = '';
let quote = '"';
let escapeNext = false;
for (let i = 0; i < command.length; i++) {
const c = command[i];
if (state === 'quotes') {
if (c !== quote) {
current += c;
} else {
args.push(current);
current = '';
state = 'start';
}
continue;
}
if (escapeNext) {
current += c;
escapeNext = false;
continue;
}
if (c === '\\' && options.handleEscape) {
escapeNext = true;
continue;
}
if (c === '"' || c === '\'') {
state = 'quotes';
quote = c;
continue;
}
if (state === 'arg') {
if (c === ' ' || c === '\t') {
args.push(current);
current = '';
state = 'start';
} else {
current += c;
}
continue;
}
if (c !== ' ' && c !== '\t') {
state = 'arg';
current += c;
}
}
if (state === 'quotes') {
throw new Error(`Unclosed quote in command line: ${command}`);
}
if (current !== '') {
args.push(current);
}
if (args.length <= 0) {
throw new Error('Empty command line');
}
return args;
};

View File

@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"strict": true,
"strictNullChecks": true
},
"rootDir": ".",
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"**/node_modules",
],
}

View File

@ -4880,6 +4880,7 @@ __metadata:
"@joplin/renderer": ^2.10.2
"@joplin/turndown": ^4.0.65
"@joplin/turndown-plugin-gfm": ^1.0.47
"@joplin/utils": ~2.10
"@types/fs-extra": 9.0.13
"@types/jest": 29.2.6
"@types/js-yaml": 4.0.5
@ -4985,6 +4986,7 @@ __metadata:
dependencies:
"@joplin/lib": ^2.10.2
"@joplin/tools": ^2.10.2
"@joplin/utils": ~2.10
"@types/fs-extra": 9.0.13
"@types/jest": 29.2.6
"@types/node": 18.11.18
@ -5138,6 +5140,7 @@ __metadata:
"@joplin/fork-htmlparser2": ^4.1.43
"@joplin/lib": ^2.10.2
"@joplin/renderer": ^2.10.2
"@joplin/utils": ~2.10
"@rmp135/sql-ts": 1.16.0
"@types/fs-extra": 9.0.13
"@types/jest": 29.2.6
@ -5201,6 +5204,14 @@ __metadata:
languageName: unknown
linkType: soft
"@joplin/utils@workspace:packages/utils, @joplin/utils@~2.10":
version: 0.0.0-use.local
resolution: "@joplin/utils@workspace:packages/utils"
dependencies:
execa: 5.1.1
languageName: unknown
linkType: soft
"@jridgewell/gen-mapping@npm:^0.1.0":
version: 0.1.1
resolution: "@jridgewell/gen-mapping@npm:0.1.1"
@ -15681,22 +15692,7 @@ __metadata:
languageName: node
linkType: hard
"execa@npm:^1.0.0":
version: 1.0.0
resolution: "execa@npm:1.0.0"
dependencies:
cross-spawn: ^6.0.0
get-stream: ^4.0.0
is-stream: ^1.1.0
npm-run-path: ^2.0.0
p-finally: ^1.0.0
signal-exit: ^3.0.0
strip-eof: ^1.0.0
checksum: ddf1342c1c7d02dd93b41364cd847640f6163350d9439071abf70bf4ceb1b9b2b2e37f54babb1d8dc1df8e0d8def32d0e81e74a2e62c3e1d70c303eb4c306bc4
languageName: node
linkType: hard
"execa@npm:^5.0.0, execa@npm:^5.1.1":
"execa@npm:5.1.1, execa@npm:^5.0.0, execa@npm:^5.1.1":
version: 5.1.1
resolution: "execa@npm:5.1.1"
dependencies:
@ -15713,6 +15709,21 @@ __metadata:
languageName: node
linkType: hard
"execa@npm:^1.0.0":
version: 1.0.0
resolution: "execa@npm:1.0.0"
dependencies:
cross-spawn: ^6.0.0
get-stream: ^4.0.0
is-stream: ^1.1.0
npm-run-path: ^2.0.0
p-finally: ^1.0.0
signal-exit: ^3.0.0
strip-eof: ^1.0.0
checksum: ddf1342c1c7d02dd93b41364cd847640f6163350d9439071abf70bf4ceb1b9b2b2e37f54babb1d8dc1df8e0d8def32d0e81e74a2e62c3e1d70c303eb4c306bc4
languageName: node
linkType: hard
"execa@npm:^7.0.0":
version: 7.1.1
resolution: "execa@npm:7.1.1"
@ -20444,6 +20455,7 @@ __metadata:
"@joplin/lib": ~2.10
"@joplin/renderer": ~2.10
"@joplin/tools": ~2.10
"@joplin/utils": ~2.10
"@types/fs-extra": 9.0.13
"@types/jest": 29.2.6
"@types/node": 18.11.18