oclif 4.7.8 → 4.8.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.
package/README.md CHANGED
@@ -100,11 +100,12 @@ hello world! (./src/commands/hello/world.ts)
100
100
 
101
101
  - [`oclif generate`](docs/generate.md) - Generate a new CLI
102
102
  - [`oclif help`](docs/help.md) - Display help for oclif.
103
+ - [`oclif init`](docs/init.md) - Initialize a new oclif CLI
103
104
  - [`oclif manifest`](docs/manifest.md) - Generates plugin manifest json (oclif.manifest.json).
104
- - [`oclif pack`](docs/pack.md) - package an oclif CLI into installable artifacts
105
+ - [`oclif pack`](docs/pack.md) - Package an oclif CLI into installable artifacts.
105
106
  - [`oclif promote`](docs/promote.md) - Promote CLI builds to a S3 release channel.
106
107
  - [`oclif readme`](docs/readme.md) - Adds commands to README.md in current directory.
107
- - [`oclif upload`](docs/upload.md) - upload installable CLI artifacts to AWS S3
108
+ - [`oclif upload`](docs/upload.md) - Upload installable CLI artifacts to AWS S3.
108
109
 
109
110
  <!-- commandsstop -->
110
111
 
@@ -0,0 +1,39 @@
1
+ import { GeneratorCommand } from '../generator';
2
+ export default class Generate extends GeneratorCommand<typeof Generate> {
3
+ static description: string;
4
+ static examples: {
5
+ command: string;
6
+ description: string;
7
+ }[];
8
+ static flaggablePrompts: {
9
+ bin: {
10
+ message: string;
11
+ validate: (d: string) => true | "Invalid bin name";
12
+ };
13
+ 'module-type': {
14
+ message: string;
15
+ options: readonly ["ESM", "CommonJS"];
16
+ validate: (d: string) => true | "Invalid module type";
17
+ };
18
+ 'package-manager': {
19
+ message: string;
20
+ options: readonly ["npm", "yarn", "pnpm"];
21
+ validate: (d: string) => true | "Invalid package manager";
22
+ };
23
+ 'topic-separator': {
24
+ message: string;
25
+ options: string[];
26
+ validate: (d: string) => true | "Invalid topic separator";
27
+ };
28
+ };
29
+ static flags: {
30
+ 'output-dir': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
31
+ yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
32
+ bin: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
33
+ "module-type": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
34
+ "package-manager": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
35
+ "topic-separator": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
36
+ };
37
+ static summary: string;
38
+ run(): Promise<void>;
39
+ }
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const core_1 = require("@oclif/core");
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const promises_1 = require("node:fs/promises");
9
+ const node_path_1 = require("node:path");
10
+ const generator_1 = require("../generator");
11
+ const util_1 = require("../util");
12
+ const VALID_MODULE_TYPES = ['ESM', 'CommonJS'];
13
+ const VALID_PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm'];
14
+ function isPackageManager(d) {
15
+ return VALID_PACKAGE_MANAGERS.includes(d);
16
+ }
17
+ function isModuleType(d) {
18
+ return VALID_MODULE_TYPES.includes(d);
19
+ }
20
+ const FLAGGABLE_PROMPTS = {
21
+ bin: {
22
+ message: 'Command bin name the CLI will export',
23
+ validate: (d) => (0, util_1.validateBin)(d) || 'Invalid bin name',
24
+ },
25
+ 'module-type': {
26
+ message: 'Select a module type',
27
+ options: VALID_MODULE_TYPES,
28
+ validate: (d) => isModuleType(d) || 'Invalid module type',
29
+ },
30
+ 'package-manager': {
31
+ message: 'Select a package manager',
32
+ options: VALID_PACKAGE_MANAGERS,
33
+ validate: (d) => isPackageManager(d) || 'Invalid package manager',
34
+ },
35
+ 'topic-separator': {
36
+ message: 'Select a topic separator',
37
+ options: ['colons', 'spaces'],
38
+ validate: (d) => d === 'colons' || d === 'spaces' || 'Invalid topic separator',
39
+ },
40
+ };
41
+ class Generate extends generator_1.GeneratorCommand {
42
+ static description = 'This will add the necessary oclif bin files, add oclif config to package.json, and install @oclif/core and ts-node.';
43
+ static examples = [
44
+ {
45
+ command: '<%= config.bin %> <%= command.id %>',
46
+ description: 'Initialize a new CLI in the current directory',
47
+ },
48
+ {
49
+ command: '<%= config.bin %> <%= command.id %> --output-dir "/path/to/existing/project"',
50
+ description: 'Initialize a new CLI in a different directory',
51
+ },
52
+ {
53
+ command: '<%= config.bin %> <%= command.id %> --topic-separator colons --bin mycli',
54
+ description: 'Supply answers for specific prompts',
55
+ },
56
+ ];
57
+ static flaggablePrompts = FLAGGABLE_PROMPTS;
58
+ static flags = {
59
+ ...(0, generator_1.makeFlags)(FLAGGABLE_PROMPTS),
60
+ 'output-dir': core_1.Flags.directory({
61
+ char: 'd',
62
+ description: 'Directory to initialize the CLI in.',
63
+ exists: true,
64
+ }),
65
+ yes: core_1.Flags.boolean({
66
+ aliases: ['defaults'],
67
+ char: 'y',
68
+ description: 'Use defaults for all prompts. Individual flags will override defaults.',
69
+ }),
70
+ };
71
+ static summary = 'Initialize a new oclif CLI';
72
+ async run() {
73
+ const outputDir = this.flags['output-dir'] ?? process.cwd();
74
+ const location = (0, node_path_1.resolve)(outputDir);
75
+ this.log(`Initializing oclif in ${chalk_1.default.green(location)}`);
76
+ const packageJSON = (await (0, generator_1.readPJSON)(location));
77
+ if (!packageJSON) {
78
+ throw new core_1.Errors.CLIError(`Could not find a package.json file in ${location}`);
79
+ }
80
+ const bin = await this.getFlagOrPrompt({
81
+ defaultValue: location.split(node_path_1.sep).at(-1) || '',
82
+ name: 'bin',
83
+ type: 'input',
84
+ });
85
+ const topicSeparator = await this.getFlagOrPrompt({
86
+ defaultValue: 'spaces',
87
+ name: 'topic-separator',
88
+ type: 'select',
89
+ });
90
+ const moduleType = await this.getFlagOrPrompt({
91
+ defaultValue: packageJSON.type === 'module' ? 'ESM' : 'CommonJS',
92
+ async maybeOtherValue() {
93
+ return packageJSON.type === 'module' ? 'ESM' : packageJSON.type === 'commonjs' ? 'CommonJS' : undefined;
94
+ },
95
+ name: 'module-type',
96
+ type: 'select',
97
+ });
98
+ const packageManager = await this.getFlagOrPrompt({
99
+ defaultValue: 'npm',
100
+ async maybeOtherValue() {
101
+ const rootFiles = await (0, promises_1.readdir)(location);
102
+ if (rootFiles.includes('package-lock.json')) {
103
+ return 'npm';
104
+ }
105
+ if (rootFiles.includes('yarn.lock')) {
106
+ return 'yarn';
107
+ }
108
+ if (rootFiles.includes('pnpm-lock.yaml')) {
109
+ return 'pnpm';
110
+ }
111
+ },
112
+ name: 'package-manager',
113
+ type: 'select',
114
+ });
115
+ this.log(`Using module type ${chalk_1.default.green(moduleType)}`);
116
+ this.log(`Using package manager ${chalk_1.default.green(packageManager)}`);
117
+ const templateOptions = { moduleType };
118
+ const projectBinPath = (0, node_path_1.join)(location, 'bin');
119
+ await this.template((0, node_path_1.join)(this.templatesDir, 'src', 'init', 'dev.cmd.ejs'), (0, node_path_1.join)(projectBinPath, 'dev.cmd'), templateOptions);
120
+ await this.template((0, node_path_1.join)(this.templatesDir, 'src', 'init', 'dev.js.ejs'), (0, node_path_1.join)(projectBinPath, 'dev.js'), templateOptions);
121
+ await this.template((0, node_path_1.join)(this.templatesDir, 'src', 'init', 'run.cmd.ejs'), (0, node_path_1.join)(projectBinPath, 'run.cmd'), templateOptions);
122
+ await this.template((0, node_path_1.join)(this.templatesDir, 'src', 'init', 'run.js.ejs'), (0, node_path_1.join)(projectBinPath, 'run.js'), templateOptions);
123
+ if (process.platform !== 'win32') {
124
+ await (0, generator_1.exec)(`chmod +x ${(0, node_path_1.join)(projectBinPath, 'run.js')}`);
125
+ await (0, generator_1.exec)(`chmod +x ${(0, node_path_1.join)(projectBinPath, 'dev.js')}`);
126
+ }
127
+ const updatedPackageJSON = {
128
+ ...packageJSON,
129
+ bin: {
130
+ ...packageJSON.bin,
131
+ [bin]: './bin/run.js',
132
+ },
133
+ oclif: {
134
+ bin,
135
+ commands: './dist/commands',
136
+ dirname: bin,
137
+ topicSeparator: topicSeparator === 'colons' ? ':' : ' ',
138
+ ...packageJSON.oclif,
139
+ },
140
+ };
141
+ await (0, promises_1.writeFile)((0, node_path_1.join)(location, 'package.json'), JSON.stringify(updatedPackageJSON, null, 2));
142
+ const installedDeps = Object.keys(packageJSON.dependencies ?? {});
143
+ if (!installedDeps.includes('@oclif/core')) {
144
+ this.log('Installing @oclif/core');
145
+ await (0, generator_1.exec)(`${packageManager} ${packageManager === 'yarn' ? 'add' : 'install'} @oclif/core`, {
146
+ cwd: location,
147
+ silent: false,
148
+ });
149
+ }
150
+ const allInstalledDeps = [...installedDeps, ...Object.keys(packageJSON.devDependencies ?? {})];
151
+ if (!allInstalledDeps.includes('ts-node')) {
152
+ this.log('Installing ts-node');
153
+ await (0, generator_1.exec)(`${packageManager} ${packageManager === 'yarn' ? 'add --dev' : 'install --save-dev'} ts-node`, {
154
+ cwd: location,
155
+ silent: false,
156
+ });
157
+ }
158
+ this.log(`\nCreated CLI ${chalk_1.default.green(bin)}`);
159
+ }
160
+ }
161
+ exports.default = Generate;
@@ -3,12 +3,31 @@ import { Command, Interfaces } from '@oclif/core';
3
3
  import { ExecOptions } from 'node:child_process';
