@bigcommerce/stencil-cli 6.1.1 → 7.0.0-beta1
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 +2 -3
- package/bin/stencil-scss-autofix.js +23 -0
- package/bin/stencil.js +1 -0
- package/lib/ScssValidator.js +119 -0
- package/lib/StencilCLISettings.js +0 -8
- package/lib/bundle-validator.js +3 -107
- package/lib/cliCommon.js +0 -1
- package/lib/css/compile.js +1 -3
- package/lib/nodeSass/AutoFixer.js +143 -0
- package/lib/nodeSass/BaseFixer.js +56 -0
- package/lib/nodeSass/BaseRulesFixer.js +31 -0
- package/lib/nodeSass/CommaRemovalFixer.js +28 -0
- package/lib/nodeSass/ConditionalImportFixer.js +230 -0
- package/lib/nodeSass/UndefinedVariableFixer.js +60 -0
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -7,9 +7,8 @@ The BigCommerce server emulator for local theme development.
|
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
We do not yet have support for versions greater than Node 14._
|
|
10
|
+
Note: Stencil requires the Node.js runtime environment,
|
|
11
|
+
versions 16.x, 18.x are supported.
|
|
13
12
|
|
|
14
13
|
Run `npm install -g @bigcommerce/stencil-cli`.
|
|
15
14
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
require('colors');
|
|
4
|
+
const program = require('../lib/commander');
|
|
5
|
+
|
|
6
|
+
const ThemeConfig = require('../lib/theme-config');
|
|
7
|
+
const NodeSassAutoFixer = require('../lib/nodeSass/AutoFixer');
|
|
8
|
+
const { THEME_PATH, PACKAGE_INFO } = require('../constants');
|
|
9
|
+
const { printCliResultErrorAndExit } = require('../lib/cliCommon');
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.version(PACKAGE_INFO.version)
|
|
13
|
+
.option(
|
|
14
|
+
'-d, --dry',
|
|
15
|
+
'will not write any changes to the file system, instead it will print the changes to the console',
|
|
16
|
+
)
|
|
17
|
+
.parse(process.argv);
|
|
18
|
+
|
|
19
|
+
const cliOptions = program.opts();
|
|
20
|
+
|
|
21
|
+
const themeConfig = ThemeConfig.getInstance(THEME_PATH);
|
|
22
|
+
|
|
23
|
+
new NodeSassAutoFixer(THEME_PATH, themeConfig, cliOptions).run().catch(printCliResultErrorAndExit);
|
package/bin/stencil.js
CHANGED
|
@@ -16,4 +16,5 @@ program
|
|
|
16
16
|
.command('pull', 'Pulls currently active theme config files and overwrites local copy')
|
|
17
17
|
.command('download', 'Downloads all the theme files')
|
|
18
18
|
.command('debug', 'Prints environment and theme settings for debug purposes')
|
|
19
|
+
.command('scss autofix', 'Prints environment and theme settings for debug purposes')
|
|
19
20
|
.parse(process.argv);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require('colors');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const cssCompiler = require('./css/compile');
|
|
6
|
+
const { recursiveReadDir } = require('./utils/fsUtils');
|
|
7
|
+
|
|
8
|
+
/* eslint-disable no-useless-escape */
|
|
9
|
+
const STYLESHEET_REGEXP = /{{\s*stylesheet\s*([\/a-zA-Z'"\.-]+)\s*}}/i;
|
|
10
|
+
|
|
11
|
+
class ScssValidator {
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param themePath
|
|
15
|
+
* @param themeConfig
|
|
16
|
+
*/
|
|
17
|
+
constructor(themePath, themeConfig) {
|
|
18
|
+
this.themePath = themePath;
|
|
19
|
+
this.themeConfig = themeConfig;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async run() {
|
|
23
|
+
const assetsPath = path.join(this.themePath, 'assets');
|
|
24
|
+
const stylesPath = path.join(this.themePath, 'assets/scss');
|
|
25
|
+
const rawConfig = await this.themeConfig.getConfig();
|
|
26
|
+
const cssFiles = await this.getCssFiles();
|
|
27
|
+
|
|
28
|
+
for await (const file of cssFiles) {
|
|
29
|
+
try {
|
|
30
|
+
/* eslint-disable-next-line no-await-in-loop */
|
|
31
|
+
await cssCompiler.compile(
|
|
32
|
+
rawConfig,
|
|
33
|
+
assetsPath,
|
|
34
|
+
file,
|
|
35
|
+
cssCompiler.SASS_ENGINE_NAME,
|
|
36
|
+
);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
const message = this.parseStencilStylesError(e);
|
|
39
|
+
console.log(e.file);
|
|
40
|
+
throw new Error(
|
|
41
|
+
`${message} while compiling css files from "${stylesPath}/${file}".`.red,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
parseStencilStylesError(e) {
|
|
48
|
+
if (e.formatted) {
|
|
49
|
+
return `${e.formatted.replace('Error: ', '')}: `;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return e.message;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async getCssFiles() {
|
|
56
|
+
const templatesPath = path.join(this.themePath, 'templates');
|
|
57
|
+
const files = await recursiveReadDir(templatesPath);
|
|
58
|
+
const cssFiles = [];
|
|
59
|
+
for await (const file of files) {
|
|
60
|
+
const content = await fs.promises.readFile(file, { encoding: 'utf-8' });
|
|
61
|
+
const result = content.match(STYLESHEET_REGEXP);
|
|
62
|
+
if (result) {
|
|
63
|
+
// remove quotes
|
|
64
|
+
const fileName = result[1].slice(1, -1);
|
|
65
|
+
|
|
66
|
+
const filePath = this.tryToResolveCssFileLocation(fileName, result);
|
|
67
|
+
if (!cssFiles.includes(filePath)) {
|
|
68
|
+
// check if file exist
|
|
69
|
+
cssFiles.push(filePath);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return cssFiles;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// returns relative path starting from root scss/css folder
|
|
78
|
+
tryToResolveCssFileLocation(fileName, result) {
|
|
79
|
+
const possibleLocations = [
|
|
80
|
+
fileName,
|
|
81
|
+
fileName.replace('/css/', '/scss/'),
|
|
82
|
+
fileName.replace('/scss/', '/css/'),
|
|
83
|
+
fileName.replace('/css/', '/scss/').replace('.css', '.scss'),
|
|
84
|
+
fileName.replace('/scss/', '/css/').replace('.scss', '.css'),
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
for (const location of possibleLocations) {
|
|
88
|
+
const filePath = path.join(this.themePath, location);
|
|
89
|
+
if (fs.existsSync(filePath)) {
|
|
90
|
+
if (!this.isRootCssFile(location)) {
|
|
91
|
+
return this.getCssFileWithoutRootFolder(location);
|
|
92
|
+
}
|
|
93
|
+
const fileParts = path.parse(filePath);
|
|
94
|
+
return fileParts.name;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw new Error(`Couldn't find file for this statement: ${result[0]}`.red);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// root folders are /assets/css /assets/scss
|
|
102
|
+
// so after split, there can be 3 or 4 elements in the array (depending if the leading slash is present)
|
|
103
|
+
isRootCssFile(location) {
|
|
104
|
+
return location.split('/').length <= 4;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getCssFileWithoutRootFolder(location) {
|
|
108
|
+
const locationParts = location.split('/');
|
|
109
|
+
if (locationParts[0] === '') {
|
|
110
|
+
locationParts.shift();
|
|
111
|
+
}
|
|
112
|
+
locationParts.shift();
|
|
113
|
+
locationParts.shift();
|
|
114
|
+
|
|
115
|
+
return locationParts.join('/');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = ScssValidator;
|
|
@@ -13,14 +13,6 @@ class StencilCLISettings {
|
|
|
13
13
|
isVerbose() {
|
|
14
14
|
return this.versboseNetworkLogging;
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
useOldNodeSassFork(flag) {
|
|
18
|
-
this.oldNodeSassFork = flag;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
isOldNodeSassForkEnabled() {
|
|
22
|
-
return this.oldNodeSassFork;
|
|
23
|
-
}
|
|
24
16
|
}
|
|
25
17
|
|
|
26
18
|
const settings = new StencilCLISettings();
|
package/lib/bundle-validator.js
CHANGED
|
@@ -14,8 +14,7 @@ const ValidatorSchemaTranslations = require('./validator/schema-translations');
|
|
|
14
14
|
const privateThemeConfigValidationSchema = require('./schemas/privateThemeConfig.json');
|
|
15
15
|
const themeConfigValidationSchema = require('./schemas/themeConfig.json');
|
|
16
16
|
const themeValidationSchema = require('./schemas/themeSchema.json');
|
|
17
|
-
const
|
|
18
|
-
const stencilCLISettings = require('./StencilCLISettings');
|
|
17
|
+
const ScssValidator = require('./ScssValidator');
|
|
19
18
|
|
|
20
19
|
const VALID_IMAGE_TYPES = ['.jpg', '.jpeg', '.png', '.gif'];
|
|
21
20
|
const WIDTH_COMPOSED = 600;
|
|
@@ -28,8 +27,6 @@ const MAX_SIZE_COMPOSED = 1024 * 1024 * 2; // 2MB
|
|
|
28
27
|
const MAX_SIZE_MOBILE = 1024 * 1024; // 1MB
|
|
29
28
|
const MAX_SIZE_DESKTOP = 1024 * 1024 * 5; // 5MB
|
|
30
29
|
|
|
31
|
-
/* eslint-disable no-useless-escape */
|
|
32
|
-
const STYLESHEET_REGEXP = /{{\s*stylesheet\s*([\/a-zA-Z'"\.-]+)\s*}}/i;
|
|
33
30
|
class BundleValidator {
|
|
34
31
|
/**
|
|
35
32
|
* Run some validations to ensure that the platform will accept the theme
|
|
@@ -44,6 +41,7 @@ class BundleValidator {
|
|
|
44
41
|
this.themePath = themePath;
|
|
45
42
|
this.objectsToValidate = ['head.scripts', 'footer.scripts'];
|
|
46
43
|
this.jsonSchemaValidatorOptions = { schemaId: 'auto', allErrors: true };
|
|
44
|
+
this.scssValidator = new ScssValidator(themePath, themeConfig);
|
|
47
45
|
|
|
48
46
|
// Array of tasks used in async.series
|
|
49
47
|
this.validationTasks = [
|
|
@@ -408,109 +406,7 @@ class BundleValidator {
|
|
|
408
406
|
}
|
|
409
407
|
|
|
410
408
|
async _validateCssFiles() {
|
|
411
|
-
|
|
412
|
-
const stylesPath = path.join(this.themePath, 'assets/scss');
|
|
413
|
-
const rawConfig = await this.themeConfig.getConfig();
|
|
414
|
-
const cssFiles = await this.getCssFiles();
|
|
415
|
-
|
|
416
|
-
for (const file of cssFiles) {
|
|
417
|
-
try {
|
|
418
|
-
const engine = stencilCLISettings.isOldNodeSassForkEnabled()
|
|
419
|
-
? cssCompiler.FALLBACK_SASS_ENGINE_NAME
|
|
420
|
-
: cssCompiler.SASS_ENGINE_NAME;
|
|
421
|
-
/* eslint-disable-next-line no-await-in-loop */
|
|
422
|
-
await cssCompiler.compile(rawConfig, assetsPath, file, engine);
|
|
423
|
-
} catch (e) {
|
|
424
|
-
const message = this.parseStencilStylesError(e);
|
|
425
|
-
if (!stencilCLISettings.isOldNodeSassForkEnabled()) {
|
|
426
|
-
throw new Error(
|
|
427
|
-
`${message}\n`.red +
|
|
428
|
-
`\n` +
|
|
429
|
-
`---------WARNING---------\n`.red +
|
|
430
|
-
`We are currently in the process of deprecating node-sass fork https://github.com/bigcommerce-labs/node-sass\n` +
|
|
431
|
-
`Your scss files were compiled using latest node-sass version https://github.com/sass/node-sass\n` +
|
|
432
|
-
`This error might indicate that your scss file is not compatible with it.\n` +
|
|
433
|
-
`There is still an option to compile scss file old fork by using --use-old-node-sass-fork.\n` +
|
|
434
|
-
`But note, that this will lead to 500 error in production in near future.\n` +
|
|
435
|
-
`---------WARNING---------\n`.red,
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
throw new Error(
|
|
439
|
-
`${message} while compiling css files from "${stylesPath}/${file}".`.red,
|
|
440
|
-
);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
async getCssFiles() {
|
|
446
|
-
const templatesPath = path.join(this.themePath, 'templates');
|
|
447
|
-
const files = await recursiveReadDir(templatesPath);
|
|
448
|
-
const cssFiles = [];
|
|
449
|
-
for await (const file of files) {
|
|
450
|
-
const content = await fs.promises.readFile(file, { encoding: 'utf-8' });
|
|
451
|
-
const result = content.match(STYLESHEET_REGEXP);
|
|
452
|
-
if (result) {
|
|
453
|
-
// remove quotes
|
|
454
|
-
const fileName = result[1].slice(1, -1);
|
|
455
|
-
|
|
456
|
-
const filePath = this.tryToResolveCssFileLocation(fileName, result);
|
|
457
|
-
if (!cssFiles.includes(filePath)) {
|
|
458
|
-
// check if file exist
|
|
459
|
-
cssFiles.push(filePath);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
return cssFiles;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// returns relative path starting from root scss/css folder
|
|
468
|
-
tryToResolveCssFileLocation(fileName, result) {
|
|
469
|
-
const possibleLocations = [
|
|
470
|
-
fileName,
|
|
471
|
-
fileName.replace('/css/', '/scss/'),
|
|
472
|
-
fileName.replace('/scss/', '/css/'),
|
|
473
|
-
fileName.replace('/css/', '/scss/').replace('.css', '.scss'),
|
|
474
|
-
fileName.replace('/scss/', '/css/').replace('.scss', '.css'),
|
|
475
|
-
];
|
|
476
|
-
|
|
477
|
-
for (const location of possibleLocations) {
|
|
478
|
-
const filePath = path.join(this.themePath, location);
|
|
479
|
-
if (fs.existsSync(filePath)) {
|
|
480
|
-
if (!this.isRootCssFile(location)) {
|
|
481
|
-
return this.getCssFileWithoutRootFolder(location);
|
|
482
|
-
}
|
|
483
|
-
const fileParts = path.parse(filePath);
|
|
484
|
-
return fileParts.name;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
throw new Error(`Couldn't find file for this statement: ${result[0]}`.red);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// root folders are /assets/css /assets/scss
|
|
492
|
-
// so after split, there can be 3 or 4 elements in the array (depending if the leading slash is present)
|
|
493
|
-
isRootCssFile(location) {
|
|
494
|
-
return location.split('/').length <= 4;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
getCssFileWithoutRootFolder(location) {
|
|
498
|
-
const locationParts = location.split('/');
|
|
499
|
-
if (locationParts[0] === '') {
|
|
500
|
-
locationParts.shift();
|
|
501
|
-
}
|
|
502
|
-
locationParts.shift();
|
|
503
|
-
locationParts.shift();
|
|
504
|
-
|
|
505
|
-
return locationParts.join('/');
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
parseStencilStylesError(e) {
|
|
509
|
-
if (e.formatted) {
|
|
510
|
-
return `${e.formatted.replace('Error: ', '')}: `;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
return e.message;
|
|
409
|
+
await this.scssValidator.run();
|
|
514
410
|
}
|
|
515
411
|
|
|
516
412
|
validateTrailingSymbols(data) {
|
package/lib/cliCommon.js
CHANGED
package/lib/css/compile.js
CHANGED
|
@@ -4,7 +4,6 @@ const { promisify } = require('util');
|
|
|
4
4
|
|
|
5
5
|
const cssAssembler = require('../css-assembler');
|
|
6
6
|
|
|
7
|
-
const FALLBACK_SASS_ENGINE_NAME = 'node-sass-fork';
|
|
8
7
|
const SASS_ENGINE_NAME = 'node-sass';
|
|
9
8
|
|
|
10
9
|
const compile = async (configuration, themeAssetsPath, fileName, engineName = SASS_ENGINE_NAME) => {
|
|
@@ -29,7 +28,7 @@ const compile = async (configuration, themeAssetsPath, fileName, engineName = SA
|
|
|
29
28
|
sourceMap: true,
|
|
30
29
|
autoprefixerOptions: {
|
|
31
30
|
cascade: configuration.autoprefixer_cascade,
|
|
32
|
-
|
|
31
|
+
overrideBrowserslist: configuration.autoprefixer_browsers,
|
|
33
32
|
},
|
|
34
33
|
};
|
|
35
34
|
const stencilStyles = new StencilStyles(console);
|
|
@@ -41,5 +40,4 @@ const compile = async (configuration, themeAssetsPath, fileName, engineName = SA
|
|
|
41
40
|
module.exports = {
|
|
42
41
|
compile,
|
|
43
42
|
SASS_ENGINE_NAME,
|
|
44
|
-
FALLBACK_SASS_ENGINE_NAME,
|
|
45
43
|
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign, operator-assignment */
|
|
2
|
+
require('colors');
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const ScssValidator = require('../ScssValidator');
|
|
8
|
+
const cssCompiler = require('../css/compile');
|
|
9
|
+
|
|
10
|
+
const BaseRulesFixer = require('./BaseRulesFixer');
|
|
11
|
+
const ConditionaImportFixer = require('./ConditionalImportFixer');
|
|
12
|
+
const CommaRemovalFixer = require('./CommaRemovalFixer');
|
|
13
|
+
const UndefinedVariableFixer = require('./UndefinedVariableFixer');
|
|
14
|
+
|
|
15
|
+
const CONDITIONAL_IMPORT = 'conditional-import';
|
|
16
|
+
const BASE_LEVEL_RULES = 'base-level-rules';
|
|
17
|
+
const POSSIBLE_WRONG_COMMA = 'possible-wrong-comma';
|
|
18
|
+
const UNDEFINED_VARIABLE = 'undefined-variable';
|
|
19
|
+
|
|
20
|
+
class AutoFixer {
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
* @param themePath
|
|
24
|
+
* @param themeConfig
|
|
25
|
+
* @param cliOptions
|
|
26
|
+
*/
|
|
27
|
+
constructor(themePath, themeConfig, cliOptions) {
|
|
28
|
+
this.themePath = themePath;
|
|
29
|
+
this.themeConfig = themeConfig;
|
|
30
|
+
this.cliOptions = cliOptions;
|
|
31
|
+
|
|
32
|
+
this.validator = new ScssValidator(themePath, themeConfig);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async run() {
|
|
36
|
+
const assetsPath = path.join(this.themePath, 'assets');
|
|
37
|
+
const rawConfig = await this.themeConfig.getConfig();
|
|
38
|
+
const cssFiles = await this.validator.getCssFiles();
|
|
39
|
+
|
|
40
|
+
let issuesDetected = false;
|
|
41
|
+
/* eslint-disable-next-line no-useless-catch */
|
|
42
|
+
try {
|
|
43
|
+
for await (const file of cssFiles) {
|
|
44
|
+
try {
|
|
45
|
+
/* eslint-disable-next-line no-await-in-loop */
|
|
46
|
+
await cssCompiler.compile(
|
|
47
|
+
rawConfig,
|
|
48
|
+
assetsPath,
|
|
49
|
+
file,
|
|
50
|
+
cssCompiler.SASS_ENGINE_NAME,
|
|
51
|
+
);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
issuesDetected = true;
|
|
54
|
+
await this.tryToFix(e, file);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!issuesDetected) {
|
|
58
|
+
console.log('No issues deteted');
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async tryToFix(err, file) {
|
|
66
|
+
const problem = this.detectProblem(err);
|
|
67
|
+
if (problem) {
|
|
68
|
+
const dirname = path.join(this.themePath, 'assets/scss');
|
|
69
|
+
let files = [];
|
|
70
|
+
if (problem === CONDITIONAL_IMPORT) {
|
|
71
|
+
const fixer = new ConditionaImportFixer(dirname, err.file);
|
|
72
|
+
files = await fixer.run();
|
|
73
|
+
} else if (problem === BASE_LEVEL_RULES) {
|
|
74
|
+
const baseRulesFixer = new BaseRulesFixer(dirname, err.file);
|
|
75
|
+
files = await baseRulesFixer.run();
|
|
76
|
+
} else if (problem === POSSIBLE_WRONG_COMMA) {
|
|
77
|
+
const toFixFile = err.file === 'stdin' ? file : err.file;
|
|
78
|
+
const commaRemovalFixer = new CommaRemovalFixer(dirname, toFixFile);
|
|
79
|
+
files = await commaRemovalFixer.run();
|
|
80
|
+
} else if (problem === UNDEFINED_VARIABLE) {
|
|
81
|
+
const toFixFile = err.file === 'stdin' ? file : err.file;
|
|
82
|
+
const fixer = new UndefinedVariableFixer(dirname, toFixFile);
|
|
83
|
+
files = await fixer.run(err.message);
|
|
84
|
+
}
|
|
85
|
+
this.parseChangedFiles(files);
|
|
86
|
+
} else {
|
|
87
|
+
const filePath = path.join(this.themePath, 'assets/scss', file + '.scss');
|
|
88
|
+
console.log("Couldn't determine and autofix the problem. Please fix it manually.".red);
|
|
89
|
+
console.log('Found trying to compile file:'.red, filePath);
|
|
90
|
+
throw new Error(err.formatted ? err.formatted : err);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
detectProblem(err) {
|
|
95
|
+
if (err.formatted) {
|
|
96
|
+
if (
|
|
97
|
+
err.formatted.includes(
|
|
98
|
+
'Error: Import directives may not be used within control directives or mixins',
|
|
99
|
+
)
|
|
100
|
+
) {
|
|
101
|
+
return CONDITIONAL_IMPORT;
|
|
102
|
+
}
|
|
103
|
+
if (
|
|
104
|
+
err.formatted.includes(
|
|
105
|
+
"Base-level rules cannot contain the parent-selector-referencing character '&'",
|
|
106
|
+
)
|
|
107
|
+
) {
|
|
108
|
+
return BASE_LEVEL_RULES;
|
|
109
|
+
}
|
|
110
|
+
if (
|
|
111
|
+
err.formatted.includes('Invalid CSS after') &&
|
|
112
|
+
err.formatted.includes('expected selector, was ', '')
|
|
113
|
+
) {
|
|
114
|
+
return POSSIBLE_WRONG_COMMA;
|
|
115
|
+
}
|
|
116
|
+
if (err.formatted.includes('Undefined variable')) {
|
|
117
|
+
return UNDEFINED_VARIABLE;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
parseChangedFiles(files) {
|
|
125
|
+
for (const file of files) {
|
|
126
|
+
this.overrideFile(file.filePath, file.data);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
overrideFile(filePath, data) {
|
|
131
|
+
const phrase = this.cliOptions.dry ? 'Would override' : 'Overriding';
|
|
132
|
+
console.log(phrase.green + ' file:'.green, filePath);
|
|
133
|
+
if (this.cliOptions.dry) {
|
|
134
|
+
console.log('---Content---'.yellow);
|
|
135
|
+
console.log(data);
|
|
136
|
+
console.log('---END of Content---'.yellow);
|
|
137
|
+
} else {
|
|
138
|
+
fs.writeFileSync(filePath, data);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = AutoFixer;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const postcss = require('postcss');
|
|
4
|
+
const postcssScss = require('postcss-scss');
|
|
5
|
+
|
|
6
|
+
class BaseFixer {
|
|
7
|
+
constructor(dirname, brokenFile) {
|
|
8
|
+
this.filePath = this.resolveScssFileName(dirname, brokenFile);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async processCss(data, plugin) {
|
|
12
|
+
const processor = postcss([plugin]);
|
|
13
|
+
return processor.process(data, { from: undefined, parser: postcssScss });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
findImportedFile(file, originalFilePath) {
|
|
17
|
+
const originalDirname = path.dirname(originalFilePath);
|
|
18
|
+
return this.resolveScssFileName(originalDirname, file);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
resolveScssFileName(dirname, fileName) {
|
|
22
|
+
if (!fileName.includes('.scss')) {
|
|
23
|
+
/* eslint-disable-next-line no-param-reassign */
|
|
24
|
+
fileName += '.scss';
|
|
25
|
+
}
|
|
26
|
+
const filePath = path.join(dirname, fileName);
|
|
27
|
+
if (!fs.existsSync(filePath)) {
|
|
28
|
+
// try with underscore
|
|
29
|
+
const fileNameWithUnderscore = this.getFileNameWithUnderscore(fileName);
|
|
30
|
+
const filePathWithUnderscore = path.join(dirname, fileNameWithUnderscore);
|
|
31
|
+
if (!fs.existsSync(filePathWithUnderscore)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Import ${fileName} wasn't resolved in ${filePath} or ${filePathWithUnderscore}`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return filePathWithUnderscore;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return filePath;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getFileNameWithUnderscore(fileName) {
|
|
43
|
+
const fileNameParts = fileName.split('/');
|
|
44
|
+
const fileNameWithUnderscore = fileNameParts
|
|
45
|
+
.map((part, i) => {
|
|
46
|
+
if (i === fileNameParts.length - 1) {
|
|
47
|
+
return '_' + part;
|
|
48
|
+
}
|
|
49
|
+
return part;
|
|
50
|
+
})
|
|
51
|
+
.join('/');
|
|
52
|
+
return fileNameWithUnderscore;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = BaseFixer;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Since previously a broken css was produced which had no effect on the page (browsers are smart enough to ignore unprocessable css)
|
|
2
|
+
// we can just comment out that part of code
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const BaseFixer = require('./BaseFixer');
|
|
6
|
+
|
|
7
|
+
class BaseRulesFixer extends BaseFixer {
|
|
8
|
+
async run() {
|
|
9
|
+
const scss = fs.readFileSync(this.filePath, 'utf8');
|
|
10
|
+
const processedFile = await this.processCss(scss, this.transform());
|
|
11
|
+
|
|
12
|
+
return [{ filePath: this.filePath, data: processedFile.css }];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
transform() {
|
|
16
|
+
return {
|
|
17
|
+
postcssPlugin: 'Transform Base Rules Issues into Comments',
|
|
18
|
+
Rule(rule, { Comment }) {
|
|
19
|
+
if (
|
|
20
|
+
rule.parent.type === 'root' &&
|
|
21
|
+
(rule.selector.startsWith('&--') || rule.selector.startsWith('&.'))
|
|
22
|
+
) {
|
|
23
|
+
const comment = new Comment({ text: rule.toString() });
|
|
24
|
+
rule.replaceWith(comment);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = BaseRulesFixer;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// There can be the case, when you have a comma at the beginning or at the end of the selector, which now is not allowed.
|
|
2
|
+
// Previously it was just ignored, but now it will cause an error.
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const BaseFixer = require('./BaseFixer');
|
|
6
|
+
|
|
7
|
+
class CommaRemovalFixer extends BaseFixer {
|
|
8
|
+
async run() {
|
|
9
|
+
const scss = fs.readFileSync(this.filePath, 'utf8');
|
|
10
|
+
const processedFile = await this.processCss(scss, this.transform());
|
|
11
|
+
|
|
12
|
+
return [{ filePath: this.filePath, data: processedFile.css }];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
transform() {
|
|
16
|
+
return {
|
|
17
|
+
postcssPlugin: 'Unwanted comma removal',
|
|
18
|
+
Rule(rule) {
|
|
19
|
+
if (rule.selector.startsWith(',') || rule.selector.endsWith(',')) {
|
|
20
|
+
/* eslint-disable-next-line no-param-reassign */
|
|
21
|
+
rule.selector = rule.selector.trim().replace(/^,?|,?$/g, '');
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = CommaRemovalFixer;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign, operator-assignment */
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
const BaseFixer = require('./BaseFixer');
|
|
5
|
+
|
|
6
|
+
class ConditionalImportFixer extends BaseFixer {
|
|
7
|
+
async run() {
|
|
8
|
+
const files = [];
|
|
9
|
+
const scss = fs.readFileSync(this.filePath, 'utf8');
|
|
10
|
+
const result = await this.processCss(scss, this.transformConditionalImport());
|
|
11
|
+
|
|
12
|
+
files.push({ filePath: this.filePath, data: result.css });
|
|
13
|
+
const imports = this.mapFilesToRootFile(result.messages, this.filePath);
|
|
14
|
+
const toUpdateNestedImports = [];
|
|
15
|
+
|
|
16
|
+
for await (const fileImport of imports) {
|
|
17
|
+
const importFile = this.findImportedFile(fileImport.file, fileImport.rootFile);
|
|
18
|
+
const importFileScss = fs.readFileSync(importFile, { encoding: 'utf8' });
|
|
19
|
+
const nestedImportFiles = await this.getNesterImportFiles(importFileScss, importFile);
|
|
20
|
+
if (nestedImportFiles) {
|
|
21
|
+
imports.push(...nestedImportFiles);
|
|
22
|
+
// at this moment I don't know if this is an actual mixin
|
|
23
|
+
toUpdateNestedImports.push(...nestedImportFiles);
|
|
24
|
+
} else {
|
|
25
|
+
const mixinFile = await this.processCss(
|
|
26
|
+
importFileScss,
|
|
27
|
+
this.transformRootToMixin(fileImport.file),
|
|
28
|
+
);
|
|
29
|
+
files.push({
|
|
30
|
+
filePath: importFile,
|
|
31
|
+
data: mixinFile.css,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (this.shouldUpdateMixinNameInRootFile(toUpdateNestedImports)) {
|
|
37
|
+
const mixins = this.mapMixinNameToImportFileName(result.mixins);
|
|
38
|
+
// the target file is not updated right away, so the fresh content is in files object
|
|
39
|
+
this.updateRootFileMixinName(files, toUpdateNestedImports, mixins);
|
|
40
|
+
}
|
|
41
|
+
return files;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
shouldUpdateMixinNameInRootFile(toUpdateNestedImports) {
|
|
45
|
+
return toUpdateNestedImports.length > 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
updateRootFileMixinName(filesChanged, toUpdateNestedImports, mixins) {
|
|
49
|
+
const rootFileHash = toUpdateNestedImports.reduce((acc, fileImport) => {
|
|
50
|
+
if (!acc[fileImport.rootFile]) {
|
|
51
|
+
acc[fileImport.rootFile] = [];
|
|
52
|
+
}
|
|
53
|
+
acc[fileImport.rootFile].push(fileImport.file);
|
|
54
|
+
return acc;
|
|
55
|
+
}, {});
|
|
56
|
+
Object.keys(rootFileHash).forEach((rootFile) => {
|
|
57
|
+
const rootFileData = filesChanged.find((file) => file.filePath.includes(this.filePath));
|
|
58
|
+
const mixinImport = rootFileHash[rootFile]
|
|
59
|
+
.map((fileImport) => `@include ${fileImport}`)
|
|
60
|
+
.join(';\n ');
|
|
61
|
+
const mixinName = this.getMixinNameByImportName(mixins, rootFile);
|
|
62
|
+
rootFileData.data = rootFileData.data.replace(
|
|
63
|
+
new RegExp(`@include ${mixinName}`, 'g'),
|
|
64
|
+
mixinImport,
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getMixinNameByImportName(mixins, importName) {
|
|
70
|
+
const found = mixins.find((m) => m.fileName === importName);
|
|
71
|
+
return found.mixinName;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
mapMixinNameToImportFileName(mixins) {
|
|
75
|
+
return mixins.map((mixin) => {
|
|
76
|
+
const fileName = this.findImportedFile(mixin.importName, this.filePath);
|
|
77
|
+
return {
|
|
78
|
+
fileName,
|
|
79
|
+
mixinName: mixin.mixinName,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
transformConditionalImport() {
|
|
85
|
+
return {
|
|
86
|
+
postcssPlugin: 'Transform Conditional Import',
|
|
87
|
+
AtRule: (rule, { AtRule, result }) => {
|
|
88
|
+
if (rule.name === 'if' || rule.name === 'else') {
|
|
89
|
+
rule.walkAtRules('import', (decl) => {
|
|
90
|
+
const newRule = new AtRule({
|
|
91
|
+
name: 'import',
|
|
92
|
+
params: decl.params,
|
|
93
|
+
source: decl.source,
|
|
94
|
+
});
|
|
95
|
+
const root = decl.root();
|
|
96
|
+
root.prepend(newRule);
|
|
97
|
+
decl.name = 'include';
|
|
98
|
+
// remove quotes from import
|
|
99
|
+
const oldName = this.prepareImportFileName(decl.params);
|
|
100
|
+
// replace slash with dash
|
|
101
|
+
const mixinName = oldName.replace(/\//g, '-');
|
|
102
|
+
decl.params = mixinName;
|
|
103
|
+
result.messages.push(oldName);
|
|
104
|
+
// adding new mixin info
|
|
105
|
+
if (!result.mixins) {
|
|
106
|
+
result.mixins = [];
|
|
107
|
+
}
|
|
108
|
+
result.mixins.push({
|
|
109
|
+
importName: oldName,
|
|
110
|
+
mixinName,
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
transformRootToMixin(filename) {
|
|
119
|
+
const self = this;
|
|
120
|
+
return {
|
|
121
|
+
postcssPlugin: 'Transform Root to Mixin',
|
|
122
|
+
Once(root, { AtRule, result }) {
|
|
123
|
+
const mixins = [];
|
|
124
|
+
root.walkAtRules('mixin', (decl) => {
|
|
125
|
+
mixins.push(decl.clone());
|
|
126
|
+
decl.remove();
|
|
127
|
+
});
|
|
128
|
+
// already wrapped in mixin
|
|
129
|
+
if (
|
|
130
|
+
root.nodes.length === 1 &&
|
|
131
|
+
root.nodes[0].type === 'atrule' &&
|
|
132
|
+
root.nodes[0].name === 'mixin'
|
|
133
|
+
) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const nodes = root.nodes.map((node) => {
|
|
137
|
+
const cloned = node.clone();
|
|
138
|
+
cloned.raws.before = '\n';
|
|
139
|
+
return cloned;
|
|
140
|
+
});
|
|
141
|
+
self.formatNodes(nodes);
|
|
142
|
+
const newRoot = new AtRule({
|
|
143
|
+
name: 'mixin',
|
|
144
|
+
params: filename.replace(/\//g, '_'),
|
|
145
|
+
source: root.source,
|
|
146
|
+
nodes,
|
|
147
|
+
});
|
|
148
|
+
if (mixins.length > 0) {
|
|
149
|
+
newRoot.raws.before = '\n';
|
|
150
|
+
}
|
|
151
|
+
result.root.nodes = [...mixins, newRoot];
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
checkForRecursiveImport() {
|
|
157
|
+
const self = this;
|
|
158
|
+
return {
|
|
159
|
+
postcssPlugin: 'Check for Recursive Import',
|
|
160
|
+
Once(root, { result }) {
|
|
161
|
+
const imports = [];
|
|
162
|
+
result.messages = [];
|
|
163
|
+
root.walkAtRules('import', (decl) => {
|
|
164
|
+
imports.push(self.prepareImportFileName(decl.params));
|
|
165
|
+
});
|
|
166
|
+
if (imports.length > 0) {
|
|
167
|
+
result.messages = [...imports];
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async getNesterImportFiles(scss, file) {
|
|
174
|
+
const result = await this.processCss(scss, this.checkForRecursiveImport());
|
|
175
|
+
if (result.messages.length > 0) {
|
|
176
|
+
return this.mapFilesToRootFile(result.messages, file);
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
mapFilesToRootFile(files, rootFile) {
|
|
182
|
+
return files.map((file) => ({
|
|
183
|
+
file,
|
|
184
|
+
rootFile,
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
formatNodes(nodes) {
|
|
189
|
+
if (nodes.length > 0) {
|
|
190
|
+
const spacer = this.getSpacer(nodes[0]);
|
|
191
|
+
this.addTabsToNodes(nodes, spacer);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
addTabsToNodes(nodes, spacer) {
|
|
196
|
+
nodes.forEach((node) => {
|
|
197
|
+
if (node.nodes) {
|
|
198
|
+
node.raws.before = node.raws.before + spacer;
|
|
199
|
+
node.raws.after = node.raws.after + spacer;
|
|
200
|
+
this.addTabsToNodes(node.nodes, spacer);
|
|
201
|
+
} else {
|
|
202
|
+
if (node.raws.before) {
|
|
203
|
+
node.raws.before = node.raws.before + spacer;
|
|
204
|
+
}
|
|
205
|
+
if (node.prop === 'src') {
|
|
206
|
+
node.value = node.value.replace(/\n/g, '\n' + spacer);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
getSpacer(node) {
|
|
213
|
+
const hasTabSpace = this.hasTabSpace(node.raws.before);
|
|
214
|
+
if (hasTabSpace) {
|
|
215
|
+
return '\t';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return ' ';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
hasTabSpace(string) {
|
|
222
|
+
return /\t/g.test(string);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
prepareImportFileName(fileName) {
|
|
226
|
+
return fileName.replace(/['"]+/g, '');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = ConditionalImportFixer;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const BaseFixer = require('./BaseFixer');
|
|
3
|
+
|
|
4
|
+
class UndefinedVariableFixer extends BaseFixer {
|
|
5
|
+
async run(errorMessage) {
|
|
6
|
+
const varName = this.getUndefinedVariableName(errorMessage);
|
|
7
|
+
const value = await this.guessVariableValue(varName);
|
|
8
|
+
|
|
9
|
+
const scss = fs.readFileSync(this.filePath, 'utf8');
|
|
10
|
+
const processedFile = await this.processCss(scss, this.transform(varName, value));
|
|
11
|
+
|
|
12
|
+
return [{ filePath: this.filePath, data: processedFile.css }];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getUndefinedVariableName(errorMessage) {
|
|
16
|
+
const match = errorMessage.match(/\$([a-zA-Z]{1,})/gi);
|
|
17
|
+
if (!match) {
|
|
18
|
+
throw new Error("Couldn't detemine undefined variable name!");
|
|
19
|
+
}
|
|
20
|
+
return match[0];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
transform(varName, value) {
|
|
24
|
+
return {
|
|
25
|
+
postcssPlugin: 'Declare unvariable variable value',
|
|
26
|
+
Once(root, { Declaration }) {
|
|
27
|
+
const newRule = new Declaration({
|
|
28
|
+
value,
|
|
29
|
+
prop: varName,
|
|
30
|
+
source: '',
|
|
31
|
+
});
|
|
32
|
+
root.prepend(newRule);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async guessVariableValue(varName) {
|
|
38
|
+
const scss = fs.readFileSync(this.filePath, 'utf8');
|
|
39
|
+
const processedFile = await this.processCss(
|
|
40
|
+
scss,
|
|
41
|
+
this.transformForGuessingVariableValue(varName),
|
|
42
|
+
);
|
|
43
|
+
return processedFile.varValue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
transformForGuessingVariableValue(varName) {
|
|
47
|
+
return {
|
|
48
|
+
postcssPlugin: 'Get first variable value found in the file',
|
|
49
|
+
Declaration: (decl, { result }) => {
|
|
50
|
+
if (decl.prop === varName) {
|
|
51
|
+
// eslint-disable-next-line no-param-reassign
|
|
52
|
+
result.varValue = decl.value;
|
|
53
|
+
// todo propertly break on first found declaration
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = UndefinedVariableFixer;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bigcommerce/stencil-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-beta1",
|
|
4
4
|
"description": "CLI tool to run BigCommerce Stores locally for theme development.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
8
|
-
"npm": ">=
|
|
7
|
+
"node": ">=16.0.0 <19.0.0",
|
|
8
|
+
"npm": ">=8.0.0 <10.0.0"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"lint": "eslint . && prettier --check . --ignore-unknown",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"stencil-pull": "./bin/stencil-pull.js",
|
|
24
24
|
"stencil-start": "./bin/stencil-start.js",
|
|
25
25
|
"stencil-release": "./bin/stencil-release.js",
|
|
26
|
-
"stencil-debug": "./bin/stencil-debug.js"
|
|
26
|
+
"stencil-debug": "./bin/stencil-debug.js",
|
|
27
|
+
"stencil-scss-autofix": "./bin/stencil-scss-autofix.js"
|
|
27
28
|
},
|
|
28
29
|
"config": {
|
|
29
30
|
"stencil_version": "1.0"
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
],
|
|
49
50
|
"dependencies": {
|
|
50
51
|
"@bigcommerce/stencil-paper": "4.2.1",
|
|
51
|
-
"@bigcommerce/stencil-styles": "
|
|
52
|
+
"@bigcommerce/stencil-styles": "5.1.0",
|
|
52
53
|
"@hapi/boom": "^10.0.0",
|
|
53
54
|
"@hapi/glue": "^8.0.0",
|
|
54
55
|
"@hapi/h2o2": "^9.1.0",
|
|
@@ -77,6 +78,8 @@
|
|
|
77
78
|
"npm-which": "^3.0.1",
|
|
78
79
|
"object-to-spawn-args": "^2.0.0",
|
|
79
80
|
"parse-json": "^5.2.0",
|
|
81
|
+
"postcss": "^8.4.21",
|
|
82
|
+
"postcss-scss": "^4.0.6",
|
|
80
83
|
"progress": "^2.0.3",
|
|
81
84
|
"recursive-readdir": "^2.2.2",
|
|
82
85
|
"semver": "^7.3.2",
|