react-native-update-cli 1.46.2 → 2.0.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 (64) hide show
  1. package/README.md +578 -1
  2. package/README.zh-CN.md +576 -0
  3. package/cli.json +18 -0
  4. package/lib/api.js +5 -5
  5. package/lib/app.js +1 -1
  6. package/lib/bundle.js +30 -28
  7. package/lib/exports.js +65 -0
  8. package/lib/index.js +100 -9
  9. package/lib/module-manager.js +125 -0
  10. package/lib/modules/app-module.js +223 -0
  11. package/lib/modules/bundle-module.js +188 -0
  12. package/lib/modules/index.js +42 -0
  13. package/lib/modules/package-module.js +16 -0
  14. package/lib/modules/user-module.js +402 -0
  15. package/lib/modules/version-module.js +16 -0
  16. package/lib/package.js +16 -6
  17. package/lib/provider.js +340 -0
  18. package/lib/user.js +3 -3
  19. package/lib/utils/app-info-parser/apk.js +1 -1
  20. package/lib/utils/app-info-parser/ipa.js +2 -2
  21. package/lib/utils/app-info-parser/resource-finder.js +35 -35
  22. package/lib/utils/app-info-parser/xml-parser/manifest.js +2 -2
  23. package/lib/utils/app-info-parser/zip.js +3 -6
  24. package/lib/utils/check-plugin.js +1 -1
  25. package/lib/utils/git.js +1 -1
  26. package/lib/utils/i18n.js +3 -1
  27. package/lib/utils/index.js +4 -4
  28. package/lib/utils/latest-version/cli.js +3 -3
  29. package/lib/utils/latest-version/index.js +4 -4
  30. package/lib/versions.js +2 -2
  31. package/package.json +4 -4
  32. package/src/api.ts +7 -7
  33. package/src/app.ts +2 -2
  34. package/src/bundle.ts +44 -32
  35. package/src/exports.ts +30 -0
  36. package/src/index.ts +118 -16
  37. package/src/module-manager.ts +149 -0
  38. package/src/modules/app-module.ts +205 -0
  39. package/src/modules/bundle-module.ts +202 -0
  40. package/src/modules/index.ts +19 -0
  41. package/src/modules/package-module.ts +11 -0
  42. package/src/modules/user-module.ts +406 -0
  43. package/src/modules/version-module.ts +8 -0
  44. package/src/package.ts +29 -16
  45. package/src/provider.ts +341 -0
  46. package/src/types.ts +125 -0
  47. package/src/user.ts +4 -3
  48. package/src/utils/app-info-parser/apk.js +62 -52
  49. package/src/utils/app-info-parser/app.js +5 -5
  50. package/src/utils/app-info-parser/ipa.js +69 -57
  51. package/src/utils/app-info-parser/resource-finder.js +50 -54
  52. package/src/utils/app-info-parser/utils.js +59 -54
  53. package/src/utils/app-info-parser/xml-parser/binary.js +366 -354
  54. package/src/utils/app-info-parser/xml-parser/manifest.js +145 -137
  55. package/src/utils/app-info-parser/zip.js +1 -1
  56. package/src/utils/check-plugin.ts +4 -2
  57. package/src/utils/dep-versions.ts +13 -6
  58. package/src/utils/git.ts +1 -1
  59. package/src/utils/i18n.ts +3 -1
  60. package/src/utils/index.ts +8 -10
  61. package/src/utils/latest-version/cli.ts +4 -4
  62. package/src/utils/latest-version/index.ts +17 -17
  63. package/src/utils/plugin-config.ts +3 -3
  64. package/src/versions.ts +3 -3