4
4
  export type FlaggablePrompt = {
5
5
  message: string;
6
- options?: string[];
6
+ options?: readonly string[] | string[];
7
7
  validate: (d: string) => boolean | string;
8
8
  };
9
9
  export type FlagsOfPrompts<T extends Record<string, FlaggablePrompt>> = Record<keyof T, Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>>;
10
10
  export type Flags<T extends typeof Command> = Interfaces.InferredFlags<(typeof GeneratorCommand)['baseFlags'] & T['flags']>;
11
11
  export type Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;
12
+ export type GetFlagOrPromptOptions = {
13
+ /**
14
+ * The default value for the prompt if the `--yes` flag is provided.
15
+ */
16
+ defaultValue: string;
17
+ /**
18
+ * A function that returns a value if the user has not provided the value via the flag. This will rerun before
19
+ * the check for the `--yes` flag.
20
+ */
21
+ maybeOtherValue?: () => Promise<string | undefined>;
22
+ /**
23
+ * The name of the flaggable prompt. Corresponds to the key in `this.flags`.
24
+ */
25
+ name: string;
26
+ /**
27
+ * The type of prompt to display.
28
+ */
29
+ type: 'input' | 'select';
30
+ };
12
31
  export declare function exec(command: string, opts?: ExecOptions & {
13
32
  silent?: boolean;
14
33
  }): Promise<{
@@ -24,11 +43,16 @@ export declare abstract class GeneratorCommand<T extends typeof Command> extends
24
43
  protected flaggablePrompts: Record<string, FlaggablePrompt>;
25
44
  protected flags: Flags<T>;
26
45
  templatesDir: string;
27
- getFlagOrPrompt({ defaultValue, name, type, }: {
28
- defaultValue: string;
29
- name: string;
30
- type: 'input' | 'select';
31
- }): Promise<any>;
46
+ /**
47
+ * Get a flag value or prompt the user for a value.
48
+ *
49
+ * Resolution order:
50
+ * - Flag value provided by the user
51
+ * - Value returned by `maybeOtherValue`
52
+ * - `defaultValue` if the `--yes` flag is provided
53
+ * - Prompt the user for a value
54
+ */
55
+ getFlagOrPrompt({ defaultValue, maybeOtherValue, name, type }: GetFlagOrPromptOptions): Promise<string>;
32
56
  init(): Promise<void>;
33
57
  template(source: string, destination: string, data?: Record<string, unknown>): Promise<void>;
34
58
  }
