@travetto/cli 8.0.0-alpha.0 → 8.0.0-alpha.2

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
@@ -59,7 +59,7 @@ This module also has a tight integration with the [VSCode plugin](https://market
59
59
  At it's heart, a cli command is the contract defined by what flags, and what arguments the command supports. Within the framework this requires three criteria to be met:
60
60
  * The file must be located in the `support/` folder, and have a name that matches `cli.*.ts`
61
61
  * The file must be a class that has a main method
62
- * The class must use the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L98) decorator
62
+ * The class must use the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L27) decorator
63
63
 
64
64
  **Code: Basic Command**
65
65
  ```typescript
@@ -94,7 +94,7 @@ Examples of mappings:
94
94
  The pattern is that underscores(_) translate to colons (:), and the `cli.` prefix, and `.ts` suffix are dropped.
95
95
 
96
96
  ## Binding Flags
97
- [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L98) is a wrapper for [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19), and so every class that uses the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L98) decorator is now a full [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19) class. The fields of the class represent the flags that are available to the command.
97
+ [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L27) is a wrapper for [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19), and so every class that uses the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L27) decorator is now a full [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19) class. The fields of the class represent the flags that are available to the command.
98
98
 
99
99
  **Code: Basic Command with Flag**
100
100
  ```typescript
@@ -131,7 +131,7 @@ $ trv basic:flag --loud
131
131
  HELLO
132
132
  ```
133
133
 
134
- The [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L98) supports the following data types for flags:
134
+ The [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L27) supports the following data types for flags:
135
135
  * Boolean values
136
136
  * Number values. The [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L172), [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L179), [@Precision](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L165), [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L99) and [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L110) decorators help provide additional validation.
137
137
  * String values. [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L99), [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L110), [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L90) and [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L64) provide additional constraints
@@ -390,7 +390,7 @@ npx trv call:db --host localhost --port 3306 --username app --password <custom>
390
390
  ```
391
391
 
392
392
  ## VSCode Integration
