450 строки
16 KiB
JavaScript
450 строки
16 KiB
JavaScript
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*/
|
|
|
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
/* eslint-disable no-undef */
|
|
|
|
"use strict";
|
|
import { promisify } from 'node:util';
|
|
import childProcess from 'node:child_process';
|
|
const exec = promisify(childProcess.exec);
|
|
import gulp from 'gulp';
|
|
import rename from 'gulp-rename';
|
|
import eslint from 'gulp-eslint';
|
|
import gulpTs from "gulp-typescript";
|
|
import replace from 'gulp-replace';
|
|
import mocha from 'gulp-mocha';
|
|
import moment from 'moment';
|
|
import gulpWebpack from 'webpack-stream';
|
|
import webpack from 'webpack';
|
|
import vsce from '@vscode/vsce';
|
|
import yargs from 'yargs';
|
|
import plumber from 'gulp-plumber';
|
|
const argv = yargs(process.argv.slice(2)).argv; // skip 'node' and 'gulp.js' args
|
|
|
|
import fetch from 'node-fetch';
|
|
import fs from 'fs-extra';
|
|
import log from 'fancy-log';
|
|
import path from 'node:path';
|
|
import pslist from 'ps-list';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
import { createRequire } from "node:module";
|
|
const require = createRequire(import.meta.url);
|
|
|
|
import webpackConfig from './webpack.config.js';
|
|
const [nodeConfig, webConfig, webWorkerConfig] = webpackConfig;
|
|
const distdir = path.resolve('./dist');
|
|
const outdir = path.resolve('./out');
|
|
const packagedir = path.resolve('./package');
|
|
const feedPAT = argv.feedPAT || process.env['AZ_DevOps_Read_PAT'];
|
|
const isOfficialBuild = argv.isOfficialBuild && argv.isOfficialBuild.toLowerCase() == "true";
|
|
const isPreviewBuild = argv.isPreviewBuild && argv.isPreviewBuild.toLowerCase() == "true";
|
|
const feedName = argv.useFeed || 'nuget.org';
|
|
|
|
async function clean() {
|
|
(await pslist())
|
|
.filter((info) => info.name.startsWith('pacTelemetryUpload'))
|
|
.forEach(info => {
|
|
log.info(`Terminating: ${info.name} - ${info.pid}...`)
|
|
process.kill(info.pid);
|
|
});
|
|
fs.emptyDirSync(outdir);
|
|
return fs.emptyDir(distdir);
|
|
}
|
|
|
|
function setTelemetryTarget() {
|
|
const telemetryConfigurationSource = isOfficialBuild
|
|
? 'src/common/telemetry/telemetryConfigurationProd.ts'
|
|
: 'src/common/telemetry/telemetryConfigurationDev.ts';
|
|
|
|
return gulp
|
|
.src(telemetryConfigurationSource)
|
|
.pipe(rename('telemetryConfiguration.ts'))
|
|
.pipe(gulp.dest(path.join('src', 'common', 'telemetry-generated')));
|
|
}
|
|
|
|
function setBuildRegion() {
|
|
const buildRegion = isOfficialBuild
|
|
? 'src/common/telemetry/buildRegionProd.ts'
|
|
: 'src/common/telemetry/buildRegionTip.ts';
|
|
|
|
return gulp
|
|
.src(buildRegion)
|
|
.pipe(rename('buildRegionConfiguration.ts'))
|
|
.pipe(gulp.dest(path.join('src', 'common', 'telemetry-generated')));
|
|
}
|
|
|
|
|
|
function compile() {
|
|
return gulp
|
|
.src('src/**/*.ts')
|
|
.pipe(plumber()) // Added error handling
|
|
.pipe(gulpWebpack(nodeConfig, webpack))
|
|
.pipe(replace("src\\\\client\\\\lib\\\\", "src/client/lib/")) // Hacky fix: vscode-nls-dev/lib/webpack-loader uses Windows style paths when built on Windows, breaking localization on Linux & Mac
|
|
.pipe(gulp.dest(distdir));
|
|
}
|
|
|
|
function compileWeb() {
|
|
return gulp
|
|
.src('src/web/**/*.ts')
|
|
.pipe(plumber()) // Added error handling
|
|
.pipe(gulpWebpack(webConfig, webpack))
|
|
.pipe(replace("src\\\\client\\\\lib\\\\", "src/client/lib/")) // Hacky fix: vscode-nls-dev/lib/webpack-loader uses Windows style paths when built on Windows, breaking localization on Linux & Mac
|
|
.pipe(gulp.dest(path.resolve(`${distdir}/web`)));
|
|
}
|
|
|
|
// compile the web worker for vscode for web
|
|
function compileWorker() {
|
|
return gulp
|
|
.src(["src/web/**/*.ts"])
|
|
.pipe(plumber()) // Added error handling
|
|
.pipe(gulpWebpack(webWorkerConfig, webpack))
|
|
.pipe(gulp.dest(path.resolve(`${distdir}/web`)));
|
|
}
|
|
|
|
async function nugetInstall(nugetSource, packageName, version, targetDir) {
|
|
// https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource
|
|
const feeds = {
|
|
'nuget.org': {
|
|
authenticated: false,
|
|
baseUrl: 'https://api.nuget.org/v3-flatcontainer/'
|
|
},
|
|
'CAP_ISVExp_Tools_Daily': {
|
|
authenticated: true,
|
|
// https://dev.azure.com/msazure/One/_packaging?_a=feed&feed=CAP_ISVExp_Tools_Daily
|
|
baseUrl: 'https://pkgs.dev.azure.com/msazure/_packaging/d3fb5788-d047-47f9-9aba-76890f5cecf0/nuget/v3/flat2/'
|
|
},
|
|
'CAP_ISVExp_Tools_Stable': {
|
|
authenticated: true,
|
|
// https://dev.azure.com/msazure/One/_packaging?_a=feed&feed=CAP_ISVExp_Tools_Stable
|
|
baseUrl: 'https://pkgs.dev.azure.com/msazure/_packaging/b0441cf8-0bc8-4fad-b126-841a6184e784/nuget/v3/flat2/'
|
|
},
|
|
'Power_Platform_Tools_Feed': {
|
|
authenticated: true,
|
|
baseUrl: 'https://pkgs.dev.azure.com/dynamicscrm/OneCRM/_packaging/6f6505ca-e632-4c4c-9115-6d45f61758cc/nuget/v3/flat2/'
|
|
}
|
|
}
|
|
|
|
const selectedFeed = feeds[nugetSource];
|
|
const baseUrl = selectedFeed.baseUrl;
|
|
|
|
packageName = packageName.toLowerCase();
|
|
version = version.toLowerCase();
|
|
const packagePath = `${packageName}/${version}/${packageName}.${version}.nupkg`;
|
|
|
|
const nupkgUrl = new URL(packagePath, baseUrl);
|
|
const reqInit = {
|
|
headers: {
|
|
'User-Agent': 'gulpfile-DPX-team/0.1',
|
|
'Accept': '*/*'
|
|
},
|
|
redirect: 'manual'
|
|
};
|
|
if (selectedFeed.authenticated) {
|
|
if (!feedPAT) {
|
|
throw new Error(`nuget feed ${nugetSource} requires authN but neither '--feedToken' argument nor env var 'AZ_DevOps_Read_PAT' was defined!`);
|
|
}
|
|
reqInit.headers['Authorization'] = `Basic ${Buffer.from('PAT:' + feedPAT).toString('base64')}`;
|
|
}
|
|
|
|
log.info(`Downloading package: ${nupkgUrl}...`);
|
|
let res = await fetch(nupkgUrl, reqInit);
|
|
if (res.status === 303) {
|
|
const location = res.headers.get('location');
|
|
const url = new URL(location);
|
|
log.info(` ... redirecting to: ${url.origin}${url.pathname}}...`);
|
|
// AzDevOps feeds will redirect to Azure storage with location url w/ SAS token: on 2nd request drop authZ header
|
|
delete reqInit.headers['Authorization'];
|
|
res = await fetch(location, reqInit);
|
|
}
|
|
if (!res.ok) {
|
|
const body = res.body.read();
|
|
throw new Error(`Cannot download ${res.url}, status: ${res.statusText} (${res.status}), body: ${body ? body.toString('ascii') : '<empty>'}`);
|
|
}
|
|
|
|
const localNupkg = path.join(targetDir, `${packageName}.${version}.nupkg`);
|
|
fs.ensureDirSync(targetDir);
|
|
return new Promise((resolve, reject) => {
|
|
res.body.pipe(fs.createWriteStream(localNupkg))
|
|
.on('close', () => {
|
|
resolve();
|
|
}).on('error', err => {
|
|
reject(err);
|
|
})
|
|
});
|
|
}
|
|
|
|
function lint() {
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
return gulp
|
|
.src(['src/**/*.ts', __filename])
|
|
.pipe(eslint({
|
|
formatter: 'verbose',
|
|
configuration: '.eslintrc.js'
|
|
}))
|
|
.pipe(eslint.format())
|
|
.pipe(eslint.results(results => {
|
|
if (results.warningCount > 0) {
|
|
throw new Error(`Found ${results.warningCount} eslint errors.`)
|
|
}
|
|
}))
|
|
.pipe(eslint.failAfterError());
|
|
}
|
|
|
|
function testUnitTests() {
|
|
return gulp
|
|
.src(
|
|
[
|
|
"src/server/test/unit/**/*.ts",
|
|
"src/client/test/unit/**/*.ts",
|
|
"src/debugger/test/unit/**/*.ts",
|
|
"src/web/client/test/unit/**/*.ts",
|
|
],
|
|
{
|
|
read: false,
|
|
}
|
|
)
|
|
.pipe(
|
|
mocha({
|
|
require: ["ts-node/register"],
|
|
ui: "bdd",
|
|
})
|
|
);
|
|
}
|
|
|
|
function testWeb() {
|
|
return gulp.src(["src/web/client/test/unit/**/*.ts"], { read: false }).pipe(
|
|
mocha({
|
|
require: ["ts-node/register"],
|
|
ui: "bdd",
|
|
})
|
|
);
|
|
}
|
|
|
|
// unit tests without special test runner
|
|
const test = gulp.series(testUnitTests, testWeb);
|
|
|
|
/**
|
|
* Compiles the integration tests and transpiles the results to /out
|
|
*/
|
|
function compileIntegrationTests() {
|
|
const tsProject = gulpTs.createProject("tsconfig.json", {
|
|
// to test puppeteer we need "dom".
|
|
// since "dom" overlaps with "webworker" we need to overwrite the lib property.
|
|
// This is a known ts issue (bot being able to have both webworker and dom): https://github.com/microsoft/TypeScript/issues/20595
|
|
lib: ["es2019", "dom", "dom.iterable"],
|
|
});
|
|
return gulp.src(["src/**/*.ts"]).pipe(tsProject()).pipe(gulp.dest("out"));
|
|
}
|
|
|
|
/**
|
|
* Tests the debugger integration tests after transpiling the source files to /out
|
|
*/
|
|
const testDebugger = gulp.series(compileIntegrationTests, async () => {
|
|
const testRunner = require("./out/debugger/test/runTest");
|
|
await testRunner.main();
|
|
});
|
|
|
|
// tests that require vscode-electron (which requires a display or xvfb)
|
|
const testInt = gulp.series(testDebugger);
|
|
|
|
/**
|
|
* Tests the debugger integration tests after transpiling the source files to /out
|
|
*/
|
|
const testWebIntegration = gulp.series(compileIntegrationTests, async () => {
|
|
const testRunner = require("./out/web/client/test/runTest");
|
|
await testRunner.main();
|
|
});
|
|
|
|
// tests that require vscode-electron (which requires a display or xvfb)
|
|
const testWebInt = gulp.series(testWebIntegration);
|
|
|
|
/**
|
|
* Tests the power-pages integration tests after transpiling the source files to /out
|
|
*/
|
|
const testDesktopIntegration = gulp.series(compileIntegrationTests, async () => {
|
|
const testRunner = require("./out/client/test/runTest");
|
|
await testRunner.main();
|
|
});
|
|
|
|
// tests that require vscode-electron (which requires a display or xvfb)
|
|
const testDesktopInt = gulp.series(testDesktopIntegration);
|
|
|
|
async function packageVsix() {
|
|
fs.ensureDirSync(packagedir);
|
|
await vsce.createVSIX({
|
|
packagePath: packagedir,
|
|
preRelease: isPreviewBuild,
|
|
});
|
|
}
|
|
|
|
async function git(args) {
|
|
args.unshift('git');
|
|
const { stdout, stderr } = await exec(args.join(' '));
|
|
return { stdout: stdout, stderr: stderr };
|
|
}
|
|
|
|
async function npx(args) {
|
|
args.unshift('npx');
|
|
const { stdout, stderr } = await exec(args.join(' '));
|
|
return { stdout: stdout, stderr: stderr };
|
|
}
|
|
|
|
async function setGitAuthN() {
|
|
const repoUrl = 'https://github.com';
|
|
const repoToken = argv.repoToken;
|
|
if (!repoToken) {
|
|
throw new Error(`Must specify parameter --repoToken with read and push rights to ${repoUrl}!`);
|
|
}
|
|
const bearer = `AUTHORIZATION: basic ${Buffer.from(`PAT:${repoToken}`).toString('base64')}`;
|
|
await git(['config', '--local', `http.${repoUrl}/.extraheader`, `"${bearer}"`]);
|
|
await git(['config', '--local', 'user.email', 'capisvaatdev@microsoft.com']);
|
|
await git(['config', '--local', 'user.name', '"DPT Tools Dev Team"']);
|
|
}
|
|
|
|
async function snapshot() {
|
|
const targetBranch = argv.targetBranch || 'release/daily';
|
|
const sourceSpecParam = argv.sourceSpec;
|
|
|
|
const tmpRepo = path.resolve('./out/tmpRepo');
|
|
fs.emptyDirSync(tmpRepo);
|
|
|
|
const repoUrl = (await git(['remote', 'get-url', '--all', 'origin'])).stdout.trim();
|
|
log.info(`snapshot: remote repoUrl: ${repoUrl}`);
|
|
const orgDir = process.cwd();
|
|
process.chdir(tmpRepo);
|
|
try {
|
|
await git(['init']);
|
|
await git(['remote', 'add', 'origin', repoUrl]);
|
|
await setGitAuthN();
|
|
await git(['fetch', 'origin']);
|
|
const remotes = (await git(['remote', 'show', 'origin'])).stdout;
|
|
const head = remotes
|
|
.split('\n')
|
|
.map(line => {
|
|
const branch = line.match(/HEAD branch:\s*(\S+)/);
|
|
if (branch && branch.length >= 2) {
|
|
return branch[1];
|
|
}
|
|
})
|
|
.filter(b => !!b);
|
|
if (!head || head.length < 1 || head.length > 1 || !head[0]) {
|
|
throw new Error(`Cannot determine HEAD from remote: ${repoUrl}`);
|
|
}
|
|
const headBranch = head[0];
|
|
if (headBranch == targetBranch) {
|
|
throw new Error(`Cannot snapshot into default HEAD branch: ${headBranch}`);
|
|
}
|
|
const sourceSpec = sourceSpecParam || `origin/${headBranch}`;
|
|
log.info(` > snap shotting '${sourceSpec}' into branch: ${targetBranch}...`);
|
|
await git(['checkout', headBranch]);
|
|
const snapshotTag = `snapshot-${targetBranch.replace('/', '_').replace(' ', '_')}-${moment.utc().format('YYMMDD[Z]HHmmss')}`;
|
|
// await git(['tag', snapshotTag, sourceSpec]);
|
|
await git(['checkout', '--force', '-B', targetBranch]);
|
|
const resetMsg = (await git(['reset', '--hard', `"${sourceSpec}"`])).stdout.trim();
|
|
log.info(` > snapshot (${snapshotTag}): ${resetMsg}`);
|
|
log.info(` > pushing snapshot branch '${targetBranch} to origin...`);
|
|
const pushMsg = (await git(['push', '--force', '--tags', 'origin', targetBranch])).stderr.trim();
|
|
log.info(` > ${pushMsg}`)
|
|
}
|
|
finally {
|
|
process.chdir(orgDir);
|
|
}
|
|
}
|
|
|
|
const cliVersion = '1.34.4';
|
|
|
|
const recompile = gulp.series(
|
|
clean,
|
|
async () => nugetInstall(feedName, 'Microsoft.PowerApps.CLI', cliVersion, path.resolve(distdir, 'pac')),
|
|
async () => nugetInstall(feedName, 'Microsoft.PowerApps.CLI.Tool', cliVersion, path.resolve(distdir, 'pac')),
|
|
translationsExport,
|
|
translationsImport,
|
|
setTelemetryTarget,
|
|
setBuildRegion,
|
|
compile,
|
|
compileWeb,
|
|
compileWorker,
|
|
);
|
|
|
|
const dist = gulp.series(
|
|
recompile,
|
|
packageVsix,
|
|
lint,
|
|
test
|
|
);
|
|
// Extract all the localizable strings from TS and package.nls.json, and package into
|
|
// an XLF for the localization team
|
|
async function translationsExport() {
|
|
await npx(["@vscode/l10n-dev", "export", "--outDir", "./l10n", "./src"]);
|
|
await npx(["@vscode/l10n-dev", "generate-xlf",
|
|
"./package.nls.json", "./l10n/bundle.l10n.json",
|
|
"--outFile", "./loc/translations-export/vscode-powerplatform.xlf"]);
|
|
return gulp.src('./loc/translations-export/vscode-powerplatform.xlf')
|
|
.pipe(replace("'", "'"))
|
|
.pipe(replace(" ", "\n"))
|
|
.pipe(gulp.dest('./loc/translations-export/'));
|
|
}
|
|
|
|
// const languages = [
|
|
// //{ id: "bg", folderName: "bul" },
|
|
// //{ id: "hu", folderName: "hun" },
|
|
// //{ id: "pl", folderName: "plk" },
|
|
// { id: "cs", folderName: "csy" },
|
|
// { id: "de", folderName: "deu" },
|
|
// { id: "es", folderName: "esn" },
|
|
// { id: "fr", folderName: "fra" },
|
|
// { id: "it", folderName: "ita" },
|
|
// { id: "ja", folderName: "jpn" },
|
|
// { id: "ko", folderName: "kor" },
|
|
// { id: "pt-BR", folderName: "ptb" },
|
|
// { id: "ru", folderName: "rus" },
|
|
// { id: "tr", folderName: "trk" },
|
|
// { id: "zh-CN", folderName: "chs" },
|
|
// { id: "zh-TW", folderName: "cht" },
|
|
// ];
|
|
|
|
async function translationsImport() {
|
|
await npx(["@vscode/l10n-dev", "import-xlf", "./loc/translations-import/*.xlf", "--outDir", "./l10n"]);
|
|
|
|
// `@vscode/l10n-dev import-xlf` places both the package.nls.*.json and bundle.l10n.*.json files in the
|
|
// same directory, but the package.nls.*.json need to reside at the repo root next to package.json
|
|
gulp.src('./l10n/package.nls.*.json')
|
|
.pipe(replace("\\r\\n", "\\n"))
|
|
.pipe(replace("\\\\n", "\\n"))
|
|
.pipe(gulp.dest('./'));
|
|
|
|
// Fix up changes from the XLF Export / Import process that cause lookup misses
|
|
return gulp.src('./l10n/bundle.l10n.*.json')
|
|
.pipe(replace("\\r\\n", "\\n"))
|
|
.pipe(replace("\\\\n", "\\n"))
|
|
.pipe(replace("'", "'"))
|
|
.pipe(gulp.dest('./l10n'));
|
|
}
|
|
|
|
export {
|
|
clean,
|
|
compile,
|
|
compileWeb,
|
|
compileWorker,
|
|
recompile,
|
|
snapshot,
|
|
lint,
|
|
test,
|
|
testWeb,
|
|
compileIntegrationTests,
|
|
testInt,
|
|
testWebInt,
|
|
testDesktopInt,
|
|
packageVsix as package,
|
|
dist as ci,
|
|
dist,
|
|
translationsExport,
|
|
translationsImport,
|
|
setGitAuthN,
|
|
compile as default,
|
|
}
|