b13-rocket 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/help.js ADDED
@@ -0,0 +1,36 @@
1
+ import chalk from 'chalk';
2
+ import { Notification } from './notification';
3
+ import { sites } from './sites';
4
+
5
+ export async function help(args) {
6
+ const { siteConfig, hasSiteConfig } = await sites(args);
7
+ const siteOptions = `--site={ ${chalk.blackBright([...siteConfig.keys()].join(' | '))} }`;
8
+ const modeOptions = `--mode={ ${chalk.blackBright('development | production (default)')} }`;
9
+
10
+ const menus = {
11
+ main: `
12
+ ${chalk.greenBright('rocket [command] <options>')}
13
+ ${chalk.blueBright('build')} ..... build JS and CSS
14
+ ${siteOptions}
15
+ ${modeOptions}
16
+ --target={ ${chalk.blackBright('js | scss | less')} }
17
+ --bundleAnalyzer=true ${chalk.blackBright('// start "webpack-bundle-analyzer"')}
18
+
19
+ ${chalk.blueBright('hmr')} ....... start hot module replacment
20
+ ${siteOptions}
21
+ ${modeOptions}
22
+
23
+ ${chalk.blueBright('create')} .... create new config file for your TYPO3 site extension
24
+
25
+ ${chalk.blueBright('stylelint')} ... format SCSS via stylelint
26
+ ${siteOptions}
27
+ --fix={ false (default) | true }
28
+
29
+ ${chalk.blueBright('help')} ...... show help menu for a command
30
+
31
+ ${chalk.blueBright('version')} ... show package version
32
+ `,
33
+ };
34
+
35
+ Notification.log(menus.main);
36
+ }
package/src/hmr.js ADDED
@@ -0,0 +1,84 @@
1
+ import { sites } from './sites';
2
+ import path from 'path';
3
+ import webpack from 'webpack';
4
+ import webpackDevServer from 'webpack-dev-server';
5
+ import { default as webpackConfig } from './webpack/webpack.config.base';
6
+ import { env } from './utility/env';
7
+ import { VueLoaderPlugin } from 'vue-loader';
8
+ import { Notification } from './notification';
9
+
10
+ // on exit
11
+ process.on('SIGINT', () => {
12
+ env.disableHMR();
13
+ Notification.log('🚀 hdgdl...');
14
+ process.exit();
15
+ });
16
+
17
+ export async function hmr(args) {
18
+ const { siteConfig, hasSiteConfig } = await sites(args);
19
+
20
+ if (!args.sitesConfigPath) {
21
+ console.log(error('missing site build config'));
22
+ return;
23
+ }
24
+
25
+ const sitesConfigPath = args.sitesConfigPath;
26
+
27
+ if (hasSiteConfig(args.site)) {
28
+ const site = siteConfig.get(args.site);
29
+ const isDdev = process.env.DDEV_PRIMARY_URL;
30
+ const primaryUrl = new URL(isDdev ? process.env.DDEV_PRIMARY_URL : 'https://127.0.0.1');
31
+ const port = isDdev ? 8088 : 8080;
32
+ const wpConfig = webpackConfig(site.config, sitesConfigPath, 'development');
33
+
34
+ let publicPath = `http://127.0.0.1:${port}/`;
35
+ if (site.config.js.path.public) {
36
+ publicPath = `https://${primaryUrl.hostname}:${port}${site.config.js.path.public}`;
37
+ }
38
+
39
+ wpConfig.output = {
40
+ filename: '[name]-hmr.js',
41
+ libraryTarget: 'umd',
42
+ library: site.config.js.filename.main,
43
+ umdNamedDefine: false,
44
+ path: path.resolve(__dirname, site.config.js.path.target),
45
+ publicPath,
46
+ };
47
+
48
+ wpConfig.plugins.push(
49
+ new VueLoaderPlugin(),
50
+ new webpack.DefinePlugin({
51
+ B13_HMR_ENABLED: JSON.stringify(true),
52
+ }),
53
+ {
54
+ apply: (compiler) => {
55
+ compiler.hooks.watchRun.tap('enableHMR', (compilation) => {
56
+ env.enableHMR();
57
+ });
58
+ },
59
+ },
60
+ );
61
+
62
+ const devServer = new webpackDevServer({
63
+ headers: { 'Access-Control-Allow-Origin': '*' },
64
+ historyApiFallback: true,
65
+ server: isDdev ? 'http' : 'https',
66
+ allowedHosts: 'all',
67
+ port,
68
+ hot: true,
69
+ client: {
70
+ webSocketURL: {
71
+ hostname: primaryUrl.hostname,
72
+ pathname: '/ws',
73
+ port,
74
+ protocol: 'wss',
75
+ },
76
+ },
77
+ }, webpack(wpConfig));
78
+
79
+ (async() => {
80
+ await devServer.start();
81
+ console.log(`Webpack Dev Server running at https://${primaryUrl.hostname}:${port}`);
82
+ })();
83
+ }
84
+ }
@@ -0,0 +1,55 @@
1
+ import chalk from 'chalk';
2
+
3
+ const bold = chalk.bold;
4
+ const success = chalk.bold.green;
5
+ const warn = chalk.bold.yellow;
6
+ const error = chalk.bold.red;
7
+ const blue = chalk.bold.blue;
8
+
9
+ export class Notification {
10
+
11
+ static log(msg = '') {
12
+ if (msg.length > 0) {
13
+ console.log(msg);
14
+ }
15
+ }
16
+
17
+ static bold(msg = '') {
18
+ if (msg.length > 0) {
19
+ console.log(bold(msg));
20
+ }
21
+ }
22
+
23
+ static warn(msg = '') {
24
+ if (msg.length > 0) {
25
+ console.warn(warn(`Warning: ${msg}`));
26
+ }
27
+ }
28
+
29
+ static success(msg = '') {
30
+ if (msg.length > 0) {
31
+ console.log(success(`Success: ${msg}`));
32
+ }
33
+ }
34
+
35
+ static error(msg = '', e) {
36
+ if (msg.length > 0) {
37
+ console.error(error(`Error: ${msg}`));
38
+ }
39
+ if (e) {
40
+ console.error(e);
41
+ }
42
+ }
43
+
44
+ static taskInfo(msg = '', siteName = '') {
45
+ if (msg.length > 0) {
46
+ let notification = `\n${msg}`;
47
+ if (siteName.length > 0) {
48
+ notification += ` // ${blue(siteName)}`;
49
+ }
50
+
51
+ console.log(notification);
52
+ console.log('------------');
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,104 @@
1
+ import chalk from 'chalk';
2
+ import { Notification } from '../notification';
3
+ import size from 'filesize';
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+ import less from 'less';
7
+
8
+ const getDirName = path.dirname;
9
+ const success = chalk.bold.green;
10
+
11
+ const reflect = p => p.then(v => ({ v, status: 'fulfilled' }),
12
+ e => ({ e, status: 'rejected' }));
13
+
14
+ /**
15
+ * LESS build
16
+ */
17
+ export class LessBuild {
18
+
19
+ constructor(_config = {}, _sitesConfigPath = './') {
20
+ this.config = _config;
21
+ this.sitesConfigPath = _sitesConfigPath;
22
+ }
23
+
24
+ /**
25
+ * build
26
+ * process all LESS file
27
+ * @returns {void}
28
+ */
29
+ build() {
30
+ const fileProcessing = [];
31
+ for (const [dest, src] of Object.entries(this.config.less.files)) {
32
+ fileProcessing.push(this.processLessFile({
33
+ src: path.resolve(this.sitesConfigPath, src),
34
+ dest: path.resolve(this.sitesConfigPath, dest),
35
+ }));
36
+ }
37
+
38
+ // wait until all async processed less files are ready
39
+ Promise.all(fileProcessing.map(reflect)).then((results) => {
40
+ const success = results.filter(x => x.status === 'fulfilled');
41
+ Notification.taskInfo('Process LESS file(s)', this.config.name);
42
+ success.forEach(item => console.log(item.v));
43
+ });
44
+ }
45
+
46
+ /**
47
+ * process less file
48
+ * @param {Object} options to process less file
49
+ * @returns {void}
50
+ */
51
+ processLessFile(options = {}) {
52
+ return new Promise((resolve, reject) => {
53
+ const targetFileName = path.basename(options.dest);
54
+
55
+ // render the result
56
+ less.render(fs.readFileSync(options.src).toString(), {
57
+ filename: path.resolve(options.src),
58
+ compress: true,
59
+ }).then((result) => {
60
+ if (!fs.existsSync(getDirName(options.dest))) {
61
+ fs.mkdirSync(getDirName(options.dest));
62
+ }
63
+
64
+ // write the result to file
65
+ fs.writeFile(options.dest, result.css, (error) => {
66
+ if (error) {
67
+ Notification.error(`Can't write file: ${options.dest} error: ${error}`);
68
+ }
69
+ fs.stat(options.dest, (err, stats) => {
70
+ resolve(`${success(targetFileName)} ${size(stats.size)}`);
71
+ });
72
+ });
73
+ },
74
+ (err) => {
75
+ Notification.error('less build failed', err);
76
+ process.exit(1);
77
+ });
78
+ });
79
+ }
80
+
81
+ /**
82
+ * make sure CSS public directory exists and is clean
83
+ * @returns {void}
84
+ */
85
+ clear() {
86
+ const cssPath = path.resolve(this.sitesConfigPath, this.config.less.path.target);
87
+ if (!fs.existsSync(cssPath)) {
88
+ fs.mkdirSync(cssPath);
89
+ }
90
+
91
+ fs.readdirSync(cssPath).forEach((file) => {
92
+
93
+ // don't remove critical css files
94
+ if (!file.match(/critical-/g)) {
95
+ fs.unlinkSync(path.resolve(this.sitesConfigPath, `${this.config.less.path.target}/${file}`), (err) => {
96
+ if (err) {
97
+ Notification.error(err);
98
+ throw err;
99
+ }
100
+ });
101
+ }
102
+ });
103
+ }
104
+ }
@@ -0,0 +1,186 @@
1
+ import chalk from 'chalk';
2
+ import { Notification } from '../notification';
3
+ import size from 'filesize';
4
+ import postcss from 'postcss';
5
+ import path from 'path';
6
+ import fs from 'fs';
7
+ import sass from 'sass';
8
+ import stylelint from 'stylelint';
9
+ import stylelintConfig from 'b13-stylelint-config';
10
+
11
+ const getDirName = path.dirname;
12
+ const success = chalk.bold.green;
13
+ const bold = chalk.bold;
14
+
15
+ const reflect = p => p.then(v => ({ v, status: 'fulfilled' }),
16
+ e => ({ e, status: 'rejected' }));
17
+
18
+ /**
19
+ * SCSS build
20
+ */
21
+ export class ScssBuild {
22
+
23
+ constructor(_config = {}, _sitesConfigPath = './') {
24
+ this.config = _config;
25
+ this.sitesConfigPath = _sitesConfigPath;
26
+ }
27
+
28
+ /**
29
+ * build
30
+ * process all SCSS file
31
+ * @returns {void}
32
+ */
33
+ build() {
34
+ const fileProcessing = [];
35
+ for (const [dest, src] of Object.entries(this.config.scss.files)) {
36
+ fileProcessing.push(this.processScssFile({
37
+ src: path.resolve(this.sitesConfigPath, src),
38
+ dest: path.resolve(this.sitesConfigPath, dest),
39
+ }));
40
+ }
41
+
42
+ // wait until all async processed scss files are ready
43
+ Promise.all(fileProcessing.map(reflect)).then((results) => {
44
+ const success = results.filter(x => x.status === 'fulfilled');
45
+ Notification.taskInfo('Process SCSS file(s)', this.config.name);
46
+ success.forEach(item => console.log(item.v));
47
+ });
48
+ }
49
+
50
+ /**
51
+ * stylelint fixer
52
+ */
53
+ stylelintFixer(fix = false) {
54
+ const files = new Set();
55
+ Object.entries(this.config.scss.files).forEach((filePath) => {
56
+ const b = filePath[1].replace(path.basename(filePath[1]), '**/*.scss');
57
+ const c = path.resolve(this.sitesConfigPath, b);
58
+ files.add(c);
59
+ });
60
+
61
+ Notification.log(`\n${bold(this.config.name)} scss file path(s):`);
62
+ [...files].forEach(file => Notification.log(`- ${file}`));
63
+
64
+ stylelint.lint({
65
+ files: [...files],
66
+ config: stylelintConfig,
67
+ fix,
68
+ })
69
+ .then((data) => {
70
+
71
+ if (data.errored) {
72
+ const resultsWithErrors = data.results.filter(item => item.errored);
73
+ Notification.log('\n');
74
+ Notification.error(`Found ${resultsWithErrors.length} stylelint errors!`);
75
+ resultsWithErrors.forEach(result => {
76
+ Notification.log('\n');
77
+ Notification.log(result.source);
78
+ result.warnings.forEach(warning => {
79
+ Notification.error(`- ${warning.text} at line: ${warning.line}`);
80
+ });
81
+ });
82
+
83
+ process.exitCode = 1;
84
+ Notification.log('\n');
85
+ Notification.error('stylelint failed');
86
+ } else {
87
+ Notification.log('\n');
88
+ Notification.success('stylelint successfull');
89
+ }
90
+ })
91
+ .catch((err) => {
92
+ process.exitCode = 1;
93
+ Notification.log('\n');
94
+ Notification.error(`stylelint failed: ${err}`);
95
+ });
96
+ }
97
+
98
+ /**
99
+ * process scss file
100
+ * @param {Object} options to process scss file
101
+ * @returns {void}
102
+ */
103
+ processScssFile(options = {}) {
104
+ return new Promise((resolve, reject) => {
105
+ const targetFileName = path.basename(options.dest);
106
+
107
+ // render the result
108
+ let result;
109
+ try {
110
+ result = sass.renderSync({
111
+ file: options.src,
112
+ outputStyle: options.style,
113
+ style: 'compressed',
114
+ });
115
+ } catch (e) {
116
+ Notification.error('scss build failed', e);
117
+ process.exit(1);
118
+ }
119
+
120
+ const postcssPlugins = [
121
+ require('autoprefixer')(),
122
+ ];
123
+
124
+ // group media queries
125
+ if (this.config.scss.groupMediaQueries) {
126
+ postcssPlugins.push(require('css-mqpacker')());
127
+ }
128
+
129
+ // group media queries
130
+ if (this.config.scss.sortMediaQueries) {
131
+ postcssPlugins.push(require('postcss-sort-media-queries')());
132
+ }
133
+
134
+ postcssPlugins.push(
135
+ require('cssnano')({ preset: 'default' }),
136
+ );
137
+
138
+ // post process the css files
139
+ // see plugins and options: https://github.com/postcss/postcss
140
+ postcss(postcssPlugins).process(
141
+ result.css.toString(),
142
+ { from: undefined },
143
+ ).then(result => {
144
+
145
+ if (!fs.existsSync(getDirName(options.dest))) {
146
+ fs.mkdirSync(getDirName(options.dest), { recursive: true });
147
+ }
148
+
149
+ // write the result to file
150
+ fs.writeFile(options.dest, result.css, (error) => {
151
+ if (error) {
152
+ Notification.error(`Can't write file: ${options.dest} error: ${error}`);
153
+ }
154
+
155
+ fs.stat(options.dest, (err, stats) => {
156
+ resolve(`${success(targetFileName)} ${size(stats.size)}`);
157
+ });
158
+ });
159
+ });
160
+ });
161
+ }
162
+
163
+ /**
164
+ * make sure CSS public directory exists and is clean
165
+ * @returns {void}
166
+ */
167
+ clear() {
168
+ const cssPath = path.resolve(this.sitesConfigPath, this.config.scss.path.target);
169
+ if (!fs.existsSync(cssPath)) {
170
+ fs.mkdirSync(cssPath, { recursive: true });
171
+ }
172
+
173
+ fs.readdirSync(cssPath).forEach((file) => {
174
+
175
+ // don't remove critical css files
176
+ if (!file.match(/critical-/g)) {
177
+ fs.unlinkSync(path.resolve(this.sitesConfigPath, `${this.config.scss.path.target}/${file}`), (err) => {
178
+ if (err) {
179
+ Notification.error(err);
180
+ throw err;
181
+ }
182
+ });
183
+ }
184
+ });
185
+ }
186
+ }
package/src/sites.js ADDED
@@ -0,0 +1,53 @@
1
+ import fs from 'fs';
2
+ import { Notification } from './notification';
3
+
4
+ class Config {
5
+ constructor(config = {}, filename = '') {
6
+ this.config = config;
7
+ this.filename = filename;
8
+ }
9
+
10
+ hasJs() {
11
+ return this.config.js && Object.keys(this.config.js).length > 0;
12
+ }
13
+
14
+ hasScss() {
15
+ return this.config.scss && Object.keys(this.config.scss).length > 0;
16
+ }
17
+
18
+ hasLess() {
19
+ return this.config.less && Object.keys(this.config.less).length > 0;
20
+ }
21
+ }
22
+
23
+
24
+ export async function sites(args) {
25
+ const sites = new Map();
26
+ const sitesConfigPath = args.sitesConfigPath;
27
+
28
+ if (!sitesConfigPath || !fs.existsSync(sitesConfigPath)) {
29
+ Notification.error('missing site build config');
30
+ return sites;
31
+ }
32
+
33
+ fs.readdirSync(
34
+ sitesConfigPath,
35
+ { withFileTypes: true },
36
+ ).forEach((file) => {
37
+ if (file.name.match(/(site_|\.package)/)) {
38
+ const config = require(`${sitesConfigPath}/${file.name}`);
39
+ sites.set(config.name, new Config(config, file.name));
40
+ }
41
+ });
42
+
43
+ const hasSiteConfig = (name = '') => {
44
+ if (!sites.has(name)) {
45
+ Notification.error(`Site ${name} not found`);
46
+ const availableSitesOptions = Array.from(sites.keys()).join(', ');
47
+ Notification.bold(`Available options: ${availableSitesOptions}`);
48
+ }
49
+ return sites.has(name);
50
+ }
51
+
52
+ return { siteConfig: sites, hasSiteConfig };
53
+ }
@@ -0,0 +1,33 @@
1
+ import { sites } from './sites';
2
+ import { ScssBuild } from './scss/scssBuild';
3
+ import { Notification } from './notification';
4
+
5
+ export async function stylelint(args) {
6
+ const { siteConfig, hasSiteConfig } = await sites(args);
7
+
8
+ if (!args.sitesConfigPath) {
9
+ Notification.error('missing site build config');
10
+ return;
11
+ }
12
+
13
+ const sitesConfigPath = args.sitesConfigPath;
14
+ const fix = args.fix === 'true';
15
+
16
+ Notification.taskInfo('Run Stylelint fixer');
17
+
18
+ if (args.site && siteConfig.has(args.site)) {
19
+
20
+ // cleanup single site
21
+ const site = siteConfig.get(args.site);
22
+ const scss = new ScssBuild(site.config, sitesConfigPath);
23
+ scss.stylelintFixer(fix);
24
+
25
+ } else {
26
+
27
+ // cleanup all sites
28
+ for (const [key, value] of siteConfig.entries()) {
29
+ const scss = new ScssBuild(value.config, sitesConfigPath);
30
+ scss.stylelintFixer(fix);
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,49 @@
1
+ import fs from 'fs';
2
+ import { Notification } from './../notification';
3
+ import path from 'path';
4
+
5
+ const ENV_FILE_PATH = path.resolve(__dirname, './../../../../../../.env');
6
+ const ENV_HRM_VAR_NAME = 'TYPO3_TS_ENABLE_HMR';
7
+ const ENV_HRM_VAR_NAME_REGEX = new RegExp(/TYPO3_TS_ENABLE_HMR="[0,1]"/, 'g');
8
+
9
+ class ENV {
10
+
11
+ constructor() {
12
+ this.hasEnv = fs.existsSync(ENV_FILE_PATH);
13
+ if (!this.hasEnv) {
14
+ Notification.warn(`No .env file found at: ${ENV_FILE_PATH}`);
15
+ }
16
+ }
17
+
18
+ enableHMR() {
19
+ if (this.hasEnv) {
20
+ let content = this.getContent();
21
+ if (!content.match(ENV_HRM_VAR_NAME_REGEX)) {
22
+ content += `
23
+ ${ENV_HRM_VAR_NAME}="1"
24
+ `;
25
+ }
26
+ this.updateFile(content.replace(ENV_HRM_VAR_NAME_REGEX, `${ENV_HRM_VAR_NAME}="1"`));
27
+ }
28
+ }
29
+
30
+ disableHMR() {
31
+ if (this.hasEnv) {
32
+ const content = this.getContent();
33
+ this.updateFile(content.replace(ENV_HRM_VAR_NAME_REGEX, `${ENV_HRM_VAR_NAME}="0"`));
34
+ }
35
+ }
36
+
37
+ updateFile(content = '') {
38
+ if (content.length > 0) {
39
+ fs.writeFileSync(ENV_FILE_PATH, content);
40
+ }
41
+ }
42
+
43
+ getContent() {
44
+ return fs.readFileSync(ENV_FILE_PATH, 'utf8');
45
+ }
46
+ }
47
+
48
+ const env = new ENV();
49
+ export { env };
package/src/version.js ADDED
@@ -0,0 +1,6 @@
1
+ import { Notification } from './notification';
2
+
3
+ export async function version() {
4
+ const packagejson = require('./../package.json');
5
+ Notification.log(packagejson.version);
6
+ }