@youcan/app 2.1.4 → 2.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 (36) hide show
  1. package/dist/cli/commands/app/dev.d.ts +13 -6
  2. package/dist/cli/commands/app/dev.js +89 -51
  3. package/dist/cli/commands/app/env/show.d.ts +7 -0
  4. package/dist/cli/commands/app/env/show.js +29 -0
  5. package/dist/cli/commands/app/generate/extension.d.ts +6 -6
  6. package/dist/cli/commands/app/generate/extension.js +50 -50
  7. package/dist/cli/commands/app/install.d.ts +5 -5
  8. package/dist/cli/commands/app/install.js +21 -21
  9. package/dist/cli/services/dev/workers/app-worker.d.ts +11 -10
  10. package/dist/cli/services/dev/workers/app-worker.js +29 -30
  11. package/dist/cli/services/dev/workers/index.d.ts +14 -10
  12. package/dist/cli/services/dev/workers/index.js +25 -19
  13. package/dist/cli/services/dev/workers/theme-extension-worker.d.ts +15 -15
  14. package/dist/cli/services/dev/workers/theme-extension-worker.js +113 -113
  15. package/dist/cli/services/dev/workers/tunnel-worker.d.ts +15 -0
  16. package/dist/cli/services/dev/workers/tunnel-worker.js +57 -0
  17. package/dist/cli/services/dev/workers/web-worker.d.ts +12 -11
  18. package/dist/cli/services/dev/workers/web-worker.js +26 -23
  19. package/dist/cli/services/generate/extensions/index.d.ts +2 -2
  20. package/dist/cli/services/generate/extensions/index.js +2 -2
  21. package/dist/cli/services/generate/extensions/theme-extension.d.ts +3 -3
  22. package/dist/cli/services/generate/extensions/theme-extension.js +17 -17
  23. package/dist/cli/services/generate/generate.d.ts +10 -10
  24. package/dist/cli/services/generate/generate.js +24 -24
  25. package/dist/constants.d.ts +5 -5
  26. package/dist/constants.js +4 -4
  27. package/dist/flags.d.ts +3 -3
  28. package/dist/flags.js +7 -7
  29. package/dist/types.d.ts +84 -69
  30. package/dist/util/app-command.d.ts +8 -0
  31. package/dist/util/app-command.js +41 -0
  32. package/dist/util/app-loader.d.ts +2 -2
  33. package/dist/util/app-loader.js +35 -35
  34. package/package.json +8 -3
  35. package/dist/util/theme-command.d.ts +0 -3
  36. package/dist/util/theme-command.js +0 -6
@@ -1,117 +1,117 @@
1
- import { Worker, Color, Session, Http, Env, Path, Filesystem, Form } from '@youcan/cli-kit';
1
+ import { Worker, Session, Http, Env, Path, Filesystem, Form } from '@youcan/cli-kit';
2
2
 
