11#!/usr/bin/env node
22
33require ( 'colors' ) ;
4- const bs = require ( 'browser-sync' ) . create ( ) ;
5- const recursiveRead = require ( 'recursive-readdir' ) ;
6- const async = require ( 'async' ) ;
7- const fetch = require ( 'node-fetch' ) ;
8- const fs = require ( 'fs' ) ;
9- const path = require ( 'path' ) ;
10-
11- const Cycles = require ( '../lib/Cycles' ) ;
12- const templateAssembler = require ( '../lib/template-assembler' ) ;
13- const { PACKAGE_INFO , DOT_STENCIL_FILE_PATH , THEME_PATH } = require ( '../constants' ) ;
4+ const { PACKAGE_INFO , DOT_STENCIL_FILE_PATH } = require ( '../constants' ) ;
145const program = require ( '../lib/commander' ) ;
15- const Server = require ( '../server' ) ;
16- const ThemeConfig = require ( '../lib/theme-config' ) ;
17- const BuildConfigManager = require ( '../lib/BuildConfigManager' ) ;
18- const { parseJsonFile } = require ( '../lib/utils/fsUtils' ) ;
19- const { checkNodeVersion } = require ( '../lib/cliCommon' ) ;
6+ const StencilStart = require ( '../lib/stencil-start' ) ;
7+ const { printCliResultErrorAndExit } = require ( '../lib/cliCommon' ) ;
208
219program
2210 . version ( PACKAGE_INFO . version )
@@ -32,262 +20,6 @@ program
3220 )
3321 . parse ( process . argv ) ;
3422
35- const cliOptions = program . opts ( ) ;
36- const templatePath = path . join ( THEME_PATH , 'templates' ) ;
37- const themeConfig = ThemeConfig . getInstance ( THEME_PATH ) ;
38-
39- // tunnel value should be true/false or a string with name
40- // https://browsersync.io/docs/options#option-tunnel
41- // convert undefined/true -> false/true
42- const tunnel =
43- typeof cliOptions . tunnel === 'string' ? cliOptions . tunnel : Boolean ( cliOptions . tunnel ) ;
44-
45- checkNodeVersion ( ) ;
46-
47- if ( ! fs . existsSync ( DOT_STENCIL_FILE_PATH ) ) {
48- throw new Error ( 'Please run' . red + ' $ stencil init' . cyan + ' first.' . red ) ;
49- }
50-
51- if ( ! fs . existsSync ( themeConfig . configPath ) ) {
52- throw new Error (
53- `${ 'You must have a ' . red + 'config.json' . cyan } file in your top level theme directory.` ,
54- ) ;
55- }
56-
57- // If the value is true it means that no variation was passed in.
58- if ( cliOptions . variation === true ) {
59- throw new Error ( 'You have to specify a value for -v or --variation' . red ) ;
60- }
61-
62- if ( cliOptions . variation ) {
63- try {
64- themeConfig . setVariationByName ( cliOptions . variation ) ;
65- } catch ( err ) {
66- throw new Error (
67- 'Error: The variation ' . red +
68- cliOptions . variation +
69- ' does not exists in your config.json file' . red ,
70- ) ;
71- }
72- }
73-
74- const dotStencilFile = parseJsonFile ( DOT_STENCIL_FILE_PATH ) ;
75- const browserSyncPort = dotStencilFile . port ;
76- dotStencilFile . port = Number ( dotStencilFile . port ) + 1 ;
77-
78- if ( ! dotStencilFile . normalStoreUrl || ! dotStencilFile . customLayouts ) {
79- throw new Error (
80- 'Error: Your stencil config is outdated. Please run' . red +
81- ' $ stencil init' . cyan +
82- ' again.' . red ,
83- ) ;
84- }
85-
86- /**
87- *
88- * @param {object } stencilConfig
89- * @param {string } currentCliVersion
90- * @returns {Promise<object> }
91- */
92- async function runAPICheck ( stencilConfig , currentCliVersion ) {
93- const staplerUrl = stencilConfig . staplerUrl
94- ? stencilConfig . staplerUrl
95- : stencilConfig . normalStoreUrl ;
96- const reqUrl = new URL ( `/stencil-version-check?v=${ currentCliVersion } ` , staplerUrl ) ;
97- let payload ;
98-
99- const headers = {
100- 'stencil-cli' : currentCliVersion ,
101- } ;
102- if ( stencilConfig . staplerUrl ) {
103- headers [ 'stencil-store-url' ] = stencilConfig . normalStoreUrl ;
104- }
105-
106- try {
107- const response = await fetch ( reqUrl , { headers } ) ;
108- if ( ! response . ok ) {
109- throw new Error ( response . statusText ) ;
110- }
111- payload = await response . json ( ) ;
112- if ( ! payload ) {
113- throw new Error ( 'Empty payload in the server response' ) ;
114- }
115- } catch ( err ) {
116- throw new Error (
117- 'The BigCommerce Store you are pointing to either does not exist or is not available at this time.'
118- . red +
119- '\nError details:\n' +
120- err . message ,
121- ) ;
122- }
123- if ( payload . error ) {
124- throw new Error ( payload . error . red ) ;
125- }
126- if ( payload . status !== 'ok' ) {
127- throw new Error (
128- 'Error: You are using an outdated version of stencil-cli, please run ' . red +
129- '$ npm install -g @bigcommerce/stencil-cli' . cyan ,
130- ) ;
131- }
132- return payload ;
133- }
134-
135- /**
136- * Assembles all the needed templates and resolves their partials.
137- * @param {string } templatesPath
138- * @param {function } callback
139- */
140- function assembleTemplates ( templatesPath , callback ) {
141- recursiveRead ( templatesPath , [ '!*.html' ] , ( err , files ) => {
142- const templateNames = files . map ( ( file ) =>
143- file . replace ( templatesPath + templatesPath . sep , '' ) . replace ( '.html' , '' ) ,
144- ) ;
145-
146- async . map (
147- templateNames ,
148- templateAssembler . assemble . bind ( null , templatesPath ) ,
149- ( err2 , results ) => {
150- if ( err2 ) {
151- callback ( err2 ) ;
152- }
153- callback ( null , results ) ;
154- } ,
155- ) ;
156- } ) ;
157- }
158-
159- /**
160- * Displays information about your environment and configuration.
161- * @returns {string }
162- */
163- function getStartUpInfo ( ) {
164- let information = '\n' ;
165-
166- information += '-----------------Startup Information-------------\n' . gray ;
167- information += '\n' ;
168- information += `.stencil location: ${ DOT_STENCIL_FILE_PATH . cyan } \n` ;
169- information += `config.json location: ${ themeConfig . configPath . cyan } \n` ;
170- information += `Store URL: ${ dotStencilFile . normalStoreUrl . cyan } \n` ;
171-
172- if ( dotStencilFile . staplerUrl ) {
173- information += `Stapler URL: ${ dotStencilFile . staplerUrl . cyan } \n` ;
174- }
175-
176- information += `SSL Store URL: ${ dotStencilFile . storeUrl . cyan } \n` ;
177- information += `Node Version: ${ process . version . cyan } \n` ;
178- information += '\n' ;
179- information += '-------------------------------------------------\n' . gray ;
180-
181- return information ;
182- }
183-
184- /**
185- * Starts up the local Stencil Server as well as starts up BrowserSync and sets some watch options.
186- */
187- async function startServer ( ) {
188- await Server . create ( {
189- dotStencilFile,
190- variationIndex : themeConfig . variationIndex || 0 ,
191- useCache : cliOptions . cache ,
192- themePath : THEME_PATH ,
193- } ) ;
194-
195- const buildConfigManger = new BuildConfigManager ( ) ;
196- let watchFiles = [ '/assets' , '/templates' , '/lang' , '/.config' ] ;
197- let watchIgnored = [ '/assets/scss' , '/assets/css' ] ;
198-
199- // Display Set up information
200- console . log ( getStartUpInfo ( ) ) ;
201-
202- // Watch sccs directory and automatically reload all css files if a file changes
203- bs . watch ( path . join ( THEME_PATH , 'assets/scss' ) , ( event ) => {
204- if ( event === 'change' ) {
205- bs . reload ( '*.css' ) ;
206- }
207- } ) ;
208-
209- bs . watch ( 'config.json' , ( event ) => {
210- if ( event === 'change' ) {
211- themeConfig . resetVariationSettings ( ) ;
212- bs . reload ( ) ;
213- }
214- } ) ;
215-
216- bs . watch ( '.config/storefront.json' , ( event , file ) => {
217- if ( event === 'change' ) {
218- console . log ( 'storefront json changed' ) ;
219- bs . emitter . emit ( 'storefront_config_file:changed' , {
220- event,
221- path : file ,
222- namespace : '' ,
223- } ) ;
224- bs . reload ( ) ;
225- }
226- } ) ;
227-
228- bs . watch ( templatePath , { ignoreInitial : true } , ( ) => {
229- assembleTemplates ( templatePath , ( err , results ) => {
230- if ( err ) {
231- console . error ( err ) ;
232- return ;
233- }
234-
235- try {
236- new Cycles ( results ) . detect ( ) ;
237- } catch ( e ) {
238- console . error ( e ) ;
239- }
240- } ) ;
241- } ) ;
242-
243- if ( buildConfigManger . watchOptions && buildConfigManger . watchOptions . files ) {
244- watchFiles = buildConfigManger . watchOptions . files ;
245- }
246-
247- if ( buildConfigManger . watchOptions && buildConfigManger . watchOptions . ignored ) {
248- watchIgnored = buildConfigManger . watchOptions . ignored ;
249- }
250-
251- bs . init ( {
252- open : ! ! cliOptions . open ,
253- port : browserSyncPort ,
254- files : watchFiles . map ( ( val ) => path . join ( THEME_PATH , val ) ) ,
255- watchOptions : {
256- ignoreInitial : true ,
257- ignored : watchIgnored . map ( ( val ) => path . join ( THEME_PATH , val ) ) ,
258- } ,
259- proxy : `localhost:${ dotStencilFile . port } ` ,
260- tunnel,
261- } ) ;
262-
263- // Handle manual reloading of browsers by typing 'rs';
264- // Borrowed from https://github.com/remy/nodemon
265- process . stdin . resume ( ) ;
266- process . stdin . setEncoding ( 'utf8' ) ;
267- process . stdin . on ( 'data' , ( data ) => {
268- const normalizedData = `${ data } ` . trim ( ) . toLowerCase ( ) ;
269-
270- // if the keys entered match the restartable value, then restart!
271- if ( normalizedData === 'rs' ) {
272- bs . reload ( ) ;
273- }
274- } ) ;
275-
276- if ( buildConfigManger . development ) {
277- buildConfigManger . initWorker ( ) . development ( bs ) ;
278- }
279- }
280-
281- async function run ( ) {
282- try {
283- const storeInfoFromAPI = await runAPICheck ( dotStencilFile , PACKAGE_INFO . version ) ;
284- dotStencilFile . storeUrl = storeInfoFromAPI . sslUrl ;
285- dotStencilFile . normalStoreUrl = storeInfoFromAPI . baseUrl ;
286-
287- await startServer ( ) ;
288- } catch ( err ) {
289- console . error ( err . message ) ;
290- }
291- }
292-
293- run ( ) ;
23+ new StencilStart ( )
24+ . run ( program . opts ( ) , DOT_STENCIL_FILE_PATH , PACKAGE_INFO . version )
25+ . catch ( printCliResultErrorAndExit ) ;
0 commit comments