From d4f29491c99f30b62aa4f71a4554a586209e9774 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 8 Apr 2025 07:46:50 -0700 Subject: [PATCH 1/9] Removed old eslint config file --- .eslintrc | 47 ----------------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index a07ef6fa..00000000 --- a/.eslintrc +++ /dev/null @@ -1,47 +0,0 @@ -{ - "extends": [ - "plugin:compat/recommended" - ], - "env": { - "browser": true, - "node": true, - "es2017": true - }, - "parserOptions": { - "ecmaVersion": 2018 - }, - "rules": { - "curly": "error", - "quotes": [ - "error", - "single", - { - "avoidEscape": true - } - ], - "no-unused-vars": [ - "error", - { - "args": "none" - } - ], - "no-implicit-globals": "error", - "block-spacing": "error", - "func-call-spacing": [ - "error", - "never" - ], - "key-spacing": "error", - "no-tabs": "error", - "no-trailing-spaces": "error", - "no-whitespace-before-property": "error", - "semi": [ - "error", - "always" - ], - "space-before-blocks": "error", - "no-eval": "error", - "no-var": "error", - "no-debugger": "error" - } -} From bcf699f1459324be3ce0b9aa7874de6bc8497ed9 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 8 Apr 2025 18:00:55 -0700 Subject: [PATCH 2/9] Switch from grunt-css-url-embed to css-url-embed This eliminates some rickety indirect dependencies and is a big step towards removing Grunt. --- Gruntfile.js | 24 ++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 33a7a2da..ec3820b2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -12,6 +12,30 @@ module.exports = function(grunt) { 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('default', ['sass:dist', "cssUrlEmbed"]); diff --git a/package.json b/package.json index f961e464..a238104a 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "devDependencies": { "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.24.0", + "css-url-embed": "github:sgravrock/css-url-embed", "eslint": "^9.24.0", "eslint-plugin-compat": "^6.0.2", "glob": "^10.2.3", @@ -44,7 +45,6 @@ "grunt-cli": "^1.3.2", "grunt-contrib-compress": "^2.0.0", "grunt-contrib-concat": "^2.0.0", - "grunt-css-url-embed": "^1.11.1", "grunt-sass": "^4.0.0", "jasmine": "^5.0.0", "jasmine-browser-runner": "github:jasmine/jasmine-browser-runner", From 4bd2feda7d0db0d8696c0733a5afb7d93fad1948 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 8 Apr 2025 18:47:53 -0700 Subject: [PATCH 3/9] Use archiver directly rather than grunt-contrib-compress Removes two old copies of glob and is another step towards removing Grunt. --- Gruntfile.js | 1 - grunt/config/compress.js | 57 --------------------------- grunt/tasks/build_standalone.js | 70 ++++++++++++++++++++++++++++++++- package.json | 2 +- 4 files changed, 70 insertions(+), 60 deletions(-) delete mode 100644 grunt/config/compress.js diff --git a/Gruntfile.js b/Gruntfile.js index ec3820b2..584f81af 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -6,7 +6,6 @@ module.exports = function(grunt) { pkg: pkg, concat: require('./grunt/config/concat.js'), sass: require('./grunt/config/sass.js'), - compress: require('./grunt/config/compress.js'), cssUrlEmbed: require('./grunt/config/cssUrlEmbed.js') }); diff --git a/grunt/config/compress.js b/grunt/config/compress.js deleted file mode 100644 index a732e147..00000000 --- a/grunt/config/compress.js +++ /dev/null @@ -1,57 +0,0 @@ -var standaloneLibDir = "lib/jasmine-" + jasmineVersion; - -function root(path) { return "./" + path; } -function libJasmineCore(path) { return root("lib/jasmine-core/" + path); } -function dist(path) { return root("dist/" + path); } - -module.exports = { - standalone: { - options: { - archive: root("dist/jasmine-standalone-" + global.jasmineVersion + ".zip") - }, - - files: [ - { src: [ root("LICENSE") ] }, - { - src: [ "jasmine_favicon.png"], - dest: standaloneLibDir, - expand: true, - cwd: root("images") - }, - { - src: [ - "jasmine.js", - "jasmine-html.js", - "jasmine.css" - ], - dest: standaloneLibDir, - expand: true, - cwd: libJasmineCore("") - }, - { - src: [ "boot0.js", "boot1.js" ], - dest: standaloneLibDir, - expand: true, - cwd: libJasmineCore("") - }, - { - src: [ "SpecRunner.html" ], - dest: root(""), - expand: true, - cwd: dist("tmp") - }, - { - src: [ "*.js" ], - dest: "src", - expand: true, - cwd: libJasmineCore("example/src/") - }, - { - src: [ "*.js" ], - dest: "spec", - expand: true, - cwd: libJasmineCore("example/spec/") - } - ] - } -}; diff --git a/grunt/tasks/build_standalone.js b/grunt/tasks/build_standalone.js index 1abccf28..e0bbb075 100644 --- a/grunt/tasks/build_standalone.js +++ b/grunt/tasks/build_standalone.js @@ -1,4 +1,8 @@ var grunt = require("grunt"); +const fs = require('fs'); +const path = require('path'); +const archiver = require('archiver'); +const glob = require('glob'); function standaloneTmpDir(path) { return "dist/tmp/" + path; } @@ -20,12 +24,76 @@ grunt.registerTask("build:cleanSpecRunner", } ); +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", - "compress:standalone", + "zipStandaloneDist", "build:cleanSpecRunner" ] ); diff --git a/package.json b/package.json index a238104a..9dc1afa8 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "devDependencies": { "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.24.0", + "archiver": "^7.0.1", "css-url-embed": "github:sgravrock/css-url-embed", "eslint": "^9.24.0", "eslint-plugin-compat": "^6.0.2", @@ -43,7 +44,6 @@ "globals": "^16.0.0", "grunt": "^1.0.4", "grunt-cli": "^1.3.2", - "grunt-contrib-compress": "^2.0.0", "grunt-contrib-concat": "^2.0.0", "grunt-sass": "^4.0.0", "jasmine": "^5.0.0", From fc935e89c69eda4ac4b04cd7f83b5dd3ec9857d3 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 8 Apr 2025 21:08:45 -0700 Subject: [PATCH 4/9] Removed grunt-contrib-concat --- Gruntfile.js | 109 +++++++++++++++++- grunt/config/concat.js | 56 --------- ...enseBanner.js.jst => licenseBanner.js.ejs} | 0 lib/jasmine-core/boot0.js | 1 + lib/jasmine-core/boot1.js | 1 + lib/jasmine-core/jasmine-html.js | 1 + lib/jasmine-core/jasmine.js | 1 + package.json | 2 +- 8 files changed, 113 insertions(+), 58 deletions(-) delete mode 100644 grunt/config/concat.js rename grunt/templates/{licenseBanner.js.jst => licenseBanner.js.ejs} (100%) diff --git a/Gruntfile.js b/Gruntfile.js index 584f81af..b1de9b01 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,10 +1,13 @@ +const fs = require('fs'); +const glob = require('glob'); +const ejs = require('ejs'); + module.exports = function(grunt) { var pkg = require("./package.json"); global.jasmineVersion = pkg.version; grunt.initConfig({ pkg: pkg, - concat: require('./grunt/config/concat.js'), sass: require('./grunt/config/sass.js'), cssUrlEmbed: require('./grunt/config/cssUrlEmbed.js') }); @@ -39,6 +42,110 @@ module.exports = function(grunt) { grunt.registerTask('default', ['sass:dist', "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', [ diff --git a/grunt/config/concat.js b/grunt/config/concat.js deleted file mode 100644 index a9c6eedf..00000000 --- a/grunt/config/concat.js +++ /dev/null @@ -1,56 +0,0 @@ -var grunt = require('grunt'); - -function license() { - var currentYear = "" + new Date(Date.now()).getFullYear(); - - return grunt.template.process( - grunt.file.read("grunt/templates/licenseBanner.js.jst"), - { data: { currentYear: currentYear}}); -} - -module.exports = { - 'jasmine-html': { - 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' - }, - jasmine: { - 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', - 'src/version.js' - ], - dest: 'lib/jasmine-core/jasmine.js' - }, - boot0: { - src: ['src/boot/boot0.js'], - dest: 'lib/jasmine-core/boot0.js' - }, - boot1: { - src: ['src/boot/boot1.js'], - dest: 'lib/jasmine-core/boot1.js' - }, - options: { - banner: license(), - process: { - data: { - version: global.jasmineVersion - } - } - } -}; diff --git a/grunt/templates/licenseBanner.js.jst b/grunt/templates/licenseBanner.js.ejs similarity index 100% rename from grunt/templates/licenseBanner.js.jst rename to grunt/templates/licenseBanner.js.ejs diff --git a/lib/jasmine-core/boot0.js b/lib/jasmine-core/boot0.js index 6f0f3739..5403bdf3 100644 --- a/lib/jasmine-core/boot0.js +++ b/lib/jasmine-core/boot0.js @@ -21,6 +21,7 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + /** This file starts the process of "booting" Jasmine. It initializes Jasmine, makes its globals available, and creates the env. This file should be loaded diff --git a/lib/jasmine-core/boot1.js b/lib/jasmine-core/boot1.js index 6368eceb..8c12f8c7 100644 --- a/lib/jasmine-core/boot1.js +++ b/lib/jasmine-core/boot1.js @@ -21,6 +21,7 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + /** This file finishes 'booting' Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 060604d5..102eaee0 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -21,6 +21,7 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + // eslint-disable-next-line no-var var jasmineRequire = window.jasmineRequire || require('./jasmine.js'); diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index c3ba71f5..c6665c2f 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -21,6 +21,7 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + // eslint-disable-next-line no-unused-vars,no-var var getJasmineRequireObj = (function(jasmineGlobal) { let jasmineRequire; diff --git a/package.json b/package.json index 9dc1afa8..b2f0ef41 100644 --- a/package.json +++ b/package.json @@ -38,13 +38,13 @@ "@eslint/js": "^9.24.0", "archiver": "^7.0.1", "css-url-embed": "github:sgravrock/css-url-embed", + "ejs": "^3.1.10", "eslint": "^9.24.0", "eslint-plugin-compat": "^6.0.2", "glob": "^10.2.3", "globals": "^16.0.0", "grunt": "^1.0.4", "grunt-cli": "^1.3.2", - "grunt-contrib-concat": "^2.0.0", "grunt-sass": "^4.0.0", "jasmine": "^5.0.0", "jasmine-browser-runner": "github:jasmine/jasmine-browser-runner", From ba033c520d01314d31394e452beef1aa5e750135 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 8 Apr 2025 21:19:03 -0700 Subject: [PATCH 5/9] Use sass directly rather than grunt-sass --- Gruntfile.js | 20 +++++++++++++++++--- grunt/config/sass.js | 13 ------------- package.json | 1 - 3 files changed, 17 insertions(+), 17 deletions(-) delete mode 100644 grunt/config/sass.js diff --git a/Gruntfile.js b/Gruntfile.js index b1de9b01..5554f3ab 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,6 +1,7 @@ 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"); @@ -8,7 +9,6 @@ module.exports = function(grunt) { grunt.initConfig({ pkg: pkg, - sass: require('./grunt/config/sass.js'), cssUrlEmbed: require('./grunt/config/cssUrlEmbed.js') }); @@ -40,7 +40,21 @@ module.exports = function(grunt) { grunt.loadTasks('grunt/tasks'); - grunt.registerTask('default', ['sass:dist', "cssUrlEmbed"]); + 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', @@ -149,7 +163,7 @@ module.exports = function(grunt) { grunt.registerTask('buildDistribution', 'Builds and lints jasmine.js, jasmine-html.js, jasmine.css', [ - 'sass:dist', + 'sass', "cssUrlEmbed", 'concat' ] diff --git a/grunt/config/sass.js b/grunt/config/sass.js deleted file mode 100644 index 3ce0ef61..00000000 --- a/grunt/config/sass.js +++ /dev/null @@ -1,13 +0,0 @@ -const sass = require('sass'); - -module.exports = { - options: { - implementation: sass, - sourceComments: false - }, - dist: { - files: { - "lib/jasmine-core/jasmine.css": "src/html/jasmine.scss" - } - } -}; diff --git a/package.json b/package.json index b2f0ef41..72bb4db5 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "globals": "^16.0.0", "grunt": "^1.0.4", "grunt-cli": "^1.3.2", - "grunt-sass": "^4.0.0", "jasmine": "^5.0.0", "jasmine-browser-runner": "github:jasmine/jasmine-browser-runner", "jsdom": "^26.0.0", From 89f3e9449d17143cf095c28302e77c243ce33eb7 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 8 Apr 2025 21:53:05 -0700 Subject: [PATCH 6/9] Use ejs to build SpecRunner.html --- grunt/tasks/build_standalone.js | 14 ++++++++++---- .../{SpecRunner.html.jst => SpecRunner.html.ejs} | 0 2 files changed, 10 insertions(+), 4 deletions(-) rename grunt/templates/{SpecRunner.html.jst => SpecRunner.html.ejs} (100%) diff --git a/grunt/tasks/build_standalone.js b/grunt/tasks/build_standalone.js index e0bbb075..cb23618d 100644 --- a/grunt/tasks/build_standalone.js +++ b/grunt/tasks/build_standalone.js @@ -3,17 +3,23 @@ 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() { - var runnerHtml = grunt.template.process( - grunt.file.read("grunt/templates/SpecRunner.html.jst"), - { data: { jasmineVersion: global.jasmineVersion }}); + const template = fs.readFileSync('grunt/templates/SpecRunner.html.ejs', + {encoding: 'utf8'}); + const runnerHtml = ejs.render(template, { jasmineVersion: global.jasmineVersion }); - grunt.file.write(standaloneTmpDir("SpecRunner.html"), runnerHtml); + if (!fs.existsSync('dist/tmp')) { + fs.mkdirSync('dist/tmp'); + } + + fs.writeFileSync('dist/tmp/SpecRunner.html', runnerHtml, + {encoding: 'utf8'}); } ); diff --git a/grunt/templates/SpecRunner.html.jst b/grunt/templates/SpecRunner.html.ejs similarity index 100% rename from grunt/templates/SpecRunner.html.jst rename to grunt/templates/SpecRunner.html.ejs From a09fdd328466559efb2a913d45c87924df77659c Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 9 Apr 2025 07:46:43 -0700 Subject: [PATCH 7/9] Removed remaining use of Grunt --- .circleci/config.yml | 2 +- .editorconfig | 2 +- Gruntfile.js | 248 ------------------ RELEASE.md | 2 +- grunt/config/cssUrlEmbed.js | 7 - grunt/tasks/build_standalone.js | 105 -------- package.json | 9 +- scripts/buildDistribution.js | 3 + scripts/buildStandaloneDist.js | 89 +++++++ scripts/lib/buildDistribution.js | 129 +++++++++ scripts/runSpecsInNode.js | 28 ++ scripts/runSpecsInParallel.js | 28 ++ {grunt/templates => src}/SpecRunner.html.ejs | 0 {grunt/templates => src}/licenseBanner.js.ejs | 0 14 files changed, 284 insertions(+), 368 deletions(-) delete mode 100644 Gruntfile.js delete mode 100644 grunt/config/cssUrlEmbed.js delete mode 100644 grunt/tasks/build_standalone.js create mode 100644 scripts/buildDistribution.js create mode 100644 scripts/buildStandaloneDist.js create mode 100644 scripts/lib/buildDistribution.js create mode 100644 scripts/runSpecsInNode.js create mode 100644 scripts/runSpecsInParallel.js rename {grunt/templates => src}/SpecRunner.html.ejs (100%) rename {grunt/templates => src}/licenseBanner.js.ejs (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index a1f9229b..80737742 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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 diff --git a/.editorconfig b/.editorconfig index 0ba33458..9f7c7232 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,6 +3,6 @@ charset = utf-8 end_of_line = lf insert_final_newline = true -[*.{js, mjs, json, sh, yml}] +[*.{js,mjs,json,sh,yml}] indent_style = space indent_size = 2 diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 5554f3ab..00000000 --- a/Gruntfile.js +++ /dev/null @@ -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(', ')); - } -} diff --git a/RELEASE.md b/RELEASE.md index 049b3570..632a75ee 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -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-.zip`, which you will upload later (see "Finally" below). ### Release the core NPM module diff --git a/grunt/config/cssUrlEmbed.js b/grunt/config/cssUrlEmbed.js deleted file mode 100644 index e883ffb3..00000000 --- a/grunt/config/cssUrlEmbed.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - encodeWithBaseDir: { - files: { - "lib/jasmine-core/jasmine.css": ["lib/jasmine-core/jasmine.css"] - } - } -}; diff --git a/grunt/tasks/build_standalone.js b/grunt/tasks/build_standalone.js deleted file mode 100644 index cb23618d..00000000 --- a/grunt/tasks/build_standalone.js +++ /dev/null @@ -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" - ] -); diff --git a/package.json b/package.json index 72bb4db5..93b87683 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/buildDistribution.js b/scripts/buildDistribution.js new file mode 100644 index 00000000..286e2549 --- /dev/null +++ b/scripts/buildDistribution.js @@ -0,0 +1,3 @@ +const buildDistribution = require('./lib/buildDistribution'); + +buildDistribution(); diff --git a/scripts/buildStandaloneDist.js b/scripts/buildStandaloneDist.js new file mode 100644 index 00000000..a1f255f6 --- /dev/null +++ b/scripts/buildStandaloneDist.js @@ -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; +} diff --git a/scripts/lib/buildDistribution.js b/scripts/lib/buildDistribution.js new file mode 100644 index 00000000..f92587ac --- /dev/null +++ b/scripts/lib/buildDistribution.js @@ -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; diff --git a/scripts/runSpecsInNode.js b/scripts/runSpecsInNode.js new file mode 100644 index 00000000..08e772f3 --- /dev/null +++ b/scripts/runSpecsInNode.js @@ -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(', ')); + } +} diff --git a/scripts/runSpecsInParallel.js b/scripts/runSpecsInParallel.js new file mode 100644 index 00000000..9e15939b --- /dev/null +++ b/scripts/runSpecsInParallel.js @@ -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)); diff --git a/grunt/templates/SpecRunner.html.ejs b/src/SpecRunner.html.ejs similarity index 100% rename from grunt/templates/SpecRunner.html.ejs rename to src/SpecRunner.html.ejs diff --git a/grunt/templates/licenseBanner.js.ejs b/src/licenseBanner.js.ejs similarity index 100% rename from grunt/templates/licenseBanner.js.ejs rename to src/licenseBanner.js.ejs From 52aaf63d22c23188243a78bbede64e3e2df7d675 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 9 Apr 2025 20:37:41 -0700 Subject: [PATCH 8/9] Create the dist dir if it doesn't already exist --- scripts/buildStandaloneDist.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/buildStandaloneDist.js b/scripts/buildStandaloneDist.js index a1f255f6..7dab4a4d 100644 --- a/scripts/buildStandaloneDist.js +++ b/scripts/buildStandaloneDist.js @@ -9,6 +9,9 @@ const buildDistribution = require('./lib/buildDistribution'); const tmpDir = 'dist/tmp' if (!fs.existsSync(tmpDir)) { + if (!fs.existsSync(path.dirname(tmpDir))) { + fs.mkdirSync(path.dirname(tmpDir)); + } fs.mkdirSync(tmpDir); } From 8be98e73ca8c658c4adae8d63b081e6178a329b4 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Thu, 10 Apr 2025 18:18:42 -0700 Subject: [PATCH 9/9] Use published css-url-embed --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93b87683..c6547796 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.24.0", "archiver": "^7.0.1", - "css-url-embed": "github:sgravrock/css-url-embed", + "css-url-embed": "^0.1.0", "ejs": "^3.1.10", "eslint": "^9.24.0", "eslint-plugin-compat": "^6.0.2",