@caweb/cli 1.2.0 → 1.3.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.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/bin/caweb +1 -1
  3. package/bin/wp-cli.phar +0 -0
  4. package/commands/a11y.js +74 -0
  5. package/{lib/commands → commands}/blocks/create-block.js +7 -8
  6. package/{lib/commands → commands}/blocks/update-block.js +3 -9
  7. package/commands/build.js +73 -0
  8. package/{lib/commands → commands/env}/destroy.js +15 -22
  9. package/{lib/commands → commands/env}/start.js +26 -27
  10. package/{lib/commands → commands/env}/stop.js +4 -10
  11. package/{lib/commands → commands}/index.js +51 -43
  12. package/commands/serve.js +78 -0
  13. package/commands/sync.js +498 -0
  14. package/{lib/commands → commands}/tasks/update-plugins.js +2 -2
  15. package/commands/test.js +100 -0
  16. package/configs/aceconfig.js +28 -0
  17. package/{lib/configs.js → configs/docker-compose.js} +30 -83
  18. package/configs/webpack.config.js +119 -0
  19. package/configs/wp-env.js +76 -0
  20. package/gen/parser.js +166 -0
  21. package/gen/site-generator.js +111 -0
  22. package/lib/admin.js +1 -1
  23. package/lib/cli.js +105 -63
  24. package/lib/helpers.js +109 -0
  25. package/lib/index.js +28 -0
  26. package/lib/spinner.js +10 -7
  27. package/lib/{caweb.js → wordpress/caweb.js} +1 -1
  28. package/lib/{divi.js → wordpress/divi.js} +1 -1
  29. package/lib/{download-sources.js → wordpress/download-sources.js} +74 -78
  30. package/lib/wordpress/index.js +16 -0
  31. package/lib/{wordpress.js → wordpress/wordpress.js} +4 -8
  32. package/package.json +41 -27
  33. package/lib/commands/test.js +0 -46
  34. package/lib/utils.js +0 -150
  35. /package/{lib/commands → commands/tasks}/shell.js +0 -0
  36. /package/lib/{options.js → wordpress/options.js} +0 -0
  37. /package/{lib/template → template}/assets/css/popover.css +0 -0
  38. /package/{lib/template → template}/assets/js/popover.js +0 -0
  39. /package/{lib/template → template}/block/edit.js.mustache +0 -0
  40. /package/{lib/template → template}/block/editor.scss.mustache +0 -0
  41. /package/{lib/template → template}/block/index.js.mustache +0 -0
  42. /package/{lib/template → template}/block/save.js.mustache +0 -0
  43. /package/{lib/template → template}/block/style.scss.mustache +0 -0
  44. /package/{lib/template → template}/index.cjs +0 -0
  45. /package/{lib/template → template}/plugin/$slug.php.mustache +0 -0
  46. /package/{lib/template → template}/plugin/core/cdec-api.php.mustache +0 -0
  47. /package/{lib/template → template}/plugin/core/filters.php.mustache +0 -0
  48. /package/{lib/template → template}/plugin/core/functions.php.mustache +0 -0
  49. /package/{lib/template → template}/plugin/inc/renderer.php.mustache +0 -0
package/lib/cli.js CHANGED
@@ -3,66 +3,54 @@
3
3
  */
4
4
  //const wpenv_cli = require('@wordpress/env/lib/cli');
5
5
 
6
- import path from 'node:path';
6
+ import path from 'path';
7
7
  import chalk from 'chalk';
8
- import parseXdebugMode from '@wordpress/env/lib/parse-xdebug-mode.js';
9
- import fs from 'fs-extra';
8
+ import fs from 'fs';
10
9
  import terminalLink from 'terminal-link';
11
- import { Command, Argument, Option } from 'commander';
10
+ import { Command, Argument, Option } from 'commander';
12
11
 
13
12
  /**
14
13
  * Internal dependencies
15
14
  */
16
- import * as env from './commands/index.js';
15
+ import * as env from '../commands/index.js';
17
16
  import {
18
17
  wpPrimary,
19
18
  wpGreen,
20
19
  wpYellow,
21
20
  wpRed,
22
- withSpinner
23
- } from './spinner.js';
21
+ withSpinner,
22
+ projectPath,
23
+ } from './index.js';
24
24
 
