@griffin-app/griffin-cli 1.0.4 → 1.0.5
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/README.md +55 -9
- package/dist/cli.js +14 -0
- package/dist/commands/env.js +3 -1
- package/dist/commands/hub/apply.js +17 -14
- package/dist/commands/hub/connect.js +10 -4
- package/dist/commands/hub/login.d.ts +1 -0
- package/dist/commands/hub/login.js +79 -0
- package/dist/commands/hub/logout.d.ts +6 -0
- package/dist/commands/hub/logout.js +16 -0
- package/dist/commands/hub/plan.js +16 -13
- package/dist/commands/hub/run.js +23 -16
- package/dist/commands/hub/runs.js +9 -16
- package/dist/commands/hub/status.js +8 -4
- package/dist/core/apply.d.ts +1 -1
- package/dist/core/apply.js +18 -19
- package/dist/core/apply.test.js +28 -17
- package/dist/core/credentials.d.ts +36 -0
- package/dist/core/credentials.js +98 -0
- package/dist/core/credentials.test.d.ts +1 -0
- package/dist/core/credentials.test.js +137 -0
- package/dist/core/diff.d.ts +5 -4
- package/dist/core/diff.js +2 -1
- package/dist/core/diff.test.js +24 -9
- package/dist/core/plan-diff.d.ts +4 -5
- package/dist/core/plan-diff.js +2 -3
- package/dist/core/sdk.d.ts +4 -0
- package/dist/core/sdk.js +12 -0
- package/dist/core/variables.js +1 -2
- package/dist/index.d.ts +8 -3
- package/dist/index.js +6 -2
- package/dist/schemas/credentials.d.ts +24 -0
- package/dist/schemas/credentials.js +23 -0
- package/dist/schemas/state.d.ts +5 -5
- package/dist/schemas/state.js +4 -4
- package/dist/utils/sdk-error.d.ts +8 -0
- package/dist/utils/sdk-error.js +114 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -145,7 +145,7 @@ List all available environments.
|
|
|
145
145
|
griffin env list
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
Shows all configured environments with an asterisk (
|
|
148
|
+
Shows all configured environments with an asterisk (\*) marking the default environment.
|
|
149
149
|
|
|
150
150
|
### Local Commands
|
|
151
151
|
|
|
@@ -166,12 +166,12 @@ Variables are loaded from `variables.yaml` for the specified environment.
|
|
|
166
166
|
|
|
167
167
|
#### `griffin hub connect`
|
|
168
168
|
|
|
169
|
-
Configure hub connection settings.
|
|
169
|
+
Configure hub connection settings. When a token is provided, it is stored in the user-level credentials file (`~/.griffin/credentials.json`) for secure, cross-project authentication.
|
|
170
170
|
|
|
171
171
|
**Options:**
|
|
172
172
|
|
|
173
173
|
- `--url <url>` - Hub URL (required)
|
|
174
|
-
- `--token <token>` - API authentication token
|
|
174
|
+
- `--token <token>` - API authentication token (optional)
|
|
175
175
|
|
|
176
176
|
**Example:**
|
|
177
177
|
|
|
@@ -179,9 +179,36 @@ Configure hub connection settings.
|
|
|
179
179
|
griffin hub connect --url https://hub.example.com --token abc123
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
+
#### `griffin hub login`
|
|
183
|
+
|
|
184
|
+
Authenticate with the hub using device authorization flow. The received token is stored in the user-level credentials file (`~/.griffin/credentials.json`) for secure access across all projects.
|
|
185
|
+
|
|
186
|
+
**Example:**
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
griffin hub login
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
After running this command, you'll be provided with a URL to complete authentication in your browser. Once authenticated, the token will be automatically saved.
|
|
193
|
+
|
|
194
|
+
#### `griffin hub logout`
|
|
195
|
+
|
|
196
|
+
Remove stored credentials for the currently configured hub or all hubs.
|
|
197
|
+
|
|
198
|
+
**Options:**
|
|
199
|
+
|
|
200
|
+
- `--all` - Remove credentials for all hubs
|
|
201
|
+
|
|
202
|
+
**Example:**
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
griffin hub logout
|
|
206
|
+
griffin hub logout --all
|
|
207
|
+
```
|
|
208
|
+
|
|
182
209
|
#### `griffin hub status`
|
|
183
210
|
|
|
184
|
-
Show hub connection status.
|
|
211
|
+
Show hub connection status, including whether credentials are configured.
|
|
185
212
|
|
|
186
213
|
**Example:**
|
|
187
214
|
|
|
@@ -264,7 +291,6 @@ griffin hub run staging --plan health-check --wait
|
|
|
264
291
|
griffin hub run production --plan api-check --force
|
|
265
292
|
```
|
|
266
293
|
|
|
267
|
-
|
|
268
294
|
## Configuration
|
|
269
295
|
|
|
270
296
|
### Environment Variables
|
|
@@ -273,7 +299,7 @@ griffin hub run production --plan api-check --force
|
|
|
273
299
|
|
|
274
300
|
### State File
|
|
275
301
|
|
|
276
|
-
Griffin stores configuration in `.griffin/state.json`:
|
|
302
|
+
Griffin stores project configuration in `.griffin/state.json`:
|
|
277
303
|
|
|
278
304
|
```json
|
|
279
305
|
{
|
|
@@ -283,9 +309,9 @@ Griffin stores configuration in `.griffin/state.json`:
|
|
|
283
309
|
"local": {}
|
|
284
310
|
},
|
|
285
311
|
"defaultEnvironment": "local",
|
|
286
|
-
"
|
|
312
|
+
"hub": {
|
|
287
313
|
"baseUrl": "https://hub.example.com",
|
|
288
|
-
"
|
|
314
|
+
"clientId": "..."
|
|
289
315
|
},
|
|
290
316
|
"discovery": {
|
|
291
317
|
"pattern": "**/__griffin__/*.{ts,js}",
|
|
@@ -294,7 +320,27 @@ Griffin stores configuration in `.griffin/state.json`:
|
|
|
294
320
|
}
|
|
295
321
|
```
|
|
296
322
|
|
|
297
|
-
**Important:** Add `.griffin/` to `.gitignore` as it contains local state
|
|
323
|
+
**Important:** Add `.griffin/` to `.gitignore` as it contains local project state.
|
|
324
|
+
|
|
325
|
+
### Credentials File
|
|
326
|
+
|
|
327
|
+
Griffin stores user-level authentication credentials in `~/.griffin/credentials.json`:
|
|
328
|
+
|
|
329
|
+
```json
|
|
330
|
+
{
|
|
331
|
+
"version": 1,
|
|
332
|
+
"hubs": {
|
|
333
|
+
"https://hub.example.com": {
|
|
334
|
+
"token": "...",
|
|
335
|
+
"updatedAt": "2024-01-24T12:00:00.000Z"
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
This file is automatically created and managed by the CLI when you use `griffin hub login` or `griffin hub connect --token <token>`. Credentials are stored per-hub URL and are available across all projects on your system.
|
|
342
|
+
|
|
343
|
+
**Security:** The credentials file is created with restricted permissions (0600) to ensure only the owner can read/write.
|
|
298
344
|
|
|
299
345
|
## Environments and Variables
|
|
300
346
|
|
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,8 @@ import { executeRuns } from "./commands/hub/runs.js";
|
|
|
13
13
|
import { executePlan } from "./commands/hub/plan.js";
|
|
14
14
|
import { executeApply } from "./commands/hub/apply.js";
|
|
15
15
|
import { executeRun } from "./commands/hub/run.js";
|
|
16
|
+
import { executeLogin } from "./commands/hub/login.js";
|
|
17
|
+
import { executeLogout } from "./commands/hub/logout.js";
|
|
16
18
|
const program = new Command();
|
|
17
19
|
program
|
|
18
20
|
.name("griffin")
|
|
@@ -106,5 +108,17 @@ hub
|
|
|
106
108
|
.action(async (env, options) => {
|
|
107
109
|
await executeRun({ ...options, env });
|
|
108
110
|
});
|
|
111
|
+
hub
|
|
112
|
+
.command("login")
|
|
113
|
+
.description("Login to the hub")
|
|
114
|
+
.action(async () => {
|
|
115
|
+
await executeLogin();
|
|
116
|
+
});
|
|
117
|
+
hub
|
|
118
|
+
.command("logout")
|
|
119
|
+
.description("Remove stored credentials")
|
|
120
|
+
.action(async (options) => {
|
|
121
|
+
await executeLogout(options);
|
|
122
|
+
});
|
|
109
123
|
// Parse arguments
|
|
110
124
|
program.parse();
|
package/dist/commands/env.js
CHANGED
|
@@ -17,7 +17,9 @@ export async function executeEnvList() {
|
|
|
17
17
|
terminal.blank();
|
|
18
18
|
for (const envName of environments) {
|
|
19
19
|
const isDefault = state.defaultEnvironment === envName;
|
|
20
|
-
const marker = isDefault
|
|
20
|
+
const marker = isDefault
|
|
21
|
+
? terminal.colors.green("●")
|
|
22
|
+
: terminal.colors.dim("○");
|
|
21
23
|
const envDisplay = isDefault
|
|
22
24
|
? terminal.colors.cyan(envName) + terminal.colors.dim(" (default)")
|
|
23
25
|
: envName;
|
|
@@ -2,8 +2,11 @@ import { loadState, resolveEnvironment } from "../../core/state.js";
|
|
|
2
2
|
import { discoverPlans, formatDiscoveryErrors } from "../../core/discovery.js";
|
|
3
3
|
import { computeDiff, formatDiff } from "../../core/diff.js";
|
|
4
4
|
import { applyDiff, formatApplyResult } from "../../core/apply.js";
|
|
5
|
-
import {
|
|
5
|
+
import { createSdkWithCredentials } from "../../core/sdk.js";
|
|
6
6
|
import { terminal } from "../../utils/terminal.js";
|
|
7
|
+
import { withSDKErrorHandling } from "../../utils/sdk-error.js";
|
|
8
|
+
import { loadVariables } from "../../core/variables.js";
|
|
9
|
+
import { resolvePlan } from "../../resolve.js";
|
|
7
10
|
/**
|
|
8
11
|
* Apply changes to the hub
|
|
9
12
|
*/
|
|
@@ -14,7 +17,7 @@ export async function executeApply(options) {
|
|
|
14
17
|
// Resolve environment
|
|
15
18
|
const envName = await resolveEnvironment(options.env);
|
|
16
19
|
// Check if runner is configured
|
|
17
|
-
if (!state.
|
|
20
|
+
if (!state.hub?.baseUrl) {
|
|
18
21
|
terminal.error("Hub connection not configured.");
|
|
19
22
|
terminal.dim("Connect with:");
|
|
20
23
|
terminal.dim(" griffin hub connect --url <url> --token <token>");
|
|
@@ -22,11 +25,8 @@ export async function executeApply(options) {
|
|
|
22
25
|
}
|
|
23
26
|
terminal.info(`Applying to ${terminal.colors.cyan(envName)} environment`);
|
|
24
27
|
terminal.blank();
|
|
25
|
-
// Create SDK clients
|
|
26
|
-
const sdk =
|
|
27
|
-
baseUrl: state.runner?.baseUrl || "",
|
|
28
|
-
apiToken: state.runner?.apiToken || "",
|
|
29
|
-
});
|
|
28
|
+
// Create SDK clients with credentials
|
|
29
|
+
const sdk = await createSdkWithCredentials(state.hub.baseUrl);
|
|
30
30
|
// Discover local plans
|
|
31
31
|
const discoveryPattern = state.discovery?.pattern || "**/__griffin__/*.{ts,js}";
|
|
32
32
|
const discoveryIgnore = state.discovery?.ignore || [
|
|
@@ -42,19 +42,22 @@ export async function executeApply(options) {
|
|
|
42
42
|
}
|
|
43
43
|
spinner.succeed(`Found ${plans.length} local plan(s)`);
|
|
44
44
|
// Fetch remote plans for this project + environment
|
|
45
|
-
const fetchSpinner = terminal
|
|
46
|
-
|
|
47
|
-
.start();
|
|
48
|
-
const response = await sdk.getPlan({
|
|
45
|
+
const fetchSpinner = terminal.spinner("Fetching remote plans...").start();
|
|
46
|
+
const response = await withSDKErrorHandling(() => sdk.getPlan({
|
|
49
47
|
query: {
|
|
50
48
|
projectId: state.projectId,
|
|
51
49
|
environment: envName,
|
|
52
50
|
},
|
|
53
|
-
});
|
|
51
|
+
}), "Failed to fetch remote plans");
|
|
54
52
|
const remotePlans = response?.data?.data;
|
|
55
53
|
fetchSpinner.succeed(`Found ${remotePlans.length} remote plan(s)`);
|
|
54
|
+
// Load variables and resolve local plans before computing diff
|
|
55
|
+
const variables = await loadVariables(envName);
|
|
56
|
+
const resolvedPlans = plans.map((p) => resolvePlan(p.plan, state.projectId, envName, variables));
|
|
56
57
|
// Compute diff (include deletions if --prune)
|
|
57
|
-
const diff = computeDiff(
|
|
58
|
+
const diff = computeDiff(resolvedPlans, remotePlans, {
|
|
59
|
+
includeDeletions: options.prune || false,
|
|
60
|
+
});
|
|
58
61
|
// Show plan
|
|
59
62
|
terminal.blank();
|
|
60
63
|
terminal.log(formatDiff(diff));
|
|
@@ -80,7 +83,7 @@ export async function executeApply(options) {
|
|
|
80
83
|
}
|
|
81
84
|
// Apply changes with environment injection
|
|
82
85
|
const applySpinner = terminal.spinner("Applying changes...").start();
|
|
83
|
-
const result = await applyDiff(diff, sdk,
|
|
86
|
+
const result = await applyDiff(diff, sdk, {
|
|
84
87
|
dryRun: options.dryRun,
|
|
85
88
|
});
|
|
86
89
|
if (result.success) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { randomBytes } from "crypto";
|
|
1
2
|
import { loadState, saveState } from "../../core/state.js";
|
|
3
|
+
import { saveHubCredentials } from "../../core/credentials.js";
|
|
2
4
|
import { terminal } from "../../utils/terminal.js";
|
|
3
5
|
/**
|
|
4
6
|
* Configure hub connection settings
|
|
@@ -6,16 +8,20 @@ import { terminal } from "../../utils/terminal.js";
|
|
|
6
8
|
export async function executeConnect(options) {
|
|
7
9
|
try {
|
|
8
10
|
const state = await loadState();
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
+
// Save token to user-level credentials file if provided
|
|
12
|
+
if (options.token) {
|
|
13
|
+
await saveHubCredentials(options.token);
|
|
14
|
+
}
|
|
15
|
+
// Update hub config in project state (without token)
|
|
16
|
+
state.hub = {
|
|
11
17
|
baseUrl: options.url,
|
|
12
|
-
|
|
18
|
+
clientId: randomBytes(16).toString("hex"),
|
|
13
19
|
};
|
|
14
20
|
await saveState(state);
|
|
15
21
|
terminal.success("Hub connection configured");
|
|
16
22
|
terminal.log(` URL: ${terminal.colors.cyan(options.url)}`);
|
|
17
23
|
if (options.token) {
|
|
18
|
-
terminal.log(` API Token: ${terminal.colors.dim("***")}`);
|
|
24
|
+
terminal.log(` API Token: ${terminal.colors.dim("***")} (saved to user credentials)`);
|
|
19
25
|
}
|
|
20
26
|
terminal.blank();
|
|
21
27
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function executeLogin(): Promise<void>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// CLI implementation
|
|
2
|
+
import { createAuthClient } from "better-auth/client";
|
|
3
|
+
import { deviceAuthorizationClient, jwtClient, } from "better-auth/client/plugins";
|
|
4
|
+
import { loadState, saveState } from "../../core/state.js";
|
|
5
|
+
import { saveHubCredentials } from "../../core/credentials.js";
|
|
6
|
+
import { terminal } from "../../utils/terminal.js";
|
|
7
|
+
import { randomBytes } from "crypto";
|
|
8
|
+
const baseURL = "http://localhost:4000/api/auth";
|
|
9
|
+
const hubBaseUrl = "http://localhost:3000";
|
|
10
|
+
//const baseURL = "https://cloud.griffin.app"
|
|
11
|
+
const oauthGrant = "urn:ietf:params:oauth:grant-type:device_code";
|
|
12
|
+
const authClient = createAuthClient({
|
|
13
|
+
baseURL: baseURL,
|
|
14
|
+
plugins: [
|
|
15
|
+
deviceAuthorizationClient(),
|
|
16
|
+
jwtClient(),
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
async function pollForToken(clientId, deviceCode, interval) {
|
|
20
|
+
const { data, error } = await authClient.device.token({
|
|
21
|
+
grant_type: oauthGrant,
|
|
22
|
+
device_code: deviceCode,
|
|
23
|
+
client_id: clientId,
|
|
24
|
+
fetchOptions: {
|
|
25
|
+
headers: {
|
|
26
|
+
"user-agent": `griffin-cli`,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
if (data?.access_token)
|
|
31
|
+
return data.access_token;
|
|
32
|
+
switch (error?.error) {
|
|
33
|
+
case "slow_down":
|
|
34
|
+
await new Promise((resolve) => setTimeout(resolve, interval * 2));
|
|
35
|
+
return pollForToken(clientId, deviceCode, interval * 2);
|
|
36
|
+
case "authorization_pending":
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
38
|
+
return pollForToken(clientId, deviceCode, interval);
|
|
39
|
+
default:
|
|
40
|
+
throw new Error(error?.error_description || "Unknown error");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function executeLogin() {
|
|
44
|
+
const state = await loadState();
|
|
45
|
+
let clientId = state.hub?.clientId;
|
|
46
|
+
if (!clientId) {
|
|
47
|
+
clientId = randomBytes(16).toString("hex");
|
|
48
|
+
}
|
|
49
|
+
//const clientId = state.hub?.clientId;
|
|
50
|
+
const { data } = await authClient.device.code({
|
|
51
|
+
client_id: clientId,
|
|
52
|
+
});
|
|
53
|
+
terminal.info(`Go to: ${data?.verification_uri_complete}`);
|
|
54
|
+
terminal.info(`Or enter code: ${data?.user_code}`);
|
|
55
|
+
// 2. Poll for authorization
|
|
56
|
+
const sessionToken = await pollForToken(clientId, data?.device_code, (data?.interval ?? 5) * 1000);
|
|
57
|
+
const { data: jwtData } = await authClient.token({
|
|
58
|
+
fetchOptions: {
|
|
59
|
+
headers: {
|
|
60
|
+
Authorization: `Bearer ${sessionToken}`,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
// Save token to user-level credentials file
|
|
65
|
+
if (jwtData?.token) {
|
|
66
|
+
await saveHubCredentials(jwtData.token);
|
|
67
|
+
terminal.success("Login successful");
|
|
68
|
+
terminal.log(` Token saved to user credentials`);
|
|
69
|
+
}
|
|
70
|
+
// Save hub config to project state (without token)
|
|
71
|
+
await saveState({
|
|
72
|
+
...state,
|
|
73
|
+
hub: {
|
|
74
|
+
...state.hub,
|
|
75
|
+
clientId: clientId,
|
|
76
|
+
baseUrl: hubBaseUrl,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { removeHubCredentials } from "../../core/credentials.js";
|
|
2
|
+
import { terminal } from "../../utils/terminal.js";
|
|
3
|
+
/**
|
|
4
|
+
* Remove stored credentials for hub
|
|
5
|
+
*/
|
|
6
|
+
export async function executeLogout(options) {
|
|
7
|
+
try {
|
|
8
|
+
await removeHubCredentials();
|
|
9
|
+
terminal.success("Credentials removed.");
|
|
10
|
+
terminal.blank();
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
terminal.error(error.message);
|
|
14
|
+
terminal.exit(1);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { loadState, resolveEnvironment } from "../../core/state.js";
|
|
2
2
|
import { discoverPlans, formatDiscoveryErrors } from "../../core/discovery.js";
|
|
3
|
-
import {
|
|
3
|
+
import { createSdkWithCredentials } from "../../core/sdk.js";
|
|
4
4
|
import { computeDiff, formatDiff, formatDiffJson } from "../../core/diff.js";
|
|
5
5
|
import { terminal } from "../../utils/terminal.js";
|
|
6
|
+
import { withSDKErrorHandling } from "../../utils/sdk-error.js";
|
|
7
|
+
import { loadVariables } from "../../core/variables.js";
|
|
8
|
+
import { resolvePlan } from "../../resolve.js";
|
|
6
9
|
/**
|
|
7
10
|
* Show what changes would be applied
|
|
8
11
|
*/
|
|
@@ -12,7 +15,7 @@ export async function executePlan(options) {
|
|
|
12
15
|
const state = await loadState();
|
|
13
16
|
// Resolve environment
|
|
14
17
|
const envName = await resolveEnvironment(options.env);
|
|
15
|
-
if (!state.
|
|
18
|
+
if (!state.hub?.baseUrl) {
|
|
16
19
|
terminal.error("Hub connection not configured.");
|
|
17
20
|
terminal.dim("Connect with:");
|
|
18
21
|
terminal.dim(" griffin hub connect --url <url> --token <token>");
|
|
@@ -32,25 +35,25 @@ export async function executePlan(options) {
|
|
|
32
35
|
terminal.exit(1);
|
|
33
36
|
}
|
|
34
37
|
spinner.succeed(`Found ${plans.length} local plan(s)`);
|
|
35
|
-
// Create SDK clients
|
|
36
|
-
const sdk =
|
|
37
|
-
baseUrl: state.runner?.baseUrl || "",
|
|
38
|
-
apiToken: state.runner?.apiToken || "",
|
|
39
|
-
});
|
|
38
|
+
// Create SDK clients with credentials
|
|
39
|
+
const sdk = await createSdkWithCredentials(state.hub.baseUrl);
|
|
40
40
|
// Fetch remote plans for this project + environment
|
|
41
|
-
const fetchSpinner = terminal
|
|
42
|
-
|
|
43
|
-
.start();
|
|
44
|
-
const response = await sdk.getPlan({
|
|
41
|
+
const fetchSpinner = terminal.spinner("Fetching remote plans...").start();
|
|
42
|
+
const response = await withSDKErrorHandling(() => sdk.getPlan({
|
|
45
43
|
query: {
|
|
46
44
|
projectId: state.projectId,
|
|
47
45
|
environment: envName,
|
|
48
46
|
},
|
|
49
|
-
});
|
|
47
|
+
}), "Failed to fetch remote plans");
|
|
50
48
|
const remotePlans = response?.data?.data;
|
|
51
49
|
fetchSpinner.succeed(`Found ${remotePlans.length} remote plan(s)`);
|
|
50
|
+
// Load variables and resolve local plans before computing diff
|
|
51
|
+
const variables = await loadVariables(envName);
|
|
52
|
+
const resolvedPlans = plans.map((p) => resolvePlan(p.plan, state.projectId, envName, variables));
|
|
52
53
|
// Compute diff (no deletions shown by default)
|
|
53
|
-
const diff = computeDiff(
|
|
54
|
+
const diff = computeDiff(resolvedPlans, remotePlans, {
|
|
55
|
+
includeDeletions: false,
|
|
56
|
+
});
|
|
54
57
|
terminal.blank();
|
|
55
58
|
// Output
|
|
56
59
|
if (options.json) {
|
package/dist/commands/hub/run.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { loadState, resolveEnvironment } from "../../core/state.js";
|
|
2
|
-
import {
|
|
2
|
+
import { createSdkWithCredentials } from "../../core/sdk.js";
|
|
3
3
|
import { discoverPlans, formatDiscoveryErrors } from "../../core/discovery.js";
|
|
4
4
|
import { computeDiff } from "../../core/diff.js";
|
|
5
5
|
import { terminal } from "../../utils/terminal.js";
|
|
6
|
+
import { withSDKErrorHandling } from "../../utils/sdk-error.js";
|
|
7
|
+
import { loadVariables } from "../../core/variables.js";
|
|
8
|
+
import { resolvePlan } from "../../resolve.js";
|
|
6
9
|
/**
|
|
7
10
|
* Trigger a plan run on the hub
|
|
8
11
|
*/
|
|
@@ -12,17 +15,14 @@ export async function executeRun(options) {
|
|
|
12
15
|
const state = await loadState();
|
|
13
16
|
// Resolve environment
|
|
14
17
|
const envName = await resolveEnvironment(options.env);
|
|
15
|
-
if (!state.
|
|
18
|
+
if (!state.hub?.baseUrl) {
|
|
16
19
|
terminal.error("Hub connection not configured.");
|
|
17
20
|
terminal.dim("Connect with:");
|
|
18
21
|
terminal.dim(" griffin hub connect --url <url> --token <token>");
|
|
19
22
|
terminal.exit(1);
|
|
20
23
|
}
|
|
21
|
-
// Create SDK clients
|
|
22
|
-
const sdk =
|
|
23
|
-
baseUrl: state.runner?.baseUrl || "",
|
|
24
|
-
apiToken: state.runner?.apiToken || "",
|
|
25
|
-
});
|
|
24
|
+
// Create SDK clients with credentials
|
|
25
|
+
const sdk = await createSdkWithCredentials(state.hub.baseUrl);
|
|
26
26
|
// Discover local plans
|
|
27
27
|
const discoveryPattern = state.discovery?.pattern || "**/__griffin__/*.{ts,js}";
|
|
28
28
|
const discoveryIgnore = state.discovery?.ignore || [
|
|
@@ -50,12 +50,12 @@ export async function executeRun(options) {
|
|
|
50
50
|
spinner.succeed(`Found local plan: ${terminal.colors.cyan(options.plan)}`);
|
|
51
51
|
// Fetch remote plans for this project + environment
|
|
52
52
|
const fetchSpinner = terminal.spinner("Checking hub...").start();
|
|
53
|
-
const response = await sdk.getPlan({
|
|
53
|
+
const response = await withSDKErrorHandling(() => sdk.getPlan({
|
|
54
54
|
query: {
|
|
55
55
|
projectId: state.projectId,
|
|
56
56
|
environment: envName,
|
|
57
57
|
},
|
|
58
|
-
});
|
|
58
|
+
}), "Failed to fetch plans from hub");
|
|
59
59
|
const remotePlans = response?.data?.data;
|
|
60
60
|
// Find remote plan by name
|
|
61
61
|
const remotePlan = remotePlans.find((p) => p.name === options.plan);
|
|
@@ -65,8 +65,11 @@ export async function executeRun(options) {
|
|
|
65
65
|
terminal.exit(1);
|
|
66
66
|
}
|
|
67
67
|
fetchSpinner.succeed("Plan found on hub");
|
|
68
|
+
// Load variables and resolve local plan before computing diff
|
|
69
|
+
const variables = await loadVariables(envName);
|
|
70
|
+
const resolvedLocalPlan = resolvePlan(localPlan.plan, state.projectId, envName, variables);
|
|
68
71
|
// Compute diff to check if local plan differs from remote
|
|
69
|
-
const diff = computeDiff([
|
|
72
|
+
const diff = computeDiff([resolvedLocalPlan], [remotePlan], {
|
|
70
73
|
includeDeletions: false,
|
|
71
74
|
});
|
|
72
75
|
const hasDiff = diff.actions.length > 0 &&
|
|
@@ -86,14 +89,14 @@ export async function executeRun(options) {
|
|
|
86
89
|
terminal.warn("Running with --force (local changes not applied)");
|
|
87
90
|
}
|
|
88
91
|
const triggerSpinner = terminal.spinner("Triggering run...").start();
|
|
89
|
-
const runResponse = await sdk.postRunsTriggerByPlanId({
|
|
92
|
+
const runResponse = await withSDKErrorHandling(() => sdk.postRunsTriggerByPlanId({
|
|
90
93
|
path: {
|
|
91
94
|
planId: remotePlan.id,
|
|
92
95
|
},
|
|
93
96
|
body: {
|
|
94
97
|
environment: envName,
|
|
95
98
|
},
|
|
96
|
-
});
|
|
99
|
+
}), "Failed to trigger run");
|
|
97
100
|
const run = runResponse?.data?.data;
|
|
98
101
|
triggerSpinner.succeed("Run triggered");
|
|
99
102
|
terminal.blank();
|
|
@@ -103,16 +106,18 @@ export async function executeRun(options) {
|
|
|
103
106
|
// Wait for completion if requested
|
|
104
107
|
if (options.wait) {
|
|
105
108
|
terminal.blank();
|
|
106
|
-
const waitSpinner = terminal
|
|
109
|
+
const waitSpinner = terminal
|
|
110
|
+
.spinner("Waiting for run to complete...")
|
|
111
|
+
.start();
|
|
107
112
|
const runId = run.id;
|
|
108
113
|
let completed = false;
|
|
109
114
|
while (!completed) {
|
|
110
115
|
await new Promise((resolve) => setTimeout(resolve, 2000)); // Poll every 2 seconds
|
|
111
|
-
const runStatusResponse = await sdk.getRunsById({
|
|
116
|
+
const runStatusResponse = await withSDKErrorHandling(() => sdk.getRunsById({
|
|
112
117
|
path: {
|
|
113
118
|
id: runId,
|
|
114
119
|
},
|
|
115
|
-
});
|
|
120
|
+
}), "Failed to fetch run status");
|
|
116
121
|
const run = runStatusResponse?.data?.data;
|
|
117
122
|
if (run.status === "completed" || run.status === "failed") {
|
|
118
123
|
completed = true;
|
|
@@ -127,7 +132,9 @@ export async function executeRun(options) {
|
|
|
127
132
|
terminal.log(`Duration: ${terminal.colors.dim((run.duration_ms / 1000).toFixed(2) + "s")}`);
|
|
128
133
|
}
|
|
129
134
|
if (run.success !== undefined) {
|
|
130
|
-
const successText = run.success
|
|
135
|
+
const successText = run.success
|
|
136
|
+
? terminal.colors.green("Yes")
|
|
137
|
+
: terminal.colors.red("No");
|
|
131
138
|
terminal.log(`Success: ${successText}`);
|
|
132
139
|
}
|
|
133
140
|
if (run.errors && run.errors.length > 0) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { loadState } from "../../core/state.js";
|
|
2
|
-
import {
|
|
2
|
+
import { createSdkWithCredentials } from "../../core/sdk.js";
|
|
3
3
|
import { terminal } from "../../utils/terminal.js";
|
|
4
|
+
import { withSDKErrorHandling } from "../../utils/sdk-error.js";
|
|
4
5
|
/**
|
|
5
6
|
* Show recent runs from the hub
|
|
6
7
|
*/
|
|
@@ -8,29 +9,26 @@ export async function executeRuns(options) {
|
|
|
8
9
|
try {
|
|
9
10
|
// Load state
|
|
10
11
|
const state = await loadState();
|
|
11
|
-
if (!state.
|
|
12
|
+
if (!state.hub?.baseUrl) {
|
|
12
13
|
terminal.error("Hub connection not configured.");
|
|
13
14
|
terminal.dim("Connect with:");
|
|
14
15
|
terminal.dim(" griffin hub connect --url <url> --token <token>");
|
|
15
16
|
terminal.exit(1);
|
|
16
17
|
}
|
|
17
|
-
// Create SDK clients
|
|
18
|
-
const sdk =
|
|
19
|
-
|
|
20
|
-
apiToken: state.runner.apiToken || undefined,
|
|
21
|
-
});
|
|
22
|
-
terminal.info(`Hub: ${terminal.colors.cyan(state.runner.baseUrl)}`);
|
|
18
|
+
// Create SDK clients with credentials
|
|
19
|
+
const sdk = await createSdkWithCredentials(state.hub.baseUrl);
|
|
20
|
+
terminal.info(`Hub: ${terminal.colors.cyan(state.hub.baseUrl)}`);
|
|
23
21
|
terminal.blank();
|
|
24
22
|
// Get recent runs
|
|
25
23
|
const limit = options.limit || 10;
|
|
26
24
|
const spinner = terminal.spinner("Fetching runs...").start();
|
|
27
|
-
const response = await sdk.getRuns({
|
|
25
|
+
const response = await withSDKErrorHandling(() => sdk.getRuns({
|
|
28
26
|
query: {
|
|
29
27
|
planId: options.plan,
|
|
30
28
|
limit: limit,
|
|
31
29
|
offset: 0,
|
|
32
30
|
},
|
|
33
|
-
});
|
|
31
|
+
}), "Failed to fetch runs");
|
|
34
32
|
const runsData = response?.data;
|
|
35
33
|
if (!runsData || runsData.total === 0) {
|
|
36
34
|
spinner.info("No runs found.");
|
|
@@ -54,12 +52,7 @@ export async function executeRuns(options) {
|
|
|
54
52
|
? `${(run.duration_ms / 1000).toFixed(2)}s`
|
|
55
53
|
: "-";
|
|
56
54
|
const started = new Date(run.startedAt).toLocaleString();
|
|
57
|
-
table.push([
|
|
58
|
-
statusIcon,
|
|
59
|
-
run.planId || "-",
|
|
60
|
-
duration,
|
|
61
|
-
started,
|
|
62
|
-
]);
|
|
55
|
+
table.push([statusIcon, run.planId || "-", duration, started]);
|
|
63
56
|
}
|
|
64
57
|
catch (error) {
|
|
65
58
|
terminal.error(`Error processing run ${run.id}: ${error.message}`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadState } from "../../core/state.js";
|
|
2
|
+
import { getHubCredentials } from "../../core/credentials.js";
|
|
2
3
|
import { terminal } from "../../utils/terminal.js";
|
|
3
4
|
/**
|
|
4
5
|
* Show hub connection status
|
|
@@ -6,17 +7,20 @@ import { terminal } from "../../utils/terminal.js";
|
|
|
6
7
|
export async function executeStatus() {
|
|
7
8
|
try {
|
|
8
9
|
const state = await loadState();
|
|
9
|
-
if (!state.
|
|
10
|
+
if (!state.hub) {
|
|
10
11
|
terminal.warn("No hub connection configured.");
|
|
11
12
|
terminal.blank();
|
|
12
13
|
terminal.dim("Connect with:");
|
|
13
14
|
terminal.dim(" griffin hub connect --url <url> --token <token>");
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
17
|
+
// Read credentials from user-level credentials file
|
|
18
|
+
const credentials = await getHubCredentials();
|
|
16
19
|
terminal.info("Hub connection:");
|
|
17
|
-
terminal.log(` URL: ${terminal.colors.cyan(state.
|
|
18
|
-
if (
|
|
19
|
-
terminal.log(` API Token: ${terminal.colors.dim(
|
|
20
|
+
terminal.log(` URL: ${terminal.colors.cyan(state.hub.baseUrl)}`);
|
|
21
|
+
if (credentials?.token) {
|
|
22
|
+
terminal.log(` API Token: ${terminal.colors.dim(credentials.token.substring(0, 8) + "...")}`);
|
|
23
|
+
terminal.log(` Updated: ${terminal.colors.dim(new Date(credentials.updatedAt).toLocaleString())}`);
|
|
20
24
|
}
|
|
21
25
|
else {
|
|
22
26
|
terminal.log(` API Token: ${terminal.colors.dim("(not set)")}`);
|
package/dist/core/apply.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface ApplyError {
|
|
|
19
19
|
* Apply diff actions to the hub.
|
|
20
20
|
* CLI injects both project and environment into plan payloads.
|
|
21
21
|
*/
|
|
22
|
-
export declare function applyDiff(diff: DiffResult, sdk: GriffinHubSdk,
|
|
22
|
+
export declare function applyDiff(diff: DiffResult, sdk: GriffinHubSdk, options?: {
|
|
23
23
|
dryRun?: boolean;
|
|
24
24
|
}): Promise<ApplyResult>;
|
|
25
25
|
/**
|