Skip to content

Commit 61f4997

Browse files
committed
feat(commands)!: declare positional parameters as params, options as required
`arguments` cannot be bound as an identifier in strict code, so a definition could never be destructured or written with property shorthand; the field is now `params`, the word the context already uses for the same values, and a stale `arguments` key is rejected with a message naming the fix. `ParamSpec`, `ParamsPolicy` and `CommandParamValues` replace the argument-named types, which stay as deprecated aliases; `platformArgument` is `platformParam`. An option spec takes `required: true`: its absence fails the invocation after the params policy and before `canExecute`, and the handler's value type drops `| undefined`, as a `default` already does. The two do not combine.
1 parent 145c39f commit 61f4997

77 files changed

Lines changed: 383 additions & 240 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎defining-commands.md‎

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default defineCommand({
2929
overwrite: booleanOption({ default: false }),
3030
output: stringOption({ alias: "o" }),
3131
},
32-
arguments: "any",
32+
params: "any",
3333
async run(ctx) {
3434
// ctx.args -> string[] of positional arguments
3535
// ctx.options -> { overwrite: boolean; output: string | undefined }
@@ -57,18 +57,18 @@ Validation happens where you can see it
5757

5858
A definition is checked at the moment `defineCommand` is called, not when the
5959
command eventually runs. A misspelled field, a missing `run`, an option
60-
declared with something other than the five helpers, an `arguments` value that
60+
declared with something other than the five helpers, an `params` value that
6161
is neither `"none"`, `"any"` nor a list of argument specs — each throws
6262
immediately, naming the command and the accepted form. The class form's meta is checked the same way at the
6363
`Command({ ... })` call, which also rejects handlers passed there; only a
6464
missing `run` method waits until the definition is first read:
6565

6666
```
6767
Invalid command definition for 'widget|add': unknown field(s) 'handler'; a
68-
definition accepts name, description, options, arguments, allowUnknownOptions,
68+
definition accepts name, description, options, params, allowUnknownOptions,
6969
canExecute, disableAnalytics, enableHooks, providers, setup, run, shortcuts,
7070
postRun. Accepted form: defineCommand({ name: "widget|add", run(ctx) { ... } })
71-
— with the optional fields description, options, arguments,
71+
— with the optional fields description, options, params,
7272
allowUnknownOptions, providers, setup, canExecute, shortcuts, postRun,
7373
disableAnalytics and enableHooks. Or the class form, class WidgetAdd extends
7474
Command({ name: "widget|add" }) { run() { ... } }, which declares the same
@@ -140,6 +140,10 @@ options: {
140140

141141
- `default` — value used when the flag is absent.
142142
- `alias` — single-dash shorthand, or an array of them (`alias: ["o", "out"]`).
143+
- `required` — the command line must pass the flag. Its absence fails the
144+
invocation with `The option '--output' is required.` after the params policy
145+
and before `canExecute`, and the handler's value type drops `| undefined`,
146+
as it does for a spec with a `default`. The two do not combine.
143147
- `hasSensitiveValue` — defaults to `false`; set it for anything that must not
144148
be recorded. There is no reason not to be explicit about credentials, paths
145149
containing user directories, and tokens.
@@ -242,20 +246,20 @@ when forwarding.
242246
Positional arguments
243247
--------------------
244248

245-
`arguments` declares what the command takes after its name:
249+
`params` declares what the command takes after its name:
246250

247251
- `"none"` (the default) — the command accepts no positional arguments. Passing
248252
any is rejected with `This command doesn't accept parameters.`
249253
- `"any"` — any number of positional arguments is accepted and handed to `run`
250254
as `ctx.args`.
251255
- an array of specs — each argument is declared, named, and validated.
252256

253-
### Declared arguments
257+
### Declared parameters
254258

255259
```ts
256260
defineCommand({
257261
name: "widget|add",
258-
arguments: [
262+
params: [
259263
{
260264
name: "platform",
261265
required: true,
@@ -308,7 +312,7 @@ The practical consequence: `ctx.params.template` is `args[1]` whether or not
308312
`args[1]` looks like a template. An argument that could be several things is a
309313
job for `validate` or for `canExecute`, not for the matcher.
310314

311-
`ctx.params` is always present, even with `arguments: "none"` or `"any"` —
315+
`ctx.params` is always present, even with `params: "none"` or `"any"` —
312316
it is simply `{}` when no specs are declared. An optional non-variadic argument
313317
the command line did not reach is absent from it; a variadic one is always
314318
there, as an array.
@@ -318,7 +322,7 @@ there, as an array.
318322
```ts
319323
defineCommand({
320324
name: "widget|add",
321-
arguments: "any",
325+
params: "any",
322326
async canExecute(ctx) {
323327
return ctx.args.length === 1;
324328
},
@@ -328,9 +332,9 @@ defineCommand({
328332
});
329333
```
330334

331-
The two fields compose. The declared `arguments` policy is enforced first, and
335+
The two fields compose. The declared `params` policy is enforced first, and
332336
`canExecute` is consulted only for command lines that already satisfy it — so a
333-
definition that leaves `arguments` at `"none"` still rejects stray positional
337+
definition that leaves `params` at `"none"` still rejects stray positional
334338
arguments even when it supplies a `canExecute`, and a `canExecute` that only
335339
inspects options cannot accidentally widen what the command accepts.
336340

@@ -350,9 +354,9 @@ The run context
350354

351355
- `ctx.args` — `string[]`, the positional arguments left after the command name
352356
(including any subcommand segments) has been consumed.
353-
- `ctx.params` — the same arguments keyed by the names the `arguments` specs
357+
- `ctx.params` — the same arguments keyed by the names the `params` specs
354358
declare, `{}` when there are none. It is spelled `params` because
355-
`arguments` is a reserved binding name in strict mode, so a destructuring
359+
`params` is a reserved binding name in strict mode, so a destructuring
356360
`const { args, arguments } = ctx` would not even parse.
357361
- `ctx.options` — the current value of each declared option, read at the moment
358362
the command executes.
@@ -379,7 +383,7 @@ the project, a file on disk — the help suggestion only gets in the way. Pass
379383
```ts
380384
defineCommand({
381385
name: "widget|add",
382-
arguments: "any",
386+
params: "any",
383387
options: { output: stringOption() },
384388
async run(ctx) {
385389
if (!ctx.options.output) {
@@ -455,7 +459,7 @@ A handler resolves what it needs itself, at the top of its own body:
455459
```ts
456460
export default defineCommand({
457461
name: "widget|add",
458-
arguments: "any",
462+
params: "any",
459463
providers: [provideProject()],
460464
async run(ctx) {
461465
const widgets = inject(WidgetService);
@@ -590,7 +594,7 @@ passed to it after `run` succeeds:
590594
```ts
591595
export default defineCommand({
592596
name: "create",
593-
arguments: [{ name: "appName", required: true }],
597+
params: [{ name: "appName", required: true }],
594598
async run(ctx) {
595599
const projectDir = await createProject(ctx.params.appName as string);
596600
return { projectDir };
@@ -637,7 +641,7 @@ export class PlatformCleanCommand extends Command({
637641
name: "platform|clean",
638642
description: "Removes and adds again the selected platform.",
639643
options: { frameworkPath: stringOption() },
640-
arguments: "any",
644+
params: "any",
641645
providers: [provideProject()],
642646
}) {
643647
private $platformCommandHelper = inject<IPlatformCommandHelper>(
@@ -656,7 +660,7 @@ export class PlatformCleanCommand extends Command({
656660
```
657661

658662
`meta` is the definition minus its handlers: `name`, `description`, `options`,
659-
`arguments`, `allowUnknownOptions`, `disableAnalytics`, `enableHooks` and
663+
`params`, `allowUnknownOptions`, `disableAnalytics`, `enableHooks` and
660664
`providers`. The
661665
handlers are methods instead — `run` is required, and `canExecute`, `postRun`
662666
and `shortcuts` are optional, each with the same meaning and the same ordering
@@ -950,7 +954,7 @@ The command gets what a typed command line gives it, in the same order: its
950954
declared options are primed into the parser — so `ctx.options` holds this
951955
command's values and its declared defaults rather than the outer command
952956
line's — then its preconditions, as the invocation opens, then `setup`, then
953-
the `arguments` policy, then `canExecute`, then `run`, `postRun`, and the
957+
the `params` policy, then `canExecute`, then `run`, `postRun`, and the
954958
command's hooks.
955959

956960
Two things differ, both because the caller is a process that has to keep
@@ -1041,7 +1045,7 @@ services. The named command is resolved and its options primed exactly as
10411045
builds its own setup from its own services; nothing crosses between the two
10421046
commands but the name and the arguments.
10431047

1044-
Pass only the arguments the child's own `arguments` policy accepts. The child
1048+
Pass only the arguments the child's own `params` policy accepts. The child
10451049
enforces that policy before its `canExecute`, so forwarding a caller's whole
10461050
argument list to a child that declares fewer is a rejection, not a wider check.
10471051

@@ -1083,7 +1087,7 @@ mapping is:
10831087
| --------------------------------- | -------------------------------------------------- |
10841088
| `options` | `dashedOptions` |
10851089
| `run` | `execute`, wrapped in an injection context |
1086-
| `arguments`, `canExecute` | `canExecute`: policy enforced, then the refinement |
1090+
| `params`, `canExecute` | `canExecute`: policy enforced, then the refinement |
10871091
| `setup` | — run inside `canExecute`/`execute`, memoised |
10881092
| `postRun` | `postCommandAction`, with `run`'s return value |
10891093
| `allowUnknownOptions` | `allowUnknownOptions` |
@@ -1092,8 +1096,8 @@ mapping is:
10921096

10931097
The compiled command always exposes `canExecute`, because `CommandsService`
10941098
stops consulting `allowedParameters` as soon as a command has one — the adapter
1095-
therefore enforces the `arguments` policy itself. `allowedParameters` stays
1096-
empty, which is why declared `arguments` are matched positionally rather than
1099+
therefore enforces the `params` policy itself. `allowedParameters` stays
1100+
empty, which is why declared `params` are matched positionally rather than
10971101
by the `ICommandParameter` scan.
10981102

10991103
Existing command classes need no migration. Reach for a definition when a

‎extensions.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ const { defineCommand, inject } = require("nativescript/contracts");
178178

179179
module.exports = defineCommand({
180180
name: "hello|world",
181-
arguments: "any",
181+
params: "any",
182182
async run(ctx) {
183183
inject("logger").info(`Hello, ${ctx.args[0] || "world"}!`);
184184
},

‎lib/commands/add-platform.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class AddPlatformCommand extends Command({
2020
description:
2121
"Configures the current project to target the selected platform.",
2222
options: addPlatformCommandOptions,
23-
arguments: "any",
23+
params: "any",
2424
providers: [provideProject()],
2525
}) {
2626
private $platformCommandHelper = inject<IPlatformCommandHelper>(

‎lib/commands/apple-login.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { IApplePortalSessionService } from "../services/apple-portal/definitions
55
export const appleLoginCommandDefinition = defineCommand({
66
name: "apple-login",
77
description: "Logs in to an Apple account and prints the session cookie.",
8-
arguments: [{ name: "appleId" }, { name: "password" }],
8+
params: [{ name: "appleId" }, { name: "password" }],
99
async run(context) {
1010
const $applePortalSessionService = inject<IApplePortalSessionService>(
1111
"applePortalSessionService",

‎lib/commands/appstore-list.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class ListiOSAppsCommand extends Command({
2121
name: "appstore|*list",
2222
description: "Lists the applications in App Store Connect.",
2323
options: listiOSAppsCommandOptions,
24-
arguments: [{ name: "appleId" }, { name: "password" }],
24+
params: [{ name: "appleId" }, { name: "password" }],
2525
providers: [provideProject()],
2626
}) {
2727
private $applePortalApplicationService =

‎lib/commands/appstore-upload.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class PublishIOSCommand extends Command({
3333
description: "Uploads a project to App Store Connect.",
3434
options: publishIOSCommandOptions,
3535
// Arguments have never been rejected here, only ignored past the third.
36-
arguments: "any",
36+
params: "any",
3737
providers: [provideProject()],
3838
}) {
3939
private $applePortalSessionService = inject<IApplePortalSessionService>(

‎lib/commands/build.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const defineBuildCommand = <const TName extends CommandName>(
5353
name,
5454
description: "Builds the project for the selected target platform.",
5555
options: buildCommandOptions,
56-
arguments: "none",
56+
params: "none",
5757
providers: [provideProject()],
5858
async canExecute(context): Promise<boolean> {
5959
const $devicePlatformsConstants =

‎lib/commands/clean.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ export const cleanCommandDefinition = defineCommand({
315315
name: "clean",
316316
description: "Cleans the project's build artefacts and dependencies.",
317317
options: cleanCommandOptions,
318-
arguments: "none",
318+
params: "none",
319319
async run(context): Promise<void> {
320320
const $projectCleanupService = inject<IProjectCleanupService>(
321321
"projectCleanupService",

‎lib/commands/command-base.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
INotConfiguredEnvOptions,
77
} from "../common/definitions/commands";
88
import {
9-
ArgumentSpec,
9+
ParamSpec,
1010
CommandContext,
1111
CommandOptionsSchema,
1212
objectOption,
@@ -49,7 +49,7 @@ export function validatePlatformArgument(
4949
}
5050

5151
/** The `platform` positional argument, shared by prepare, deploy and embed. */
52-
export const platformArgument: ArgumentSpec<any> = {
52+
export const platformParam: ParamSpec<any> = {
5353
name: "platform",
5454
validate(value, context) {
5555
validatePlatformArgument(context.injector, value);

‎lib/commands/config.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function requireConfigKey(context: CommandContext): void {
4444
export const configListCommandDefinition = defineCommand({
4545
name: "config|*list",
4646
description: "Prints the project configuration.",
47-
arguments: "none",
47+
params: "none",
4848
async run(): Promise<void> {
4949
const $projectConfigService = inject<IProjectConfigService>(
5050
"projectConfigService",
@@ -63,7 +63,7 @@ export const configListCommandDefinition = defineCommand({
6363
export const configGetCommandDefinition = defineCommand({
6464
name: "config|get",
6565
description: "Prints the value the project configuration holds for a key.",
66-
arguments: "any",
66+
params: "any",
6767
async canExecute(context): Promise<boolean> {
6868
requireConfigKey(context);
6969

@@ -88,7 +88,7 @@ export const configGetCommandDefinition = defineCommand({
8888
export const configSetCommandDefinition = defineCommand({
8989
name: "config|set",
9090
description: "Sets a value in the project configuration.",
91-
arguments: "any",
91+
params: "any",
9292
async canExecute(context): Promise<boolean> {
9393
requireConfigKey(context);
9494

0 commit comments

Comments
 (0)