@bigcommerce/stencil-cli 6.1.0 → 6.1.1

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 CHANGED
@@ -7,8 +7,9 @@ The BigCommerce server emulator for local theme development.
7
7
 
8
8
  ## Install
9
9
 
10
- Note: Stencil requires the Node.js runtime environment,
11
- versions 16.x, 18.x are supported.
10
+ _Note: Stencil requires the Node.js runtime environment,
11
+ version 14.x is supported.
12
+ We do not yet have support for versions greater than Node 14._
12
13
 
13
14
  Run `npm install -g @bigcommerce/stencil-cli`.
14
15
 
package/bin/stencil.js CHANGED
@@ -16,5 +16,4 @@ 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')
20
19
  .parse(process.argv);
@@ -13,6 +13,14 @@ 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
+ }
16
24
  }
17
25
 
18
26
  const settings = new StencilCLISettings();
@@ -14,7 +14,8 @@ 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 ScssValidator = require('./ScssValidator');
17
+ const cssCompiler = require('./css/compile');
18
+ const stencilCLISettings = require('./StencilCLISettings');
18
19
 
19
20
  const VALID_IMAGE_TYPES = ['.jpg', '.jpeg', '.png', '.gif'];
20
21
  const WIDTH_COMPOSED = 600;
@@ -27,6 +28,8 @@ const MAX_SIZE_COMPOSED = 1024 * 1024 * 2; // 2MB
27
28
  const MAX_SIZE_MOBILE = 1024 * 1024; // 1MB
28
29
  const MAX_SIZE_DESKTOP = 1024 * 1024 * 5; // 5MB
29
30
 
