@gmickel/gno 1.23.0 → 1.24.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.
@@ -0,0 +1,324 @@
1
+ import type {
2
+ SetupConnectorCompositionDeps,
3
+ SetupConnectorDefinition,
4
+ SetupConnectorResult,
5
+ SetupConnectorState,
6
+ } from "../../core/setup-activation";
7
+ import type { SqliteAdapter } from "../../store/sqlite/adapter";
8
+ import type {
9
+ SetupCommandOptions,
10
+ SetupCommandOutcome,
11
+ SetupCommandResult,
12
+ } from "./setup";
13
+
14
+ import { getIndexDbPath } from "../../app/constants";
15
+ import { getConfigPaths, loadConfig, toAbsolutePath } from "../../config";
16
+ import {
17
+ composeSetupConnectors,
18
+ SETUP_ACTIVATION_SCHEMA_VERSION,
19
+ unavailableSetupConnectorComposition,
20
+ } from "../../core/setup-activation";
21
+ import {
22
+ getConnectorDefinition,
23
+ getConnectorStatuses,
24
+ installConnector,
25
+ verifyInstalledConnector,
26
+ } from "../../serve/connectors";
27
+ import { SqliteAdapter as DefaultSqliteAdapter } from "../../store/sqlite/adapter";
28
+ import {
29
+ formatSetupResult,
30
+ lexicalSuccessIsProven,
31
+ SETUP_COMMAND_SCHEMA_VERSION,
32
+ setup,
33
+ } from "./setup";
34
+
35
+ export interface SetupActivationResult {
36
+ schemaVersion: typeof SETUP_ACTIVATION_SCHEMA_VERSION;
37
+ status: "completed" | "completed_with_actions" | "failed";
38
+ setup: SetupCommandResult;
39
+ connectors: SetupConnectorResult[];
40
+ }
41
+
42
+ export type SetupOutputResult = SetupCommandResult | SetupActivationResult;
43
+
44
+ export interface SetupOutputOutcome {
45
+ result: SetupOutputResult;
46
+ exitCode: 0 | 1 | 2;
47
+ }
48
+
49
+ export interface SetupProfileAdvisoryInput {
50
+ folder: string;
51
+ collection: string;
52
+ }
53
+
54
+ export interface SetupActivationCommandOptions extends SetupCommandOptions {
55
+ connectorIds?: string[];
56
+ connectorWorkspace?: { cwd?: string; homeDir?: string };
57
+ connectorDeps?: SetupConnectorCompositionDeps;
58
+ createActivationStore?: () => SqliteAdapter;
59
+ discoverProfileAdvisory?: (
60
+ input: SetupProfileAdvisoryInput
61
+ ) => Promise<unknown>;
62
+ }
63
+
64
+ function dedupeConnectorIds(connectorIds: string[]): string[] {
65
+ return [...new Set(connectorIds)];
66
+ }
67
+
68
+ function connectorStateFromStatus(
69
+ status: Awaited<ReturnType<typeof getConnectorStatuses>>[number]
70
+ ): SetupConnectorState {
71
+ return {
72
+ id: status.id,
73
+ kind: status.installKind,
74
+ target: status.target,
75
+ scope: status.scope,
76
+ installed: status.installed,
77
+ configurationError: Boolean(status.error),
78
+ };
79
+ }
80
+
81
+ function connectorDefinitions(
82
+ connectorIds: string[]
83
+ ): SetupConnectorDefinition[] | null {
84
+ const definitions: SetupConnectorDefinition[] = [];
85
+ for (const connectorId of connectorIds) {
86
+ const definition = getConnectorDefinition(connectorId);
87
+ if (!definition) {
88
+ return null;
89
+ }
90
+ definitions.push(definition);
91
+ }
92
+ return definitions;
93
+ }
94
+
95
+ function invalidConnectorOutcome(connectorIds: string[]): SetupCommandOutcome {
96
+ const unknown = connectorIds.find(
97
+ (connectorId) => getConnectorDefinition(connectorId) === null
98
+ );
99
+ return {
100
+ result: {
101
+ schemaVersion: SETUP_COMMAND_SCHEMA_VERSION,
102
+ status: "failed",
103
+ lexical: {
104
+ receipt: null,
105
+ error: {
106
+ code: "invalid_connector",
107
+ message: `Unknown connector: ${unknown ?? ""}`.trim(),
108
+ remediation:
109
+ "Run `gno setup --help` and pass one documented connector ID.",
110
+ },
111
+ },
112
+ semantic: null,
113
+ },
114
+ exitCode: 1,
115
+ };
116
+ }
117
+
118
+ function failedSetupActivationOutcome(
119
+ setupOutcome: SetupCommandOutcome
120
+ ): SetupOutputOutcome {
121
+ return {
122
+ result: {
123
+ schemaVersion: SETUP_ACTIVATION_SCHEMA_VERSION,
124
+ status: "failed",
125
+ setup: setupOutcome.result,
126
+ connectors: [],
127
+ },
128
+ exitCode: setupOutcome.exitCode,
129
+ };
130
+ }
131
+
132
+ function defaultConnectorDeps(
133
+ workspace: { cwd?: string; homeDir?: string } | undefined
134
+ ): SetupConnectorCompositionDeps {
135
+ return {
136
+ getStates: async () =>
137
+ (await getConnectorStatuses(workspace)).map(connectorStateFromStatus),
138
+ install: async (connectorId, context) =>
139
+ connectorStateFromStatus(
140
+ await installConnector(
141
+ connectorId,
142
+ { reinstall: false },
143
+ {
144
+ ...workspace,
145
+ indexName: context.indexName,
146
+ configPath: context.configPath,
147
+ }
148
+ )
149
+ ),
150
+ verify: async (connectorId, store, collection) =>
151
+ verifyInstalledConnector(
152
+ connectorId,
153
+ store,
154
+ collection,
155
+ undefined,
156
+ workspace
157
+ ),
158
+ };
159
+ }
160
+
161
+ async function discoverProfileAdvisory(
162
+ discover: SetupActivationCommandOptions["discoverProfileAdvisory"],
163
+ input: SetupProfileAdvisoryInput
164
+ ): Promise<void> {
165
+ if (!discover) {
166
+ return;
167
+ }
168
+ try {
169
+ await discover(input);
170
+ } catch {
171
+ // Advisory discovery cannot mutate or fail verified setup.
172
+ }
173
+ }
174
+
175
+ function withUnavailableConnectors(
176
+ setupResult: SetupCommandResult,
177
+ definitions: SetupConnectorDefinition[]
178
+ ): SetupOutputOutcome {
179
+ const unavailable = unavailableSetupConnectorComposition(definitions);
180
+ return {
181
+ result: {
182
+ schemaVersion: SETUP_ACTIVATION_SCHEMA_VERSION,
183
+ status: unavailable.status,
184
+ setup: setupResult,
185
+ connectors: unavailable.connectors,
186
+ },
187
+ exitCode: 0,
188
+ };
189
+ }
190
+
191
+ async function closeActivationStore(
192
+ store: SqliteAdapter | null
193
+ ): Promise<void> {
194
+ if (!store) {
195
+ return;
196
+ }
197
+ try {
198
+ await store.close();
199
+ } catch {
200
+ // Connector cleanup cannot replace proven lexical success.
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Add opt-in connector onboarding beside the unchanged setup result.
206
+ * No-connector calls retain setup-command-result@1.0; connector-mode failures
207
+ * wrap the unchanged failed setup result without running connector actions.
208
+ */
209
+ export async function setupWithActivation(
210
+ options: SetupActivationCommandOptions
211
+ ): Promise<SetupOutputOutcome> {
212
+ const requestedIds = dedupeConnectorIds(options.connectorIds ?? []);
213
+ const definitions = connectorDefinitions(requestedIds);
214
+ if (!definitions) {
215
+ return failedSetupActivationOutcome(invalidConnectorOutcome(requestedIds));
216
+ }
217
+
218
+ const {
219
+ connectorIds: _connectorIds,
220
+ connectorWorkspace: _connectorWorkspace,
221
+ connectorDeps: _connectorDeps,
222
+ createActivationStore: _createActivationStore,
223
+ discoverProfileAdvisory: _discoverProfileAdvisory,
224
+ ...setupOptions
225
+ } = options;
226
+ const setupOutcome = await setup(setupOptions);
227
+ if (
228
+ setupOutcome.exitCode !== 0 ||
229
+ setupOutcome.result.status !== "completed"
230
+ ) {
231
+ return definitions.length > 0
232
+ ? failedSetupActivationOutcome(setupOutcome)
233
+ : setupOutcome;
234
+ }
235
+
236
+ const lexicalReceipt = setupOutcome.result.lexical.receipt;
237
+ const collection = lexicalReceipt?.collection.name;
238
+ if (
239
+ !lexicalReceipt ||
240
+ !collection ||
241
+ !lexicalSuccessIsProven(lexicalReceipt)
242
+ ) {
243
+ return setupOutcome;
244
+ }
245
+
246
+ await discoverProfileAdvisory(options.discoverProfileAdvisory, {
247
+ folder: lexicalReceipt.input.folder,
248
+ collection,
249
+ });
250
+ if (definitions.length === 0) {
251
+ return setupOutcome;
252
+ }
253
+
254
+ let store: SqliteAdapter | null = null;
255
+ try {
256
+ const paths = getConfigPaths();
257
+ const configPath = toAbsolutePath(options.configPath ?? paths.configFile);
258
+ const indexName = options.indexName ?? "default";
259
+ const configResult = await loadConfig(configPath);
260
+ if (!configResult.ok) {
261
+ return withUnavailableConnectors(setupOutcome.result, definitions);
262
+ }
263
+
264
+ store =
265
+ options.createActivationStore?.() ??
266
+ (new DefaultSqliteAdapter() as SqliteAdapter);
267
+ store.setConfigPath(configPath);
268
+ const opened = await store.open(
269
+ getIndexDbPath(indexName),
270
+ configResult.value.ftsTokenizer
271
+ );
272
+ if (!opened.ok) {
273
+ return withUnavailableConnectors(setupOutcome.result, definitions);
274
+ }
275
+
276
+ const composition = await composeSetupConnectors({
277
+ connectorIds: requestedIds,
278
+ definitions,
279
+ collection,
280
+ store,
281
+ installContext: { indexName, configPath },
282
+ deps:
283
+ options.connectorDeps ??
284
+ defaultConnectorDeps(options.connectorWorkspace),
285
+ });
286
+ return {
287
+ result: {
288
+ schemaVersion: SETUP_ACTIVATION_SCHEMA_VERSION,
289
+ status: composition.status,
290
+ setup: setupOutcome.result,
291
+ connectors: composition.connectors,
292
+ },
293
+ exitCode: 0,
294
+ };
295
+ } catch {
296
+ return withUnavailableConnectors(setupOutcome.result, definitions);
297
+ } finally {
298
+ await closeActivationStore(store);
299
+ }
300
+ }
301
+
302
+ function isSetupActivationResult(
303
+ result: SetupOutputResult
304
+ ): result is SetupActivationResult {
305
+ return "setup" in result;
306
+ }
307
+
308
+ export function formatSetupOutputResult(
309
+ result: SetupOutputResult,
310
+ options: { json: boolean }
311
+ ): string {
312
+ if (options.json) {
313
+ return JSON.stringify(result, null, 2);
314
+ }
315
+ if (!isSetupActivationResult(result)) {
316
+ return formatSetupResult(result, options);
317
+ }
318
+ const setupOutput = formatSetupResult(result.setup, options);
319
+ const connectorOutput = result.connectors.map(
320
+ (connector) =>
321
+ `connector=${connector.connectorId} installation=${connector.installation} verification=${connector.verification} code=${connector.code} remediation=${connector.remediation}`
322
+ );
323
+ return [setupOutput, ...connectorOutput].join("\n");
324
+ }