Skip to content

Commit 83381c5

Browse files
committed
feat(key-shortcuts): declarative table with a generic engine
ns start's key handling becomes a declarative table over a caller-supplied context, with state on the context and capabilities on the injector; the key-command surface leaves the injector facade. Failures from the spawned run children surface in the parent, and NS_NO_OPEN keeps the CLI from launching a browser where nobody is watching.
1 parent 70ff020 commit 83381c5

19 files changed

Lines changed: 1395 additions & 753 deletions

File tree

lib/bootstrap.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,22 @@ registerBuiltInCommand<typeof import("./commands/run").visionRunCommand>(
243243
"run|visionos",
244244
() => require("./commands/run").visionRunCommand,
245245
);
246+
registerBuiltInCommand<typeof import("./commands/open").iosOpenCommand>(
247+
"open|ios",
248+
() => require("./commands/open").iosOpenCommand,
249+
);
250+
registerBuiltInCommand<typeof import("./commands/open").androidOpenCommand>(
251+
"open|android",
252+
() => require("./commands/open").androidOpenCommand,
253+
);
254+
registerBuiltInCommand<typeof import("./commands/open").visionOpenCommand>(
255+
"open|visionos",
256+
() => require("./commands/open").visionOpenCommand,
257+
);
258+
registerBuiltInCommand<typeof import("./commands/open").visionOpenCommand>(
259+
"open|vision",
260+
() => require("./commands/open").visionOpenCommand,
261+
);
246262
registerBuiltInCommand<
247263
typeof import("./commands/typings").typingsCommandDefinition
248264
>("typings", () => require("./commands/typings").typingsCommandDefinition);
@@ -705,7 +721,7 @@ injector.require("tempService", "./services/temp-service");
705721

706722
injector.require("sharedEventBus", "./shared-event-bus");
707723

708-
injector.require("keyCommandHelper", "./helpers/key-command-helper");
724+
injector.require("keyShortcutService", "./services/key-shortcuts");
709725

710726
registerBuiltInCommand<
711727
typeof import("./commands/start").startCommandDefinition
@@ -744,4 +760,3 @@ registerBuiltInCommand<
744760
registerBuiltInCommand<
745761
typeof import("./commands/widget").widgetIOSCommandDefinition
746762
>("widget|ios", () => require("./commands/widget").widgetIOSCommandDefinition);
747-
require("./key-commands/bootstrap");

lib/commands/open.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,20 @@ const openCommandOptions = {
195195
/**
196196
* `prepare` reads the options service rather than this command's context, so
197197
* the CLI-wide `--watch` has to be pinned there and not just defaulted here.
198+
* It is restored afterwards because a key shortcut runs this inside a process
199+
* whose own live sync is still watching.
198200
*/
199-
const disableWatch = ($options: IOptions): void => {
201+
const withoutWatch = async <T>(
202+
$options: IOptions,
203+
work: () => Promise<T>,
204+
): Promise<T> => {
205+
const previous = $options.watch;
200206
$options.watch = false;
207+
try {
208+
return await work();
209+
} finally {
210+
$options.watch = previous;
211+
}
201212
};
202213

203214
export const iosOpenCommand = defineCommand({
@@ -212,8 +223,9 @@ export const iosOpenCommand = defineCommand({
212223
};
213224
},
214225
async run(context, services): Promise<void> {
215-
disableWatch(services.$options);
216-
await openXcodeProject(services, "ios", false);
226+
await withoutWatch(services.$options, () =>
227+
openXcodeProject(services, "ios", false),
228+
);
217229
},
218230
});
219231

@@ -229,8 +241,9 @@ export const visionOpenCommand = defineCommand({
229241
};
230242
},
231243
async run(context, services): Promise<void> {
232-
disableWatch(services.$options);
233-
await openVisionOSProject(services, services.$options, false);
244+
await withoutWatch(services.$options, () =>
245+
openVisionOSProject(services, services.$options, false),
246+
);
234247
},
235248
});
236249

@@ -246,7 +259,8 @@ export const androidOpenCommand = defineCommand({
246259
};
247260
},
248261
async run(context, services): Promise<void> {
249-
disableWatch(services.$options);
250-
await openAndroidStudioProject(services, "Android", false);
262+
await withoutWatch(services.$options, () =>
263+
openAndroidStudioProject(services, "Android", false),
264+
);
251265
},
252266
});

