@memlab/cli 1.0.3 → 1.0.4

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.
@@ -30,6 +30,7 @@ export default class BaseCommand extends Command {
30
30
  getPrerequisites(): BaseCommand[];
31
31
  isInternalCommand(): boolean;
32
32
  getOptions(): BaseOption[];
33
+ getExcludedOptions(): BaseOption[];
33
34
  getSubCommands(): BaseCommand[];
34
35
  run(_options: CLIOptions): Promise<void>;
35
36
  }
@@ -55,21 +55,23 @@ class Command {
55
55
  const uniqueOptions = new Map();
56
56
  const visitedCommands = new Set();
57
57
  const queue = [self];
58
+ const excludedOptions = new Set(self.getExcludedOptions().map(option => option.getOptionName()));
58
59
  while (queue.length > 0) {
59
60
  const cur = queue.shift();
60
- if (cur) {
61
- const options = cur.getOptions();
62
- for (const option of options) {
63
- const optionName = option.getOptionName();
64
- if (!uniqueOptions.has(optionName)) {
65
- uniqueOptions.set(optionName, option);
66
- }
61
+ const options = cur.getOptions();
62
+ for (const option of options) {
63
+ const optionName = option.getOptionName();
64
+ if (excludedOptions.has(optionName)) {
65
+ continue;
67
66
  }
68
- visitedCommands.add(cur.getCommandName());
69
- for (const prereq of cur.getPrerequisites()) {
70
- if (!visitedCommands.has(prereq.getCommandName())) {
71
- queue.push(prereq);
72
- }
67
+ if (!uniqueOptions.has(optionName)) {
68
+ uniqueOptions.set(optionName, option);
69
+ }
70
+ }
71
+ visitedCommands.add(cur.getCommandName());
72
+ for (const prereq of cur.getPrerequisites()) {
73
+ if (!visitedCommands.has(prereq.getCommandName())) {
74
+ queue.push(prereq);
73
75
  }
74
76
  }
75
77
  }
@@ -112,6 +114,13 @@ class BaseCommand extends Command {
112
114
  getOptions() {
113
115
  return [];
114
116
  }
117
+ // commands from getPrerequisites may propagate
118
+ // options that does not make sense for the
119
+ // current command, this returns the list of
120
+ // options that should be excluded from helper text
121
+ getExcludedOptions() {
122
+ return [];
123
+ }
115
124
  // get subcommands of this command
116
125
  // for example command 'A' has two sub-commands 'B' and 'C'
117
126
  // CLI supports running in terminal: `memlab A B` or `memlab A C`
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @emails oncall+ws_labs
8
+ * @format
9
+ */
10
+ import BaseCommand from './BaseCommand';
11
+ export default class CommandLoader {
12
+ private isLoaded;
13
+ private OSSModules;
14
+ private modules;
15
+ private modulePaths;
16
+ getModules(): Map<string, BaseCommand>;
17
+ getModulePaths(): Map<string, string>;
18
+ registerCommands(): void;
19
+ private registerCommandsFromDir;
20
+ private postRegistration;
21
+ }
22
+ //# sourceMappingURL=CommandLoader.d.ts.map
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @emails oncall+ws_labs
9
+ * @format
10
+ */
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const fs_1 = __importDefault(require("fs"));
16
+ const path_1 = __importDefault(require("path"));
17
+ const core_1 = require("@memlab/core");
18
+ const BaseCommand_1 = __importDefault(require("./BaseCommand"));
19
+ const GenerateCLIDocCommand_1 = __importDefault(require("./commands/helper/GenerateCLIDocCommand"));
20
+ class CommandLoader {
21
+ constructor() {
22
+ this.isLoaded = false;
23
+ this.OSSModules = new Map();
24
+ this.modules = new Map();
25
+ this.modulePaths = new Map();
26
+ }
27
+ getModules() {
28
+ if (!this.isLoaded) {
29
+ this.registerCommands();
30
+ }
31
+ return this.modules;
32
+ }
33
+ getModulePaths() {
34
+ if (!this.isLoaded) {
35
+ this.registerCommands();
36
+ }
37
+ return this.modulePaths;
38
+ }
39
+ registerCommands() {
40
+ const modulesDir = path_1.default.resolve(__dirname, 'commands');
41
+ this.registerCommandsFromDir(modulesDir);
42
+ this.postRegistration();
43
+ }
44
+ registerCommandsFromDir(modulesDir) {
45
+ const moduleFiles = fs_1.default.readdirSync(modulesDir);
46
+ for (const moduleFile of moduleFiles) {
47
+ const modulePath = path_1.default.join(modulesDir, moduleFile);
48
+ // recursively import modules from subdirectories
49
+ if (fs_1.default.lstatSync(modulePath).isDirectory()) {
50
+ this.registerCommandsFromDir(modulePath);
51
+ continue;
52
+ }
53
+ // only import modules files ends with with Command.js
54
+ if (!moduleFile.endsWith('Command.js')) {
55
+ continue;
56
+ }
57
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
58
+ const module = require(modulePath);
59
+ const moduleConstructor = typeof module.default === 'function' ? module.default : module;
60
+ const moduleInstance = new moduleConstructor();
61
+ if (!(moduleInstance instanceof BaseCommand_1.default)) {
62
+ core_1.utils.haltOrThrow('loading a command that does not extend BaseCommand');
63
+ }
64
+ const commandName = moduleInstance.getCommandName();
65
+ const loadingOssCommand = !core_1.fileManager.isWithinInternalDirectory(modulePath);
66
+ // reigster OSS commands
67
+ if (loadingOssCommand) {
68
+ this.OSSModules.set(commandName, moduleInstance);
69
+ }
70
+ // register all commands
71
+ if (this.modules.has(commandName)) {
72
+ // resolve conflict
73
+ const ossCommandLoaded = !core_1.fileManager.isWithinInternalDirectory(this.modulePaths.get(commandName));
74
+ if (ossCommandLoaded === loadingOssCommand) {
75
+ // when both commands are open source or neither are open source
76
+ core_1.info.midLevel(`MemLab command ${commandName} is already registered`);
77
+ }
78
+ else if (!ossCommandLoaded && loadingOssCommand) {
79
+ // when open source command tries to overwrite non-open source command
80
+ continue;
81
+ }
82
+ }
83
+ this.modules.set(commandName, moduleInstance);
84
+ this.modulePaths.set(commandName, modulePath);
85
+ }
86
+ }
87
+ postRegistration() {
88
+ const cliDocCommand = new GenerateCLIDocCommand_1.default();
89
+ const instance = this.modules.get(cliDocCommand.getCommandName());
90
+ if (instance) {
91
+ instance.setModulesMap(this.OSSModules);
92
+ }
93
+ }
94
+ }
95
+ exports.default = CommandLoader;
@@ -8,7 +8,7 @@
8
8
  * @format
9
9
  */
10
10
  import type { ParsedArgs } from 'minimist';
11
- import { MemLabConfig, AnyRecord } from '@memlab/core';
11
+ import type { AnyRecord, MemLabConfig } from '@memlab/core';
12
12
  import BaseCommand from './BaseCommand';
13
13
  declare type RunCommandOptions = {
14
14
  isPrerequisite?: boolean;
@@ -17,7 +17,6 @@ declare type RunCommandOptions = {
17
17
  };
18
18
  declare class CommandDispatcher {
19
19
  private modules;
20
- private modulePaths;
21
20
  private executedCommands;
22
21
  private executingCommandStack;
23
22
  constructor();
@@ -26,9 +25,6 @@ declare class CommandDispatcher {
26
25
  private runCommand;
27
26
  runSubCommandIfAny(command: BaseCommand, args: ParsedArgs, runCmdOpt: RunCommandOptions): Promise<void>;
28
27
  private helper;
29
- private registerBuiltInCommands;
30
- private registerCommands;
31
- private registerCommandsFromDir;
32
28
  }
33
29
  declare const _default: CommandDispatcher;
34
30
  export default _default;
@@ -22,40 +22,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  };
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  const core_1 = require("@memlab/core");
25
- const fs_1 = __importDefault(require("fs"));
26
- const path_1 = __importDefault(require("path"));
27
- const core_2 = require("@memlab/core");
28
- const BaseCommand_1 = __importDefault(require("./BaseCommand"));
29
25
  const HelperCommand_1 = __importDefault(require("./commands/helper/HelperCommand"));
30
- const VerboseOption_1 = __importDefault(require("./options/VerboseOption"));
31
- const SetContinuousTestOption_1 = __importDefault(require("./options/SetContinuousTestOption"));
32
- const DebugOption_1 = __importDefault(require("./options/DebugOption"));
33
- const SilentOption_1 = __importDefault(require("./options/SilentOption"));
34
- const HelperOption_1 = __importDefault(require("./options/HelperOption"));
35
- const universalOptions = [
36
- new HelperOption_1.default(),
37
- new VerboseOption_1.default(),
38
- new SetContinuousTestOption_1.default(),
39
- new DebugOption_1.default(),
40
- new SilentOption_1.default(),
41
- ];
26
+ const UniversalOptions_1 = __importDefault(require("./options/lib/UniversalOptions"));
27
+ const CommandLoader_1 = __importDefault(require("./CommandLoader"));
42
28
  const helperCommand = new HelperCommand_1.default();
43
- helperCommand.setUniversalOptions(universalOptions);
44
29
  class CommandDispatcher {
45
30
  constructor() {
46
31
  this.executedCommands = new Set();
47
32
  this.executingCommandStack = [];
48
33
  // auto load all command modules
49
- this.modules = new Map();
50
- this.modulePaths = new Map();
51
- this.registerBuiltInCommands();
52
- this.registerCommands();
34
+ const commandLoader = new CommandLoader_1.default();
35
+ this.modules = commandLoader.getModules();
53
36
  }
54
37
  dispatch(args) {
55
38
  return __awaiter(this, void 0, void 0, function* () {
56
39
  // triggered by `memlab` (without specific command)
57
40
  if (!args._ || !(args._.length >= 1)) {
58
- core_2.info.error('\n command argument missing');
41
+ core_1.info.error('\n command argument missing');
59
42
  yield this.helper(args);
60
43
  return;
61
44
  }
@@ -83,7 +66,7 @@ class CommandDispatcher {
83
66
  }
84
67
  parseOptions(command, config, args) {
85
68
  return __awaiter(this, void 0, void 0, function* () {
86
- const options = [...universalOptions, ...command.getOptions()];
69
+ const options = [...UniversalOptions_1.default, ...command.getOptions()];
87
70
  const configFromOptions = Object.create(null);
88
71
  for (const option of options) {
89
72
  const ret = yield option.run(config, args);
@@ -113,7 +96,7 @@ class CommandDispatcher {
113
96
  yield this.runCommand(prereq, args, Object.assign({ isPrerequisite: true }, runCmdOpt));
114
97
  }
115
98
  // parse command line options
116
- const c = yield this.parseOptions(command, core_2.config, args);
99
+ const c = yield this.parseOptions(command, core_1.config, args);
117
100
  Object.assign(runCmdOpt.configFromOptions, c);
118
101
  const { configFromOptions } = runCmdOpt;
119
102
  // execute command
@@ -142,7 +125,7 @@ class CommandDispatcher {
142
125
  return;
143
126
  }
144
127
  }
145
- core_2.info.error(`Invalid sub-command \`${args._[subCommandIndex]}\` of \`${command.getCommandName()}\`\n`);
128
+ core_1.info.error(`Invalid sub-command \`${args._[subCommandIndex]}\` of \`${command.getCommandName()}\`\n`);
146
129
  yield this.helper(args, command);
147
130
  });
148
131
  }
@@ -156,50 +139,5 @@ class CommandDispatcher {
156
139
  });
157
140
  });
158
141
  }
159
- registerBuiltInCommands() {
160
- // TBA
161
- }
162
- registerCommands() {
163
- const modulesDir = path_1.default.resolve(__dirname, 'commands');
164
- this.registerCommandsFromDir(modulesDir);
165
- }
166
- registerCommandsFromDir(modulesDir) {
167
- const moduleFiles = fs_1.default.readdirSync(modulesDir);
168
- for (const moduleFile of moduleFiles) {
169
- const modulePath = path_1.default.join(modulesDir, moduleFile);
170
- // recursively import modules from subdirectories
171
- if (fs_1.default.lstatSync(modulePath).isDirectory()) {
172
- this.registerCommandsFromDir(modulePath);
173
- continue;
174
- }
175
- // only import modules files ends with with Command.js
176
- if (!moduleFile.endsWith('Command.js')) {
177
- continue;
178
- }
179
- // eslint-disable-next-line @typescript-eslint/no-var-requires
180
- const module = require(modulePath);
181
- const moduleConstructor = typeof module.default === 'function' ? module.default : module;
182
- const moduleInstance = new moduleConstructor();
183
- if (!(moduleInstance instanceof BaseCommand_1.default)) {
184
- core_1.utils.haltOrThrow('loading a command that does not extend BaseCommand');
185
- }
186
- const commandName = moduleInstance.getCommandName();
187
- if (this.modules.has(commandName)) {
188
- // resolve conflict
189
- const ossCommandLoaded = !core_2.fileManager.isWithinInternalDirectory(this.modulePaths.get(commandName));
190
- const loadingOssCommand = !core_2.fileManager.isWithinInternalDirectory(modulePath);
191
- if (ossCommandLoaded === loadingOssCommand) {
192
- // when both commands are open source or neither are open source
193
- core_2.info.midLevel(`MemLab command ${commandName} is already registered`);
194
- }
195
- else if (!ossCommandLoaded && loadingOssCommand) {
196
- // when open source command tries to overwrite non-open source command
197
- continue;
198
- }
199
- }
200
- this.modules.set(commandName, moduleInstance);
201
- this.modulePaths.set(commandName, modulePath);
202
- }
203
- }
204
142
  }
205
143
  exports.default = new CommandDispatcher();
@@ -8,13 +8,15 @@
8
8
  * @format
9
9
  */
10
10
  import type { BaseOption, CLIOptions } from '@memlab/core';
11
- import BaseCommand from '../BaseCommand';
11
+ import BaseCommand, { CommandCategory } from '../BaseCommand';
12
12
  export default class MemLabRunCommand extends BaseCommand {
13
13
  getCommandName(): string;
14
14
  getDescription(): string;
15
15
  getExamples(): string[];
16
16
  getPrerequisites(): BaseCommand[];
17
17
  getOptions(): BaseOption[];
18
+ getExcludedOptions(): BaseOption[];
19
+ getCategory(): CommandCategory;
18
20
  run(options: CLIOptions): Promise<void>;
19
21
  }
20
22
  //# sourceMappingURL=MemLabRunCommand.d.ts.map
@@ -8,6 +8,29 @@
8
8
  * @emails oncall+ws_labs
9
9
  * @format
10
10
  */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
11
34
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
12
35
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
13
36
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -24,11 +47,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
24
47
  const fs_1 = __importDefault(require("fs"));
25
48
  const path_1 = __importDefault(require("path"));
26
49
  const core_1 = require("@memlab/core");
27
- const BaseCommand_1 = __importDefault(require("../BaseCommand"));
50
+ const BaseCommand_1 = __importStar(require("../BaseCommand"));
28
51
  const CheckLeakCommand_1 = __importDefault(require("./heap/CheckLeakCommand"));
29
52
  const InitDirectoryCommand_1 = __importDefault(require("./InitDirectoryCommand"));
30
53
  const TakeSnapshotCommand_1 = __importDefault(require("./snapshot/TakeSnapshotCommand"));
31
54
  const SetWorkingDirectoryOption_1 = __importDefault(require("../options/SetWorkingDirectoryOption"));
55
+ const AppOption_1 = __importDefault(require("../options/AppOption"));
56
+ const InteractionOption_1 = __importDefault(require("../options/InteractionOption"));
57
+ const SkipSnapshotOption_1 = __importDefault(require("../options/SkipSnapshotOption"));
58
+ const RunningModeOption_1 = __importDefault(require("../options/RunningModeOption"));
59
+ const BaselineFileOption_1 = __importDefault(require("../options/heap/BaselineFileOption"));
60
+ const TargetFileOption_1 = __importDefault(require("../options/heap/TargetFileOption"));
61
+ const FinalFileOption_1 = __importDefault(require("../options/heap/FinalFileOption"));
62
+ const SnapshotDirectoryOption_1 = __importDefault(require("../options/heap/SnapshotDirectoryOption"));
63
+ const JSEngineOption_1 = __importDefault(require("../options/heap/JSEngineOption"));
32
64
  class MemLabRunCommand extends BaseCommand_1.default {
33
65
  getCommandName() {
34
66
  return 'run';
@@ -37,7 +69,11 @@ class MemLabRunCommand extends BaseCommand_1.default {
37
69
  return 'find memory leaks in web apps';
38
70
  }
39
71
  getExamples() {
40
- return ['--app=comet --interaction=watch'];
72
+ return [
73
+ '--scenario <TEST_SCENARIO_FILE>',
74
+ '--scenario /tmp/test-scenario.js',
75
+ '--scenario /tmp/test-scenario.js --work-dir /tmp/test-1/',
76
+ ];
41
77
  }
42
78
  getPrerequisites() {
43
79
  return [
@@ -49,6 +85,22 @@ class MemLabRunCommand extends BaseCommand_1.default {
49
85
  getOptions() {
50
86
  return [new SetWorkingDirectoryOption_1.default()];
51
87
  }
88
+ getExcludedOptions() {
89
+ return [
90
+ new AppOption_1.default(),
91
+ new InteractionOption_1.default(),
92
+ new SkipSnapshotOption_1.default(),
93
+ new RunningModeOption_1.default(),
94
+ new BaselineFileOption_1.default(),
95
+ new TargetFileOption_1.default(),
96
+ new FinalFileOption_1.default(),
97
+ new SnapshotDirectoryOption_1.default(),
98
+ new JSEngineOption_1.default(),
99
+ ];
100
+ }
101
+ getCategory() {
102
+ return BaseCommand_1.CommandCategory.COMMON;
103
+ }
52
104
  run(options) {
53
105
  var _a;
54
106
  return __awaiter(this, void 0, void 0, function* () {
@@ -14,6 +14,7 @@ export default class RunMeasureCommand extends BaseCommand {
14
14
  getCommandName(): string;
15
15
  getDescription(): string;
16
16
  getPrerequisites(): BaseCommand[];
17
+ getExamples(): string[];
17
18
  getOptions(): BaseOption[];
18
19
  run(options: CLIOptions): Promise<void>;
19
20
  }
@@ -50,6 +50,13 @@ class RunMeasureCommand extends BaseCommand_1.default {
50
50
  getPrerequisites() {
51
51
  return [new CleanRunDataCommand_1.default(), new InitDirectoryCommand_1.default()];
52
52
  }
53
+ getExamples() {
54
+ return [
55
+ '--scenario <TEST_SCENARIO_FILE>',
56
+ '--scenario /tmp/test-scenario.js',
57
+ '--scenario /tmp/test-scenario.js --work-dir /tmp/test-1/',
58
+ ];
59
+ }
53
60
  getOptions() {
54
61
  return [
55
62
  new NumberOfRunsOption_1.default(),
@@ -14,6 +14,7 @@ export default class FBWarmupAppCommand extends BaseCommand {
14
14
  getCommandName(): string;
15
15
  getDescription(): string;
16
16
  getPrerequisites(): BaseCommand[];
17
+ getExamples(): string[];
17
18
  getOptions(): BaseOption[];
18
19
  run(_options: CLIOptions): Promise<void>;
19
20
  }
@@ -43,6 +43,12 @@ class FBWarmupAppCommand extends BaseCommand_1.default {
43
43
  getPrerequisites() {
44
44
  return [new InitDirectoryCommand_1.default(), new CheckXvfbSupportCommand_1.default()];
45
45
  }
46
+ getExamples() {
47
+ return [
48
+ '--scenario <TEST_SCENARIO_FILE>',
49
+ '--scenario /tmp/test-scenario.js',
50
+ ];
51
+ }
46
52
  getOptions() {
47
53
  return [
48
54
  new AppOption_1.default(),
@@ -69,7 +69,11 @@ class GetRetainerTraceCommand extends BaseCommand_1.default {
69
69
  return BaseCommand_1.CommandCategory.COMMON;
70
70
  }
71
71
  getExamples() {
72
- return ['--node-id=@3123123'];
72
+ return [
73
+ '--node-id=<HEAP_OBJECT_ID>',
74
+ '--node-id=@3123123',
75
+ '--node-id=128127',
76
+ ];
73
77
  }
74
78
  getOptions() {
75
79
  return [
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @emails oncall+ws_labs
8
+ * @format
9
+ */
10
+ import type { CLIOptions } from '@memlab/core';
11
+ import BaseCommand from '../../BaseCommand';
12
+ export default class GenerateCLIDocCommand extends BaseCommand {
13
+ private modules;
14
+ private generatedCommandInIndex;
15
+ private universalOptions;
16
+ getCommandName(): string;
17
+ getDescription(): string;
18
+ isInternalCommand(): boolean;
19
+ setModulesMap(modules: Map<string, BaseCommand>): void;
20
+ run(options: CLIOptions): Promise<void>;
21
+ private generateDocs;
22
+ private writeCommandCategories;
23
+ private writeCategory;
24
+ private writeCategoryHeader;
25
+ private writeCommand;
26
+ private writeTextWithNewLine;
27
+ private touchFile;
28
+ private writeCodeBlock;
29
+ private writeCommandOptions;
30
+ }
31
+ //# sourceMappingURL=GenerateCLIDocCommand.d.ts.map
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @emails oncall+ws_labs
9
+ * @format
10
+ */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
35
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
36
+ return new (P || (P = Promise))(function (resolve, reject) {
37
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
38
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
39
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
40
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
41
+ });
42
+ };
43
+ var __importDefault = (this && this.__importDefault) || function (mod) {
44
+ return (mod && mod.__esModule) ? mod : { "default": mod };
45
+ };
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ const path_1 = __importDefault(require("path"));
48
+ const fs_extra_1 = __importDefault(require("fs-extra"));
49
+ const BaseCommand_1 = __importStar(require("../../BaseCommand"));
50
+ const core_1 = require("@memlab/core");
51
+ const UniversalOptions_1 = __importDefault(require("../../options/lib/UniversalOptions"));
52
+ class GenerateCLIDocCommand extends BaseCommand_1.default {
53
+ constructor() {
54
+ super(...arguments);
55
+ this.modules = new Map();
56
+ this.generatedCommandInIndex = new Set();
57
+ this.universalOptions = UniversalOptions_1.default;
58
+ }
59
+ getCommandName() {
60
+ return 'gen-cli-doc';
61
+ }
62
+ getDescription() {
63
+ return 'generate CLI markdown documentations';
64
+ }
65
+ isInternalCommand() {
66
+ return true;
67
+ }
68
+ setModulesMap(modules) {
69
+ this.modules = modules;
70
+ }
71
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
72
+ run(options) {
73
+ return __awaiter(this, void 0, void 0, function* () {
74
+ const docsDir = core_1.fileManager.getDocDir();
75
+ const cliDocsDir = path_1.default.join(docsDir, 'cli');
76
+ this.generateDocs(cliDocsDir);
77
+ });
78
+ }
79
+ generateDocs(cliDocsDir) {
80
+ // first clean up the dir
81
+ fs_extra_1.default.removeSync(cliDocsDir);
82
+ fs_extra_1.default.mkdirSync(cliDocsDir);
83
+ // create index markdown file
84
+ const indexFile = path_1.default.join(cliDocsDir, 'CLI-commands.md');
85
+ this.writeCommandCategories(indexFile);
86
+ }
87
+ writeCommandCategories(docFile) {
88
+ this.writeTextWithNewLine(docFile, '# Command Line Interface');
89
+ for (const category in BaseCommand_1.CommandCategory) {
90
+ const commandsToPrintFirst = [];
91
+ this.writeCategory(docFile, category, commandsToPrintFirst);
92
+ }
93
+ }
94
+ writeCategory(docFile, category, commandsToPrintFirst) {
95
+ // commands defined in commandsToPrintFirst
96
+ const commands = [];
97
+ for (const command of commandsToPrintFirst) {
98
+ const name = command.getCommandName();
99
+ if (this.generatedCommandInIndex.has(name)) {
100
+ continue;
101
+ }
102
+ commands.push(command);
103
+ }
104
+ // other commands in this category
105
+ for (const moduleEntries of this.modules) {
106
+ const command = moduleEntries[1];
107
+ if (category !== command.getCategory()) {
108
+ continue;
109
+ }
110
+ if (command.isInternalCommand() && !core_1.config.verbose) {
111
+ continue;
112
+ }
113
+ const name = command.getCommandName();
114
+ if (this.generatedCommandInIndex.has(name)) {
115
+ continue;
116
+ }
117
+ commands.push(command);
118
+ }
119
+ if (commands.length === 0) {
120
+ return;
121
+ }
122
+ this.writeCategoryHeader(docFile, category);
123
+ for (const command of commands) {
124
+ this.writeCommand(docFile, command);
125
+ this.generatedCommandInIndex.add(command.getCommandName());
126
+ }
127
+ this.writeTextWithNewLine(docFile, '');
128
+ }
129
+ writeCategoryHeader(docFile, category) {
130
+ const categoryName = category.toUpperCase();
131
+ this.writeTextWithNewLine(docFile, `\n## ${categoryName} Commands\n`);
132
+ }
133
+ writeCommand(docFile, command, indent = '') {
134
+ const name = command.getFullCommand();
135
+ const desc = core_1.utils.upperCaseFirstCharacter(command.getDescription());
136
+ // write command title
137
+ this.writeTextWithNewLine(docFile, `\n###${indent} memlab ${name}\n`);
138
+ // write description
139
+ this.writeTextWithNewLine(docFile, `${desc}\n`);
140
+ // get example
141
+ const examples = command.getExamples();
142
+ let example = '';
143
+ if (examples.length > 0) {
144
+ example = examples[0].trim();
145
+ }
146
+ // write command synopsis
147
+ const cmd = `memlab ${name} ${example}`;
148
+ this.writeCodeBlock(docFile, cmd, 'bash');
149
+ // write command examples if there is any
150
+ const exampleBlock = examples
151
+ .slice(1)
152
+ .map(example => `memlab ${name} ${example.trim()}`)
153
+ .join('\n');
154
+ if (exampleBlock.length > 0) {
155
+ this.writeTextWithNewLine(docFile, '\n#### examples\n');
156
+ this.writeCodeBlock(docFile, exampleBlock, 'bash');
157
+ }
158
+ // write options
159
+ this.writeCommandOptions(docFile, command);
160
+ const subCommands = command.getSubCommands();
161
+ for (const subCommand of subCommands) {
162
+ this.writeCommand(docFile, subCommand, indent + '#');
163
+ }
164
+ }
165
+ writeTextWithNewLine(docFile, content) {
166
+ this.touchFile(docFile);
167
+ fs_extra_1.default.appendFileSync(docFile, `${content}\n`, 'UTF-8');
168
+ }
169
+ touchFile(docFile) {
170
+ if (!fs_extra_1.default.existsSync(docFile)) {
171
+ fs_extra_1.default.writeFileSync(docFile, '', 'UTF-8');
172
+ }
173
+ }
174
+ writeCodeBlock(docFile, code, codeType = '') {
175
+ let normalizedCode = code;
176
+ while (normalizedCode.endsWith('\n')) {
177
+ normalizedCode = normalizedCode.slice(0, normalizedCode.length - 1);
178
+ }
179
+ this.touchFile(docFile);
180
+ fs_extra_1.default.appendFileSync(docFile, '```' + codeType + '\n' + normalizedCode + '\n```\n', 'UTF-8');
181
+ }
182
+ writeCommandOptions(docFile, command) {
183
+ const options = [
184
+ ...command.getFullOptionsFromPrerequisiteChain(),
185
+ ...this.universalOptions,
186
+ ];
187
+ if (options.length === 0) {
188
+ return;
189
+ }
190
+ this.writeTextWithNewLine(docFile, '\n**Options**:');
191
+ const optionsText = [];
192
+ for (const option of options) {
193
+ let header = `**\`--${option.getOptionName()}\`**`;
194
+ if (option.getOptionShortcut()) {
195
+ header += `, **\`-${option.getOptionShortcut()}\`**`;
196
+ }
197
+ const desc = option.getDescription();
198
+ optionsText.push({ header, desc });
199
+ }
200
+ for (const optionText of optionsText) {
201
+ const header = optionText.header;
202
+ const msg = ` * ${header}: ${optionText.desc}`;
203
+ this.writeTextWithNewLine(docFile, msg);
204
+ }
205
+ }
206
+ }
207
+ exports.default = GenerateCLIDocCommand;
@@ -50,11 +50,12 @@ const string_width_1 = __importDefault(require("string-width"));
50
50
  const core_1 = require("@memlab/core");
51
51
  const CommandOrder_1 = __importDefault(require("./lib/CommandOrder"));
52
52
  const BaseCommand_1 = __importStar(require("../../BaseCommand"));
53
+ const UniversalOptions_1 = __importDefault(require("../../options/lib/UniversalOptions"));
53
54
  class HelperCommand extends BaseCommand_1.default {
54
55
  constructor() {
55
56
  super(...arguments);
56
57
  this.printedCommand = new Set();
57
- this.universalOptions = [];
58
+ this.universalOptions = UniversalOptions_1.default;
58
59
  }
59
60
  // The following terminal command will initiate with this command
60
61
  // `memlab <command-name>`
@@ -14,6 +14,7 @@ export default class TakeSnapshotCommand extends BaseCommand {
14
14
  getCommandName(): string;
15
15
  getDescription(): string;
16
16
  getPrerequisites(): BaseCommand[];
17
+ getExamples(): string[];
17
18
  getOptions(): BaseOption[];
18
19
  run(_options: CLIOptions): Promise<void>;
19
20
  }
@@ -53,6 +53,13 @@ class TakeSnapshotCommand extends BaseCommand_1.default {
53
53
  new CheckXvfbSupportCommand_1.default(),
54
54
  ];
55
55
  }
56
+ getExamples() {
57
+ return [
58
+ '--scenario <TEST_SCENARIO_FILE>',
59
+ '--scenario /tmp/test-scenario.js',
60
+ '--scenario /tmp/test-scenario.js --work-dir /tmp/test-1/',
61
+ ];
62
+ }
56
63
  getOptions() {
57
64
  return [
58
65
  new AppOption_1.default(),
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @emails oncall+ws_labs
8
+ * @format
9
+ */
10
+ import DebugOption from '../DebugOption';
11
+ import HelperOption from '../HelperOption';
12
+ import SetContinuousTestOption from '../SetContinuousTestOption';
13
+ import SilentOption from '../SilentOption';
14
+ import VerboseOption from '../VerboseOption';
15
+ declare const universalOptions: (DebugOption | HelperOption | SetContinuousTestOption | SilentOption | VerboseOption)[];
16
+ export default universalOptions;
17
+ //# sourceMappingURL=UniversalOptions.d.ts.map
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @emails oncall+ws_labs
9
+ * @format
10
+ */
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const DebugOption_1 = __importDefault(require("../DebugOption"));
16
+ const HelperOption_1 = __importDefault(require("../HelperOption"));
17
+ const SetContinuousTestOption_1 = __importDefault(require("../SetContinuousTestOption"));
18
+ const SilentOption_1 = __importDefault(require("../SilentOption"));
19
+ const VerboseOption_1 = __importDefault(require("../VerboseOption"));
20
+ const universalOptions = [
21
+ new HelperOption_1.default(),
22
+ new VerboseOption_1.default(),
23
+ new SetContinuousTestOption_1.default(),
24
+ new DebugOption_1.default(),
25
+ new SilentOption_1.default(),
26
+ ];
27
+ exports.default = universalOptions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memlab/cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "license": "MIT",
5
5
  "description": "command line interface for memlab",
6
6
  "author": "Liang Gong <lgong@fb.com>",
@@ -55,6 +55,7 @@
55
55
  },
56
56
  "scripts": {
57
57
  "build-pkg": "tsc",
58
+ "publish-patch": "npm version patch --force && npm publish",
58
59
  "clean-pkg": "rm -rf ./dist && rm -rf ./node_modules && rm -f ./tsconfig.tsbuildinfo"
59
60
  },
60
61
  "bugs": {