31
+ /* eslint-disable no-useless-escape */
32
+ const STYLESHEET_REGEXP = /{{\s*stylesheet\s*([\/a-zA-Z'"\.-]+)\s*}}/i;
30
33
  class BundleValidator {
31
34
  /**
32
35
  * Run some validations to ensure that the platform will accept the theme
@@ -41,7 +44,6 @@ class BundleValidator {
41
44
  this.themePath = themePath;
42
45
  this.objectsToValidate = ['head.scripts', 'footer.scripts'];
43
46
  this.jsonSchemaValidatorOptions = { schemaId: 'auto', allErrors: true };
44
- this.scssValidator = new ScssValidator(themePath, themeConfig);
45
47
 
46
48
  // Array of tasks used in async.series
47
49
  this.validationTasks = [
@@ -406,7 +408,109 @@ class BundleValidator {
406
408
  }
407
409
 
408
410
  async _validateCssFiles() {
409
- await this.scssValidator.run();
411
+ const assetsPath = path.join(this.themePath, 'assets');
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;
410
514
  }
411
515
 
412
516
  validateTrailingSymbols(data) {
package/lib/cliCommon.js CHANGED
@@ -98,6 +98,7 @@ function applyCommonOptions(program) {
98
98
 
99
99
  function setupCLI(options) {
100
100
  stencilCLISettings.setVerbose(options.verbose);
101
+ stencilCLISettings.useOldNodeSassFork(options.useOldNodeSassFork);
101
102
  }
102
103
 
103
104
  function prepareCommand(program) {
@@ -4,6 +4,7 @@ const { promisify } = require('util');
4
4
 
5
5
  const cssAssembler = require('../css-assembler');
6
6
 
7
+ const FALLBACK_SASS_ENGINE_NAME = 'node-sass-fork';
7
8
  const SASS_ENGINE_NAME = 'node-sass';
8
9
 
9
10
  const compile = async (configuration, themeAssetsPath, fileName, engineName = SASS_ENGINE_NAME) => {
@@ -28,7 +29,7 @@ const compile = async (configuration, themeAssetsPath, fileName, engineName = SA
28
29
  sourceMap: true,
29
30
  autoprefixerOptions: {
30
31
  cascade: configuration.autoprefixer_cascade,
31
- overrideBrowserslist: configuration.autoprefixer_browsers,
32
+ browsers: configuration.autoprefixer_browsers,
32
33
  },
33
34
  };
34
35
  const stencilStyles = new StencilStyles(console);
@@ -40,4 +41,5 @@ const compile = async (configuration, themeAssetsPath, fileName, engineName = SA
40
41
  module.exports = {
41
42
  compile,
42
43
  SASS_ENGINE_NAME,
44
+ FALLBACK_SASS_ENGINE_NAME,
43
45
  };
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@bigcommerce/stencil-cli",
3
- "version": "6.1.0",
3
+ "version": "6.1.1",
4
4
  "description": "CLI tool to run BigCommerce Stores locally for theme development.",
5
5
  "main": "index.js",
6
6
  "engines": {
7
- "node": ">=16.0.0 <19.0.0",
8
- "npm": ">=8.0.0 <10.0.0"
7
+ "node": ">=14.0.0 <15.0.0",
8
+ "npm": ">=v6.11.3 <9.0.0"
9
9
  },
10
10
  "scripts": {
11
11
  "lint": "eslint . && prettier --check . --ignore-unknown",
@@ -23,8 +23,7 @@
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",
27
- "stencil-scss-autofix": "./bin/stencil-scss-autofix.js"
26
+ "stencil-debug": "./bin/stencil-debug.js"
28
27
  },
29
28
  "config": {
30
29
  "stencil_version": "1.0"
@@ -49,7 +48,7 @@
49
48
  ],
50
49
  "dependencies": {
51
50
  "@bigcommerce/stencil-paper": "4.2.1",
52
- "@bigcommerce/stencil-styles": "5.1.0",
51
+ "@bigcommerce/stencil-styles": "4.0.0",
53
52
  "@hapi/boom": "^10.0.0",
54
53
  "@hapi/glue": "^8.0.0",
55
54
  "@hapi/h2o2": "^9.1.0",
@@ -78,8 +77,6 @@
78
77
  "npm-which": "^3.0.1",
79
78
  "object-to-spawn-args": "^2.0.0",
80
79
  "parse-json": "^5.2.0",
81
- "postcss": "^8.4.21",
82
- "postcss-scss": "^4.0.6",
83
80
  "progress": "^2.0.3",
84
81
  "recursive-readdir": "^2.2.2",
85
82
  "semver": "^7.3.2",
@@ -1,23 +0,0 @@
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/NodeSassAutoFixer');
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);
@@ -1,253 +0,0 @@
1
- /* eslint-disable no-param-reassign, operator-assignment */
2
- require('colors');
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
- const postcss = require('postcss');
7
- const postcssScss = require('postcss-scss');
8
-
9
- const ScssValidator = require('./ScssValidator');
10
- const cssCompiler = require('./css/compile');
11
-
12
- const CONDITIONAL_IMPORT = 'conditional-import';
13
-
14
- class NodeSassAutoFixer {
15
- /**
16
- *
17
- * @param themePath
18
- * @param themeConfig
19
- * @param cliOptions
20
- */
21
- constructor(themePath, themeConfig, cliOptions) {
22
- this.themePath = themePath;
23
- this.themeConfig = themeConfig;
24
- this.cliOptions = cliOptions;
25
-
26
- this.validator = new ScssValidator(themePath, themeConfig);
27
- }
28
-
29
- async run() {
30
- const assetsPath = path.join(this.themePath, 'assets');
31
- const rawConfig = await this.themeConfig.getConfig();
32
- const cssFiles = await this.validator.getCssFiles();
33
-
34
- let issuesDetected = false;
35
- /* eslint-disable-next-line no-useless-catch */
36
- try {
37
- for await (const file of cssFiles) {
38
- try {
39
- /* eslint-disable-next-line no-await-in-loop */
40
- await cssCompiler.compile(
41
- rawConfig,
42
- assetsPath,
43
- file,
44
- cssCompiler.SASS_ENGINE_NAME,
45
- );
46
- } catch (e) {
47
- issuesDetected = true;
48
- await this.tryToFix(e, file);
49
- }
50
- }
51
- if (!issuesDetected) {
52
- console.log('No issues deteted');
53
- }
54
- } catch (e) {
55
- throw e;
56
- }
57
- }
58
-
59
- async tryToFix(err, file) {
60
- const problem = this.detectProblem(err);
61
- if (problem) {
62
- const dirname = path.join(this.themePath, 'assets/scss');
63
- const filePath = this.resolveScssFileName(dirname, err.file);
64
- if (problem === CONDITIONAL_IMPORT) {
65
- await this.fixConditionalImport(filePath);
66
- }
67
- } else {
68
- const filePath = path.join(this.themePath, 'assets/scss', file + '.scss');
69
- console.log("Couldn't determine and autofix the problem. Please fix it manually.".red);
70
- console.log('Found trying compile file:'.red, filePath);
71
- throw new Error(err);
72
- }
73
- }
74
-
75
- detectProblem(err) {
76
- if (err.formatted) {
77
- if (
78
- err.formatted.includes(
79
- 'Error: Import directives may not be used within control directives or mixins',
80
- )
81
- ) {
82
- return CONDITIONAL_IMPORT;
83
- }
84
- }
85
-
86
- return null;
87
- }
88
-
89
- async fixConditionalImport(filePath) {
90
- const scss = fs.readFileSync(filePath, 'utf8');
91
- const condImportFile = await this.processCss(scss, this.transformConditionalImport());
92
-
93
- this.overrideFile(filePath, condImportFile.css);
94
- for await (const message of condImportFile.messages) {
95
- if (message.type === 'import') {
96
- const importFile = this.findImportedFile(message.filename, filePath);
97
- const importFileScss = fs.readFileSync(importFile);
98
- const mixinFile = await this.processCss(
99
- importFileScss,
100
- this.transformRootToMixin(message),
101
- );
102
- this.overrideFile(importFile, mixinFile.css);
103
- }
104
- }
105
- }
106
-
107
- transformConditionalImport() {
108
- return {
109
- postcssPlugin: 'Transform Conditional Import',
110
- AtRule: (rule, { AtRule, result }) => {
111
- if (rule.name === 'if') {
112
- rule.walkAtRules('import', (decl) => {
113
- const newRule = new AtRule({
114
- name: 'import',
115
- params: decl.params,
116
- source: decl.source,
117
- });
118
- const root = decl.root();
119
- root.prepend(newRule);
120
- decl.name = 'include';
121
- decl.params = decl.params.replace(/['"]+/g, '');
122
- result.messages.push({
123
- type: 'import',
124
- filename: decl.params,
125
- });
126
- });
127
- }
128
- },
129
- };
130
- }
131
-
132
- transformRootToMixin(data) {
133
- const self = this;
134
- return {
135
- postcssPlugin: 'Transform Root to Mixin',
136
- Once(root, { AtRule, result }) {
137
- // already wrapped in mixin
138
- if (
139
- root.nodes.length === 1 &&
140
- root.nodes[0].type === 'atrule' &&
141
- root.nodes[0].name === 'mixin'
142
- ) {
143
- return;
144
- }
145
- const nodes = root.nodes.map((node) => {
146
- const cloned = node.clone();
147
- cloned.raws.before = '\n';
148
- return cloned;
149
- });
150
- self.formatNodes(nodes);
151
- const newRoot = new AtRule({
152
- name: 'mixin',
153
- params: data.filename,
154
- source: root.source,
155
- nodes,
156
- });
157
- result.root.nodes = [newRoot];
158
- },
159
- };
160
- }
161
-
162
- formatNodes(nodes) {
163
- const spacer = this.getSpacer(nodes[0]);
164
- this.addTabsToNodes(nodes, spacer);
165
- }
166
-
167
- addTabsToNodes(nodes, spacer) {
168
- nodes.forEach((node) => {
169
- if (node.nodes) {
170
- node.raws.before = node.raws.before + spacer;
171
- node.raws.after = node.raws.after + spacer;
172
- this.addTabsToNodes(node.nodes, spacer);
173
- } else {
174
- if (node.raws.before) {
175
- node.raws.before = node.raws.before + spacer;
176
- }
177
- if (node.prop === 'src') {
178
- node.value = node.value.replace(/\n/g, '\n' + spacer);
179
- }
180
- }
181
- });
182
- }
183
-
184
- getSpacer(node) {
185
- const hasTabSpace = this.hasTabSpace(node.raws.before);
186
- if (hasTabSpace) {
187
- return '\t';
188
- }
189
-
190
- return ' ';
191
- }
192
-
193
- hasTabSpace(string) {
194
- return /\t/g.test(string);
195
- }
196
-
197
- async processCss(data, plugin) {
198
- const processor = postcss([plugin]);
199
- return processor.process(data, { from: undefined, parser: postcssScss });
200
- }
201
-
202
- findImportedFile(file, originalFilePath) {
203
- const originalDirname = path.dirname(originalFilePath);
204
- return this.resolveScssFileName(originalDirname, file);
205
- }
206
-
207
- resolveScssFileName(dirname, fileName) {
208
- if (!fileName.includes('.scss')) {
209
- fileName += '.scss';
210
- }
211
- const filePath = path.join(dirname, fileName);
212
- if (!fs.existsSync(filePath)) {
213
- // try with underscore
214
- const withUnderscoreFileName = this.getFileNameWithUnderscore(fileName);
215
- const filePathWithUnderscore = path.join(dirname, withUnderscoreFileName);
216
- if (!fs.existsSync(filePathWithUnderscore)) {
217
- throw new Error(
218
- `Import ${fileName} wasn't resolved in ${filePath} or ${filePathWithUnderscore}`,
219
- );
220
- }
221
- return filePathWithUnderscore;
222
- }
223
-
224
- return filePath;
225
- }
226
-
227
- getFileNameWithUnderscore(fileName) {
228
- const fileNameParts = fileName.split('/');
229
- const withUnderscoreFileName = fileNameParts
230
- .map((part, i) => {
231
- if (i === fileNameParts.length - 1) {
232
- return '_' + part;
233
- }
234
- return part;
235
- })
236
- .join('/');
237
- return withUnderscoreFileName;
238
- }
239
-
240
- overrideFile(filePath, data) {
241
- const phrase = this.cliOptions.dry ? 'Would override' : 'Overriding';
242
- console.log(phrase.green + ' file:'.green, filePath);
243
- if (this.cliOptions.dry) {
244
- console.log('---Content---'.yellow);
245
- console.log(data);
246
- console.log('---END of Content---'.yellow);
247
- } else {
248
- fs.writeFileSync(filePath, data);
249
- }
250
- }
251
- }
252
-
253
- module.exports = NodeSassAutoFixer;
@@ -1,119 +0,0 @@
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;