balm-core 4.1.0 → 4.2.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/README.md +20 -29
- package/lib/plugins/imagemin.js +10 -18
- package/lib/plugins/less.js +1 -0
- package/lib/plugins/replace.js +2 -2
- package/lib/plugins/sass.js +80 -75
- package/lib/plugins/sftp.js +9 -5
- package/package.json +26 -26
- package/tslib/plugins/imagemin.js +9 -15
- package/tslib/plugins/sass.js +52 -53
- package/tslib/plugins/sftp.js +24 -5
package/README.md
CHANGED
|
@@ -5,11 +5,8 @@
|
|
|
5
5
|
<br>
|
|
6
6
|
<br>
|
|
7
7
|
|
|
8
|
-
[![NPM version][balm-image]][balm-url]
|
|
8
|
+
[![NPM version][balm-core-image]][balm-core-url]
|
|
9
9
|
[![License][license-image]][license-url]
|
|
10
|
-
[![Dependency Status][deps-image]][deps-url]
|
|
11
|
-
[![Build Status][travis-image]][travis-url]
|
|
12
|
-
[![Build Status][appveyor-image]][appveyor-url]
|
|
13
10
|
[![Coverage Percentage][cover-image]][cover-url]
|
|
14
11
|
|
|
15
12
|
<h1>BalmJS</h1>
|
|
@@ -87,15 +84,15 @@ npm init -y
|
|
|
87
84
|
### 1. Installing `balm`
|
|
88
85
|
|
|
89
86
|
```sh
|
|
90
|
-
yarn global add balm-core
|
|
91
|
-
yarn add -D balm
|
|
87
|
+
yarn global add balm-core@next
|
|
88
|
+
yarn add -D balm@next
|
|
92
89
|
```
|
|
93
90
|
|
|
94
91
|
OR
|
|
95
92
|
|
|
96
93
|
```sh
|
|
97
|
-
npm install -g balm-core
|
|
98
|
-
npm install -D balm
|
|
94
|
+
npm install -g balm-core@next
|
|
95
|
+
npm install -D balm@next
|
|
99
96
|
```
|
|
100
97
|
|
|
101
98
|
> We currently recommend using [Yarn](https://yarnpkg.com/en/docs/install) instead of npm.
|
|
@@ -153,15 +150,15 @@ To download [example](https://balm.js.org/balm-example.zip) and try it, visit [b
|
|
|
153
150
|
|
|
154
151
|
## Ecosystem
|
|
155
152
|
|
|
156
|
-
| Project | Status | Description
|
|
157
|
-
| ----------------------------------------------------- | ------------------------------------------------------ |
|
|
158
|
-
| **[balm-core](https://github.com/balmjs/balm)** | [![NPM version][balm-core-image]][balm-core-url] | :black_joker: BalmJS compiler core (required for `balm 3.0
|
|
159
|
-
| **[balm](https://github.com/balmjs/balm)** | [![NPM version][balm-image]][balm-url] | :black_joker: BalmJS runtime core
|
|
160
|
-
| **[balm-cli](https://github.com/balmjs/balm-cli)** | [![NPM version][balm-cli-image]][balm-cli-url] | :spades: BalmJS scaffolding tool
|
|
161
|
-
| [balm-gui](https://github.com/balmjs/balm-gui) | N/A | :clubs: GUI for BalmJS
|
|
162
|
-
| [balm-ui-lite](https://github.com/balmjs/ui-vue-lite) | [![NPM version][balm-ui-lite-image]][balm-ui-lite-url] | :hearts: Material Design Lite + Vue
|
|
163
|
-
| **[balm-ui](https://github.com/balmjs/ui-vue)** | [![NPM version][balm-ui-image]][balm-ui-url] | :diamonds: Next Generation Material UI for Vue.js
|
|
164
|
-
| [balm-scroll](https://github.com/balmjs/balm-scroll) | [![NPM version][balm-scroll-image]][balm-scroll-url] | :scroll: Smooth scrolling for Vue.js
|
|
153
|
+
| Project | Status | Description |
|
|
154
|
+
| ----------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------- |
|
|
155
|
+
| **[balm-core](https://github.com/balmjs/balm)** | [![NPM version][balm-core-image]][balm-core-url] | :black_joker: BalmJS compiler core (required for `balm 3.0+`) |
|
|
156
|
+
| **[balm](https://github.com/balmjs/balm)** | [![NPM version][balm-image]][balm-url] | :black_joker: BalmJS runtime core |
|
|
157
|
+
| **[balm-cli](https://github.com/balmjs/balm-cli)** | [![NPM version][balm-cli-image]][balm-cli-url] | :spades: BalmJS scaffolding tool |
|
|
158
|
+
| [balm-gui](https://github.com/balmjs/balm-gui) | N/A | :clubs: GUI for BalmJS |
|
|
159
|
+
| [balm-ui-lite](https://github.com/balmjs/ui-vue-lite) | [![NPM version][balm-ui-lite-image]][balm-ui-lite-url] | :hearts: Material Design Lite + Vue |
|
|
160
|
+
| **[balm-ui](https://github.com/balmjs/ui-vue)** | [![NPM version][balm-ui-image]][balm-ui-url] | :diamonds: Next Generation Material UI for Vue.js |
|
|
161
|
+
| [balm-scroll](https://github.com/balmjs/balm-scroll) | [![NPM version][balm-scroll-image]][balm-scroll-url] | :scroll: Smooth scrolling for Vue.js |
|
|
165
162
|
|
|
166
163
|
## Contributing
|
|
167
164
|
|
|
@@ -173,25 +170,19 @@ We'd love for you to contribute and make BalmJS even better than it is today! Pl
|
|
|
173
170
|
|
|
174
171
|
© 2016-present, [Elf-mousE](http://elf-mouse.me/)
|
|
175
172
|
|
|
176
|
-
[balm-core-image]: https://
|
|
173
|
+
[balm-core-image]: https://img.shields.io/npm/v/balm-core/next.svg
|
|
177
174
|
[balm-core-url]: https://npmjs.org/package/balm-core
|
|
178
|
-
[balm-image]: https://
|
|
175
|
+
[balm-image]: https://img.shields.io/npm/v/balm/next.svg
|
|
179
176
|
[balm-url]: https://npmjs.org/package/balm
|
|
180
177
|
[license-image]: https://img.shields.io/npm/l/balm-core.svg?sanitize=true
|
|
181
178
|
[license-url]: https://www.npmjs.com/package/balm-core
|
|
182
|
-
[deps-image]: https://david-dm.org/balmjs/balm.svg?path=packages/balm-core&theme=shields.io
|
|
183
|
-
[deps-url]: https://david-dm.org/balmjs/balm?path=packages/balm-core
|
|
184
|
-
[travis-image]: https://travis-ci.com/balmjs/balm.svg?branch=master
|
|
185
|
-
[travis-url]: https://travis-ci.com/balmjs/balm
|
|
186
|
-
[appveyor-image]: https://ci.appveyor.com/api/projects/status/github/balmjs/balm?svg=true
|
|
187
|
-
[appveyor-url]: https://ci.appveyor.com/project/balmjs/balm
|
|
188
179
|
[cover-image]: https://coveralls.io/repos/balmjs/balm/badge.svg
|
|
189
180
|
[cover-url]: https://coveralls.io/r/balmjs/balm
|
|
190
|
-
[balm-cli-image]: https://
|
|
181
|
+
[balm-cli-image]: https://img.shields.io/npm/v/balm-cli.svg
|
|
191
182
|
[balm-cli-url]: https://npmjs.org/package/balm-cli
|
|
192
|
-
[balm-ui-lite-image]: https://
|
|
183
|
+
[balm-ui-lite-image]: https://img.shields.io/npm/v/balm-ui-lite.svg
|
|
193
184
|
[balm-ui-lite-url]: https://npmjs.org/package/balm-ui-lite
|
|
194
|
-
[balm-ui-image]: https://
|
|
185
|
+
[balm-ui-image]: https://img.shields.io/npm/v/balm-ui.svg
|
|
195
186
|
[balm-ui-url]: https://npmjs.org/package/balm-ui
|
|
196
|
-
[balm-scroll-image]: https://
|
|
187
|
+
[balm-scroll-image]: https://img.shields.io/npm/v/balm-scroll.svg
|
|
197
188
|
[balm-scroll-url]: https://npmjs.org/package/balm-scroll
|
package/lib/plugins/imagemin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Reference `gulp-imagemin@
|
|
1
|
+
// Reference `gulp-imagemin@8.0.0`
|
|
2
2
|
import imagemin from 'imagemin';
|
|
3
3
|
import prettyBytes from 'pretty-bytes';
|
|
4
4
|
import through2Concurrent from 'through2-concurrent';
|
|
@@ -15,18 +15,10 @@ const loadPlugin = (plugin, ...args) => {
|
|
|
15
15
|
|
|
16
16
|
const exposePlugin = plugin => (...args) => loadPlugin(plugin, ...args);
|
|
17
17
|
|
|
18
|
-
const getDefaultPlugins = () => defaultPlugins.
|
|
19
|
-
const instance = loadPlugin(plugin);
|
|
20
|
-
|
|
21
|
-
if (!instance) {
|
|
22
|
-
return plugins;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return plugins.concat(instance);
|
|
26
|
-
}, []);
|
|
18
|
+
const getDefaultPlugins = () => defaultPlugins.flatMap(plugin => loadPlugin(plugin));
|
|
27
19
|
|
|
28
20
|
const gulpImagemin = customPlugins => {
|
|
29
|
-
const validExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.svg'];
|
|
21
|
+
const validExtensions = new Set(['.jpg', '.jpeg', '.png', '.gif', '.svg']);
|
|
30
22
|
let totalBytes = 0;
|
|
31
23
|
let totalSavedBytes = 0;
|
|
32
24
|
let totalFiles = 0;
|
|
@@ -42,7 +34,7 @@ const gulpImagemin = customPlugins => {
|
|
|
42
34
|
return;
|
|
43
35
|
}
|
|
44
36
|
|
|
45
|
-
if (!validExtensions.
|
|
37
|
+
if (!validExtensions.has(node.path.extname(file.path).toLowerCase())) {
|
|
46
38
|
BalmJS.logger.info(PLUGIN_NAME, `Skipping unsupported image "${file.relative}"`);
|
|
47
39
|
callback(null, file);
|
|
48
40
|
return;
|
|
@@ -59,8 +51,8 @@ const gulpImagemin = customPlugins => {
|
|
|
59
51
|
const optimizedSize = data.length;
|
|
60
52
|
const saved = originalSize - optimizedSize;
|
|
61
53
|
const percent = originalSize > 0 ? saved / originalSize * 100 : 0;
|
|
62
|
-
const
|
|
63
|
-
const
|
|
54
|
+
const savedMessage = `saved ${prettyBytes(saved)} - ${percent.toFixed(1).replace(/\.0$/, '')}%`;
|
|
55
|
+
const message = saved > 0 ? savedMessage : 'already optimized';
|
|
64
56
|
|
|
65
57
|
if (saved > 0) {
|
|
66
58
|
totalBytes += originalSize;
|
|
@@ -68,7 +60,7 @@ const gulpImagemin = customPlugins => {
|
|
|
68
60
|
totalFiles++;
|
|
69
61
|
}
|
|
70
62
|
|
|
71
|
-
BalmJS.logger.debug(PLUGIN_NAME, `${file.relative} (${
|
|
63
|
+
BalmJS.logger.debug(PLUGIN_NAME, `${file.relative} (${message})`);
|
|
72
64
|
file.contents = data;
|
|
73
65
|
callback(null, file);
|
|
74
66
|
} catch (error) {
|
|
@@ -81,13 +73,13 @@ const gulpImagemin = customPlugins => {
|
|
|
81
73
|
|
|
82
74
|
function flush(callback) {
|
|
83
75
|
const percent = totalBytes > 0 ? totalSavedBytes / totalBytes * 100 : 0;
|
|
84
|
-
let
|
|
76
|
+
let message = `Minified ${totalFiles} image${totalFiles > 1 ? 's' : ''}`;
|
|
85
77
|
|
|
86
78
|
if (totalFiles > 0) {
|
|
87
|
-
|
|
79
|
+
message += ` (saved ${prettyBytes(totalSavedBytes)} - ${percent.toFixed(1).replace(/\.0$/, '')}%)`;
|
|
88
80
|
}
|
|
89
81
|
|
|
90
|
-
BalmJS.logger.info(PLUGIN_NAME,
|
|
82
|
+
BalmJS.logger.info(PLUGIN_NAME, message);
|
|
91
83
|
callback();
|
|
92
84
|
}
|
|
93
85
|
|
package/lib/plugins/less.js
CHANGED
package/lib/plugins/replace.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object);
|
|
1
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
2
2
|
|
|
3
|
-
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]
|
|
3
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
4
4
|
|
|
5
5
|
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
6
6
|
|
package/lib/plugins/sass.js
CHANGED
|
@@ -1,32 +1,99 @@
|
|
|
1
|
+
// Reference `gulp-rename@5.1.0`
|
|
2
|
+
import { Transform } from 'node:stream';
|
|
1
3
|
import sass from 'sass';
|
|
2
4
|
import replaceExtension from 'replace-ext';
|
|
3
5
|
import stripAnsi from 'strip-ansi';
|
|
4
6
|
import applySourceMap from 'vinyl-sourcemaps-apply';
|
|
7
|
+
import ansiColors from 'ansi-colors';
|
|
5
8
|
const PLUGIN_NAME = 'sass';
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
const transformObj = transform => new Transform({
|
|
11
|
+
transform,
|
|
12
|
+
objectMode: true
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Handles returning the file to the stream
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const filePush = (file, sassObject, callback) => {
|
|
20
|
+
// Build Source Maps!
|
|
21
|
+
if (sassObject.map) {
|
|
22
|
+
// Transform map into JSON
|
|
23
|
+
const sassMap = JSON.parse(sassObject.map.toString()); // Grab the stdout and transform it into stdin
|
|
24
|
+
|
|
25
|
+
const sassMapFile = sassMap.file.replace(/^stdout$/, 'stdin'); // Grab the base filename that's being worked on
|
|
26
|
+
|
|
27
|
+
const sassFileSrc = file.relative; // Grab the path portion of the file that's being worked on
|
|
28
|
+
|
|
29
|
+
const sassFileSrcPath = node.path.dirname(sassFileSrc);
|
|
30
|
+
|
|
31
|
+
if (sassFileSrcPath) {
|
|
32
|
+
const sourceFileIndex = sassMap.sources.indexOf(sassMapFile); // Prepend the path to all files in the sources array except the file that's being worked on
|
|
33
|
+
|
|
34
|
+
sassMap.sources = sassMap.sources.map((source, index) => index === sourceFileIndex ? source : node.path.join(sassFileSrcPath, source));
|
|
35
|
+
} // Remove 'stdin' from souces and replace with filenames!
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
sassMap.sources = sassMap.sources.filter(src => src !== 'stdin' && src); // Replace the map file with the original filename (but new extension)
|
|
39
|
+
|
|
40
|
+
sassMap.file = replaceExtension(sassFileSrc, '.css'); // Apply the map
|
|
41
|
+
|
|
42
|
+
applySourceMap(file, sassMap);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
file.contents = sassObject.css;
|
|
46
|
+
file.path = replaceExtension(file.path, '.css');
|
|
47
|
+
|
|
48
|
+
if (file.stat) {
|
|
49
|
+
file.stat.atime = file.stat.mtime = file.stat.ctime = new Date();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
callback(null, file);
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Handles error message
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
const handleError = (error, file, callback) => {
|
|
60
|
+
const filePath = (error.file === 'stdin' ? file.path : error.file) || file.path;
|
|
61
|
+
const relativePath = node.path.relative(process.cwd(), filePath);
|
|
62
|
+
const message = `${ansiColors.underline(relativePath)}\n${error.formatted}`;
|
|
63
|
+
error.messageFormatted = message;
|
|
64
|
+
error.messageOriginal = error.message;
|
|
65
|
+
error.message = stripAnsi(message);
|
|
66
|
+
error.relativePath = relativePath;
|
|
67
|
+
return callback(new PluginError(PLUGIN_NAME, error));
|
|
68
|
+
}; //////////////////////////////
|
|
8
69
|
// Main Gulp Sass function
|
|
9
70
|
//////////////////////////////
|
|
10
|
-
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
const gulpSass = options => transformObj((file, encoding, callback) => {
|
|
11
74
|
if (file.isNull()) {
|
|
12
|
-
|
|
75
|
+
callback(null, file);
|
|
76
|
+
return;
|
|
13
77
|
}
|
|
14
78
|
|
|
15
79
|
if (file.isStream()) {
|
|
16
|
-
|
|
80
|
+
callback(new PluginError(PLUGIN_NAME, 'Streaming not supported'));
|
|
81
|
+
return;
|
|
17
82
|
}
|
|
18
83
|
|
|
19
|
-
if (node.path.basename(file.path).
|
|
20
|
-
|
|
84
|
+
if (node.path.basename(file.path).startsWith('_')) {
|
|
85
|
+
callback();
|
|
86
|
+
return;
|
|
21
87
|
}
|
|
22
88
|
|
|
23
89
|
if (!file.contents.length) {
|
|
24
90
|
file.path = replaceExtension(file.path, '.css');
|
|
25
|
-
|
|
91
|
+
callback(null, file);
|
|
92
|
+
return;
|
|
26
93
|
}
|
|
27
94
|
|
|
28
95
|
const opts = options || {};
|
|
29
|
-
opts.data = file.contents.toString(); //
|
|
96
|
+
opts.data = file.contents.toString(); // We set the file path here so that libsass can correctly resolve import paths
|
|
30
97
|
|
|
31
98
|
opts.file = file.path; // Ensure `indentedSyntax` is true if a `.sass` file
|
|
32
99
|
|
|
@@ -43,85 +110,23 @@ const gulpSass = options => through2.obj((file, encoding, cb) => {
|
|
|
43
110
|
opts.includePaths = [];
|
|
44
111
|
}
|
|
45
112
|
|
|
46
|
-
opts.includePaths.unshift(node.path.dirname(file.path)); // Generate Source Maps if
|
|
113
|
+
opts.includePaths.unshift(node.path.dirname(file.path)); // Generate Source Maps if the source-map plugin is present
|
|
47
114
|
|
|
48
115
|
if (file.sourceMap) {
|
|
49
116
|
opts.sourceMap = file.path;
|
|
50
117
|
opts.omitSourceMapUrl = true;
|
|
51
118
|
opts.sourceMapContents = true;
|
|
52
119
|
} //////////////////////////////
|
|
53
|
-
// Handles returning the file to the stream
|
|
54
|
-
//////////////////////////////
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const filePush = sassObj => {
|
|
58
|
-
let sassMap;
|
|
59
|
-
let sassMapFile;
|
|
60
|
-
let sassFileSrc;
|
|
61
|
-
let sassFileSrcPath;
|
|
62
|
-
let sourceFileIndex; // Build Source Maps!
|
|
63
|
-
|
|
64
|
-
if (sassObj.map) {
|
|
65
|
-
// Transform map into JSON
|
|
66
|
-
sassMap = JSON.parse(sassObj.map.toString()); // Grab the stdout and transform it into stdin
|
|
67
|
-
|
|
68
|
-
sassMapFile = sassMap.file.replace(/^stdout$/, 'stdin'); // Grab the base file name that's being worked on
|
|
69
|
-
|
|
70
|
-
sassFileSrc = file.relative; // Grab the path portion of the file that's being worked on
|
|
71
|
-
|
|
72
|
-
sassFileSrcPath = node.path.dirname(sassFileSrc);
|
|
73
|
-
|
|
74
|
-
if (sassFileSrcPath) {
|
|
75
|
-
// Prepend the path to all files in the sources array except the file that's being worked on
|
|
76
|
-
sourceFileIndex = sassMap.sources.indexOf(sassMapFile);
|
|
77
|
-
sassMap.sources = sassMap.sources.map((source, index) => {
|
|
78
|
-
return index === sourceFileIndex ? source : node.path.join(sassFileSrcPath, source);
|
|
79
|
-
});
|
|
80
|
-
} // Remove 'stdin' from sources and replace with filenames!
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
sassMap.sources = sassMap.sources.filter(src => src !== 'stdin' && src); // Replace the map file with the original file name (but new extension)
|
|
84
|
-
|
|
85
|
-
sassMap.file = replaceExtension(sassFileSrc, '.css'); // Apply the map
|
|
86
|
-
|
|
87
|
-
applySourceMap(file, sassMap);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
file.contents = sassObj.css;
|
|
91
|
-
file.path = replaceExtension(file.path, '.css');
|
|
92
|
-
|
|
93
|
-
if (file.stat) {
|
|
94
|
-
file.stat.atime = file.stat.mtime = file.stat.ctime = new Date();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
cb(null, file);
|
|
98
|
-
}; //////////////////////////////
|
|
99
|
-
// Handles error message
|
|
100
|
-
//////////////////////////////
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const errorM = error => {
|
|
104
|
-
const filePath = (error.file === 'stdin' ? file.path : error.file) || file.path;
|
|
105
|
-
const relativePath = node.path.relative(process.cwd(), filePath);
|
|
106
|
-
const message = [relativePath, error.formatted].join('\n');
|
|
107
|
-
error.messageFormatted = message;
|
|
108
|
-
error.messageOriginal = error.message;
|
|
109
|
-
error.message = stripAnsi(message);
|
|
110
|
-
error.relativePath = relativePath;
|
|
111
|
-
return cb(new PluginError(PLUGIN_NAME, error));
|
|
112
|
-
}; //////////////////////////////
|
|
113
120
|
// Sync Sass render
|
|
114
121
|
//////////////////////////////
|
|
115
122
|
|
|
116
123
|
|
|
117
124
|
try {
|
|
118
|
-
gulpSass.compiler && filePush(gulpSass.compiler.renderSync(opts));
|
|
125
|
+
gulpSass.compiler && filePush(file, gulpSass.compiler.renderSync(opts), callback);
|
|
119
126
|
} catch (error) {
|
|
120
|
-
|
|
127
|
+
handleError(error, file, callback);
|
|
121
128
|
}
|
|
122
129
|
});
|
|
123
130
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return gulpSass(options);
|
|
127
|
-
});
|
|
131
|
+
gulpSass.compiler = sass;
|
|
132
|
+
export default gulpSass;
|
package/lib/plugins/sftp.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// Reference `gulp-sftp@0.1.5`
|
|
2
|
-
import
|
|
2
|
+
import { Client } from 'ssh2';
|
|
3
3
|
import parents from 'parents';
|
|
4
4
|
import async from 'async';
|
|
5
5
|
const PLUGIN_NAME = 'sftp';
|
|
6
|
+
const DefaultAlgorithms = {
|
|
7
|
+
kex: ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group14-sha256', 'diffie-hellman-group15-sha512', 'diffie-hellman-group16-sha512', 'diffie-hellman-group17-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha1', 'diffie-hellman-group14-sha1', 'diffie-hellman-group1-sha1']
|
|
8
|
+
};
|
|
6
9
|
|
|
7
10
|
const normalizePath = _path => _path.replace(/\\/g, '/');
|
|
8
11
|
|
|
@@ -145,7 +148,8 @@ function gulpSftp(options) {
|
|
|
145
148
|
const connectionOptions = {
|
|
146
149
|
host: options.host,
|
|
147
150
|
port: options.port || 22,
|
|
148
|
-
username
|
|
151
|
+
username,
|
|
152
|
+
algorithms: options.algorithms || DefaultAlgorithms
|
|
149
153
|
};
|
|
150
154
|
|
|
151
155
|
if (password) {
|
|
@@ -162,10 +166,10 @@ function gulpSftp(options) {
|
|
|
162
166
|
connectionOptions.readyTimeout = options.timeout;
|
|
163
167
|
}
|
|
164
168
|
|
|
165
|
-
const conn = new
|
|
169
|
+
const conn = new Client();
|
|
166
170
|
connCache = conn;
|
|
167
171
|
conn.on('ready', () => {
|
|
168
|
-
BalmJS.logger.debug(PLUGIN_NAME, '
|
|
172
|
+
BalmJS.logger.debug(PLUGIN_NAME, 'Client :: ready');
|
|
169
173
|
conn.sftp((err, sftp) => {
|
|
170
174
|
if (err) {
|
|
171
175
|
throw err;
|
|
@@ -187,7 +191,7 @@ function gulpSftp(options) {
|
|
|
187
191
|
this.emit('error', new PluginError(PLUGIN_NAME, err));
|
|
188
192
|
});
|
|
189
193
|
conn.on('end', () => {
|
|
190
|
-
BalmJS.logger.debug(PLUGIN_NAME, '
|
|
194
|
+
BalmJS.logger.debug(PLUGIN_NAME, 'Client :: end');
|
|
191
195
|
});
|
|
192
196
|
conn.on('close', () => {
|
|
193
197
|
if (!finished) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "balm-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "BalmJS compiler core",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"balm",
|
|
@@ -42,17 +42,17 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"ansi-colors": "^4.1.1",
|
|
45
|
-
"async": "^3.2.
|
|
46
|
-
"autoprefixer": "^10.
|
|
45
|
+
"async": "^3.2.3",
|
|
46
|
+
"autoprefixer": "^10.4.2",
|
|
47
47
|
"babel-loader": "^8.2.3",
|
|
48
48
|
"browser-sync": "^2.27.7",
|
|
49
49
|
"connect-history-api-fallback": "^1.6.0",
|
|
50
|
-
"css-loader": "^6.5.
|
|
51
|
-
"css-minimizer-webpack-plugin": "^3.
|
|
52
|
-
"cssnano": "^5.0.
|
|
50
|
+
"css-loader": "^6.5.1",
|
|
51
|
+
"css-minimizer-webpack-plugin": "^3.3.1",
|
|
52
|
+
"cssnano": "^5.0.15",
|
|
53
53
|
"del": "^6.0.0",
|
|
54
|
-
"esbuild": "^0.
|
|
55
|
-
"fancy-log": "^
|
|
54
|
+
"esbuild": "^0.14",
|
|
55
|
+
"fancy-log": "^2.0.0",
|
|
56
56
|
"file-loader": "^6.2.0",
|
|
57
57
|
"gulp-eslint": "^6.0.0",
|
|
58
58
|
"gulp-if": "^3.0.0",
|
|
@@ -63,50 +63,50 @@
|
|
|
63
63
|
"gulp-useref": "^5.0.0",
|
|
64
64
|
"gulp-zip": "^5.1.0",
|
|
65
65
|
"gulp.spritesmith": "^6.12.1",
|
|
66
|
-
"html-loader": "^3.
|
|
66
|
+
"html-loader": "^3.1.0",
|
|
67
67
|
"html-minifier": "^4.0.0",
|
|
68
68
|
"html-webpack-plugin": "^5.5.0",
|
|
69
69
|
"http-proxy-middleware": "^2.0.1",
|
|
70
70
|
"imagemin": "^8.0.1",
|
|
71
71
|
"istextorbinary": "^6.0.0",
|
|
72
72
|
"less": "^4.1.2",
|
|
73
|
-
"mini-css-extract-plugin": "^2.
|
|
73
|
+
"mini-css-extract-plugin": "^2.5.2",
|
|
74
74
|
"modernizr": "^3.11.8",
|
|
75
75
|
"parents": "^1.0.1",
|
|
76
76
|
"plugin-error": "^1.0.1",
|
|
77
|
-
"postcss": "^8.
|
|
77
|
+
"postcss": "^8.4.5",
|
|
78
78
|
"postcss-import": "^14.0.2",
|
|
79
|
-
"postcss-load-config": "^3.1.
|
|
80
|
-
"postcss-loader": "6.2.
|
|
81
|
-
"postcss-preset-env": "^
|
|
79
|
+
"postcss-load-config": "^3.1.1",
|
|
80
|
+
"postcss-loader": "6.2.1",
|
|
81
|
+
"postcss-preset-env": "^7.2.3",
|
|
82
82
|
"pretty-bytes": "^5.6.0",
|
|
83
83
|
"readable-stream": "^3.6.0",
|
|
84
84
|
"replace-ext": "^2.0.0",
|
|
85
|
-
"rollup": "^2.
|
|
85
|
+
"rollup": "^2.64.0",
|
|
86
86
|
"rollup-plugin-terser": "^7.0.2",
|
|
87
|
-
"sass": "1.
|
|
88
|
-
"sass-loader": "^12.
|
|
89
|
-
"ssh2": "^
|
|
87
|
+
"sass": "^1.48.0",
|
|
88
|
+
"sass-loader": "^12.4.0",
|
|
89
|
+
"ssh2": "^1.5.0",
|
|
90
90
|
"strip-ansi": "^7.0.1",
|
|
91
91
|
"style-loader": "^3.3.1",
|
|
92
|
-
"terser": "^5.
|
|
93
|
-
"terser-webpack-plugin": "^5.
|
|
92
|
+
"terser": "^5.10.0",
|
|
93
|
+
"terser-webpack-plugin": "^5.3.0",
|
|
94
94
|
"through2": "^4.0.2",
|
|
95
95
|
"through2-concurrent": "^2.0.0",
|
|
96
96
|
"tslib": "^2.3.1",
|
|
97
97
|
"url-loader": "^4.1.1",
|
|
98
98
|
"vinyl-sourcemaps-apply": "^0.2.1",
|
|
99
|
-
"webpack": "^5.
|
|
99
|
+
"webpack": "^5.66.0",
|
|
100
100
|
"webpack-bundle-analyzer": "^4.5.0",
|
|
101
|
-
"webpack-dev-middleware": "^5.
|
|
101
|
+
"webpack-dev-middleware": "^5.3.0",
|
|
102
102
|
"webpack-hot-middleware": "^2.25.1",
|
|
103
103
|
"webpack-merge": "^5.8.0",
|
|
104
|
-
"workbox-build": "^6.
|
|
104
|
+
"workbox-build": "^6.4.2"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
|
-
"@swc/core": "^1.2
|
|
107
|
+
"@swc/core": "^1.2",
|
|
108
108
|
"@types/express": "^4.17.13",
|
|
109
|
-
"@types/node": "^
|
|
109
|
+
"@types/node": "^17",
|
|
110
110
|
"@types/uglify-js": "^3.13.1",
|
|
111
111
|
"@types/webpack": "^5.28.0"
|
|
112
112
|
},
|
|
@@ -119,5 +119,5 @@
|
|
|
119
119
|
"engines": {
|
|
120
120
|
"node": ">=12.13.0"
|
|
121
121
|
},
|
|
122
|
-
"gitHead": "
|
|
122
|
+
"gitHead": "23c92f2ef3b5240d24ec2b59f56d6ae49e33757b"
|
|
123
123
|
}
|
|
@@ -12,15 +12,9 @@ const loadPlugin = (plugin, ...args) => {
|
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
const exposePlugin = (plugin) => (...args) => loadPlugin(plugin, ...args);
|
|
15
|
-
const getDefaultPlugins = () => defaultPlugins.
|
|
16
|
-
const instance = loadPlugin(plugin);
|
|
17
|
-
if (!instance) {
|
|
18
|
-
return plugins;
|
|
19
|
-
}
|
|
20
|
-
return plugins.concat(instance);
|
|
21
|
-
}, []);
|
|
15
|
+
const getDefaultPlugins = () => defaultPlugins.flatMap((plugin) => loadPlugin(plugin));
|
|
22
16
|
const gulpImagemin = (customPlugins) => {
|
|
23
|
-
const validExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.svg'];
|
|
17
|
+
const validExtensions = new Set(['.jpg', '.jpeg', '.png', '.gif', '.svg']);
|
|
24
18
|
let totalBytes = 0;
|
|
25
19
|
let totalSavedBytes = 0;
|
|
26
20
|
let totalFiles = 0;
|
|
@@ -33,7 +27,7 @@ const gulpImagemin = (customPlugins) => {
|
|
|
33
27
|
callback(new PluginError(PLUGIN_NAME, 'Streaming not supported'));
|
|
34
28
|
return;
|
|
35
29
|
}
|
|
36
|
-
if (!validExtensions.
|
|
30
|
+
if (!validExtensions.has(node.path.extname(file.path).toLowerCase())) {
|
|
37
31
|
BalmJS.logger.info(PLUGIN_NAME, `Skipping unsupported image "${file.relative}"`);
|
|
38
32
|
callback(null, file);
|
|
39
33
|
return;
|
|
@@ -48,16 +42,16 @@ const gulpImagemin = (customPlugins) => {
|
|
|
48
42
|
const optimizedSize = data.length;
|
|
49
43
|
const saved = originalSize - optimizedSize;
|
|
50
44
|
const percent = originalSize > 0 ? (saved / originalSize) * 100 : 0;
|
|
51
|
-
const
|
|
45
|
+
const savedMessage = `saved ${prettyBytes(saved)} - ${percent
|
|
52
46
|
.toFixed(1)
|
|
53
47
|
.replace(/\.0$/, '')}%`;
|
|
54
|
-
const
|
|
48
|
+
const message = saved > 0 ? savedMessage : 'already optimized';
|
|
55
49
|
if (saved > 0) {
|
|
56
50
|
totalBytes += originalSize;
|
|
57
51
|
totalSavedBytes += saved;
|
|
58
52
|
totalFiles++;
|
|
59
53
|
}
|
|
60
|
-
BalmJS.logger.debug(PLUGIN_NAME, `${file.relative} (${
|
|
54
|
+
BalmJS.logger.debug(PLUGIN_NAME, `${file.relative} (${message})`);
|
|
61
55
|
file.contents = data;
|
|
62
56
|
callback(null, file);
|
|
63
57
|
}
|
|
@@ -70,13 +64,13 @@ const gulpImagemin = (customPlugins) => {
|
|
|
70
64
|
}
|
|
71
65
|
function flush(callback) {
|
|
72
66
|
const percent = totalBytes > 0 ? (totalSavedBytes / totalBytes) * 100 : 0;
|
|
73
|
-
let
|
|
67
|
+
let message = `Minified ${totalFiles} image${totalFiles > 1 ? 's' : ''}`;
|
|
74
68
|
if (totalFiles > 0) {
|
|
75
|
-
|
|
69
|
+
message += ` (saved ${prettyBytes(totalSavedBytes)} - ${percent
|
|
76
70
|
.toFixed(1)
|
|
77
71
|
.replace(/\.0$/, '')}%)`;
|
|
78
72
|
}
|
|
79
|
-
BalmJS.logger.info(PLUGIN_NAME,
|
|
73
|
+
BalmJS.logger.info(PLUGIN_NAME, message);
|
|
80
74
|
callback();
|
|
81
75
|
}
|
|
82
76
|
return through2Concurrent.obj({ maxConcurrency: 8 }, transform, flush);
|
package/tslib/plugins/sass.js
CHANGED
|
@@ -1,21 +1,62 @@
|
|
|
1
|
+
import { Transform } from 'node:stream';
|
|
1
2
|
import sass from 'sass';
|
|
2
3
|
import replaceExtension from 'replace-ext';
|
|
3
4
|
import stripAnsi from 'strip-ansi';
|
|
4
5
|
import applySourceMap from 'vinyl-sourcemaps-apply';
|
|
6
|
+
import ansiColors from 'ansi-colors';
|
|
5
7
|
const PLUGIN_NAME = 'sass';
|
|
6
|
-
const
|
|
8
|
+
const transformObj = (transform) => new Transform({ transform, objectMode: true });
|
|
9
|
+
const filePush = (file, sassObject, callback) => {
|
|
10
|
+
if (sassObject.map) {
|
|
11
|
+
const sassMap = JSON.parse(sassObject.map.toString());
|
|
12
|
+
const sassMapFile = sassMap.file.replace(/^stdout$/, 'stdin');
|
|
13
|
+
const sassFileSrc = file.relative;
|
|
14
|
+
const sassFileSrcPath = node.path.dirname(sassFileSrc);
|
|
15
|
+
if (sassFileSrcPath) {
|
|
16
|
+
const sourceFileIndex = sassMap.sources.indexOf(sassMapFile);
|
|
17
|
+
sassMap.sources = sassMap.sources.map((source, index) => index === sourceFileIndex
|
|
18
|
+
? source
|
|
19
|
+
: node.path.join(sassFileSrcPath, source));
|
|
20
|
+
}
|
|
21
|
+
sassMap.sources = sassMap.sources.filter((src) => src !== 'stdin' && src);
|
|
22
|
+
sassMap.file = replaceExtension(sassFileSrc, '.css');
|
|
23
|
+
applySourceMap(file, sassMap);
|
|
24
|
+
}
|
|
25
|
+
file.contents = sassObject.css;
|
|
26
|
+
file.path = replaceExtension(file.path, '.css');
|
|
27
|
+
if (file.stat) {
|
|
28
|
+
file.stat.atime = file.stat.mtime = file.stat.ctime = new Date();
|
|
29
|
+
}
|
|
30
|
+
callback(null, file);
|
|
31
|
+
};
|
|
32
|
+
const handleError = (error, file, callback) => {
|
|
33
|
+
const filePath = ((error.file === 'stdin' ? file.path : error.file) ||
|
|
34
|
+
file.path);
|
|
35
|
+
const relativePath = node.path.relative(process.cwd(), filePath);
|
|
36
|
+
const message = `${ansiColors.underline(relativePath)}\n${error.formatted}`;
|
|
37
|
+
error.messageFormatted = message;
|
|
38
|
+
error.messageOriginal = error.message;
|
|
39
|
+
error.message = stripAnsi(message);
|
|
40
|
+
error.relativePath = relativePath;
|
|
41
|
+
return callback(new PluginError(PLUGIN_NAME, error));
|
|
42
|
+
};
|
|
43
|
+
const gulpSass = (options) => transformObj((file, encoding, callback) => {
|
|
7
44
|
if (file.isNull()) {
|
|
8
|
-
|
|
45
|
+
callback(null, file);
|
|
46
|
+
return;
|
|
9
47
|
}
|
|
10
48
|
if (file.isStream()) {
|
|
11
|
-
|
|
49
|
+
callback(new PluginError(PLUGIN_NAME, 'Streaming not supported'));
|
|
50
|
+
return;
|
|
12
51
|
}
|
|
13
|
-
if (node.path.basename(file.path).
|
|
14
|
-
|
|
52
|
+
if (node.path.basename(file.path).startsWith('_')) {
|
|
53
|
+
callback();
|
|
54
|
+
return;
|
|
15
55
|
}
|
|
16
56
|
if (!file.contents.length) {
|
|
17
57
|
file.path = replaceExtension(file.path, '.css');
|
|
18
|
-
|
|
58
|
+
callback(null, file);
|
|
59
|
+
return;
|
|
19
60
|
}
|
|
20
61
|
const opts = (options || {});
|
|
21
62
|
opts.data = file.contents.toString();
|
|
@@ -37,55 +78,13 @@ const gulpSass = (options) => through2.obj((file, encoding, cb) => {
|
|
|
37
78
|
opts.omitSourceMapUrl = true;
|
|
38
79
|
opts.sourceMapContents = true;
|
|
39
80
|
}
|
|
40
|
-
const filePush = (sassObj) => {
|
|
41
|
-
let sassMap;
|
|
42
|
-
let sassMapFile;
|
|
43
|
-
let sassFileSrc;
|
|
44
|
-
let sassFileSrcPath;
|
|
45
|
-
let sourceFileIndex;
|
|
46
|
-
if (sassObj.map) {
|
|
47
|
-
sassMap = JSON.parse(sassObj.map.toString());
|
|
48
|
-
sassMapFile = sassMap.file.replace(/^stdout$/, 'stdin');
|
|
49
|
-
sassFileSrc = file.relative;
|
|
50
|
-
sassFileSrcPath = node.path.dirname(sassFileSrc);
|
|
51
|
-
if (sassFileSrcPath) {
|
|
52
|
-
sourceFileIndex = sassMap.sources.indexOf(sassMapFile);
|
|
53
|
-
sassMap.sources = sassMap.sources.map((source, index) => {
|
|
54
|
-
return index === sourceFileIndex
|
|
55
|
-
? source
|
|
56
|
-
: node.path.join(sassFileSrcPath, source);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
sassMap.sources = sassMap.sources.filter((src) => src !== 'stdin' && src);
|
|
60
|
-
sassMap.file = replaceExtension(sassFileSrc, '.css');
|
|
61
|
-
applySourceMap(file, sassMap);
|
|
62
|
-
}
|
|
63
|
-
file.contents = sassObj.css;
|
|
64
|
-
file.path = replaceExtension(file.path, '.css');
|
|
65
|
-
if (file.stat) {
|
|
66
|
-
file.stat.atime = file.stat.mtime = file.stat.ctime = new Date();
|
|
67
|
-
}
|
|
68
|
-
cb(null, file);
|
|
69
|
-
};
|
|
70
|
-
const errorM = (error) => {
|
|
71
|
-
const filePath = ((error.file === 'stdin' ? file.path : error.file) ||
|
|
72
|
-
file.path);
|
|
73
|
-
const relativePath = node.path.relative(process.cwd(), filePath);
|
|
74
|
-
const message = [relativePath, error.formatted].join('\n');
|
|
75
|
-
error.messageFormatted = message;
|
|
76
|
-
error.messageOriginal = error.message;
|
|
77
|
-
error.message = stripAnsi(message);
|
|
78
|
-
error.relativePath = relativePath;
|
|
79
|
-
return cb(new PluginError(PLUGIN_NAME, error));
|
|
80
|
-
};
|
|
81
81
|
try {
|
|
82
|
-
gulpSass.compiler &&
|
|
82
|
+
gulpSass.compiler &&
|
|
83
|
+
filePush(file, gulpSass.compiler.renderSync(opts), callback);
|
|
83
84
|
}
|
|
84
85
|
catch (error) {
|
|
85
|
-
|
|
86
|
+
handleError(error, file, callback);
|
|
86
87
|
}
|
|
87
88
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return gulpSass(options);
|
|
91
|
-
};
|
|
89
|
+
gulpSass.compiler = sass;
|
|
90
|
+
export default gulpSass;
|
package/tslib/plugins/sftp.js
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Client } from 'ssh2';
|
|
2
2
|
import parents from 'parents';
|
|
3
3
|
import async from 'async';
|
|
4
4
|
const PLUGIN_NAME = 'sftp';
|
|
5
|
+
const DefaultAlgorithms = {
|
|
6
|
+
kex: [
|
|
7
|
+
'curve25519-sha256',
|
|
8
|
+
'curve25519-sha256@libssh.org',
|
|
9
|
+
'ecdh-sha2-nistp256',
|
|
10
|
+
'ecdh-sha2-nistp384',
|
|
11
|
+
'ecdh-sha2-nistp521',
|
|
12
|
+
'diffie-hellman-group-exchange-sha256',
|
|
13
|
+
'diffie-hellman-group14-sha256',
|
|
14
|
+
'diffie-hellman-group15-sha512',
|
|
15
|
+
'diffie-hellman-group16-sha512',
|
|
16
|
+
'diffie-hellman-group17-sha512',
|
|
17
|
+
'diffie-hellman-group18-sha512',
|
|
18
|
+
'diffie-hellman-group-exchange-sha1',
|
|
19
|
+
'diffie-hellman-group14-sha1',
|
|
20
|
+
'diffie-hellman-group1-sha1'
|
|
21
|
+
]
|
|
22
|
+
};
|
|
5
23
|
const normalizePath = (_path) => _path.replace(/\\/g, '/');
|
|
6
24
|
function resolveHomePath(key) {
|
|
7
25
|
if (key.location) {
|
|
@@ -99,7 +117,8 @@ function gulpSftp(options) {
|
|
|
99
117
|
const connectionOptions = {
|
|
100
118
|
host: options.host,
|
|
101
119
|
port: options.port || 22,
|
|
102
|
-
username
|
|
120
|
+
username,
|
|
121
|
+
algorithms: options.algorithms || DefaultAlgorithms
|
|
103
122
|
};
|
|
104
123
|
if (password) {
|
|
105
124
|
connectionOptions.password = password;
|
|
@@ -115,10 +134,10 @@ function gulpSftp(options) {
|
|
|
115
134
|
if (options.timeout) {
|
|
116
135
|
connectionOptions.readyTimeout = options.timeout;
|
|
117
136
|
}
|
|
118
|
-
const conn = new
|
|
137
|
+
const conn = new Client();
|
|
119
138
|
connCache = conn;
|
|
120
139
|
conn.on('ready', () => {
|
|
121
|
-
BalmJS.logger.debug(PLUGIN_NAME, '
|
|
140
|
+
BalmJS.logger.debug(PLUGIN_NAME, 'Client :: ready');
|
|
122
141
|
conn.sftp((err, sftp) => {
|
|
123
142
|
if (err) {
|
|
124
143
|
throw err;
|
|
@@ -138,7 +157,7 @@ function gulpSftp(options) {
|
|
|
138
157
|
this.emit('error', new PluginError(PLUGIN_NAME, err));
|
|
139
158
|
});
|
|
140
159
|
conn.on('end', () => {
|
|
141
|
-
BalmJS.logger.debug(PLUGIN_NAME, '
|
|
160
|
+
BalmJS.logger.debug(PLUGIN_NAME, 'Client :: end');
|
|
142
161
|
});
|
|
143
162
|
conn.on('close', () => {
|
|
144
163
|
if (!finished) {
|