navy 6.0.0 → 7.0.0-alpha.2

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 (149) hide show
  1. package/lib/__tests__/config-provider.js +75 -0
  2. package/lib/__tests__/config.js +130 -0
  3. package/lib/__tests__/driver-logging.js +148 -0
  4. package/lib/__tests__/driver.js +19 -0
  5. package/lib/__tests__/errors.js +49 -0
  6. package/lib/__tests__/http-proxy.js +214 -0
  7. package/lib/__tests__/index.js +25 -0
  8. package/lib/__tests__/service.js +16 -0
  9. package/lib/cli/__tests__/develop.js +239 -0
  10. package/lib/cli/__tests__/external-ip.js +68 -0
  11. package/lib/cli/__tests__/health.js +257 -0
  12. package/lib/cli/__tests__/https.js +210 -0
  13. package/lib/cli/__tests__/import.js +110 -0
  14. package/lib/cli/__tests__/index.js +118 -0
  15. package/lib/cli/__tests__/lan-ip.js +90 -0
  16. package/lib/cli/__tests__/launch.js +179 -0
  17. package/lib/cli/__tests__/live.js +155 -0
  18. package/lib/cli/__tests__/local-ip.js +72 -0
  19. package/lib/cli/__tests__/logs.js +52 -0
  20. package/lib/cli/__tests__/open.js +65 -0
  21. package/lib/cli/__tests__/program.js +472 -0
  22. package/lib/cli/__tests__/ps.js +345 -0
  23. package/lib/cli/__tests__/refresh-config.js +95 -0
  24. package/lib/cli/__tests__/run.js +54 -0
  25. package/lib/cli/__tests__/status.js +204 -0
  26. package/lib/cli/__tests__/updates.js +243 -0
  27. package/lib/cli/__tests__/wait-for-healthy.js +134 -0
  28. package/lib/cli/config/__tests__/index.js +275 -0
  29. package/lib/cli/config/__tests__/wrapper.js +53 -0
  30. package/lib/cli/config/index.js +19 -37
  31. package/lib/cli/config/wrapper.js +0 -6
  32. package/lib/cli/develop.js +7 -21
  33. package/lib/cli/doctor/__tests__/clean-compose-files.js +78 -0
  34. package/lib/cli/doctor/__tests__/index.js +67 -0
  35. package/lib/cli/doctor/__tests__/invalid-compose-config.js +103 -0
  36. package/lib/cli/doctor/__tests__/invalid-state.js +83 -0
  37. package/lib/cli/doctor/__tests__/util.js +91 -0
  38. package/lib/cli/doctor/clean-compose-files.js +5 -13
  39. package/lib/cli/doctor/index.js +0 -12
  40. package/lib/cli/doctor/invalid-compose-config.js +0 -4
  41. package/lib/cli/doctor/invalid-state.js +0 -6
  42. package/lib/cli/doctor/util.js +0 -10
  43. package/lib/cli/external-ip.js +0 -4
  44. package/lib/cli/health.js +0 -12
  45. package/lib/cli/https.js +9 -25
  46. package/lib/cli/import.js +0 -12
  47. package/lib/cli/index.js +0 -9
  48. package/lib/cli/lan-ip.js +2 -8
  49. package/lib/cli/launch.js +0 -9
  50. package/lib/cli/live.js +6 -16
  51. package/lib/cli/local-ip.js +2 -7
  52. package/lib/cli/logs.js +0 -3
  53. package/lib/cli/open.js +2 -7
  54. package/lib/cli/program.js +73 -101
  55. package/lib/cli/ps.js +1 -21
  56. package/lib/cli/refresh-config.js +0 -7
  57. package/lib/cli/run.js +0 -3
  58. package/lib/cli/status.js +0 -11
  59. package/lib/cli/updates.js +0 -22
  60. package/lib/cli/util/__tests__/get-or-initialise-navy.js +66 -0
  61. package/lib/cli/util/__tests__/import.js +123 -0
  62. package/lib/cli/util/__tests__/index.js +17 -0
  63. package/lib/cli/util/__tests__/merge-action-options.js +47 -0
  64. package/lib/cli/util/__tests__/reconfigure.js +78 -0
  65. package/lib/cli/util/get-or-initialise-navy.js +0 -7
  66. package/lib/cli/util/import.js +0 -9
  67. package/lib/cli/util/index.js +0 -2
  68. package/lib/cli/util/merge-action-options.js +20 -0
  69. package/lib/cli/util/reconfigure.js +0 -4
  70. package/lib/cli/wait-for-healthy.js +0 -21
  71. package/lib/client/registry/__tests__/get-credentials.js +62 -0
  72. package/lib/client/registry/__tests__/get-endpoint.js +124 -0
  73. package/lib/client/registry/__tests__/get-fat-manifest.js +67 -0
  74. package/lib/client/registry/__tests__/get-token.js +66 -0
  75. package/lib/client/registry/__tests__/helpers.js +26 -0
  76. package/lib/client/registry/get-credentials.js +3 -9
  77. package/lib/client/registry/get-endpoint.js +29 -63
  78. package/lib/client/registry/get-fat-manifest.js +2 -9
  79. package/lib/client/registry/get-token.js +2 -13
  80. package/lib/client/registry/helpers.js +0 -4
  81. package/lib/config-provider.js +0 -12
  82. package/lib/config-providers/filesystem/__tests__/index.js +176 -0
  83. package/lib/config-providers/filesystem/index.js +5 -23
  84. package/lib/config-providers/npm/__tests__/index.js +226 -0
  85. package/lib/config-providers/npm/__tests__/util.js +1 -2
  86. package/lib/config-providers/npm/index.js +12 -35
  87. package/lib/config-providers/npm/util.js +0 -3
  88. package/lib/config.js +4 -19
  89. package/lib/domain/__tests__/container-image.js +81 -0
  90. package/lib/domain/__tests__/oci-api-specification.js +23 -0
  91. package/lib/domain/container-image.js +8 -21
  92. package/lib/domain/oci-api-specification.js +3 -5
  93. package/lib/driver-logging.js +0 -19
  94. package/lib/driver.js +0 -4
  95. package/lib/drivers/docker-compose/__tests__/client.js +249 -0
  96. package/lib/drivers/docker-compose/__tests__/index.js +430 -0
  97. package/lib/drivers/docker-compose/client.js +0 -16
  98. package/lib/drivers/docker-compose/index.js +7 -49
  99. package/lib/errors.js +0 -10
  100. package/lib/http-proxy.js +28 -23
  101. package/lib/index.js +1 -9
  102. package/lib/middleware/__tests__/add-service-proxy-config.js +258 -0
  103. package/lib/middleware/__tests__/develop.js +120 -0
  104. package/lib/middleware/__tests__/helpers.js +154 -0
  105. package/lib/middleware/__tests__/port-override.js +125 -0
  106. package/lib/middleware/__tests__/set-env-vars.js +94 -0
  107. package/lib/middleware/__tests__/set-image.js +76 -0
  108. package/lib/middleware/__tests__/set-logging-driver.js +94 -0
  109. package/lib/middleware/__tests__/tag-override.js +92 -0
  110. package/lib/middleware/add-service-proxy-config.js +8 -16
  111. package/lib/middleware/develop.js +2 -5
  112. package/lib/middleware/helpers.js +6 -12
  113. package/lib/middleware/port-override.js +5 -8
  114. package/lib/middleware/set-env-vars.js +6 -6
  115. package/lib/middleware/set-image.js +4 -5
  116. package/lib/middleware/set-logging-driver.js +6 -6
  117. package/lib/middleware/tag-override.js +4 -6
  118. package/lib/navy/__tests__/default-middleware.js +40 -0
  119. package/lib/navy/__tests__/index.js +1612 -0
  120. package/lib/navy/__tests__/middleware.js +71 -0
  121. package/lib/navy/__tests__/plugin-interface.js +121 -0
  122. package/lib/navy/__tests__/state.js +103 -0
  123. package/lib/navy/__tests__/util.js +24 -0
  124. package/lib/navy/default-middleware.js +0 -10
  125. package/lib/navy/index.js +83 -138
  126. package/lib/navy/middleware.js +0 -6
  127. package/lib/navy/plugin-interface.js +2 -10
  128. package/lib/navy/state.js +12 -24
  129. package/lib/navy/util.js +0 -1
  130. package/lib/service.js +2 -3
  131. package/lib/util/__tests__/exec-async.js +83 -0
  132. package/lib/util/__tests__/external-ip.js +97 -2
  133. package/lib/util/__tests__/get-lan-ip.js +46 -0
  134. package/lib/util/__tests__/has-update.js +136 -0
  135. package/lib/util/__tests__/https.js +301 -0
  136. package/lib/util/__tests__/navyrc.js +45 -0
  137. package/lib/util/__tests__/service-host.js +63 -5
  138. package/lib/util/__tests__/table.js +44 -0
  139. package/lib/util/docker-client.js +2 -10
  140. package/lib/util/exec-async.js +0 -4
  141. package/lib/util/external-ip.js +8 -12
  142. package/lib/util/fs.js +1 -6
  143. package/lib/util/get-lan-ip.js +0 -5
  144. package/lib/util/has-update.js +2 -14
  145. package/lib/util/https.js +11 -55
  146. package/lib/util/navyrc.js +0 -5
  147. package/lib/util/service-host.js +0 -17
  148. package/lib/util/table.js +0 -6
  149. package/package.json +14 -13
