Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ae2f54e
feat(commands): extend defineCommand for the CLI's own commands
edusperoni Sep 14, 2026
e4bf7da
refactor(commands): migrate every command to defineCommand
edusperoni Sep 14, 2026
13db77e
feat(commands): register commands lazily against a checked definition…
edusperoni Sep 14, 2026
70ff020
feat(commands)!: dispatch in process, rename ctx.arguments to ctx.params
edusperoni Sep 14, 2026
83381c5
feat(key-shortcuts): declarative table with a generic engine
edusperoni Sep 14, 2026
6d8cc6e
feat(key-shortcuts): registry contract and command-declared shortcuts
edusperoni Sep 14, 2026
53551c7
fix(bundler): tear the bundler down before starting another one
edusperoni Sep 14, 2026
2b1ee61
feat(key-shortcuts): r/R/B restart ladder, hint reprint, session scoping
edusperoni Sep 14, 2026
217a38b
feat(commands): a class form built on defineCommand
edusperoni Sep 14, 2026
3361798
refactor(commands): one context per invocation and the class-form sweep
edusperoni Sep 14, 2026
693e34d
feat(commands): the in-process dispatcher as a contract
edusperoni Sep 14, 2026
d987e0b
refactor(commands): fail through the context and read options from it
edusperoni Sep 23, 2026
d7de648
feat(commands): definition providers, multi providers and preconditions
edusperoni Sep 23, 2026
b41cadd
fix(commands): close the review gaps in the command API
edusperoni Sep 23, 2026
3549b4c
refactor(commands): drop what never shipped and the leftovers the rev…
edusperoni Sep 23, 2026
ffa6afa
chore: leave declarations.d.ts untouched
edusperoni Sep 23, 2026
145c39f
fix: address the first review round on the command API and shortcuts
edusperoni Sep 23, 2026
61f4997
feat(commands)!: declare positional parameters as `params`, options a…
edusperoni Sep 23, 2026
95ec594
docs(commands): name the reserved word the params spelling avoids
edusperoni Sep 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(commands): migrate every command to defineCommand
Platform validation, dynamic delegation, native-add and widget, test,
create/install/post-install-cli/help, enforced-parameter, device,
self-contained, platform, plugin and hooks, open|*, and the rest.
Class-based surfaces the migration left without callers are deprecated
rather than removed, since extensions may import them.
  • Loading branch information
edusperoni committed Sep 14, 2026
commit e4bf7dad047c414e5e591eb54836ab9c0d713d78
2 changes: 0 additions & 2 deletions lib/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,6 @@ injector.require(
"vitestExecutionService",
"./services/vitest-execution-service",
);
injector.requireCommand("dev-test|android", "./commands/test");
injector.requireCommand("dev-test|ios", "./commands/test");
injector.requireCommand("test|android", "./commands/test");
injector.requireCommand("test|ios", "./commands/test");
injector.requireCommand("test|vision", "./commands/test");
Expand Down
143 changes: 86 additions & 57 deletions lib/commands/add-platform.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,102 @@
import { ValidatePlatformCommandBase } from "./command-base";
import { IProjectData } from "../definitions/project";
import {
IOptions,
IPlatformCommandHelper,
IPlatformValidationService,
} from "../declarations";
import { IPlatformsDataService } from "../definitions/platform";
import { ICommandParameter, ICommand } from "../common/definitions/commands";
canExecuteCommandBase,
injectPlatformCommandServices,
IPlatformCommandServices,
} from "./command-base";
import { IPlatformCommandHelper } from "../declarations";
import { IErrors } from "../common/declarations";
import { injector } from "../common/yok";
import {
CommandContext,
CommandOptionsSchema,
defineCommand,
stringOption,
} from "../common/define-command";
import { inject } from "../common/di";
import { registerCommand } from "../common/services/command-definition-adapter";

const addPlatformCommandOptions = {
frameworkPath: stringOption(),
} satisfies CommandOptionsSchema;

