navy 7.0.0-alpha.2 → 7.0.0-alpha.3

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.
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+
3
+ var _chai = require("chai");
4
+ var _commander = require("commander");
5
+ /* eslint-env mocha */
6
+
7
+ /**
8
+ * These tests check that the commander configuration the navy CLI relies on
9
+ * (positional options + per-subcommand option declarations) rejects options
10
+ * that a subcommand does not declare, using the exact error format commander
11
+ * emits by default.
12
+ *
13
+ * We build a minimal program with the same shape as packages/navy/src/cli/program.js
14
+ * rather than loading that module, because importing program.js has side effects
15
+ * (config reading, action wiring) that are not relevant to argument parsing.
16
+ */
17
+ describe('cli/program unknown option handling', function () {
18
+ function buildProgram() {
19
+ const program = new _commander.Command();
20
+ program.exitOverride();
21
+ program.configureOutput({
22
+ writeErr: () => {},
23
+ writeOut: () => {}
24
+ });
25
+ program.enablePositionalOptions();
26
+ program.option('-e, --navy [env]', 'set the navy name to be used', 'dev');
27
+ program.command('status').option('--json', 'output JSON instead of a table').action(() => {});
28
+ program.command('start [services...]').option('-e, --navy [env]', 'set the navy name to be used', 'dev').action(() => {});
29
+ return program;
30
+ }
31
+ describe('a subcommand that does not declare the option', function () {
32
+ it('should throw a commander error for an unknown short option', function () {
33
+ const program = buildProgram();
34
+ (0, _chai.expect)(() => program.parse(['node', 'navy', 'status', '-e', 'foo'])).to.throw(/unknown option '-e'/);
35
+ });
36
+ it('should produce a non-zero exit code on the thrown error', function () {
37
+ const program = buildProgram();
38
+ try {
39
+ program.parse(['node', 'navy', 'status', '-e', 'foo']);
40
+ _chai.expect.fail('expected parse to throw');
41
+ } catch (ex) {
42
+ (0, _chai.expect)(ex.exitCode).to.be.greaterThan(0);
43
+ }
44
+ });
45
+ it('should emit the canonical commander unknown-option message', function () {
46
+ const program = buildProgram();
47
+ try {
48
+ program.parse(['node', 'navy', 'status', '-e', 'foo']);
49
+ _chai.expect.fail('expected parse to throw');
50
+ } catch (ex) {
51
+ (0, _chai.expect)(ex.message).to.equal("error: unknown option '-e'");
52
+ }
53
+ });
54
+ it('should still accept declared options on the same subcommand', function () {
55
+ const program = buildProgram();
56
+ (0, _chai.expect)(() => program.parse(['node', 'navy', 'status', '--json'])).to.not.throw();
57
+ });
58
+ });
59
+ describe('a subcommand that declares the option', function () {
60
+ it('should accept the option after the subcommand name', function () {
61
+ const program = buildProgram();
62
+ (0, _chai.expect)(() => program.parse(['node', 'navy', 'start', '-e', 'foo'])).to.not.throw();
63
+ });
64
+ it('should accept the option before the subcommand name (parent inherits)', function () {
65
+ const program = buildProgram();
66
+ (0, _chai.expect)(() => program.parse(['node', 'navy', '-e', 'foo', 'start'])).to.not.throw();
67
+ });
68
+ });
69
+ });
@@ -28,6 +28,7 @@ describe('cli/program', function () {
28
28
  helpHandlersByCommand = {};
29
29
  fakeProgram = {
30
30
  option: sandbox.stub().returnsThis(),
31
+ enablePositionalOptions: sandbox.stub().returnsThis(),
31
32
  command(spec) {
32
33
  const name = spec.split(' ')[0];
33
34
  const cmd = {
@@ -155,6 +156,10 @@ describe('cli/program', function () {
155
156
  loadModule();
156
157
  (0, _chai.expect)(fakeProgram.option.calledWith('-e, --navy [env]', _sinon.default.match.string, 'dev')).to.equal(true);
157
158
  });
159
+ it('should enable positional options so unknown subcommand options are rejected', function () {
160
+ loadModule();
161
+ (0, _chai.expect)(fakeProgram.enablePositionalOptions.calledOnce).to.equal(true);
162
+ });
158
163
  });
159
164
  describe('lazyRequire wrapped action', function () {
160
165
  it('should pass through arguments to the underlying module', async function () {
@@ -454,6 +459,7 @@ describe('cli/program', function () {
454
459
  capturedActions = {};
455
460
  return {
456
461
  option: sandbox.stub().returnsThis(),
462
+ enablePositionalOptions: sandbox.stub().returnsThis(),
457
463
  command(spec) {
458
464
  const name = spec.split(' ')[0];
459
465
  const cmd = {
@@ -135,6 +135,11 @@ function lazyRequire(path) {
135
135
  };
136
136
  }
137
137
  const defaultNavy = process.env.NAVY_NAME || (0, _config.getConfig)().defaultNavy;
138
+
139
+ // Strictly separate parent and subcommand options so an option that isn't
140
+ // declared on a subcommand (e.g. `navy status -e foo`) is reported as
141
+ // unknown rather than silently absorbed by the parent program.
142
+ _commander.program.enablePositionalOptions();
138
143
  _commander.program.option('-e, --navy [env]', `set the navy name to be used [${defaultNavy}]`, defaultNavy);
139
144
  const importCommand = _commander.program.command('import').option('-e, --navy [env]', `set the navy name to be used [${defaultNavy}]`, defaultNavy).description('Imports docker compose configuration from the current working directory and initialises a new navy').action(lazyRequire('./import'));
140
145
  (0, _configProvider.getImportCommandLineOptions)().forEach(opt => importCommand.option(...opt));
@@ -44,4 +44,28 @@ describe('cli/util/merge-action-options', function () {
44
44
  });
45
45
  (0, _chai.expect)(spy.calledOnce).to.equal(true);
46
46
  });
47
+ it('should let an explicit subcommand option beat the inherited parent default', function () {
48
+ const command = {
49
+ optsWithGlobals: () => ({
50
+ navy: 'parent-default'
51
+ }),
52
+ getOptionValueSource: key => key === 'navy' ? 'cli' : 'default'
53
+ };
54
+ const merged = (0, _mergeActionOptions.mergeActionOptions)({
55
+ navy: 'from-subcommand'
56
+ }, command);
57
+ (0, _chai.expect)(merged.navy).to.equal('from-subcommand');
58
+ });
59
+ it('should keep the parent value when the subcommand value came from its default', function () {
60
+ const command = {
61
+ optsWithGlobals: () => ({
62
+ navy: 'from-parent'
63
+ }),
64
+ getOptionValueSource: () => 'default'
65
+ };
66
+ const merged = (0, _mergeActionOptions.mergeActionOptions)({
67
+ navy: 'subcommand-default'
68
+ }, command);
69
+ (0, _chai.expect)(merged.navy).to.equal('from-parent');
70
+ });
47
71
  });
@@ -6,15 +6,26 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.mergeActionOptions = mergeActionOptions;
7
7
  /**
8
8
  * Merge Commander's per-command opts with inherited globals (e.g. `navy -e dev ps`).
9
- * `optsWithGlobals()` is the combined view; spread it after local opts so globals
10
- * override stale option defaults on the subcommand.
9
+ * `optsWithGlobals()` has "globals overwrite locals" semantics, which makes parent
10
+ * defaults win over child defaults. With positional options enabled, however, a
11
+ * value supplied after the subcommand name (e.g. `navy ps -e dev`) is parsed onto
12
+ * the subcommand, not the parent, so we re-apply any option whose source on the
13
+ * subcommand is non-default to keep the explicit value.
11
14
  */
12
15
  function mergeActionOptions(parsedOpts, command) {
13
16
  if (!command || typeof command.optsWithGlobals !== 'function') {
14
17
  return parsedOpts;
15
18
  }
16
- return {
17
- ...parsedOpts,
19
+ const merged = {
18
20
  ...command.optsWithGlobals()
19
21
  };
22
+ if (typeof command.getOptionValueSource === 'function') {
23
+ for (const key of Object.keys(parsedOpts)) {
24
+ const source = command.getOptionValueSource(key);
25
+ if (source && source !== 'default') {
26
+ merged[key] = parsedOpts[key];
27
+ }
28
+ }
29
+ }
30
+ return merged;
20
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "navy",
3
- "version": "7.0.0-alpha.2",
3
+ "version": "7.0.0-alpha.3",
4
4
  "description": "Quick and powerful development environments using Docker and Docker Compose",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",