Removed remaining use of Grunt

This commit is contained in:
Steve Gravrock
2025-04-09 07:46:43 -07:00
parent 89f3e9449d
commit a09fdd3284
14 changed files with 284 additions and 368 deletions

View File

@@ -61,7 +61,7 @@ jobs:
at: .
- run:
name: Run tests in parallel
command: npx grunt execSpecsInParallel
command: npm run test:parallel
test_browsers: &test_browsers
executor: node18

View File

@@ -1,248 +0,0 @@
const fs = require('fs');
const glob = require('glob');
const ejs = require('ejs');
const sass = require('sass');
module.exports = function(grunt) {
var pkg = require("./package.json");
global.jasmineVersion = pkg.version;
grunt.initConfig({
pkg: pkg,
cssUrlEmbed: require('./grunt/config/cssUrlEmbed.js')
});
require('load-grunt-tasks')(grunt);
grunt.registerMultiTask('cssUrlEmbed', "Embed URLs as base64 strings inside your stylesheets", function () {
var done = this.async();
import('css-url-embed').then(cssEmbed => {
for (const file of this.files) {
try {
grunt.log.subhead('Processing source file "' + file.src[0] + '"');
const urls = cssEmbed.processFile(file.src[0], file.dest);
for (const url of urls) {
grunt.log.ok('"' + url + '" embedded');
}
grunt.log.writeln('File "' + file.dest + '" created');
} catch (e) {
grunt.log.error(e);
grunt.fail.warn('URL embedding failed\n');
}
}
done();
});
});
grunt.loadTasks('grunt/tasks');
grunt.registerTask('sass',
'Compile sass to css',
function() {
try {
const output = sass.compile('src/html/jasmine.scss');
fs.writeFileSync('lib/jasmine-core/jasmine.css', output.css,
{encoding: 'utf8'});
} catch (e) {
console.error(e);
throw e;
}
}
);
grunt.registerTask('default', ['sass', "cssUrlEmbed"]);
grunt.registerTask('concat',
'Concatenate files',
function() {
try {
const configs = [
{
src: [
'src/html/requireHtml.js',
'src/html/HtmlReporter.js',
'src/html/HtmlSpecFilter.js',
'src/html/ResultsNode.js',
'src/html/QueryString.js',
'src/html/**/*.js'
],
dest: 'lib/jasmine-core/jasmine-html.js',
},
{
dest: 'lib/jasmine-core/jasmine.js',
src: [
'src/core/requireCore.js',
'src/core/matchers/requireMatchers.js',
'src/core/base.js',
'src/core/util.js',
'src/core/Spec.js',
'src/core/Order.js',
'src/core/Env.js',
'src/core/JsApiReporter.js',
'src/core/PrettyPrinter',
'src/core/Suite',
'src/core/**/*.js',
{
template: 'src/version.js',
data: {version: jasmineVersion}
},
],
},
{
dest: 'lib/jasmine-core/boot0.js',
src: ['src/boot/boot0.js'],
},
{
dest: 'lib/jasmine-core/boot1.js',
src: ['src/boot/boot1.js'],
}
];
const licenseBanner = {
template: 'grunt/templates/licenseBanner.js.ejs',
data: {currentYear: new Date(Date.now()).getFullYear()}
};
for (const {src, dest} of configs) {
src.unshift(licenseBanner);
function expand(srcListEntry) {
if (typeof srcListEntry === 'object') {
return srcListEntry;
}
return glob.sync(srcListEntry)
.sort(function (a, b) {
// Match the sort order of previous build tools, so that the
// output is the same.
a = a.toLowerCase();
b = b.toLowerCase();
if (a < b) {
return -1;
} else if (a === b) {
return 0;
} else {
return 1;
}
});
}
const srcs = src.flatMap(expand);
const seen = new Set();
const chunks = [];
for (const s of srcs) {
let content;
if (!seen.has(s)) {
if (s.template) {
const template = fs.readFileSync(s.template, {encoding: 'utf8'});
content = ejs.render(template, s.data);
} else {
content = fs.readFileSync(s, {encoding: 'utf8'});
}
chunks.push(content);
seen.add(s);
}
}
fs.writeFileSync(dest, chunks.join('\n'), {encoding: 'utf8'});
}
} catch (e) {
console.error(e);
throw e;
}
}
);
grunt.registerTask('buildDistribution',
'Builds and lints jasmine.js, jasmine-html.js, jasmine.css',
[
'sass',
"cssUrlEmbed",
'concat'
]
);
grunt.registerTask("execSpecsInNode",
"Run Jasmine core specs in Node.js",
function() {
verifyNoGlobals(() => require('./lib/jasmine-core.js').noGlobals());
const done = this.async(),
Jasmine = require('jasmine'),
jasmineCore = require('./lib/jasmine-core.js'),
jasmine = new Jasmine({jasmineCore: jasmineCore});
jasmine.loadConfigFile('./spec/support/jasmine.json');
jasmine.exitOnCompletion = false;
jasmine.execute().then(
result => done(result.overallStatus === 'passed'),
err => {
console.error(err);
done(false);
}
);
}
);
grunt.registerTask("execSpecsInParallel",
"Run Jasmine core specs in parallel in Node.js",
function() {
// Need to require this here rather than at the top of the file
// so that we don't break verifyNoGlobals above by loading jasmine-core
// too early
const ParallelRunner = require('jasmine/parallel');
let numWorkers = require('os').cpus().length;
if (process.env['CIRCLECI']) {
// On Circle CI, the above gives the number of CPU cores on the host
// computer, which is unrelated to the resources actually available
// to the container. 2 workers gives peak performance with our current
// configuration, but 4 might increase the odds of discovering any
// parallel-specific bugs.
numWorkers = 4;
}
const done = this.async();
const runner = new ParallelRunner({
jasmineCore: require('./lib/jasmine-core.js'),
numWorkers
});
runner.loadConfigFile('./spec/support/jasmine.json')
.then(() => {
runner.exitOnCompletion = false;
return runner.execute();
}).then(
jasmineDoneInfo => done(jasmineDoneInfo.overallStatus === 'passed'),
err => {
console.error(err);
done(false);
}
);
}
);
grunt.registerTask("execSpecsInNode:performance",
"Run Jasmine performance specs in Node.js",
function() {
require("shelljs").exec("node_modules/.bin/jasmine JASMINE_CONFIG_PATH=spec/support/jasmine-performance.json");
}
);
};
function verifyNoGlobals(fn) {
const initialGlobals = Object.keys(global);
fn();
const extras = Object.keys(global).filter(k => !initialGlobals.includes(k));
if (extras.length !== 0) {
throw new Error('Globals were unexpectedly created: ' + extras.join(', '));
}
}

