diff --git a/CHANGELOG.md b/CHANGELOG.md index f2d33a3ac..8be973910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## v1.14.0 - 2018-12-23 + +### Features + +* **CLI**: Basic setup +* **CLI**: Export all Content +* **UI**: Edit asset tags and names in asset field. +* **UI**: Preview for SVG assets. +* **UI**: No stacked bars for API performance chart and a checkbox to toggle between stacked and non-stacked bars. +* **Users**: Invite users to an app even if they do not have an account yet. +* **Users**: Github authentication. +* **Client Library**: Integrated autogenerated management library. +* **Content**: Preview urls for schemas. +* **Content**: Button to show all input fields for localized fields. +* **Scripting**: Access to user roles. + +### Bugfixes + +* **API**: Several bugfixes for the JSON API and Swagger +* **UI**: Fixed dependencies and sortable lists. +* **UI**: Fixed disabled state for custom field editors. +* **Permissions**: Fixed duplicate permissions. + +### Refactorings + +* *Improved build performance for the Frontend. +* *Migration to Angular7 + ## v1.13.0 - 2018-12-08 ### Features diff --git a/README.md b/README.md index e0dca96d1..66aebe9c8 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Please join our community forum: https://support.squidex.io ## Status -Current Version 1.13.0. Roadmap: https://trello.com/b/KakM4F3S/squidex-roadmap +Current Version 1.14.0. Roadmap: https://trello.com/b/KakM4F3S/squidex-roadmap ## Prerequisites diff --git a/src/Squidex/app-config/tslint/linter.js b/src/Squidex/app-config/tslint/linter.js new file mode 100644 index 000000000..eb91a9f4a --- /dev/null +++ b/src/Squidex/app-config/tslint/linter.js @@ -0,0 +1,41 @@ +const { Runner, run } = require('tslint/lib/runner'); + +const options = JSON.parse(process.argv[2]) || {}; + +function logToParent(message) { + process.stdout.write(message); +} + +function runLinter(runnerOptions, write) { + const logTsLint = message => { + write('tslint:' + message) + }; + + if (run) { + const logger = { log: logTsLint, error: logTsLint }; + + return run(runnerOptions, logger); + } else if (Runner) { + return new Promise(resolve => { + new Runner(runnerOptions, { write: logTsLint }).run(resolve); + }); + } else { + write('tsinfo:Unable to launch tslint. No suitable runner found.'); + } +} + +logToParent('tsinfo:Linting started in separate process...'); + +const runnerOptions = Object.assign({ + exclude: [], + format: 'json' +}, options); + +runLinter(runnerOptions, logToParent) + .then(() => { + logToParent('tsinfo:Linting complete.'); + + process.exit(); + }).catch(error => { + logToParent(`tserror:Error starting linter: ${error}\n${error.stack}`); + }); \ No newline at end of file diff --git a/src/Squidex/app-config/tslint/plugin.js b/src/Squidex/app-config/tslint/plugin.js new file mode 100644 index 000000000..a62db13b6 --- /dev/null +++ b/src/Squidex/app-config/tslint/plugin.js @@ -0,0 +1,157 @@ +const path = require('path'); +const chalk = require('chalk'); +const { fork } = require('child_process'); + +function apply(options, compiler) { + let linterProcess; + let linterPromise; + let linterIteration = 0; + + function compileHook() { + if (linterProcess && linterProcess.kill) { + // Exits any outstanding child process if one exists + linterProcess.kill(); + } + + let { files = [] } = options; + + if (!files.length) { + process.stdout.write(chalk.yellow.bold('\n[tslint-plugin] No `files` option specified.\n')); + return; + } + + options.files = Array.isArray(files) ? files : [files]; + + // Spawn a child process to run the linter + linterProcess = fork(path.resolve(__dirname, 'linter.js'), [JSON.stringify(options)], { + silent: true + }); + + // Use the iteration to cancel previous promises. + linterIteration++; + + linterPromise = new Promise(resolve => { + const linterOutBuffer = []; + + linterProcess.stdout.on('data', (message) => { + if (message) { + const msg = message.toString(); + + for (let line of msg.split('\n')) { + const indexOfSeparator = line.indexOf(':'); + + if (indexOfSeparator > 0) { + const type = line.substring(0, indexOfSeparator); + const body = line.substring(indexOfSeparator + 1); + + switch (type) { + case 'tslint': { + const json = JSON.parse(body); + + for (let item of json) { + linterOutBuffer.push(item); + } + + break; + } + case 'tsinfo': { + process.stdout.write(chalk.cyan(`[tslint-plugin] ${body}\n`)); + break; + } + case 'tserror': { + process.stderr.write(chalk.red(`[tslint-plugin] ${body}\n`)); + break; + } + default: { + process.stderr.write(msg); + } + } + } else { + process.stdout.write(line); + } + } + } + }); + + linterProcess.once('exit', () => { + resolve({ iteration: linterIteration, out: linterOutBuffer }); + + // Clean up the linterProcess when finished + delete linterProcess; + }); + }); + } + + function createError(message) { + const error = new Error(message); + delete error.stackTrace; + return error; + } + + function emitHook(compilation, callback) { + if (linterPromise && options.waitForLinting) { + linterPromise.then(result => { + for (let r of result.out) { + const msg = `${r.name}:${r.startPosition.line + 1}:${r.startPosition.character + 1} [tslint] ${r.ruleName}: ${r.failure}`; + + if (r.ruleSeverity === 'ERROR' || options.warningsAsError) { + compilation.errors.push(createError(msg)); + } else { + compilation.warnings.push(createError(msg)); + } + } + + callback(); + }); + } else { + callback(); + } + } + + function doneHook() { + const currentIteration = linterIteration; + + if (linterPromise && !options.waitForLinting) { + let isResolved = false; + + linterPromise.then(result => { + isResolved = true; + + // If the iterations are not the same another process has already been started and we cancel these results. + if (result.iteration === currentIteration) { + for (let r of result.out) { + const msg = `${r.name}:${r.startPosition.line + 1}:${r.startPosition.character + 1} [tslint] ${r.ruleName}: ${r.failure}`; + + if (r.ruleSeverity === 'ERROR' || options.warningsAsError) { + process.stderr.write(chalk.red(msg + '\n')); + } else { + process.stdout.write(chalk.yellow(msg + '\n')); + } + } + } + }); + + if (!isResolved) { + process.stdout.write(chalk.cyan(`[tslint-plugin] Waiting for results...\n`)); + } + } + } + + if (compiler.hooks) { + // Webpack 4 + compiler.hooks.compile.tap('TSLintWebpackPlugin', compileHook); + compiler.hooks.emit.tapAsync('TSLintWebpackPlugin', emitHook); + compiler.hooks.done.tap('TSLintWebpackPlugin', doneHook); + } else { + // Backwards compatibility + compiler.plugin('compile', compileHook); + compiler.plugin('emit', emitHook); + compiler.plugin('done', doneHook); + } +} + +module.exports = function TSLintWebpackPlugin(options = {}) { + return { + apply: apply.bind(this, options) + }; +}; diff --git a/src/Squidex/app-config/webpack.config.js b/src/Squidex/app-config/webpack.config.js index 536f75309..e04247fba 100644 --- a/src/Squidex/app-config/webpack.config.js +++ b/src/Squidex/app-config/webpack.config.js @@ -6,9 +6,7 @@ const plugins = { // https://github.com/webpack-contrib/mini-css-extract-plugin MiniCssExtractPlugin: require('mini-css-extract-plugin'), // https://github.com/dividab/tsconfig-paths-webpack-plugin - TsconfigPathsPlugin: require('tsconfig-paths-webpack-plugin'), - // https://github.com/jrparish/tslint-webpack-plugin - TsLintPlugin: require('tslint-webpack-plugin') + TsconfigPathsPlugin: require('tsconfig-paths-webpack-plugin') }; module.exports = { @@ -132,14 +130,6 @@ module.exports = { } }), - new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/), - - new plugins.TsLintPlugin({ - files: ['./app/**/*.ts'], - /** - * Path to a configuration file. - */ - config: helpers.root('tslint.json') - }) + new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/) ] }; \ No newline at end of file diff --git a/src/Squidex/app-config/webpack.run.dev.js b/src/Squidex/app-config/webpack.run.dev.js index 4e54e431f..f633ba0fd 100644 --- a/src/Squidex/app-config/webpack.run.dev.js +++ b/src/Squidex/app-config/webpack.run.dev.js @@ -4,6 +4,11 @@ helpers = require('./helpers'), runConfig = require('./webpack.run.base.js'); +const plugins = { + // https://github.com/jrparish/tslint-webpack-plugin + TsLintPlugin: require('./tslint/plugin') +}; + module.exports = webpackMerge(runConfig, { mode: 'development', @@ -41,7 +46,15 @@ module.exports = webpackMerge(runConfig, { }, plugins: [ - new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)fesm5/, helpers.root('./src'), {}) + new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)fesm5/, helpers.root('./src'), {}), + + new plugins.TsLintPlugin({ + files: ['./app/**/*.ts'], + /** + * Path to a configuration file. + */ + config: helpers.root('tslint.json') + }) ], devServer: { diff --git a/src/Squidex/app-config/webpack.run.prod.js b/src/Squidex/app-config/webpack.run.prod.js index 9f113a622..e92dfb269 100644 --- a/src/Squidex/app-config/webpack.run.prod.js +++ b/src/Squidex/app-config/webpack.run.prod.js @@ -12,7 +12,9 @@ const plugins = { // https://github.com/webpack-contrib/mini-css-extract-plugin MiniCssExtractPlugin: require('mini-css-extract-plugin'), // https://github.com/NMFR/optimize-css-assets-webpack-plugin - OptimizeCSSAssetsPlugin: require("optimize-css-assets-webpack-plugin") + OptimizeCSSAssetsPlugin: require("optimize-css-assets-webpack-plugin"), + // https://github.com/jrparish/tslint-webpack-plugin + TsLintPlugin: require('./tslint/plugin') }; helpers.removeLoaders(runConfig, ['scss', 'ts']); @@ -99,6 +101,18 @@ module.exports = webpackMerge(runConfig, { skipSourceGeneration: false, tsConfigPath: './tsconfig.json' }), + + new plugins.TsLintPlugin({ + files: ['./app/**/*.ts'], + /** + * Path to a configuration file. + */ + config: helpers.root('tslint.json'), + /** + * Wait for linting and fail the build when linting error occur. + */ + waitForLinting: true + }) ], optimization: {