Skip to content

Commit 28e7169

Browse files
committed
docs: lead with the typed API for hooks and demote string tokens
extending-cli.md's in-process section now opens with the recommended hook pattern (hookArgs parameter + $injector.di.get(Token)) and moves parameter-name injection into a labeled legacy section. String-token lookups are framed as the migration bridge throughout, not a co-equal API.
1 parent babff18 commit 28e7169

2 files changed

Lines changed: 50 additions & 35 deletions

File tree

dependency-injection.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@ import { inject, DoctorService } from "nativescript/contracts";
2222

2323
class PlatformChecker {
2424
private doctorService = inject(DoctorService); // typed, no decorators needed
25-
private logger = inject("logger"); // string tokens still work
2625

2726
async check(projectDir: string): Promise<boolean> {
2827
return this.doctorService.canExecuteLocalBuild({ projectDir });
2928
}
3029
}
3130
```
3231

32+
Services that have no typed token yet remain reachable by their registry name —
33+
`inject("logger")` — but that is the migration bridge, not the API: prefer the
34+
token wherever one exists, and mint a token rather than a new string name.
35+
3336
Tokens: `@Contract`
3437
-------------------
3538

@@ -88,7 +91,9 @@ class EnvironmentChecker {
8891
```
8992

9093
`Injector.get()` accepts a contract class, a string name, or a `$`-prefixed
91-
string name — all three return the same instance.
94+
string name — all three return the same instance. The string forms exist for
95+
interoperability with the legacy registry; use the class token whenever one
96+
exists.
9297

9398
Registering: providers
9499
----------------------
@@ -103,8 +108,8 @@ const injector = new Injector([
103108
// deferred loading: the module is require()d on first resolution only
104109
provideLazy(DoctorService, () => require("./doctor-service").DoctorServiceImpl),
105110

106-
{ provide: "config", useValue: { DISABLE_HOOKS: false } },
107-
{ provide: "dispatcher", useFactory: () => createDispatcher(), shared: false },
111+
{ provide: Config, useValue: { DISABLE_HOOKS: false } },
112+
{ provide: Dispatcher, useFactory: () => createDispatcher(), shared: false },
108113
]);
109114

110115
// registration is also allowed after construction; re-registering a token
@@ -125,6 +130,11 @@ Provider kinds:
125130
instance. Transient instances are still retained by the container so
126131
`dispose()` reaches them.
127132

133+
String keys are accepted anywhere a token is (`{ provide: "logger", useValue }`)
134+
— that is how the legacy facade registers, and how per-call overrides address
135+
not-yet-migrated dependencies. New registrations should mint a `@Contract`
136+
token instead of a new string name.
137+
128138
For per-call construction with overrides (a fresh instance of a class with some
129139
dependencies replaced), use `createInstance`:
130140