lib/commands/run.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { ERROR_NO_VALID_SUBCOMMAND_FORMAT } from "../common/constants";
22
import { IErrors, IHostInfo } from "../common/declarations";
3-
import {
4-
IKeyCommandHelper,
5-
IKeyCommandPlatform,
6-
} from "../common/definitions/key-commands";
73
import {
84
booleanOption,
95
CommandContext,
@@ -21,6 +17,11 @@ import {
2117
import { IOptions, IPlatformValidationService } from "../declarations";
2218
import { IMigrateController } from "../definitions/migrate";
2319
import { IProjectData, IProjectDataService } from "../definitions/project";
20+
import {
21+
DevicePlatformName,
22+
IKeyShortcutService,
23+
keyShortcuts,
24+
} from "../services/key-shortcuts";
2425

2526
const runCommandOptions = {
2627
force: booleanOption(),
@@ -43,7 +44,7 @@ export interface IRunCommandServices {
4344
$devicePlatformsConstants: Mobile.IDevicePlatformsConstants;
4445
$errors: IErrors;
4546
$hostInfo: IHostInfo;
46-
$keyCommandHelper: IKeyCommandHelper;
47+
$keyShortcutService: IKeyShortcutService;
4748
$liveSyncCommandHelper: ILiveSyncCommandHelper;
4849
$migrateController: IMigrateController;
4950
$options: IOptions;
@@ -60,7 +61,7 @@ export function setupRunCommand(): IRunCommandServices {
6061
),
6162
$errors: inject<IErrors>("errors"),
6263
$hostInfo: inject<IHostInfo>("hostInfo"),
63-
$keyCommandHelper: inject<IKeyCommandHelper>("keyCommandHelper"),
64+
$keyShortcutService: inject<IKeyShortcutService>("keyShortcutService"),
6465
$liveSyncCommandHelper: inject<ILiveSyncCommandHelper>(
6566
"liveSyncCommandHelper",
6667
),
@@ -126,10 +127,13 @@ export async function runRunCommand(
126127
);
127128

128129
if (process.env.NS_IS_INTERACTIVE) {
129-
services.$keyCommandHelper.attachKeyCommands(
130-
<IKeyCommandPlatform>services.platform,
131-
"run",
132-
);
130+
services.$keyShortcutService.attach({
131+
context: {
132+
platform: <DevicePlatformName>services.platform,
133+
processType: "run",
134+
},
135+
shortcuts: keyShortcuts(),
136+
});
133137
}
134138
}
135139

lib/common/contracts/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,5 @@ export type {
1414
DeferredCommandRejection,
1515
DeferredCommandResult,
1616
} from "./command-registry";
17-
export { KeyCommandRegistry } from "./key-command-registry";
1817
export { ModuleRegistry } from "./module-registry";
1918
export { PublicApiBuilder } from "./public-api-builder";

lib/common/contracts/key-command-registry.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

lib/common/definitions/key-commands.ts

Lines changed: 0 additions & 62 deletions
This file was deleted.

lib/common/definitions/yok.d.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { IDictionary } from "../declarations";
22
import { Injector } from "../di/injector";
33
import { Provider } from "../di/providers";
44
import { CommandRegistry } from "../contracts/command-registry";
5-
import { KeyCommandRegistry } from "../contracts/key-command-registry";
65
import { ModuleRegistry } from "../contracts/module-registry";
76
import { PublicApiBuilder } from "../contracts/public-api-builder";
87

@@ -13,12 +12,7 @@ import { PublicApiBuilder } from "../contracts/public-api-builder";
1312
* this; the interface survives until the hook/extension deprecation completes.
1413
*/
1514
interface IInjector
16-
extends
17-
Injector,
18-
CommandRegistry,
19-
KeyCommandRegistry,
20-
ModuleRegistry,
21-
PublicApiBuilder {
15+
extends Injector, CommandRegistry, ModuleRegistry, PublicApiBuilder {
2216
/**
2317
* Resolves an implementation by constructor function.
2418
* The injector will create new instances for every call.

lib/common/opener.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,26 @@ import * as xopen from "open";
22
import { IOpener } from "../declarations";
33
import { injector } from "./yok";
44

5+
/**
6+
* Launching a browser or an external app is unwanted wherever nobody is
7+
* watching a desktop: CI, test runs, and agents driving the CLI. Opting out
8+
* has to live here because this is the only place the CLI opens anything.
9+
*/
10+
export function isOpeningExternallyDisabled(): boolean {
11+
const flag = (process.env.NS_NO_OPEN || "").toLowerCase();
12+
if (flag) {
13+
return !["0", "false", "off", "no"].includes(flag);
14+
}
15+
16+
return !!(process.env.CI || process.env.JENKINS_HOME);
17+
}
18+
519
export class Opener implements IOpener {
620
public open(target: string, appname?: string): any {
21+
if (isOpeningExternallyDisabled()) {
22+
return undefined;
23+
}
24+
725
return xopen(target, {
826
app: {
927
name: appname,

lib/common/services/help-service.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "../declarations";
1111
import { IInjector } from "../definitions/yok";
1212
import { injector } from "../yok";
13+
import { isOpeningExternallyDisabled } from "../opener";
1314
import { IExtensibilityService } from "../definitions/extensibility";
1415
import { IOpener } from "../../declarations";
1516
import * as _ from "lodash";
@@ -83,6 +84,12 @@ export class HelpService implements IHelpService {
8384
public async openHelpForCommandInBrowser(
8485
commandData: ICommandData,
8586
): Promise<void> {
87+
if (isOpeningExternallyDisabled()) {
88+
// Nothing is watching a desktop, so the terminal is the only place
89+
// this help can land.
90+
return this.showCommandLineHelp(commandData);
91+
}
92+
8693
const { commandName } = commandData;
8794
const htmlPage =
8895
(await this.convertCommandNameToFileName(commandData)) +

0 commit comments

Comments
 (0)