2020-10-21 01:23:55 +02:00
const path = require ( 'path' ) ;
2021-01-04 20:46:43 +02:00
const crypto = require ( 'crypto' ) ;
2020-11-18 12:17:27 +02:00
const fs = require ( 'fs-extra' ) ;
2021-01-04 20:46:43 +02:00
const chalk = require ( 'chalk' ) ;
2020-10-21 01:23:55 +02:00
const CopyPlugin = require ( 'copy-webpack-plugin' ) ;
2020-11-18 12:17:27 +02:00
const WebpackOnBuildPlugin = require ( 'on-build-webpack' ) ;
const tar = require ( 'tar' ) ;
const glob = require ( 'glob' ) ;
2021-01-04 20:46:43 +02:00
const execSync = require ( 'child_process' ) . execSync ;
const rootDir = path . resolve ( _ _dirname ) ;
const distDir = path . resolve ( rootDir , 'dist' ) ;
const srcDir = path . resolve ( rootDir , 'src' ) ;
const publishDir = path . resolve ( rootDir , 'publish' ) ;
const manifestPath = ` ${ srcDir } /manifest.json ` ;
const packageJsonPath = ` ${ rootDir } /package.json ` ;
const manifest = readManifest ( manifestPath ) ;
const pluginArchiveFilePath = path . resolve ( publishDir , ` ${ manifest . id } .jpl ` ) ;
const pluginInfoFilePath = path . resolve ( publishDir , ` ${ manifest . id } .json ` ) ;
fs . removeSync ( distDir ) ;
fs . removeSync ( publishDir ) ;
fs . mkdirpSync ( publishDir ) ;
function validatePackageJson ( ) {
const content = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) ) ;
if ( ! content . name || content . name . indexOf ( 'joplin-plugin-' ) !== 0 ) {
console . warn ( chalk . yellow ( ` WARNING: To publish the plugin, the package name should start with "joplin-plugin-" (found " ${ content . name } ") in ${ packageJsonPath } ` ) ) ;
}
if ( ! content . keywords || content . keywords . indexOf ( 'joplin-plugin' ) < 0 ) {
console . warn ( chalk . yellow ( ` WARNING: To publish the plugin, the package keywords should include "joplin-plugin" (found " ${ JSON . stringify ( content . keywords ) } ") in ${ packageJsonPath } ` ) ) ;
}
if ( content . scripts && content . scripts . postinstall ) {
console . warn ( chalk . yellow ( ` WARNING: package.json contains a "postinstall" script. It is recommended to use a "prepare" script instead so that it is executed before publish. In ${ packageJsonPath } ` ) ) ;
}
}
function fileSha256 ( filePath ) {
const content = fs . readFileSync ( filePath ) ;
return crypto . createHash ( 'sha256' ) . update ( content ) . digest ( 'hex' ) ;
}
function currentGitInfo ( ) {
try {
let branch = execSync ( 'git rev-parse --abbrev-ref HEAD' , { stdio : 'pipe' } ) . toString ( ) . trim ( ) ;
const commit = execSync ( 'git rev-parse HEAD' , { stdio : 'pipe' } ) . toString ( ) . trim ( ) ;
if ( branch === 'HEAD' ) branch = 'master' ;
return ` ${ branch } : ${ commit } ` ;
} catch ( error ) {
const messages = error . message ? error . message . split ( '\n' ) : [ '' ] ;
console . info ( chalk . cyan ( 'Could not get git commit (not a git repo?):' , messages [ 0 ] . trim ( ) ) ) ;
console . info ( chalk . cyan ( 'Git information will not be stored in plugin info file' ) ) ;
return '' ;
}
}
2020-11-18 12:17:27 +02:00
function readManifest ( manifestPath ) {
const content = fs . readFileSync ( manifestPath , 'utf8' ) ;
const output = JSON . parse ( content ) ;
if ( ! output . id ) throw new Error ( ` Manifest plugin ID is not set in ${ manifestPath } ` ) ;
return output ;
}
function createPluginArchive ( sourceDir , destPath ) {
2020-12-11 15:28:59 +02:00
const distFiles = glob . sync ( ` ${ sourceDir } /**/* ` , { nodir : true } )
2020-11-18 12:17:27 +02:00
. map ( f => f . substr ( sourceDir . length + 1 ) ) ;
2020-11-23 19:06:52 +02:00
if ( ! distFiles . length ) {
// Usually means there's an error, which is going to be printed by
// webpack
2021-01-04 20:46:43 +02:00
console . warn ( chalk . yellow ( 'Plugin archive was not created because the "dist" directory is empty' ) ) ;
2020-11-23 19:06:52 +02:00
return ;
}
2020-11-18 12:17:27 +02:00
fs . removeSync ( destPath ) ;
tar . create (
{
strict : true ,
portable : true ,
file : destPath ,
cwd : sourceDir ,
sync : true ,
} ,
distFiles
) ;
2021-01-04 20:46:43 +02:00
console . info ( chalk . cyan ( ` Plugin archive has been created in ${ destPath } ` ) ) ;
2020-11-18 12:17:27 +02:00
}
2021-01-04 20:46:43 +02:00
function createPluginInfo ( manifestPath , destPath , jplFilePath ) {
const contentText = fs . readFileSync ( manifestPath , 'utf8' ) ;
const content = JSON . parse ( contentText ) ;
content . _publish _hash = ` sha256: ${ fileSha256 ( jplFilePath ) } ` ;
content . _publish _commit = currentGitInfo ( ) ;
fs . writeFileSync ( destPath , JSON . stringify ( content , null , '\t' ) , 'utf8' ) ;
}
2020-10-21 01:23:55 +02:00
2021-01-04 20:46:43 +02:00
function onBuildCompleted ( ) {
createPluginArchive ( distDir , pluginArchiveFilePath ) ;
createPluginInfo ( manifestPath , pluginInfoFilePath , pluginArchiveFilePath ) ;
validatePackageJson ( ) ;
}
2020-11-23 19:06:52 +02:00
2021-01-03 15:21:48 +02:00
const baseConfig = {
2020-10-21 01:23:55 +02:00
mode : 'production' ,
target : 'node' ,
2021-01-04 20:46:43 +02:00
stats : 'errors-only' ,
2020-10-21 01:23:55 +02:00
module : {
rules : [
{
test : /\.tsx?$/ ,
use : 'ts-loader' ,
exclude : /node_modules/ ,
} ,
] ,
} ,
2021-01-03 15:21:48 +02:00
} ;
const pluginConfig = Object . assign ( { } , baseConfig , {
entry : './src/index.ts' ,
2020-10-21 01:23:55 +02:00
resolve : {
alias : {
2020-11-15 16:18:46 +02:00
api : path . resolve ( _ _dirname , 'api' ) ,
2020-10-21 01:23:55 +02:00
} ,
2020-11-15 16:18:46 +02:00
extensions : [ '.tsx' , '.ts' , '.js' ] ,
2020-10-21 01:23:55 +02:00
} ,
output : {
filename : 'index.js' ,
2020-11-18 12:17:27 +02:00
path : distDir ,
2020-10-21 01:23:55 +02:00
} ,
2021-01-03 15:21:48 +02:00
} ) ;
const lastStepConfig = {
2020-10-21 01:23:55 +02:00
plugins : [
new CopyPlugin ( {
patterns : [
{
2020-11-15 16:18:46 +02:00
from : '**/*' ,
2020-10-21 01:23:55 +02:00
context : path . resolve ( _ _dirname , 'src' ) ,
to : path . resolve ( _ _dirname , 'dist' ) ,
globOptions : {
ignore : [
2021-01-03 15:21:48 +02:00
// All TypeScript files are compiled to JS and
// already copied into /dist so we don't copy them.
2020-10-21 01:23:55 +02:00
'**/*.ts' ,
'**/*.tsx' ,
2021-01-03 15:21:48 +02:00
// Currently we don't support JS files for the main
// plugin script. We support it for content scripts,
2021-01-04 20:46:43 +02:00
// but they should be declared in manifest.json,
2021-01-03 15:21:48 +02:00
// and then they are also compiled and copied to
// /dist. So wse also don't need to copy JS files.
'**/*.js' ,
2020-10-21 01:23:55 +02:00
] ,
} ,
} ,
] ,
} ) ,
2021-01-04 20:46:43 +02:00
new WebpackOnBuildPlugin ( onBuildCompleted ) ,
2020-10-21 01:23:55 +02:00
] ,
} ;
2021-01-03 15:21:48 +02:00
const contentScriptConfig = Object . assign ( { } , baseConfig , {
resolve : {
alias : {
api : path . resolve ( _ _dirname , 'api' ) ,
} ,
extensions : [ '.tsx' , '.ts' , '.js' ] ,
} ,
} ) ;
function resolveContentScriptPaths ( name ) {
if ( [ '.js' , '.ts' , '.tsx' ] . includes ( path . extname ( name ) . toLowerCase ( ) ) ) {
throw new Error ( ` Content script path must not include file extension: ${ name } ` ) ;
}
const pathsToTry = [
` ./src/ ${ name } .ts ` ,
` ${ './src/' + '/' } ${ name } .js ` ,
] ;
for ( const pathToTry of pathsToTry ) {
if ( fs . pathExistsSync ( ` ${ rootDir } / ${ pathToTry } ` ) ) {
return {
entry : pathToTry ,
output : {
filename : ` ${ name } .js ` ,
path : distDir ,
library : 'default' ,
libraryTarget : 'commonjs' ,
libraryExport : 'default' ,
} ,
} ;
}
}
throw new Error ( ` Could not find content script " ${ name } " at locations ${ JSON . stringify ( pathsToTry ) } ` ) ;
}
function createContentScriptConfigs ( ) {
if ( ! manifest . content _scripts ) return [ ] ;
const output = [ ] ;
for ( const contentScriptName of manifest . content _scripts ) {
const scriptPaths = resolveContentScriptPaths ( contentScriptName ) ;
output . push ( Object . assign ( { } , contentScriptConfig , {
entry : scriptPaths . entry ,
output : scriptPaths . output ,
} ) ) ;
}
return output ;
}
const exportedConfigs = [ pluginConfig ] . concat ( createContentScriptConfigs ( ) ) ;
exportedConfigs [ exportedConfigs . length - 1 ] = Object . assign ( { } , exportedConfigs [ exportedConfigs . length - 1 ] , lastStepConfig ) ;
module . exports = exportedConfigs ;