2023-04-04 17:01:34 +02:00
import { pathExists , readFile , writeFile , unlink , stat , createWriteStream } from 'fs-extra' ;
import { hasCredentialFile , readCredentialFile } from '@joplin/lib/utils/credentialFiles' ;
2023-03-19 17:37:07 +02:00
import { execCommand as execCommand2 , commandToString } from '@joplin/utils' ;
2021-01-18 16:37:27 +02:00
2020-01-22 22:33:43 +02:00
const fetch = require ( 'node-fetch' ) ;
2020-11-06 20:45:45 +02:00
const execa = require ( 'execa' ) ;
2021-01-24 21:25:32 +02:00
const moment = require ( 'moment' ) ;
2020-01-22 22:33:43 +02:00
2021-12-01 18:16:04 +02:00
export interface GitHubReleaseAsset {
name : string ;
browser_download_url : string ;
}
export interface GitHubRelease {
assets : GitHubReleaseAsset [ ] ;
tag_name : string ;
upload_url : string ;
html_url : string ;
prerelease : boolean ;
draft : boolean ;
2023-09-13 20:01:27 +02:00
body : string ;
2021-12-01 18:16:04 +02:00
}
2023-06-30 10:11:26 +02:00
async function insertChangelog ( tag : string , changelogPath : string , changelog : string , isPrerelease : boolean , repoTagUrl = '' ) {
2022-02-23 11:02:13 +02:00
repoTagUrl = repoTagUrl || 'https://github.com/laurent22/joplin/releases/tag' ;
2023-06-06 11:55:02 +02:00
const currentText = await readFile ( changelogPath , 'utf8' ) ;
2021-01-24 21:25:32 +02:00
const lines = currentText . split ( '\n' ) ;
const beforeLines = [ ] ;
const afterLines = [ ] ;
for ( const line of lines ) {
if ( afterLines . length ) {
afterLines . push ( line ) ;
continue ;
}
if ( line . indexOf ( '##' ) === 0 ) {
afterLines . push ( line ) ;
continue ;
}
beforeLines . push ( line ) ;
}
const header = [
'##' ,
2022-02-23 11:02:13 +02:00
` [ ${ tag } ]( ${ repoTagUrl } / ${ tag } ) ` ,
2021-01-24 21:25:32 +02:00
] ;
2021-05-14 17:17:02 +02:00
if ( isPrerelease ) header . push ( '(Pre-release)' ) ;
header . push ( '-' ) ;
// eslint-disable-next-line no-useless-escape
header . push ( ` ${ moment . utc ( ) . format ( 'YYYY-MM-DD\THH:mm:ss' ) } Z ` ) ;
2021-01-24 21:25:32 +02:00
let newLines = [ ] ;
newLines . push ( header . join ( ' ' ) ) ;
newLines . push ( '' ) ;
newLines = newLines . concat ( changelog . split ( '\n' ) ) ;
newLines . push ( '' ) ;
const output = beforeLines . concat ( newLines ) . concat ( afterLines ) ;
return output . join ( '\n' ) ;
}
2021-12-21 13:38:05 +02:00
export function releaseFinalGitCommands ( appName : string , newVersion : string , newTag : string ) : string {
2021-01-24 21:25:32 +02:00
const finalCmds = [
'git add -A' ,
` git commit -m " ${ appName } ${ newVersion } " ` ,
` git tag " ${ newTag } " ` ,
'git push' ,
2023-02-09 13:48:22 +02:00
` git push origin refs/tags/ ${ newTag } ` ,
2021-01-24 21:25:32 +02:00
] ;
2021-12-21 13:38:05 +02:00
return finalCmds . join ( ' && ' ) ;
}
2022-02-23 11:02:13 +02:00
export async function completeReleaseWithChangelog ( changelogPath : string , newVersion : string , newTag : string , appName : string , isPreRelease : boolean , repoTagUrl = '' ) {
2022-02-23 16:20:38 +02:00
const changelog = ( await execCommand2 ( ` node ${ rootDir } /packages/tools/git-changelog ${ newTag } --publish-format full ` , { showStdout : false } ) ) . trim ( ) ;
2021-12-21 13:38:05 +02:00
2022-02-23 11:02:13 +02:00
const newChangelog = await insertChangelog ( newTag , changelogPath , changelog , isPreRelease , repoTagUrl ) ;
2021-12-21 13:38:05 +02:00
2023-04-04 17:01:34 +02:00
await writeFile ( changelogPath , newChangelog ) ;
2021-12-21 13:38:05 +02:00
2021-01-24 21:25:32 +02:00
console . info ( '' ) ;
console . info ( 'Verify that the changelog is correct:' ) ;
console . info ( '' ) ;
console . info ( ` ${ process . env . EDITOR } " ${ changelogPath } " ` ) ;
console . info ( '' ) ;
console . info ( 'Then run these commands:' ) ;
console . info ( '' ) ;
2021-12-21 13:38:05 +02:00
console . info ( releaseFinalGitCommands ( appName , newVersion , newTag ) ) ;
2021-01-24 21:25:32 +02:00
}
2021-01-18 16:37:27 +02:00
async function loadGitHubUsernameCache() {
const path = ` ${ __dirname } /github_username_cache.json ` ;
2023-04-04 17:01:34 +02:00
if ( await pathExists ( path ) ) {
const jsonString = await readFile ( path , 'utf8' ) ;
2021-01-18 16:37:27 +02:00
return JSON . parse ( jsonString ) ;
}
return { } ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-01-18 16:37:27 +02:00
async function saveGitHubUsernameCache ( cache : any ) {
const path = ` ${ __dirname } /github_username_cache.json ` ;
2023-04-04 17:01:34 +02:00
await writeFile ( path , JSON . stringify ( cache ) ) ;
2021-01-18 16:37:27 +02:00
}
// Returns the project root dir
2022-03-10 18:03:51 +02:00
export const rootDir : string = require ( 'path' ) . dirname ( require ( 'path' ) . dirname ( __dirname ) ) ;
2017-12-04 20:16:14 +02:00
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-08-28 11:49:31 +02:00
export function execCommand ( command : string , options : any = null ) : Promise < string > {
2021-05-15 15:13:08 +02:00
options = options || { } ;
2019-07-30 09:35:42 +02:00
const exec = require ( 'child_process' ) . exec ;
2017-12-04 20:16:14 +02:00
return new Promise ( ( resolve , reject ) = > {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-05-15 15:13:08 +02:00
exec ( command , options , ( error : any , stdout : any , stderr : any ) = > {
2017-12-04 20:16:14 +02:00
if ( error ) {
2022-07-23 09:31:32 +02:00
if ( error . signal === 'SIGTERM' ) {
2017-12-04 20:16:14 +02:00
resolve ( 'Process was killed' ) ;
} else {
reject ( error ) ;
}
} else {
2020-11-08 19:41:56 +02:00
resolve ( [ stdout . trim ( ) , stderr . trim ( ) ] . join ( '\n' ) ) ;
2017-12-04 20:16:14 +02:00
}
} ) ;
2019-10-13 01:21:56 +02:00
} ) ;
2020-11-06 20:45:45 +02:00
}
2022-01-07 14:04:59 +02:00
export function resolveRelativePathWithinDir ( baseDir : string , . . . relativePath : string [ ] ) : string {
2021-01-05 17:25:15 +02:00
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 ;
2021-01-18 16:37:27 +02:00
}
2021-01-05 17:25:15 +02:00
2021-01-18 16:37:27 +02:00
export function execCommandVerbose ( commandName : string , args : string [ ] = [ ] ) {
2020-11-06 20:45:45 +02:00
console . info ( ` > ${ commandToString ( commandName , args ) } ` ) ;
const promise = execa ( commandName , args ) ;
promise . stdout . pipe ( process . stdout ) ;
return promise ;
2021-01-18 16:37:27 +02:00
}
export function execCommandWithPipes ( executable : string , args : string [ ] ) {
2020-03-14 01:46:14 +02:00
const spawn = require ( 'child_process' ) . spawn ;
2019-10-13 01:21:56 +02:00
return new Promise ( ( resolve , reject ) = > {
2020-02-05 00:09:34 +02:00
const child = spawn ( executable , args , { stdio : 'inherit' } ) ;
2019-10-13 01:21:56 +02:00
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-01-18 16:37:27 +02:00
child . on ( 'error' , ( error : any ) = > {
2019-10-13 01:21:56 +02:00
reject ( error ) ;
} ) ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-01-18 16:37:27 +02:00
child . on ( 'close' , ( code : any ) = > {
2019-10-13 01:21:56 +02:00
if ( code !== 0 ) {
reject ( ` Ended with code ${ code } ` ) ;
} else {
2021-01-18 16:37:27 +02:00
resolve ( null ) ;
2019-10-13 01:21:56 +02:00
}
} ) ;
2017-12-04 20:16:14 +02:00
} ) ;
2021-01-18 16:37:27 +02:00
}
2017-12-04 20:16:14 +02:00
2021-01-18 16:37:27 +02:00
export function toSystemSlashes ( path : string ) {
2020-11-06 20:45:45 +02:00
const os = process . platform ;
if ( os === 'win32' ) return path . replace ( /\//g , '\\' ) ;
return path . replace ( /\\/g , '/' ) ;
2021-01-18 16:37:27 +02:00
}
2020-11-06 20:45:45 +02:00
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-01-18 16:37:27 +02:00
export async function setPackagePrivateField ( filePath : string , value : any ) {
2023-04-04 17:01:34 +02:00
const text = await readFile ( filePath , 'utf8' ) ;
2020-12-28 00:22:29 +02:00
const obj = JSON . parse ( text ) ;
if ( ! value ) {
delete obj . private ;
} else {
obj . private = true ;
}
2023-04-04 17:01:34 +02:00
await writeFile ( filePath , JSON . stringify ( obj , null , 2 ) , 'utf8' ) ;
2021-01-18 16:37:27 +02:00
}
2020-12-28 00:22:29 +02:00
2021-01-18 16:37:27 +02:00
export async function downloadFile ( url : string , targetPath : string ) {
2017-12-04 20:16:14 +02:00
const https = require ( 'https' ) ;
return new Promise ( ( resolve , reject ) = > {
2023-04-04 17:01:34 +02:00
const file = createWriteStream ( targetPath ) ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-02-20 17:02:29 +02:00
https . get ( url , ( response : any ) = > {
2019-09-19 23:51:18 +02:00
if ( response . statusCode !== 200 ) reject ( new Error ( ` HTTP error ${ response . statusCode } ` ) ) ;
2017-12-04 20:16:14 +02:00
response . pipe ( file ) ;
2023-02-20 17:02:29 +02:00
file . on ( 'finish' , ( ) = > {
2019-10-09 21:35:13 +02:00
// file.close();
2021-01-18 16:37:27 +02:00
resolve ( null ) ;
2017-12-04 20:16:14 +02:00
} ) ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-01-18 16:37:27 +02:00
} ) . on ( 'error' , ( error : any ) = > {
2017-12-04 20:16:14 +02:00
reject ( error ) ;
} ) ;
} ) ;
2021-01-18 16:37:27 +02:00
}
2017-12-04 20:16:14 +02:00
2021-01-18 16:37:27 +02:00
export function fileSha256 ( filePath : string ) {
2017-12-04 20:16:14 +02:00
return new Promise ( ( resolve , reject ) = > {
const crypto = require ( 'crypto' ) ;
const fs = require ( 'fs' ) ;
const algo = 'sha256' ;
const shasum = crypto . createHash ( algo ) ;
const s = fs . ReadStream ( filePath ) ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-02-20 17:02:29 +02:00
s . on ( 'data' , ( d : any ) = > { shasum . update ( d ) ; } ) ;
s . on ( 'end' , ( ) = > {
2017-12-04 20:16:14 +02:00
const d = shasum . digest ( 'hex' ) ;
resolve ( d ) ;
} ) ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-02-20 17:02:29 +02:00
s . on ( 'error' , ( error : any ) = > {
2017-12-04 20:16:14 +02:00
reject ( error ) ;
} ) ;
} ) ;
2021-01-18 16:37:27 +02:00
}
2017-12-04 20:16:14 +02:00
2021-01-18 16:37:27 +02:00
export async function unlinkForce ( filePath : string ) {
2017-12-04 20:16:14 +02:00
try {
2023-04-04 17:01:34 +02:00
await unlink ( filePath ) ;
2017-12-04 20:16:14 +02:00
} catch ( error ) {
if ( error . code === 'ENOENT' ) return ;
throw error ;
}
2021-01-18 16:37:27 +02:00
}
2017-12-04 20:16:14 +02:00
2021-01-18 16:37:27 +02:00
export function fileExists ( filePath : string ) {
2017-12-04 20:16:14 +02:00
return new Promise ( ( resolve , reject ) = > {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-04-04 17:01:34 +02:00
stat ( filePath , ( error : any ) = > {
2023-02-16 12:55:24 +02:00
if ( ! error ) {
2017-12-04 20:16:14 +02:00
resolve ( true ) ;
2023-02-16 12:55:24 +02:00
} else if ( error . code === 'ENOENT' ) {
2017-12-04 20:16:14 +02:00
resolve ( false ) ;
} else {
2023-02-16 12:55:24 +02:00
reject ( error ) ;
2017-12-04 20:16:14 +02:00
}
} ) ;
} ) ;
2021-01-18 16:37:27 +02:00
}
2020-01-22 22:33:43 +02:00
2021-01-18 16:37:27 +02:00
export async function gitRepoClean ( ) : Promise < boolean > {
const output = await execCommand2 ( 'git status --porcelain' , { quiet : true } ) ;
return ! output . trim ( ) ;
2020-01-22 22:33:43 +02:00
}
2021-01-18 16:37:27 +02:00
export async function gitRepoCleanTry() {
if ( ! ( await gitRepoClean ( ) ) ) throw new Error ( ` There are pending changes in the repository: ${ process . cwd ( ) } ` ) ;
2020-01-22 22:33:43 +02:00
}
2021-01-20 01:02:58 +02:00
export async function gitPullTry ( ignoreIfNotBranch = true ) {
2020-11-18 12:36:19 +02:00
try {
2021-01-18 16:37:27 +02:00
await execCommand ( 'git pull' ) ;
2020-11-18 12:36:19 +02:00
} catch ( error ) {
2021-01-20 01:02:58 +02:00
if ( ignoreIfNotBranch && error . message . includes ( 'no tracking information for the current branch' ) ) {
2020-11-18 12:36:19 +02:00
console . info ( 'Skipping git pull because no tracking information on current branch' ) ;
} else {
throw error ;
}
}
2021-01-18 16:37:27 +02:00
}
2020-11-18 12:36:19 +02:00
2022-04-12 17:44:45 +02:00
export const gitCurrentBranch = async ( ) : Promise < string > = > {
const output = await execCommand2 ( 'git rev-parse --abbrev-ref HEAD' , { quiet : true } ) ;
return output . trim ( ) ;
} ;
2021-01-18 16:37:27 +02:00
export async function githubUsername ( email : string , name : string ) {
2024-03-18 20:11:19 +02:00
if ( email . endsWith ( '@users.noreply.github.com' ) ) {
const splitted = email . split ( '@' ) [ 0 ] . split ( '+' ) ;
return splitted . length === 1 ? splitted [ 0 ] : splitted [ 1 ] ;
}
2020-01-22 22:33:43 +02:00
const cache = await loadGitHubUsernameCache ( ) ;
2020-01-25 00:43:55 +02:00
const cacheKey = ` ${ email } : ${ name } ` ;
if ( cacheKey in cache ) return cache [ cacheKey ] ;
2020-01-22 22:33:43 +02:00
let output = null ;
2021-01-18 16:37:27 +02:00
const oauthToken = await githubOauthToken ( ) ;
2020-01-22 22:33:43 +02:00
2020-01-25 00:43:55 +02:00
const urlsToTry = [
` https://api.github.com/search/users?q= ${ encodeURI ( email ) } +in:email ` ,
2021-08-28 11:49:31 +02:00
// Note that this can fail if the email could not be found and the user
// shares a name with someone else. It's rare enough that we can leave
// it for now.
// https://github.com/laurent22/joplin/pull/5390
2020-01-25 00:43:55 +02:00
` https://api.github.com/search/users?q=user: ${ encodeURI ( name ) } ` ,
] ;
for ( const url of urlsToTry ) {
const response = await fetch ( url , {
method : 'GET' ,
headers : {
'Content-Type' : 'application/json' ,
'Authorization' : ` token ${ oauthToken } ` ,
} ,
} ) ;
const responseText = await response . text ( ) ;
2020-01-22 22:33:43 +02:00
2020-01-25 00:43:55 +02:00
if ( ! response . ok ) continue ;
2020-01-22 22:33:43 +02:00
const responseJson = JSON . parse ( responseText ) ;
2020-01-25 00:43:55 +02:00
if ( ! responseJson || ! responseJson . items || responseJson . items . length !== 1 ) continue ;
output = responseJson . items [ 0 ] . login ;
break ;
2020-01-22 22:33:43 +02:00
}
2020-01-25 00:43:55 +02:00
cache [ cacheKey ] = output ;
2020-01-22 22:33:43 +02:00
await saveGitHubUsernameCache ( cache ) ;
2020-01-25 00:43:55 +02:00
2020-01-22 22:33:43 +02:00
return output ;
2021-01-18 16:37:27 +02:00
}
2020-01-22 22:33:43 +02:00
2021-01-18 16:37:27 +02:00
export function patreonOauthToken() {
return readCredentialFile ( 'patreon_oauth_token.txt' ) ;
}
2020-04-16 00:11:42 +02:00
2021-01-18 16:37:27 +02:00
export function githubOauthToken() {
2023-04-04 17:01:34 +02:00
const filename = 'github_oauth_token.txt' ;
if ( hasCredentialFile ( filename ) ) return readCredentialFile ( filename ) ;
if ( process . env . JOPLIN_GITHUB_OAUTH_TOKEN ) return process . env . JOPLIN_GITHUB_OAUTH_TOKEN ;
throw new Error ( ` Cannot get Oauth token. Neither ${ filename } nor the env variable JOPLIN_GITHUB_OAUTH_TOKEN are present ` ) ;
2021-01-18 16:37:27 +02:00
}
2018-02-04 19:42:33 +02:00
2021-12-01 18:16:04 +02:00
// Note that the GitHub API releases/latest is broken on the joplin-android repo
// as of Nov 2021 (last working on 3 November 2021, first broken on 19
// November). It used to return the latest **published** release but now it
2024-02-26 12:16:23 +02:00
// returns... some release, always the same one, but not the latest one. GitHub
2021-12-01 18:16:04 +02:00
// says that nothing has changed on the API, although it used to work. So since
// we can't use /latest anymore, we need to fetch all the releases to find the
// latest published one.
2023-07-11 19:37:43 +02:00
//
// As of July 2023 /latest seems to be working again, so switching back to this
// method, but let's keep the old method just in case they break the API again.
2021-12-01 18:16:04 +02:00
export async function gitHubLatestRelease ( repoName : string ) : Promise < GitHubRelease > {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-07-11 19:37:43 +02:00
const response : any = await fetch ( ` https://api.github.com/repos/laurent22/ ${ repoName } /releases/latest ` , {
headers : {
'Content-Type' : 'application/json' ,
'User-Agent' : 'Joplin Readme Updater' ,
} ,
} ) ;
if ( ! response . ok ) throw new Error ( ` Cannot fetch releases: ${ response . statusText } ` ) ;
return response . json ( ) ;
}
export async function gitHubLatestRelease_KeepInCaseMicrosoftBreaksTheApiAgain ( repoName : string ) : Promise < GitHubRelease > {
2021-12-01 18:16:04 +02:00
let pageNum = 1 ;
while ( true ) {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-12-01 18:16:04 +02:00
const response : any = await fetch ( ` https://api.github.com/repos/laurent22/ ${ repoName } /releases?page= ${ pageNum } ` , {
headers : {
'Content-Type' : 'application/json' ,
'User-Agent' : 'Joplin Readme Updater' ,
} ,
} ) ;
if ( ! response . ok ) throw new Error ( ` Cannot fetch releases: ${ response . statusText } ` ) ;
const releases = await response . json ( ) ;
if ( ! releases . length ) throw new Error ( 'Cannot find latest release' ) ;
for ( const release of releases ) {
if ( release . prerelease || release . draft ) continue ;
return release ;
}
pageNum ++ ;
}
}
2023-09-13 20:01:27 +02:00
export const gitHubLatestReleases = async ( page : number , perPage : number ) = > {
const response = await fetch ( ` https://api.github.com/repos/laurent22/joplin/releases?page= ${ page } &per_page= ${ perPage } ` , {
headers : {
'Content-Type' : 'application/json' ,
'User-Agent' : 'Joplin Forum Updater' ,
} ,
} ) ;
if ( ! response . ok ) throw new Error ( ` Cannot fetch releases: ${ response . statusText } ` ) ;
const releases : GitHubRelease [ ] = await response . json ( ) ;
if ( ! releases . length ) throw new Error ( 'Cannot find latest release' ) ;
return releases ;
} ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-12-01 18:16:04 +02:00
export async function githubRelease ( project : string , tagName : string , options : any = null ) : Promise < GitHubRelease > {
2023-06-01 13:02:36 +02:00
options = { isDraft : false ,
isPreRelease : false , . . . options } ;
2019-01-12 00:07:23 +02:00
2021-01-18 16:37:27 +02:00
const oauthToken = await githubOauthToken ( ) ;
2019-07-30 09:35:42 +02:00
2019-09-19 23:51:18 +02:00
const response = await fetch ( ` https://api.github.com/repos/laurent22/ ${ project } /releases ` , {
2019-07-30 09:35:42 +02:00
method : 'POST' ,
2018-02-04 19:42:33 +02:00
body : JSON.stringify ( {
tag_name : tagName ,
name : tagName ,
2019-01-12 00:07:23 +02:00
draft : options.isDraft ,
prerelease : options.isPreRelease ,
2018-02-04 19:42:33 +02:00
} ) ,
headers : {
'Content-Type' : 'application/json' ,
2019-09-19 23:51:18 +02:00
'Authorization' : ` token ${ oauthToken } ` ,
2018-02-04 19:42:33 +02:00
} ,
} ) ;
const responseText = await response . text ( ) ;
2019-07-30 09:35:42 +02:00
2019-09-19 23:51:18 +02:00
if ( ! response . ok ) throw new Error ( ` Cannot create GitHub release: ${ responseText } ` ) ;
2018-02-04 19:42:33 +02:00
const responseJson = JSON . parse ( responseText ) ;
2019-09-19 23:51:18 +02:00
if ( ! responseJson . url ) throw new Error ( ` No URL for release: ${ responseText } ` ) ;
2018-02-04 19:42:33 +02:00
return responseJson ;
2021-01-18 16:37:27 +02:00
}
2018-02-04 19:42:33 +02:00
2023-09-13 21:34:16 +02:00
export const gitHubLinkify = ( markdown : string ) = > {
markdown = markdown . replace ( /#(\d+)/g , '[#$1](https://github.com/laurent22/joplin/issues/$1)' ) ;
markdown = markdown . replace ( /\(([a-f0-9]+)\)/g , '([$1](https://github.com/laurent22/joplin/commit/$1))' ) ;
return markdown ;
} ;
2021-01-18 16:37:27 +02:00
export function readline ( question : string ) {
2019-09-13 00:16:42 +02:00
return new Promise ( ( resolve ) = > {
2019-06-15 19:58:09 +02:00
const readline = require ( 'readline' ) ;
const rl = readline . createInterface ( {
input : process.stdin ,
2019-07-30 09:35:42 +02:00
output : process.stdout ,
2019-06-15 19:58:09 +02:00
} ) ;
2021-01-18 16:37:27 +02:00
rl . question ( ` ${ question } ` , ( answer : string ) = > {
2019-06-15 19:58:09 +02:00
resolve ( answer ) ;
rl . close ( ) ;
} ) ;
} ) ;
2021-01-18 16:37:27 +02:00
}
2019-06-15 19:58:09 +02:00
2021-01-18 16:37:27 +02:00
export function isLinux() {
2018-10-13 00:25:11 +02:00
return process && process . platform === 'linux' ;
2021-01-18 16:37:27 +02:00
}
2018-10-13 00:25:11 +02:00
2021-01-18 16:37:27 +02:00
export function isWindows() {
2018-10-13 00:25:11 +02:00
return process && process . platform === 'win32' ;
2021-01-18 16:37:27 +02:00
}
2018-10-13 00:25:11 +02:00
2021-01-18 16:37:27 +02:00
export function isMac() {
2018-10-13 00:25:11 +02:00
return process && process . platform === 'darwin' ;
2021-01-18 16:37:27 +02:00
}
2018-10-13 00:25:11 +02:00
2021-01-18 16:37:27 +02:00
export async function insertContentIntoFile ( filePath : string , markerOpen : string , markerClose : string , contentToInsert : string ) {
2023-04-04 17:01:34 +02:00
let content = await readFile ( filePath , 'utf-8' ) ;
2019-07-18 19:36:29 +02:00
// [^]* matches any character including new lines
2019-09-19 23:51:18 +02:00
const regex = new RegExp ( ` ${ markerOpen } [^]*? ${ markerClose } ` ) ;
2019-07-18 19:36:29 +02:00
content = content . replace ( regex , markerOpen + contentToInsert + markerClose ) ;
2023-04-04 17:01:34 +02:00
await writeFile ( filePath , content ) ;
2021-01-18 16:37:27 +02:00
}
2019-07-18 19:36:29 +02:00
2021-01-18 16:37:27 +02:00
export function dirname ( path : string ) {
2020-11-05 18:58:23 +02:00
if ( ! path ) throw new Error ( 'Path is empty' ) ;
const s = path . split ( /\/|\\/ ) ;
s . pop ( ) ;
return s . join ( '/' ) ;
2021-01-18 16:37:27 +02:00
}
2020-11-05 18:58:23 +02:00
2021-01-18 16:37:27 +02:00
export function basename ( path : string ) {
2020-11-05 18:58:23 +02:00
if ( ! path ) throw new Error ( 'Path is empty' ) ;
const s = path . split ( /\/|\\/ ) ;
return s [ s . length - 1 ] ;
2021-01-18 16:37:27 +02:00
}
2020-11-05 18:58:23 +02:00
2021-01-18 16:37:27 +02:00
export function filename ( path : string , includeDir = false ) {
2020-11-05 18:58:23 +02:00
if ( ! path ) throw new Error ( 'Path is empty' ) ;
2021-01-18 16:37:27 +02:00
const output = includeDir ? path : basename ( path ) ;
2020-11-05 18:58:23 +02:00
if ( output . indexOf ( '.' ) < 0 ) return output ;
const splitted = output . split ( '.' ) ;
splitted . pop ( ) ;
return splitted . join ( '.' ) ;
2021-01-18 16:37:27 +02:00
}
2020-11-05 18:58:23 +02:00
2021-01-18 16:37:27 +02:00
export function fileExtension ( path : string ) {
2020-11-05 18:58:23 +02:00
if ( ! path ) throw new Error ( 'Path is empty' ) ;
const output = path . split ( '.' ) ;
if ( output . length <= 1 ) return '' ;
return output [ output . length - 1 ] ;
2021-01-18 16:37:27 +02:00
}