@@ -0,0 +1,472 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _chai = require("chai");
5
+ var _sinon = _interopRequireDefault(require("sinon"));
6
+ var _proxyquire = _interopRequireDefault(require("proxyquire"));
7
+ var _errors = require("../../errors");
8
+ /* eslint-env mocha */
9
+
10
+ describe('cli/program', function () {
11
+ let sandbox;
12
+ let fakeProgram;
13
+ let actionsByCommand;
14
+ let getConfigStub;
15
+ let startDriverLoggingStub;
16
+ let stopDriverLoggingStub;
17
+ let getImportCommandLineOptionsStub;
18
+ let getNavyStub;
19
+ let navyStub;
20
+ let consoleLogStub;
21
+ let consoleErrorStub;
22
+ let processExitStub;
23
+ let processOnStub;
24
+ let originalEnvName;
25
+ let helpHandlersByCommand;
26
+ function loadModule() {
27
+ actionsByCommand = {};
28
+ helpHandlersByCommand = {};
29
+ fakeProgram = {
30
+ option: sandbox.stub().returnsThis(),
31
+ command(spec) {
32
+ const name = spec.split(' ')[0];
33
+ const cmd = {
34
+ option: sandbox.stub().returnsThis(),
35
+ description: sandbox.stub().returnsThis(),
36
+ action(handler) {
37
+ actionsByCommand[name] = handler;
38
+ return cmd;
39
+ },
40
+ on(event, handler) {
41
+ if (event === '--help') {
42
+ helpHandlersByCommand[name] = handler;
43
+ }
44
+ return cmd;
45
+ }
46
+ };
47
+ return cmd;
48
+ }
49
+ };
50
+ return _proxyquire.default.noCallThru()('../program', {
51
+ commander: {
52
+ program: fakeProgram
53
+ },
54
+ '../errors': {
55
+ NavyError: _errors.NavyError
56
+ },
57
+ '../config': {
58
+ getConfig: getConfigStub
59
+ },
60
+ '../driver-logging': {
61
+ startDriverLogging: startDriverLoggingStub,
62
+ stopDriverLogging: stopDriverLoggingStub
63
+ },
64
+ '../config-provider': {
65
+ getImportCommandLineOptions: getImportCommandLineOptionsStub
66
+ },
67
+ '../navy': {
68
+ getNavy: getNavyStub
69
+ },
70
+ './import': () => Promise.resolve('imported'),
71
+ './launch': () => Promise.resolve('launched'),
72
+ './ps': () => Promise.resolve(undefined),
73
+ './updates': () => Promise.resolve(undefined),
74
+ './logs': () => Promise.resolve(undefined),
75
+ './health': () => Promise.resolve(undefined),
76
+ './wait-for-healthy': () => Promise.resolve(undefined),
77
+ './https': () => Promise.resolve(undefined),
78
+ './open': () => Promise.resolve(undefined),
79
+ './develop': () => Promise.resolve(undefined),
80
+ './live': () => Promise.resolve(undefined),
81
+ './run': () => Promise.resolve(undefined),
82
+ './refresh-config': () => Promise.resolve(undefined),
83
+ './status': () => Promise.resolve(undefined),
84
+ './doctor': () => Promise.resolve(undefined),
85
+ './config/wrapper': () => Promise.resolve(undefined),
86
+ './external-ip': () => Promise.resolve(undefined),
87
+ './lan-ip': () => Promise.resolve(undefined),
88
+ './local-ip': () => Promise.resolve(undefined)
89
+ });
90
+ }
91
+ beforeEach(function () {
92
+ sandbox = _sinon.default.createSandbox();
93
+ getConfigStub = sandbox.stub().returns({
94
+ defaultNavy: 'dev'
95
+ });
96
+ startDriverLoggingStub = sandbox.stub();
97
+ stopDriverLoggingStub = sandbox.stub();
98
+ getImportCommandLineOptionsStub = sandbox.stub().returns([['-c, --config-provider [provider]', 'Config provider to use']]);
99
+ navyStub = {
100
+ ensurePluginsLoaded: sandbox.stub().resolves(),
101
+ emitAsync: sandbox.stub().resolves(),
102
+ destroy: sandbox.stub().resolves(),
103
+ kill: sandbox.stub().resolves(),
104
+ start: sandbox.stub().resolves(),
105
+ stop: sandbox.stub().resolves(),
106
+ url: sandbox.stub().resolves('http://web'),
107
+ getAvailableServiceNames: sandbox.stub().resolves(['web', 'api'])
108
+ };
109
+ getNavyStub = sandbox.stub().returns(navyStub);
110
+ consoleLogStub = sandbox.stub(console, 'log');
111
+ consoleErrorStub = sandbox.stub(console, 'error');
112
+ processExitStub = sandbox.stub(process, 'exit');
113
+ processOnStub = sandbox.stub(process, 'on');
114
+ originalEnvName = process.env.NAVY_NAME;
115
+ });
116
+ afterEach(function () {
117
+ if (originalEnvName === undefined) {
118
+ delete process.env.NAVY_NAME;
119
+ } else {
120
+ process.env.NAVY_NAME = originalEnvName;
121
+ }
122
+ sandbox.restore();
123
+ });
124
+ describe('module side effects', function () {
125
+ it('should register all known commands on the program', function () {
126
+ loadModule();
127
+ (0, _chai.expect)(actionsByCommand).to.have.keys('import', 'launch', 'destroy', 'delete', 'ps', 'start', 'stop', 'restart', 'kill', 'rm', 'update', 'updates', 'logs', 'health', 'wait-for-healthy', 'use-tag', 'reset-tag', 'https', 'use-port', 'reset-port', 'url', 'open', 'port', 'available-services', 'develop', 'live', 'run', 'refresh-config', 'status', 'doctor', 'config', 'external-ip', 'use-lan-ip', 'use-local-ip');
128
+ });
129
+ it('should derive the default navy from NAVY_NAME env when set', function () {
130
+ process.env.NAVY_NAME = 'foo-env';
131
+ loadModule();
132
+ (0, _chai.expect)(actionsByCommand.import).to.be.a('function');
133
+ });
134
+ it('should fall back to getConfig().defaultNavy when env is not set', function () {
135
+ delete process.env.NAVY_NAME;
136
+ loadModule();
137
+ (0, _chai.expect)(getConfigStub.called).to.equal(true);
138
+ });
139
+ it('should add import command-line options from getImportCommandLineOptions', function () {
140
+ loadModule();
141
+ (0, _chai.expect)(getImportCommandLineOptionsStub.calledOnce).to.equal(true);
142
+ });
143
+ it('should export the program as default', function () {
144
+ const mod = loadModule();
145
+ (0, _chai.expect)(mod).to.equal(fakeProgram);
146
+ });
147
+ it('should print examples when each command --help handler is invoked', function () {
148
+ loadModule();
149
+ Object.keys(helpHandlersByCommand).forEach(name => {
150
+ helpHandlersByCommand[name]();
151
+ });
152
+ (0, _chai.expect)(consoleLogStub.called).to.equal(true);
153
+ });
154
+ it('should register a global -e, --navy option on the root program', function () {
155
+ loadModule();
156
+ (0, _chai.expect)(fakeProgram.option.calledWith('-e, --navy [env]', _sinon.default.match.string, 'dev')).to.equal(true);
157
+ });
158
+ });
159
+ describe('lazyRequire wrapped action', function () {
160
+ it('should pass through arguments to the underlying module', async function () {
161
+ loadModule();
162
+ const result = actionsByCommand.import({
163
+ navy: 'env-1'
164
+ });
165
+ return (0, _chai.expect)(result).to.exist;
166
+ });
167
+ it('should call stopDriverLogging and prettyPrint when the action throws a NavyError', async function () {
168
+ const err = new _errors.NavyError('boom');
169
+ const prettyPrintStub = sandbox.stub(err, 'prettyPrint');
170
+ _proxyquire.default.noCallThru()('../program', {
171
+ commander: {
172
+ program: createCapturingProgram()
173
+ },
174
+ '../errors': {
175
+ NavyError: _errors.NavyError
176
+ },
177
+ '../config': {
178
+ getConfig: getConfigStub
179
+ },
180
+ '../driver-logging': {
181
+ startDriverLogging: startDriverLoggingStub,
182
+ stopDriverLogging: stopDriverLoggingStub
183
+ },
184
+ '../config-provider': {
185
+ getImportCommandLineOptions: getImportCommandLineOptionsStub
186
+ },
187
+ '../navy': {
188
+ getNavy: getNavyStub
189
+ },
190
+ './import': () => Promise.reject(err),
191
+ './launch': () => Promise.resolve(),
192
+ './ps': () => Promise.resolve(),
193
+ './updates': () => Promise.resolve(),
194
+ './logs': () => Promise.resolve(),
195
+ './health': () => Promise.resolve(),
196
+ './wait-for-healthy': () => Promise.resolve(),
197
+ './https': () => Promise.resolve(),
198
+ './open': () => Promise.resolve(),
199
+ './develop': () => Promise.resolve(),
200
+ './live': () => Promise.resolve(),
201
+ './run': () => Promise.resolve(),
202
+ './refresh-config': () => Promise.resolve(),
203
+ './status': () => Promise.resolve(),
204
+ './doctor': () => Promise.resolve(),
205
+ './config/wrapper': () => Promise.resolve(),
206
+ './external-ip': () => Promise.resolve(),
207
+ './lan-ip': () => Promise.resolve(),
208
+ './local-ip': () => Promise.resolve()
209
+ });
210
+ capturedActions.import({
211
+ navy: 'env-1'
212
+ });
213
+ await new Promise(resolve => setImmediate(resolve));
214
+ (0, _chai.expect)(prettyPrintStub.calledOnce).to.equal(true);
215
+ (0, _chai.expect)(processExitStub.calledWith(1)).to.equal(true);
216
+ (0, _chai.expect)(stopDriverLoggingStub.calledWith({
217
+ success: false
218
+ })).to.equal(true);
219
+ });
220
+ it('should pretty-print invariant violations with troubleshooting hint', async function () {
221
+ const err = new Error('CODE: something went wrong');
222
+ err.name = 'Invariant Violation';
223
+ _proxyquire.default.noCallThru()('../program', {
224
+ commander: {
225
+ program: createCapturingProgram()
226
+ },
227
+ '../errors': {
228
+ NavyError: _errors.NavyError
229
+ },
230
+ '../config': {
231
+ getConfig: getConfigStub
232
+ },
233
+ '../driver-logging': {
234
+ startDriverLogging: startDriverLoggingStub,
235
+ stopDriverLogging: stopDriverLoggingStub
236
+ },
237
+ '../config-provider': {
238
+ getImportCommandLineOptions: getImportCommandLineOptionsStub
239
+ },
240
+ '../navy': {
241
+ getNavy: getNavyStub
242
+ },
243
+ './import': () => Promise.reject(err),
244
+ './launch': () => Promise.resolve(),
245
+ './ps': () => Promise.resolve(),
246
+ './updates': () => Promise.resolve(),
247
+ './logs': () => Promise.resolve(),
248
+ './health': () => Promise.resolve(),
249
+ './wait-for-healthy': () => Promise.resolve(),
250
+ './https': () => Promise.resolve(),
251
+ './open': () => Promise.resolve(),
252
+ './develop': () => Promise.resolve(),
253
+ './live': () => Promise.resolve(),
254
+ './run': () => Promise.resolve(),
255
+ './refresh-config': () => Promise.resolve(),
256
+ './status': () => Promise.resolve(),
257
+ './doctor': () => Promise.resolve(),
258
+ './config/wrapper': () => Promise.resolve(),
259
+ './external-ip': () => Promise.resolve(),
260
+ './lan-ip': () => Promise.resolve(),
261
+ './local-ip': () => Promise.resolve()
262
+ });
263
+ capturedActions.import({
264
+ navy: 'env-1'
265
+ });
266
+ await new Promise(resolve => setImmediate(resolve));
267
+ const printed = consoleLogStub.getCalls().map(c => c.args[0] || '').join('\n');
268
+ (0, _chai.expect)(printed).to.contain('Invariant Violation');
269
+ (0, _chai.expect)(printed).to.contain('navy doctor');
270
+ (0, _chai.expect)(processExitStub.calledWith(1)).to.equal(true);
271
+ });
272
+ it('should print stack for non-NavyError, non-Invariant errors', async function () {
273
+ const err = new Error('something broke');
274
+ _proxyquire.default.noCallThru()('../program', {
275
+ commander: {
276
+ program: createCapturingProgram()
277
+ },
278
+ '../errors': {
279
+ NavyError: _errors.NavyError
280
+ },
281
+ '../config': {
282
+ getConfig: getConfigStub
283
+ },
284
+ '../driver-logging': {
285
+ startDriverLogging: startDriverLoggingStub,
286
+ stopDriverLogging: stopDriverLoggingStub
287
+ },
288
+ '../config-provider': {
289
+ getImportCommandLineOptions: getImportCommandLineOptionsStub
290
+ },
291
+ '../navy': {
292
+ getNavy: getNavyStub
293
+ },
294
+ './import': () => Promise.reject(err),
295
+ './launch': () => Promise.resolve(),
296
+ './ps': () => Promise.resolve(),
297
+ './updates': () => Promise.resolve(),
298
+ './logs': () => Promise.resolve(),
299
+ './health': () => Promise.resolve(),
300
+ './wait-for-healthy': () => Promise.resolve(),
301
+ './https': () => Promise.resolve(),
302
+ './open': () => Promise.resolve(),
303
+ './develop': () => Promise.resolve(),
304
+ './live': () => Promise.resolve(),
305
+ './run': () => Promise.resolve(),
306
+ './refresh-config': () => Promise.resolve(),
307
+ './status': () => Promise.resolve(),
308
+ './doctor': () => Promise.resolve(),
309
+ './config/wrapper': () => Promise.resolve(),
310
+ './external-ip': () => Promise.resolve(),
311
+ './lan-ip': () => Promise.resolve(),
312
+ './local-ip': () => Promise.resolve()
313
+ });
314
+ capturedActions.import({
315
+ navy: 'env-1'
316
+ });
317
+ await new Promise(resolve => setImmediate(resolve));
318
+ (0, _chai.expect)(consoleErrorStub.calledWith(err.stack)).to.equal(true);
319
+ });
320
+
321
+ // NOTE: The internal wrapper in program.js does `if (res.catch)` without
322
+ // null-checking, so passing a non-promise return value would throw. All
323
+ // production action handlers return promises, so this code path is not
324
+ // exercised in practice. We do not test it because asserting that
325
+ // behaviour would lock in a latent bug.
326
+ });
327
+ describe('basicCliWrapper action', function () {
328
+ beforeEach(function () {
329
+ loadModule();
330
+ });
331
+ it('should call the named navy method with the supplied service list', async function () {
332
+ navyStub.start = sandbox.stub().resolves();
333
+ await actionsByCommand.start(['web', 'api'], {
334
+ navy: 'env-1'
335
+ }, {});
336
+ (0, _chai.expect)(navyStub.start.calledOnce).to.equal(true);
337
+ (0, _chai.expect)(navyStub.start.firstCall.args[0]).to.eql(['web', 'api']);
338
+ (0, _chai.expect)(navyStub.ensurePluginsLoaded.calledOnce).to.equal(true);
339
+ });
340
+ it('should merge optsWithGlobals so global navy is used when Commander passes the command', async function () {
341
+ navyStub.start = sandbox.stub().resolves();
342
+ const fakeCommand = {
343
+ optsWithGlobals: () => ({
344
+ navy: 'from-global'
345
+ })
346
+ };
347
+ await actionsByCommand.start(['web'], {
348
+ navy: 'subcommand-default'
349
+ }, fakeCommand);
350
+ (0, _chai.expect)(getNavyStub.firstCall.args[0]).to.equal('from-global');
351
+ });
352
+ it('should pass undefined when called with an empty service list', async function () {
353
+ navyStub.start = sandbox.stub().resolves();
354
+ await actionsByCommand.start([], {
355
+ navy: 'env-1'
356
+ }, {});
357
+ (0, _chai.expect)(navyStub.start.firstCall.args[0]).to.equal(undefined);
358
+ });
359
+ it('should drive logging by default', async function () {
360
+ navyStub.start = sandbox.stub().resolves();
361
+ await actionsByCommand.start(['web'], {
362
+ navy: 'env-1'
363
+ }, {});
364
+ (0, _chai.expect)(startDriverLoggingStub.calledWith('Starting services...')).to.equal(true);
365
+ (0, _chai.expect)(stopDriverLoggingStub.calledOnce).to.equal(true);
366
+ });
367
+ it('should skip driver logging for the url command (driverLogging: false)', async function () {
368
+ navyStub.url = sandbox.stub().resolves('http://web');
369
+ await actionsByCommand.url('web', {
370
+ navy: 'env-1'
371
+ }, {});
372
+ (0, _chai.expect)(startDriverLoggingStub.called).to.equal(false);
373
+ (0, _chai.expect)(stopDriverLoggingStub.called).to.equal(false);
374
+ });
375
+ it('should print arrays joined by newlines', async function () {
376
+ navyStub.getAvailableServiceNames = sandbox.stub().resolves(['web', 'api']);
377
+ await actionsByCommand['available-services']({
378
+ navy: 'env-1'
379
+ }, {});
380
+ (0, _chai.expect)(consoleLogStub.calledWith('web\napi')).to.equal(true);
381
+ });
382
+ it('should print non-null scalar return values', async function () {
383
+ navyStub.url = sandbox.stub().resolves('http://web');
384
+ await actionsByCommand.url('web', {
385
+ navy: 'env-1'
386
+ }, {});
387
+ (0, _chai.expect)(consoleLogStub.calledWith('http://web')).to.equal(true);
388
+ });
389
+ it('should not print when the navy method returns null', async function () {
390
+ navyStub.start = sandbox.stub().resolves(null);
391
+ await actionsByCommand.start(['web'], {
392
+ navy: 'env-1'
393
+ }, {});
394
+ (0, _chai.expect)(consoleLogStub.called).to.equal(false);
395
+ });
396
+ it('should redirect destroy with services to kill via serviceBasedAlias', async function () {
397
+ navyStub.kill = sandbox.stub().resolves();
398
+ await actionsByCommand.destroy('web api', {
399
+ navy: 'env-1'
400
+ }, {});
401
+ (0, _chai.expect)(navyStub.kill.calledOnce).to.equal(true);
402
+ (0, _chai.expect)(navyStub.kill.firstCall.args[0]).to.eql(['web', 'api']);
403
+ const printed = consoleLogStub.getCalls().map(c => c.args[0] || '').join('\n');
404
+ (0, _chai.expect)(printed).to.contain('should not be called with a list');
405
+ });
406
+ it('should emit cli.before.<fn> and cli.after.<fn>', async function () {
407
+ navyStub.start = sandbox.stub().resolves();
408
+ await actionsByCommand.start(['web'], {
409
+ navy: 'env-1'
410
+ }, {});
411
+ const events = navyStub.emitAsync.getCalls().map(c => c.args[0]);
412
+ (0, _chai.expect)(events).to.include('cli.before.start');
413
+ (0, _chai.expect)(events).to.include('cli.after.start');
414
+ });
415
+ it('should register an unhandledRejection handler', async function () {
416
+ navyStub.start = sandbox.stub().resolves();
417
+ await actionsByCommand.start(['web'], {
418
+ navy: 'env-1'
419
+ }, {});
420
+ (0, _chai.expect)(processOnStub.calledWith('unhandledRejection')).to.equal(true);
421
+ });
422
+ describe('unhandledRejection handler', function () {
423
+ it('should pretty-print NavyError and exit', async function () {
424
+ navyStub.start = sandbox.stub().resolves();
425
+ await actionsByCommand.start(['web'], {
426
+ navy: 'env-1'
427
+ }, {});
428
+ const handlers = processOnStub.getCalls().filter(c => c.args[0] === 'unhandledRejection').map(c => c.args[1]);
429
+ const handler = handlers[handlers.length - 1];
430
+ const err = new _errors.NavyError('boom');
431
+ const prettyPrintStub = sandbox.stub(err, 'prettyPrint');
432
+ handler(err);
433
+ (0, _chai.expect)(prettyPrintStub.calledOnce).to.equal(true);
434
+ (0, _chai.expect)(processExitStub.called).to.equal(true);
435
+ });
436
+ it('should print stack for non-NavyError and exit', async function () {
437
+ navyStub.start = sandbox.stub().resolves();
438
+ await actionsByCommand.start(['web'], {
439
+ navy: 'env-1'
440
+ }, {});
441
+ const handlers = processOnStub.getCalls().filter(c => c.args[0] === 'unhandledRejection').map(c => c.args[1]);
442
+ const handler = handlers[handlers.length - 1];
443
+ const err = new Error('boom');
444
+ handler(err);
445
+ (0, _chai.expect)(consoleErrorStub.calledWith(err.stack)).to.equal(true);
446
+ (0, _chai.expect)(processExitStub.called).to.equal(true);
447
+ });
448
+ });
449
+ });
450
+
451
+ // Helper to capture program actions for the failure-path tests
452
+ let capturedActions;
453
+ function createCapturingProgram() {
454
+ capturedActions = {};
455
+ return {
456
+ option: sandbox.stub().returnsThis(),
457
+ command(spec) {
458
+ const name = spec.split(' ')[0];
459
+ const cmd = {
460
+ option: sandbox.stub().returnsThis(),
461
+ description: sandbox.stub().returnsThis(),
462
+ action(handler) {
463
+ capturedActions[name] = handler;
464
+ return cmd;
465
+ },
466
+ on: sandbox.stub().returnsThis()
467
+ };
468
+ return cmd;
469
+ }
470
+ };
471
+ }
472
+ });