532 строки
20 KiB
JavaScript
532 строки
20 KiB
JavaScript
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
const path = require('path');
|
|
const merge = require('lodash/merge');
|
|
const sass = require('sass');
|
|
const targets = require('./targets.config');
|
|
|
|
module.exports = function (grunt) {
|
|
const typedScssModulesPath = path.resolve('./node_modules/.bin/typed-scss-modules');
|
|
const webpackPath = path.resolve('./node_modules/.bin/webpack');
|
|
|
|
const extensionPath = 'extension';
|
|
|
|
const packageReportPath = path.join('packages', 'report');
|
|
const packageReportBundlePath = path.join(packageReportPath, 'bundle');
|
|
const packageReportDropPath = path.join(packageReportPath, 'drop');
|
|
|
|
const packageUIPath = path.join('packages', 'ui');
|
|
const packageUIBundlePath = path.join(packageUIPath, 'bundle');
|
|
const packageUIDropPath = path.join(packageUIPath, 'drop');
|
|
|
|
const packageValidatorPath = path.join('packages', 'validator');
|
|
const packageValidatorBundlePath = path.join(packageValidatorPath, 'bundle');
|
|
const packageValidatorDropPath = path.join(packageValidatorPath, 'drop');
|
|
|
|
const packageAxeConfigPath = path.join('packages', 'axe-config');
|
|
const packageAxeConfigBundlePath = path.join(packageAxeConfigPath, 'bundle');
|
|
const packageAxeConfigDropPath = path.join(packageAxeConfigPath, 'drop');
|
|
|
|
function mustExist(file, reason) {
|
|
const normalizedFile = path.normalize(file);
|
|
if (!grunt.file.exists(normalizedFile)) {
|
|
grunt.fail.fatal(`Missing required file ${normalizedFile}\n${reason}`);
|
|
}
|
|
}
|
|
|
|
grunt.initConfig({
|
|
bom: {
|
|
cwd: path.resolve('./src/**/*.{ts,tsx,js,snap,html,scss,css}'),
|
|
},
|
|
clean: {
|
|
intermediates: ['dist', extensionPath],
|
|
'package-report': packageReportDropPath,
|
|
'package-ui': packageUIDropPath,
|
|
'package-validator': packageValidatorDropPath,
|
|
'package-axe-config': packageAxeConfigDropPath,
|
|
scss: path.join('src', '**/*.scss.d.ts'),
|
|
},
|
|
concurrent: {
|
|
'compile-all': ['exec:esbuild-dev', 'exec:esbuild-prod'],
|
|
},
|
|
copy: {
|
|
code: {
|
|
files: [
|
|
{
|
|
cwd: './src',
|
|
src: ['manifest.json'],
|
|
dest: extensionPath,
|
|
expand: true,
|
|
},
|
|
{
|
|
cwd: './src',
|
|
src: ['./**/*.html', '!./tests/**/*'],
|
|
dest: extensionPath,
|
|
expand: true,
|
|
},
|
|
],
|
|
},
|
|
images: {
|
|
files: [
|
|
{
|
|
cwd: './src',
|
|
src: ['./**/*.{png,ico,icns}', '!./tests/**/*'],
|
|
dest: extensionPath,
|
|
expand: true,
|
|
},
|
|
],
|
|
},
|
|
styles: {
|
|
files: [
|
|
{
|
|
cwd: './src',
|
|
src: '**/*.css',
|
|
dest: extensionPath,
|
|
expand: true,
|
|
},
|
|
{
|
|
cwd: './dist/src/reports',
|
|
src: '*.css',
|
|
dest: path.join(extensionPath, 'reports'),
|
|
expand: true,
|
|
},
|
|
{
|
|
cwd: './dist/src/views',
|
|
src: '**/*.css',
|
|
dest: path.join(extensionPath, 'views'),
|
|
expand: true,
|
|
},
|
|
{
|
|
cwd: './dist/src/DetailsView/Styles',
|
|
src: '*.css',
|
|
dest: path.join(extensionPath, 'DetailsView/styles/default'),
|
|
expand: true,
|
|
},
|
|
{
|
|
cwd: './dist/src/injected/styles',
|
|
src: '*.css',
|
|
dest: path.join(extensionPath, 'injected/styles/default'),
|
|
expand: true,
|
|
},
|
|
{
|
|
cwd: './dist/src/popup/Styles',
|
|
src: '*.css',
|
|
dest: path.join(extensionPath, 'popup/styles/default'),
|
|
expand: true,
|
|
},
|
|
{
|
|
cwd: './dist/src/debug-tools',
|
|
src: '*.css',
|
|
dest: path.join(extensionPath, 'debug-tools'),
|
|
expand: true,
|
|
},
|
|
],
|
|
},
|
|
'package-report': {
|
|
files: [
|
|
{
|
|
cwd: '.',
|
|
src: path.join(packageReportBundlePath, 'report.bundle.js'),
|
|
dest: path.join(packageReportDropPath, 'index.js'),
|
|
},
|
|
{
|
|
cwd: '.',
|
|
src: './src/reports/package/accessibilityInsightsReport.d.ts',
|
|
dest: path.join(packageReportDropPath, 'index.d.ts'),
|
|
},
|
|
],
|
|
},
|
|
'package-ui': {
|
|
files: [
|
|
{
|
|
cwd: '.',
|
|
src: path.join(packageUIBundlePath, 'ui.bundle.js'),
|
|
dest: path.join(packageUIDropPath, 'index.js'),
|
|
},
|
|
{
|
|
cwd: '.',
|
|
src: path.join(packageUIBundlePath, 'ui.css'),
|
|
dest: path.join(packageUIDropPath, 'ui.css'),
|
|
},
|
|
],
|
|
},
|
|
'package-validator': {
|
|
files: [
|
|
{
|
|
cwd: '.',
|
|
src: path.join(packageValidatorBundlePath, 'validator.bundle.js'),
|
|
dest: path.join(packageValidatorDropPath, 'index.js'),
|
|
},
|
|
],
|
|
},
|
|
'package-axe-config': {
|
|
files: [
|
|
{
|
|
cwd: '.',
|
|
src: path.join(packageAxeConfigPath, 'index.js'),
|
|
dest: path.join(packageAxeConfigDropPath, 'index.js'),
|
|
},
|
|
],
|
|
},
|
|
},
|
|
exec: {
|
|
'esbuild-dev': `node esbuild.js`,
|
|
'esbuild-prod': `node esbuild.js --env prod`,
|
|
'esbuild-package-report': `node esbuild.js --env report`,
|
|
'webpack-package-ui': `"${webpackPath}" --config-name package-ui`,
|
|
'esbuild-package-validator': `node esbuild.js --env validator`,
|
|
'generate-validator': `node ${packageValidatorDropPath}`,
|
|
'esbuild-package-axe-config': `node esbuild.js --env axe-config`,
|
|
'generate-axe-config': `node ${path.join(
|
|
packageAxeConfigBundlePath,
|
|
'axe-config-generator.bundle.js',
|
|
)} ${path.join(packageAxeConfigDropPath, 'axe-config.json')}`,
|
|
'generate-scss-typings': `"${typedScssModulesPath}" src --exportType default`,
|
|
},
|
|
sass: {
|
|
options: {
|
|
implementation: sass,
|
|
outputStyle: 'expanded',
|
|
},
|
|
dist: {
|
|
files: [
|
|
{
|
|
src: 'src/**/*.scss',
|
|
dest: 'dist',
|
|
expand: true,
|
|
ext: '.css',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
'embed-styles': {
|
|
'package-report': {
|
|
cwd: packageReportBundlePath,
|
|
src: '**/*bundle.js',
|
|
dest: packageReportBundlePath,
|
|
expand: true,
|
|
cssPath: path.resolve('extension', 'prodBundle'),
|
|
},
|
|
},
|
|
watch: {
|
|
images: {
|
|
files: ['src/**/*.{png,ico,icns}'],
|
|
tasks: ['copy:images', 'drop:dev'],
|
|
},
|
|
'non-webpack-code': {
|
|
files: ['src/**/*.html', 'src/manifest.json'],
|
|
tasks: ['copy:code', 'drop:dev'],
|
|
},
|
|
scss: {
|
|
files: ['src/**/*.scss'],
|
|
tasks: ['sass', 'copy:styles', 'drop:dev'],
|
|
},
|
|
// We assume esbuild --watch is running separately (usually via 'yarn watch')
|
|
'esbuild-dev-output': {
|
|
files: ['extension/devBundle/**/*.*'],
|
|
tasks: ['drop:dev'],
|
|
},
|
|
},
|
|
});
|
|
|
|
const targetNames = Object.keys(targets);
|
|
const releaseTargets = Object.keys(targets).filter(t => targets[t].release);
|
|
const extensionReleaseTargets = releaseTargets.filter(
|
|
t => targets[t].config.options.productCategory === 'extension',
|
|
);
|
|
|
|
targetNames.forEach(targetName => {
|
|
const { config, bundleFolder, telemetryKeyIdentifier } = targets[targetName];
|
|
|
|
const { productCategory } = config.options;
|
|
|
|
const dropPath = path.join(`drop/${productCategory}`, targetName);
|
|
const dropExtensionPath = path.join(dropPath, 'product');
|
|
|
|
const productCategorySpecificCopyFiles = [];
|
|
productCategorySpecificCopyFiles.push({
|
|
src: 'LICENSE',
|
|
dest: `${dropExtensionPath}/LICENSE`,
|
|
});
|
|
|
|
grunt.config.merge({
|
|
drop: {
|
|
[targetName]: {
|
|
// empty on purpose
|
|
},
|
|
},
|
|
configure: {
|
|
[targetName]: {
|
|
configJSPath: path.join(dropExtensionPath, 'insights.config.js'),
|
|
configJSONPath: path.join(dropExtensionPath, 'insights.config.json'),
|
|
config,
|
|
telemetryKeyIdentifier,
|
|
},
|
|
},
|
|
manifest: {
|
|
[targetName]: {
|
|
manifestSrc: path.join('src', 'manifest.json'),
|
|
manifestDest: path.join(dropExtensionPath, 'manifest.json'),
|
|
config,
|
|
},
|
|
},
|
|
clean: {
|
|
[targetName]: dropPath,
|
|
},
|
|
'embed-styles': {
|
|
[targetName]: {
|
|
cwd: path.resolve(extensionPath, bundleFolder),
|
|
src: ['**/*bundle.js', '**/*bundle.js.map'],
|
|
dest: path.resolve(extensionPath, bundleFolder),
|
|
cssPath: path.resolve(extensionPath, bundleFolder),
|
|
expand: true,
|
|
},
|
|
},
|
|
copy: {
|
|
[targetName]: {
|
|
files: [
|
|
{
|
|
cwd: path.resolve(extensionPath, bundleFolder),
|
|
src: ['*.js', '*.js.map', '*.css', '*.css.map'],
|
|
dest: path.resolve(dropExtensionPath, 'bundle'),
|
|
expand: true,
|
|
},
|
|
{
|
|
cwd: extensionPath,
|
|
src: ['**/*.{png,icns,ico,css,woff}'],
|
|
dest: dropExtensionPath,
|
|
expand: true,
|
|
},
|
|
{
|
|
cwd: 'deploy',
|
|
src: ['Gruntfile.js', 'package.json', 'yarn.lock'],
|
|
dest: dropPath,
|
|
expand: true,
|
|
},
|
|
{
|
|
cwd: extensionPath,
|
|
src: ['**/*.html'],
|
|
dest: dropExtensionPath,
|
|
expand: true,
|
|
},
|
|
...productCategorySpecificCopyFiles,
|
|
],
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
grunt.loadNpmTasks('grunt-bom-removal');
|
|
grunt.loadNpmTasks('grunt-concurrent');
|
|
grunt.loadNpmTasks('grunt-contrib-clean');
|
|
grunt.loadNpmTasks('grunt-contrib-copy');
|
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
|
grunt.loadNpmTasks('grunt-exec');
|
|
grunt.loadNpmTasks('grunt-sass');
|
|
|
|
grunt.registerMultiTask('embed-styles', function () {
|
|
const { cssPath } = this.data;
|
|
this.files.forEach(file => {
|
|
const {
|
|
src: [src],
|
|
dest,
|
|
} = file;
|
|
grunt.log.writeln(`embedding style in ${src}`);
|
|
const fileOptions = { options: { encoding: 'utf8' } };
|
|
const input = grunt.file.read(src, fileOptions);
|
|
// eslint-disable-next-line no-useless-escape
|
|
const rex = /\<\<CSS:([a-zA-Z\-\.\/]+)\>\>/g;
|
|
const output = input.replace(rex, (_, cssName) => {
|
|
const cssFile = path.resolve(cssPath, cssName);
|
|
grunt.log.writeln(` embedding from ${cssFile}`);
|
|
const styles = grunt.file.read(cssFile, fileOptions);
|
|
return styles.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
});
|
|
grunt.file.write(dest, output, fileOptions);
|
|
grunt.log.writeln(` written to ${dest}`);
|
|
});
|
|
});
|
|
|
|
grunt.registerMultiTask('configure', function () {
|
|
const { config, configJSONPath, configJSPath, telemetryKeyIdentifier } = this.data;
|
|
// We pass this as an option from a build variable not because it is a secret
|
|
// (it can be found easily enough from released builds), but to make it harder
|
|
// to accidentally pollute release telemetry with data from local builds.
|
|
if (telemetryKeyIdentifier && grunt.option(telemetryKeyIdentifier)) {
|
|
config.options.appInsightsInstrumentationKey = grunt.option(telemetryKeyIdentifier);
|
|
}
|
|
|
|
const configJSON = JSON.stringify(config, undefined, 4);
|
|
grunt.file.write(configJSONPath, configJSON);
|
|
const copyrightHeader =
|
|
'// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n';
|
|
const configJS = `${copyrightHeader}globalThis.insights = ${configJSON};`;
|
|
grunt.file.write(configJSPath, configJS);
|
|
});
|
|
|
|
grunt.registerMultiTask('manifest', function () {
|
|
const { config, manifestSrc, manifestDest } = this.data;
|
|
const manifestJSON = grunt.file.readJSON(manifestSrc);
|
|
|
|
merge(manifestJSON, {
|
|
name: config.options.fullName,
|
|
description: config.options.extensionDescription,
|
|
icons: {
|
|
16: config.options.icon16,
|
|
48: config.options.icon48,
|
|
128: config.options.icon128,
|
|
},
|
|
action: {
|
|
default_icon: {
|
|
20: config.options.icon16,
|
|
40: config.options.icon48,
|
|
},
|
|
},
|
|
});
|
|
|
|
grunt.file.write(manifestDest, JSON.stringify(manifestJSON, undefined, 2));
|
|
});
|
|
|
|
grunt.registerMultiTask('drop', function () {
|
|
const targetName = this.target;
|
|
const { bundleFolder, mustExistFile, config } = targets[targetName];
|
|
|
|
const { productCategory } = config.options;
|
|
|
|
const dropPath = path.join(`drop/${productCategory}`, targetName);
|
|
const dropExtensionPath = path.join(dropPath, 'product');
|
|
|
|
const mustExistPath = path.join(extensionPath, bundleFolder, mustExistFile);
|
|
|
|
mustExist(mustExistPath, 'Have you run the appropriate compiler (esbuild/webpack)?');
|
|
|
|
grunt.task.run('embed-styles:' + targetName);
|
|
grunt.task.run('clean:' + targetName);
|
|
grunt.task.run('copy:' + targetName);
|
|
grunt.task.run('configure:' + targetName);
|
|
grunt.task.run('manifest:' + targetName);
|
|
console.log(`${targetName} extension is in ${dropExtensionPath}`);
|
|
});
|
|
|
|
grunt.registerTask('package-report', function () {
|
|
const mustExistPath = path.join(packageReportBundlePath, 'report.bundle.js');
|
|
|
|
mustExist(mustExistPath, 'Have you run esbuild?');
|
|
|
|
grunt.task.run('embed-styles:package-report');
|
|
grunt.task.run('clean:package-report');
|
|
grunt.task.run('copy:package-report');
|
|
console.log(`package is in ${packageReportDropPath}`);
|
|
});
|
|
|
|
grunt.registerTask('package-ui', function () {
|
|
const mustExistPath = path.join(packageUIBundlePath, 'ui.bundle.js');
|
|
|
|
mustExist(mustExistPath, 'Have you run webpack?');
|
|
|
|
grunt.task.run('clean:package-ui');
|
|
grunt.task.run('copy:package-ui');
|
|
console.log(`package is in ${packageUIDropPath}`);
|
|
});
|
|
|
|
grunt.registerTask('package-validator', function () {
|
|
const mustExistPath = path.join(packageValidatorBundlePath, 'validator.bundle.js');
|
|
|
|
mustExist(mustExistPath, 'Have you run esbuild?');
|
|
|
|
grunt.task.run('clean:package-validator');
|
|
grunt.task.run('copy:package-validator');
|
|
console.log(`package is in ${packageValidatorDropPath}`);
|
|
});
|
|
|
|
grunt.registerTask('package-axe-config', function () {
|
|
const mustExistPath = path.join(
|
|
packageAxeConfigBundlePath,
|
|
'axe-config-generator.bundle.js',
|
|
);
|
|
|
|
mustExist(mustExistPath, 'Have you run esbuild?');
|
|
|
|
grunt.task.run('clean:package-axe-config');
|
|
grunt.task.run('copy:package-axe-config');
|
|
console.log(`package is in ${packageAxeConfigDropPath}`);
|
|
});
|
|
|
|
grunt.registerTask('extension-release-drops', function () {
|
|
extensionReleaseTargets.forEach(targetName => {
|
|
grunt.task.run('drop:' + targetName);
|
|
});
|
|
});
|
|
|
|
grunt.registerTask('ada-cat', function () {
|
|
if (process.env.SHOW_ADA !== 'false') {
|
|
console.log(
|
|
'Image of Ada sleeping follows. Set environment variable SHOW_ADA to false to hide.',
|
|
);
|
|
const adaFile = 'docs/art/ada-cat.ansi256.txt';
|
|
const adaArt = grunt.file.read(adaFile);
|
|
console.log(adaArt);
|
|
}
|
|
});
|
|
|
|
grunt.registerTask('build-assets', ['sass', 'copy:code', 'copy:styles', 'copy:images']);
|
|
|
|
// Main entry points for npm scripts:
|
|
grunt.registerTask('build-dev', [
|
|
'clean:intermediates',
|
|
'exec:generate-scss-typings',
|
|
'build-package-validator',
|
|
'exec:generate-validator',
|
|
'exec:esbuild-dev',
|
|
'build-assets',
|
|
'drop:dev',
|
|
]);
|
|
grunt.registerTask('build-prod', [
|
|
'clean:intermediates',
|
|
'exec:generate-scss-typings',
|
|
'build-package-validator',
|
|
'exec:generate-validator',
|
|
'exec:esbuild-prod',
|
|
'build-assets',
|
|
'drop:production',
|
|
]);
|
|
grunt.registerTask('build-package-report', [
|
|
'clean:intermediates',
|
|
'exec:generate-scss-typings',
|
|
'build-package-validator',
|
|
'exec:generate-validator',
|
|
'exec:esbuild-prod', // required to get the css assets
|
|
'exec:esbuild-package-report',
|
|
'build-assets',
|
|
'package-report',
|
|
]);
|
|
grunt.registerTask('build-package-ui', [
|
|
'clean:intermediates',
|
|
'exec:generate-scss-typings',
|
|
'exec:webpack-package-ui',
|
|
'build-assets',
|
|
'package-ui',
|
|
]);
|
|
grunt.registerTask('build-package-validator', [
|
|
'exec:esbuild-package-validator',
|
|
'package-validator',
|
|
]);
|
|
grunt.registerTask('generate-axe-config', [
|
|
'exec:esbuild-package-axe-config',
|
|
'package-axe-config',
|
|
'exec:generate-axe-config',
|
|
]);
|
|
grunt.registerTask('build-all', [
|
|
'clean:intermediates',
|
|
'exec:generate-scss-typings',
|
|
'build-package-validator',
|
|
'exec:generate-validator',
|
|
'concurrent:compile-all',
|
|
'build-assets',
|
|
'drop:dev',
|
|
'extension-release-drops',
|
|
]);
|
|
|
|
grunt.registerTask('default', ['build-dev']);
|
|
};
|