const gulp = require('gulp'); /** * @typedef { { src:string; 'npm/dev':string; 'npm/min':string; built:string; releaseDev:string; releaseMin:string; } } ICorePaths * @typedef { { src:string; dev:string; min:string; esm: string; } } IPluginPaths * @typedef { { name:string; contrib:string; modulePrefix:string; rootPath:string; paths:IPluginPaths } } IPlugin * @typedef { { METADATA: {CORE:{paths:ICorePaths}; PLUGINS:IPlugin[];} } } IMetadata * @type { IMetadata } */ const metadata = require('./monaco-editor/metadata'); const es = require('event-stream'); const path = require('path'); const fs = require('fs'); const rimraf = require('rimraf'); const cp = require('child_process'); const yaserver = require('yaserver'); const http = require('http'); const CleanCSS = require('clean-css'); const uncss = require('uncss'); const File = require('vinyl'); const ts = require('typescript'); const WEBSITE_GENERATED_PATH = path.join(__dirname, 'monaco-editor/website/playground/new-samples'); /** @type {string} */ const MONACO_EDITOR_VERSION = (function () { const packageJsonPath = path.join(__dirname, 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString()); const version = packageJson.version; if (!/\d+\.\d+\.\d+/.test(version)) { console.log('unrecognized package.json version: ' + version); process.exit(0); } return version; })(); async function _execute(task) { // Always invoke as if it were a callback task return new Promise((resolve, reject) => { if (task.length === 1) { // this is a calback task task((err) => { if (err) { return reject(err); } resolve(); }); return; } const taskResult = task(); if (typeof taskResult === 'undefined') { // this is a sync task resolve(); return; } if (typeof taskResult.then === 'function') { // this is a promise returning task taskResult.then(resolve, reject); return; } // this is a stream returning task taskResult.on('end', (_) => resolve()); taskResult.on('error', (err) => reject(err)); }); } function taskSeries(...tasks) { return async () => { for (let i = 0; i < tasks.length; i++) { await _execute(tasks[i]); } }; } const cleanReleaseTask = function (cb) { rimraf('release', { maxBusyTries: 1 }, cb); }; gulp.task( 'release', taskSeries(cleanReleaseTask, function () { return es.merge( // dev folder releaseOne('dev'), // min folder releaseOne('min'), // esm folder ESM_release(), // package.json gulp .src('package.json') .pipe( es.through(function (data) { var json = JSON.parse(data.contents.toString()); json.private = false; data.contents = Buffer.from(JSON.stringify(json, null, ' ')); delete json.scripts['postinstall']; this.emit('data', data); }) ) .pipe(gulp.dest('release')), gulp.src('CHANGELOG.md').pipe(gulp.dest('release')), // min-maps folder gulp.src('node_modules/monaco-editor-core/min-maps/**/*').pipe(gulp.dest('release/min-maps')), // other files gulp .src([ 'node_modules/monaco-editor-core/LICENSE', 'node_modules/monaco-editor-core/monaco.d.ts', 'node_modules/monaco-editor-core/ThirdPartyNotices.txt', 'README.md' ]) .pipe(addPluginDTS()) .pipe(addPluginThirdPartyNotices()) .pipe(gulp.dest('release')) ); }) ); /** * Release to `dev` or `min`. * @param {'dev'|'min'} type * @returns {NodeJS.ReadWriteStream} */ function releaseOne(type) { return es.merge( gulp .src('node_modules/monaco-editor-core/' + type + '/**/*') .pipe(addPluginContribs(type)) .pipe(gulp.dest('release/' + type)), pluginStreams(type, 'release/' + type + '/') ); } /** * Release plugins to `dev` or `min`. * @param {'dev'|'min'} type * @param {string} destinationPath * @returns {NodeJS.ReadWriteStream} */ function pluginStreams(type, destinationPath) { return es.merge( metadata.METADATA.PLUGINS.map(function (plugin) { return pluginStream(plugin, type, destinationPath); }) ); } /** * Release a plugin to `dev` or `min`. * @param {IPlugin} plugin * @param {'dev'|'min'} type * @param {string} destinationPath * @returns {NodeJS.ReadWriteStream} */ function pluginStream(plugin, type, destinationPath) { const pluginPath = path.join(plugin.rootPath, plugin.paths[type]); // dev or min const contribPath = path.join(pluginPath, plugin.contrib.substr(plugin.modulePrefix.length)) + '.js'; return gulp .src([pluginPath + '/**/*', '!' + contribPath]) .pipe( es.through( /** * @param {File} data */ function (data) { if (!/_\.contribution/.test(data.path)) { this.emit('data', data); return; } let contents = data.contents.toString(); contents = contents.replace( 'define(["require", "exports"],', 'define(["require", "exports", "vs/editor/editor.api"],' ); data.contents = Buffer.from(contents); this.emit('data', data); } ) ) .pipe(gulp.dest(destinationPath + plugin.modulePrefix)); } /** * Edit editor.main.js: * - rename the AMD module 'vs/editor/editor.main' to 'vs/editor/edcore.main' * - append monaco.contribution modules from plugins * - append new AMD module 'vs/editor/editor.main' that stiches things together * * @param {'dev'|'min'} type * @returns {NodeJS.ReadWriteStream} */ function addPluginContribs(type) { return es.through( /** * @param {File} data */ function (data) { if (!/editor\.main\.js$/.test(data.path)) { this.emit('data', data); return; } let contents = data.contents.toString(); // Rename the AMD module 'vs/editor/editor.main' to 'vs/editor/edcore.main' contents = contents.replace(/"vs\/editor\/editor\.main\"/, '"vs/editor/edcore.main"'); /** @type {string[]} */ let extraContent = []; /** @type {string[]} */ let allPluginsModuleIds = []; metadata.METADATA.PLUGINS.forEach(function (plugin) { allPluginsModuleIds.push(plugin.contrib); const pluginPath = path.join(plugin.rootPath, plugin.paths[type]); // dev or min const contribPath = path.join(__dirname, pluginPath, plugin.contrib.substr(plugin.modulePrefix.length)) + '.js'; let contribContents = fs.readFileSync(contribPath).toString(); contribContents = contribContents.replace( /define\((['"][a-z\/\-]+\/fillers\/monaco-editor-core['"]),\[\],/, "define($1,['vs/editor/editor.api']," ); extraContent.push(contribContents); }); extraContent.push( `define("vs/editor/editor.main", ["vs/editor/edcore.main","${allPluginsModuleIds.join( '","' )}"], function(api) { return api; });` ); let insertIndex = contents.lastIndexOf('//# sourceMappingURL='); if (insertIndex === -1) { insertIndex = contents.length; } contents = contents.substring(0, insertIndex) + '\n' + extraContent.join('\n') + '\n' + contents.substring(insertIndex); data.contents = Buffer.from(contents); this.emit('data', data); } ); } /** * @returns {NodeJS.ReadWriteStream} */ function ESM_release() { return es.merge( gulp .src([ 'node_modules/monaco-editor-core/esm/**/*', // we will create our own editor.api.d.ts which also contains the plugins API '!node_modules/monaco-editor-core/esm/vs/editor/editor.api.d.ts' ]) .pipe(ESM_addImportSuffix()) .pipe(ESM_addPluginContribs('release/esm')) .pipe(gulp.dest('release/esm')), ESM_pluginStreams('release/esm/') ); } /** * Release plugins to `esm`. * @param {string} destinationPath * @returns {NodeJS.ReadWriteStream} */ function ESM_pluginStreams(destinationPath) { return es.merge( metadata.METADATA.PLUGINS.map(function (plugin) { return ESM_pluginStream(plugin, destinationPath); }) ); } /** * Release a plugin to `esm`. * Adds a dependency to 'vs/editor/editor.api' in contrib files in order for `monaco` to be defined. * Rewrites imports for 'monaco-editor-core/**' * @param {IPlugin} plugin * @param {string} destinationPath * @returns {NodeJS.ReadWriteStream} */ function ESM_pluginStream(plugin, destinationPath) { const DESTINATION = path.join(__dirname, destinationPath); const pluginPath = path.join(plugin.rootPath, plugin.paths['esm']); return gulp .src([pluginPath + '/**/*']) .pipe( es.through( /** * @param {File} data */ function (data) { if (!/(\.js$)|(\.ts$)/.test(data.path)) { this.emit('data', data); return; } let contents = data.contents.toString(); const info = ts.preProcessFile(contents); for (let i = info.importedFiles.length - 1; i >= 0; i--) { let importText = info.importedFiles[i].fileName; const pos = info.importedFiles[i].pos; const end = info.importedFiles[i].end; if (!/(^\.\/)|(^\.\.\/)/.test(importText)) { // non-relative import if (!/^monaco-editor-core/.test(importText)) { console.error( `Non-relative import for unknown module: ${importText} in ${data.path}` ); process.exit(0); } if (importText === 'monaco-editor-core') { importText = 'monaco-editor-core/esm/vs/editor/editor.api'; } const myFileDestPath = path.join(DESTINATION, plugin.modulePrefix, data.relative); const importFilePath = path.join( DESTINATION, importText.substr('monaco-editor-core/esm/'.length) ); let relativePath = path .relative(path.dirname(myFileDestPath), importFilePath) .replace(/\\/g, '/'); if (!/(^\.\/)|(^\.\.\/)/.test(relativePath)) { relativePath = './' + relativePath; } contents = contents.substring(0, pos + 1) + relativePath + contents.substring(end + 1); } } data.contents = Buffer.from(contents); this.emit('data', data); } ) ) .pipe( es.through( /** * @param {File} data */ function (data) { if (!/monaco\.contribution\.js$/.test(data.path)) { this.emit('data', data); return; } const myFileDestPath = path.join(DESTINATION, plugin.modulePrefix, data.relative); const apiFilePath = path.join(DESTINATION, 'vs/editor/editor.api'); let relativePath = path .relative(path.dirname(myFileDestPath), apiFilePath) .replace(/\\/g, '/'); if (!/(^\.\/)|(^\.\.\/)/.test(relativePath)) { relativePath = './' + relativePath; } let contents = data.contents.toString(); contents = `import '${relativePath}';\n` + contents; data.contents = Buffer.from(contents); this.emit('data', data); } ) ) .pipe(ESM_addImportSuffix()) .pipe(gulp.dest(destinationPath + plugin.modulePrefix)); } /** * Adds `.js` to all import statements. * @returns {NodeJS.ReadWriteStream} */ function ESM_addImportSuffix() { return es.through( /** * @param {File} data */ function (data) { if (!/\.js$/.test(data.path)) { this.emit('data', data); return; } let contents = data.contents.toString(); const info = ts.preProcessFile(contents); for (let i = info.importedFiles.length - 1; i >= 0; i--) { const importText = info.importedFiles[i].fileName; const pos = info.importedFiles[i].pos; const end = info.importedFiles[i].end; if (/\.css$/.test(importText)) { continue; } contents = contents.substring(0, pos + 1) + importText + '.js' + contents.substring(end + 1); } data.contents = Buffer.from(contents); this.emit('data', data); } ); } /** * - Rename esm/vs/editor/editor.main.js to esm/vs/editor/edcore.main.js * - Create esm/vs/editor/editor.main.js that that stiches things together * @param {string} dest * @returns {NodeJS.ReadWriteStream} */ function ESM_addPluginContribs(dest) { const DESTINATION = path.join(__dirname, dest); return es.through( /** * @param {File} data */ function (data) { if (!/editor\.main\.js$/.test(data.path)) { this.emit('data', data); return; } this.emit( 'data', new File({ path: data.path.replace(/editor\.main/, 'edcore.main'), base: data.base, contents: data.contents }) ); const mainFileDestPath = path.join(DESTINATION, 'vs/editor/editor.main.js'); /** @type {string[]} */ let mainFileImports = []; metadata.METADATA.PLUGINS.forEach(function (plugin) { const contribDestPath = path.join(DESTINATION, plugin.contrib); let relativePath = path .relative(path.dirname(mainFileDestPath), contribDestPath) .replace(/\\/g, '/'); if (!/(^\.\/)|(^\.\.\/)/.test(relativePath)) { relativePath = './' + relativePath; } mainFileImports.push(relativePath); }); const mainFileContents = mainFileImports.map((name) => `import '${name}';`).join('\n') + `\n\nexport * from './edcore.main';`; this.emit( 'data', new File({ path: data.path, base: data.base, contents: Buffer.from(mainFileContents) }) ); } ); } /** * Edit monaco.d.ts: * - append monaco.d.ts from plugins * @returns {NodeJS.ReadWriteStream} */ function addPluginDTS() { return es.through( /** * @param {File} data */ function (data) { if (!/monaco\.d\.ts$/.test(data.path)) { this.emit('data', data); return; } let contents = data.contents.toString(); /** @type {string[]} */ const extraContent = []; metadata.METADATA.PLUGINS.forEach(function (plugin) { const dtsPath = path.join(plugin.rootPath, './monaco.d.ts'); try { let plugindts = fs.readFileSync(dtsPath).toString(); plugindts = plugindts.replace(/\/\/\/ string} callback * @returns {string} */ function replaceWithRelativeResource(dataPath, contents, regex, callback) { return contents.replace(regex, function (_, m0) { var filePath = path.join(path.dirname(dataPath), m0); return callback(m0, fs.readFileSync(filePath)); }); } var waiting = 0; var done = false; return es .merge( gulp .src(['monaco-editor/website/**/*'], { dot: true }) .pipe( es.through( /** * @param {File} data */ function (data) { if (!data.contents || !/\.(html)$/.test(data.path) || /new-samples/.test(data.path)) { return this.emit('data', data); } let contents = data.contents.toString(); contents = contents.replace(/\.\.\/release\/dev/g, 'node_modules/monaco-editor/min'); contents = contents.replace(/{{version}}/g, MONACO_EDITOR_VERSION); contents = contents.replace(/{{year}}/g, new Date().getFullYear()); // Preload xhr contents contents = replaceWithRelativeResource( data.path, contents, /
' +
										fileContents
											.toString('utf8')
											.replace(/&/g, '&')
											.replace(//g, '>') +
										'
' ); } ); // Inline fork.png contents = replaceWithRelativeResource( data.path, contents, /src="(\.\/fork.png)"/g, function (m0, fileContents) { return 'src="data:image/png;base64,' + fileContents.toString('base64') + '"'; } ); let allCSS = ''; let tmpcontents = replaceWithRelativeResource( data.path, contents, /' + output + ''; } return ''; } ); } // Inline javascript contents = replaceWithRelativeResource( data.path, contents, /'; } ); data.contents = Buffer.from(contents.split(/\r\n|\r|\n/).join('\n')); this.emit('data', data); if (done && waiting === 0) { this.emit('end'); } }.bind(this) ); }, function () { done = true; if (waiting === 0) { this.emit('end'); } } ) ) .pipe(gulp.dest('../monaco-editor-website')) ) .pipe( es.through( /** * @param {File} data */ function (data) { this.emit('data', data); }, function () { // temporarily create package.json so that npm install doesn't bark fs.writeFileSync('../monaco-editor-website/package.json', '{}'); fs.writeFileSync('../monaco-editor-website/.nojekyll', ''); cp.execSync('npm install monaco-editor', { cwd: path.join(__dirname, '../monaco-editor-website') }); fs.unlinkSync('../monaco-editor-website/package.json'); this.emit('end'); } ) ); }); gulp.task('build-website', buildWebsiteTask); gulp.task('prepare-website-branch', async function () { cp.execSync('git init', { cwd: path.join(__dirname, '../monaco-editor-website') }); let remoteUrl = cp.execSync('git remote get-url origin'); let committerUserName = cp.execSync("git log --format='%an' -1"); let committerEmail = cp.execSync("git log --format='%ae' -1"); cp.execSync(`git config user.name ${committerUserName}`, { cwd: path.join(__dirname, '../monaco-editor-website') }); cp.execSync(`git config user.email ${committerEmail}`, { cwd: path.join(__dirname, '../monaco-editor-website') }); cp.execSync(`git remote add origin ${remoteUrl}`, { cwd: path.join(__dirname, '../monaco-editor-website') }); cp.execSync('git checkout -b gh-pages', { cwd: path.join(__dirname, '../monaco-editor-website') }); cp.execSync('git add .', { cwd: path.join(__dirname, '../monaco-editor-website') }); cp.execSync('git commit -m "Publish website"', { cwd: path.join(__dirname, '../monaco-editor-website') }); console.log('RUN monaco-editor-website>git push origin gh-pages --force'); }); const generateTestSamplesTask = function () { var sampleNames = fs.readdirSync(path.join(__dirname, 'monaco-editor/test/samples')); var samples = sampleNames.map(function (sampleName) { var samplePath = path.join(__dirname, 'monaco-editor/test/samples', sampleName); var sampleContent = fs.readFileSync(samplePath).toString(); return { name: sampleName, content: sampleContent }; }); var prefix = '//This is a generated file via gulp generate-test-samples\ndefine([], function() { return'; var suffix = '; });'; fs.writeFileSync( path.join(__dirname, 'monaco-editor/test/samples-all.generated.js'), prefix + JSON.stringify(samples, null, '\t') + suffix ); var PLAY_SAMPLES = require(path.join(WEBSITE_GENERATED_PATH, 'all.js')).PLAY_SAMPLES; var locations = []; for (var i = 0; i < PLAY_SAMPLES.length; i++) { var sample = PLAY_SAMPLES[i]; var sampleId = sample.id; var samplePath = path.join(WEBSITE_GENERATED_PATH, sample.path); var html = fs.readFileSync(path.join(samplePath, 'sample.html')); var js = fs.readFileSync(path.join(samplePath, 'sample.js')); var css = fs.readFileSync(path.join(samplePath, 'sample.css')); var result = [ '', '', '', '', ' ', ' ', '', '', '', '[<< BACK]
', 'THIS IS A GENERATED FILE VIA gulp generate-test-samples', '', '
', '', '
', '
', '', '', html, '', '', '
', '
', '', '', '', '', '', '' ]; fs.writeFileSync( path.join(__dirname, 'monaco-editor/test/playground.generated/' + sampleId + '.html'), result.join('\n') ); locations.push({ path: sampleId + '.html', name: sample.chapter + ' > ' + sample.name }); } var index = [ '', '', '', '', ' ', '', '', '[<< BACK]
', 'THIS IS A GENERATED FILE VIA gulp generate-test-samples

', locations .map(function (location) { return ( '' + location.name + '' ); }) .join('
\n'), '', '', '', '' ]; fs.writeFileSync( path.join(__dirname, 'monaco-editor/test/playground.generated/index.html'), index.join('\n') ); }; function createSimpleServer(rootDir, port) { yaserver .createServer({ rootDir: rootDir }) .then((staticServer) => { const server = http.createServer((request, response) => { return staticServer.handle(request, response); }); server.listen(port, '127.0.0.1', () => { console.log(`Running at http://127.0.0.1:${port}`); }); }); } gulp.task('generate-test-samples', taskSeries(generateTestSamplesTask)); gulp.task( 'simpleserver', taskSeries(generateTestSamplesTask, function () { const SERVER_ROOT = path.normalize(path.join(__dirname, '../')); createSimpleServer(SERVER_ROOT, 8080); createSimpleServer(SERVER_ROOT, 8088); }) );