Skip to content

Commit d839373

Browse files
committed
fix: address review findings on the DI foundation
- doctor-service: printWarnings accepts an optional trackResult matching its contracts; runSetupScript returns the setup script result instead of resolving undefined; canExecuteLocalBuild no longer dereferences its optional argument - deprecation tracer: a report dropped for lack of a logger is no longer latched as delivered - it reports once a logger exists - yok: global.$injector is an accessor pair, so a direct third-party assignment stays synchronized with the binding getInjector() reads - docs: note the useValue + shared:false quirk; show where Injector is imported from in the late-lookup example
1 parent da89071 commit d839373

7 files changed

Lines changed: 54 additions & 12 deletions

File tree

dependency-injection.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ Provider kinds:
123123
|---|---|---|
124124
| Class | `provide(Token, Impl)` / `{ provide, useClass }` | constructed with `new Impl()` inside an injection context, so `inject()` works in its fields |
125125
| Lazy class | `provideLazy(Token, () => Impl)` / `{ provide, useLazyClass }` | loader runs on first `get()` only — keeps startup lazy |
126-
| Value | `{ provide, useValue }` | registered instance; re-registering replaces the cached instance |
126+
| Value | `{ provide, useValue }` | registered instance; re-registering replaces the cached instance. With `shared: false` there is no resolver and `get()` throws — a preserved legacy quirk |
127127
| Factory | `{ provide, useFactory }` | called inside an injection context |
128128

129129
`shared: false` makes a provider transient: every resolution constructs a fresh

extending-cli.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ module.exports = function (hookArgs) {
9292
};
9393
```
9494

95-
* `inject()` is valid in the synchronous part of the hook body — not after an `await`. Resolve what you need up front; for late lookups, grab the container first: `const injector = inject(Injector)`, then `injector.get(...)` later.
95+
* `inject()` is valid in the synchronous part of the hook body — not after an `await`. Resolve what you need up front; for late lookups, grab the container first: `const injector = inject(Injector)` (`Injector` is exported from `nativescript/contracts` too), then `injector.get(...)` later.
9696
* `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. A future typed hook API (`defineHook` with an explicit context object) will replace this parameter; it is the one remaining piece of the legacy convention.
9797
* 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.
9898
* Only a first tranche of services has typed tokens so far ([dependency-injection.md](dependency-injection.md#available-contracts) lists them); a service without a token is reachable by its registry name — `inject("logger")` — as a migration bridge.

lib/common/deprecation.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,14 @@ export function reportDeprecation(report: IDeprecationReport): void {
4242
if (reported.has(key)) {
4343
return;
4444
}
45-
reported.add(key);
4645

4746
const logger = report.logger || tryResolveGlobalLogger();
4847
if (!logger) {
48+
// Deliberately not latched: a report dropped for lack of a logger must
49+
// still be deliverable once one becomes resolvable.
4950
return;
5051
}
52+
reported.add(key);
5153

5254
if (stage === "warn") {
5355
logger.warn(message);

lib/common/yok.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -604,10 +604,18 @@ export class Yok extends Injector implements IInjector {
604604
}
605605
}
606606

607-
if (!(<any>global).$injector) {
608-
(<any>global).$injector = new Yok();
609-
injector = (<any>global).$injector;
610-
}
607+
// The global is the published legacy surface. It is an accessor pair so a
608+
// direct `global.$injector = x` assignment — allowed for third parties —
609+
// stays synchronized with the module binding that getInjector() and internal
610+
// code read; a plain data property would silently fork the two.
611+
injector = (<any>global).$injector || new Yok();
612+
Object.defineProperty(global, "$injector", {
613+
get: () => injector,
614+
set: (value: IInjector) => {
615+
injector = value;
616+
},
617+
configurable: true,
618+
});
611619

612620
/**
613621
* Accessor for the process-wide facade, for code that cannot receive the
@@ -625,6 +633,7 @@ export function getInjector(): IInjector {
625633
* the container via inject(Injector) instead of a process-wide global.
626634
*/
627635
export function setGlobalInjector(inj: IInjector): IInjector {
628-
(<any>global).$injector = injector = inj;
636+
injector = inj;
637+
(<any>global).$injector = inj;
629638
return inj;
630639
}

lib/services/doctor-service.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export class DoctorServiceImpl
7979
) {}
8080

8181
public async printWarnings(configOptions?: {
82-
trackResult: boolean;
82+
trackResult?: boolean;
8383
projectDir?: string;
8484
runtimeVersion?: string;
8585
options?: IOptions;
@@ -177,15 +177,16 @@ export class DoctorServiceImpl
177177
"Running the setup script to try and automatically configure your environment.",
178178
);
179179

180+
let result: ISpawnResult;
180181
if (this.$hostInfo.isDarwin) {
181-
await this.runSetupScriptCore(
182+
result = await this.runSetupScriptCore(
182183
DoctorServiceImpl.DarwinSetupScriptLocation,
183184
[],
184185
);
185186
}
186187

187188
if (this.$hostInfo.isWindows) {
188-
await this.runSetupScriptCore(
189+
result = await this.runSetupScriptCore(
189190
DoctorServiceImpl.WindowsSetupScriptExecutable,
190191
DoctorServiceImpl.WindowsSetupScriptArguments,
191192
);
@@ -195,6 +196,8 @@ export class DoctorServiceImpl
195196
action: TrackActionNames.RunSetupScript,
196197
additionalData: "Finished",
197198
});
199+
200+
return result;
198201
}
199202

200203
public async canExecuteLocalBuild(configuration?: {
@@ -203,6 +206,7 @@ export class DoctorServiceImpl
203206
runtimeVersion?: string;
204207
forceCheck?: boolean;
205208
}): Promise<boolean> {
209+
configuration = configuration || {};
206210
await this.$analyticsService.trackEventActionInGoogleAnalytics({
207211
action: TrackActionNames.CheckLocalBuildSetup,
208212
additionalData: "Starting",

test/compat/injector-facade-surface.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { assert } from "chai";
2-
import { Yok } from "../../lib/common/yok";
2+
import { Yok, getInjector } from "../../lib/common/yok";
33
import { Injector, inject, runInInjectionContext } from "../../lib/common/di";
44
import {
55
CommandRegistry,
@@ -62,6 +62,19 @@ describe("injector facade surface", () => {
6262
assert.strictEqual(sub.resolve("injector"), sub);
6363
});
6464

65+
it("keeps getInjector() synchronized with a direct global.$injector assignment", () => {
66+
const previous = getInjector();
67+
const fresh = new Yok();
68+
69+
(<any>global).$injector = fresh;
70+
try {
71+
assert.strictEqual(getInjector(), fresh);
72+
} finally {
73+
(<any>global).$injector = previous;
74+
}
75+
assert.strictEqual(getInjector(), previous);
76+
});
77+
6578
it("assigns the process-wide global.$injector", () => {
6679
assert.isOk((<any>global).$injector);
6780
for (const member of FACADE_METHODS) {

test/deprecation.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,18 @@ describe("deprecation tracer", () => {
8484
setGlobalInjector(previousInjector);
8585
}
8686
});
87+
88+
it("still delivers a report that was previously dropped for lack of a logger", () => {
89+
const previousInjector = getInjector();
90+
setGlobalInjector(new Yok());
91+
try {
92+
reportDeprecation({ api: "test.redeliver" });
93+
} finally {
94+
setGlobalInjector(previousInjector);
95+
}
96+
97+
reportDeprecation({ api: "test.redeliver", logger });
98+
99+
assert.include(logger.traceOutput, "test.redeliver");
100+
});
87101
});

0 commit comments

Comments
 (0)