@@ -0,0 +1,341 @@
1
+ import { getSession, loadSession } from './api';
2
+ import { getPlatform, getSelectedApp } from './app';
3
+ import type {
4
+ BundleOptions,
5
+ CLIProvider,
6
+ CommandContext,
7
+ CommandResult,
8
+ CustomWorkflow,
9
+ Platform,
10
+ PublishOptions,
11
+ Session,
12
+ UploadOptions,
13
+ Version,
14
+ } from './types';
15
+
16
+ export class CLIProviderImpl implements CLIProvider {
17
+ private workflows: Map<string, CustomWorkflow> = new Map();
18
+ private session?: Session;
19
+
20
+ constructor() {
21
+ this.init();
22
+ }
23
+
24
+ private async init() {
25
+ try {
26
+ await loadSession();
27
+ this.session = getSession();
28
+ } catch (error) {}
29
+ }
30
+
31
+ async bundle(options: BundleOptions): Promise<CommandResult> {
32
+ try {
33
+ const context: CommandContext = {
34
+ args: [],
35
+ options: {
36
+ dev: options.dev || false,
37
+ platform: options.platform,
38
+ bundleName: options.bundleName || 'index.bundlejs',
39
+ entryFile: options.entryFile || 'index.js',
40
+ output: options.output || '${tempDir}/output/${platform}.${time}.ppk',
41
+ sourcemap: options.sourcemap || false,
42
+ taro: options.taro || false,
43
+ expo: options.expo || false,
44
+ rncli: options.rncli || false,
45
+ disableHermes: options.disableHermes || false,
46
+ },
47
+ };
48
+
49
+ const { bundleCommands } = await import('./bundle');
50
+ await bundleCommands.bundle(context);
51
+
52
+ return {
53
+ success: true,
54
+ data: { message: 'Bundle created successfully' },
55
+ };
56
+ } catch (error) {
57
+ return {
58
+ success: false,
59
+ error:
60
+ error instanceof Error
61
+ ? error.message
62
+ : 'Unknown error during bundling',
63
+ };
64
+ }
65
+ }
66
+
67
+ async publish(options: PublishOptions): Promise<CommandResult> {
68
+ try {
69
+ const context: CommandContext = {
70
+ args: [],
71
+ options: {
72
+ name: options.name,
73
+ description: options.description,
74
+ metaInfo: options.metaInfo,
75
+ packageId: options.packageId,
76
+ packageVersion: options.packageVersion,
77
+ minPackageVersion: options.minPackageVersion,
78
+ maxPackageVersion: options.maxPackageVersion,
79
+ packageVersionRange: options.packageVersionRange,
80
+ rollout: options.rollout,
81
+ dryRun: options.dryRun || false,
82
+ },
83
+ };
84
+
85
+ const { versionCommands } = await import('./versions');
86
+ await versionCommands.publish(context);
87
+
88
+ return {
89
+ success: true,
90
+ data: { message: 'Version published successfully' },
91
+ };
92
+ } catch (error) {
93
+ return {
94
+ success: false,
95
+ error:
96
+ error instanceof Error
97
+ ? error.message
98
+ : 'Unknown error during publishing',
99
+ };
100
+ }
101
+ }
102
+
103
+ async upload(options: UploadOptions): Promise<CommandResult> {
104
+ try {
105
+ const platform = await this.getPlatform(options.platform);
106
+ const { appId } = await this.getSelectedApp(platform);
107
+
108
+ const filePath = options.filePath;
109
+ const fileType = filePath.split('.').pop()?.toLowerCase();
110
+
111
+ const context: CommandContext = {
112
+ args: [filePath],
113
+ options: { platform, appId },
114
+ };
115
+
116
+ const { packageCommands } = await import('./package');
117
+
118
+ switch (fileType) {
119
+ case 'ipa':
120
+ await packageCommands.uploadIpa(context);
121
+ break;
122
+ case 'apk':
123
+ await packageCommands.uploadApk(context);
124
+ break;
125
+ case 'app':
126
+ await packageCommands.uploadApp(context);
127
+ break;
128
+ default:
129
+ throw new Error(`Unsupported file type: ${fileType}`);
130
+ }
131
+
132
+ return {
133
+ success: true,
134
+ data: { message: 'File uploaded successfully' },
135
+ };
136
+ } catch (error) {
137
+ return {
138
+ success: false,
139
+ error:
140
+ error instanceof Error
141
+ ? error.message
142
+ : 'Unknown error during upload',
143
+ };
144
+ }
145
+ }
146
+
147
+ async getSelectedApp(
148
+ platform?: Platform,
149
+ ): Promise<{ appId: string; platform: Platform }> {
150
+ const resolvedPlatform = await this.getPlatform(platform);
151
+ return getSelectedApp(resolvedPlatform);
152
+ }
153
+
154
+ async listApps(platform?: Platform): Promise<CommandResult> {
155
+ try {
156
+ const resolvedPlatform = await this.getPlatform(platform);
157
+ const { appCommands } = await import('./app');
158
+ await appCommands.apps({ options: { platform: resolvedPlatform } });
159
+
160
+ return {
161
+ success: true,
162
+ data: { message: 'Apps listed successfully' },
163
+ };
164
+ } catch (error) {
165
+ return {
166
+ success: false,
167
+ error:
168
+ error instanceof Error ? error.message : 'Unknown error listing apps',
169
+ };
170
+ }
171
+ }
172
+
173
+ async createApp(name: string, platform: Platform): Promise<CommandResult> {
174
+ try {
175
+ const { appCommands } = await import('./app');
176
+ await appCommands.createApp({
177
+ options: {
178
+ name,
179
+ platform,
180
+ downloadUrl: '',
181
+ },
182
+ });
183
+
184
+ return {
185
+ success: true,
186
+ data: { message: 'App created successfully' },
187
+ };
188
+ } catch (error) {
189
+ return {
190
+ success: false,
191
+ error:
192
+ error instanceof Error ? error.message : 'Unknown error creating app',
193
+ };
194
+ }
195
+ }
196
+
197
+ async listVersions(appId: string): Promise<CommandResult> {
198
+ try {
199
+ const context: CommandContext = {
200
+ args: [],
201
+ options: { appId },
202
+ };
203
+
204
+ const { versionCommands } = await import('./versions');
205
+ await versionCommands.versions(context);
206
+
207
+ return {
208
+ success: true,
209
+ data: { message: 'Versions listed successfully' },
210
+ };
211
+ } catch (error) {
212
+ return {
213
+ success: false,
214
+ error:
215
+ error instanceof Error
216
+ ? error.message
217
+ : 'Unknown error listing versions',
218
+ };
219
+ }
220
+ }
221
+
222
+ async updateVersion(
223
+ appId: string,
224
+ versionId: string,
225
+ updates: Partial<Version>,
226
+ ): Promise<CommandResult> {
227
+ try {
228
+ const context: CommandContext = {
229
+ args: [versionId],
230
+ options: {
231
+ appId,
232
+ ...updates,
233
+ },
234
+ };
235
+
236
+ const { versionCommands } = await import('./versions');
237
+ await versionCommands.update(context);
238
+
239
+ return {
240
+ success: true,
241
+ data: { message: 'Version updated successfully' },
242
+ };
243
+ } catch (error) {
244
+ return {
245
+ success: false,
246
+ error:
247
+ error instanceof Error
248
+ ? error.message
249
+ : 'Unknown error updating version',
250
+ };
251
+ }
252
+ }
253
+
254
+ async getPlatform(platform?: Platform): Promise<Platform> {
255
+ return getPlatform(platform);
256
+ }
257
+
258
+ async loadSession(): Promise<Session> {
259
+ await loadSession();
260
+ this.session = getSession();
261
+ if (!this.session) {
262
+ throw new Error('Failed to load session');
263
+ }
264
+ return this.session;
265
+ }
266
+
267
+ registerWorkflow(workflow: CustomWorkflow): void {
268
+ this.workflows.set(workflow.name, workflow);
269
+ }
270
+
271
+ async executeWorkflow(
272
+ workflowName: string,
273
+ context: CommandContext,
274
+ ): Promise<CommandResult> {
275
+ const workflow = this.workflows.get(workflowName);
276
+ if (!workflow) {
277
+ return {
278
+ success: false,
279
+ error: `Workflow '${workflowName}' not found`,
280
+ };
281
+ }
282
+
283
+ try {
284
+ let previousResult: any = null;
285
+ for (const step of workflow.steps) {
286
+ if (step.condition && !step.condition(context)) {
287
+ console.log(`Skipping step '${step.name}' due to condition`);
288
+ continue;
289
+ }
290
+
291
+ console.log(`Executing step '${step.name}'`);
292
+ previousResult = await step.execute(context, previousResult);
293
+ }
294
+
295
+ return {
296
+ success: true,
297
+ data: {
298
+ message: `Workflow '${workflowName}' completed successfully`,
299
+ result: previousResult,
300
+ },
301
+ };
302
+ } catch (error) {
303
+ return {
304
+ success: false,
305
+ error:
306
+ error instanceof Error
307
+ ? error.message
308
+ : `Workflow '${workflowName}' failed`,
309
+ };
310
+ }
311
+ }
312
+
313
+ getRegisteredWorkflows(): string[] {
314
+ return Array.from(this.workflows.keys());
315
+ }
316
+
317
+ async listPackages(appId?: string): Promise<CommandResult> {
318
+ try {
319
+ const context: CommandContext = {
320
+ args: [],
321
+ options: appId ? { appId } : {},
322
+ };
323
+
324
+ const { listPackage } = await import('./package');
325
+ const result = await listPackage(appId || '');
326
+
327
+ return {
328
+ success: true,
329
+ data: result,
330
+ };
331
+ } catch (error) {
332
+ return {
333
+ success: false,
334
+ error:
335
+ error instanceof Error
336
+ ? error.message
337
+ : 'Unknown error listing packages',
338
+ };
339
+ }
340
+ }
341
+ }
package/src/types.ts CHANGED
@@ -12,6 +12,12 @@ export type Platform = 'ios' | 'android' | 'harmony';
12
12
  export interface Package {
13
13
  id: string;
14
14
  name: string;
15
+ version?: string;
16
+ status?: string;
17
+ appId?: string;
18
+ appKey?: string;
19
+ versionName?: any;
20
+ buildTime?: any;
15
21
  }
