-
Notifications
You must be signed in to change notification settings - Fork 666
/
Gruntfile.js
882 lines (779 loc) · 32.1 KB
/
Gruntfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
/************************************************************************************************************
QUICK START
grunt
- Build the complete set of VexFlow libraries (with source-maps) for production and debug use.
This is the 'default' grunt task.
grunt test
- Build the VexFlow debug libraries and run the QUnit command line tests with 'tests/flow-headless-browser.html'.
grunt reference
- Build VexFlow and copy the current build/ to the reference/ folder (the copy:reference task),
so that we can compare future builds to the reference/ via `grunt test:reference`.
*************************************************************************************************************
DEVELOP
grunt watch
- FAST: Watch for changes and produces the debug CJS libraries in build/cjs/.
grunt watch:esm
- FAST: Watch for changes and build the ESM libraries in build/esm/.
- Also see `grunt test:browser:esm` below.
grunt watch:prod
- SLOW: Watch for changes and builds the production libraries.
*************************************************************************************************************
TEST
grunt test:cmd
- Run the QUnit command line tests with 'tests/flow-headless-browser.html'.
grunt test:browser:cjs
- Opens flow.html in the default browser. Loads the CJS build.
grunt test:browser:esm
- Runs `npx http-server` from the vexflow/ directory to serve tests/flow.html and build/esm/.
- Watches for changes and builds the ESM libraries.
- Opens http://localhost:8080/tests/flow.html?esm=true to run the tests with the ESM build
grunt get:releases:versionX:versionY:...
- Retrieve previous releases for regression testing purposes.
- For example: grunt get:releases:3.0.9:4.0.0
grunt clean
- Remove all files generated by the build process.
Search this file for `// grunt` to see other supported grunt tasks.
*************************************************************************************************************
RELEASE
To automatically release to GitHub, you need to have a personal access token with "repo" rights.
Generate one here: https://github.com/settings/tokens/new?scopes=repo&description=release-it
Also, make sure your authenticator app is ready to generate a 2FA one time password for npm.
grunt && npm pack
- Create a *.tgz that can be emailed to a friend or uploaded to a test server.
npm install from the *.tgz file or test server URL to verify your project works with this build of VexFlow.
grunt release
- Run the release script to build, commit, and publish to npm and GitHub.
This assumes you have fully tested the build.
*************************************************************************************************************
ENVIRONMENT VARIABLES (optional):
VEX_DEBUG_CIRCULAR_DEPENDENCIES
if true, we display a list of circular dependencies in the code.
VEX_DEVTOOL
Specify an alternative webpack devtool config (the default is 'source-map').
Pass in 'false' to disable source maps.
https://webpack.js.org/configuration/devtool/
VEX_GENERATE_OPTIONS
options for controlling the ./tools/generate_images.js script.
see the 'generate:current' and 'generate:reference' tasks.
To pass in environment variables, you can use your ~/.bash_profile or do something like:
export VEX_DEBUG_CIRCULAR_DEPENDENCIES=true
export VEX_DEVTOOL=eval
grunt
You can also do it all on one line:
VEX_DEBUG_CIRCULAR_DEPENDENCIES=true VEX_DEVTOOL=eval grunt
*************************************************************************************************************/
const path = require('path');
const fs = require('fs');
const os = require('os');
const { execSync, spawnSync } = require('child_process');
const ts = require('typescript');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const open = require('opener');
const concurrently = require('concurrently');
// release-it can only be dynamically imported.
const releaseItDynamicImport = import('release-it');
// A module entry file `entry/xxxx.ts` will be mapped to a build output file in build/cjs/ or /build/esm/entry/.
// Also see the package.json `exports` field, which is one way for projects to specify which entry file to import.
const VEX = 'vexflow';
const VEX_BRAVURA = 'vexflow-bravura';
const VEX_GONVILLE = 'vexflow-gonville';
const VEX_LELAND = 'vexflow-leland';
const VEX_PETALUMA = 'vexflow-petaluma';
const VEX_CORE = 'vexflow-core'; // Supports dynamic import of the font modules below.
const VEX_DEBUG = 'vexflow-debug';
const VEX_DEBUG_TESTS = 'vexflow-debug-with-tests';
const versionInfo = require('./tools/version_info');
// Add a banner to the top of some CJS output files.
const banner =
`VexFlow ${versionInfo.VERSION} ${versionInfo.DATE} ${versionInfo.ID}\n` +
`Copyright (c) 2010 Mohit Muthanna Cheppudira <mohit@muthanna.com>\n` +
`https://www.vexflow.com https://github.com/0xfe/vexflow`;
// Output directories & files.
const BASE_DIR = __dirname;
const BUILD_DIR = path.join(BASE_DIR, 'build');
const BUILD_CJS_DIR = path.join(BUILD_DIR, 'cjs');
const BUILD_ESM_DIR = path.join(BUILD_DIR, 'esm');
const BUILD_IMAGES_CURRENT_DIR = path.join(BUILD_DIR, 'images', 'current');
const BUILD_IMAGES_REFERENCE_DIR = path.join(BUILD_DIR, 'images', 'reference');
const REFERENCE_DIR = path.join(BASE_DIR, 'reference');
const REFERENCE_IMAGES_DIR = path.join(REFERENCE_DIR, 'images');
const WEBPACK_CACHE_DIR = path.join(BASE_DIR, 'node_modules', '.cache', 'webpack');
const ESLINT_CACHE_FILE = path.join(BASE_DIR, 'node_modules', '.cache', 'eslint.json');
const BUILD_ESM_PACKAGE_JSON_FILE = path.join(BUILD_ESM_DIR, 'package.json');
const LOCALHOST = 'http://127.0.0.1:8080';
// Flags for setting the webpack mode.
// See: https://webpack.js.org/configuration/mode/
// PRODUCTION_MODE enables minification and DEVELOPMENT_MODE disables code minification.
const PRODUCTION_MODE = 'production';
const DEVELOPMENT_MODE = 'development';
// Read environment variables to configure our scripts.
let DEBUG_CIRCULAR_DEPENDENCIES, DEVTOOL, GENERATE_IMAGES_ARGS;
function readEnvironmentVariables() {
const env = process.env;
let val = env.VEX_DEBUG_CIRCULAR_DEPENDENCIES;
DEBUG_CIRCULAR_DEPENDENCIES = val === 'true' || val === '1';
// Control the type of source maps that will be produced.
// See: https://webpack.js.org/configuration/devtool/
// In version 3.0.9 this environment variable was called VEX_GENMAP.
DEVTOOL = env.VEX_DEVTOOL || 'source-map'; // for production builds with high quality source maps.
if (DEVTOOL === 'false') {
DEVTOOL = false;
}
val = env.VEX_GENERATE_OPTIONS;
GENERATE_IMAGES_ARGS = val ? val.split(' ') : [];
}
readEnvironmentVariables();
function runCommand(command, ...args) {
// The stdio option passes the output from the spawned process back to this process's console.
spawnSync(command, args, { stdio: 'inherit' });
}
function webpackConfigs() {
let pluginBanner;
let pluginCircular;
let pluginFork;
let pluginTerser;
// entryFiles is one of the following:
// an array of file names
// an object that maps entry names to file names
// a file name string
// returns a webpack config object.
function getConfig(entryFiles, mode, addBanner, libraryName, watch = false) {
let entry, filename;
if (Array.isArray(entryFiles)) {
entry = {};
for (const entryFileName of entryFiles) {
// The entry point is a full path to a typescript file in vexflow/entry/.
entry[entryFileName] = path.join(BASE_DIR, 'entry/', entryFileName + '.ts');
}
filename = '[name].js'; // output file names are based on the keys of the entry object above.
} else if (typeof entryFiles === 'object') {
entry = {};
for (const k in entryFiles) {
const entryFileName = entryFiles[k];
entry[k] = path.join(BASE_DIR, 'entry/', entryFileName + '.ts');
}
filename = '[name].js'; // output file names are based on the keys of the entry object above.
} else {
// entryFiles is a string representing a single file name.
const entryFileName = entryFiles;
entry = path.join(BASE_DIR, 'entry/', entryFileName + '.ts');
filename = entryFileName + '.js'; // output file name is the same as the entry file name, but with the js extension.
}
// Support different ways of loading VexFlow.
// The `globalObject` string is assigned to `root` in line 15 of vexflow-debug.js.
// VexFlow is exported as root["Vex"], and can be accessed via:
// - `window.Vex` in browsers
// - `globalThis.Vex` in node JS >= 12
// - `this.Vex` in all other environments
// See: https://webpack.js.org/configuration/output/#outputglobalobject
//
// IMPORTANT: The outer parentheses are required! Webpack inserts this string into the final output, and
// without the parentheses, code splitting will be broken. Search for `webpackChunkVex` inside the output files.
let globalObject = `(typeof window !== 'undefined' ? window : typeof globalThis !== 'undefined' ? globalThis : this)`;
function getPlugins() {
const plugins = [];
if (addBanner && typeof banner !== 'undefined') {
if (!pluginBanner) {
pluginBanner = new webpack.BannerPlugin(banner);
}
plugins.push(pluginBanner);
}
if (DEBUG_CIRCULAR_DEPENDENCIES) {
if (!pluginCircular) {
const CircularDependencyPlugin = require('circular-dependency-plugin');
pluginCircular = new CircularDependencyPlugin({ cwd: process.cwd() });
}
plugins.push(pluginCircular);
}
if (!pluginFork) {
pluginFork = new ForkTsCheckerWebpackPlugin({
typescript: {
diagnosticOptions: {
semantic: true,
syntactic: true,
declaration: true,
global: true,
},
},
});
plugins.push(pluginFork);
}
return plugins;
}
let optimization;
if (mode === PRODUCTION_MODE) {
if (!pluginTerser) {
pluginTerser = new TerserPlugin({
extractComments: false, // DO NOT extract the banner into a separate file.
parallel: os.cpus().length - 1,
terserOptions: { compress: true },
});
}
optimization = {
minimize: true,
minimizer: [pluginTerser],
};
}
// Turn on webpack's cache in DEVELOPMENT_MODE.
const cache = mode === DEVELOPMENT_MODE ? { type: 'filesystem' } : false;
return {
mode,
entry,
cache,
watch,
output: {
path: BUILD_CJS_DIR,
filename: filename,
library: {
name: libraryName,
type: 'umd',
export: 'default',
},
globalObject,
},
resolve: { extensions: ['.ts', '.tsx', '.js', '...'] },
devtool: DEVTOOL,
module: {
rules: [
{
test: /version\.ts$/,
loader: 'string-replace-loader',
options: {
multiple: [
{ search: '__VF_VERSION__', replace: versionInfo.VERSION },
{ search: '__VF_GIT_COMMIT_ID__', replace: versionInfo.ID },
{ search: '__VF_BUILD_DATE__', replace: versionInfo.DATE },
],
},
},
{
test: /(\.ts$|\.js$)/,
exclude: /node_modules/,
resolve: { fullySpecified: false },
use: [
{
// https://webpack.js.org/guides/build-performance/#typescript-loader
// https://www.npmjs.com/package/fork-ts-checker-webpack-plugin
loader: 'ts-loader',
options: {
configFile: 'tsconfig.json',
transpileOnly: true,
},
},
],
},
],
},
plugins: getPlugins(),
optimization,
};
}
// Friendly names for boolean flags that we use below.
const BANNER = true;
const WATCH = true;
function prodConfig(watch = false) {
return getConfig(
[VEX, VEX_BRAVURA, VEX_GONVILLE, VEX_LELAND, VEX_PETALUMA, VEX_CORE],
PRODUCTION_MODE,
BANNER,
'Vex',
watch
);
}
// The font modules need to have different webpack configs because they have a different
// exported library name (e.g., VexFlowFont.Bravura instead of Vex).
function fontConfigs(watch = false) {
return [
getConfig('vexflow-font-bravura', PRODUCTION_MODE, !BANNER, ['VexFlowFont', 'Bravura'], watch),
getConfig('vexflow-font-leland', PRODUCTION_MODE, !BANNER, ['VexFlowFont', 'Leland'], watch),
getConfig('vexflow-font-petaluma', PRODUCTION_MODE, !BANNER, ['VexFlowFont', 'Petaluma'], watch),
getConfig('vexflow-font-gonville', PRODUCTION_MODE, !BANNER, ['VexFlowFont', 'Gonville'], watch),
getConfig('vexflow-font-custom', PRODUCTION_MODE, !BANNER, ['VexFlowFont', 'Custom'], watch),
// ADD_MUSIC_FONT
// Add a webpack config for exporting your font module.
// Provide the base name of your font entry file.
// For an entry file at `entry/vexflow-font-xxx.ts`, use the string 'vexflow-font-xxx'.
// Webpack will use this config to generate a CJS font module at `build/cjs/vexflow-font-xxx.js`.
// getConfig('vexflow-font-xxx', PRODUCTION_MODE, !BANNER, ['VexFlowFont', 'XXX'], watch),
];
}
function debugConfig(watch = false) {
return getConfig([VEX_DEBUG, VEX_DEBUG_TESTS], DEVELOPMENT_MODE, BANNER, 'Vex', watch);
}
return {
// grunt webpack:prodAndDebug
prodAndDebug: () => [prodConfig(), ...fontConfigs(), debugConfig()],
// grunt webpack:prod
prod: () => [prodConfig(), ...fontConfigs()],
// grunt webpack:debug
debug: () => debugConfig(),
// grunt webpack:watchProd
watchProd: () => [prodConfig(WATCH), ...fontConfigs(WATCH)],
// grunt webpack:watchDebug
watchDebug: () => debugConfig(WATCH),
};
}
module.exports = (grunt) => {
const log = grunt.log.writeln;
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
webpack: webpackConfigs(),
// grunt qunit
// Run unit tests on the command line by loading tests/flow-headless-browser.html.
// Requires the CJS build to be present in the `build/cjs/` directory (See: grunt build:cjs).
// The grunt-contrib-qunit package uses puppeteer to load the test page.
qunit: {
files: ['tests/flow-headless-browser.html'],
options: {
puppeteer: {
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
},
},
},
copy: {
// grunt copy:reference
// After `grunt test` call this to save the current build/ to reference/.
reference: {
files: [
{
expand: true,
cwd: BUILD_DIR,
src: ['**'],
dest: REFERENCE_DIR,
},
],
},
// grunt copy:save_reference_images
// build/images/reference/ => reference/images/
save_reference_images: {
files: [
{
expand: true,
cwd: BUILD_IMAGES_REFERENCE_DIR,
src: ['**'],
dest: REFERENCE_IMAGES_DIR,
},
],
},
// grunt copy:restore_reference_images
// reference/images/ => build/images/reference/
restore_reference_images: {
files: [
{
expand: true,
cwd: REFERENCE_IMAGES_DIR,
src: ['**'],
dest: BUILD_IMAGES_REFERENCE_DIR,
},
],
},
},
// grunt clean
// Calls all clean tasks below.
clean: {
// grunt clean:build
build: { src: [BUILD_DIR] },
// grunt clean:build_esm
build_esm: { src: [BUILD_ESM_DIR] },
// grunt clean:reference
reference: { src: [REFERENCE_DIR] },
// grunt clean:reference_images
// Delete the image cache at reference/images/.
reference_images: { src: [REFERENCE_IMAGES_DIR] },
// grunt clean:webpack_cache
// For debug builds, we use a webpack cache to speed up rebuilds.
// https://webpack.js.org/guides/build-performance/#persistent-cache
webpack_cache: { src: [WEBPACK_CACHE_DIR] },
// grunt clean:eslint_cache
eslint_cache: { src: [ESLINT_CACHE_FILE] },
},
});
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-qunit');
grunt.loadNpmTasks('grunt-webpack');
// grunt
// Build all targets for production and debugging.
grunt.registerTask('default', 'Build all VexFlow targets.', [
'clean:build',
'webpack:prodAndDebug',
'build:esm',
'build:types',
'build:docs',
]);
// grunt test
// Run command line qunit tests.
grunt.registerTask('test', 'Run command line unit tests.', ['clean:build', 'webpack:debug', 'qunit']);
// grunt build:cjs
grunt.registerTask('build:cjs', 'Use webpack to create CJS files in build/cjs/', 'webpack:prodAndDebug');
// grunt build:esm
// grunt build:esm:watch
// Output individual ES module files to build/esm/.
// Also fixes the imports and exports so that they all end in .js.
grunt.registerTask('build:esm', 'Use tsc to create ES module files in build/esm/', function (arg) {
function fixESM() {
// Add .js file extensions to ESM imports and re-exports.
runCommand('node', './tools/fix-esm-imports.mjs', './build/esm/');
// Update the version.js file.
versionInfo.saveESMVersionFile();
}
let configFile = 'tsconfig.esm.json';
log('ESM: Building to ./build/esm/');
// The build/esm/ folder needs a package.json that specifies { "type": "module" }.
// This indicates that all *.js files in `vexflow/build/esm/` are ES modules.
fs.mkdirSync(BUILD_ESM_DIR, { recursive: true });
fs.writeFileSync(BUILD_ESM_PACKAGE_JSON_FILE, '{\n "type": "module"\n}\n');
if (arg === 'watch') {
this.async(); // Set grunt's async mode to keep the task running forever.
const TscWatchClient = require('tsc-watch/client');
const watch = new TscWatchClient();
watch.on('started', () => {
console.log('Press CTRL + C to quit.');
});
watch.on('success', () => {
versionInfo.update();
fixESM();
});
watch.start('-p', configFile, '--noClear');
} else {
TypeScript.compile(configFile);
fixESM();
}
});
// grunt build:types
// Output *.d.ts files to build/types/.
grunt.registerTask('build:types', 'Use tsc to create *.d.ts files in build/types/', () => {
log('Types: Building *.d.ts files in build/types/');
TypeScript.compile('tsconfig.types.json');
});
// grunt build:docs
// Use TypeDoc to automatically build API documentation to docs/api/.
grunt.registerTask('build:docs', 'Build API documentation to docs/api/.', () => {
runCommand('npx', 'typedoc');
});
// grunt watch
// Watch for changes and build debug CJS files.
grunt.registerTask('watch', 'The fastest way to iterate while working on VexFlow', [
'clean:build',
'webpack:watchDebug',
]);
// grunt watch:prod
// Watch for changes and build production CJS files. This might be slow!
grunt.registerTask('watch:prod', 'Watch for changes and build production CJS files.', [
'clean:build',
'webpack:watchProd',
]);
// grunt watch:esm
// Watch for changes and build esm/*.
// Run a web server with `npx http-server` to serve flow.html and the ESM files.
grunt.registerTask('watch:esm', 'Watch for changes and build ESM files to build/esm/*', [
'clean:build',
'build:esm:watch',
]);
// If you have already compiled the libraries, you can use the three `grunt test:xxx` tasks
// below to test the existing build:
// grunt test:cmd
grunt.registerTask('test:cmd', 'Run command line unit tests.', 'qunit');
// grunt test:browser:cjs
// Open the default browser to the flow.html test page.
grunt.registerTask(
'test:browser:cjs',
'Test the CJS build by loading the flow.html file in the default browser.',
() => {
// If the CJS build doesn't exist, build it.
if (!fs.existsSync(BUILD_CJS_DIR)) {
log('Building the CJS files.');
grunt.task.run('webpack:debug');
} else {
log('CJS files already exist. Skipping the build step. To rebuild, run:');
log('grunt clean:build && grunt test:browser:cjs');
}
open('./tests/flow.html');
}
);
// grunt test:browser:esm
// Starts a web server (e.g., `npx http-server`) and opens the default browser to
// http://localhost:8080/tests/flow.html?esm=true The -c-1 option disables caching.
// Testing ES module code requires a web server.
grunt.registerTask(
'test:browser:esm',
'Test the ESM build in a web server by navigating to http://localhost:8080/tests/flow.html?esm=true',
function () {
log('Launching http-server...');
log('Building the ESM files in watch mode...');
grunt.task.run('clean:build_esm');
this.async(); // keep this grunt task alive.
concurrently([
{ command: 'grunt build:esm:watch', name: 'watch:esm' },
{ command: 'npx http-server -c-1 -o /tests/flow.html?esm=true', name: 'server' },
]);
}
);
// grunt reference
// Build the current HEAD revision and copy it to reference/
// After developing new features or fixing a bug, you can compare the current
// working tree against the reference with: grunt test:reference
grunt.registerTask('reference', 'Build to reference/.', [
'clean:build',
'clean:reference',
'webpack:prodAndDebug',
'build:esm',
'copy:reference',
]);
// grunt generate:current
// Create images from the VexFlow in build/.
// node ./tools/generate_images.js build ./build/images/current ${VEX_GENERATE_OPTIONS}
grunt.registerTask('generate:current', 'Create images from the VexFlow version in build/.', () => {
runCommand('node', './tools/generate_images.js', 'build', './build/images/current', ...GENERATE_IMAGES_ARGS);
});
// grunt generate:reference
// Create images from VexFlow library in reference/
// node ./tools/generate_images.js reference ./build/images/reference ${VEX_GENERATE_OPTIONS}
grunt.registerTask('generate:reference', 'Create images from the VexFlow version in reference/.', () => {
runCommand('node', './tools/generate_images.js', 'reference', './build/images/reference', ...GENERATE_IMAGES_ARGS);
});
// grunt generate:release:X.Y.Z
// Create images from the VexFlow library in releases/X.Y.Z/
// node ./tools/generate_images.js releases/X.Y.Z ./build/images/X.Y.Z ${VEX_GENERATE_OPTIONS}
grunt.registerTask('generate:release', 'Create images from the VexFlow version in releases/X.Y.Z/', (ver) => {
console.log(`Creating images with VexFlow version ${ver}.`);
console.log(`Saving images to build/images/${ver}/`);
runCommand(
'node',
'./tools/generate_images.js',
'releases/' + ver,
'./build/images/' + ver,
...GENERATE_IMAGES_ARGS
);
});
// grunt diff:reference
// Visual regression test compares images from the current build vs images from the reference build.
grunt.registerTask(
'diff:reference',
'Compare images created by the build/ and reference/ versions of VexFlow.',
() => {
runCommand('./tools/visual_regression.sh', 'reference');
}
);
// grunt diff:ver:X.Y.Z
// grunt diff:ver:3.0.9 compares images created by the VexFlow library in build/ and releases/3.0.9/.
// Run this after you have created images with `grunt generate:release:X.Y.Z`.
grunt.registerTask(
'diff:ver',
'Compare images created by the build/ and releases/X.Y.Z/ versions of VexFlow',
(version) => {
// Make sure the folder exists.
const dirA = path.join(BUILD_DIR, 'images', version);
const dirB = BUILD_IMAGES_CURRENT_DIR;
if (!fs.existsSync(dirA)) {
grunt.fail.fatal('Missing images directory.\n' + dirA);
}
if (!fs.existsSync(dirB)) {
grunt.fail.fatal('Missing images directory\n' + dirB);
}
runCommand('./tools/visual_regression.sh', version);
}
);
// grunt test:reference
// Visual regression test to compare the current build/ and reference/ versions of VexFlow.
grunt.registerTask('test:reference', 'Generate images from build/ and reference/ and compare them.', [
'test',
'generate:current',
'generate:reference',
'diff:reference',
]);
// grunt test:reference:cache
// Faster than `grunt test:reference` because it reuses the existing reference images if available.
grunt.registerTask('test:reference:cache', 'Save and Reuse existing reference images.', [
'cache:save:reference',
'test',
'generate:current',
'cache:restore:reference',
'diff:reference',
]);
// grunt cache:save:reference
// Helper task to save the references images.
grunt.registerTask('cache:save:reference', 'Helper task for test:reference:cache.', () => {
if (fs.existsSync(BUILD_IMAGES_REFERENCE_DIR)) {
grunt.task.run('clean:reference_images');
grunt.task.run('copy:save_reference_images');
}
});
// grunt cache:restore:reference
// Helper task to make sure the reference images are available for our visual regression tests.
// If the reference images are not available, run the 'generate:reference' task.
grunt.registerTask('cache:restore:reference', 'Helper task for test:reference:cache.', () => {
if (fs.existsSync(REFERENCE_IMAGES_DIR)) {
grunt.task.run('copy:restore_reference_images');
} else {
grunt.task.run('generate:reference');
}
});
// grunt get:releases:X.Y.Z:A.B.C:...
// grunt get:releases:3.0.9:4.0.0 => node ./tools/get_releases.mjs 3.0.9 4.0.0
// Retrieve previous releases from the git repository or from npm.
// Note: the arguments are separated by colons!
grunt.registerTask('get:releases', 'Retrieve previous releases.', (...args) => {
runCommand('node', './tools/get_releases.mjs', ...args);
});
// grunt test:release:X.Y.Z
// Visual regression test to compare the current build/ and releases/X.Y.Z/ versions of VexFlow.
// Assume the release has already been retrieved with the 'get:releases' task above.
grunt.registerTask('test:release', 'Generate images from build/ and releases/X.Y.Z and compare them.', (ver) => {
if (!ver) {
grunt.fail.fatal('Missing version number.\nUsage: grunt test:release:X.Y.Z');
}
grunt.task.run('test', 'generate:current', 'generate:release:' + ver, 'diff:ver:' + ver);
});
// Release to npm and GitHub.
// Specify "dry-run" to walk through the release process without actually doing anything.
// Optionally provide a preRelease tag ( alpha | beta | rc ).
// Remember to use your GitHub personal access token:
// GITHUB_TOKEN=XYZ grunt release
// GITHUB_TOKEN=XYZ grunt release:alpha
// GITHUB_TOKEN=XYZ grunt release:beta
// GITHUB_TOKEN=XYZ grunt release:rc
// GITHUB_TOKEN=XYZ grunt release:dry-run
// GITHUB_TOKEN=XYZ grunt release:dry-run:alpha
// GITHUB_TOKEN=XYZ grunt release:dry-run:beta
// GITHUB_TOKEN=XYZ grunt release:dry-run:rc
// See the wiki for information on manual releases:
// https://github.com/0xfe/vexflow/wiki/Build,-Test,-Release#publish-manually-to-npm-and-github
grunt.registerTask('release', 'Produce the complete build. Release to npm and GitHub.', function (...args) {
if (!process.env.GITHUB_TOKEN) {
console.log(
'GITHUB_TOKEN environment variable is missing.\n' +
'You can manually release to GitHub at https://github.com/0xfe/vexflow/releases/new\n' +
'Or use the GitHub CLI:\n' +
'gh release create 4.0.0 --title "Release 4.0.0"\n\n'
);
}
const done = this.async();
// See the full list of options at:
// https://github.com/release-it/release-it
// https://github.com/release-it/release-it/blob/master/config/release-it.json
const options = {
verbose: 1, // See the output of each hook.
// verbose: 2, // Only for debugging.
hooks: {
'before:init': ['grunt clean'],
'after:bump': ['grunt', 'echo Adding build/ folder...', 'git add -f build/'],
'after:npm:release': [],
'after:git:release': [],
'after:github:release': [],
'after:release': ['echo Successfully released ${name} ${version} to https://github.com/${repo.repository}'],
},
git: {
commitMessage: 'Release VexFlow ${version}',
requireCleanWorkingDir: false,
commit: true,
tag: true,
push: true,
pushRepo: 'git@github.com:0xfe/vexflow.git',
},
github: {
release: true,
releaseName: '${version}',
skipChecks: false,
},
npm: { publish: true },
'disable-metrics': true,
};
// Support boolean flags (e.g., 'git.commit=false').
function setBooleanFlag(arg) {
// 'git.commit=false' => ['git.commit', 'false']
const parts = arg.split('=');
if (parts.length !== 2) {
return;
}
// 'git.commit' => ['git', 'commit']
const [key_0, key_1] = parts[0].split('.');
const val = parts[1] === 'true'; // any other value === false
options[key_0][key_1] = val;
}
args.forEach((arg) => {
if (arg === 'dry-run') {
options['dry-run'] = true;
log('====== DRY RUN MODE ======');
} else if (arg === 'alpha' || arg === 'beta' || arg === 'rc') {
// Handle preRelease tags: alpha | beta | rc.
options['preRelease'] = arg;
} else if (arg.startsWith('verbose')) {
// verbose=1 => See the output of each hook.
// verbose=2 => For debugging the release script.
const parts = arg.split('=');
if (parts.length === 2) {
const val = parseInt(parts[1]) === 1 ? 1 : 2;
options.verbose = val;
}
} else if (arg.startsWith('git.') || arg.startsWith('github.') || arg.startsWith('npm.')) {
setBooleanFlag(arg);
}
});
releaseItDynamicImport
.then((module) => {
const runTasks = module.default;
return runTasks(options);
})
.then((output) => {
try {
log('Removing build/ folder...');
execSync('git rm -rf build/', { stdio: 'pipe' }); // { stdio: 'pipe' } hides the output.
execSync(`git commit -m 'Remove build/ after releasing version ${output.version} to npm and GitHub.'`);
runCommand('git', 'push');
} catch (e) {
// If the build/ folder was not added/checked in, we do nothing.
}
done();
});
});
};
// Call tsc programmatically:
// TypeScript.compile(pathToTSConfigFile);
const TypeScript = (() => {
function logErrors(diagnostics) {
// Print each ts.Diagnostic object to the console.
diagnostics.forEach((diagnostic) => {
let message = 'Error';
if (diagnostic.file) {
const where = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
message += ' ' + diagnostic.file.fileName + ' ' + where.line + ', ' + where.character + 1;
}
message += ': ' + ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
console.log(message);
});
}
function readConfigFile(configFile) {
const configFileText = fs.readFileSync(configFile).toString();
const configFileJSON = ts.parseConfigFileTextToJson(configFile, configFileText);
const configObject = configFileJSON.config;
if (!configObject) {
logErrors([configFileJSON.error]);
process.exit(1);
}
const configParsed = ts.parseJsonConfigFileContent(configObject, ts.sys, path.dirname(configFile));
if (configParsed.errors.length > 0) {
logErrors(configParsed.errors);
process.exit(1);
}
return configParsed;
}
// Return true if the command was successful (the compilation returned no errors).
function compile(tsConfigFile) {
const config = readConfigFile(tsConfigFile);
console.log('TypeScript: Compiling ' + config.fileNames.length + ' files...');
const program = ts.createProgram(config.fileNames, config.options);
const emitResult = program.emit();
logErrors(ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics));
if (emitResult.emitSkipped) {
return false; // FAIL
} else {
return true; // SUCCESS!
}
}
// Expose a single public function.
return { compile };
})();