build-scripts/packageExtension.ts (144 lines of code) (raw):
/*!
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: MIT
*/
import ncp = require('child_process')
import esbuild = require('esbuild')
import fs = require('fs-extra')
import path = require('path')
import folders = require('./scriptUtils')
import os = require('os')
const timeMessage = 'Packaged extension'
const manifestFile = 'vss-extension.json'
const ignoredFolders = ['Common', '.DS_Store']
const vstsFiles = ['task.json', 'task.loc.json', 'package.json', 'icon.png', 'Strings']
interface CommandLineOptions {
publisher?: string
}
/**
* Patch shelljs, because it is not compatible with esbuild
* More info: https://github.com/aws/aws-toolkit-azure-devops/pull/539
*/
class ShelljsPatch {
private static readonly shelljsRootPath = path.resolve(process.cwd(), './node_modules/shelljs/')
private static readonly shelljsEntryPath = path.join(ShelljsPatch.shelljsRootPath, 'shell.js')
private originalContent: string | undefined
patch() {
const sourceRequireString =
"require('./commands').forEach(function (command) {\n require('./src/' + command);\n});"
const originalContent = fs.readFileSync(ShelljsPatch.shelljsEntryPath, 'utf-8')
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fixedRequireString = require(path.join(ShelljsPatch.shelljsRootPath, 'commands.js'))
.map((command: string) => `require('./src/${command}.js');`)
.join('\n')
const patchedContent = originalContent.replace(sourceRequireString, fixedRequireString)
if (originalContent === patchedContent) {
throw new Error('Could not patch shelljs, was this npm package updated?')
}
fs.writeFileSync(ShelljsPatch.shelljsEntryPath, patchedContent)
this.originalContent = originalContent
}
unpatch() {
if (this.originalContent) {
fs.writeFileSync(ShelljsPatch.shelljsEntryPath, this.originalContent)
this.originalContent = undefined
}
}
}
const shelljsPatch = new ShelljsPatch()
function findMatchingFiles(directory: string) {
return fs.readdirSync(directory)
}
function installNodePackages(directory: string) {
fs.mkdirpSync(directory)
const npmCmd = `npm install --prefix ${directory} azure-pipelines-task-lib --only=production`
const output = ncp.execSync(npmCmd)
console.log(output.toString('utf8'))
}
function generateGitHashFile() {
try {
const response = ncp.execSync('git rev-parse HEAD', { encoding: 'utf-8' }).toString()
console.log(`Putting git hash ${response.trim()} into the package directory`)
fs.outputFileSync(path.join(folders.packageRoot, '.gitcommit'), response.trim())
} catch (e) {
console.log(`Getting commit hash failed ${e}`)
throw e
}
}
function packagePlugin(options: CommandLineOptions) {
fs.mkdirpSync(folders.packageRoot)
fs.mkdirpSync(folders.packageTasks)
const npmFolder = path.join(folders.buildRoot, 'npmcache')
fs.copySync(path.join(folders.repoRoot, 'LICENSE'), path.join(folders.packageRoot, 'LICENSE'), { overwrite: true })
fs.copySync(path.join(folders.repoRoot, 'README.md'), path.join(folders.packageRoot, 'README.md'), {
overwrite: true
})
fs.copySync(path.join(folders.repoRoot, 'build', manifestFile), path.join(folders.packageRoot, manifestFile), {
overwrite: true
})
generateGitHashFile()
// stage manifest images
fs.copySync(path.join(folders.repoRoot, 'images'), path.join(folders.packageRoot, 'images'), { overwrite: true })
// get required npm packages that will be copied
installNodePackages(npmFolder)
shelljsPatch.patch()
// clean, dedupe and pack each task as needed
findMatchingFiles(folders.sourceTasks).forEach(function(taskName) {
console.log('Processing task ' + taskName)
if (ignoredFolders.some(folderName => folderName === taskName)) {
console.log('Skpping task ' + taskName)
return
}
const taskBuildFolder = path.join(folders.buildTasks, taskName)
const taskPackageFolder = path.join(folders.packageTasks, taskName)
fs.mkdirpSync(taskPackageFolder)
// eslint-disable-next-line @typescript-eslint/no-var-requires
const taskDef = require(path.join(taskBuildFolder, 'task.json'))
if (
!Object.hasOwnProperty.call(taskDef.execution, 'Node') &&
!Object.hasOwnProperty.call(taskDef.execution, 'Node10') &&
!Object.hasOwnProperty.call(taskDef.execution, 'Node14')
) {
console.log('Copying non-node task ' + taskName)
fs.copySync(taskBuildFolder, taskPackageFolder)
return
}
for (const resourceFile of vstsFiles) {
fs.copySync(path.join(taskBuildFolder, resourceFile), path.join(taskPackageFolder, resourceFile), {
overwrite: true
})
}
// we also need lib.json from azure pipelines task lib or else localization will not work properly
fs.copySync(
path.join(folders.repoRoot, 'node_modules/azure-pipelines-task-lib/lib.json'),
path.join(taskPackageFolder, 'lib.json'),
{ overwrite: true }
)
const inputFilename = path.join(taskBuildFolder, taskName + '.runner.js')
console.log('packing node-based task')
const result = esbuild.buildSync({
entryPoints: [inputFilename],
bundle: true,
platform: 'node',
target: ['node10'],
minify: true,
outfile: `${taskPackageFolder}/${taskName}.js`
})
result.warnings.forEach(warning => console.log(warning))
})
shelljsPatch.unpatch()
console.log('Creating deployment vsix')
const binName = os.platform() === 'win32' ? `tfx.cmd` : 'tfx'
const tfx = path.join(process.cwd(), 'node_modules', '.bin', binName)
const args: string[] = ['extension', 'create', '--root', folders.packageRoot]
args.push('--output-path', folders.packageRoot)
args.push('--manifests', path.join(folders.packageRoot, manifestFile))
if (options.publisher) {
args.push('--publisher', options.publisher)
}
console.log('Packaging with:' + `${tfx} ${args.join(' ')}`)
ncp.execFileSync(tfx, args, {
stdio: 'inherit',
shell: os.platform() === 'win32' // for tfx.cmd on Windows.
})
console.log('Packaging successful')
}
console.time(timeMessage)
const commandLineInput = process.argv.slice(2) ?? ''
const parsedOptions: CommandLineOptions = {}
if (commandLineInput.length > 0 && commandLineInput[0].split('=')[0] === 'publisher') {
parsedOptions.publisher = commandLineInput[0].split('=')[1]
}
try {
packagePlugin(parsedOptions)
} catch (e) {
shelljsPatch.unpatch()
throw e
}
console.timeEnd(timeMessage)