@gaia-ai/gaia 0.11.2 → 0.11.4
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/src/config.d.ts +14 -0
- package/dist/src/config.js +251 -0
- package/dist/src/host.js +2 -2
- package/package.json +20 -20
- package/dist/src/upgrade.d.ts +0 -48
- package/dist/src/upgrade.js +0 -121
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ConfigReport, GaiaCommandHost } from '@gaia-ai/core';
|
|
2
|
+
import { type Command } from 'commander';
|
|
3
|
+
export interface ConfigCommandDeps {
|
|
4
|
+
cwd?: string;
|
|
5
|
+
home?: string;
|
|
6
|
+
isTTY?: boolean;
|
|
7
|
+
env?: NodeJS.ProcessEnv;
|
|
8
|
+
host?: GaiaCommandHost;
|
|
9
|
+
stdout?: (text: string) => void;
|
|
10
|
+
prompt?: (label: string, secret?: boolean) => Promise<string>;
|
|
11
|
+
stateDir?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function renderConfigReport(report: ConfigReport, dryRun?: boolean): string;
|
|
14
|
+
export declare function registerConfig(program: Command, deps?: ConfigCommandDeps): void;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { homedir, hostname } from 'node:os';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { applyConfigPlan, planConfig, } from '@gaia-ai/conductor/config';
|
|
5
|
+
import { Option } from 'commander';
|
|
6
|
+
/** Terminal-only input. Secret characters are never echoed. */
|
|
7
|
+
async function promptInput(label, secret = false) {
|
|
8
|
+
const rl = createInterface({
|
|
9
|
+
input: process.stdin,
|
|
10
|
+
output: process.stderr,
|
|
11
|
+
terminal: true,
|
|
12
|
+
});
|
|
13
|
+
const terminal = rl;
|
|
14
|
+
let muted = false;
|
|
15
|
+
terminal._writeToOutput = (text) => {
|
|
16
|
+
if (!muted)
|
|
17
|
+
process.stderr.write(text);
|
|
18
|
+
};
|
|
19
|
+
try {
|
|
20
|
+
return await new Promise((resolve, reject) => {
|
|
21
|
+
rl.once('SIGINT', () => reject(new Error('cancelled')));
|
|
22
|
+
rl.question(`${label}: `, (answer) => {
|
|
23
|
+
if (secret)
|
|
24
|
+
process.stderr.write('\n');
|
|
25
|
+
resolve(answer.trim());
|
|
26
|
+
});
|
|
27
|
+
muted = secret;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
rl.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function renderConfigReport(report, dryRun = false) {
|
|
35
|
+
const lines = [
|
|
36
|
+
`gaia config ${report.operation}: ${report.result}${dryRun ? ' (dry run; no files written)' : ''}`,
|
|
37
|
+
`Scope: ${report.resolved_scopes.join(', ')}`,
|
|
38
|
+
`Machine root: ${report.roots.machine}`,
|
|
39
|
+
`Connection root: ${report.roots.connection}`,
|
|
40
|
+
`Project root: ${report.roots.project ?? 'not applicable'}`,
|
|
41
|
+
];
|
|
42
|
+
for (const file of report.files.filter((file) => file.selected))
|
|
43
|
+
lines.push(`${file.layer}: ${file.path} (${file.state}; schema ${file.version.effective ?? 'unversioned'} → ${file.version.current ?? 'unversioned'}; ${file.fingerprint ?? 'absent'})`);
|
|
44
|
+
for (const change of report.changes)
|
|
45
|
+
lines.push(`${change.path}: schema ${change.before_version ?? 'absent/unversioned'} → ${change.after_version ?? 'unversioned'}\n${change.diff}`);
|
|
46
|
+
for (const finding of report.findings)
|
|
47
|
+
lines.push(`${finding.severity} ${finding.code}: ${finding.path}${finding.key ? ` [${finding.key}]` : ''}\n ${finding.expected}\n ${finding.remediation}\n Recheck: ${finding.recheck}`);
|
|
48
|
+
lines.push(`Offline: ${report.verification.offline.status}. Online: ${report.verification.online.status} — ${report.verification.online.reason}`);
|
|
49
|
+
if (report.operation === 'init' && report.result !== 'ok')
|
|
50
|
+
lines.push('Setup incomplete. Prepared files remain available for repair; inspect the file states above.');
|
|
51
|
+
if (report.operation === 'upgrade' &&
|
|
52
|
+
report.verification.readiness === 'unverified')
|
|
53
|
+
lines.push('Connection readiness is unverified; successful migration does not verify server access.');
|
|
54
|
+
return `${lines.join('\n')}\n`;
|
|
55
|
+
}
|
|
56
|
+
export function registerConfig(program, deps = {}) {
|
|
57
|
+
const group = program
|
|
58
|
+
.command('config')
|
|
59
|
+
.description('initialize, migrate and validate scoped GAIA configuration');
|
|
60
|
+
for (const operation of ['init', 'upgrade', 'check']) {
|
|
61
|
+
const command = group
|
|
62
|
+
.command(operation)
|
|
63
|
+
.description(operation === 'init'
|
|
64
|
+
? 'initialize or repair selected configuration and verify readiness'
|
|
65
|
+
: operation === 'upgrade'
|
|
66
|
+
? 'migrate selected configuration without changing existing values'
|
|
67
|
+
: 'validate configuration offline; optionally verify online access')
|
|
68
|
+
.option('--scope <scope>', 'machine | connection | project | all; init requires a scope')
|
|
69
|
+
.addOption(new Option('--format <format>', 'report format; JSON disables prompts')
|
|
70
|
+
.choices(['text', 'json'])
|
|
71
|
+
.default('text'))
|
|
72
|
+
.option('--path <directory>', 'project root only')
|
|
73
|
+
.option('--project <name>', 'explicit project name for initialization')
|
|
74
|
+
.option('--machine-path <file>', 'override machine configuration path')
|
|
75
|
+
.option('--config <file>', 'override global connection configuration path')
|
|
76
|
+
.option('--yes', 'disable prompts and automatic selections')
|
|
77
|
+
.option('--online', 'authenticate and verify each selected workspace (not applicable for machine scope)');
|
|
78
|
+
if (operation !== 'check')
|
|
79
|
+
command
|
|
80
|
+
.option('--dry-run', 'report planned changes without imports, network or writes')
|
|
81
|
+
.addOption(new Option('--project-connection <choice>', 'retain inheritance/existing override, or explicitly create one')
|
|
82
|
+
.choices(['keep', 'create'])
|
|
83
|
+
.default('keep'))
|
|
84
|
+
.option('--machine-id <id>', 'explicit host identity')
|
|
85
|
+
.option('--user-id <id>', 'local operator identifier')
|
|
86
|
+
.option('--max-parallel <number>', 'positive integer host capacity')
|
|
87
|
+
.option('--base-url <url>', 'connection server URL')
|
|
88
|
+
.option('--client-id <id>', 'OAuth client identifier')
|
|
89
|
+
.option('--workspace-id <uuid>', 'explicit workspace UUID')
|
|
90
|
+
.option('--secret-env <name>', 'environment variable containing the OAuth secret');
|
|
91
|
+
command.action(async (options) => {
|
|
92
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
93
|
+
const env = deps.env ?? process.env;
|
|
94
|
+
const interactive = (deps.isTTY ?? Boolean(process.stdin.isTTY && process.stdout.isTTY)) &&
|
|
95
|
+
!options.yes &&
|
|
96
|
+
!options.dryRun &&
|
|
97
|
+
!env.CI &&
|
|
98
|
+
options.format !== 'json';
|
|
99
|
+
const prompt = deps.prompt ?? promptInput;
|
|
100
|
+
let cancelled = false;
|
|
101
|
+
if (operation === 'init' && !options.scope && interactive) {
|
|
102
|
+
try {
|
|
103
|
+
options.scope = await prompt('Configuration scope (machine, connection, project, all)');
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
cancelled = true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const inputs = {};
|
|
110
|
+
for (const key of [
|
|
111
|
+
'machineId',
|
|
112
|
+
'userId',
|
|
113
|
+
'baseUrl',
|
|
114
|
+
'clientId',
|
|
115
|
+
'workspaceId',
|
|
116
|
+
]) {
|
|
117
|
+
const value = options[key];
|
|
118
|
+
if (value !== undefined)
|
|
119
|
+
inputs[key] = value;
|
|
120
|
+
}
|
|
121
|
+
if (options.maxParallel !== undefined)
|
|
122
|
+
inputs.maxParallel = Number(options.maxParallel);
|
|
123
|
+
const secret = options.secretEnv ? env[options.secretEnv] : undefined;
|
|
124
|
+
if (secret !== undefined)
|
|
125
|
+
inputs.secret = secret;
|
|
126
|
+
const planning = {
|
|
127
|
+
operation,
|
|
128
|
+
cwd,
|
|
129
|
+
home: deps.home ?? homedir(),
|
|
130
|
+
projectConnection: options.projectConnection === 'create' ? 'create' : 'inherit',
|
|
131
|
+
inputs,
|
|
132
|
+
};
|
|
133
|
+
for (const key of ['scope', 'path', 'project', 'machinePath']) {
|
|
134
|
+
const value = options[key];
|
|
135
|
+
if (value !== undefined)
|
|
136
|
+
planning[key] = value;
|
|
137
|
+
}
|
|
138
|
+
if (options.config !== undefined)
|
|
139
|
+
planning.connectionPath = options.config;
|
|
140
|
+
let plan = planConfig(planning);
|
|
141
|
+
if (operation === 'init' &&
|
|
142
|
+
options.project &&
|
|
143
|
+
!options.path &&
|
|
144
|
+
['project', 'all'].includes(options.scope ?? '') &&
|
|
145
|
+
!plan.report.roots.project) {
|
|
146
|
+
planning.path = cwd;
|
|
147
|
+
plan = planConfig(planning);
|
|
148
|
+
}
|
|
149
|
+
const freshConnection = operation === 'init' &&
|
|
150
|
+
plan.report.resolved_scopes.includes('connection') &&
|
|
151
|
+
plan.report.files.some((file) => file.kind === 'connection' &&
|
|
152
|
+
file.selected &&
|
|
153
|
+
file.state === 'absent');
|
|
154
|
+
if (interactive &&
|
|
155
|
+
operation !== 'check' &&
|
|
156
|
+
!cancelled &&
|
|
157
|
+
!plan.report.findings.some((f) => f.stage === 'scope')) {
|
|
158
|
+
const fields = [
|
|
159
|
+
['machineId', 'machine_id', 'Machine identity'],
|
|
160
|
+
['userId', 'user_id', 'Local operator identifier'],
|
|
161
|
+
['baseUrl', 'site.base_url', 'Server base URL'],
|
|
162
|
+
['clientId', 'client_id', 'OAuth client identifier'],
|
|
163
|
+
['secret', 'client_secret', 'OAuth client secret'],
|
|
164
|
+
['workspaceId', 'workspace_id', 'Workspace UUID'],
|
|
165
|
+
];
|
|
166
|
+
for (const [input, key, label] of fields) {
|
|
167
|
+
const requiredForFreshConnection = freshConnection &&
|
|
168
|
+
['baseUrl', 'clientId', 'secret', 'workspaceId'].includes(input);
|
|
169
|
+
if (inputs[input] !== undefined ||
|
|
170
|
+
(!requiredForFreshConnection &&
|
|
171
|
+
!plan.report.findings.some((f) => plan.report.resolved_scopes.includes(f.owner) &&
|
|
172
|
+
f.code === 'missing_required_key' &&
|
|
173
|
+
(f.key === key || f.key?.endsWith(`.${key}`)))))
|
|
174
|
+
continue;
|
|
175
|
+
try {
|
|
176
|
+
const suggested = input === 'machineId' ? hostname().split('.')[0] : undefined;
|
|
177
|
+
const answer = (await prompt(`${label}${suggested ? ` [${suggested}]` : ''}`, input === 'secret')) || suggested;
|
|
178
|
+
if (answer !== undefined)
|
|
179
|
+
inputs[input] = answer;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
cancelled = true;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
plan = planConfig(planning);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
let report = plan.report;
|
|
189
|
+
if (cancelled) {
|
|
190
|
+
report.findings.push({
|
|
191
|
+
code: 'cancelled_selection',
|
|
192
|
+
severity: 'error',
|
|
193
|
+
stage: 'scope',
|
|
194
|
+
path: cwd,
|
|
195
|
+
key: null,
|
|
196
|
+
expected: 'Explicit setup input is required.',
|
|
197
|
+
observed: null,
|
|
198
|
+
owner: 'project',
|
|
199
|
+
related: [],
|
|
200
|
+
statically_determined: false,
|
|
201
|
+
remediation: 'Rerun gaia config init with an explicit --scope and required input flags.',
|
|
202
|
+
recheck: 'gaia config check --scope all',
|
|
203
|
+
});
|
|
204
|
+
report.result = 'blocked';
|
|
205
|
+
}
|
|
206
|
+
if (!cancelled && !options.dryRun && operation !== 'check') {
|
|
207
|
+
report = applyConfigPlan(plan);
|
|
208
|
+
if (!report.findings.some((f) => f.severity === 'error')) {
|
|
209
|
+
const current = planConfig({
|
|
210
|
+
...planning,
|
|
211
|
+
operation: 'check',
|
|
212
|
+
}).report;
|
|
213
|
+
report.files = current.files;
|
|
214
|
+
report.findings = current.findings;
|
|
215
|
+
report.result = current.result;
|
|
216
|
+
report.verification = current.verification;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (!cancelled &&
|
|
220
|
+
!options.dryRun &&
|
|
221
|
+
(operation === 'init' || options.online) &&
|
|
222
|
+
!report.findings.some((f) => f.severity === 'error')) {
|
|
223
|
+
const { verifyConfigConnections } = await import('@gaia-ai/core');
|
|
224
|
+
report = await verifyConfigConnections(report, {
|
|
225
|
+
cwd,
|
|
226
|
+
host: deps.host ?? {
|
|
227
|
+
resolveBases: [import.meta.url, pathToFileURL(`${cwd}/`).href],
|
|
228
|
+
},
|
|
229
|
+
...(deps.stateDir ? { stateDir: deps.stateDir } : {}),
|
|
230
|
+
...(freshConnection ? { ephemeralClientCredentials: true } : {}),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
else if (!options.dryRun &&
|
|
234
|
+
(operation === 'init' || options.online) &&
|
|
235
|
+
report.verification.online.status !== 'not_applicable') {
|
|
236
|
+
const reason = 'Online validation skipped because offline errors require repair or input was cancelled.';
|
|
237
|
+
report.verification.online.reason = reason;
|
|
238
|
+
report.verification.skipped.push({
|
|
239
|
+
path: report.roots.connection,
|
|
240
|
+
stage: 'online',
|
|
241
|
+
reason,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
(deps.stdout ?? ((text) => process.stdout.write(text)))(options.format === 'json'
|
|
245
|
+
? `${JSON.stringify(report)}\n`
|
|
246
|
+
: renderConfigReport(report, options.dryRun));
|
|
247
|
+
if (report.result !== 'ok')
|
|
248
|
+
process.exitCode = 1;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
package/dist/src/host.js
CHANGED
|
@@ -5,8 +5,8 @@ import { commands as conductorCommands } from '@gaia-ai/conductor/preset';
|
|
|
5
5
|
import { resolveCliVersion, resolveModuleEslintStyle, } from '@gaia-ai/core';
|
|
6
6
|
import { commands as uiCommands } from '@gaia-ai/ui/preset';
|
|
7
7
|
import { Command } from 'commander';
|
|
8
|
+
import { registerConfig } from './config.js';
|
|
8
9
|
import { registerUpdate } from './update.js';
|
|
9
|
-
import { registerUpgrade } from './upgrade.js';
|
|
10
10
|
import { registerValidate } from './validate.js';
|
|
11
11
|
// GAIA-201: the `gaia` plugin HOST. The meta package `@gaia-ai/gaia` owns the
|
|
12
12
|
// CLI entrypoint (AC-1); ui / conductor / dropsh / deployment are declared,
|
|
@@ -162,7 +162,7 @@ export async function buildHostProgram(argv, opts = {}) {
|
|
|
162
162
|
.action(() => {
|
|
163
163
|
console.log(cliVersion);
|
|
164
164
|
});
|
|
165
|
-
|
|
165
|
+
registerConfig(program, { host });
|
|
166
166
|
registerUpdate(program);
|
|
167
167
|
registerValidate(program);
|
|
168
168
|
const target = opts.forceTarget ?? scanTarget(argv, names);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaia-ai/gaia",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.4",
|
|
4
4
|
"description": "GAIA meta package: the `gaia` plugin-host CLI + all runtime plugins. Global install target.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,25 +38,25 @@
|
|
|
38
38
|
"jsonapi"
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@gaia-ai/addon-auth-basic": "^0.11.
|
|
42
|
-
"@gaia-ai/addon-claude": "^0.11.
|
|
43
|
-
"@gaia-ai/addon-codex": "^0.11.
|
|
44
|
-
"@gaia-ai/addon-deployment": "^0.11.
|
|
45
|
-
"@gaia-ai/addon-dropsh": "^0.11.
|
|
46
|
-
"@gaia-ai/addon-essentials": "^0.11.
|
|
47
|
-
"@gaia-ai/addon-gaia-ui": "^0.11.
|
|
48
|
-
"@gaia-ai/addon-gaia-ui-artifacts": "^0.11.
|
|
49
|
-
"@gaia-ai/addon-grok": "^0.11.
|
|
50
|
-
"@gaia-ai/addon-herdr": "^0.11.
|
|
51
|
-
"@gaia-ai/addon-kimi": "^0.11.
|
|
52
|
-
"@gaia-ai/addon-opencode": "^0.11.
|
|
53
|
-
"@gaia-ai/addon-pi": "^0.11.
|
|
54
|
-
"@gaia-ai/addon-remote-drupal": "^0.11.
|
|
55
|
-
"@gaia-ai/addon-workspace": "^0.11.
|
|
56
|
-
"@gaia-ai/addon-workspace-git": "^0.11.
|
|
57
|
-
"@gaia-ai/conductor": "^0.11.
|
|
58
|
-
"@gaia-ai/core": "^0.11.
|
|
59
|
-
"@gaia-ai/ui": "^0.11.
|
|
41
|
+
"@gaia-ai/addon-auth-basic": "^0.11.4",
|
|
42
|
+
"@gaia-ai/addon-claude": "^0.11.4",
|
|
43
|
+
"@gaia-ai/addon-codex": "^0.11.4",
|
|
44
|
+
"@gaia-ai/addon-deployment": "^0.11.4",
|
|
45
|
+
"@gaia-ai/addon-dropsh": "^0.11.4",
|
|
46
|
+
"@gaia-ai/addon-essentials": "^0.11.4",
|
|
47
|
+
"@gaia-ai/addon-gaia-ui": "^0.11.4",
|
|
48
|
+
"@gaia-ai/addon-gaia-ui-artifacts": "^0.11.4",
|
|
49
|
+
"@gaia-ai/addon-grok": "^0.11.4",
|
|
50
|
+
"@gaia-ai/addon-herdr": "^0.11.4",
|
|
51
|
+
"@gaia-ai/addon-kimi": "^0.11.4",
|
|
52
|
+
"@gaia-ai/addon-opencode": "^0.11.4",
|
|
53
|
+
"@gaia-ai/addon-pi": "^0.11.4",
|
|
54
|
+
"@gaia-ai/addon-remote-drupal": "^0.11.4",
|
|
55
|
+
"@gaia-ai/addon-workspace": "^0.11.4",
|
|
56
|
+
"@gaia-ai/addon-workspace-git": "^0.11.4",
|
|
57
|
+
"@gaia-ai/conductor": "^0.11.4",
|
|
58
|
+
"@gaia-ai/core": "^0.11.4",
|
|
59
|
+
"@gaia-ai/ui": "^0.11.4",
|
|
60
60
|
"commander": "^12.1.0"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/dist/src/upgrade.d.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { type ProjectConnectionChoice } from '@gaia-ai/conductor';
|
|
2
|
-
import type { Command } from 'commander';
|
|
3
|
-
/** Where the running `gaia` resolved from and what it calls itself. */
|
|
4
|
-
export interface GaiaInstallInfo {
|
|
5
|
-
/** The meta package version (`0.0.0` when the manifest is unreadable). */
|
|
6
|
-
version: string;
|
|
7
|
-
/** Absolute path of the package root this module was loaded from. */
|
|
8
|
-
root: string;
|
|
9
|
-
/** `global install` when that root sits inside `node_modules`, else `dev clone`. */
|
|
10
|
-
kind: 'dev clone' | 'global install';
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Identify the running install from THIS module's own URL — walking up to the
|
|
14
|
-
* nearest `package.json` — rather than from `$PATH` or `process.argv`, so a shim
|
|
15
|
-
* that execs a checkout's build reports the checkout, not the shim.
|
|
16
|
-
*/
|
|
17
|
-
export declare function resolveInstallInfo(): GaiaInstallInfo;
|
|
18
|
-
/**
|
|
19
|
-
* GAIA-218 (AC-3, AC-4): resolve what to do with the PROJECT connection override.
|
|
20
|
-
* Default no in every branch — only an explicit yes opts in. Non-interactive
|
|
21
|
-
* (no TTY) ⇒ always `skip`. The prompt is injected so the host test can stub it.
|
|
22
|
-
*
|
|
23
|
-
* - existing project `gaia.config.js` → "remove this override? [y/N]" → `remove`|`skip`
|
|
24
|
-
* - none → "create an override? [y/N]" → `create`|`skip`
|
|
25
|
-
*/
|
|
26
|
-
export declare function resolveProjectConnectionChoice(opts: {
|
|
27
|
-
existing: string | undefined;
|
|
28
|
-
isTTY: boolean;
|
|
29
|
-
prompt: (question: string) => Promise<boolean>;
|
|
30
|
-
}): Promise<ProjectConnectionChoice>;
|
|
31
|
-
/**
|
|
32
|
-
* GAIA-230 (AC-9): the intro lines printed BEFORE the first migration step, on
|
|
33
|
-
* `--dry-run` too. `home` is where the schema `from` is read (the home connection
|
|
34
|
-
* config `~/.gaia/gaia.config.js`, the one every repo inherits); `to` is the
|
|
35
|
-
* version this gaia ships. Pure — returns the lines rather than printing them.
|
|
36
|
-
*/
|
|
37
|
-
export declare function renderUpgradeIntro(opts: {
|
|
38
|
-
cwd: string;
|
|
39
|
-
home: string;
|
|
40
|
-
install?: GaiaInstallInfo;
|
|
41
|
-
}): string[];
|
|
42
|
-
/** Register `gaia upgrade` on the host program. `deps` is injectable for tests. */
|
|
43
|
-
export declare function registerUpgrade(program: Command, deps?: {
|
|
44
|
-
prompt?: (question: string) => Promise<boolean>;
|
|
45
|
-
isTTY?: boolean;
|
|
46
|
-
cwd?: string;
|
|
47
|
-
home?: string;
|
|
48
|
-
}): void;
|
package/dist/src/upgrade.js
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { dirname, join, sep } from 'node:path';
|
|
4
|
-
import { createInterface } from 'node:readline';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { GAIA_CONFIG_SCHEMA_VERSION, hasProjectConnection, readConfigSchemaVersion, runUpgrade, } from '@gaia-ai/conductor';
|
|
7
|
-
/**
|
|
8
|
-
* Identify the running install from THIS module's own URL — walking up to the
|
|
9
|
-
* nearest `package.json` — rather than from `$PATH` or `process.argv`, so a shim
|
|
10
|
-
* that execs a checkout's build reports the checkout, not the shim.
|
|
11
|
-
*/
|
|
12
|
-
export function resolveInstallInfo() {
|
|
13
|
-
let root = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
let version = '0.0.0';
|
|
15
|
-
for (;;) {
|
|
16
|
-
try {
|
|
17
|
-
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
18
|
-
version = pkg.version ?? '0.0.0';
|
|
19
|
-
break;
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
const parent = dirname(root);
|
|
23
|
-
if (parent === root)
|
|
24
|
-
break;
|
|
25
|
-
root = parent;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
version,
|
|
30
|
-
root,
|
|
31
|
-
kind: root.includes(`${sep}node_modules${sep}`)
|
|
32
|
-
? 'global install'
|
|
33
|
-
: 'dev clone',
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
/** Ask a yes/no question on the terminal; anything but an explicit yes is no. */
|
|
37
|
-
async function promptYesNo(question) {
|
|
38
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
39
|
-
try {
|
|
40
|
-
const answer = await new Promise((resolve) => rl.question(question, resolve));
|
|
41
|
-
const a = answer.trim().toLowerCase();
|
|
42
|
-
return a === 'y' || a === 'yes';
|
|
43
|
-
}
|
|
44
|
-
finally {
|
|
45
|
-
rl.close();
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* GAIA-218 (AC-3, AC-4): resolve what to do with the PROJECT connection override.
|
|
50
|
-
* Default no in every branch — only an explicit yes opts in. Non-interactive
|
|
51
|
-
* (no TTY) ⇒ always `skip`. The prompt is injected so the host test can stub it.
|
|
52
|
-
*
|
|
53
|
-
* - existing project `gaia.config.js` → "remove this override? [y/N]" → `remove`|`skip`
|
|
54
|
-
* - none → "create an override? [y/N]" → `create`|`skip`
|
|
55
|
-
*/
|
|
56
|
-
export async function resolveProjectConnectionChoice(opts) {
|
|
57
|
-
if (!opts.isTTY)
|
|
58
|
-
return 'skip';
|
|
59
|
-
if (opts.existing !== undefined) {
|
|
60
|
-
const yes = await opts.prompt(`remove this project connection override (${opts.existing})? [y/N] `);
|
|
61
|
-
return yes ? 'remove' : 'skip';
|
|
62
|
-
}
|
|
63
|
-
const yes = await opts.prompt('create a project connection override for this repo? [y/N] ');
|
|
64
|
-
return yes ? 'create' : 'skip';
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* GAIA-230 (AC-9): the intro lines printed BEFORE the first migration step, on
|
|
68
|
-
* `--dry-run` too. `home` is where the schema `from` is read (the home connection
|
|
69
|
-
* config `~/.gaia/gaia.config.js`, the one every repo inherits); `to` is the
|
|
70
|
-
* version this gaia ships. Pure — returns the lines rather than printing them.
|
|
71
|
-
*/
|
|
72
|
-
export function renderUpgradeIntro(opts) {
|
|
73
|
-
const info = opts.install ?? resolveInstallInfo();
|
|
74
|
-
const homeConnection = join(opts.home, '.gaia', 'gaia.config.js');
|
|
75
|
-
const from = readConfigSchemaVersion(homeConnection);
|
|
76
|
-
return [
|
|
77
|
-
`gaia upgrade — gaia v${info.version} (${info.kind})`,
|
|
78
|
-
` resolved from: ${info.root}`,
|
|
79
|
-
` config schema: v${from} → v${GAIA_CONFIG_SCHEMA_VERSION} (${homeConnection})`,
|
|
80
|
-
` the .gaia/ configs of ${opts.cwd} and of ${join(opts.home, '.gaia')} get migrated`,
|
|
81
|
-
];
|
|
82
|
-
}
|
|
83
|
-
/** Register `gaia upgrade` on the host program. `deps` is injectable for tests. */
|
|
84
|
-
export function registerUpgrade(program, deps = {}) {
|
|
85
|
-
program
|
|
86
|
-
.command('upgrade')
|
|
87
|
-
.description('migrate this install: the split config model (gaia.config.js connection + conductor.config.js engine; ~/.gaia/machine.config.js) and the GAIA-224 addon package names (@gaia-ai/plugin-*, @gaia-ai/core/builtins and the @gaia-ai/core/plugins + @gaia-ai/gaia/plugins host barrels → @gaia-ai/addon-*). Prompts before creating/removing a project connection override.')
|
|
88
|
-
.option('--dry-run', 'print the migration plan without writing anything', false)
|
|
89
|
-
.action(async (opts) => {
|
|
90
|
-
const cwd = deps.cwd ?? process.cwd();
|
|
91
|
-
const home = deps.home ?? homedir();
|
|
92
|
-
const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY);
|
|
93
|
-
const prompt = deps.prompt ?? promptYesNo;
|
|
94
|
-
// GAIA-230 (AC-9): the intro comes first — before the connection prompt and
|
|
95
|
-
// before the first migration step, so it is on the record either way.
|
|
96
|
-
for (const line of renderUpgradeIntro({ cwd, home }))
|
|
97
|
-
console.log(line);
|
|
98
|
-
const projectConnection = await resolveProjectConnectionChoice({
|
|
99
|
-
existing: hasProjectConnection(cwd),
|
|
100
|
-
isTTY,
|
|
101
|
-
prompt,
|
|
102
|
-
});
|
|
103
|
-
// `cwd`/`home` are forwarded so an injected pair really scopes the run —
|
|
104
|
-
// without them `runUpgrade` reached for `process.cwd()`/`homedir()` and
|
|
105
|
-
// rewrote the developer's own `.gaia/` from a test.
|
|
106
|
-
const report = runUpgrade({
|
|
107
|
-
cwd,
|
|
108
|
-
home,
|
|
109
|
-
dryRun: opts.dryRun,
|
|
110
|
-
projectConnection,
|
|
111
|
-
});
|
|
112
|
-
for (const line of report.actions)
|
|
113
|
-
console.log(line);
|
|
114
|
-
if (report.alreadyCurrent)
|
|
115
|
-
console.log('gaia upgrade: already current');
|
|
116
|
-
else if (opts.dryRun)
|
|
117
|
-
console.log('gaia upgrade: dry run (no changes written)');
|
|
118
|
-
else
|
|
119
|
-
console.log('gaia upgrade: done');
|
|
120
|
-
});
|
|
121
|
-
}
|