16
22
 
17
23
  export interface Version {
@@ -20,3 +26,122 @@ export interface Version {
20
26
  name: string;
21
27
  packages?: Package[];
22
28
  }
29
+
30
+ export interface CommandContext {
31
+ args: string[];
32
+ options: Record<string, any>;
33
+ platform?: Platform;
34
+ appId?: string;
35
+ session?: Session;
36
+ }
37
+
38
+ export interface CommandResult {
39
+ success: boolean;
40
+ data?: any;
41
+ error?: string;
42
+ }
43
+
44
+ export interface CommandDefinition {
45
+ name: string;
46
+ description?: string;
47
+ handler: (context: CommandContext) => Promise<CommandResult>;
48
+ options?: Record<
49
+ string,
50
+ {
51
+ hasValue?: boolean;
52
+ default?: any;
53
+ description?: string;
54
+ }
55
+ >;
56
+ }
57
+
58
+ export interface BundleOptions {
59
+ dev?: boolean;
60
+ platform?: Platform;
61
+ bundleName?: string;
62
+ entryFile?: string;
63
+ output?: string;
64
+ sourcemap?: boolean;
65
+ taro?: boolean;
66
+ expo?: boolean;
67
+ rncli?: boolean;
68
+ disableHermes?: boolean;
69
+ }
70
+
71
+ export interface PublishOptions {
72
+ name?: string;
73
+ description?: string;
74
+ metaInfo?: string;
75
+ packageId?: string;
76
+ packageVersion?: string;
77
+ minPackageVersion?: string;
78
+ maxPackageVersion?: string;
79
+ packageVersionRange?: string;
80
+ rollout?: number;
81
+ dryRun?: boolean;
82
+ }
83
+
84
+ export interface UploadOptions {
85
+ platform?: Platform;
86
+ filePath: string;
87
+ appId?: string;
88
+ }
89
+
90
+ export interface WorkflowStep {
91
+ name: string;
92
+ description?: string;
93
+ execute: (context: CommandContext, previousResult?: any) => Promise<any>;
94
+ condition?: (context: CommandContext) => boolean;
95
+ }
96
+
97
+ export interface CustomWorkflow {
98
+ name: string;
99
+ description?: string;
100
+ steps: WorkflowStep[];
101
+ validate?: (context: CommandContext) => boolean;
102
+ options?: Record<
103
+ string,
104
+ {
105
+ hasValue?: boolean;
106
+ default?: any;
107
+ description?: string;
108
+ }
109
+ >;
110
+ }
111
+
112
+ export interface CLIProvider {
113
+ bundle: (options: BundleOptions) => Promise<CommandResult>;
114
+ publish: (options: PublishOptions) => Promise<CommandResult>;
115
+ upload: (options: UploadOptions) => Promise<CommandResult>;
116
+
117
+ createApp: (name: string, platform: Platform) => Promise<CommandResult>;
118
+ listApps: (platform?: Platform) => Promise<CommandResult>;
119
+ getSelectedApp: (
120
+ platform?: Platform,
121
+ ) => Promise<{ appId: string; platform: Platform }>;
122
+
123
+ listVersions: (appId: string) => Promise<CommandResult>;
124
+ updateVersion: (
125
+ appId: string,
126
+ versionId: string,
127
+ updates: Partial<Version>,
128
+ ) => Promise<CommandResult>;
129
+
130
+ getPlatform: (platform?: Platform) => Promise<Platform>;
131
+ loadSession: () => Promise<Session>;
132
+
133
+ registerWorkflow: (workflow: CustomWorkflow) => void;
134
+ executeWorkflow: (
135
+ workflowName: string,
136
+ context: CommandContext,
137
+ ) => Promise<CommandResult>;
138
+ }
139
+
140
+ export interface CLIModule {
141
+ name: string;
142
+ version: string;
143
+ commands?: CommandDefinition[];
144
+ workflows?: CustomWorkflow[];
145
+ init?: (provider: CLIProvider) => void;
146
+ cleanup?: () => void;
147
+ }
package/src/user.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { question } from './utils';
2
- import { post, get, replaceSession, saveSession, closeSession } from './api';
3
1
  import crypto from 'crypto';
2
+ import type { CommandContext } from 'types';
3
+ import { closeSession, get, post, replaceSession, saveSession } from './api';
4
+ import { question } from './utils';
4
5
  import { t } from './utils/i18n';
5
6
 
6
7
  function md5(str: string) {
@@ -19,7 +20,7 @@ export const userCommands = {
19
20
  await saveSession();
20
21
  console.log(t('welcomeMessage', { name: info.name }));
21
22
  },
22
- logout: async () => {
23
+ logout: async (context: CommandContext) => {
23
24
  await closeSession();
24
25
  console.log(t('loggedOut'));
25
26
  },
@@ -1,64 +1,74 @@
1
- const Zip = require('./zip')
2
- const { mapInfoResource, findApkIconPath, getBase64FromBuffer } = require('./utils')
3
- const ManifestName = /^androidmanifest\.xml$/
4
- const ResourceName = /^resources\.arsc$/
1
+ const Zip = require('./zip');
2
+ const {
3
+ mapInfoResource,
4
+ findApkIconPath,
5
+ getBase64FromBuffer,
6
+ } = require('./utils');
7
+ const ManifestName = /^androidmanifest\.xml$/;
8
+ const ResourceName = /^resources\.arsc$/;
5
9
 
6
- const ManifestXmlParser = require('./xml-parser/manifest')
7
- const ResourceFinder = require('./resource-finder')
10
+ const ManifestXmlParser = require('./xml-parser/manifest');
11
+ const ResourceFinder = require('./resource-finder');
8
12
 
9
13
  class ApkParser extends Zip {
10
14
  /**
11
15
  * parser for parsing .apk file
12
16
  * @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
13
17
  */
14
- constructor (file) {
15
- super(file)
18
+ constructor(file) {
19
+ super(file);
16
20
  if (!(this instanceof ApkParser)) {
17
- return new ApkParser(file)
21
+ return new ApkParser(file);
18
22
  }
19
23
  }
20
- parse () {
24
+ parse() {
21
25
  return new Promise((resolve, reject) => {
22
- this.getEntries([ManifestName, ResourceName]).then(buffers => {
23
- if (!buffers[ManifestName]) {
24
- throw new Error('AndroidManifest.xml can\'t be found.')
25
- }
26
- let apkInfo = this._parseManifest(buffers[ManifestName])
27
- let resourceMap
28
- if (!buffers[ResourceName]) {
29
- resolve(apkInfo)
30
- } else {
31
- // parse resourceMap
32
- resourceMap = this._parseResourceMap(buffers[ResourceName])
33
- // update apkInfo with resourceMap
34
- apkInfo = mapInfoResource(apkInfo, resourceMap)
35
-
36
- // find icon path and parse icon
37
- const iconPath = findApkIconPath(apkInfo)
38
- if (iconPath) {
39
- this.getEntry(iconPath).then(iconBuffer => {
40
- apkInfo.icon = iconBuffer ? getBase64FromBuffer(iconBuffer) : null
41
- resolve(apkInfo)
42
- }).catch(e => {
43
- apkInfo.icon = null
44
- resolve(apkInfo)
45
- console.warn('[Warning] failed to parse icon: ', e)
46
- })
26
+ this.getEntries([ManifestName, ResourceName])
27
+ .then((buffers) => {
28
+ if (!buffers[ManifestName]) {
29
+ throw new Error("AndroidManifest.xml can't be found.");
30
+ }
31
+ let apkInfo = this._parseManifest(buffers[ManifestName]);
32
+ let resourceMap;
33
+ if (!buffers[ResourceName]) {
34
+ resolve(apkInfo);
47
35
  } else {
48
- apkInfo.icon = null
49
- resolve(apkInfo)
36
+ // parse resourceMap
37
+ resourceMap = this._parseResourceMap(buffers[ResourceName]);
38
+ // update apkInfo with resourceMap
39
+ apkInfo = mapInfoResource(apkInfo, resourceMap);
40
+
41
+ // find icon path and parse icon
42
+ const iconPath = findApkIconPath(apkInfo);
43
+ if (iconPath) {
44
+ this.getEntry(iconPath)
45
+ .then((iconBuffer) => {
46
+ apkInfo.icon = iconBuffer
47
+ ? getBase64FromBuffer(iconBuffer)
48
+ : null;
49
+ resolve(apkInfo);
50
+ })
51
+ .catch((e) => {
52
+ apkInfo.icon = null;
53
+ resolve(apkInfo);
54
+ console.warn('[Warning] failed to parse icon: ', e);
55
+ });
56
+ } else {
57
+ apkInfo.icon = null;
58
+ resolve(apkInfo);
59
+ }
50
60
  }
51
- }
52
- }).catch(e => {
53
- reject(e)
54
- })
55
- })
61
+ })
62
+ .catch((e) => {
63
+ reject(e);
64
+ });
65
+ });
56
66
  }
57
67
  /**
58
68
  * Parse manifest
59
69
  * @param {Buffer} buffer // manifest file's buffer
60
70
  */
61
- _parseManifest (buffer) {
71
+ _parseManifest(buffer) {
62
72
  try {
63
73
  const parser = new ManifestXmlParser(buffer, {
64
74
  ignore: [
@@ -66,25 +76,25 @@ class ApkParser extends Zip {
66
76
  'application.service',
67
77
  'application.receiver',
68
78
  'application.provider',
69
- 'permission-group'
70
- ]
71
- })
72
- return parser.parse()
79
+ 'permission-group',
80
+ ],
81
+ });
82
+ return parser.parse();
73
83
  } catch (e) {
74
- throw new Error('Parse AndroidManifest.xml error: ', e)
84
+ throw new Error('Parse AndroidManifest.xml error: ', e);
75
85
  }
76
86
  }
77
87
  /**
78
88
  * Parse resourceMap
79
89
  * @param {Buffer} buffer // resourceMap file's buffer
80
90
  */
81
- _parseResourceMap (buffer) {
91
+ _parseResourceMap(buffer) {
82
92
  try {
83
- return new ResourceFinder().processResourceTable(buffer)
93
+ return new ResourceFinder().processResourceTable(buffer);
84
94
  } catch (e) {
85
- throw new Error('Parser resources.arsc error: ' + e)
95
+ throw new Error('Parser resources.arsc error: ' + e);
86
96
  }
87
97
  }
88
98
  }
89
99
 
90
- module.exports = ApkParser
100
+ module.exports = ApkParser;
@@ -1,16 +1,16 @@
1
- const Zip = require('./zip')
1
+ const Zip = require('./zip');
2
2
 
3
3
  class AppParser extends Zip {
4
4
  /**
5
5
  * parser for parsing .apk file
6
6
  * @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
7
7
  */
8
- constructor (file) {
9
- super(file)
8
+ constructor(file) {
9
+ super(file);
10
10
  if (!(this instanceof AppParser)) {
11
- return new AppParser(file)
11
+ return new AppParser(file);
12
12
  }
13
13
  }
14
14
  }
15
15
 
16
- module.exports = AppParser
16
+ module.exports = AppParser;