View File

@@ -41,7 +41,7 @@ When ready to release - specs are all green and the stories are done:
### Build standalone distribution
1. Build the standalone distribution with `grunt buildStandaloneDist`
1. Build the standalone distribution with `npm run buildStandaloneDist`
1. This will generate `dist/jasmine-standalone-<version>.zip`, which you will upload later (see "Finally" below).
### Release the core NPM module

View File

@@ -1,7 +0,0 @@
module.exports = {
encodeWithBaseDir: {
files: {
"lib/jasmine-core/jasmine.css": ["lib/jasmine-core/jasmine.css"]
}
}
};

View File

@@ -1,105 +0,0 @@
var grunt = require("grunt");
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const glob = require('glob');
const ejs = require('ejs');
function standaloneTmpDir(path) { return "dist/tmp/" + path; }
grunt.registerTask("build:compileSpecRunner",
"Processes the spec runner template and writes to a tmp file",
function() {
const template = fs.readFileSync('grunt/templates/SpecRunner.html.ejs',
{encoding: 'utf8'});
const runnerHtml = ejs.render(template, { jasmineVersion: global.jasmineVersion });
if (!fs.existsSync('dist/tmp')) {
fs.mkdirSync('dist/tmp');
}
fs.writeFileSync('dist/tmp/SpecRunner.html', runnerHtml,
{encoding: 'utf8'});
}
);
grunt.registerTask("build:cleanSpecRunner",
"Deletes the tmp spec runner file",
function() {
grunt.file.delete(standaloneTmpDir(""));
}
);
const standaloneFileGroups = [
{
src: [
'LICENSE',
'dist/tmp/SpecRunner.html',
]
},
{
src: [
'images/jasmine_favicon.png',
'lib/jasmine-core/jasmine.js',
'lib/jasmine-core/jasmine-html.js',
'lib/jasmine-core/jasmine.css',
'lib/jasmine-core/boot0.js',
'lib/jasmine-core/boot1.js',
],
destDir: 'lib/jasmine-' + jasmineVersion
},
{
src: glob.sync('lib/jasmine-core/example/src/*.js'),
destDir: 'src'
},
{
src: glob.sync('lib/jasmine-core/example/spec/*.js'),
destDir: 'spec'
}
];
grunt.registerTask("zipStandaloneDist",
"Creates a zip file for the standalone distribution",
function() {
const done = this.async();
const destPath = `./dist/jasmine-standalone-${global.jasmineVersion}.zip`;
const output = fs.createWriteStream(destPath);
const archive = archiver('zip');
output.on('close', done);
archive.on('warning', function (err) {
grunt.fail.warn(err)
});
archive.on('error', function (err) {
grunt.fail.warn(err)
});
archive.pipe(output);
for (const group of standaloneFileGroups) {
for (const srcPath of group.src) {
let destPath = path.basename(srcPath);
if (group.destDir) {
destPath = `${group.destDir}/${destPath}`;
}
archive.file(srcPath, {name: destPath});
}
}
archive.finalize();
}
);
grunt.registerTask("buildStandaloneDist",
"Builds a standalone distribution",
[
"buildDistribution",
"build:compileSpecRunner",
"zipStandaloneDist",
"build:cleanSpecRunner"
]
);

