Skip to content

Commit 30a85d1

Browse files
committed
Fork and reload for config changes
1 parent 38f1100 commit 30a85d1

File tree

6 files changed

+44
-54
lines changed

6 files changed

+44
-54
lines changed

bin/typedoc

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
#!/usr/bin/env node
22
//@ts-check
33

4-
/* eslint-disable @typescript-eslint/no-var-requires */
5-
import("../dist/lib/cli.js");
4+
const { fork } = require("child_process");
5+
6+
function main() {
7+
fork(__dirname + "/../dist/lib/cli.js", process.argv.slice(2), {
8+
stdio: "inherit",
9+
}).on("exit", (code) => {
10+
// Watch restart required? Fork a new child
11+
if (code === 7) main();
12+
else process.exit(code || 0);
13+
});
14+
}
15+
16+
main();

site/options/other.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,6 @@ configuration files, files imported by `@include`/`@includeCode`, and any
1818
files explicitly registered by plugins as needing to be watched, as well
1919
as all your TypeScript source files.
2020

21-
Note, however, that if you are defining your typedoc configuration using an ESM
22-
module (`.mjs`, or `.js` in a `"type": "module"` project) on Windows, typedoc
23-
will not be able to reload it when it changes, due to the way node's import
24-
caching works on Windows. (And if your configuration files import or require
25-
other modules, those modules won't be reloaded either, regardless of platform!)
26-
27-
In such cases, you'll need to either switch to a .cjs file, manually restart
28-
the build, or use an external watcher like `onchange` to restart typedoc when
29-
relevant files change.
30-
3121
## preserveWatchOutput
3222

3323
```bash

src/lib/application.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -462,11 +462,16 @@ export class Application extends AbstractComponent<
462462
this._watchFile?.(path, shouldRestart);
463463
}
464464

465-
public convertAndWatch(
465+
/**
466+
* Run a convert / watch process.
467+
*
468+
* @param success Callback to run after each convert, receiving the project
469+
* @returns True if the watch process should be restarted due to a
470+
* configuration change, false for an options error
471+
*/
472+
public async convertAndWatch(
466473
success: (project: ProjectReflection) => Promise<void>,
467-
/** Callback to restart watching when options or other critical files change */
468-
restartWatch?: () => unknown,
469-
): void {
474+
): Promise<boolean> {
470475
if (
471476
!this.options.getValue("preserveWatchOutput") &&
472477
this.logger instanceof ConsoleLogger
@@ -498,7 +503,7 @@ export class Application extends AbstractComponent<
498503
// have reported in the first time... just error out for now. I'm not convinced anyone will actually notice.
499504
if (this.options.getFileNames().length === 0) {
500505
this.logger.error(this.i18n.solution_not_supported_in_watch_mode());
501-
return;
506+
return false;
502507
}
503508

504509
// Support for packages mode is currently unimplemented
@@ -507,7 +512,7 @@ export class Application extends AbstractComponent<
507512
this.entryPointStrategy !== EntryPointStrategy.Expand
508513
) {
509514
this.logger.error(this.i18n.strategy_not_supported_in_watch_mode());
510-
return;
515+
return false;
511516
}
512517

513518
const tsconfigFile =
@@ -575,16 +580,13 @@ export class Application extends AbstractComponent<
575580
);
576581
};
577582

583+
/** resolver for the returned promise */
584+
let exitWatch: (restart: boolean) => unknown;
578585
const restartMain = (file: string) => {
579586
if (restarting) return;
580-
if (!restartWatch)
581-
this.logger.warn(
582-
this.i18n.file_0_changed_but_cant_restart(nicePath(file)),
583-
);
584-
else
585-
this.logger.info(
586-
this.i18n.file_0_changed_restarting(nicePath(file)),
587-
);
587+
this.logger.info(
588+
this.i18n.file_0_changed_restarting(nicePath(file)),
589+
);
588590
restarting = true;
589591
currentProgram = undefined;
590592
this.clearWatches();
@@ -594,7 +596,7 @@ export class Application extends AbstractComponent<
594596
const runSuccess = () => {
595597
if (restarting && successFinished) {
596598
successFinished = false;
597-
if (restartWatch) restartWatch();
599+
exitWatch(true);
598600
return;
599601
}
600602

@@ -673,6 +675,11 @@ export class Application extends AbstractComponent<
673675
};
674676

675677
const tsWatcher = ts.createWatchProgram(host);
678+
679+
// Don't return to caller until the watch needs to restart
680+
return await new Promise((res) => {
681+
exitWatch = res;
682+
});
676683
}
677684

678685
validate(project: ProjectReflection) {

src/lib/cli.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ async function main() {
3232
if (exitCode !== ExitCodes.Watching) {
3333
app.logger.verbose(`Full run took ${Date.now() - start}ms`);
3434
logRunSummary(app.logger);
35-
process.exit(exitCode);
3635
}
36+
process.exit(exitCode);
3737
} catch (error) {
3838
console.error("TypeDoc exiting with unexpected error:");
3939
console.error(error);
@@ -73,11 +73,12 @@ async function run(app: td.Application) {
7373
}
7474

7575
if (app.options.getValue("watch")) {
76-
app.convertAndWatch(async (project) => {
76+
return (await app.convertAndWatch(async (project) => {
7777
app.validate(project);
7878
await app.generateOutputs(project);
79-
}, main);
80-
return ExitCodes.Watching;
79+
}))
80+
? ExitCodes.Watching
81+
: ExitCodes.OptionError;
8182
}
8283

8384
const project = await app.convert();

src/lib/internationalization/locales/en.cts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ export = {
1515
"The provided tsconfig file looks like a solution style tsconfig, which is not supported in watch mode",
1616
strategy_not_supported_in_watch_mode:
1717
"entryPointStrategy must be set to either resolve or expand for watch mode",
18-
file_0_changed_but_cant_restart:
19-
"File {0} changed but no restart callback was given; please restart manually",
2018
file_0_changed_restarting:
2119
"Configuration file {0} changed: full restart required...",
2220
file_0_changed_rebuilding: "File {0} changed: rebuilding output...",

src/lib/utils/options/readers/typedoc.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import { createRequire } from "module";
1212
import { pathToFileURL } from "url";
1313
import type { TranslatedString } from "../../../internationalization/internationalization.js";
1414

15-
const require = createRequire(import.meta.url);
16-
1715
/**
1816
* Obtains option values from typedoc.json
1917
*/
@@ -90,25 +88,10 @@ export class TypeDocReader implements OptionsReader {
9088
}
9189
} else {
9290
try {
93-
if (process.platform === "win32") {
94-
// Node on Windows doesn't support the `?` trick for
95-
// cache-busting, so try using require()
96-
delete require.cache[require.resolve(file)];
97-
try {
98-
const mod = require(file);
99-
fileContent = mod.default ?? mod;
100-
} catch (error: any) {
101-
// Only recent node can require an ESM
102-
if (error?.code !== "ERR_REQUIRE_ESM") throw error;
103-
}
104-
}
105-
if (!fileContent) {
106-
const esmPath = pathToFileURL(file).toString();
107-
// Cache-bust for reload on watch
108-
fileContent = await (
109-
await import(`${esmPath}?${Date.now()}`)
110-
).default;
111-
}
91+
// On Windows, we need to ensure this path is a file path.
92+
// Or we'll get ERR_UNSUPPORTED_ESM_URL_SCHEME
93+
const esmPath = pathToFileURL(file).toString();
94+
fileContent = await (await import(esmPath)).default;
11295
} catch (error) {
11396
logger.error(
11497
logger.i18n.failed_read_options_file_0(nicePath(file)),

0 commit comments

Comments
 (0)