3
- class ThemeExtensionWorker extends Worker.Abstract {
4
- command;
5
- app;
6
- extension;
7
- logger;
8
- queue = [];
9
- FILE_TYPES = [
10
- 'assets',
11
- 'locales',
12
- 'snippets',
13
- 'blocks',
14
- ];
15
- constructor(command, app, extension) {
16
- super();
17
- this.command = command;
18
- this.app = app;
19
- this.extension = extension;
20
- this.logger = new Worker.Logger('stdout', 'extensions', Color.yellow);
21
- }
22
- async boot() {
23
- const session = await Session.authenticate(this.command);
24
- try {
25
- const res = await Http.post(`${Env.apiHostname()}/apps/${this.app.config.id}/extensions/create`, {
26
- headers: { Authorization: `Bearer ${session.access_token}` },
27
- body: JSON.stringify({ ...this.extension.config }),
28
- });
29
- this.extension.id = res.id;
30
- this.extension.metadata = res.metadata;
31
- }
32
- catch (err) {
33
- this.command.error(err);
34
- }
35
- }
36
- async run() {
37
- this.logger.write(`pushed '${this.extension.config.name}' to a draft...`);
38
- for (const type of this.FILE_TYPES) {
39
- const descriptors = this.extension.metadata[type] ?? [];
40
- const directory = Path.resolve(this.extension.root, type);
41
- const present = await Filesystem.exists(directory)
42
- ? await Filesystem.readdir(directory)
43
- : [];
44
- present.filter(f => !descriptors.find(d => d.file_name === f))
45
- .forEach(async (file) => this.enqueue('put', type, file));
46
- descriptors.forEach(async (descriptor) => {
47
- const path = Path.resolve(directory, descriptor.file_name);
48
- if (!(await Filesystem.exists(path))) {
49
- return this.enqueue('del', type, descriptor.file_name);
50
- }
51
- this.enqueue('put', type, descriptor.file_name);
52
- });
53
- }
54
- const paths = this.FILE_TYPES
55
- .map(p => Path.resolve(this.extension.root, p));
56
- const watcher = Filesystem.watch(paths, {
57
- persistent: true,
58
- ignoreInitial: true,
59
- awaitWriteFinish: {
60
- stabilityThreshold: 50,
61
- },
62
- });
63
- this.command.controller.signal.addEventListener('abort', () => {
64
- watcher.close();
65
- });
66
- watcher.on('all', async (event, path) => {
67
- try {
68
- if (!['add', 'change', 'unlink'].includes(event)) {
69
- return;
70
- }
71
- const [filetype, filename] = [
72
- Path.basename(Path.dirname(path)),
73
- Path.basename(path),
74
- ];
75
- switch (event) {
76
- case 'add':
77
- case 'change':
78
- this.enqueue('put', filetype, filename);
79
- break;
80
- case 'unlink':
81
- this.enqueue('del', filetype, filename);
82
- break;
83
- }
84
- }
85
- catch (err) {
86
- this.command.output.warn(err);
87
- }
88
- });
89
- // quick racing conditions hack
90
- setInterval(async () => {
91
- const task = this.queue.shift();
92
- if (task == null) {
93
- return;
94
- }
95
- await task();
96
- }, 10);
97
- }
98
- enqueue(op, type, name) {
99
- this.queue.push(async () => {
100
- const path = Path.join(this.extension.root, type, name);
101
- const payload = {
102
- file_name: name,
103
- file_type: type,
104
- file_operation: op,
105
- };
106
- if (op === 'put') {
107
- payload.file_content = await Form.file(path);
108
- }
109
- await Http.post(`${Env.apiHostname()}/apps/${this.app.config.id}/extensions/${this.extension.id}/file`, {
110
- body: Form.convert(payload),
111
- });
112
- this.logger.write(`[${op === 'put' ? 'updated' : 'deleted'}] - ${Path.join(type, name)}`);
113
- });
114
- }
3
+ class ThemeExtensionWorker extends Worker.Abstract {
4
+ command;
5
+ app;
6
+ extension;
7
+ logger;
8
+ queue = [];
9
+ FILE_TYPES = [
10
+ 'assets',
11
+ 'locales',
12
+ 'snippets',
13
+ 'blocks',
14
+ ];
15
+ constructor(command, app, extension) {
16
+ super();
17
+ this.command = command;
18
+ this.app = app;
19
+ this.extension = extension;
20
+ this.logger = new Worker.Logger('extensions', 'yellow');
21
+ }
22
+ async boot() {
23
+ const session = await Session.authenticate(this.command);
24
+ try {
25
+ const res = await Http.post(`${Env.apiHostname()}/apps/${this.app.config.id}/extensions/create`, {
26
+ headers: { Authorization: `Bearer ${session.access_token}` },
27
+ body: JSON.stringify({ ...this.extension.config }),
28
+ });
29
+ this.extension.id = res.id;
30
+ this.extension.metadata = res.metadata;
31
+ }
32
+ catch (err) {
33
+ this.command.error(err);
34
+ }
35
+ }
36
+ async run() {
37
+ this.logger.write(`pushed '${this.extension.config.name}' to a draft...`);
38
+ for (const type of this.FILE_TYPES) {
39
+ const descriptors = this.extension.metadata[type] ?? [];
40
+ const directory = Path.resolve(this.extension.root, type);
41
+ const present = await Filesystem.exists(directory)
42
+ ? await Filesystem.readdir(directory)
43
+ : [];
44
+ present.filter(f => !descriptors.find(d => d.file_name === f))
45
+ .forEach(async (file) => this.enqueue('put', type, file));
46
+ descriptors.forEach(async (descriptor) => {
47
+ const path = Path.resolve(directory, descriptor.file_name);
48
+ if (!(await Filesystem.exists(path))) {
49
+ return this.enqueue('del', type, descriptor.file_name);
50
+ }
51
+ this.enqueue('put', type, descriptor.file_name);
52
+ });
53
+ }
54
+ const paths = this.FILE_TYPES
55
+ .map(p => Path.resolve(this.extension.root, p));
56
+ const watcher = Filesystem.watch(paths, {
57
+ persistent: true,
58
+ ignoreInitial: true,
59
+ awaitWriteFinish: {
60
+ stabilityThreshold: 50,
61
+ },
62
+ });
63
+ this.command.controller.signal.addEventListener('abort', () => {
64
+ watcher.close();
65
+ });
66
+ watcher.on('all', async (event, path) => {
67
+ try {
68
+ if (!['add', 'change', 'unlink'].includes(event)) {
69
+ return;
70
+ }
71
+ const [filetype, filename] = [
72
+ Path.basename(Path.dirname(path)),
73
+ Path.basename(path),
74
+ ];
75
+ switch (event) {
76
+ case 'add':
77
+ case 'change':
78
+ this.enqueue('put', filetype, filename);
79
+ break;
80
+ case 'unlink':
81
+ this.enqueue('del', filetype, filename);
82
+ break;
83
+ }
84
+ }
85
+ catch (err) {
86
+ this.command.output.warn(err);
87
+ }
88
+ });
89
+ // quick racing conditions hack
90
+ setInterval(async () => {
91
+ const task = this.queue.shift();
92
+ if (task == null) {
93
+ return;
94
+ }
95
+ await task();
96
+ }, 10);
97
+ }
98
+ enqueue(op, type, name) {
99
+ this.queue.push(async () => {
100
+ const path = Path.join(this.extension.root, type, name);
101
+ const payload = {
102
+ file_name: name,
103
+ file_type: type,
104
+ file_operation: op,
105
+ };
106
+ if (op === 'put') {
107
+ payload.file_content = await Form.file(path);
108
+ }
109
+ await Http.post(`${Env.apiHostname()}/apps/${this.app.config.id}/extensions/${this.extension.id}/file`, {
110
+ body: Form.convert(payload),
111
+ });
112
+ this.logger.write(`[${op === 'put' ? 'updated' : 'deleted'}] - ${Path.join(type, name)}`);
113
+ });
114
+ }
115
115
  }
116
116
 
117
117
  export { ThemeExtensionWorker as default };
@@ -0,0 +1,15 @@
1
+ import { Worker } from '@youcan/cli-kit';
2
+ import type { Services } from '@youcan/cli-kit';
3
+ import type { App } from '@/types';
4
+ import type { AppCommand } from '@/util/app-command';
5
+ export default class TunnelWorker extends Worker.Abstract {
6
+ private command;
7
+ private app;
8
+ private tunnelService;
9
+ private readonly logger;
10
+ private url;
11
+ constructor(command: AppCommand, app: App, tunnelService: Services.Cloudflared);
12
+ boot(): Promise<void>;
13
+ run(): Promise<void>;
14
+ private checkForError;
15
+ }
@@ -0,0 +1,57 @@
1
+ import { Worker, System } from '@youcan/cli-kit';
2
+
3
+ class TunnelWorker extends Worker.Abstract {
4
+ command;
5
+ app;
6
+ tunnelService;
7
+ logger;
8
+ url = null;
9
+ constructor(command, app, tunnelService) {
10
+ super();
11
+ this.command = command;
12
+ this.app = app;
13
+ this.tunnelService = tunnelService;
14
+ this.logger = new Worker.Logger('tunnel', 'dim');
15
+ }
16
+ async boot() {
17
+ if (!this.app.networkConfig) {
18
+ throw new Error('app network config is not set');
19
+ }
20
+ this.logger.write('start tunneling the app');
21
+ await this.tunnelService.tunnel(this.app.networkConfig.port);
22
+ // Stop the execution for while and see if the tunnel is available.
23
+ await System.sleep(5);
24
+ const url = this.tunnelService.getUrl();
25
+ if (url) {
26
+ this.logger.write(`tunneled url obtained: \`${url}\``);
27
+ this.url = url;
28
+ this.app.networkConfig.appUrl = this.url;
29
+ }
30
+ }
31
+ async run() {
32
+ const timeInterval = 500;
33
+ if (this.url) {
34
+ return;
35
+ }
36
+ setInterval(() => {
37
+ if (this.url !== null) {
38
+ this.checkForError();
39
+ return;
40
+ }
41
+ this.url = this.tunnelService.getUrl();
42
+ if (this.url) {
43
+ this.logger.write(`tunneled url obtained: \`${this.url}\``);
44
+ this.app.networkConfig.appUrl = this.url;
45
+ this.command.syncAppConfig();
46
+ }
47
+ }, timeInterval);
48
+ }
49
+ checkForError() {
50
+ const error = this.tunnelService.getError();
51
+ if (error) {
52
+ throw new Error(`Tunnel stopped: ${error}`);
53
+ }
54
+ }
55
+ }
56
+
57
+ export { TunnelWorker as default };
@@ -1,11 +1,12 @@
1
- import { type Cli, Worker } from '@youcan/cli-kit';
2
- import type { App, Web } from '@/types';
3
- export default class WebWorker extends Worker.Abstract {
4
- private command;
5
- private app;
6
- private web;
7
- private logger;
8
- constructor(command: Cli.Command, app: App, web: Web);
9
- boot(): Promise<void>;
10
- run(): Promise<void>;
11
- }
1
+ import { type Cli, Worker } from '@youcan/cli-kit';
2
+ import type { App, Web } from '@/types';
3
+ export default class WebWorker extends Worker.Abstract {
4
+ private readonly command;
5
+ private readonly app;
6
+ private readonly web;
7
+ private readonly env;
8
+ private logger;
9
+ constructor(command: Cli.Command, app: App, web: Web, env: Record<string, string>);
10
+ boot(): Promise<void>;
11
+ run(): Promise<void>;
12
+ }
@@ -1,27 +1,30 @@
1
- import { Worker, Color, System } from '@youcan/cli-kit';
1
+ import { Worker, System } from '@youcan/cli-kit';
2
2
 
3
- class WebWorker extends Worker.Abstract {
4
- command;
5
- app;
6
- web;
7
- logger;
8
- constructor(command, app, web) {
9
- super();
10
- this.command = command;
11
- this.app = app;
12
- this.web = web;
13
- this.logger = new Worker.Logger('stderr', this.web.config.name || 'web', Color.blue);
14
- }
15
- async boot() {
16
- }
17
- async run() {
18
- const [cmd, ...args] = this.web.config.commands.dev.split(' ');
19
- return System.exec(cmd, args, {
20
- stdout: this.logger,
21
- signal: this.command.controller.signal,
22
- stderr: new Worker.Logger('stderr', this.web.config.name || 'web', Color.red),
23
- });
24
- }
3
+ class WebWorker extends Worker.Abstract {
4
+ command;
5
+ app;
6
+ web;
7
+ env;
8
+ logger;
9
+ constructor(command, app, web, env) {
10
+ super();
11
+ this.command = command;
12
+ this.app = app;
13
+ this.web = web;
14
+ this.env = env;
15
+ this.logger = new Worker.Logger(this.web.config.name || 'web', 'blue');
16
+ }
17
+ async boot() {
18
+ }
19
+ async run() {
20
+ const [cmd, ...args] = this.web.config.commands.dev.split(' ');
21
+ return System.exec(cmd, args, {
22
+ stdout: this.logger,
23
+ signal: this.command.controller.signal,
24
+ stderr: new Worker.Logger(this.web.config.name || 'web', 'red'),
25
+ env: this.env,
26
+ });
27
+ }
25
28
  }
26
29
 
27
30
  export { WebWorker as default };
@@ -1,2 +1,2 @@
1
- declare const _default: import("../../../../types").ExtensionTemplate[];
2
- export default _default;
1
+ declare const _default: import("../../../../types").ExtensionTemplate[];
2
+ export default _default;
@@ -1,7 +1,7 @@
1
1
  import ThemeExtension from './theme-extension.js';
2
2
 
3
- var extensions = [
4
- ThemeExtension,
3
+ var extensions = [
4
+ ThemeExtension,
5
5
  ];
6
6
 
7
7
  export { extensions as default };
@@ -1,3 +1,3 @@
1
- import type { ExtensionTemplate } from '@/types';
2
- declare const _default: ExtensionTemplate;
3
- export default _default;
1
+ import type { ExtensionTemplate } from '@/types';
2
+ declare const _default: ExtensionTemplate;
3
+ export default _default;
@@ -1,20 +1,20 @@
1
- var ThemeExtension = {
2
- name: 'Theme extension',
3
- identifier: 'theme-extension',
4
- description: 'Liquid snippets to extend store themes',
5
- types: [
6
- {
7
- url: 'https://github.com/youcan-shop/app-extension-templates',
8
- type: 'theme',
9
- flavors: [
10
- {
11
- name: 'Liquid',
12
- value: 'liquid',
13
- path: 'theme-extension/liquid',
14
- },
15
- ],
16
- },
17
- ],
1
+ var ThemeExtension = {
2
+ name: 'Theme extension',
3
+ identifier: 'theme-extension',
4
+ description: 'Liquid snippets to extend store themes',
5
+ types: [
6
+ {
7
+ url: 'https://github.com/youcan-shop/app-extension-templates',
8
+ type: 'theme',
9
+ flavors: [
10
+ {
11
+ name: 'Liquid',
12
+ value: 'liquid',
13
+ path: 'theme-extension/liquid',
14
+ },
15
+ ],
16
+ },
17
+ ],
18
18
  };
19
19
 
20
20
  export { ThemeExtension as default };
@@ -1,10 +1,10 @@
1
- import type { ExtensionFlavor, ExtensionTemplateType, InitialAppConfig } from '@/types';
2
- export declare function ensureExtensionDirectoryExists(name: string): Promise<string>;
3
- export interface InitExtensionOptions {
4
- name: string;
5
- app: InitialAppConfig;
6
- directory: string;
7
- type: ExtensionTemplateType;
8
- flavor?: ExtensionFlavor;
9
- }
10
- export declare function initThemeExtension(options: InitExtensionOptions): Promise<void>;
1
+ import type { ExtensionFlavor, ExtensionTemplateType, InitialAppConfig } from '@/types';
2
+ export declare function ensureExtensionDirectoryExists(name: string): Promise<string>;
3
+ export interface InitExtensionOptions {
4
+ name: string;
5
+ app: InitialAppConfig;
6
+ directory: string;
7
+ type: ExtensionTemplateType;
8
+ flavor?: ExtensionFlavor;
9
+ }
10
+ export declare function initThemeExtension(options: InitExtensionOptions): Promise<void>;
@@ -1,30 +1,30 @@
1
1
  import { Path, Filesystem, Git } from '@youcan/cli-kit';
2
2
  import { EXTENSION_CONFIG_FILENAME } from '../../../constants.js';
3
3
 
4
- async function ensureExtensionDirectoryExists(name) {
5
- const dir = Path.join(Path.cwd(), 'extensions', name);
6
- if (await Filesystem.exists(dir)) {
7
- throw new Error(`The '${name}' already exists, choose a new name for your extension`);
8
- }
9
- await Filesystem.mkdir(dir);
10
- return dir;
11
- }
12
- async function initThemeExtension(options) {
13
- return Filesystem.tapIntoTmp(async (tmp) => {
14
- const directory = Path.join(tmp, 'download');
15
- await Filesystem.mkdir(directory);
16
- await Git.clone({
17
- url: options.type.url,
18
- destination: directory,
19
- shallow: true,
20
- });
21
- const flavorPath = Path.join(directory, options.flavor?.path || '');
22
- if (!await Filesystem.exists(flavorPath)) {
23
- throw new Error(`Extension flavor '${options.flavor?.name}' is unavailble`);
24
- }
25
- await Filesystem.move(flavorPath, options.directory, { overwrite: true });
26
- await Filesystem.writeJsonFile(Path.join(options.directory, EXTENSION_CONFIG_FILENAME), { name: options.name, type: options.type.type });
27
- });
4
+ async function ensureExtensionDirectoryExists(name) {
5
+ const dir = Path.join(Path.cwd(), 'extensions', name);
6
+ if (await Filesystem.exists(dir)) {
7
+ throw new Error(`The '${name}' already exists, choose a new name for your extension`);
8
+ }
9
+ await Filesystem.mkdir(dir);
10
+ return dir;
11
+ }
12
+ async function initThemeExtension(options) {
13
+ return Filesystem.tapIntoTmp(async (tmp) => {
14
+ const directory = Path.join(tmp, 'download');
15
+ await Filesystem.mkdir(directory);
16
+ await Git.clone({
17
+ url: options.type.url,
18
+ destination: directory,
19
+ shallow: true,
20
+ });
21
+ const flavorPath = Path.join(directory, options.flavor?.path || '');
22
+ if (!await Filesystem.exists(flavorPath)) {
23
+ throw new Error(`Extension flavor '${options.flavor?.name}' is unavailble`);
24
+ }
25
+ await Filesystem.move(flavorPath, options.directory, { overwrite: true });
26
+ await Filesystem.writeJsonFile(Path.join(options.directory, EXTENSION_CONFIG_FILENAME), { name: options.name, type: options.type.type });
27
+ });
28
28
  }
29
29
 
30
30
  export { ensureExtensionDirectoryExists, initThemeExtension };
@@ -1,5 +1,5 @@
1
- export declare const APP_CONFIG_FILENAME = "youcan.app.json";
2
- export declare const WEB_CONFIG_FILENAME = "youcan.web.json";
3
- export declare const EXTENSION_CONFIG_FILENAME = "youcan.extension.json";
4
- export declare const DEFAULT_WEBS_DIR = ".";
5
- export declare const DEFAULT_EXTENSIONS_DIR = "extensions/";
1
+ export declare const APP_CONFIG_FILENAME = "youcan.app.json";
2
+ export declare const WEB_CONFIG_FILENAME = "youcan.web.json";
3
+ export declare const EXTENSION_CONFIG_FILENAME = "youcan.extension.json";
4
+ export declare const DEFAULT_WEBS_DIR = ".";
5
+ export declare const DEFAULT_EXTENSIONS_DIR = "extensions/";
package/dist/constants.js CHANGED
@@ -1,7 +1,7 @@
1
- const APP_CONFIG_FILENAME = 'youcan.app.json';
2
- const WEB_CONFIG_FILENAME = 'youcan.web.json';
3
- const EXTENSION_CONFIG_FILENAME = 'youcan.extension.json';
4
- const DEFAULT_WEBS_DIR = '.';
1
+ const APP_CONFIG_FILENAME = 'youcan.app.json';
2
+ const WEB_CONFIG_FILENAME = 'youcan.web.json';
3
+ const EXTENSION_CONFIG_FILENAME = 'youcan.extension.json';
4
+ const DEFAULT_WEBS_DIR = '.';
5
5
  const DEFAULT_EXTENSIONS_DIR = 'extensions/';
6
6
 
7
7
  export { APP_CONFIG_FILENAME, DEFAULT_EXTENSIONS_DIR, DEFAULT_WEBS_DIR, EXTENSION_CONFIG_FILENAME, WEB_CONFIG_FILENAME };
package/dist/flags.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export declare const APP_FLAGS: {
2
- path: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
3
- };
1
+ export declare const APP_FLAGS: {
2
+ path: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
3
+ };
package/dist/flags.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { Path } from '@youcan/cli-kit';
3
3
 
4
- const APP_FLAGS = {
5
- path: Flags.string({
6
- env: 'YC_FLAG_PATH',
7
- default: async () => Path.cwd(),
8
- parse: async (input) => Path.resolve(input),
9
- description: 'The path to your app directory.',
10
- }),
4
+ const APP_FLAGS = {
5
+ path: Flags.string({
6
+ env: 'YC_FLAG_PATH',
7
+ default: async () => Path.cwd(),
8
+ parse: async (input) => Path.resolve(input),
9
+ description: 'The path to your app directory.',
10
+ }),
11
11
  };
12
12
 
13
13
  export { APP_FLAGS };