View File

@@ -15,9 +15,11 @@
],
"scripts": {
"posttest": "eslint \"src/**/*.js\" \"spec/**/*.js\" && prettier --check \"src/**/*.js\" \"spec/**/*.js\"",
"test": "grunt --stack execSpecsInNode",
"test": "node scripts/runSpecsInNode.js",
"test:parallel": "node scripts/runSpecsInParallel.js",
"cleanup": "prettier --write \"src/**/*.js\" \"spec/**/*.js\"",
"build": "grunt buildDistribution",
"build": "node scripts/buildDistribution.js",
"buildStandaloneDist": "node scripts/buildStandaloneDist.js",
"serve": "node spec/support/localJasmineBrowser.js",
"serve:performance": "node spec/support/localJasmineBrowser.js jasmine-browser-performance.json",
"ci": "node spec/support/ci.js",
@@ -43,12 +45,9 @@
"eslint-plugin-compat": "^6.0.2",
"glob": "^10.2.3",
"globals": "^16.0.0",
"grunt": "^1.0.4",
"grunt-cli": "^1.3.2",
"jasmine": "^5.0.0",
"jasmine-browser-runner": "github:jasmine/jasmine-browser-runner",
"jsdom": "^26.0.0",
"load-grunt-tasks": "^5.1.0",
"prettier": "1.17.1",
"rimraf": "^5.0.10",
"sass": "^1.58.3",

View File

@@ -0,0 +1,3 @@
const buildDistribution = require('./lib/buildDistribution');
buildDistribution();

View File

@@ -0,0 +1,89 @@
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const ejs = require('ejs');
const archiver = require('archiver');
const { rimrafSync } = require('rimraf');
const buildDistribution = require('./lib/buildDistribution');
const tmpDir = 'dist/tmp'
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir);
}
buildStandaloneDist().finally(function() {
rimrafSync(tmpDir);
});
async function buildStandaloneDist() {
buildDistribution();
const pkg = JSON.parse(fs.readFileSync('package.json'));
compileSpecRunner(pkg.version);
await zipStandaloneDist(pkg.version);
}
function compileSpecRunner(jasmineVersion) {
const template = fs.readFileSync('src/SpecRunner.html.ejs',
{encoding: 'utf8'});
const runnerHtml = ejs.render(template, { jasmineVersion });
fs.writeFileSync('dist/tmp/SpecRunner.html', runnerHtml,
{encoding: 'utf8'});
}
async function zipStandaloneDist(jasmineVersion) {
const fileGroups = [
{
src: [
'LICENSE',
'dist/tmp/SpecRunner.html',
]
},
{
src: [
'images/jasmine_favicon.png',
'lib/jasmine-core/jasmine.js',
'lib/jasmine-core/jasmine-html.js',
'lib/jasmine-core/jasmine.css',
'lib/jasmine-core/boot0.js',
'lib/jasmine-core/boot1.js',
],
destDir: 'lib/jasmine-' + jasmineVersion
},
{
src: glob.sync('lib/jasmine-core/example/src/*.js'),
destDir: 'src'
},
{
src: glob.sync('lib/jasmine-core/example/spec/*.js'),
destDir: 'spec'
}
];
const destPath = `./dist/jasmine-standalone-${jasmineVersion}.zip`;
const output = fs.createWriteStream(destPath);
const archive = archiver('zip');
const done = new Promise(function(resolve, reject) {
output.on('close', resolve);
archive.on('warning', reject);
archive.on('error', reject);
});
archive.pipe(output);
for (const group of fileGroups) {
for (const srcPath of group.src) {
let destPath = path.basename(srcPath);
if (group.destDir) {
destPath = `${group.destDir}/${destPath}`;
}
archive.file(srcPath, {name: destPath});
}
}
archive.finalize();
await done;
}

View File

