1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-08 13:06:15 +02:00

Plugins: Updated plugin repo script

This commit is contained in:
Laurent Cozic 2021-01-18 14:37:27 +00:00
parent 0d2bf6d787
commit 52da072f9a
15 changed files with 8610 additions and 194 deletions

View File

@ -6,4 +6,5 @@ packages/app-desktop
packages/app-cli
packages/app-mobile
packages/app-clipper
packages/generator-joplin
packages/generator-joplin
packages/plugin-repo-cli

View File

@ -1371,6 +1371,12 @@ packages/lib/uuid.js.map
packages/lib/versionInfo.d.ts
packages/lib/versionInfo.js
packages/lib/versionInfo.js.map
packages/plugin-repo-cli/dummy.test.d.ts
packages/plugin-repo-cli/dummy.test.js
packages/plugin-repo-cli/dummy.test.js.map
packages/plugin-repo-cli/index.d.ts
packages/plugin-repo-cli/index.js
packages/plugin-repo-cli/index.js.map
packages/plugins/ToggleSidebars/api/index.d.ts
packages/plugins/ToggleSidebars/api/index.js
packages/plugins/ToggleSidebars/api/index.js.map
@ -1461,21 +1467,6 @@ packages/renderer/utils.js.map
packages/server/src/app.d.ts
packages/server/src/app.js
packages/server/src/app.js.map
packages/server/src/config-base.d.ts
packages/server/src/config-base.js
packages/server/src/config-base.js.map
packages/server/src/config-buildTypes.d.ts
packages/server/src/config-buildTypes.js
packages/server/src/config-buildTypes.js.map
packages/server/src/config-dev.d.ts
packages/server/src/config-dev.js
packages/server/src/config-dev.js.map
packages/server/src/config-prod.d.ts
packages/server/src/config-prod.js
packages/server/src/config-prod.js.map
packages/server/src/config-tests.d.ts
packages/server/src/config-tests.js
packages/server/src/config-tests.js.map
packages/server/src/config.d.ts
packages/server/src/config.js
packages/server/src/config.js.map
@ -1686,10 +1677,13 @@ packages/server/src/utils/urlUtils.js.map
packages/server/src/utils/uuidgen.d.ts
packages/server/src/utils/uuidgen.js
packages/server/src/utils/uuidgen.js.map
packages/tools/build-plugin-repository.d.ts
packages/tools/build-plugin-repository.js
packages/tools/build-plugin-repository.js.map
packages/tools/lerna-add.d.ts
packages/tools/lerna-add.js
packages/tools/lerna-add.js.map
packages/tools/release-server.d.ts
packages/tools/release-server.js
packages/tools/release-server.js.map
packages/tools/tool-utils.d.ts
packages/tools/tool-utils.js
packages/tools/tool-utils.js.map
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD

30
.gitignore vendored
View File

@ -1359,6 +1359,12 @@ packages/lib/uuid.js.map
packages/lib/versionInfo.d.ts
packages/lib/versionInfo.js
packages/lib/versionInfo.js.map
packages/plugin-repo-cli/dummy.test.d.ts
packages/plugin-repo-cli/dummy.test.js
packages/plugin-repo-cli/dummy.test.js.map
packages/plugin-repo-cli/index.d.ts
packages/plugin-repo-cli/index.js
packages/plugin-repo-cli/index.js.map
packages/plugins/ToggleSidebars/api/index.d.ts
packages/plugins/ToggleSidebars/api/index.js
packages/plugins/ToggleSidebars/api/index.js.map
@ -1449,21 +1455,6 @@ packages/renderer/utils.js.map
packages/server/src/app.d.ts
packages/server/src/app.js
packages/server/src/app.js.map
packages/server/src/config-base.d.ts
packages/server/src/config-base.js
packages/server/src/config-base.js.map
packages/server/src/config-buildTypes.d.ts
packages/server/src/config-buildTypes.js
packages/server/src/config-buildTypes.js.map
packages/server/src/config-dev.d.ts
packages/server/src/config-dev.js
packages/server/src/config-dev.js.map
packages/server/src/config-prod.d.ts
packages/server/src/config-prod.js
packages/server/src/config-prod.js.map
packages/server/src/config-tests.d.ts
packages/server/src/config-tests.js
packages/server/src/config-tests.js.map
packages/server/src/config.d.ts
packages/server/src/config.js
packages/server/src/config.js.map
@ -1674,10 +1665,13 @@ packages/server/src/utils/urlUtils.js.map
packages/server/src/utils/uuidgen.d.ts
packages/server/src/utils/uuidgen.js
packages/server/src/utils/uuidgen.js.map
packages/tools/build-plugin-repository.d.ts
packages/tools/build-plugin-repository.js
packages/tools/build-plugin-repository.js.map
packages/tools/lerna-add.d.ts
packages/tools/lerna-add.js
packages/tools/lerna-add.js.map
packages/tools/release-server.d.ts
packages/tools/release-server.js
packages/tools/release-server.js.map
packages/tools/tool-utils.d.ts
packages/tools/tool-utils.js
packages/tools/tool-utils.js.map
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD

View File

@ -14,7 +14,6 @@
"buildApiDoc": "npm start --prefix=packages/app-cli -- apidoc ../../readme/api/references/rest_api.md",
"buildDoc": "./packages/tools/build-all.sh",
"buildPluginDoc": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme './Assets/PluginDocTheme/' --readme './Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out docs/api/references/plugin_api packages/lib/services/plugins/api/",
"buildPluginRepo": "node packages/tools/build-plugin-repository.js",
"buildTranslations": "npm run tsc && node packages/tools/build-translation.js",
"buildTranslationsNoTsc": "node packages/tools/build-translation.js",
"buildWebsite": "npm run buildApiDoc && node ./packages/tools/build-website.js && npm run buildPluginDoc",

View File

@ -52,7 +52,7 @@
"coveralls": "^3.0.1",
"eslint": "^6.0.0",
"eslint-config-prettier": "^6.0.0",
"jest": "^24.8.0",
"jest": "^26.6.3",
"prettier": "^1.18.2",
"ts-jest": "^24.0.2",
"typescript": "^3.5.3"

View File

@ -0,0 +1,9 @@
// Dummy test because the Jest setup is done but there's for now no test.
describe('dummy', () => {
it('should pass', () => {
expect(1).toBe(1);
});
});

View File

@ -3,7 +3,7 @@ import * as path from 'path';
import * as process from 'process';
import validatePluginId from '@joplin/lib/services/plugins/utils/validatePluginId';
import markdownUtils, { MarkdownTableHeader, MarkdownTableRow } from '@joplin/lib/markdownUtils';
const { execCommand, execCommandVerbose, rootDir, resolveRelativePathWithinDir, gitPullTry } = require('./tool-utils.js');
import { execCommand2, resolveRelativePathWithinDir, gitPullTry, gitRepoCleanTry, gitRepoClean } from '@joplin/tools/tool-utils.js';
interface NpmPackage {
name: string;
@ -45,6 +45,7 @@ async function checkPluginRepository(dirPath: string) {
const previousDir = process.cwd();
process.chdir(dirPath);
await gitRepoCleanTry();
await gitPullTry();
process.chdir(previousDir);
}
@ -70,13 +71,13 @@ async function extractPluginFilesFromPackage(existingManifests: any, workDir: st
const previousDir = process.cwd();
process.chdir(workDir);
await execCommandVerbose('npm', ['install', packageName, '--save', '--ignore-scripts']);
await execCommand2(`npm install ${packageName} --save --ignore-scripts`, { showOutput: false });
const pluginDir = resolveRelativePathWithinDir(workDir, 'node_modules', packageName, 'publish');
const files = await fs.readdir(pluginDir);
const manifestFilePath = path.resolve(pluginDir, files.find(f => path.extname(f) === '.json'));
const pluginFilePath = path.resolve(pluginDir, files.find(f => path.extname(f) === '.jpl'));
const manifestFilePath = path.resolve(pluginDir, files.find((f: any) => path.extname(f) === '.json'));
const pluginFilePath = path.resolve(pluginDir, files.find((f: any) => path.extname(f) === '.jpl'));
if (!(await fs.pathExists(manifestFilePath))) throw new Error(`Could not find manifest file at ${manifestFilePath}`);
if (!(await fs.pathExists(pluginFilePath))) throw new Error(`Could not find plugin file at ${pluginFilePath}`);
@ -155,23 +156,41 @@ async function updateReadme(readmePath: string, manifests: any) {
const tableRegex = /<!-- PLUGIN_LIST -->([^]*)<!-- PLUGIN_LIST -->/;
const content = await fs.readFile(readmePath, 'utf8');
const content = await fs.pathExists(readmePath) ? await fs.readFile(readmePath, 'utf8') : '<!-- PLUGIN_LIST -->\n<!-- PLUGIN_LIST -->';
const newContent = content.replace(tableRegex, `<!-- PLUGIN_LIST -->\n${mdTable}\n<!-- PLUGIN_LIST -->`);
await fs.writeFile(readmePath, newContent, 'utf8');
}
async function main() {
// We assume that the repository is located in a directory next to the main
// Joplin monorepo.
const repoDir = path.resolve(path.dirname(rootDir), 'joplin-plugins');
interface CommandBuildArgs {
pluginRepoDir: string;
}
enum ProcessingActionType {
Add = 1,
Update = 2,
}
function commitMessage(actionType: ProcessingActionType, npmPackage: NpmPackage): string {
const output: string[] = [];
if (actionType === ProcessingActionType.Add) {
output.push('New');
} else {
output.push('Update');
}
output.push(`${npmPackage.name}@${npmPackage.version}`);
return output.join(': ');
}
async function processNpmPackage(npmPackage: NpmPackage, repoDir: string) {
const tempDir = `${repoDir}/temp`;
const pluginManifestsPath = path.resolve(repoDir, 'manifests.json');
const obsoleteManifestsPath = path.resolve(repoDir, 'obsoletes.json');
const errorsPath = path.resolve(repoDir, 'errors.json');
await checkPluginRepository(repoDir);
await fs.mkdirp(tempDir);
const originalPluginManifests = await readJsonFile(pluginManifestsPath, {});
@ -181,29 +200,31 @@ async function main() {
...obsoleteManifests,
};
const searchResults = (await execCommand('npm search joplin-plugin --searchlimit 5000 --json')).trim();
const npmPackages = pluginInfoFromSearchResults(JSON.parse(searchResults));
const packageTempDir = `${tempDir}/packages`;
await fs.mkdirp(packageTempDir);
const previousDir = process.cwd();
process.chdir(packageTempDir);
await execCommand('npm init --yes --loglevel silent');
await execCommand2('npm init --yes --loglevel silent', { quiet: true });
const errors: any[] = [];
const errors: any = await readJsonFile(errorsPath, {});
delete errors[npmPackage.name];
let actionType: ProcessingActionType = ProcessingActionType.Update;
let manifests: any = {};
for (const npmPackage of npmPackages) {
try {
const packageName = npmPackage.name;
const destDir = `${repoDir}/plugins/`;
const manifest = await extractPluginFilesFromPackage(existingManifests, packageTempDir, packageName, destDir);
if (!obsoleteManifests[manifest.id]) manifests[manifest.id] = manifest;
} catch (error) {
console.error(error);
errors.push(error);
try {
const destDir = `${repoDir}/plugins/`;
const manifest = await extractPluginFilesFromPackage(existingManifests, packageTempDir, npmPackage.name, destDir);
if (!existingManifests[manifest.id]) {
actionType = ProcessingActionType.Add;
}
if (!obsoleteManifests[manifest.id]) manifests[manifest.id] = manifest;
} catch (error) {
console.error(error);
errors[npmPackage.name] = error.message || '';
}
// We preserve the original manifests so that if a plugin has been removed
@ -217,20 +238,79 @@ async function main() {
await fs.writeFile(pluginManifestsPath, JSON.stringify(manifests, null, '\t'), 'utf8');
if (errors.length) {
const toWrite = errors.map((e: any) => {
return {
message: e.message || '',
};
});
await fs.writeFile(errorsPath, JSON.stringify(toWrite, null, '\t'), 'utf8');
if (Object.keys(errors).length) {
await fs.writeFile(errorsPath, JSON.stringify(errors, null, '\t'), 'utf8');
} else {
await fs.remove(errorsPath);
}
await updateReadme(`${repoDir}/README.md`, manifests);
process.chdir(previousDir);
await fs.remove(tempDir);
process.chdir(repoDir);
if (!(await gitRepoClean())) {
await execCommand2('git add -A', { showOutput: false });
await execCommand2(['git', 'commit', '-m', commitMessage(actionType, npmPackage)], { showOutput: false });
} else {
console.info('Nothing to commit');
}
}
async function commandBuild(args: CommandBuildArgs) {
const repoDir = args.pluginRepoDir;
await checkPluginRepository(repoDir);
const searchResults = (await execCommand2('npm search joplin-plugin --searchlimit 5000 --json', { showOutput: false })).trim();
const npmPackages = pluginInfoFromSearchResults(JSON.parse(searchResults));
for (const npmPackage of npmPackages) {
await processNpmPackage(npmPackage, repoDir);
}
await execCommand2('git push');
}
async function main() {
const scriptName: string = 'plugin-repo-cli';
const commands: Record<string, Function> = {
build: commandBuild,
};
let selectedCommand: string = '';
let selectedCommandArgs: string = '';
function setSelectedCommand(name: string, args: any) {
selectedCommand = name;
selectedCommandArgs = args;
}
require('yargs')
.scriptName(scriptName)
.usage('$0 <cmd> [args]')
.command('build <plugin-repo-dir>', 'Build the plugin repository', (yargs: any) => {
yargs.positional('plugin-repo-dir', {
type: 'string',
describe: 'Directory where the plugin repository is located',
});
}, (args: any) => setSelectedCommand('build', args))
.help()
.argv;
if (!selectedCommand) {
console.error(`Please provide a command name or type \`${scriptName} --help\` for help`);
process.exit(1);
}
if (!commands[selectedCommand]) {
console.error(`No such command: ${selectedCommand}`);
process.exit(1);
}
await commands[selectedCommand](selectedCommandArgs);
}
main().catch((error) => {

4894
packages/plugin-repo-cli/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
{
"name": "@joplin/plugin-repo-builder",
"version": "1.7.0",
"description": "",
"main": "index.js",
"publishConfig": {
"access": "public"
},
"scripts": {
"tsc": "tsc --project tsconfig.json",
"watch": "tsc --watch --project tsconfig.json",
"prepare": "npm run tsc",
"test": "jest",
"test-ci": "npm run test"
},
"author": "",
"license": "MIT",
"dependencies": {
"@joplin/lib": "*",
"@joplin/tools": "*",
"fs-extra": "^9.0.1",
"yargs": "^16.0.3"
},
"devDependencies": {
"@types/jest": "^26.0.15",
"@types/node": "^14.14.6",
"@types/fs-extra": "^9.0.6",
"jest": "^26.6.3",
"typescript": "^4.1.3"
}
}

View File

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"**/node_modules",
],
}

View File

@ -0,0 +1,40 @@
// // npx lerna add --no-bootstrap --scope @joplin/plugin-repo-builder -D jest && npx lerna bootstrap --no-ci --include-dependents --include-dependencies --scope @joplin/plugin-repo-builder
// import { execCommand2, rootDir, gitPullTry } from './tool-utils.js';
// async function main() {
// const argv = require('yargs').argv;
// console.info(process.argv);
// const args = [];
// if (argv.D) args.push('-D');
// await execCommand2('npx lerna add --no-bootstrap --scope @joplin/plugin-repo-builder -D jest');
// //npx lerna add --no-bootstrap --scope @joplin/plugin-repo-builder -D jest && npx lerna bootstrap --no-ci --include-dependents --include-dependencies --scope @joplin/plugin-repo-builder
// // process.chdir(rootDir);
// // const version = (await execCommand2('npm version patch')).trim();
// // const versionShort = version.substr(1);
// // const tagName = `server-${version}`;
// // process.chdir(rootDir);
// // console.info(`Running from: ${process.cwd()}`);
// // await execCommand2(`docker build -t "joplin/server:${versionShort}" -f Dockerfile.server .`);
// // await execCommand2(`docker tag "joplin/server:${versionShort}" "joplin/server:latest"`);
// // await execCommand2(`docker push joplin/server:${versionShort}`);
// // await execCommand2('docker push joplin/server:latest');
// // await execCommand2('git add -A');
// // await execCommand2(`git commit -m 'Server release ${version}'`);
// // await execCommand2(`git tag ${tagName}`);
// // await execCommand2('git push');
// // await execCommand2('git push --tags');
// }
// main().catch((error) => {
// console.error('Fatal error');
// console.error(error);
// process.exit(1);
// });

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
const { execCommand2, rootDir, gitPullTry } = require('./tool-utils.js');
import { execCommand2, rootDir, gitPullTry } from './tool-utils';
const serverDir = `${rootDir}/packages/server`;

View File

@ -80,6 +80,7 @@ async function main() {
await updatePackageVersion(`${rootDir}/packages/app-cli/package.json`, majorMinorVersion);
await updatePackageVersion(`${rootDir}/packages/generator-joplin/package.json`, majorMinorVersion);
await updatePackageVersion(`${rootDir}/packages/server/package.json`, majorMinorVersion);
await updatePackageVersion(`${rootDir}/packages/plugin-repo-cli/package.json`, majorMinorVersion);
await updateGradleVersion(`${rootDir}/packages/app-mobile/android/app/build.gradle`, majorMinorVersion);
await updateCodeProjVersion(`${rootDir}/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj`, iosVersionHack(majorMinorVersion));
await updateClipperManifestVersion(`${rootDir}/packages/app-clipper/manifest.json`, majorMinorVersion);

View File

@ -1,16 +1,51 @@
import * as fs from 'fs-extra';
import { execSync } from 'child_process';
const fetch = require('node-fetch');
const fs = require('fs-extra');
const execa = require('execa');
const { execSync } = require('child_process');
const { splitCommandString } = require('@joplin/lib/string-utils');
const toolUtils = {};
function quotePath(path: string) {
if (!path) return '';
if (path.indexOf('"') < 0 && path.indexOf(' ') < 0) return path;
path = path.replace(/"/, '\\"');
return `"${path}"`;
}
toolUtils.execCommand = function(command) {
function commandToString(commandName: string, args: string[] = []) {
const output = [quotePath(commandName)];
for (const arg of args) {
output.push(quotePath(arg));
}
return output.join(' ');
}
async function loadGitHubUsernameCache() {
const path = `${__dirname}/github_username_cache.json`;
if (await fs.pathExists(path)) {
const jsonString = await fs.readFile(path, 'utf8');
return JSON.parse(jsonString);
}
return {};
}
async function saveGitHubUsernameCache(cache: any) {
const path = `${__dirname}/github_username_cache.json`;
await fs.writeFile(path, JSON.stringify(cache));
}
// Returns the project root dir
export const rootDir = require('path').dirname(require('path').dirname(__dirname));
export function execCommand(command: string) {
const exec = require('child_process').exec;
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
exec(command, (error: any, stdout: any, stderr: any) => {
if (error) {
if (error.signal == 'SIGTERM') {
resolve('Process was killed');
@ -22,39 +57,28 @@ toolUtils.execCommand = function(command) {
}
});
});
};
function quotePath(path) {
if (!path) return '';
if (path.indexOf('"') < 0 && path.indexOf(' ') < 0) return path;
path = path.replace(/"/, '\\"');
return `"${path}"`;
}
function commandToString(commandName, args = []) {
const output = [quotePath(commandName)];
for (const arg of args) {
output.push(quotePath(arg));
}
return output.join(' ');
}
toolUtils.resolveRelativePathWithinDir = function(baseDir, ...relativePath) {
export function resolveRelativePathWithinDir(baseDir: string, ...relativePath: string[]) {
const path = require('path');
const resolvedBaseDir = path.resolve(baseDir);
const resolvedPath = path.resolve(baseDir, ...relativePath);
if (resolvedPath.indexOf(resolvedBaseDir) !== 0) throw new Error(`Resolved path for relative path "${JSON.stringify(relativePath)}" is not within base directory "${baseDir}" (Was resolved to ${resolvedPath})`);
return resolvedPath;
};
}
toolUtils.execCommandVerbose = function(commandName, args = []) {
export function execCommandVerbose(commandName: string, args: string[] = []) {
console.info(`> ${commandToString(commandName, args)}`);
const promise = execa(commandName, args);
promise.stdout.pipe(process.stdout);
return promise;
};
}
interface ExecCommandOptions {
showInput?: boolean;
showOutput?: boolean;
quiet?: boolean;
}
// There's lot of execCommandXXX functions, but eventually all scripts should
// use the one below, which supports:
@ -62,66 +86,79 @@ toolUtils.execCommandVerbose = function(commandName, args = []) {
// - Printing the command being executed
// - Printing the output in real time (piping to stdout)
// - Returning the command result as string
toolUtils.execCommand2 = async function(command, options = null) {
export async function execCommand2(command: string | string[], options: ExecCommandOptions = null): Promise<string> {
options = {
showInput: true,
showOutput: true,
quiet: false,
...options,
};
if (options.showInput) console.info(`> ${command}`);
const args = splitCommandString(command);
if (options.quiet) {
options.showInput = false;
options.showOutput = 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.showOutput) promise.stdout.pipe(process.stdout);
const result = await promise;
return result.stdout;
};
return result.stdout.trim();
}
toolUtils.execCommandWithPipes = function(executable, args) {
export function execCommandWithPipes(executable: string, args: string[]) {
const spawn = require('child_process').spawn;
return new Promise((resolve, reject) => {
const child = spawn(executable, args, { stdio: 'inherit' });
child.on('error', (error) => {
child.on('error', (error: any) => {
reject(error);
});
child.on('close', (code) => {
child.on('close', (code: any) => {
if (code !== 0) {
reject(`Ended with code ${code}`);
} else {
resolve();
resolve(null);
}
});
});
};
}
toolUtils.toSystemSlashes = function(path) {
export function toSystemSlashes(path: string) {
const os = process.platform;
if (os === 'win32') return path.replace(/\//g, '\\');
return path.replace(/\\/g, '/');
};
}
toolUtils.deleteLink = async function(path) {
if (toolUtils.isWindows()) {
export function deleteLink(path: string) {
if (isWindows()) {
try {
execSync(`rmdir "${toolUtils.toSystemSlashes(path)}"`, { stdio: 'pipe' });
execSync(`rmdir "${toSystemSlashes(path)}"`, { stdio: 'pipe' });
} catch (error) {
// console.info('Error: ' + error.message);
}
} else {
try {
fs.unlinkSync(toolUtils.toSystemSlashes(path));
fs.unlinkSync(toSystemSlashes(path));
} catch (error) {
// ignore
}
}
};
}
toolUtils.setPackagePrivateField = async function(filePath, value) {
export async function setPackagePrivateField(filePath: string, value: any) {
const text = await fs.readFile(filePath, 'utf8');
const obj = JSON.parse(text);
if (!value) {
@ -130,9 +167,9 @@ toolUtils.setPackagePrivateField = async function(filePath, value) {
obj.private = true;
}
await fs.writeFile(filePath, JSON.stringify(obj, null, 2), 'utf8');
};
}
toolUtils.credentialDir = async function() {
export async function credentialDir() {
const username = require('os').userInfo().username;
const toTry = [
@ -143,48 +180,45 @@ toolUtils.credentialDir = async function() {
];
for (const dirPath of toTry) {
if (await fs.exists(dirPath)) return dirPath;
if (await fs.pathExists(dirPath)) return dirPath;
}
throw new Error(`Could not find credential directory in any of these paths: ${JSON.stringify(toTry)}`);
};
}
// Returns the project root dir
toolUtils.rootDir = require('path').dirname(require('path').dirname(__dirname));
toolUtils.credentialFile = async function(filename) {
const rootDir = await toolUtils.credentialDir();
export async function credentialFile(filename: string) {
const rootDir = await credentialDir();
const output = `${rootDir}/${filename}`;
if (!(await fs.exists(output))) throw new Error(`No such file: ${output}`);
if (!(await fs.pathExists(output))) throw new Error(`No such file: ${output}`);
return output;
};
}
toolUtils.readCredentialFile = async function(filename) {
const filePath = await toolUtils.credentialFile(filename);
export async function readCredentialFile(filename: string) {
const filePath = await credentialFile(filename);
const r = await fs.readFile(filePath);
return r.toString();
};
}
toolUtils.downloadFile = function(url, targetPath) {
export async function downloadFile(url: string, targetPath: string) {
const https = require('https');
const fs = require('fs');
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(targetPath);
https.get(url, function(response) {
https.get(url, function(response: any) {
if (response.statusCode !== 200) reject(new Error(`HTTP error ${response.statusCode}`));
response.pipe(file);
file.on('finish', function() {
// file.close();
resolve();
resolve(null);
});
}).on('error', (error) => {
}).on('error', (error: any) => {
reject(error);
});
});
};
}
toolUtils.fileSha256 = function(filePath) {
export function fileSha256(filePath: string) {
return new Promise((resolve, reject) => {
const crypto = require('crypto');
const fs = require('fs');
@ -192,18 +226,18 @@ toolUtils.fileSha256 = function(filePath) {
const shasum = crypto.createHash(algo);
const s = fs.ReadStream(filePath);
s.on('data', function(d) { shasum.update(d); });
s.on('data', function(d: any) { shasum.update(d); });
s.on('end', function() {
const d = shasum.digest('hex');
resolve(d);
});
s.on('error', function(error) {
s.on('error', function(error: any) {
reject(error);
});
});
};
}
toolUtils.unlinkForce = async function(filePath) {
export async function unlinkForce(filePath: string) {
const fs = require('fs-extra');
try {
@ -212,13 +246,13 @@ toolUtils.unlinkForce = async function(filePath) {
if (error.code === 'ENOENT') return;
throw error;
}
};
}
toolUtils.fileExists = async function(filePath) {
export function fileExists(filePath: string) {
const fs = require('fs-extra');
return new Promise((resolve, reject) => {
fs.stat(filePath, function(err) {
fs.stat(filePath, function(err: any) {
if (err == null) {
resolve(true);
} else if (err.code == 'ENOENT') {
@ -228,27 +262,22 @@ toolUtils.fileExists = async function(filePath) {
}
});
});
};
async function loadGitHubUsernameCache() {
const path = `${__dirname}/github_username_cache.json`;
if (await fs.exists(path)) {
const jsonString = await fs.readFile(path);
return JSON.parse(jsonString);
}
return {};
}
async function saveGitHubUsernameCache(cache) {
const path = `${__dirname}/github_username_cache.json`;
await fs.writeFile(path, JSON.stringify(cache));
export async function gitRepoClean(): Promise<boolean> {
const output = await execCommand2('git status --porcelain', { quiet: true });
return !output.trim();
}
toolUtils.gitPullTry = async function() {
export async function gitRepoCleanTry() {
if (!(await gitRepoClean())) throw new Error(`There are pending changes in the repository: ${process.cwd()}`);
}
export async function gitPullTry() {
try {
await toolUtils.execCommand('git pull');
await execCommand('git pull');
} catch (error) {
if (error.message.includes('no tracking information for the current branch')) {
console.info('Skipping git pull because no tracking information on current branch');
@ -256,16 +285,16 @@ toolUtils.gitPullTry = async function() {
throw error;
}
}
};
}
toolUtils.githubUsername = async function(email, name) {
export async function githubUsername(email: string, name: string) {
const cache = await loadGitHubUsernameCache();
const cacheKey = `${email}:${name}`;
if (cacheKey in cache) return cache[cacheKey];
let output = null;
const oauthToken = await toolUtils.githubOauthToken();
const oauthToken = await githubOauthToken();
const urlsToTry = [
`https://api.github.com/search/users?q=${encodeURI(email)}+in:email`,
@ -296,23 +325,23 @@ toolUtils.githubUsername = async function(email, name) {
await saveGitHubUsernameCache(cache);
return output;
};
}
toolUtils.patreonOauthToken = async function() {
return toolUtils.readCredentialFile('patreon_oauth_token.txt');
};
export function patreonOauthToken() {
return readCredentialFile('patreon_oauth_token.txt');
}
toolUtils.githubOauthToken = async function() {
return toolUtils.readCredentialFile('github_oauth_token.txt');
};
export function githubOauthToken() {
return readCredentialFile('github_oauth_token.txt');
}
toolUtils.githubRelease = async function(project, tagName, options = null) {
export async function githubRelease(project: string, tagName: string, options: any = null) {
options = Object.assign({}, {
isDraft: false,
isPreRelease: false,
}, options);
const oauthToken = await toolUtils.githubOauthToken();
const oauthToken = await githubOauthToken();
const response = await fetch(`https://api.github.com/repos/laurent22/${project}/releases`, {
method: 'POST',
@ -336,9 +365,9 @@ toolUtils.githubRelease = async function(project, tagName, options = null) {
if (!responseJson.url) throw new Error(`No URL for release: ${responseText}`);
return responseJson;
};
}
toolUtils.readline = question => {
export function readline(question: string) {
return new Promise((resolve) => {
const readline = require('readline');
@ -347,63 +376,61 @@ toolUtils.readline = question => {
output: process.stdout,
});
rl.question(`${question} `, answer => {
rl.question(`${question} `, (answer: string) => {
resolve(answer);
rl.close();
});
});
};
}
toolUtils.isLinux = () => {
export function isLinux() {
return process && process.platform === 'linux';
};
}
toolUtils.isWindows = () => {
export function isWindows() {
return process && process.platform === 'win32';
};
}
toolUtils.isMac = () => {
export function isMac() {
return process && process.platform === 'darwin';
};
}
toolUtils.insertContentIntoFile = async function(filePath, markerOpen, markerClose, contentToInsert) {
export async function insertContentIntoFile(filePath: string, markerOpen: string, markerClose: string, contentToInsert: string) {
const fs = require('fs-extra');
let content = await fs.readFile(filePath, 'utf-8');
// [^]* matches any character including new lines
const regex = new RegExp(`${markerOpen}[^]*?${markerClose}`);
content = content.replace(regex, markerOpen + contentToInsert + markerClose);
await fs.writeFile(filePath, content);
};
}
toolUtils.dirname = (path) => {
export function dirname(path: string) {
if (!path) throw new Error('Path is empty');
const s = path.split(/\/|\\/);
s.pop();
return s.join('/');
};
}
toolUtils.basename = (path) => {
export function basename(path: string) {
if (!path) throw new Error('Path is empty');
const s = path.split(/\/|\\/);
return s[s.length - 1];
};
}
toolUtils.filename = (path, includeDir = false) => {
export function filename(path: string, includeDir = false) {
if (!path) throw new Error('Path is empty');
const output = includeDir ? path : toolUtils.basename(path);
const output = includeDir ? path : basename(path);
if (output.indexOf('.') < 0) return output;
const splitted = output.split('.');
splitted.pop();
return splitted.join('.');
};
}
toolUtils.fileExtension = (path) => {
export function fileExtension(path: string) {
if (!path) throw new Error('Path is empty');
const output = path.split('.');
if (output.length <= 1) return '';
return output[output.length - 1];
};
module.exports = toolUtils;
}