@flowscripter/dynamic-cli-framework 7.0.10 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/src/cli/BaseCLI.d.ts.map +1 -1
- package/dist/src/cli/BaseCLI.js +24 -22
- package/dist/src/cli/BaseCLIFeatureOptions.d.ts +5 -0
- package/dist/src/cli/BaseCLIFeatureOptions.d.ts.map +1 -1
- package/dist/src/cli/DefaultRuntimeCLI.js +1 -1
- package/dist/src/runtime/lifecycle/priorities.d.ts +50 -0
- package/dist/src/runtime/lifecycle/priorities.d.ts.map +1 -0
- package/dist/src/runtime/lifecycle/priorities.js +49 -0
- package/dist/src/runtime/runner.d.ts +10 -1
- package/dist/src/runtime/runner.d.ts.map +1 -1
- package/dist/src/runtime/runner.js +7 -1
- package/dist/src/service/configuration/DefaultKeyValueService.d.ts +9 -1
- package/dist/src/service/configuration/DefaultKeyValueService.d.ts.map +1 -1
- package/dist/src/service/configuration/DefaultKeyValueService.js +16 -1
- package/dist/src/service/configuration/KeyValueServiceProvider.d.ts.map +1 -1
- package/dist/src/service/configuration/KeyValueServiceProvider.js +15 -9
- package/dist/src/service/fetch/DefaultFetchService.d.ts.map +1 -1
- package/dist/src/service/fetch/DefaultFetchService.js +2 -1
- package/dist/src/service/printer/PrinterServiceProvider.d.ts.map +1 -1
- package/dist/src/service/printer/PrinterServiceProvider.js +2 -1
- package/dist/src/service/shutdown/ShutdownServiceProvider.js +3 -3
- package/dist/src/service/spawn/DefaultSpawnService.d.ts.map +1 -1
- package/dist/src/service/spawn/DefaultSpawnService.js +3 -1
- package/dist/src/service/upgrade/DefaultUpgradeService.d.ts +30 -2
- package/dist/src/service/upgrade/DefaultUpgradeService.d.ts.map +1 -1
- package/dist/src/service/upgrade/DefaultUpgradeService.js +99 -6
- package/dist/src/service/upgrade/UpgradeServiceProvider.d.ts +32 -2
- package/dist/src/service/upgrade/UpgradeServiceProvider.d.ts.map +1 -1
- package/dist/src/service/upgrade/UpgradeServiceProvider.js +82 -14
- package/dist/src/startup/banner/bannerStartupTask.d.ts +5 -5
- package/dist/src/startup/banner/bannerStartupTask.d.ts.map +1 -1
- package/dist/src/startup/banner/bannerStartupTask.js +14 -10
- package/index.ts +20 -0
- package/package.json +2 -2
- package/src/cli/BaseCLI.ts +67 -21
- package/src/cli/BaseCLIFeatureOptions.ts +5 -0
- package/src/cli/DefaultRuntimeCLI.ts +1 -1
- package/src/runtime/lifecycle/priorities.ts +67 -0
- package/src/runtime/runner.ts +17 -2
- package/src/service/configuration/DefaultKeyValueService.ts +22 -1
- package/src/service/configuration/KeyValueServiceProvider.ts +20 -9
- package/src/service/fetch/DefaultFetchService.ts +2 -1
- package/src/service/printer/PrinterServiceProvider.ts +2 -1
- package/src/service/shutdown/ShutdownServiceProvider.ts +3 -3
- package/src/service/spawn/DefaultSpawnService.ts +3 -1
- package/src/service/upgrade/DefaultUpgradeService.ts +112 -5
- package/src/service/upgrade/UpgradeServiceProvider.ts +112 -20
- package/src/service/upgrade/command/UpgradeSubCommand.ts +2 -2
- package/src/startup/banner/bannerStartupTask.ts +16 -10
package/src/cli/BaseCLI.ts
CHANGED
|
@@ -57,9 +57,28 @@ import ImagePrinterServiceProvider from "../service/imagePrinter/ImagePrinterSer
|
|
|
57
57
|
import SpawnServiceProvider from "../service/spawn/SpawnServiceProvider.ts";
|
|
58
58
|
import FetchServiceProvider from "../service/fetch/FetchServiceProvider.ts";
|
|
59
59
|
import UpgradeServiceProvider, {
|
|
60
|
+
createAutoUpgradeStartupTask,
|
|
60
61
|
createUpgradeCheckStartupTask,
|
|
61
62
|
} from "../service/upgrade/UpgradeServiceProvider.ts";
|
|
62
63
|
import PluginServiceProvider from "../service/plugin/PluginServiceProvider.ts";
|
|
64
|
+
import {
|
|
65
|
+
ARGUMENT_PROMPTER_SERVICE_PRIORITY,
|
|
66
|
+
AUTO_UPGRADE_STARTUP_TASK_PRIORITY,
|
|
67
|
+
COMPLETION_SERVICE_PRIORITY,
|
|
68
|
+
CONFIGURATION_SERVICE_PRIORITY,
|
|
69
|
+
FETCH_SERVICE_PRIORITY,
|
|
70
|
+
IMAGE_PRINTER_SERVICE_PRIORITY,
|
|
71
|
+
KEY_VALUE_SERVICE_PRIORITY,
|
|
72
|
+
PLUGIN_SERVICE_PRIORITY,
|
|
73
|
+
PRINTER_SERVICE_PRIORITY,
|
|
74
|
+
PROMPTER_SERVICE_PRIORITY,
|
|
75
|
+
SHUTDOWN_SERVICE_PRIORITY,
|
|
76
|
+
SPAWN_SERVICE_PRIORITY,
|
|
77
|
+
STARTUP_SERVICE_PRIORITY,
|
|
78
|
+
TABLE_GENERATOR_SERVICE_PRIORITY,
|
|
79
|
+
UPGRADE_CHECK_STARTUP_TASK_PRIORITY,
|
|
80
|
+
UPGRADE_SERVICE_PRIORITY,
|
|
81
|
+
} from "../runtime/lifecycle/priorities.ts";
|
|
63
82
|
const logger = getLogger("BaseCLI");
|
|
64
83
|
|
|
65
84
|
/**
|
|
@@ -152,6 +171,7 @@ export default class BaseCLI implements CLI {
|
|
|
152
171
|
fetchServiceEnabled: false,
|
|
153
172
|
upgradeServiceEnabled: false,
|
|
154
173
|
upgradeLocationsConfig: { supportedPlatforms: [] },
|
|
174
|
+
restartAfterAutoUpgrade: false,
|
|
155
175
|
pluginServiceEnabled: false,
|
|
156
176
|
pluginServiceRemoteConfig: { name: "", registryUrl: "", packageJsonNamespace: "" },
|
|
157
177
|
pluginServiceLocalConfig: { nodeModulesPath: "", packageJsonNamespace: "" },
|
|
@@ -177,9 +197,7 @@ export default class BaseCLI implements CLI {
|
|
|
177
197
|
// create a context
|
|
178
198
|
this.#context = new DefaultContext(this.#cliConfig);
|
|
179
199
|
|
|
180
|
-
|
|
181
|
-
// dependency on anything since its own initService() is a no-op.
|
|
182
|
-
this.#startupServiceProvider = new StartupServiceProvider(95);
|
|
200
|
+
this.#startupServiceProvider = new StartupServiceProvider(STARTUP_SERVICE_PRIORITY);
|
|
183
201
|
|
|
184
202
|
this.#stdoutTerminal = stdoutTerminal;
|
|
185
203
|
this.#printerService = new DefaultPrinterService(
|
|
@@ -251,10 +269,12 @@ export default class BaseCLI implements CLI {
|
|
|
251
269
|
}
|
|
252
270
|
|
|
253
271
|
// create and add core services
|
|
254
|
-
this.addServiceProvider(new ShutdownServiceProvider(
|
|
272
|
+
this.addServiceProvider(new ShutdownServiceProvider(SHUTDOWN_SERVICE_PRIORITY));
|
|
255
273
|
this.addServiceProvider(this.#startupServiceProvider);
|
|
256
|
-
this.addServiceProvider(
|
|
257
|
-
|
|
274
|
+
this.addServiceProvider(
|
|
275
|
+
new PrinterServiceProvider(PRINTER_SERVICE_PRIORITY, this.#printerService),
|
|
276
|
+
);
|
|
277
|
+
this.addServiceProvider(new TableGeneratorServiceProvider(TABLE_GENERATOR_SERVICE_PRIORITY));
|
|
258
278
|
|
|
259
279
|
const canPrompt = this.#keyReader !== undefined && this.#stderrTerminal.isTty();
|
|
260
280
|
|
|
@@ -271,7 +291,9 @@ export default class BaseCLI implements CLI {
|
|
|
271
291
|
this.#printerService,
|
|
272
292
|
);
|
|
273
293
|
prompterService.promptEnabled = this.#options.promptingEnabled;
|
|
274
|
-
this.addServiceProvider(
|
|
294
|
+
this.addServiceProvider(
|
|
295
|
+
new PrompterServiceProvider(PROMPTER_SERVICE_PRIORITY, prompterService),
|
|
296
|
+
);
|
|
275
297
|
} else {
|
|
276
298
|
logger.debug(
|
|
277
299
|
() =>
|
|
@@ -284,7 +306,12 @@ export default class BaseCLI implements CLI {
|
|
|
284
306
|
if (this.#options.argumentPrompterServiceEnabled) {
|
|
285
307
|
if (prompterService !== undefined) {
|
|
286
308
|
const argumentPrompterService = new DefaultArgumentPrompterService(prompterService);
|
|
287
|
-
this.addServiceProvider(
|
|
309
|
+
this.addServiceProvider(
|
|
310
|
+
new ArgumentPrompterServiceProvider(
|
|
311
|
+
ARGUMENT_PROMPTER_SERVICE_PRIORITY,
|
|
312
|
+
argumentPrompterService,
|
|
313
|
+
),
|
|
314
|
+
);
|
|
288
315
|
} else {
|
|
289
316
|
logger.debug(
|
|
290
317
|
"Skipping ArgumentPrompterServiceProvider: argumentPrompterServiceEnabled is true but prompting is unavailable",
|
|
@@ -294,7 +321,9 @@ export default class BaseCLI implements CLI {
|
|
|
294
321
|
|
|
295
322
|
if (this.#options.imagePrinterServiceEnabled) {
|
|
296
323
|
if (this.#stdoutTerminal.isTty()) {
|
|
297
|
-
this.addServiceProvider(
|
|
324
|
+
this.addServiceProvider(
|
|
325
|
+
new ImagePrinterServiceProvider(IMAGE_PRINTER_SERVICE_PRIORITY, this.#stdoutTerminal),
|
|
326
|
+
);
|
|
298
327
|
} else {
|
|
299
328
|
logger.debug(
|
|
300
329
|
"Skipping ImagePrinterServiceProvider: imagePrinterServiceEnabled is true but stdout is not a TTY",
|
|
@@ -305,32 +334,39 @@ export default class BaseCLI implements CLI {
|
|
|
305
334
|
// The plugin service routes package manager output through SpawnService so that it is
|
|
306
335
|
// quoted, marked and cleared on success; enabling plugins therefore also enables spawning.
|
|
307
336
|
if (this.#options.spawnServiceEnabled || this.#options.pluginServiceEnabled) {
|
|
308
|
-
this.addServiceProvider(new SpawnServiceProvider(
|
|
337
|
+
this.addServiceProvider(new SpawnServiceProvider(SPAWN_SERVICE_PRIORITY));
|
|
309
338
|
}
|
|
310
339
|
|
|
311
340
|
if (this.#options.fetchServiceEnabled) {
|
|
312
|
-
this.addServiceProvider(new FetchServiceProvider(
|
|
341
|
+
this.addServiceProvider(new FetchServiceProvider(FETCH_SERVICE_PRIORITY));
|
|
313
342
|
}
|
|
314
343
|
|
|
315
344
|
if (this.#options.completionServiceEnabled) {
|
|
316
345
|
const completionService = new DefaultCompletionService();
|
|
317
346
|
this.addServiceProvider(
|
|
318
|
-
new CompletionServiceProvider(
|
|
347
|
+
new CompletionServiceProvider(
|
|
348
|
+
COMPLETION_SERVICE_PRIORITY,
|
|
349
|
+
completionService,
|
|
350
|
+
this.#commandRegistry,
|
|
351
|
+
),
|
|
319
352
|
);
|
|
320
353
|
}
|
|
321
354
|
|
|
322
355
|
let upgradeServiceProvider: UpgradeServiceProvider | undefined;
|
|
323
356
|
if (this.#options.upgradeServiceEnabled) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
357
|
+
upgradeServiceProvider = new UpgradeServiceProvider(
|
|
358
|
+
UPGRADE_SERVICE_PRIORITY,
|
|
359
|
+
this.#options.upgradeLocationsConfig,
|
|
360
|
+
this.#options.restartAfterAutoUpgrade,
|
|
361
|
+
);
|
|
362
|
+
upgradeServiceProvider.setRestartArgs(args);
|
|
327
363
|
this.addServiceProvider(upgradeServiceProvider);
|
|
328
364
|
}
|
|
329
365
|
|
|
330
366
|
if (this.#options.pluginServiceEnabled) {
|
|
331
367
|
this.addServiceProvider(
|
|
332
368
|
new PluginServiceProvider(
|
|
333
|
-
|
|
369
|
+
PLUGIN_SERVICE_PRIORITY,
|
|
334
370
|
this.#options.pluginServiceRemoteConfig,
|
|
335
371
|
this.#options.pluginServiceLocalConfig,
|
|
336
372
|
this.#commandRegistry,
|
|
@@ -340,16 +376,14 @@ export default class BaseCLI implements CLI {
|
|
|
340
376
|
}
|
|
341
377
|
|
|
342
378
|
const configurationServiceProvider = new ConfigurationServiceProvider(
|
|
343
|
-
|
|
379
|
+
CONFIGURATION_SERVICE_PRIORITY,
|
|
344
380
|
this.#options.envVarsSupportEnabled,
|
|
345
381
|
this.#options.configFileSupportEnabled,
|
|
346
382
|
this.#options.secretServiceEnabled,
|
|
347
383
|
);
|
|
348
384
|
this.addServiceProvider(configurationServiceProvider);
|
|
349
|
-
// priority 89 sits just below ConfigurationServiceProvider's 90 - its config-file read
|
|
350
|
-
// happens in its own initService(), before KeyValueServiceProvider's initService() runs.
|
|
351
385
|
const keyValueServiceProvider = new KeyValueServiceProvider(
|
|
352
|
-
|
|
386
|
+
KEY_VALUE_SERVICE_PRIORITY,
|
|
353
387
|
configurationServiceProvider,
|
|
354
388
|
this.#options.keyValueServiceEnabled,
|
|
355
389
|
this.#options.secretServiceEnabled,
|
|
@@ -369,7 +403,15 @@ export default class BaseCLI implements CLI {
|
|
|
369
403
|
}
|
|
370
404
|
|
|
371
405
|
if (upgradeServiceProvider?.upgradeService) {
|
|
372
|
-
this.addStartupTask(
|
|
406
|
+
this.addStartupTask(
|
|
407
|
+
createUpgradeCheckStartupTask(
|
|
408
|
+
upgradeServiceProvider.upgradeService,
|
|
409
|
+
UPGRADE_CHECK_STARTUP_TASK_PRIORITY,
|
|
410
|
+
),
|
|
411
|
+
);
|
|
412
|
+
this.addStartupTask(
|
|
413
|
+
createAutoUpgradeStartupTask(upgradeServiceProvider, AUTO_UPGRADE_STARTUP_TASK_PRIORITY),
|
|
414
|
+
);
|
|
373
415
|
}
|
|
374
416
|
|
|
375
417
|
// directly-registered StartupTasks (e.g. the banner task) aren't backed by a ServiceProvider,
|
|
@@ -435,6 +477,10 @@ export default class BaseCLI implements CLI {
|
|
|
435
477
|
defaultCommand,
|
|
436
478
|
this.#startupServiceProvider.startupService,
|
|
437
479
|
);
|
|
480
|
+
// a startup task ended the run, having already handled its outcome itself
|
|
481
|
+
if (runResult.exitRequested) {
|
|
482
|
+
return { runState: runResult.runState };
|
|
483
|
+
}
|
|
438
484
|
// then handle the result...
|
|
439
485
|
if (runResult.runState === RunState.NO_COMMAND) {
|
|
440
486
|
// print usage
|
|
@@ -16,6 +16,11 @@ export default interface BaseCLIFeatureOptions {
|
|
|
16
16
|
readonly fetchServiceEnabled?: boolean;
|
|
17
17
|
readonly upgradeServiceEnabled?: boolean;
|
|
18
18
|
readonly upgradeLocationsConfig?: UpgradeLocationsConfig;
|
|
19
|
+
/**
|
|
20
|
+
* When an automatic upgrade on startup succeeds, run the upgraded executable with the same
|
|
21
|
+
* arguments and end this run with its result, rather than continuing on the old version.
|
|
22
|
+
*/
|
|
23
|
+
readonly restartAfterAutoUpgrade?: boolean;
|
|
19
24
|
readonly pluginServiceEnabled?: boolean;
|
|
20
25
|
readonly pluginServiceRemoteConfig?: NpmjsPluginRepositoryConfig;
|
|
21
26
|
readonly pluginServiceLocalConfig?: NpmPluginRepositoryConfig;
|
|
@@ -72,7 +72,7 @@ export default class DefaultRuntimeCLI extends BaseCLI {
|
|
|
72
72
|
stderrTerminal,
|
|
73
73
|
new TtyStyler(colorLevel, supportsHyperlinks(process.stderr)),
|
|
74
74
|
keyReader,
|
|
75
|
-
{ promptingEnabled, ...options },
|
|
75
|
+
{ promptingEnabled, restartAfterAutoUpgrade: true, ...options },
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Priorities of the framework's built-in service providers, startup tasks and shutdown tasks.
|
|
3
|
+
* Higher values run earlier (see {@link sortByPriority}). Service provider priorities also order
|
|
4
|
+
* their `initService()` calls within the startup sequence: when a provider and a directly
|
|
5
|
+
* registered startup task share a priority, the provider runs first.
|
|
6
|
+
*
|
|
7
|
+
* Consumer service providers and startup tasks (e.g. the banner task) choose their own
|
|
8
|
+
* priorities relative to these.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Shutdown service provider: first, so signal handling is in place before anything else. */
|
|
12
|
+
export const SHUTDOWN_SERVICE_PRIORITY = 100;
|
|
13
|
+
|
|
14
|
+
/** Startup service provider: no ordering dependency, as its `initService()` is a no-op. */
|
|
15
|
+
export const STARTUP_SERVICE_PRIORITY = 95;
|
|
16
|
+
|
|
17
|
+
/** Configuration service provider: reads the config file in its `initService()`. */
|
|
18
|
+
export const CONFIGURATION_SERVICE_PRIORITY = 90;
|
|
19
|
+
|
|
20
|
+
/** Key-value service provider: just below configuration, whose config-file read it depends on. */
|
|
21
|
+
export const KEY_VALUE_SERVICE_PRIORITY = 89;
|
|
22
|
+
|
|
23
|
+
/** Printer service provider. */
|
|
24
|
+
export const PRINTER_SERVICE_PRIORITY = 80;
|
|
25
|
+
|
|
26
|
+
/** Prompter service provider. */
|
|
27
|
+
export const PROMPTER_SERVICE_PRIORITY = 75;
|
|
28
|
+
|
|
29
|
+
/** Table generator service provider. */
|
|
30
|
+
export const TABLE_GENERATOR_SERVICE_PRIORITY = 70;
|
|
31
|
+
|
|
32
|
+
/** Argument prompter service provider: below the prompter it uses. */
|
|
33
|
+
export const ARGUMENT_PROMPTER_SERVICE_PRIORITY = 65;
|
|
34
|
+
|
|
35
|
+
/** Spawn service provider. */
|
|
36
|
+
export const SPAWN_SERVICE_PRIORITY = 58;
|
|
37
|
+
|
|
38
|
+
/** Fetch service provider. */
|
|
39
|
+
export const FETCH_SERVICE_PRIORITY = 57;
|
|
40
|
+
|
|
41
|
+
/** Upgrade service provider: below the spawn and fetch services it uses. */
|
|
42
|
+
export const UPGRADE_SERVICE_PRIORITY = 56;
|
|
43
|
+
|
|
44
|
+
/** Background startup task which refreshes the cached upgrade check result. */
|
|
45
|
+
export const UPGRADE_CHECK_STARTUP_TASK_PRIORITY = 56;
|
|
46
|
+
|
|
47
|
+
/** Image printer service provider. */
|
|
48
|
+
export const IMAGE_PRINTER_SERVICE_PRIORITY = 55;
|
|
49
|
+
|
|
50
|
+
/** Plugin service provider. */
|
|
51
|
+
export const PLUGIN_SERVICE_PRIORITY = 50;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Startup task which prompts to enable automatic upgrades and performs them. It runs below
|
|
55
|
+
* consumer startup tasks such as the banner (typically 45 or more) and above the completion
|
|
56
|
+
* prompt.
|
|
57
|
+
*/
|
|
58
|
+
export const AUTO_UPGRADE_STARTUP_TASK_PRIORITY = 10;
|
|
59
|
+
|
|
60
|
+
/** Completion service provider, whose `initService()` prompts to enable shell completion. */
|
|
61
|
+
export const COMPLETION_SERVICE_PRIORITY = 5;
|
|
62
|
+
|
|
63
|
+
/** Shutdown tasks which clean up in-flight work (spinners, progress bars, spawns, fetches). */
|
|
64
|
+
export const CLEANUP_SHUTDOWN_TASK_PRIORITY = 0;
|
|
65
|
+
|
|
66
|
+
/** Shutdown task which flushes key-value data to the config file, after all other cleanup. */
|
|
67
|
+
export const KEY_VALUE_FLUSH_SHUTDOWN_TASK_PRIORITY = -100;
|
package/src/runtime/runner.ts
CHANGED
|
@@ -36,6 +36,12 @@ import { sortByPriority } from "./lifecycle/PriorityTaskList.ts";
|
|
|
36
36
|
|
|
37
37
|
const logger = getLogger("runner");
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* The {@link RunResult} returned by {@link run}, with `exitRequested` set when a startup task ended
|
|
41
|
+
* the run early by returning an `exitRequest`.
|
|
42
|
+
*/
|
|
43
|
+
export type RunnerResult = RunResult & { readonly exitRequested?: true };
|
|
44
|
+
|
|
39
45
|
/**
|
|
40
46
|
* Wrap a {@link ServiceProvider}'s `initService()` call as a {@link StartupTask} so it can be
|
|
41
47
|
* merged with directly-registered {@link StartupTask}s (e.g. the banner task) into a single
|
|
@@ -560,6 +566,8 @@ async function findAndExecuteDefaultNonModifierCommand(
|
|
|
560
566
|
* - Run the task: awaited before continuing if its mode is "blocking" (the default, and how
|
|
561
567
|
* every ServiceProvider's initService() behaves); fired without awaiting if "background"
|
|
562
568
|
* (errors logged, not propagated).
|
|
569
|
+
* - Return immediately if a "blocking" task returned an exitRequest, using its runState and
|
|
570
|
+
* skipping the remaining tasks and the command.
|
|
563
571
|
* 2. Scan arguments for any GlobalModifierCommand clauses not provided by a StartupTask
|
|
564
572
|
* - For each discovered clause:
|
|
565
573
|
* - Set any argument defaults from ConfigurationServiceProvider
|
|
@@ -615,7 +623,7 @@ export async function run(
|
|
|
615
623
|
context: Context,
|
|
616
624
|
defaultCommand?: Command,
|
|
617
625
|
startupService?: DefaultStartupService,
|
|
618
|
-
): Promise<
|
|
626
|
+
): Promise<RunnerResult> {
|
|
619
627
|
if (defaultCommand && !isSubCommand(defaultCommand) && !isGlobalCommand(defaultCommand)) {
|
|
620
628
|
throw new Error(
|
|
621
629
|
"If a default command is provided, if must be a global command or sub-command!",
|
|
@@ -681,7 +689,14 @@ export async function run(
|
|
|
681
689
|
: context;
|
|
682
690
|
|
|
683
691
|
if ((task.mode ?? "blocking") === "blocking") {
|
|
684
|
-
await task.run(taskContext);
|
|
692
|
+
const outcome = await task.run(taskContext);
|
|
693
|
+
if (outcome?.exitRequest) {
|
|
694
|
+
logger.debug(
|
|
695
|
+
() =>
|
|
696
|
+
`Startup task '${task.id}' requested exit with run state ${outcome.exitRequest.runState}`,
|
|
697
|
+
);
|
|
698
|
+
return { runState: outcome.exitRequest.runState, exitRequested: true };
|
|
699
|
+
}
|
|
685
700
|
} else {
|
|
686
701
|
void task.run(taskContext).catch((error: unknown) => {
|
|
687
702
|
logger.debug(() => `Background startup task '${task.id}' failed: ${error}`);
|
|
@@ -16,16 +16,37 @@ export default class DefaultKeyValueService implements KeyValueService {
|
|
|
16
16
|
readonly #keyValueData: Map<string, ValueNode>;
|
|
17
17
|
#dirty = false;
|
|
18
18
|
readonly #secretService: DefaultSecretService | undefined;
|
|
19
|
+
readonly #flushToStorage: (() => Promise<void>) | undefined;
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
/**
|
|
22
|
+
* @param keyValueData the scope's data.
|
|
23
|
+
* @param secretService optional secret service for {@link Secret} values.
|
|
24
|
+
* @param flushToStorage optional callback which writes every scope's pending changes to storage
|
|
25
|
+
* and marks them clean, invoked by {@link flush}.
|
|
26
|
+
*/
|
|
27
|
+
constructor(
|
|
28
|
+
keyValueData: Map<string, ValueNode>,
|
|
29
|
+
secretService?: DefaultSecretService,
|
|
30
|
+
flushToStorage?: () => Promise<void>,
|
|
31
|
+
) {
|
|
21
32
|
this.#keyValueData = keyValueData;
|
|
22
33
|
this.#secretService = secretService;
|
|
34
|
+
this.#flushToStorage = flushToStorage;
|
|
23
35
|
}
|
|
24
36
|
|
|
25
37
|
public isDirty(): boolean {
|
|
26
38
|
return this.#dirty;
|
|
27
39
|
}
|
|
28
40
|
|
|
41
|
+
public markClean(): void {
|
|
42
|
+
this.#dirty = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public async flush(): Promise<void> {
|
|
46
|
+
await this.#flushToStorage?.();
|
|
47
|
+
this.#dirty = false;
|
|
48
|
+
}
|
|
49
|
+
|
|
29
50
|
public async get<T extends ValueNode = ValueNode>(key: string): Promise<T> {
|
|
30
51
|
const value = this.#keyValueData.get(key);
|
|
31
52
|
if (value === undefined) {
|
|
@@ -12,6 +12,7 @@ import DefaultSecretService from "./DefaultSecretService.ts";
|
|
|
12
12
|
import type ConfigurationServiceProvider from "./ConfigurationServiceProvider.ts";
|
|
13
13
|
import type { KeyValueServiceScopeType } from "./ConfigurationServiceProvider.ts";
|
|
14
14
|
import type DefaultContext from "../../runtime/DefaultContext.ts";
|
|
15
|
+
import { KEY_VALUE_FLUSH_SHUTDOWN_TASK_PRIORITY } from "../../runtime/lifecycle/priorities.ts";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Registered under {@link KEY_VALUE_SERVICE_ID} in the plain, undecorated {@link Context} purely
|
|
@@ -37,6 +38,10 @@ class UnreachableKeyValueService implements KeyValueService {
|
|
|
37
38
|
return Promise.reject(new Error(UnreachableKeyValueService.#message));
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
flush(): Promise<void> {
|
|
42
|
+
return Promise.reject(new Error(UnreachableKeyValueService.#message));
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
static readonly #message =
|
|
41
46
|
"KeyValueService accessed without a resolved scope - this should be unreachable";
|
|
42
47
|
}
|
|
@@ -188,7 +193,9 @@ export default class KeyValueServiceProvider implements ServiceProvider {
|
|
|
188
193
|
const secretService = this.secretServiceEnabled
|
|
189
194
|
? new DefaultSecretService(this.#cliConfigName!, `${scopeType}_${scopeKey}`)
|
|
190
195
|
: undefined;
|
|
191
|
-
const scopedService = new DefaultKeyValueService(keyValueData, secretService)
|
|
196
|
+
const scopedService = new DefaultKeyValueService(keyValueData, secretService, () =>
|
|
197
|
+
this.#flushDirtyScopes(),
|
|
198
|
+
);
|
|
192
199
|
scopedServices.set(scopeKey, scopedService);
|
|
193
200
|
return scopedService;
|
|
194
201
|
}
|
|
@@ -229,15 +236,21 @@ export default class KeyValueServiceProvider implements ServiceProvider {
|
|
|
229
236
|
|
|
230
237
|
/**
|
|
231
238
|
* Flush every scoped KeyValueService's data to the configuration file, if any of them report
|
|
232
|
-
* themselves dirty. Registered as a low-priority ShutdownTask (see
|
|
233
|
-
* runs once, after other shutdown cleanup, regardless of how long any
|
|
239
|
+
* themselves dirty, then mark them all clean. Registered as a low-priority ShutdownTask (see
|
|
240
|
+
* {@link initService}) so it runs once, after other shutdown cleanup, regardless of how long any
|
|
241
|
+
* scope was written to. Also backs {@link KeyValueService.flush} for every scope.
|
|
234
242
|
*/
|
|
235
243
|
async #flushDirtyScopes(): Promise<void> {
|
|
236
|
-
const
|
|
244
|
+
const scopedServices = [
|
|
237
245
|
...this.#commandScopedKeyValueServices.values(),
|
|
238
246
|
...this.#serviceScopedKeyValueServices.values(),
|
|
239
|
-
]
|
|
240
|
-
await this.#configurationServiceProvider.flushIfDirty(
|
|
247
|
+
];
|
|
248
|
+
await this.#configurationServiceProvider.flushIfDirty(
|
|
249
|
+
scopedServices.some((service) => service.isDirty()),
|
|
250
|
+
);
|
|
251
|
+
for (const service of scopedServices) {
|
|
252
|
+
service.markClean();
|
|
253
|
+
}
|
|
241
254
|
}
|
|
242
255
|
|
|
243
256
|
public async initService(context: Context): Promise<void> {
|
|
@@ -245,9 +258,7 @@ export default class KeyValueServiceProvider implements ServiceProvider {
|
|
|
245
258
|
const shutdownService = context.getServiceById(SHUTDOWN_SERVICE_ID) as ShutdownService;
|
|
246
259
|
shutdownService.registerTask({
|
|
247
260
|
id: `${KEY_VALUE_SERVICE_ID}-flush`,
|
|
248
|
-
|
|
249
|
-
// other shutdown cleanup (which defaults to priority 0).
|
|
250
|
-
priority: -100,
|
|
261
|
+
priority: KEY_VALUE_FLUSH_SHUTDOWN_TASK_PRIORITY,
|
|
251
262
|
run: () => this.#flushDirtyScopes(),
|
|
252
263
|
});
|
|
253
264
|
}
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
} from "@flowscripter/dynamic-cli-framework-api";
|
|
7
7
|
import { SHUTDOWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
8
8
|
import getLogger from "../../util/logger.ts";
|
|
9
|
+
import { CLEANUP_SHUTDOWN_TASK_PRIORITY } from "../../runtime/lifecycle/priorities.ts";
|
|
9
10
|
|
|
10
11
|
const logger = getLogger("DefaultFetchService");
|
|
11
12
|
|
|
@@ -38,7 +39,7 @@ export default class DefaultFetchService implements FetchService {
|
|
|
38
39
|
shutdownService.enterLongRunningMode();
|
|
39
40
|
shutdownService.registerTask({
|
|
40
41
|
id: `fetch:${input.toString()}`,
|
|
41
|
-
priority:
|
|
42
|
+
priority: CLEANUP_SHUTDOWN_TASK_PRIORITY,
|
|
42
43
|
run: async () => {
|
|
43
44
|
if (settled) {
|
|
44
45
|
return;
|
|
@@ -9,6 +9,7 @@ import NoHyperlinksCommand from "./command/NoHyperlinksCommand.ts";
|
|
|
9
9
|
import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
10
10
|
import type { ShutdownService } from "@flowscripter/dynamic-cli-framework-api";
|
|
11
11
|
import { SHUTDOWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
12
|
+
import { CLEANUP_SHUTDOWN_TASK_PRIORITY } from "../../runtime/lifecycle/priorities.ts";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Provides a {@link PrinterService}.
|
|
@@ -45,7 +46,7 @@ export default class PrinterServiceProvider implements ServiceProvider {
|
|
|
45
46
|
const shutdownService = context.getServiceById(SHUTDOWN_SERVICE_ID) as ShutdownService;
|
|
46
47
|
shutdownService.registerTask({
|
|
47
48
|
id: PRINTER_SERVICE_ID,
|
|
48
|
-
priority:
|
|
49
|
+
priority: CLEANUP_SHUTDOWN_TASK_PRIORITY,
|
|
49
50
|
run: async () => {
|
|
50
51
|
await this.printerService.hideSpinner();
|
|
51
52
|
await this.printerService.hideAllProgressBars();
|
|
@@ -2,7 +2,7 @@ import process from "node:process";
|
|
|
2
2
|
import type { ServiceInfo, ServiceProvider } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
3
|
import DefaultShutdownService from "./DefaultShutdownService.ts";
|
|
4
4
|
import type { ShutdownService } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
-
import { SHUTDOWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
+
import { RunState, SHUTDOWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
6
|
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
7
7
|
import getLogger from "../../util/logger.ts";
|
|
8
8
|
import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -37,7 +37,7 @@ export default class ShutdownServiceProvider implements ServiceProvider {
|
|
|
37
37
|
shutdownState.interruptCount++;
|
|
38
38
|
if (!shutdownState.longRunningMode || shutdownState.interruptCount >= 3) {
|
|
39
39
|
ShutdownServiceProvider.shutdown().then(() => {
|
|
40
|
-
process.exit(
|
|
40
|
+
process.exit(RunState.INTERRUPTED);
|
|
41
41
|
});
|
|
42
42
|
} else {
|
|
43
43
|
shutdownState.shutdownRequested = true;
|
|
@@ -46,7 +46,7 @@ export default class ShutdownServiceProvider implements ServiceProvider {
|
|
|
46
46
|
|
|
47
47
|
static onTerminate(): void {
|
|
48
48
|
ShutdownServiceProvider.shutdown().then(() => {
|
|
49
|
-
process.exit(
|
|
49
|
+
process.exit(RunState.TERMINATED);
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
} from "@flowscripter/dynamic-cli-framework-api";
|
|
10
10
|
import { PRINTER_SERVICE_ID, SHUTDOWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
11
11
|
import getLogger from "../../util/logger.ts";
|
|
12
|
+
import { CLEANUP_SHUTDOWN_TASK_PRIORITY } from "../../runtime/lifecycle/priorities.ts";
|
|
12
13
|
|
|
13
14
|
const logger = getLogger("DefaultSpawnService");
|
|
14
15
|
|
|
@@ -107,6 +108,7 @@ export default class DefaultSpawnService implements SpawnService {
|
|
|
107
108
|
try {
|
|
108
109
|
proc = Bun.spawn(resolveForPlatform(command), {
|
|
109
110
|
cwd: options.cwd,
|
|
111
|
+
env: options.env === undefined ? undefined : { ...process.env, ...options.env },
|
|
110
112
|
stdin: mode === "inherit" ? "inherit" : "ignore",
|
|
111
113
|
stdout: mode === "wrapped" ? "pipe" : mode === "ignore" ? "ignore" : "inherit",
|
|
112
114
|
stderr: mode === "wrapped" ? "pipe" : mode === "ignore" ? "ignore" : "inherit",
|
|
@@ -124,7 +126,7 @@ export default class DefaultSpawnService implements SpawnService {
|
|
|
124
126
|
|
|
125
127
|
shutdownService.registerTask({
|
|
126
128
|
id: `spawn:${command.join(" ")}`,
|
|
127
|
-
priority:
|
|
129
|
+
priority: CLEANUP_SHUTDOWN_TASK_PRIORITY,
|
|
128
130
|
run: async () => {
|
|
129
131
|
if (settled) {
|
|
130
132
|
return;
|