@flowscripter/dynamic-cli-framework 3.0.1 → 4.0.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 +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/src/cli/BaseCLI.d.ts +1 -0
- package/dist/src/cli/BaseCLI.d.ts.map +1 -1
- package/dist/src/cli/BaseCLI.js +8 -0
- package/dist/src/cli/BaseCLIFeatureOptions.d.ts +4 -0
- package/dist/src/cli/BaseCLIFeatureOptions.d.ts.map +1 -1
- package/dist/src/launcher.d.ts +0 -2
- package/dist/src/launcher.d.ts.map +1 -1
- package/dist/src/launcher.js +0 -20
- package/dist/src/service/completion/CompletionServiceProvider.js +4 -4
- package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts +19 -8
- package/dist/src/service/configuration/ConfigurationServiceProvider.d.ts.map +1 -1
- package/dist/src/service/configuration/ConfigurationServiceProvider.js +26 -33
- package/dist/src/service/configuration/DefaultKeyValueService.d.ts +6 -6
- package/dist/src/service/configuration/DefaultKeyValueService.d.ts.map +1 -1
- package/dist/src/service/configuration/DefaultKeyValueService.js +60 -19
- package/dist/src/service/configuration/resolveSecrets.d.ts +11 -0
- package/dist/src/service/configuration/resolveSecrets.d.ts.map +1 -0
- package/dist/src/service/configuration/resolveSecrets.js +31 -0
- package/dist/src/service/plugin/DefaultPluginService.d.ts +5 -2
- package/dist/src/service/plugin/DefaultPluginService.d.ts.map +1 -1
- package/dist/src/service/plugin/DefaultPluginService.js +21 -2
- package/dist/src/service/plugin/PluginServiceProvider.d.ts +17 -0
- package/dist/src/service/plugin/PluginServiceProvider.d.ts.map +1 -0
- package/dist/src/service/plugin/PluginServiceProvider.js +95 -0
- package/dist/src/service/upgrade/DefaultUpgradeService.d.ts.map +1 -1
- package/dist/src/service/upgrade/DefaultUpgradeService.js +12 -10
- package/dist/src/service/upgrade/UpgradeServiceProvider.js +4 -4
- package/index.ts +2 -7
- package/package.json +3 -3
- package/src/cli/BaseCLI.ts +17 -0
- package/src/cli/BaseCLIFeatureOptions.ts +7 -0
- package/src/launcher.ts +0 -35
- package/src/service/completion/CompletionServiceProvider.ts +4 -4
- package/src/service/configuration/ConfigurationServiceProvider.ts +36 -40
- package/src/service/configuration/DefaultKeyValueService.ts +76 -22
- package/src/service/configuration/resolveSecrets.ts +35 -0
- package/src/service/plugin/DefaultPluginService.ts +41 -3
- package/src/service/plugin/PluginServiceProvider.ts +142 -0
- package/src/service/upgrade/DefaultUpgradeService.ts +10 -10
- package/src/service/upgrade/UpgradeServiceProvider.ts +4 -4
- package/dist/src/cli/DynamicPluginRuntimeCLI.d.ts +0 -20
- package/dist/src/cli/DynamicPluginRuntimeCLI.d.ts.map +0 -1
- package/dist/src/cli/DynamicPluginRuntimeCLI.js +0 -50
- package/dist/src/service/plugin/DefaultPluginServiceProvider.d.ts +0 -15
- package/dist/src/service/plugin/DefaultPluginServiceProvider.d.ts.map +0 -1
- package/dist/src/service/plugin/DefaultPluginServiceProvider.js +0 -56
- package/src/cli/DynamicPluginRuntimeCLI.ts +0 -77
- package/src/service/plugin/DefaultPluginServiceProvider.ts +0 -82
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
ValueNode,
|
|
3
|
+
KeyValueService,
|
|
4
|
+
SettableValueNode,
|
|
5
|
+
} from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
+
import { Secret, SECRET_SENTINEL_PREFIX } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
7
|
import type DefaultSecretService from "./DefaultSecretService.ts";
|
|
8
|
+
import resolveSecrets from "./resolveSecrets.ts";
|
|
4
9
|
|
|
5
10
|
export default class DefaultKeyValueService implements KeyValueService {
|
|
6
|
-
#keyValueData: Map<string,
|
|
11
|
+
#keyValueData: Map<string, ValueNode> | undefined;
|
|
7
12
|
#dirty = false;
|
|
8
13
|
readonly #secretService: DefaultSecretService | undefined;
|
|
9
14
|
|
|
@@ -11,7 +16,7 @@ export default class DefaultKeyValueService implements KeyValueService {
|
|
|
11
16
|
this.#secretService = secretService;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
|
-
public setKeyValueData(keyValueData: Map<string,
|
|
19
|
+
public setKeyValueData(keyValueData: Map<string, ValueNode>) {
|
|
15
20
|
if (this.#keyValueData) {
|
|
16
21
|
throw new Error("Attempt to overwrite key-value data, it should be cleared first");
|
|
17
22
|
}
|
|
@@ -28,7 +33,7 @@ export default class DefaultKeyValueService implements KeyValueService {
|
|
|
28
33
|
return this.#dirty;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
public async
|
|
36
|
+
public async get<T extends ValueNode = ValueNode>(key: string): Promise<T> {
|
|
32
37
|
if (this.#keyValueData === undefined) {
|
|
33
38
|
throw new Error("Attempt to access undefined key-value data");
|
|
34
39
|
}
|
|
@@ -37,56 +42,105 @@ export default class DefaultKeyValueService implements KeyValueService {
|
|
|
37
42
|
throw new Error("Attempt to access unknown key");
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
const resolveSecret = async (bunSecretName: string): Promise<unknown> => {
|
|
41
46
|
if (!this.#secretService) {
|
|
42
47
|
throw new Error("Secret sentinel found but no secret service is available");
|
|
43
48
|
}
|
|
44
|
-
const bunSecretName = value.slice(SECRET_SENTINEL_PREFIX.length);
|
|
45
49
|
const secretValue = await this.#secretService.getSecret(bunSecretName);
|
|
46
50
|
if (secretValue === null) {
|
|
47
51
|
throw new Error(`Secret not found in OS secret store for key: '${key}'`);
|
|
48
52
|
}
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(secretValue);
|
|
55
|
+
} catch {
|
|
56
|
+
return secretValue;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
51
59
|
|
|
52
|
-
return value
|
|
60
|
+
return resolveSecrets(value, resolveSecret) as Promise<T>;
|
|
53
61
|
}
|
|
54
62
|
|
|
55
|
-
public
|
|
63
|
+
public has(key: string): Promise<boolean> {
|
|
56
64
|
if (this.#keyValueData === undefined) {
|
|
57
65
|
return Promise.reject(new Error("Attempt to access undefined key-value data"));
|
|
58
66
|
}
|
|
59
67
|
return Promise.resolve(this.#keyValueData.has(key));
|
|
60
68
|
}
|
|
61
69
|
|
|
62
|
-
public async
|
|
70
|
+
public async set(key: string, value: SettableValueNode): Promise<void> {
|
|
63
71
|
if (this.#keyValueData === undefined) {
|
|
64
72
|
throw new Error("Attempt to access undefined key-value data");
|
|
65
73
|
}
|
|
66
|
-
|
|
74
|
+
this.#keyValueData.set(key, await this.#storeSecrets(key, value, []));
|
|
75
|
+
this.#dirty = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async #storeSecrets(
|
|
79
|
+
key: string,
|
|
80
|
+
value: SettableValueNode,
|
|
81
|
+
path: Array<string>,
|
|
82
|
+
): Promise<ValueNode> {
|
|
83
|
+
if (value instanceof Secret) {
|
|
67
84
|
if (!this.#secretService) {
|
|
68
85
|
throw new Error("Attempt to set a secret but no secret service is available");
|
|
69
86
|
}
|
|
70
|
-
const
|
|
71
|
-
this.#
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
const secretName = [key, ...path].join("_");
|
|
88
|
+
const bunSecretName = await this.#secretService.setSecret(
|
|
89
|
+
secretName,
|
|
90
|
+
JSON.stringify(value.value),
|
|
91
|
+
);
|
|
92
|
+
return SECRET_SENTINEL_PREFIX + bunSecretName;
|
|
74
93
|
}
|
|
75
|
-
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
const result: Array<ValueNode> = [];
|
|
96
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
97
|
+
result.push(await this.#storeSecrets(key, value[i]!, [...path, String(i)]));
|
|
98
|
+
}
|
|
99
|
+
return result as ValueNode;
|
|
100
|
+
}
|
|
101
|
+
if (typeof value === "object" && value !== null) {
|
|
102
|
+
const result: Record<string, ValueNode> = {};
|
|
103
|
+
for (const [propertyName, propertyValue] of Object.entries(value)) {
|
|
104
|
+
result[propertyName] = await this.#storeSecrets(key, propertyValue, [
|
|
105
|
+
...path,
|
|
106
|
+
propertyName,
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
109
|
+
return result as ValueNode;
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
76
112
|
}
|
|
77
113
|
|
|
78
|
-
public async
|
|
114
|
+
public async delete(key: string): Promise<void> {
|
|
79
115
|
if (this.#keyValueData === undefined) {
|
|
80
116
|
throw new Error("Attempt to access undefined key-value data");
|
|
81
117
|
}
|
|
82
118
|
const value = this.#keyValueData.get(key);
|
|
83
|
-
if (value !== undefined
|
|
119
|
+
if (value !== undefined) {
|
|
120
|
+
await this.#deleteSecrets(value);
|
|
121
|
+
}
|
|
122
|
+
this.#keyValueData.delete(key);
|
|
123
|
+
this.#dirty = true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async #deleteSecrets(value: ValueNode): Promise<void> {
|
|
127
|
+
if (typeof value === "string" && value.startsWith(SECRET_SENTINEL_PREFIX)) {
|
|
84
128
|
if (this.#secretService) {
|
|
85
129
|
const bunSecretName = value.slice(SECRET_SENTINEL_PREFIX.length);
|
|
86
130
|
await this.#secretService.deleteSecret(bunSecretName);
|
|
87
131
|
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (Array.isArray(value)) {
|
|
135
|
+
for (const item of value) {
|
|
136
|
+
await this.#deleteSecrets(item);
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (typeof value === "object" && value !== null) {
|
|
141
|
+
for (const propertyValue of Object.values(value)) {
|
|
142
|
+
await this.#deleteSecrets(propertyValue as ValueNode);
|
|
143
|
+
}
|
|
88
144
|
}
|
|
89
|
-
this.#keyValueData.delete(key);
|
|
90
|
-
this.#dirty = true;
|
|
91
145
|
}
|
|
92
146
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { SECRET_SENTINEL_PREFIX } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recursively walk the provided value and resolve any string leaf - at any depth - which starts
|
|
5
|
+
* with {@link SECRET_SENTINEL_PREFIX}, via the provided resolver callback.
|
|
6
|
+
*
|
|
7
|
+
* @param value the value to walk (a JSON-serializable value or a `PopulatedValues` /
|
|
8
|
+
* `PopulatedValueType` tree).
|
|
9
|
+
* @param resolveSecret callback used to resolve a sentinel-prefixed string (with the prefix
|
|
10
|
+
* stripped) to its actual value. Should throw if the secret cannot be found.
|
|
11
|
+
*/
|
|
12
|
+
export default async function resolveSecrets<T>(
|
|
13
|
+
value: T,
|
|
14
|
+
resolveSecret: (sentinelValue: string) => Promise<unknown>,
|
|
15
|
+
): Promise<T> {
|
|
16
|
+
if (typeof value === "string" && value.startsWith(SECRET_SENTINEL_PREFIX)) {
|
|
17
|
+
const bunSecretName = value.slice(SECRET_SENTINEL_PREFIX.length);
|
|
18
|
+
return (await resolveSecret(bunSecretName)) as unknown as T;
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === "object" && value !== null) {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
const resolved = [];
|
|
23
|
+
for (const item of value) {
|
|
24
|
+
resolved.push(await resolveSecrets(item, resolveSecret));
|
|
25
|
+
}
|
|
26
|
+
return resolved as unknown as T;
|
|
27
|
+
}
|
|
28
|
+
const resolved: Record<string, unknown> = {};
|
|
29
|
+
for (const [key, val] of Object.entries(value)) {
|
|
30
|
+
resolved[key] = await resolveSecrets(val, resolveSecret);
|
|
31
|
+
}
|
|
32
|
+
return resolved as unknown as T;
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
@@ -1,15 +1,53 @@
|
|
|
1
1
|
import type { PluginService } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import type { KeyValueService } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
3
|
import type {
|
|
3
4
|
MarketplacePluginManager,
|
|
4
5
|
SearchQuery,
|
|
5
6
|
VersionedPluginDescriptor,
|
|
7
|
+
NpmjsPluginRepositoryConfig,
|
|
8
|
+
NpmPluginRepositoryConfig,
|
|
9
|
+
} from "@flowscripter/dynamic-plugin-framework";
|
|
10
|
+
import {
|
|
11
|
+
NpmPluginManager,
|
|
12
|
+
NpmjsPluginRepository,
|
|
13
|
+
NpmPluginRepository,
|
|
6
14
|
} from "@flowscripter/dynamic-plugin-framework";
|
|
7
15
|
|
|
8
16
|
export default class DefaultPluginService implements PluginService {
|
|
9
|
-
readonly #
|
|
17
|
+
readonly #defaultRemoteConfig: NpmjsPluginRepositoryConfig;
|
|
18
|
+
readonly #defaultLocalConfig: NpmPluginRepositoryConfig;
|
|
19
|
+
#pluginManager: MarketplacePluginManager;
|
|
20
|
+
|
|
21
|
+
constructor(remoteConfig: NpmjsPluginRepositoryConfig, localConfig: NpmPluginRepositoryConfig) {
|
|
22
|
+
this.#defaultRemoteConfig = remoteConfig;
|
|
23
|
+
this.#defaultLocalConfig = localConfig;
|
|
24
|
+
this.#pluginManager = new NpmPluginManager(
|
|
25
|
+
[new NpmjsPluginRepository(remoteConfig)],
|
|
26
|
+
new NpmPluginRepository(localConfig),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get pluginManager(): MarketplacePluginManager {
|
|
31
|
+
return this.#pluginManager;
|
|
32
|
+
}
|
|
10
33
|
|
|
11
|
-
|
|
12
|
-
|
|
34
|
+
async applyKeyValueOverrides(keyValueService: KeyValueService): Promise<void> {
|
|
35
|
+
let remotesConfig: NpmjsPluginRepositoryConfig[] = [this.#defaultRemoteConfig];
|
|
36
|
+
if (await keyValueService.has("remotes-config")) {
|
|
37
|
+
remotesConfig = (await keyValueService.get(
|
|
38
|
+
"remotes-config",
|
|
39
|
+
)) as unknown as NpmjsPluginRepositoryConfig[];
|
|
40
|
+
}
|
|
41
|
+
let localConfig: NpmPluginRepositoryConfig = this.#defaultLocalConfig;
|
|
42
|
+
if (await keyValueService.has("local-config")) {
|
|
43
|
+
localConfig = (await keyValueService.get(
|
|
44
|
+
"local-config",
|
|
45
|
+
)) as unknown as NpmPluginRepositoryConfig;
|
|
46
|
+
}
|
|
47
|
+
this.#pluginManager = new NpmPluginManager(
|
|
48
|
+
remotesConfig.map((config) => new NpmjsPluginRepository(config)),
|
|
49
|
+
new NpmPluginRepository(localConfig),
|
|
50
|
+
);
|
|
13
51
|
}
|
|
14
52
|
|
|
15
53
|
search(query: Readonly<SearchQuery>): AsyncIterable<Readonly<VersionedPluginDescriptor>> {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { ServiceInfo, ServiceProvider } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
+
import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
|
+
import type { PrinterService, SpawnService } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
+
import { PRINTER_SERVICE_ID, SPAWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
+
import type { FetchService } from "@flowscripter/dynamic-cli-framework-api";
|
|
7
|
+
import { FETCH_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
8
|
+
import { KEY_VALUE_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
9
|
+
import type { KeyValueService } from "@flowscripter/dynamic-cli-framework-api";
|
|
10
|
+
import { DYNAMIC_CLI_FRAMEWORK_COMMAND_FACTORY_EXTENSION_POINT } from "@flowscripter/dynamic-cli-framework-api";
|
|
11
|
+
import { DYNAMIC_CLI_FRAMEWORK_SERVICE_PROVIDER_FACTORY_EXTENSION_POINT } from "@flowscripter/dynamic-cli-framework-api";
|
|
12
|
+
import type { CommandFactory } from "@flowscripter/dynamic-cli-framework-api";
|
|
13
|
+
import type { ServiceProviderFactory } from "@flowscripter/dynamic-cli-framework-api";
|
|
14
|
+
import type {
|
|
15
|
+
FetchCapable,
|
|
16
|
+
NpmjsPluginRepositoryConfig,
|
|
17
|
+
NpmPluginRepositoryConfig,
|
|
18
|
+
SpawnCapable,
|
|
19
|
+
} from "@flowscripter/dynamic-plugin-framework";
|
|
20
|
+
import { PLUGIN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
21
|
+
import DefaultPluginService from "./DefaultPluginService.ts";
|
|
22
|
+
import { PluginGroupCommand } from "./command/PluginGroupCommand.ts";
|
|
23
|
+
import SpawnInterfaceAdapter, {
|
|
24
|
+
type SpawnInterfaceAdapterOptions,
|
|
25
|
+
} from "./SpawnInterfaceAdapter.ts";
|
|
26
|
+
import FetchInterfaceAdapter, {
|
|
27
|
+
type FetchInterfaceAdapterOptions,
|
|
28
|
+
} from "./FetchInterfaceAdapter.ts";
|
|
29
|
+
import DefaultCommandRegistry from "../../runtime/registry/DefaultCommandRegistry.ts";
|
|
30
|
+
import DefaultServiceProviderRegistry from "../../runtime/registry/DefaultServiceProviderRegistry.ts";
|
|
31
|
+
import type DefaultContext from "../../runtime/DefaultContext.ts";
|
|
32
|
+
import getLogger from "../../util/logger.ts";
|
|
33
|
+
|
|
34
|
+
const logger = getLogger("PluginServiceProvider");
|
|
35
|
+
|
|
36
|
+
export default class PluginServiceProvider implements ServiceProvider {
|
|
37
|
+
readonly serviceId = PLUGIN_SERVICE_ID;
|
|
38
|
+
readonly servicePriority: number;
|
|
39
|
+
readonly #remoteConfig: NpmjsPluginRepositoryConfig;
|
|
40
|
+
readonly #localConfig: NpmPluginRepositoryConfig;
|
|
41
|
+
readonly #commandRegistry: DefaultCommandRegistry;
|
|
42
|
+
readonly #serviceProviderRegistry: DefaultServiceProviderRegistry;
|
|
43
|
+
readonly #spawnInterfaceAdapterOptions: SpawnInterfaceAdapterOptions;
|
|
44
|
+
readonly #fetchInterfaceAdapterOptions: FetchInterfaceAdapterOptions;
|
|
45
|
+
#pluginService: DefaultPluginService | undefined;
|
|
46
|
+
#cliConfig: CLIConfig | undefined;
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
servicePriority: number,
|
|
50
|
+
remoteConfig: NpmjsPluginRepositoryConfig,
|
|
51
|
+
localConfig: NpmPluginRepositoryConfig,
|
|
52
|
+
commandRegistry: DefaultCommandRegistry,
|
|
53
|
+
serviceProviderRegistry: DefaultServiceProviderRegistry,
|
|
54
|
+
spawnInterfaceAdapterOptions: SpawnInterfaceAdapterOptions = {},
|
|
55
|
+
fetchInterfaceAdapterOptions: FetchInterfaceAdapterOptions = {},
|
|
56
|
+
) {
|
|
57
|
+
this.servicePriority = servicePriority;
|
|
58
|
+
this.#remoteConfig = remoteConfig;
|
|
59
|
+
this.#localConfig = localConfig;
|
|
60
|
+
this.#commandRegistry = commandRegistry;
|
|
61
|
+
this.#serviceProviderRegistry = serviceProviderRegistry;
|
|
62
|
+
this.#spawnInterfaceAdapterOptions = spawnInterfaceAdapterOptions;
|
|
63
|
+
this.#fetchInterfaceAdapterOptions = fetchInterfaceAdapterOptions;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getServiceInfo(cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
67
|
+
this.#cliConfig = cliConfig;
|
|
68
|
+
this.#pluginService = new DefaultPluginService(this.#remoteConfig, this.#localConfig);
|
|
69
|
+
return {
|
|
70
|
+
service: this.#pluginService,
|
|
71
|
+
commands: [new PluginGroupCommand()],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async initService(context: Context): Promise<void> {
|
|
76
|
+
const pluginService = this.#pluginService!;
|
|
77
|
+
|
|
78
|
+
if (context.doesServiceExist(KEY_VALUE_SERVICE_ID)) {
|
|
79
|
+
const keyValueService = context.getServiceById(KEY_VALUE_SERVICE_ID) as KeyValueService;
|
|
80
|
+
await pluginService.applyKeyValueOverrides(keyValueService);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const pluginManager = pluginService.pluginManager;
|
|
84
|
+
|
|
85
|
+
if (!context.doesServiceExist(SPAWN_SERVICE_ID)) {
|
|
86
|
+
logger.debug("SpawnService not available, plugin manager will spawn processes directly");
|
|
87
|
+
} else if (!("setSpawn" in pluginManager)) {
|
|
88
|
+
logger.debug("Plugin manager does not support SpawnCapable, skipping spawn injection");
|
|
89
|
+
} else {
|
|
90
|
+
const spawnService = context.getServiceById(SPAWN_SERVICE_ID) as SpawnService;
|
|
91
|
+
const printerService = context.getServiceById(PRINTER_SERVICE_ID) as PrinterService;
|
|
92
|
+
const adapter = new SpawnInterfaceAdapter(
|
|
93
|
+
spawnService,
|
|
94
|
+
printerService,
|
|
95
|
+
this.#spawnInterfaceAdapterOptions,
|
|
96
|
+
);
|
|
97
|
+
(pluginManager as unknown as SpawnCapable).setSpawn(adapter);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!context.doesServiceExist(FETCH_SERVICE_ID)) {
|
|
101
|
+
logger.debug("FetchService not available, plugin manager will fetch directly");
|
|
102
|
+
} else if (!("setFetch" in pluginManager)) {
|
|
103
|
+
logger.debug("Plugin manager does not support FetchCapable, skipping fetch injection");
|
|
104
|
+
} else {
|
|
105
|
+
const fetchService = context.getServiceById(FETCH_SERVICE_ID) as FetchService;
|
|
106
|
+
const adapter = new FetchInterfaceAdapter(fetchService, this.#fetchInterfaceAdapterOptions);
|
|
107
|
+
(pluginManager as unknown as FetchCapable).setFetch(adapter);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await pluginManager.registerExtensions(DYNAMIC_CLI_FRAMEWORK_COMMAND_FACTORY_EXTENSION_POINT);
|
|
111
|
+
await pluginManager.registerExtensions(
|
|
112
|
+
DYNAMIC_CLI_FRAMEWORK_SERVICE_PROVIDER_FACTORY_EXTENSION_POINT,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const commandFactoryExtensions = await pluginManager.getRegisteredExtensions(
|
|
116
|
+
DYNAMIC_CLI_FRAMEWORK_COMMAND_FACTORY_EXTENSION_POINT,
|
|
117
|
+
);
|
|
118
|
+
for (const ext of commandFactoryExtensions) {
|
|
119
|
+
const factory = (await pluginManager.instantiate(ext.extensionHandle)) as CommandFactory;
|
|
120
|
+
for (const command of factory.getCommands()) {
|
|
121
|
+
this.#commandRegistry.addCommand(command);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const spFactoryExtensions = await pluginManager.getRegisteredExtensions(
|
|
126
|
+
DYNAMIC_CLI_FRAMEWORK_SERVICE_PROVIDER_FACTORY_EXTENSION_POINT,
|
|
127
|
+
);
|
|
128
|
+
for (const ext of spFactoryExtensions) {
|
|
129
|
+
const factory = (await pluginManager.instantiate(
|
|
130
|
+
ext.extensionHandle,
|
|
131
|
+
)) as ServiceProviderFactory;
|
|
132
|
+
for (const sp of factory.getServiceProviders()) {
|
|
133
|
+
const info = await sp.getServiceInfo(this.#cliConfig!);
|
|
134
|
+
if (info.service) {
|
|
135
|
+
(context as DefaultContext).addServiceInstance(sp.serviceId, info.service);
|
|
136
|
+
}
|
|
137
|
+
info.commands.forEach((cmd) => this.#commandRegistry.addCommand(cmd, sp.serviceId));
|
|
138
|
+
this.#serviceProviderRegistry.addServiceProvider(sp);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -23,7 +23,9 @@ import getLogger from "../../util/logger.ts";
|
|
|
23
23
|
const logger = getLogger("DefaultUpgradeService");
|
|
24
24
|
|
|
25
25
|
// checkForUpgrade() runs opportunistically on every CLI invocation (via BannerServiceProvider),
|
|
26
|
-
// so
|
|
26
|
+
// so that opportunistic call is raced against this timeout so it never stalls CLI startup.
|
|
27
|
+
// A deliberate caller (waitForResult=true, e.g. the `upgrade` command) awaits checkForUpgrade()
|
|
28
|
+
// directly with no timeout, so its underlying network/spawn calls are never artificially cut off.
|
|
27
29
|
export const VERSION_CHECK_TIMEOUT_MS = 250;
|
|
28
30
|
|
|
29
31
|
function describeSpawnFailure(result: Extract<SpawnResult, { ok: false }>): string {
|
|
@@ -61,10 +63,11 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
61
63
|
public getUpgradeCheckResult(waitForResult = false): Promise<UpgradeCheckResult | undefined> {
|
|
62
64
|
if (!this.#upgradeCheckPromise) {
|
|
63
65
|
// Only cache a successful result. An `undefined` result can come from a transient
|
|
64
|
-
// failure (e.g. the opportunistic startup check
|
|
65
|
-
//
|
|
66
|
-
// caller (e.g. the `upgrade` command explicitly waiting via
|
|
67
|
-
// chance of a fresh attempt for the rest of this process's
|
|
66
|
+
// failure (e.g. the opportunistic startup check racing past VERSION_CHECK_TIMEOUT_MS
|
|
67
|
+
// before checkForUpgrade() itself resolves) - caching that would permanently deny a
|
|
68
|
+
// later, deliberate caller (e.g. the `upgrade` command explicitly waiting via
|
|
69
|
+
// waitForResult=true) any chance of a fresh attempt for the rest of this process's
|
|
70
|
+
// lifetime.
|
|
68
71
|
this.#upgradeCheckPromise = this.checkForUpgrade().then((result) => {
|
|
69
72
|
if (result === undefined) {
|
|
70
73
|
this.#upgradeCheckPromise = undefined;
|
|
@@ -220,7 +223,7 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
220
223
|
}
|
|
221
224
|
const result = await this.#spawnService.spawn(
|
|
222
225
|
["brew", "list", "--versions", this.#config.homebrew.formula],
|
|
223
|
-
{ mode: "ignore", longRunning: false
|
|
226
|
+
{ mode: "ignore", longRunning: false },
|
|
224
227
|
);
|
|
225
228
|
return result.ok;
|
|
226
229
|
}
|
|
@@ -231,7 +234,7 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
231
234
|
}
|
|
232
235
|
const result = await this.#spawnService.spawn(
|
|
233
236
|
["winget", "list", "--id", this.#config.winget.packageId],
|
|
234
|
-
{ mode: "ignore", longRunning: false
|
|
237
|
+
{ mode: "ignore", longRunning: false },
|
|
235
238
|
);
|
|
236
239
|
return result.ok;
|
|
237
240
|
}
|
|
@@ -262,7 +265,6 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
262
265
|
try {
|
|
263
266
|
const response = await this.#fetchService.fetch(
|
|
264
267
|
`https://api.github.com/repos/${owner}/${repo}/releases/latest`,
|
|
265
|
-
{ timeoutMs: VERSION_CHECK_TIMEOUT_MS },
|
|
266
268
|
);
|
|
267
269
|
if (!response.ok) {
|
|
268
270
|
return undefined;
|
|
@@ -287,7 +289,6 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
287
289
|
try {
|
|
288
290
|
const response = await this.#fetchService.fetch(
|
|
289
291
|
`https://raw.githubusercontent.com/${tapOwner}/homebrew-${tapName}/main/${formula}.rb`,
|
|
290
|
-
{ timeoutMs: VERSION_CHECK_TIMEOUT_MS },
|
|
291
292
|
);
|
|
292
293
|
if (!response.ok) {
|
|
293
294
|
return undefined;
|
|
@@ -310,7 +311,6 @@ export default class DefaultUpgradeService implements UpgradeService {
|
|
|
310
311
|
{
|
|
311
312
|
mode: "wrapped",
|
|
312
313
|
longRunning: false,
|
|
313
|
-
timeoutMs: VERSION_CHECK_TIMEOUT_MS,
|
|
314
314
|
onOutput: (line) => lines.push(line),
|
|
315
315
|
},
|
|
316
316
|
);
|
|
@@ -71,8 +71,8 @@ export default class UpgradeServiceProvider implements ServiceProvider {
|
|
|
71
71
|
|
|
72
72
|
const keyValueService = context.getServiceById(KEY_VALUE_SERVICE_ID) as KeyValueService;
|
|
73
73
|
|
|
74
|
-
if (await keyValueService.
|
|
75
|
-
const status = await keyValueService.
|
|
74
|
+
if (await keyValueService.has("upgrade-status")) {
|
|
75
|
+
const status = await keyValueService.get("upgrade-status");
|
|
76
76
|
if (status === "declined") {
|
|
77
77
|
logger.debug(() => "Auto-upgrade previously declined, skipping");
|
|
78
78
|
return;
|
|
@@ -120,11 +120,11 @@ export default class UpgradeServiceProvider implements ServiceProvider {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
if (enableResult.value !== true) {
|
|
123
|
-
await keyValueService.
|
|
123
|
+
await keyValueService.set("upgrade-status", "declined");
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
await keyValueService.
|
|
127
|
+
await keyValueService.set("upgrade-status", "enabled");
|
|
128
128
|
await this.#checkAndUpgrade(context, upgradeService, cliConfig);
|
|
129
129
|
}
|
|
130
130
|
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { MarketplacePluginManager } from "@flowscripter/dynamic-plugin-framework";
|
|
2
|
-
import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
-
import type BaseCLIFeatureOptions from "./BaseCLIFeatureOptions.ts";
|
|
4
|
-
import type { RunResult } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
-
import DefaultRuntimeCLI from "./DefaultRuntimeCLI.ts";
|
|
6
|
-
/**
|
|
7
|
-
* Extension of {@link DefaultRuntimeCLI} that discovers and loads installed plugins before running.
|
|
8
|
-
*
|
|
9
|
-
* On each invocation of {@link run}, all locally installed plugins are discovered via the
|
|
10
|
-
* {@link MarketplacePluginManager}. Commands provided by {@link CommandFactory} extensions and
|
|
11
|
-
* service providers provided by {@link ServiceProviderFactory} extensions are registered before
|
|
12
|
-
* the runner starts, so they participate in the full initialization lifecycle.
|
|
13
|
-
*/
|
|
14
|
-
export default class DynamicPluginRuntimeCLI extends DefaultRuntimeCLI {
|
|
15
|
-
#private;
|
|
16
|
-
constructor(cliConfig: CLIConfig, pluginManager: MarketplacePluginManager, options?: BaseCLIFeatureOptions);
|
|
17
|
-
run(): Promise<RunResult>;
|
|
18
|
-
loadPlugins(): Promise<void>;
|
|
19
|
-
}
|
|
20
|
-
//# sourceMappingURL=DynamicPluginRuntimeCLI.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DynamicPluginRuntimeCLI.d.ts","sourceRoot":"","sources":["../../../src/cli/DynamicPluginRuntimeCLI.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAC;AACvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,qBAAqB,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,iBAAiB,MAAM,wBAAwB,CAAC;AAQvD;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,OAAO,uBAAwB,SAAQ,iBAAiB;;gBAIlE,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,wBAAwB,EACvC,OAAO,CAAC,EAAE,qBAAqB;IAOlB,GAAG,IAAI,OAAO,CAAC,SAAS,CAAC;IAUlC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;CAgCnC"}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { RunState } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
-
import DefaultRuntimeCLI from "./DefaultRuntimeCLI.js";
|
|
3
|
-
import DefaultPluginServiceProvider from "../service/plugin/DefaultPluginServiceProvider.js";
|
|
4
|
-
import { DYNAMIC_CLI_FRAMEWORK_COMMAND_FACTORY_EXTENSION_POINT } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
-
import { DYNAMIC_CLI_FRAMEWORK_SERVICE_PROVIDER_FACTORY_EXTENSION_POINT } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
-
import process from "node:process";
|
|
7
|
-
/**
|
|
8
|
-
* Extension of {@link DefaultRuntimeCLI} that discovers and loads installed plugins before running.
|
|
9
|
-
*
|
|
10
|
-
* On each invocation of {@link run}, all locally installed plugins are discovered via the
|
|
11
|
-
* {@link MarketplacePluginManager}. Commands provided by {@link CommandFactory} extensions and
|
|
12
|
-
* service providers provided by {@link ServiceProviderFactory} extensions are registered before
|
|
13
|
-
* the runner starts, so they participate in the full initialization lifecycle.
|
|
14
|
-
*/
|
|
15
|
-
export default class DynamicPluginRuntimeCLI extends DefaultRuntimeCLI {
|
|
16
|
-
#pluginManager;
|
|
17
|
-
constructor(cliConfig, pluginManager, options) {
|
|
18
|
-
super(cliConfig, options);
|
|
19
|
-
this.#pluginManager = pluginManager;
|
|
20
|
-
this.addServiceProvider(new DefaultPluginServiceProvider(pluginManager));
|
|
21
|
-
}
|
|
22
|
-
async run() {
|
|
23
|
-
try {
|
|
24
|
-
await this.loadPlugins();
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
console.error(err);
|
|
28
|
-
process.exit(RunState.RUNTIME_ERROR);
|
|
29
|
-
}
|
|
30
|
-
return super.run();
|
|
31
|
-
}
|
|
32
|
-
async loadPlugins() {
|
|
33
|
-
await this.#pluginManager.registerExtensions(DYNAMIC_CLI_FRAMEWORK_COMMAND_FACTORY_EXTENSION_POINT);
|
|
34
|
-
await this.#pluginManager.registerExtensions(DYNAMIC_CLI_FRAMEWORK_SERVICE_PROVIDER_FACTORY_EXTENSION_POINT);
|
|
35
|
-
const commandFactoryExtensions = await this.#pluginManager.getRegisteredExtensions(DYNAMIC_CLI_FRAMEWORK_COMMAND_FACTORY_EXTENSION_POINT);
|
|
36
|
-
for (const ext of commandFactoryExtensions) {
|
|
37
|
-
const factory = (await this.#pluginManager.instantiate(ext.extensionHandle));
|
|
38
|
-
for (const command of factory.getCommands()) {
|
|
39
|
-
this.addCommand(command);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const spFactoryExtensions = await this.#pluginManager.getRegisteredExtensions(DYNAMIC_CLI_FRAMEWORK_SERVICE_PROVIDER_FACTORY_EXTENSION_POINT);
|
|
43
|
-
for (const ext of spFactoryExtensions) {
|
|
44
|
-
const factory = (await this.#pluginManager.instantiate(ext.extensionHandle));
|
|
45
|
-
for (const sp of factory.getServiceProviders()) {
|
|
46
|
-
this.addServiceProvider(sp);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ServiceInfo, ServiceProvider } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
-
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
-
import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
|
-
import type { MarketplacePluginManager } from "@flowscripter/dynamic-plugin-framework";
|
|
5
|
-
import { type SpawnInterfaceAdapterOptions } from "./SpawnInterfaceAdapter.ts";
|
|
6
|
-
import { type FetchInterfaceAdapterOptions } from "./FetchInterfaceAdapter.ts";
|
|
7
|
-
export default class DefaultPluginServiceProvider implements ServiceProvider {
|
|
8
|
-
#private;
|
|
9
|
-
readonly serviceId = "PLUGIN_SERVICE";
|
|
10
|
-
readonly servicePriority: number;
|
|
11
|
-
constructor(pluginManager: MarketplacePluginManager, servicePriority?: number, spawnInterfaceAdapterOptions?: SpawnInterfaceAdapterOptions, fetchInterfaceAdapterOptions?: FetchInterfaceAdapterOptions);
|
|
12
|
-
getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo>;
|
|
13
|
-
initService(context: Context): Promise<void>;
|
|
14
|
-
}
|
|
15
|
-
//# sourceMappingURL=DefaultPluginServiceProvider.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultPluginServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/plugin/DefaultPluginServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKzE,OAAO,KAAK,EAEV,wBAAwB,EAEzB,MAAM,wCAAwC,CAAC;AAIhD,OAA8B,EAC5B,KAAK,4BAA4B,EAClC,MAAM,4BAA4B,CAAC;AACpC,OAA8B,EAC5B,KAAK,4BAA4B,EAClC,MAAM,4BAA4B,CAAC;AAKpC,MAAM,CAAC,OAAO,OAAO,4BAA6B,YAAW,eAAe;;IAC1E,QAAQ,CAAC,SAAS,oBAAqB;IACvC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;gBAO/B,aAAa,EAAE,wBAAwB,EACvC,eAAe,SAAK,EACpB,4BAA4B,GAAE,4BAAiC,EAC/D,4BAA4B,GAAE,4BAAiC;IAQ3D,cAAc,CAAC,UAAU,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IAQjE,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CA4B7C"}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { PRINTER_SERVICE_ID, SPAWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
-
import { FETCH_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
-
import { PLUGIN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
|
-
import DefaultPluginService from "./DefaultPluginService.js";
|
|
5
|
-
import { PluginGroupCommand } from "./command/PluginGroupCommand.js";
|
|
6
|
-
import SpawnInterfaceAdapter, {} from "./SpawnInterfaceAdapter.js";
|
|
7
|
-
import FetchInterfaceAdapter, {} from "./FetchInterfaceAdapter.js";
|
|
8
|
-
import getLogger from "../../util/logger.js";
|
|
9
|
-
const logger = getLogger("DefaultPluginServiceProvider");
|
|
10
|
-
export default class DefaultPluginServiceProvider {
|
|
11
|
-
serviceId = PLUGIN_SERVICE_ID;
|
|
12
|
-
servicePriority;
|
|
13
|
-
#pluginManager;
|
|
14
|
-
#spawnInterfaceAdapterOptions;
|
|
15
|
-
#fetchInterfaceAdapterOptions;
|
|
16
|
-
#pluginService;
|
|
17
|
-
constructor(pluginManager, servicePriority = 50, spawnInterfaceAdapterOptions = {}, fetchInterfaceAdapterOptions = {}) {
|
|
18
|
-
this.#pluginManager = pluginManager;
|
|
19
|
-
this.servicePriority = servicePriority;
|
|
20
|
-
this.#spawnInterfaceAdapterOptions = spawnInterfaceAdapterOptions;
|
|
21
|
-
this.#fetchInterfaceAdapterOptions = fetchInterfaceAdapterOptions;
|
|
22
|
-
}
|
|
23
|
-
async getServiceInfo(_cliConfig) {
|
|
24
|
-
this.#pluginService = new DefaultPluginService(this.#pluginManager);
|
|
25
|
-
return {
|
|
26
|
-
service: this.#pluginService,
|
|
27
|
-
commands: [new PluginGroupCommand()],
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
initService(context) {
|
|
31
|
-
if (!context.doesServiceExist(SPAWN_SERVICE_ID)) {
|
|
32
|
-
logger.debug("SpawnService not available, plugin manager will spawn processes directly");
|
|
33
|
-
}
|
|
34
|
-
else if (!("setSpawn" in this.#pluginManager)) {
|
|
35
|
-
logger.debug("Plugin manager does not support SpawnCapable, skipping spawn injection");
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
const spawnService = context.getServiceById(SPAWN_SERVICE_ID);
|
|
39
|
-
const printerService = context.getServiceById(PRINTER_SERVICE_ID);
|
|
40
|
-
const adapter = new SpawnInterfaceAdapter(spawnService, printerService, this.#spawnInterfaceAdapterOptions);
|
|
41
|
-
this.#pluginManager.setSpawn(adapter);
|
|
42
|
-
}
|
|
43
|
-
if (!context.doesServiceExist(FETCH_SERVICE_ID)) {
|
|
44
|
-
logger.debug("FetchService not available, plugin manager will fetch directly");
|
|
45
|
-
}
|
|
46
|
-
else if (!("setFetch" in this.#pluginManager)) {
|
|
47
|
-
logger.debug("Plugin manager does not support FetchCapable, skipping fetch injection");
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
const fetchService = context.getServiceById(FETCH_SERVICE_ID);
|
|
51
|
-
const adapter = new FetchInterfaceAdapter(fetchService, this.#fetchInterfaceAdapterOptions);
|
|
52
|
-
this.#pluginManager.setFetch(adapter);
|
|
53
|
-
}
|
|
54
|
-
return Promise.resolve();
|
|
55
|
-
}
|
|
56
|
-
}
|