export class AddPlatformCommand
extends ValidatePlatformCommandBase
implements ICommand
{
public allowedParameters: ICommandParameter[] = [];
export type AddPlatformCommandContext = CommandContext<
typeof addPlatformCommandOptions
>;

export interface IAddPlatformCommandServices extends IPlatformCommandServices {
$errors: IErrors;
$platformCommandHelper: IPlatformCommandHelper;
}

export function setupAddPlatformCommand(): IAddPlatformCommandServices {
const services = {
...injectPlatformCommandServices(),
$errors: inject<IErrors>("errors"),
$platformCommandHelper: inject<IPlatformCommandHelper>(
"platformCommandHelper",
),
};
services.$projectData.initializeProjectData();

return services;
}

constructor(
$options: IOptions,
private $platformCommandHelper: IPlatformCommandHelper,
$platformValidationService: IPlatformValidationService,
$projectData: IProjectData,
$platformsDataService: IPlatformsDataService,
private $errors: IErrors
) {
super(
$options,
$platformsDataService,
$platformValidationService,
$projectData
export async function canExecuteAddPlatformCommand(
context: AddPlatformCommandContext,
services: IAddPlatformCommandServices,
): Promise<boolean> {
const args = context.args;
if (!args || args.length === 0) {
services.$errors.failWithHelp(
"No platform specified. Please specify a platform to add.",
);
this.$projectData.initializeProjectData();
}

public async execute(args: string[]): Promise<void> {
await this.$platformCommandHelper.addPlatforms(
args,
this.$projectData,
this.$options.frameworkPath
let canExecute = true;
for (const arg of args) {
services.$platformValidationService.validatePlatform(
arg,
services.$projectData,
);
}

public async canExecute(args: string[]): Promise<boolean> {
if (!args || args.length === 0) {
this.$errors.failWithHelp(
"No platform specified. Please specify a platform to add."
if (
!services.$platformValidationService.isPlatformSupportedForOS(
arg,
services.$projectData,
)
) {
services.$errors.fail(
`Applications for platform ${arg} cannot be built on this OS`,
);
}

let canExecute = true;
for (const arg of args) {
this.$platformValidationService.validatePlatform(arg, this.$projectData);

if (
!this.$platformValidationService.isPlatformSupportedForOS(
arg,
this.$projectData
)
) {
this.$errors.fail(
`Applications for platform ${arg} cannot be built on this OS`
);
}
// The assignment overwrites the previous platform's verdict, so only the
// last one decides. Kept as it was.
canExecute = await canExecuteCommandBase(services, arg);
}

canExecute = await super.canExecuteCommandBase(arg);
}
return canExecute;
}

return canExecute;
}
export async function runAddPlatformCommand(
context: AddPlatformCommandContext,
services: IAddPlatformCommandServices,
): Promise<void> {
await services.$platformCommandHelper.addPlatforms(
context.args,
services.$projectData,
context.options.frameworkPath,
);
}

injector.registerCommand("platform|add", AddPlatformCommand);
export const addPlatformCommandDefinition = defineCommand({
name: "platform|add",
description:
"Configures the current project to target the selected platform.",
options: addPlatformCommandOptions,
arguments: "any",
setup: setupAddPlatformCommand,
canExecute: canExecuteAddPlatformCommand,
run: runAddPlatformCommand,
});

registerCommand(addPlatformCommandDefinition);
102 changes: 59 additions & 43 deletions lib/commands/apple-login.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,65 @@
import { StringCommandParameter } from "../common/command-params";
import { ICommand, ICommandParameter } from "../common/definitions/commands";
import { IErrors } from "../common/declarations";
import { IInjector } from "../common/definitions/yok";
import { injector } from "../common/yok";
import { CommandContext, defineCommand } from "../common/define-command";
import { inject } from "../common/di";
import { registerCommand } from "../common/services/command-definition-adapter";
import { IApplePortalSessionService } from "../services/apple-portal/definitions";

export class AppleLogin implements ICommand {
public allowedParameters: ICommandParameter[] = [
new StringCommandParameter(this.$injector),
new StringCommandParameter(this.$injector),
];

constructor(
private $applePortalSessionService: IApplePortalSessionService,
private $errors: IErrors,
private $injector: IInjector,
private $logger: ILogger,
private $prompter: IPrompter
) {}

public async execute(args: string[]): Promise<void> {
let username = args[0];
if (!username) {
username = await this.$prompter.getString("Apple ID", {
allowEmpty: false,
});
}

let password = args[1];
if (!password) {
password = await this.$prompter.getPassword("Apple ID password");
}

const user = await this.$applePortalSessionService.createUserSession({
username,
password,
export type AppleLoginCommandContext = CommandContext;

export interface IAppleLoginCommandServices {
$applePortalSessionService: IApplePortalSessionService;
$errors: IErrors;
$logger: ILogger;
$prompter: IPrompter;
}

export function setupAppleLoginCommand(): IAppleLoginCommandServices {
return {
$applePortalSessionService: inject<IApplePortalSessionService>(
"applePortalSessionService",
),
$errors: inject<IErrors>("errors"),
$logger: inject<ILogger>("logger"),
$prompter: inject<IPrompter>("prompter"),
};
}

export async function runAppleLoginCommand(
context: AppleLoginCommandContext,
services: IAppleLoginCommandServices,
): Promise<void> {
let username = context.args[0];
if (!username) {
username = await services.$prompter.getString("Apple ID", {
allowEmpty: false,
});
if (!user.areCredentialsValid) {
this.$errors.fail(
`Invalid username and password combination. Used '${username}' as the username.`
);
}

const output = Buffer.from(user.userSessionCookie).toString("base64");
this.$logger.info(output);
}

let password = context.args[1];
if (!password) {
password = await services.$prompter.getPassword("Apple ID password");
}

const user = await services.$applePortalSessionService.createUserSession({
username,
password,
});
if (!user.areCredentialsValid) {
services.$errors.fail(
`Invalid username and password combination. Used '${username}' as the username.`,
);
}

const output = Buffer.from(user.userSessionCookie).toString("base64");
services.$logger.info(output);
}
injector.registerCommand("apple-login", AppleLogin);

export const appleLoginCommandDefinition = defineCommand({
name: "apple-login",
description: "Logs in to an Apple account and prints the session cookie.",
arguments: [{ name: "appleId" }, { name: "password" }],
setup: setupAppleLoginCommand,
run: runAppleLoginCommand,
});

registerCommand(appleLoginCommandDefinition);
Loading