package/lib/generator.js CHANGED
@@ -62,7 +62,16 @@ class GeneratorCommand extends core_1.Command {
62
62
  flaggablePrompts;
63
63
  flags;
64
64
  templatesDir;
65
- async getFlagOrPrompt({ defaultValue, name, type, }) {
65
+ /**
66
+ * Get a flag value or prompt the user for a value.
67
+ *
68
+ * Resolution order:
69
+ * - Flag value provided by the user
70
+ * - Value returned by `maybeOtherValue`
71
+ * - `defaultValue` if the `--yes` flag is provided
72
+ * - Prompt the user for a value
73
+ */
74
+ async getFlagOrPrompt({ defaultValue, maybeOtherValue, name, type }) {
66
75
  if (!this.flaggablePrompts)
67
76
  throw new Error('No flaggable prompts defined');
68
77
  if (!this.flaggablePrompts[name])
@@ -79,9 +88,19 @@ class GeneratorCommand extends core_1.Command {
79
88
  return defaultValue;
80
89
  }
81
90
  };
91
+ const checkMaybeOtherValue = async () => {
92
+ if (!maybeOtherValue)
93
+ return;
94
+ const otherValue = await maybeOtherValue();
95
+ if (otherValue) {
96
+ this.log(`${chalk_1.default.green('?')} ${chalk_1.default.bold(this.flaggablePrompts[name].message)} ${chalk_1.default.cyan(otherValue)}`);
97
+ return otherValue;
98
+ }
99
+ };
82
100
  switch (type) {
83
101
  case 'select': {
84
102
  return (maybeFlag() ??
103
+ (await checkMaybeOtherValue()) ??
85
104
  maybeDefault() ??
86
105
  // Dynamic import because @inquirer/select is ESM only. Once oclif is ESM, we can make this a normal import
87
106
  // so that we can avoid importing on every single question.
@@ -93,6 +112,7 @@ class GeneratorCommand extends core_1.Command {
93
112
  }
94
113
  case 'input': {
95
114
  return (maybeFlag() ??
115
+ (await checkMaybeOtherValue()) ??
96
116
  maybeDefault() ??
97
117
  // Dynamic import because @inquirer/input is ESM only. Once oclif is ESM, we can make this a normal import
98
118
  // so that we can avoid importing on every single question.
@@ -173,6 +173,127 @@
173
173
  "generate.js"
174
174
  ]
175
175
  },
176
+ "init": {
177
+ "aliases": [],
178
+ "args": {},
179
+ "description": "This will add the necessary oclif bin files, add oclif config to package.json, and install @oclif/core and ts-node.",
180
+ "examples": [
181
+ {
182
+ "command": "<%= config.bin %> <%= command.id %>",
183
+ "description": "Initialize a new CLI in the current directory"
184
+ },
185
+ {
186
+ "command": "<%= config.bin %> <%= command.id %> --output-dir \"/path/to/existing/project\"",
187
+ "description": "Initialize a new CLI in a different directory"
188
+ },
189
+ {
190
+ "command": "<%= config.bin %> <%= command.id %> --topic-separator colons --bin mycli",
191
+ "description": "Supply answers for specific prompts"
192
+ }
193
+ ],
194
+ "flags": {
195
+ "bin": {
196
+ "description": "Supply answer for prompt: Command bin name the CLI will export",
197
+ "name": "bin",
198
+ "hasDynamicHelp": false,
199
+ "multiple": false,
200
+ "type": "option"
201
+ },
202
+ "module-type": {
203
+ "description": "Supply answer for prompt: Select a module type",
204
+ "name": "module-type",
205
+ "hasDynamicHelp": false,
206
+ "multiple": false,
207
+ "options": [
208
+ "ESM",
209
+ "CommonJS"
210
+ ],
211
+ "type": "option"
212
+ },
213
+ "package-manager": {
214
+ "description": "Supply answer for prompt: Select a package manager",
215
+ "name": "package-manager",
216
+ "hasDynamicHelp": false,
217
+ "multiple": false,
218
+ "options": [
219
+ "npm",
220
+ "yarn",
221
+ "pnpm"
222
+ ],
223
+ "type": "option"
224
+ },
225
+ "topic-separator": {
226
+ "description": "Supply answer for prompt: Select a topic separator",
227
+ "name": "topic-separator",
228
+ "hasDynamicHelp": false,
229
+ "multiple": false,
230
+ "options": [
231
+ "colons",
232
+ "spaces"
233
+ ],
234
+ "type": "option"
235
+ },
236
+ "output-dir": {
237
+ "char": "d",
238
+ "description": "Directory to initialize the CLI in.",
239
+ "name": "output-dir",
240
+ "hasDynamicHelp": false,
241
+ "multiple": false,
242
+ "type": "option"
243
+ },
244
+ "yes": {
245
+ "aliases": [
246
+ "defaults"
247
+ ],
248
+ "char": "y",
249
+ "description": "Use defaults for all prompts. Individual flags will override defaults.",
250
+ "name": "yes",
251
+ "allowNo": false,
252
+ "type": "boolean"
253
+ }
254
+ },
255
+ "hasDynamicHelp": false,
256
+ "hiddenAliases": [],
257
+ "id": "init",
258
+ "pluginAlias": "oclif",
259
+ "pluginName": "oclif",
260
+ "pluginType": "core",
261
+ "strict": true,
262
+ "summary": "Initialize a new oclif CLI",
263
+ "flaggablePrompts": {
264
+ "bin": {
265
+ "message": "Command bin name the CLI will export"
266
+ },
267
+ "module-type": {
268
+ "message": "Select a module type",
269
+ "options": [
270
+ "ESM",
271
+ "CommonJS"
272
+ ]
273
+ },
274
+ "package-manager": {
275
+ "message": "Select a package manager",
276
+ "options": [
277
+ "npm",
278
+ "yarn",
279
+ "pnpm"
280
+ ]
281
+ },
282
+ "topic-separator": {
283
+ "message": "Select a topic separator",
284
+ "options": [
285
+ "colons",
286
+ "spaces"
287
+ ]
288
+ }
289
+ },
290
+ "isESM": false,
291
+ "relativePath": [
292
+ "lib",
293
+ "commands",
294
+ "init.js"
295
+ ]
296
+ },
176
297
  "lock": {
177
298
  "aliases": [],
178
299
  "args": {},
@@ -911,5 +1032,5 @@
911
1032
  ]
912
1033
  }
913
1034
  },
914
- "version": "4.7.8"
1035
+ "version": "4.8.0"
915
1036
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "oclif",
3
3
  "description": "oclif: create your own CLI",
4
- "version": "4.7.8",
4
+ "version": "4.8.0",
5
5
  "author": "Salesforce",
6
6
  "bin": {
7
7
  "oclif": "bin/run.js"
@@ -114,10 +114,10 @@
114
114
  },
115
115
  "topics": {
116
116
  "pack": {
117
- "description": "package an oclif CLI into installable artifacts"
117
+ "description": "Package an oclif CLI into installable artifacts."
118
118
  },
119
119
  "upload": {
120
- "description": "upload installable CLI artifacts to AWS S3"
120
+ "description": "Upload installable CLI artifacts to AWS S3."
121
121
  }
122
122
  }
123
123
  },
@@ -134,6 +134,7 @@
134
134
  "prepare": "husky",
135
135
  "test:integration:cli": "mocha test/integration/cli.test.ts --timeout 600000",
136
136
  "test:integration:deb": "mocha test/integration/deb.test.ts --timeout 900000",
137
+ "test:integration:init": "mocha test/integration/init.test.ts --timeout 600000",
137
138
  "test:integration:macos": "mocha test/integration/macos.test.ts --timeout 900000",
138
139
  "test:integration:publish": "mocha test/integration/publish.test.ts --timeout 900000",
139
140
  "test:integration:sf": "mocha test/integration/sf.test.ts --timeout 600000",
@@ -0,0 +1,12 @@
1
+ <% switch (moduleType) {
2
+ case 'ESM' : _%>
3
+ @echo off
4
+
5
+ node --loader ts-node/esm --no-warnings=ExperimentalWarning "%~dp0\dev" %*
6
+ <%_ break;
7
+ case 'CommonJS' : _%>
8
+ @echo off
9
+
10
+ node "%~dp0\dev" %*
11
+ <%_ break;
12
+ } _%>
@@ -0,0 +1,17 @@
1
+ <% switch (moduleType) {
2
+ case 'ESM' : _%>
3
+ #!/usr/bin/env -S node --loader ts-node/esm --no-warnings=ExperimentalWarning
4
+
5
+ import {execute} from '@oclif/core'
6
+ await execute({development: true, dir: import.meta.url})
7
+ <%_ break;
8
+ case 'CommonJS' : _%>
9
+ #!/usr/bin/env node_modules/.bin/ts-node
10
+
11
+ // eslint-disable-next-line node/shebang, unicorn/prefer-top-level-await
12
+ (async () => {
13
+ const oclif = await import('@oclif/core')
14
+ await oclif.execute({development: true, dir: __dirname})
15
+ })()
16
+ <%_ break;
17
+ } _%>
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node "%~dp0\run" %*
@@ -0,0 +1,17 @@
1
+ <% switch (moduleType) {
2
+ case 'ESM' : _%>
3
+ #!/usr/bin/env node
4
+
5
+ import {execute} from '@oclif/core'
6
+ await execute({dir: import.meta.url})
7
+ <%_ break;
8
+ case 'CommonJS' : _%>
9
+ #!/usr/bin/env node
10
+
11
+ // eslint-disable-next-line unicorn/prefer-top-level-await
12
+ (async () => {
13
+ const oclif = await import('@oclif/core')
14
+ await oclif.execute({development: false, dir: __dirname})
15
+ })()
16
+ <%_ break;
17
+ } _%>