25
- const localPath = path.resolve( path.join(process.cwd(), 'node_modules/@caweb/cli/package.json') )
26
- const pkg = JSON.parse( await fs.readFile(localPath) );
25
+ const localFile = path.join(projectPath, 'package.json');
26
+ const pkg = JSON.parse( fs.readFileSync(localFile) );
27
27
  const program = new Command();
28
28
 
29
- export default function cli() {
30
- const envArg = new Argument('[environment]', 'Which environment to use.')
31
- .choices([
32
- 'development',
33
- 'tests',
34
- 'all'
35
- ])
36
- .default('development');
37
-
38
- const containerArg = new Argument('<container>', 'The underlying Docker service to run the command on.')
39
- .choices([
40
- 'mysql',
41
- 'tests-mysql',
42
- 'wordpress',
43
- 'tests-wordpress',
44
- 'cli',
45
- 'tests-cli',
46
- ])
47
- .default('development');
29
+ const containerArg = new Argument('<container>', 'The underlying Docker service to run the command on.')
30
+ .choices([
31
+ 'mysql',
32
+ 'tests-mysql',
33
+ 'wordpress',
34
+ 'tests-wordpress',
35
+ 'cli',
36
+ 'tests-cli',
37
+ ])
38
+ .default('development');
48
39
 
49
- program
50
- .name(wpPrimary( 'caweb'))
51
- .usage( wpYellow( '<command>' ) )
52
- .description('Command Line Interface utilized by CAWebPublishing to accomplish several tasks.')
53
- .version( pkg.version )
54
- .option(
55
- '--debug',
56
- 'Enable debug output.',
57
- false
58
- )
59
- .allowUnknownOption(true)
60
- .configureHelp({
61
- sortSubcommands: true,
62
- sortOptions: true,
63
- showGlobalOptions: true,
64
- })
65
- .addHelpCommand(false)
40
+ const envArg = new Argument('[environment]', 'Which environment to use.')
41
+ .choices([
42
+ 'development',
43
+ 'tests',
44
+ 'all'
45
+ ])
46
+ .default('development');
47
+
48
+
49
+ /**
50
+ * Adds commands for wp-env
51
+ */
52
+ function addWPEnvCommands(){
53
+
66
54
 
67
55
  // Start command.
68
56
  program.command('start')
@@ -128,13 +116,6 @@ export default function cli() {
128
116
  .allowUnknownOption(true)
129
117
  .action( withSpinner(env.destroy) )
130
118
 
131
- // Shell Command.
132
- program.command('shell')
133
- .description('Open shell terminal in WordPress environment.')
134
- .addArgument(envArg)
135
- .allowUnknownOption(true)
136
- .action( withSpinner(env.shell) )
137
-
138
119
  // Stop Command.
139
120
  program.command('stop')
140
121
  .description(
@@ -145,6 +126,12 @@ export default function cli() {
145
126
  .allowUnknownOption(true)
146
127
  .action( withSpinner(env.stop) )
147
128
 
129
+ // Install Path Command.
130
+ program.command('install-path')
131
+ .description('Get the path where all of the environment files are stored. This includes the Docker files, WordPress, PHPUnit files, and any sources that were downloaded.')
132
+ .allowUnknownOption(true)
133
+ .action( withSpinner(env.installPath) )
134
+
148
135
  // Clean Command.
149
136
  program.command('clean')
150
137
  .description(
@@ -172,7 +159,7 @@ export default function cli() {
172
159
  )
173
160
  .allowUnknownOption(true)
174
161
  .action( withSpinner(env.logs) )
175
-
162
+
176
163
 
177
164
  // Run Command.
178
165
  program.command('run')
@@ -189,12 +176,60 @@ export default function cli() {
189
176
  .allowUnknownOption(true)
190
177
  .action( withSpinner(env.run) )
191
178
 
179
+
180
+ // Shell Command.
181
+ program.command('shell')
182
+ .description('Open shell terminal in WordPress environment.')
183
+ .addArgument(envArg)
184
+ .allowUnknownOption(true)
185
+ .action( withSpinner(env.shell) )
186
+
187
+ }
188
+
189
+ export default function cli() {
190
+
191
+
192
+ program
193
+ .name(wpPrimary( 'caweb'))
194
+ .usage( wpYellow( '<command>' ) )
195
+ .description('Command Line Interface utilized by CAWebPublishing to accomplish several tasks.')
196
+ .version( pkg.version )
197
+ .option(
198
+ '--debug',
199
+ 'Enable debug output.',
200
+ false
201
+ )
202
+ .allowUnknownOption(true)
203
+ .configureHelp({
204
+ sortSubcommands: true,
205
+ sortOptions: true,
206
+ showGlobalOptions: true,
207
+ })
208
+ .helpCommand(false)
209
+
192
210
 
193
- // Install Path Command.
194
- program.command('install-path')
195
- .description('Get the path where all of the environment files are stored. This includes the Docker files, WordPress, PHPUnit files, and any sources that were downloaded.')
211
+ // Build Command.
212
+ program.command('build')
213
+ .description('Builds the current project.')
196
214
  .allowUnknownOption(true)
197
- .action( withSpinner(env.installPath) )
215
+ .action(withSpinner(env.build))
216
+
217
+
218
+ // Serve Command.
219
+ program.command('serve')
220
+ .description('Serve the current project')
221
+ .option(
222
+ '--no-template',
223
+ 'Disables inclusion of the template page header & footer, starting off with a plain html page.'
224
+ )
225
+ .allowUnknownOption(true)
226
+ .action(withSpinner(env.serve))
227
+
228
+ // a11y Command.
229
+ program.command('a11y')
230
+ .description('Runs accessibility checks.')
231
+ .allowUnknownOption(true)
232
+ .action(withSpinner(env.a11y))
198
233
 
199
234
  // Update Plugins Command.
200
235
  program.command('update-plugins')
@@ -218,18 +253,25 @@ export default function cli() {
218
253
  .allowUnknownOption(true)
219
254
  .action( withSpinner(env.updateBlock) )
220
255
 
256
+
257
+ // Update a Design System Block Command.
258
+ program.command('sync')
259
+ .description('Sync changes from one destination to another.')
260
+ .argument('<from>', 'Target Site URL with current changes.')
261
+ .argument('<to>', 'Destination Site URL that should be synced.')
262
+ .allowUnknownOption(true)
263
+ .action( withSpinner(env.sync) )
264
+
221
265
  // Test Command.
222
266
  // Ensure this is commented out.
223
267
  program.command('test')
224
268
  .description('Test commands on a WordPress environment')
225
- .addArgument(envArg)
226
- .option(
227
- '--scripts',
228
- 'Execute any configured lifecycle scripts.',
229
- true
230
- )
269
+ //.addArgument(envArg)
231
270
  .allowUnknownOption(true)
232
271
  .action(withSpinner(env.test))
272
+
273
+
274
+ addWPEnvCommands();
233
275
 
234
276
  return program;
235
277
  };
package/lib/helpers.js ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import path from 'path';
5
+ import spawn from 'cross-spawn';
6
+ import { fileURLToPath } from 'url';
7
+ import { sync as resolveBin } from 'resolve-bin';
8
+ import { v2 as dockerCompose } from 'docker-compose';
9
+
10
+ /**
11
+ * Internal dependencies
12
+ */
13
+
14
+ /**
15
+ * Path Directory Locations
16
+ * - currentPath - Current Directory
17
+ * - projectPath - Current Project Path
18
+ * - appPath - Current Application Path
19
+ */
20
+ const currentPath = path.dirname(fileURLToPath(import.meta.url));
21
+ const projectPath = path.resolve( currentPath, '..' );
22
+ const appPath = path.resolve( projectPath, '../../../' );
23
+
24
+ /**
25
+ * Runs command directly.
26
+ *
27
+ * @param string cmd Command to run.
28
+ * @param string[] args List of command arguments.
29
+ * @param string[] opts List of spawn options.
30
+ *
31
+ * @returns {Promise}
32
+ */
33
+ async function runCmd(cmd, args,opts = { stdio: 'inherit' }){
34
+ // fix various commands.
35
+ switch (cmd) {
36
+ case 'npm':
37
+ case 'npx':
38
+ /**
39
+ * On Windows we run npm.cmd, on Linux we run npm
40
+ */
41
+ cmd += /^win/.test(process.platform) ? '.cmd' : '';
42
+ break;
43
+ case 'webpack':
44
+ cmd = resolveBin(cmd)
45
+ break
46
+ }
47
+
48
+ return spawn.sync( cmd, args, {...opts, env: process.env});
49
+
50
+ }
51
+
52
+ /**
53
+ * Runs commands on the given WordPress environment.
54
+ *
55
+ * @param {string} environment Which environment to run docker command on.
56
+ * @param {string[]} cmds Array of commands to run.
57
+ * @param {WPConfig} config The wp-env config object.
58
+ * @param {Object} spinner A CLI spinner which indicates progress.
59
+ *
60
+ * @returns {Promise}
61
+ */
62
+ async function runCLICmds(
63
+ environment,
64
+ cmds,
65
+ config,
66
+ spinner
67
+ ) {
68
+
69
+ // We return the promise whether there is an output or an error.
70
+ return await dockerCompose.run(
71
+ environment === 'development' ? 'cli' : 'tests-cli',
72
+ [ 'bash', '-c', cmds.join( ' && ' ) ],
73
+ {
74
+ cwd: config.workDirectoryPath,
75
+ env: process.env,
76
+ commandOptions: [],
77
+ log: config.debug,
78
+ callback: (buffer, result) => {
79
+ if( config.debug ){
80
+ spinner.text = buffer.toString();
81
+ }
82
+ }
83
+ }
84
+ ).then(
85
+ (output) => {
86
+ // Remove the Container information and new lines.
87
+ output.err = output.err.replace(/\s*Container .*Running\n|\n/g, '')
88
+ output.out = output.out.replace(/\s*Container .*Running\n|\n/g, '')
89
+
90
+ return '' !== output.out ? output.out : output.err;
91
+ },
92
+ (error) => {
93
+ // Remove the Container information and new lines.
94
+ error.err = error.err.replace(/\s*Container .*Running\n|\n/g, '')
95
+
96
+ return error;
97
+ }
98
+ );
99
+ }
100
+
101
+
102
+
103
+ export {
104
+ currentPath,
105
+ projectPath,
106
+ appPath,
107
+ runCLICmds,
108
+ runCmd
109
+ };
package/lib/index.js ADDED
@@ -0,0 +1,28 @@
1
+ import {
2
+ wpPrimary,
3
+ wpGreen,
4
+ wpRed,
5
+ wpYellow,
6
+ withSpinner,
7
+ } from './spinner.js';
8
+
9
+ import {
10
+ runCmd,
11
+ runCLICmds,
12
+ currentPath,
13
+ projectPath,
14
+ appPath
15
+ } from './helpers.js';
16
+
17
+ export {
18
+ currentPath,
19
+ projectPath,
20
+ appPath,
21
+ wpPrimary,
22
+ wpGreen,
23
+ wpRed,
24
+ wpYellow,
25
+ withSpinner,
26
+ runCmd,
27
+ runCLICmds
28
+ }
package/lib/spinner.js CHANGED
@@ -18,13 +18,16 @@ const withSpinner =
18
18
  // lets combine arguments with options
19
19
  const cmd = args.pop();
20
20
 
21
- cmd.registeredArguments.forEach(arg => {
22
- // get arg from list.
23
- let v = args.shift();
24
-
25
- // add arg to options.
26
- args[args.length - 1][arg._name] = v;
27
- });
21
+ if( cmd.registeredArguments ){
22
+ cmd.registeredArguments.forEach(arg => {
23
+ // get arg from list.
24
+ let v = args.shift();
25
+
26
+ // add arg to options.
27
+ args[args.length - 1][arg._name] = v;
28
+ });
29
+ }
30
+
28
31
 
29
32
  // add any global options.
30
33
  args[0] = {
@@ -7,7 +7,7 @@ import retry from '@wordpress/env/lib/retry.js';
7
7
  * Internal dependencies
8
8
  */
9
9
  import { CAWEB_OPTIONS } from'./options.js';
10
- import { runCLICmds } from'./utils.js';
10
+ import { runCLICmds } from'../helpers.js';
11
11
  import { isMultisite } from'./wordpress.js';
12
12
  import { configureDivi } from'./divi.js';
13
13
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Internal dependencies
3
3
  */
4
- import { runCLICmds } from './utils.js';
4
+ import { runCLICmds } from '../helpers.js';
5
5
  import { DIVI_OPTIONS } from './options.js';
6
6
 
7
7
 
@@ -29,81 +29,6 @@ const pipeline = util.promisify( stream.pipeline );
29
29
  const extractZip = util.promisify( zip );
30
30
  const rimraf = util.promisify( rim );
31
31
 
32
- /**
33
- * Download CAWeb Resources.
34
- *
35
- * @param {Object} spinner The spinner object to show progress.
36
- * @return {WPConfig} The config object we've loaded.
37
- */
38
- async function downloadSources(
39
- {
40
- spinner,
41
- config
42
- }
43
- ) {
44
- const progresses = {};
45
- const getProgressSetter = ( id ) => ( progress ) => {
46
- progresses[ id ] = progress;
47
- spinner.text =
48
- `Downloading ${id}.\n` +
49
- Object.entries( progresses )
50
- .map(
51
- ( [ key, value ] ) =>
52
- ` - ${ key }: ${ ( value * 100 ).toFixed( 0 ) }/100%`
53
- )
54
- .join( '\n' );
55
- };
56
-
57
- const { workDirectoryPath } = config;
58
- const { development: dev, tests: test } = config.env;
59
-
60
- let pluginDir = path.resolve(workDirectoryPath, 'plugins');
61
- let themeDir = path.resolve(workDirectoryPath, 'themes');
62
-
63
- let sources = dowloadPlugins(pluginDir);
64
-
65
- // Add Divi Theme and plugin to sources.
66
- if( (undefined !== dev.config.ET_USERNAME &&
67
- undefined !== dev.config.ET_API_KEY) ||
68
- (undefined !== test.config.ET_USERNAME &&
69
- undefined !== test.config.ET_API_KEY)
70
- ){
71
- let url = 'https://www.elegantthemes.com/api/api_downloads.php';
72
- let user = undefined !== dev.config.ET_USERNAME ? dev.config.ET_USERNAME : test.config.ET_USERNAME;
73
- let key = undefined !== dev.config.ET_API_KEY ? dev.config.ET_API_KEY : test.config.ET_API_KEY;
74
-
75
- // Add Divi sources.
76
- sources = sources.concat( [
77
- {
78
- basename: 'Divi',
79
- url: `${url}?api_update=1&theme=Divi&api_key=${key}&username=${user}`,
80
- path: path.join(themeDir, 'Divi'),
81
- type: 'zip'
82
- },
83
- {
84
- basename: 'Divi Plugin',
85
- url: `${url}?api_update=1&theme=divi-builder&api_key=${key}&username=${user}`,
86
- path: path.join(pluginDir, 'divi-builder'),
87
- type: 'zip'
88
- }
89
- ]);
90
- }
91
-
92
- // Ensure plugin/theme directory exists for downloading resources.
93
- fs.ensureDir(themeDir);
94
- fs.ensureDir(pluginDir);
95
-
96
- await Promise.all(
97
- sources.map( ( source ) =>
98
- downloadSource( source, {
99
- onProgress: getProgressSetter( source.basename ),
100
- spinner,
101
- } )
102
- )
103
- );
104
-
105
- };
106
-
107
32
  /**
108
33
  * Downloads the given source if necessary. The specific action taken depends
109
34
  * on the source type. The source is downloaded to source.path.
@@ -272,6 +197,77 @@ function dowloadPlugins(pluginDir){
272
197
  ]
273
198
  }
274
199
 
275
- export {
276
- downloadSources
277
- }
200
+ /**
201
+ * Download CAWeb Resources.
202
+ *
203
+ * @param {Object} spinner The spinner object to show progress.
204
+ * @return {WPConfig} The config object we've loaded.
205
+ */
206
+ export async function downloadSources(
207
+ {
208
+ spinner,
209
+ config
210
+ }
211
+ ) {
212
+ const progresses = {};
213
+ const getProgressSetter = ( id ) => ( progress ) => {
214
+ progresses[ id ] = progress;
215
+ spinner.text =
216
+ `Downloading ${id}.\n` +
217
+ Object.entries( progresses )
218
+ .map(
219
+ ( [ key, value ] ) =>
220
+ ` - ${ key }: ${ ( value * 100 ).toFixed( 0 ) }/100%`
221
+ )
222
+ .join( '\n' );
223
+ };
224
+
225
+ const { workDirectoryPath } = config;
226
+ const { development: dev, tests: test } = config.env;
227
+
228
+ let pluginDir = path.resolve(workDirectoryPath, 'plugins');
229
+ let themeDir = path.resolve(workDirectoryPath, 'themes');
230
+
231
+ let sources = dowloadPlugins(pluginDir);
232
+
233
+ // Add Divi Theme and plugin to sources.
234
+ if( (undefined !== dev.config.ET_USERNAME &&
235
+ undefined !== dev.config.ET_API_KEY) ||
236
+ (undefined !== test.config.ET_USERNAME &&
237
+ undefined !== test.config.ET_API_KEY)
238
+ ){
239
+ let url = 'https://www.elegantthemes.com/api/api_downloads.php';
240
+ let user = undefined !== dev.config.ET_USERNAME ? dev.config.ET_USERNAME : test.config.ET_USERNAME;
241
+ let key = undefined !== dev.config.ET_API_KEY ? dev.config.ET_API_KEY : test.config.ET_API_KEY;
242
+
243
+ // Add Divi sources.
244
+ sources = sources.concat( [
245
+ {
246
+ basename: 'Divi',
247
+ url: `${url}?api_update=1&theme=Divi&api_key=${key}&username=${user}`,
248
+ path: path.join(themeDir, 'Divi'),
249
+ type: 'zip'
250
+ },
251
+ {
252
+ basename: 'Divi Plugin',
253
+ url: `${url}?api_update=1&theme=divi-builder&api_key=${key}&username=${user}`,
254
+ path: path.join(pluginDir, 'divi-builder'),
255
+ type: 'zip'
256
+ }
257
+ ]);
258
+ }
259
+
260
+ // Ensure plugin/theme directory exists for downloading resources.
261
+ fs.ensureDir(themeDir);
262
+ fs.ensureDir(pluginDir);
263
+
264
+ await Promise.all(
265
+ sources.map( ( source ) =>
266
+ downloadSource( source, {
267
+ onProgress: getProgressSetter( source.basename ),
268
+ spinner,
269
+ } )
270
+ )
271
+ );
272
+
273
+ };
@@ -0,0 +1,16 @@
1
+ import { activateCAWeb, configureCAWeb } from "./caweb.js";
2
+ import { downloadSources } from "./download-sources.js";
3
+ import { configureDivi, isDiviThemeActive } from "./divi.js";
4
+ import { configureWordPress, isMultisite, convertToMultisite, generateHTAccess } from "./wordpress.js";
5
+
6
+ export {
7
+ activateCAWeb,
8
+ configureCAWeb,
9
+ downloadSources,
10
+ configureDivi,
11
+ isDiviThemeActive,
12
+ configureWordPress,
13
+ isMultisite,
14
+ convertToMultisite,
15
+ generateHTAccess
16
+ }
@@ -1,18 +1,14 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import fs from 'fs-extra';
5
- import path from 'node:path';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
6
 
7
7
  /**
8
8
  * Internal dependencies
9
9
  */
10
- import {runCLICmds} from './utils.js';
10
+ import {runCLICmds} from '../helpers.js';
11
11
 
12
- /**
13
- * Promisified dependencies
14
- */
15
- const { writeFile } = fs.promises;
16
12
 
17
13
  /**
18
14
  * Checks whether WordPress environment is a multisite installation.
@@ -128,7 +124,7 @@ async function generateHTAccess(environment, workDirectoryPath, subdomain){
128
124
 
129
125
  let folder = 'development' === environment ? 'WordPress' : 'Tests-WordPress'
130
126
 
131
- await writeFile(path.join(workDirectoryPath, folder, '.htaccess'), htaccess);
127
+ fs.writeFileSync(path.join(workDirectoryPath, folder, '.htaccess'), htaccess);
132
128
 
133
129
  }
134
130