extending-cli.md

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,34 @@ Execute Hooks In-Process
7777
7878
When your hook is a Node.js script, the CLI executes it in-process. This gives you access to the entire internal state of the CLI and all of its functions.
7979
80-
The CLI assumes that this is a CommonJS module and calls its single exported function with four parameters. The type of the parameters is described in the `.d.ts` files which are part of the CLI source code [here](https://github.com/NativeScript/nativescript-cli/tree/master/lib/definitions) and [here](https://github.com/telerik/mobile-cli-lib/tree/master/definitions).
80+
The CLI assumes that this is a CommonJS module and calls its single exported function.
81+
82+
## Writing a hook
83+
84+
Declare a `hookArgs` parameter if you need the payload of the operation being hooked, and resolve CLI services through the typed injection API: `global.$injector.di` is the CLI's container, and the `nativescript/contracts` subpath exports its tokens. See [dependency-injection.md](dependency-injection.md) for the full API.
85+
86+
```JavaScript
87+
const { DoctorService } = require("nativescript/contracts");
88+
89+
module.exports = function (hookArgs) {
90+
const di = global.$injector.di;
91+
92+
const doctorService = di.get(DoctorService); // typed token
93+
const logger = di.get("logger"); // no typed token yet — registry name
94+
95+
logger.info("running my hook");
96+
return doctorService.canExecuteLocalBuild();
97+
};
98+
```
99+
100+
* `hookArgs` contains the parameters of the CLI function being hooked; its shape depends on the hook point. Declare it only when you need it — a hook may also take no parameters at all.
101+
* Tokens resolve by class first and by their canonical name on a miss, so this works even if your dependency tree carries its own copy of `nativescript` — a duplicated token class still resolves to the running CLI's service.
102+
* Only a first tranche of services has typed tokens so far ([dependency-injection.md](dependency-injection.md#available-contracts) lists them); every other service is reachable by name — `di.get("logger")` returns the same instance as the legacy `$injector.resolve("logger")`.
103+
* If you build your hook in TypeScript, add `nativescript` as a `devDependency` and import the same names: `import { DoctorService } from "nativescript/contracts"`.
104+
* `inject()` itself requires an injection context, which hook invocation does not provide — use `di.get(...)` as above. A typed hook API that also replaces the `hookArgs` parameter with an explicit context is planned.
105+
106+
## The hook contract
81107

82-
Parameter | Type | Description
83-
---|---|---
84-
`$logger` | ILogger | Use the members of this class to show messages to the user cooperating with the CLI internal state.
85-
`$projectData` | IProjectData | Contains data about the project, such as project directory, ID, dependencies, etc.
86-
`$usbLiveSyncService` | ILiveSyncService | Use this variable to check whether a LiveSync or normal build is in progress.
87-
`hookArgs` | Any | Contains all the parameters of the original function in the CLI which is being hooked.
88-
89108
The hook must return a Promise. If the hook succeeds, it must fullfil the promise, but the fullfilment value is ignored.
90109
The hook can also reject the promise with an instance of Error. The returned error can have two optional members controlling the CLI.
91110

@@ -95,33 +114,19 @@ Member | Type | Description
95114
`errorAsWarning` | Boolean | Set this to treat the returned error as warning. The CLI prints the error.message colored as a warning and continues executing the current command.
96115

97116
If these two members are not set, the CLI prints the returned error colored as fatal error and stops executing the current command.
98-
99-
Furthermore, the global variable `$injector` of type `IInjector` provides access to the CLI Dependency Injector, through which all code services are available.
100-
101-
Using the Typed Injection API
102-
==============================
103117

104-
The CLI also ships a typed, token-based injection API from the `nativescript/contracts` subpath, backed by the same container as `$injector` — a service resolved through a token is the identical instance you get by parameter name. See [dependency-injection.md](dependency-injection.md) for the full API.
118+
## Legacy: parameter-name injection
105119

106-
From a hook, access it through the injector's `di` container:
120+
Historically, a hook received CLI services by naming them as parameters: the CLI parses the exported function's parameter names and injects the service registered under each name. Existing hooks written this way keep working unchanged, but **new hooks should use the pattern above** — parameter-name service injection is slated for deprecation, and hooks that use it are reported through the CLI's deprecation tracer (visible with `--log trace`, or as warnings with `NS_DEPRECATIONS=warn`).
107121

108-
```JavaScript
109-
const { DoctorService } = require("nativescript/contracts");
110-
111-
module.exports = function ($injector, $projectData) {
112-
const doctorService = $injector.di.get(DoctorService);
113-
return doctorService.canExecuteLocalBuild({
114-
projectDir: $projectData.projectDir,
115-
});
116-
};
117-
```
118-
119-
Notes:
122+
Parameter | Type | Description
123+
---|---|---
124+
`$logger` | ILogger | Use the members of this class to show messages to the user cooperating with the CLI internal state.
125+
`$projectData` | IProjectData | Contains data about the project, such as project directory, ID, dependencies, etc.
126+
`$usbLiveSyncService` | ILiveSyncService | Use this variable to check whether a LiveSync or normal build is in progress.
127+
`hookArgs` | Any | Contains all the parameters of the original function in the CLI which is being hooked.
120128

121-
* Tokens resolve by class first and by their canonical name on a miss, so this works even if your dependency tree carries its own copy of `nativescript` — a duplicated token class still resolves to the running CLI's service.
122-
* If you build your hook in TypeScript, add `nativescript` as a `devDependency` and import the same names: `import { DoctorService } from "nativescript/contracts"`.
123-
* `inject()` itself requires an injection context, which the current hook invocation does not provide — hooks should use `$injector.di.get(...)` as above. A typed hook API that removes the parameter-name convention entirely is planned; until then, parameter-name injection remains fully supported, though its usage is traced for eventual deprecation (run the CLI with `--log trace` to see the reports, or set `NS_DEPRECATIONS=warn` to surface them).
124-
* Only a first tranche of services has typed tokens so far ([dependency-injection.md](dependency-injection.md#available-contracts) lists them); every other service remains reachable by name — `$injector.di.get("logger")` and `$injector.resolve("logger")` are equivalent.
129+
The type of the parameters is described in the `.d.ts` files which are part of the CLI source code [here](https://github.com/NativeScript/nativescript-cli/tree/master/lib/definitions). Any registered service name is injectable, not only the ones listed; the global variable `$injector` of type `IInjector` likewise remains available. A parameter the CLI cannot resolve causes the hook to be skipped with a warning.
125130

126131
Commands with Hooking Support
127132
==============================

0 commit comments

Comments
 (0)