393
- By default, cli commands do not expose themselves to the VSCode extension, as the majority of them are not intended for that sort of operation. [Web API](https://github.com/travetto/travetto/tree/main/module/web#readme "Declarative support for creating Web Applications") does expose a cli target `web:http` that will show up, to help run/debug a web application. Any command can mark itself as being a run target, and will be eligible for running from within the [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=arcsine.travetto-plugin). This is achieved by setting the `runTarget` field on the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L98) decorator. This means the target will be visible within the editor tooling.
393
+ By default, cli commands do not expose themselves to the VSCode extension, as the majority of them are not intended for that sort of operation. [Web API](https://github.com/travetto/travetto/tree/main/module/web#readme "Declarative support for creating Web Applications") does expose a cli target `web:http` that will show up, to help run/debug a web application. Any command can mark itself as being a run target, and will be eligible for running from within the [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=arcsine.travetto-plugin). This is achieved by setting the `runTarget` field on the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L27) decorator. This means the target will be visible within the editor tooling.
394
394
 
395
395
  **Code: Simple Run Target**
396
396
  ```typescript
@@ -412,47 +412,19 @@ export class RunCommand {
412
412
 
413
413
  **Code: Anatomy of a Command**
414
414
  ```typescript
415
- export interface CliCommandShape<T extends unknown[] = unknown[]> {
416
- /**
417
- * Parsed state
418
- */
419
- _parsed?: ParsedState;
420
- /**
421
- * Config
422
- */
423
- _cfg?: CliCommandConfig;
415
+ export interface CliCommandShape {
424
416
  /**
425
417
  * Action target of the command
426
418
  */
427
- main(...args: T): OrProm<undefined | void>;
419
+ main(...args: unknown[]): OrProm<undefined | void>;
428
420
  /**
429
421
  * Run before main runs
430
422
  */
431
- preMain?(): OrProm<void>;
423
+ finalize?(help?: boolean): OrProm<void>;
432
424
  /**
433
425
  * Extra help
434
426
  */
435
427
  help?(): OrProm<string[]>;
436
- /**
437
- * Run before help is displayed
438
- */
439
- preHelp?(): OrProm<void>;
440
- /**
441
- * Is the command active/eligible for usage
442
- */
443
- isActive?(): boolean;
444
- /**
445
- * Run before binding occurs
446
- */
447
- preBind?(): OrProm<void>;
448
- /**
449
- * Run before validation occurs
450
- */
451
- preValidate?(): OrProm<void>;
452
- /**
453
- * Validation method
454
- */
455
- validate?(...args: T): OrProm<CliValidationError | CliValidationError[] | undefined>;
456
428
  }
457
429
  ```
458
430
 
@@ -463,7 +435,7 @@ If the goal is to run a more complex application, which may include depending on
463
435
  ```typescript
464
436
  import { Runtime, toConcrete } from '@travetto/runtime';
465
437
  import { DependencyRegistryIndex } from '@travetto/di';
466
- import { CliCommand, type CliCommandShape } from '@travetto/cli';
438
+ import { CliCommand, CliDebugIpcFlag, CliModuleFlag, CliProfilesFlag, CliRestartOnChangeFlag, type CliCommandShape } from '@travetto/cli';
467
439
  import { NetUtil } from '@travetto/web';
468
440
  import { Registry } from '@travetto/registry';
469
441
 
@@ -472,7 +444,7 @@ import type { WebHttpServer } from '../src/types.ts';
472
444
  /**
473
445
  * Run a web server
474
446
  */
475
- @CliCommand({ runTarget: true, with: { debugIpc: 'optional', restartOnChange: true, module: true, profiles: true } })
447
+ @CliCommand()
476
448
  export class WebHttpCommand implements CliCommandShape {
477
449
 
478
450
  /** Port to run on */
@@ -481,7 +453,19 @@ export class WebHttpCommand implements CliCommandShape {
481
453
  /** Kill conflicting port owner */
482
454
  killConflict?: boolean = Runtime.localDevelopment;
483
455
 
484
- preMain(): void {
456
+ @CliModuleFlag({ short: 'm' })
457
+ module: string;
458
+
459
+ @CliProfilesFlag()
460
+ profile: string[];
461
+
462
+ @CliRestartOnChangeFlag()
463
+ restartOnChange: boolean = true;
464
+
465
+ @CliDebugIpcFlag()
466
+ debugIpc?: boolean;
467
+
468
+ finalize(): void {
485
469
  if (this.port) {
486
470
  process.env.WEB_HTTP_PORT = `${this.port}`;
487
471
  }
@@ -512,32 +496,56 @@ As noted in the example above, `fields` is specified in this execution, with sup
512
496
  The `module` field is slightly more complex, but is geared towards supporting commands within a monorepo context. This flag ensures that a module is specified if running from the root of the monorepo, and that the module provided is real, and can run the desired command. When running from an explicit module folder in the monorepo, the module flag is ignored.
513
497
 
514
498
  ### Custom Validation
515
- In addition to dependency injection, the command contract also allows for a custom validation function, which will have access to bound command (flags, and args) as well as the unknown arguments. When a command implements this method, any [CliValidationError](https://github.com/travetto/travetto/tree/main/module/cli/src/types.ts#L20) errors that are returned will be shared with the user, and fail to invoke the `main` method.
499
+ In addition to dependency injection, the command contract also allows for a custom validation function, which will have access to bound command (flags, and args) as well as the unknown arguments. When a command implements this method, any [ValidationError](https://github.com/travetto/travetto/tree/main/module/schema/src/validate/types.ts#L10) errors that are returned will be shared with the user, and fail to invoke the `main` method.
516
500
 
517
- **Code: CliValidationError**
501
+ **Code: ValidationError**
518
502
  ```typescript
519
- export interface CliValidationError {
503
+ export interface ValidationError {
520
504
  /**
521
505
  * The error message
522
506
  */
523
507
  message: string;
524
508
  /**
525
- * Source of validation
509
+ * The object path of the error
526
510
  */
527
- source?: 'flag' | 'arg' | 'custom';
528
- };
511
+ path: string;
512
+ /**
513
+ * The kind of validation
514
+ */
515
+ kind: ValidationKind;
516
+ /**
517
+ * The value provided
518
+ */
519
+ value?: unknown;
520
+ /**
521
+ * Regular expression to match
522
+ */
523
+ regex?: string;
524
+ /**
525
+ * Number to compare against
526
+ */
527
+ limit?: NumericLikeIntrinsic;
528
+ /**
529
+ * The type of the field
530
+ */
531
+ type?: string;
532
+ /**
533
+ * Source of the error
534
+ */
535
+ source?: string;
536
+ }
529
537
  ```
530
538
 
531
539
  A simple example of the validation can be found in the `doc` command:
532
540
 
533
541
  **Code: Simple Validation Example**
534
542
  ```typescript
535
- async validate(): Promise<CliValidationError | undefined> {
536
- const docFile = path.resolve(this.input);
537
- if (!(await fs.stat(docFile).catch(() => false))) {
538
- return { message: `input: ${this.input} does not exist`, source: 'flag' };
539
- }
543
+ @Validator(async (cmd) => {
544
+ const docFile = path.resolve(cmd.input);
545
+ if (!(await fs.stat(docFile).catch(() => false))) {
546
+ return { message: `input: ${cmd.input} does not exist`, path: 'input', source: 'flag', kind: 'invalid' };
540
547
  }
548
+ })
541
549
  ```
542
550
 
543
551
  ## CLI - service
@@ -550,7 +558,8 @@ $ trv service --help
550
558
  Usage: service [options] <action:restart|start|status|stop> [services...:string]
551
559
 
552
560
  Options:
553
- -h, --help display help for command
561
+ -q, --quiet (default: false)
562
+ -h, --help display help for command
554
563
 
555
564
  Available Services
556
565
  --------------------
package/__index__.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { } from './src/trv.d.ts';
2
2
  export * from './src/types.ts';
3
3
  export * from './src/execute.ts';
4
- export * from './src/error.ts';
5
4
  export * from './src/schema.ts';
6
5
  export * from './src/schema-export.ts';
7
6
  export * from './src/registry/decorator.ts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/cli",
3
- "version": "8.0.0-alpha.0",
3
+ "version": "8.0.0-alpha.2",
4
4
  "type": "module",
5
5
  "description": "CLI infrastructure for Travetto framework",
6
6
  "keywords": [
@@ -29,8 +29,8 @@
29
29
  "directory": "module/cli"
30
30
  },
31
31
  "dependencies": {
32
- "@travetto/schema": "^8.0.0-alpha.0",
33
- "@travetto/terminal": "^8.0.0-alpha.0"
32
+ "@travetto/schema": "^8.0.0-alpha.1",
33
+ "@travetto/terminal": "^8.0.0-alpha.1"
34
34
  },
35
35
  "travetto": {
36
36
  "displayName": "Command Line Interface",
package/src/execute.ts CHANGED
@@ -3,7 +3,6 @@ import { ConsoleManager, Runtime, ShutdownManager, Util } from '@travetto/runtim
3
3
  import { HelpUtil } from './help.ts';
4
4
  import { CliCommandRegistryIndex } from './registry/registry-index.ts';
5
5
  import { CliCommandSchemaUtil } from './schema.ts';
6
- import { CliUnknownCommandError, CliValidationResultError } from './error.ts';
7
6
  import { CliParseUtil } from './parse.ts';
8
7
  import type { CliCommandShape } from './types.ts';
9
8
 
@@ -12,71 +11,46 @@ import type { CliCommandShape } from './types.ts';
12
11
  */
13
12
  export class ExecutionManager {
14
13
 
15
- /** Error handler */
16
- static async #onError(error: unknown): Promise<void> {
17
- process.exitCode ??= 1;
18
- if (error instanceof CliValidationResultError) {
19
- console.error!(HelpUtil.renderValidationError(error));
20
- console.error!(await HelpUtil.renderCommandHelp(error.command));
21
- } else if (error instanceof CliUnknownCommandError) {
22
- if (error.help) {
23
- console.error!(error.help);
24
- } else {
25
- console.error!(error.defaultMessage, '\n');
26
- console.error!(await HelpUtil.renderAllHelp(''));
27
- }
28
- } else {
29
- console.error!(error);
30
- }
31
- console.error!();
32
- }
33
-
34
- /** Bind command */
35
- static async #bindCommand(cmd: string, args: string[]): Promise<{ command: CliCommandShape, boundArgs: unknown[] }> {
36
- const [{ instance: command, schema }] = await CliCommandRegistryIndex.load([cmd]);
37
- const fullArgs = await CliParseUtil.expandArgs(schema, args);
14
+ /**
15
+ * Execute the command line
16
+ * @param args
17
+ */
18
+ static async run(argv: string[]): Promise<void> {
19
+ let command: CliCommandShape | undefined;
38
20
 
39
- const state = command._parsed = await CliParseUtil.parse(schema, fullArgs);
21
+ const { cmd, args, help } = CliParseUtil.getArgs(argv);
22
+ if (!cmd) {
23
+ return console.info!(await HelpUtil.renderAllHelp());
24
+ }
40
25
 
41
- await command.preBind?.();
42
- const boundArgs = CliCommandSchemaUtil.bindInput(command, state);
43
- return { command, boundArgs };
44
- }
26
+ try {
27
+ const [{ instance, schema, config }] = await CliCommandRegistryIndex.load([cmd]);
28
+ command = instance;
29
+ const fullArgs = await CliParseUtil.expandArgs(schema, args);
45
30
 
46
- /** Run command */
47
- static async #runCommand(cmd: string, args: string[]): Promise<void> {
48
- const { command, boundArgs } = await this.#bindCommand(cmd, args);
31
+ const state = await CliParseUtil.parse(schema, fullArgs);
32
+ CliParseUtil.setState(instance, state);
49
33
 
50
- await command.preValidate?.();
51
- await CliCommandSchemaUtil.validate(command, boundArgs);
34
+ const boundArgs = CliCommandSchemaUtil.bindInput(instance, state);
35
+ await instance.finalize?.(help);
52
36
 
53
- await command._cfg!.preMain?.(command);
54
- await command.preMain?.();
37
+ if (help) {
38
+ return console.log!(await HelpUtil.renderCommandHelp(instance));
39
+ }
55
40
 
56
- ConsoleManager.debug(Runtime.debug);
57
- await command.main(...boundArgs);
58
- }
41
+ await CliCommandSchemaUtil.validate(command, boundArgs);
59
42
 
60
- /**
61
- * Execute the command line
62
- * @param args
63
- */
64
- static async run(argv: string[]): Promise<void> {
65
- try {
66
43
  // Wait 50ms to allow stdout to flush on shutdown
67
44
  ShutdownManager.signal.addEventListener('abort', () => Util.blockingTimeout(50));
68
45
 
69
- const { cmd, args, help } = CliParseUtil.getArgs(argv);
70
- if (!cmd) {
71
- console.info!(await HelpUtil.renderAllHelp());
72
- } else if (help) {
73
- const { command } = await this.#bindCommand(cmd, args);
74
- console.log!(await HelpUtil.renderCommandHelp(command));
75
- } else {
76
- await this.#runCommand(cmd, args);
46
+ for (const preMain of config.preMain ?? []) {
47
+ await preMain(instance);
77
48
  }
49
+
50
+ ConsoleManager.debug(Runtime.debug);
51
+ await instance.main(...boundArgs);
78
52
  } catch (error) {
79
- await this.#onError(error);
53
+ await HelpUtil.renderError(error, cmd, command);
80
54
  } finally {
81
55
  await ShutdownManager.shutdown();
82
56
  }
package/src/help.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import util from 'node:util';
2
2
 
3
- import { castKey, getClass, JSONUtil } from '@travetto/runtime';
4
- import { SchemaRegistryIndex } from '@travetto/schema';
3
+ import { castKey, getClass, JSONUtil, Runtime } from '@travetto/runtime';
4
+ import { SchemaRegistryIndex, ValidationResultError } from '@travetto/schema';
5
5
 
6
6
  import { cliTpl } from './color.ts';
7
7
  import type { CliCommandShape } from './types.ts';
8
- import { CliCommandRegistryIndex } from './registry/registry-index.ts';
9
- import type { CliValidationResultError } from './error.ts';
8
+ import { CliCommandRegistryIndex, UNKNOWN_COMMAND } from './registry/registry-index.ts';
10
9
  import { CliSchemaExportUtil } from './schema-export.ts';
11
10
 
12
11
  const validationSourceMap: Record<string, string> = {
@@ -17,11 +16,39 @@ const validationSourceMap: Record<string, string> = {
17
16
  const ifDefined = <T>(value: T | null | '' | undefined): T | undefined =>
18
17
  (value === null || value === '' || value === undefined) ? undefined : value;
19
18
 
19
+ const toItem = (name: string, pkg: string, prod?: boolean) => [name, Runtime.getInstallCommand(pkg, prod)] as const;
20
+
21
+ const INSTALL_COMMANDS = new Map<string, string>([
22
+ ...['test', 'test:watch', 'test:direct'].map(item => toItem(item, '@travetto/test')),
23
+ ...['lint', 'lint:register', 'eslint', 'eslint:register'].map(item => toItem(item, '@travetto/eslint')),
24
+ ...['model:install', 'model:export'].map(item => toItem(item, '@travetto/model', true)),
25
+ ...['openapi:spec', 'openapi:client'].map(item => toItem(item, '@travetto/openapi', true)),
26
+ ...['email:compile', 'email:test', 'email:editor'].map(item => toItem(item, '@travetto/email-compiler')),
27
+ ...['pack', 'pack:zip', 'pack:docker'].map(item => toItem(item, '@travetto/pack')),
28
+ ...['repo:publish', 'repo:version', 'repo:exec', 'repo:list', 'repo:version-sync'].map(item => toItem(item, '@travetto/repo')),
29
+ toItem('web:http', '@travetto/web-http', true),
30
+ toItem('doc', '@travetto/doc'),
31
+ toItem('web:rpc-client', '@travetto/web-rpc', true),
32
+ ]);
33
+
20
34
  /**
21
35
  * Utilities for showing help
22
36
  */
23
37
  export class HelpUtil {
24
38
 
39
+ /** Render the unknown command message */
40
+ static renderUnknownCommandMessage(cmd: string): string {
41
+ const install = INSTALL_COMMANDS.get(cmd);
42
+ if (install) {
43
+ return cliTpl`
44
+ ${{ title: 'Missing Package' }}\n${'-'.repeat(20)}\nTo use ${{ input: cmd }} please run:\n
45
+ ${{ identifier: install }}
46
+ `;
47
+ } else {
48
+ return cliTpl`${{ subtitle: 'Unknown command' }}: ${{ input: cmd }}`;
49
+ }
50
+ }
51
+
25
52
  /**
26
53
  * Render command-specific help
27
54
  * @param command
@@ -31,8 +58,6 @@ export class HelpUtil {
31
58
  const { name: commandName } = CliCommandRegistryIndex.get(getClass(command));
32
59
  const args = schema.methods.main?.parameters ?? [];
33
60
 
34
- await command.preHelp?.();
35
-
36
61
  // Ensure finalized
37
62
 
38
63
  const usage: string[] = [cliTpl`${{ title: 'Usage:' }} ${{ param: commandName }} ${{ input: '[options]' }}`,];
@@ -132,7 +157,7 @@ export class HelpUtil {
132
157
  /**
133
158
  * Render validation error to a string
134
159
  */
135
- static renderValidationError(validationError: CliValidationResultError): string {
160
+ static renderValidationError(validationError: ValidationResultError): string {
136
161
  return [
137
162
  cliTpl`${{ failure: 'Execution failed' }}:`,
138
163
  ...validationError.details.errors.map(error => {
@@ -144,4 +169,20 @@ export class HelpUtil {
144
169
  '',
145
170
  ].join('\n');
146
171
  }
172
+
173
+ /** Error handler */
174
+ static async renderError(error: unknown, cmd: string, command?: CliCommandShape): Promise<void> {
175
+ process.exitCode ??= 1;
176
+ if (error instanceof ValidationResultError) {
177
+ console.error!(this.renderValidationError(error));
178
+ }
179
+ if (command) {
180
+ console.error!(await this.renderCommandHelp(command));
181
+ } else if (error === UNKNOWN_COMMAND) {
182
+ console.error!(this.renderUnknownCommandMessage(cmd));
183
+ } else {
184
+ console.error!(error);
185
+ }
186
+ console.error!();
187
+ }
147
188
  }
package/src/parse.ts CHANGED
@@ -20,6 +20,8 @@ export const isBoolFlag = (value?: SchemaInputConfig): boolean => value?.type ==
20
20
 
21
21
  export type AliasesParseResult = Record<'long' | 'short' | 'raw' | 'env', string[]>;
22
22
 
23
+ const STATE_SYMBOL = Symbol();
24
+
23
25
  /**
24
26
  * Parsing support for the cli
25
27
  */
@@ -238,4 +240,34 @@ export class CliParseUtil {
238
240
  return result;
239
241
  }, { long: [], short: [], raw: [], env: [] });
240
242
  }
243
+
244
+ /**
245
+ * Build aliases for a schema config
246
+ */
247
+ static buildAliases(config: { full?: string, short?: string, envVars?: string[] }, ...extraEnvVars: string[]): Partial<SchemaFieldConfig> {
248
+ const envVars = [...config.envVars ?? [], ...extraEnvVars];
249
+ return {
250
+ aliases: [
251
+ ...(config.full ? [config.full.startsWith('-') ? config.full : `--${config.full}`] : []),
252
+ ...(config.short ? [config.short.startsWith('-') ? config.short : `-${config.short}`] : []),
253
+ ...(envVars.length ? envVars.map(CliParseUtil.toEnvField) : [])
254
+ ]
255
+ };
256
+ }
257
+
258
+ /**
259
+ * Get the state from an object
260
+ */
261
+ static getState<T extends object>(item: T): ParsedState | undefined {
262
+ const local: T & { [STATE_SYMBOL]?: ParsedState } = item;
263
+ return local[STATE_SYMBOL];
264
+ }
265
+
266
+ /**
267
+ * Set the state
268
+ */
269
+ static setState<T extends object>(item: T, state: ParsedState): void {
270
+ const local: T & { [STATE_SYMBOL]?: ParsedState } = item;
271
+ local[STATE_SYMBOL] = state;
272
+ }
241
273
  }