@@ -0,0 +1,129 @@
const fs = require('fs');
const sass = require('sass');
const glob = require('glob');
const ejs = require('ejs');
const cssUrlEmbed = require('css-url-embed');
function buildDistribution() {
compileSass();
embedCssAssets();
concatFiles();
}
function embedCssAssets() {
const cssPath = 'lib/jasmine-core/jasmine.css';
cssUrlEmbed.processFile(cssPath, cssPath, function(filePath) {
if (filePath.endsWith('.png')) {
return 'image/png';
} else if (filePath.endsWith('.svg')) {
return 'image/svg+xml';
} else {
throw new Error(`Don't know MIME type for file: ${filePath}`);
}
});
}
function compileSass() {
const output = sass.compile('src/html/jasmine.scss');
fs.writeFileSync('lib/jasmine-core/jasmine.css', output.css,
{encoding: 'utf8'});
}
function concatFiles() {
const pkg = JSON.parse(fs.readFileSync('package.json'));
const configs = [
{
src: [
'src/html/requireHtml.js',
'src/html/HtmlReporter.js',
'src/html/HtmlSpecFilter.js',
'src/html/ResultsNode.js',
'src/html/QueryString.js',
'src/html/**/*.js'
],
dest: 'lib/jasmine-core/jasmine-html.js',
},
{
dest: 'lib/jasmine-core/jasmine.js',
src: [
'src/core/requireCore.js',
'src/core/matchers/requireMatchers.js',
'src/core/base.js',
'src/core/util.js',
'src/core/Spec.js',
'src/core/Order.js',
'src/core/Env.js',
'src/core/JsApiReporter.js',
'src/core/PrettyPrinter',
'src/core/Suite',
'src/core/**/*.js',
{
template: 'src/version.js',
data: {version: pkg.version}
},
],
},
{
dest: 'lib/jasmine-core/boot0.js',
src: ['src/boot/boot0.js'],
},
{
dest: 'lib/jasmine-core/boot1.js',
src: ['src/boot/boot1.js'],
}
];
const licenseBanner = {
template: 'src/licenseBanner.js.ejs',
data: {currentYear: new Date(Date.now()).getFullYear()}
};
for (const {src, dest} of configs) {
src.unshift(licenseBanner);
function expand(srcListEntry) {
if (typeof srcListEntry === 'object') {
return srcListEntry;
}
return glob.sync(srcListEntry)
.sort(function (a, b) {
// Match the sort order of previous build tools, so that the
// output is the same.
a = a.toLowerCase();
b = b.toLowerCase();
if (a < b) {
return -1;
} else if (a === b) {
return 0;
} else {
return 1;
}
});
}
const srcs = src.flatMap(expand);
const seen = new Set();
const chunks = [];
for (const s of srcs) {
let content;
if (!seen.has(s)) {
if (s.template) {
const template = fs.readFileSync(s.template, {encoding: 'utf8'});
content = ejs.render(template, s.data);
} else {
content = fs.readFileSync(s, {encoding: 'utf8'});
}
chunks.push(content);
seen.add(s);
}
}
fs.writeFileSync(dest, chunks.join('\n'), {encoding: 'utf8'});
}
}
module.exports = buildDistribution;

28
scripts/runSpecsInNode.js Normal file
View File

@@ -0,0 +1,28 @@
verifyNoGlobals(() => require('../lib/jasmine-core.js').noGlobals());
const Jasmine = require('jasmine');
const jasmineCore = require('../lib/jasmine-core.js');
const runner = new Jasmine({jasmineCore: jasmineCore});
runner.loadConfigFile('./spec/support/jasmine.json');
runner.exitOnCompletion = false;
runner.execute()
.then(
result => result.overallStatus === 'passed',
err => {
console.error(err);
return false;
}
)
.then(ok => process.exit(ok ? 0 : 1));
function verifyNoGlobals(fn) {
const initialGlobals = Object.keys(global);
fn();
const extras = Object.keys(global).filter(k => !initialGlobals.includes(k));
if (extras.length !== 0) {
throw new Error('Globals were unexpectedly created: ' + extras.join(', '));
}
}

View File

@@ -0,0 +1,28 @@
const ParallelRunner = require('jasmine/parallel');
const jasmineCore = require('../lib/jasmine-core.js');
let numWorkers = require('os').cpus().length;
if (process.env['CIRCLECI']) {
// On Circle CI, the above gives the number of CPU cores on the host
// computer, which is unrelated to the resources actually available
// to the container. 2 workers gives peak performance with our current
// configuration, but 4 might increase the odds of discovering any
// parallel-specific bugs.
numWorkers = 4;
}
const runner = new ParallelRunner({jasmineCore, numWorkers});
runner.loadConfigFile('./spec/support/jasmine.json')
.then(() => {
runner.exitOnCompletion = false;
return runner.execute();
})
.then(
jasmineDoneInfo => jasmineDoneInfo.overallStatus === 'passed',
err => {
console.error(err);
return false;
}
)
.then(ok => process.exit(ok ? 0 : 1));