@griffin-app/griffin-cli 1.0.0 → 1.0.2
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/cli.d.ts +2 -0
- package/dist/cli.js +105 -0
- package/dist/commands/apply.d.ts +9 -0
- package/dist/commands/apply.js +76 -0
- package/dist/commands/config.d.ts +36 -0
- package/dist/commands/config.js +144 -0
- package/dist/commands/configure-runner-host.d.ts +2 -0
- package/dist/commands/configure-runner-host.js +41 -0
- package/dist/commands/deploy.d.ts +1 -0
- package/dist/commands/deploy.js +36 -0
- package/dist/commands/env.d.ts +25 -0
- package/dist/commands/env.js +121 -0
- package/dist/commands/execute-remote.d.ts +1 -0
- package/dist/commands/execute-remote.js +33 -0
- package/dist/commands/generate-key.d.ts +9 -0
- package/dist/commands/generate-key.js +27 -0
- package/dist/commands/hub/apply.d.ts +10 -0
- package/dist/commands/hub/apply.js +81 -0
- package/dist/commands/hub/config.d.ts +27 -0
- package/dist/commands/hub/config.js +102 -0
- package/dist/commands/hub/connect.d.ts +8 -0
- package/dist/commands/hub/connect.js +25 -0
- package/dist/commands/hub/plan.d.ts +8 -0
- package/dist/commands/hub/plan.js +58 -0
- package/dist/commands/hub/run.d.ts +10 -0
- package/dist/commands/hub/run.js +126 -0
- package/dist/commands/hub/runs.d.ts +8 -0
- package/dist/commands/hub/runs.js +75 -0
- package/dist/commands/hub/status.d.ts +4 -0
- package/dist/commands/hub/status.js +29 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +41 -0
- package/dist/commands/local/config.d.ts +28 -0
- package/dist/commands/local/config.js +82 -0
- package/dist/commands/local/run.d.ts +4 -0
- package/dist/commands/local/run.js +49 -0
- package/dist/commands/logs.d.ts +1 -0
- package/dist/commands/logs.js +20 -0
- package/dist/commands/plan.d.ts +8 -0
- package/dist/commands/plan.js +58 -0
- package/dist/commands/run-remote.d.ts +11 -0
- package/dist/commands/run-remote.js +98 -0
- package/dist/commands/run.d.ts +4 -0
- package/dist/commands/run.js +86 -0
- package/dist/commands/runner.d.ts +12 -0
- package/dist/commands/runner.js +53 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.js +75 -0
- package/dist/commands/validate.d.ts +4 -0
- package/dist/commands/validate.js +46 -0
- package/dist/core/apply.d.ts +28 -0
- package/dist/core/apply.js +135 -0
- package/dist/core/apply.test.d.ts +1 -0
- package/dist/core/apply.test.js +140 -0
- package/dist/core/diff.d.ts +42 -0
- package/dist/core/diff.js +212 -0
- package/dist/core/diff.test.d.ts +1 -0
- package/dist/core/diff.test.js +122 -0
- package/dist/core/discovery.d.ts +23 -0
- package/dist/core/discovery.js +89 -0
- package/dist/core/plan-diff.d.ts +42 -0
- package/dist/core/plan-diff.js +257 -0
- package/dist/core/project.d.ts +8 -0
- package/dist/core/project.js +46 -0
- package/dist/core/sdk.d.ts +16 -0
- package/dist/core/sdk.js +23 -0
- package/dist/core/state.d.ts +52 -0
- package/dist/core/state.js +151 -0
- package/dist/core/variables.d.ts +19 -0
- package/dist/core/variables.js +100 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +21 -0
- package/dist/schemas/payload.d.ts +6 -0
- package/dist/schemas/payload.js +8 -0
- package/dist/schemas/state.d.ts +43 -0
- package/dist/schemas/state.js +54 -0
- package/dist/test-discovery.d.ts +4 -0
- package/dist/test-discovery.js +25 -0
- package/dist/test-runner.d.ts +6 -0
- package/dist/test-runner.js +59 -0
- package/package.json +6 -5
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { executeInit } from "./commands/init.js";
|
|
4
|
+
import { executeValidate } from "./commands/validate.js";
|
|
5
|
+
import { executeGenerateKey } from "./commands/generate-key.js";
|
|
6
|
+
// Local commands
|
|
7
|
+
import { executeRunLocal } from "./commands/local/run.js";
|
|
8
|
+
// Hub commands
|
|
9
|
+
import { executeConnect } from "./commands/hub/connect.js";
|
|
10
|
+
import { executeStatus } from "./commands/hub/status.js";
|
|
11
|
+
import { executeRuns } from "./commands/hub/runs.js";
|
|
12
|
+
import { executePlan } from "./commands/hub/plan.js";
|
|
13
|
+
import { executeApply } from "./commands/hub/apply.js";
|
|
14
|
+
import { executeRun } from "./commands/hub/run.js";
|
|
15
|
+
const program = new Command();
|
|
16
|
+
program
|
|
17
|
+
.name("griffin")
|
|
18
|
+
.description("Griffin CLI - Monitoring as Code")
|
|
19
|
+
.version("1.0.0");
|
|
20
|
+
// Top-level commands
|
|
21
|
+
program
|
|
22
|
+
.command("init")
|
|
23
|
+
.description("Initialize griffin in the current directory")
|
|
24
|
+
.option("--project <name>", "Project ID (defaults to package.json name or directory name)")
|
|
25
|
+
.action(async (options) => {
|
|
26
|
+
await executeInit(options);
|
|
27
|
+
});
|
|
28
|
+
program
|
|
29
|
+
.command("validate")
|
|
30
|
+
.description("Validate test plan files without syncing")
|
|
31
|
+
.action(async () => {
|
|
32
|
+
await executeValidate();
|
|
33
|
+
});
|
|
34
|
+
program
|
|
35
|
+
.command("generate-key")
|
|
36
|
+
.description("Generate a cryptographically secure API key for authentication")
|
|
37
|
+
.action(async () => {
|
|
38
|
+
await executeGenerateKey();
|
|
39
|
+
});
|
|
40
|
+
// Local command group
|
|
41
|
+
const local = program.command("local").description("Local test execution");
|
|
42
|
+
local
|
|
43
|
+
.command("run")
|
|
44
|
+
.description("Run tests locally against an environment")
|
|
45
|
+
.option("--env <name>", "Environment to run against (uses default if not specified)")
|
|
46
|
+
.action(async (options) => {
|
|
47
|
+
await executeRunLocal(options);
|
|
48
|
+
});
|
|
49
|
+
// Hub command group
|
|
50
|
+
const hub = program.command("hub").description("Griffin Hub operations");
|
|
51
|
+
hub
|
|
52
|
+
.command("connect")
|
|
53
|
+
.description("Configure hub connection")
|
|
54
|
+
.requiredOption("--url <url>", "Hub URL")
|
|
55
|
+
.option("--token <token>", "API authentication token")
|
|
56
|
+
.action(async (options) => {
|
|
57
|
+
await executeConnect(options);
|
|
58
|
+
});
|
|
59
|
+
hub
|
|
60
|
+
.command("status")
|
|
61
|
+
.description("Show hub connection status")
|
|
62
|
+
.action(async () => {
|
|
63
|
+
await executeStatus();
|
|
64
|
+
});
|
|
65
|
+
hub
|
|
66
|
+
.command("runs")
|
|
67
|
+
.description("Show recent runs from the hub")
|
|
68
|
+
.option("--plan <name>", "Filter by plan name")
|
|
69
|
+
.option("--limit <number>", "Number of runs to show", "10")
|
|
70
|
+
.action(async (options) => {
|
|
71
|
+
await executeRuns({
|
|
72
|
+
...options,
|
|
73
|
+
limit: parseInt(options.limit, 10),
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
hub
|
|
77
|
+
.command("plan")
|
|
78
|
+
.description("Show what changes would be applied")
|
|
79
|
+
.option("--env <name>", "Environment to plan for (uses default if not specified)")
|
|
80
|
+
.option("--json", "Output in JSON format")
|
|
81
|
+
.action(async (options) => {
|
|
82
|
+
await executePlan(options);
|
|
83
|
+
});
|
|
84
|
+
hub
|
|
85
|
+
.command("apply")
|
|
86
|
+
.description("Apply changes to the hub")
|
|
87
|
+
.option("--env <name>", "Environment to apply to (uses default if not specified)")
|
|
88
|
+
.option("--auto-approve", "Skip confirmation prompt")
|
|
89
|
+
.option("--dry-run", "Show what would be done without making changes")
|
|
90
|
+
.option("--prune", "Delete plans on hub that don't exist locally")
|
|
91
|
+
.action(async (options) => {
|
|
92
|
+
await executeApply(options);
|
|
93
|
+
});
|
|
94
|
+
hub
|
|
95
|
+
.command("run")
|
|
96
|
+
.description("Trigger a plan run on the hub")
|
|
97
|
+
.requiredOption("--plan <name>", "Plan name to run")
|
|
98
|
+
.requiredOption("--env <name>", "Target environment")
|
|
99
|
+
.option("--wait", "Wait for run to complete")
|
|
100
|
+
.option("--force", "Run even if local plan differs from hub")
|
|
101
|
+
.action(async (options) => {
|
|
102
|
+
await executeRun(options);
|
|
103
|
+
});
|
|
104
|
+
// Parse arguments
|
|
105
|
+
program.parse();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { loadState, resolveEnvironment } from "../core/state.js";
|
|
2
|
+
import { discoverPlans, formatDiscoveryErrors } from "../core/discovery.js";
|
|
3
|
+
import { computeDiff, formatDiff } from "../core/diff.js";
|
|
4
|
+
import { applyDiff, formatApplyResult } from "../core/apply.js";
|
|
5
|
+
import { createSdkClients } from "../core/sdk.js";
|
|
6
|
+
/**
|
|
7
|
+
* Apply changes to the runner
|
|
8
|
+
*/
|
|
9
|
+
export async function executeApply(options) {
|
|
10
|
+
try {
|
|
11
|
+
// Load state
|
|
12
|
+
const state = await loadState();
|
|
13
|
+
// Resolve environment
|
|
14
|
+
const envName = await resolveEnvironment(options.env);
|
|
15
|
+
// Check if runner is configured
|
|
16
|
+
if (!state.runner?.baseUrl) {
|
|
17
|
+
console.error("Error: Runner URL not configured.");
|
|
18
|
+
console.log("Configure with:");
|
|
19
|
+
console.log(" griffin runner set --base-url <url> --api-token <token>");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
console.log(`Applying to '${envName}' environment`);
|
|
23
|
+
console.log("");
|
|
24
|
+
// Create SDK clients
|
|
25
|
+
const { planApi } = createSdkClients({
|
|
26
|
+
baseUrl: state.runner.baseUrl,
|
|
27
|
+
apiToken: state.runner.apiToken || undefined,
|
|
28
|
+
});
|
|
29
|
+
// Discover local plans
|
|
30
|
+
const discoveryPattern = state.discovery?.pattern || "**/__griffin__/*.{ts,js}";
|
|
31
|
+
const discoveryIgnore = state.discovery?.ignore || [
|
|
32
|
+
"node_modules/**",
|
|
33
|
+
"dist/**",
|
|
34
|
+
];
|
|
35
|
+
const { plans, errors } = await discoverPlans(discoveryPattern, discoveryIgnore);
|
|
36
|
+
if (errors.length > 0) {
|
|
37
|
+
console.error(formatDiscoveryErrors(errors));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
// Fetch remote plans for this project
|
|
41
|
+
const response = await planApi.planGet(state.projectId);
|
|
42
|
+
const remotePlans = response.data.data.map((p) => p);
|
|
43
|
+
// Compute diff for this environment
|
|
44
|
+
const diff = computeDiff(plans.map((p) => p.plan), state, remotePlans, envName);
|
|
45
|
+
// Show plan
|
|
46
|
+
console.log(formatDiff(diff));
|
|
47
|
+
console.log("");
|
|
48
|
+
// Check if there are changes
|
|
49
|
+
if (diff.summary.creates + diff.summary.updates + diff.summary.deletes ===
|
|
50
|
+
0) {
|
|
51
|
+
console.log("No changes to apply.");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Ask for confirmation unless auto-approved
|
|
55
|
+
if (!options.autoApprove && !options.dryRun) {
|
|
56
|
+
console.log("Do you want to perform these actions? (yes/no)");
|
|
57
|
+
// For now, just proceed - in a real implementation, we'd use readline
|
|
58
|
+
// to get user input
|
|
59
|
+
console.log("Note: Use --auto-approve flag to skip confirmation");
|
|
60
|
+
console.log("");
|
|
61
|
+
}
|
|
62
|
+
// Apply changes
|
|
63
|
+
const result = await applyDiff(diff, state, planApi, envName, {
|
|
64
|
+
dryRun: options.dryRun,
|
|
65
|
+
});
|
|
66
|
+
console.log("");
|
|
67
|
+
console.log(formatApplyResult(result));
|
|
68
|
+
if (!result.success) {
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error(`Error: ${error.message}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface ConfigSetOptions {
|
|
2
|
+
organizationId: string;
|
|
3
|
+
environment: string;
|
|
4
|
+
targetKey: string;
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Set a target for an organization and environment
|
|
9
|
+
*/
|
|
10
|
+
export declare function executeConfigSet(options: ConfigSetOptions): Promise<void>;
|
|
11
|
+
export interface ConfigGetOptions {
|
|
12
|
+
organizationId: string;
|
|
13
|
+
environment: string;
|
|
14
|
+
targetKey: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get a target for an organization and environment
|
|
18
|
+
*/
|
|
19
|
+
export declare function executeConfigGet(options: ConfigGetOptions): Promise<void>;
|
|
20
|
+
export interface ConfigListOptions {
|
|
21
|
+
organizationId?: string;
|
|
22
|
+
environment?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* List all runner configs
|
|
26
|
+
*/
|
|
27
|
+
export declare function executeConfigList(options: ConfigListOptions): Promise<void>;
|
|
28
|
+
export interface ConfigDeleteOptions {
|
|
29
|
+
organizationId: string;
|
|
30
|
+
environment: string;
|
|
31
|
+
targetKey: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Delete a target from an organization and environment
|
|
35
|
+
*/
|
|
36
|
+
export declare function executeConfigDelete(options: ConfigDeleteOptions): Promise<void>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { loadState } from "../core/state.js";
|
|
2
|
+
import { createSdkClients } from "../core/sdk.js";
|
|
3
|
+
/**
|
|
4
|
+
* Set a target for an organization and environment
|
|
5
|
+
*/
|
|
6
|
+
export async function executeConfigSet(options) {
|
|
7
|
+
try {
|
|
8
|
+
const state = await loadState();
|
|
9
|
+
if (!state.runner?.baseUrl) {
|
|
10
|
+
console.error("Error: Runner URL not configured.");
|
|
11
|
+
console.log("Configure with:");
|
|
12
|
+
console.log(" griffin runner set --base-url <url> --api-token <token>");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const { configApi } = createSdkClients({
|
|
16
|
+
baseUrl: state.runner.baseUrl,
|
|
17
|
+
apiToken: state.runner.apiToken,
|
|
18
|
+
});
|
|
19
|
+
const result = await configApi.configOrganizationIdEnvironmentTargetsTargetKeyPut(options.organizationId, options.environment, options.targetKey, {
|
|
20
|
+
baseUrl: options.baseUrl,
|
|
21
|
+
});
|
|
22
|
+
//.setTarget(options.organizationId, options.environment, options.targetKey, options.baseUrl);
|
|
23
|
+
//const result = await runnerRequest<{ data: RunnerConfig }>(
|
|
24
|
+
// state.runner.baseUrl,
|
|
25
|
+
// state.runner.apiToken,
|
|
26
|
+
// path,
|
|
27
|
+
// {
|
|
28
|
+
// method: "PUT",
|
|
29
|
+
// body: JSON.stringify({ baseUrl: options.baseUrl }),
|
|
30
|
+
// },
|
|
31
|
+
//);
|
|
32
|
+
console.log(`✓ Target "${options.targetKey}" set to ${options.baseUrl}`);
|
|
33
|
+
console.log(` Organization: ${options.organizationId}`);
|
|
34
|
+
console.log(` Environment: ${options.environment}`);
|
|
35
|
+
console.log(` Updated at: ${new Date(result.data.data.updatedAt).toLocaleString()}`);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error(`Error: ${error.message}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get a target for an organization and environment
|
|
44
|
+
*/
|
|
45
|
+
export async function executeConfigGet(options) {
|
|
46
|
+
try {
|
|
47
|
+
const state = await loadState();
|
|
48
|
+
if (!state.runner?.baseUrl) {
|
|
49
|
+
console.error("Error: Runner URL not configured.");
|
|
50
|
+
console.log("Configure with:");
|
|
51
|
+
console.log(" griffin runner set --base-url <url> --api-token <token>");
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
const { configApi } = createSdkClients({
|
|
55
|
+
baseUrl: state.runner.baseUrl,
|
|
56
|
+
apiToken: state.runner.apiToken,
|
|
57
|
+
});
|
|
58
|
+
const result = await configApi.configOrganizationIdEnvironmentTargetsTargetKeyGet(options.organizationId, options.environment, options.targetKey);
|
|
59
|
+
console.log(`Target: ${options.targetKey}`);
|
|
60
|
+
console.log(`Base URL: ${result.data.data.baseUrl}`);
|
|
61
|
+
console.log(`Organization: ${options.organizationId}`);
|
|
62
|
+
console.log(`Environment: ${options.environment}`);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error(`Error: ${error.message}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* List all runner configs
|
|
71
|
+
*/
|
|
72
|
+
export async function executeConfigList(options) {
|
|
73
|
+
try {
|
|
74
|
+
const state = await loadState();
|
|
75
|
+
if (!state.runner?.baseUrl) {
|
|
76
|
+
console.error("Error: Runner URL not configured.");
|
|
77
|
+
console.log("Configure with:");
|
|
78
|
+
console.log(" griffin runner set --base-url <url> --api-token <token>");
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
const params = new URLSearchParams();
|
|
82
|
+
if (options.organizationId)
|
|
83
|
+
params.append("organizationId", options.organizationId);
|
|
84
|
+
if (options.environment)
|
|
85
|
+
params.append("environment", options.environment);
|
|
86
|
+
const { configApi } = createSdkClients({
|
|
87
|
+
baseUrl: state.runner.baseUrl,
|
|
88
|
+
apiToken: state.runner.apiToken,
|
|
89
|
+
});
|
|
90
|
+
const result = await configApi.configGet(options.organizationId, options.environment);
|
|
91
|
+
if (result.data.data.length === 0 || !result.data.data) {
|
|
92
|
+
console.log("No runner configs found.");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
console.log(`Found ${result.data.data.length} runner config(s):`);
|
|
96
|
+
console.log("");
|
|
97
|
+
for (const config of result.data.data) {
|
|
98
|
+
console.log(`Organization: ${config.organizationId}`);
|
|
99
|
+
console.log(`Environment: ${config.environment}`);
|
|
100
|
+
console.log(`Targets:`);
|
|
101
|
+
const targetCount = Object.keys(config.targets).length;
|
|
102
|
+
if (targetCount === 0) {
|
|
103
|
+
console.log(" (none)");
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
for (const [key, baseUrl] of Object.entries(config.targets)) {
|
|
107
|
+
console.log(` ${key}: ${baseUrl}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
console.log(`Updated: ${new Date(config.updatedAt).toLocaleString()}`);
|
|
111
|
+
console.log("");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error(`Error: ${error.message}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Delete a target from an organization and environment
|
|
121
|
+
*/
|
|
122
|
+
export async function executeConfigDelete(options) {
|
|
123
|
+
try {
|
|
124
|
+
const state = await loadState();
|
|
125
|
+
if (!state.runner?.baseUrl) {
|
|
126
|
+
console.error("Error: Runner URL not configured.");
|
|
127
|
+
console.log("Configure with:");
|
|
128
|
+
console.log(" griffin runner set --base-url <url> --api-token <token>");
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
const { configApi } = createSdkClients({
|
|
132
|
+
baseUrl: state.runner.baseUrl,
|
|
133
|
+
apiToken: state.runner.apiToken,
|
|
134
|
+
});
|
|
135
|
+
await configApi.configOrganizationIdEnvironmentTargetsTargetKeyDelete(options.organizationId, options.environment, options.targetKey);
|
|
136
|
+
console.log(`✓ Target "${options.targetKey}" deleted`);
|
|
137
|
+
console.log(` Organization: ${options.organizationId}`);
|
|
138
|
+
console.log(` Environment: ${options.environment}`);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
console.error(`Error: ${error.message}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
const CONFIG_DIR = path.join(os.homedir(), ".griffin");
|
|
5
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
6
|
+
export async function executeConfigureRunnerHost(host) {
|
|
7
|
+
console.log(`Configuring runner host: ${host}`);
|
|
8
|
+
// Ensure config directory exists
|
|
9
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
10
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
// Read existing config or create new one
|
|
13
|
+
let config = {};
|
|
14
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
15
|
+
try {
|
|
16
|
+
const configData = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
17
|
+
config = JSON.parse(configData);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
console.warn("Warning: Could not read existing config, creating new one");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Update runner host
|
|
24
|
+
config.runnerHost = host;
|
|
25
|
+
// Write config
|
|
26
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
27
|
+
console.log(`Configuration saved to ${CONFIG_FILE}`);
|
|
28
|
+
}
|
|
29
|
+
export function getRunnerHost() {
|
|
30
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const configData = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
35
|
+
const config = JSON.parse(configData);
|
|
36
|
+
return config.runnerHost || null;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function executeDeploy(): Promise<void>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { findTestFiles } from "../test-discovery";
|
|
2
|
+
import { getRunnerHost } from "./configure-runner-host";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
export async function executeDeploy() {
|
|
6
|
+
const runnerHost = getRunnerHost();
|
|
7
|
+
if (!runnerHost) {
|
|
8
|
+
console.error("ERROR: No runner host configured. Run: griffin configure-runner-host <host>");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
console.log("Deploying tests to runner...");
|
|
12
|
+
const testFiles = findTestFiles();
|
|
13
|
+
const workspaceRoot = findWorkspaceRoot();
|
|
14
|
+
const testSystemPath = path.join(workspaceRoot, "griffin-ts", "dist");
|
|
15
|
+
if (!fs.existsSync(testSystemPath)) {
|
|
16
|
+
throw new Error("Test system not built. Please run: cd griffin-ts && npm install && npm run build");
|
|
17
|
+
}
|
|
18
|
+
// TODO: Implement deployment logic
|
|
19
|
+
// - Read each test file
|
|
20
|
+
// - Execute it to get JSON plan
|
|
21
|
+
// - Send to runner API
|
|
22
|
+
// - Handle responses
|
|
23
|
+
console.log("Deployed.");
|
|
24
|
+
}
|
|
25
|
+
function findWorkspaceRoot() {
|
|
26
|
+
let current = process.cwd();
|
|
27
|
+
while (current !== path.dirname(current)) {
|
|
28
|
+
const griffinCliPath = path.join(current, "griffin-cli");
|
|
29
|
+
const testSystemPath = path.join(current, "griffin-ts");
|
|
30
|
+
if (fs.existsSync(griffinCliPath) && fs.existsSync(testSystemPath)) {
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
current = path.dirname(current);
|
|
34
|
+
}
|
|
35
|
+
return process.cwd();
|
|
36
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface EnvAddOptions {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
}
|
|
4
|
+
export interface EnvRemoveOptions {
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
export interface EnvDefaultOptions {
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* List all environments
|
|
12
|
+
*/
|
|
13
|
+
export declare function executeEnvList(): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Add or update an environment
|
|
16
|
+
*/
|
|
17
|
+
export declare function executeEnvAdd(name: string, options: EnvAddOptions): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Remove an environment
|
|
20
|
+
*/
|
|
21
|
+
export declare function executeEnvRemove(name: string): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Set default environment
|
|
24
|
+
*/
|
|
25
|
+
export declare function executeEnvDefault(name: string): Promise<void>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { loadState, addEnvironment, removeEnvironment, setDefaultEnvironment, } from "../core/state.js";
|
|
2
|
+
/**
|
|
3
|
+
* List all environments
|
|
4
|
+
*/
|
|
5
|
+
export async function executeEnvList() {
|
|
6
|
+
try {
|
|
7
|
+
const state = await loadState();
|
|
8
|
+
const envNames = Object.keys(state.environments);
|
|
9
|
+
if (envNames.length === 0) {
|
|
10
|
+
console.log("No environments configured.");
|
|
11
|
+
console.log("");
|
|
12
|
+
console.log("Add an environment with:");
|
|
13
|
+
console.log(" griffin env add <name> --base-url <url>");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
console.log("Environments:");
|
|
17
|
+
console.log("");
|
|
18
|
+
envNames.forEach((name) => {
|
|
19
|
+
const config = state.environments[name];
|
|
20
|
+
const isDefault = name === state.defaultEnvironment;
|
|
21
|
+
const marker = isDefault ? " (default)" : "";
|
|
22
|
+
console.log(` ${name}${marker}`);
|
|
23
|
+
console.log(` URL: ${config.baseUrl}`);
|
|
24
|
+
});
|
|
25
|
+
console.log("");
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error(`Error: ${error.message}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Add or update an environment
|
|
34
|
+
*/
|
|
35
|
+
export async function executeEnvAdd(name, options) {
|
|
36
|
+
try {
|
|
37
|
+
const state = await loadState();
|
|
38
|
+
const isUpdate = name in state.environments;
|
|
39
|
+
await addEnvironment(name, { baseUrl: options.baseUrl });
|
|
40
|
+
if (isUpdate) {
|
|
41
|
+
console.log(`✓ Updated environment '${name}'`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log(`✓ Added environment '${name}'`);
|
|
45
|
+
}
|
|
46
|
+
console.log(` URL: ${options.baseUrl}`);
|
|
47
|
+
// Show if this was set as default
|
|
48
|
+
const updatedState = await loadState();
|
|
49
|
+
if (updatedState.defaultEnvironment === name && !isUpdate) {
|
|
50
|
+
console.log(" (set as default - first environment)");
|
|
51
|
+
}
|
|
52
|
+
console.log("");
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error(`Error: ${error.message}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Remove an environment
|
|
61
|
+
*/
|
|
62
|
+
export async function executeEnvRemove(name) {
|
|
63
|
+
try {
|
|
64
|
+
const state = await loadState();
|
|
65
|
+
// Check if environment exists
|
|
66
|
+
if (!(name in state.environments)) {
|
|
67
|
+
console.error(`Error: Environment '${name}' does not exist`);
|
|
68
|
+
console.log("");
|
|
69
|
+
console.log("Available environments:");
|
|
70
|
+
Object.keys(state.environments).forEach((env) => {
|
|
71
|
+
console.log(` - ${env}`);
|
|
72
|
+
});
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
// Warn if there are plans in this environment
|
|
76
|
+
const planCount = state.plans[name]?.length || 0;
|
|
77
|
+
if (planCount > 0) {
|
|
78
|
+
console.log(`Warning: Environment '${name}' has ${planCount} synced plan(s).`);
|
|
79
|
+
console.log("This will remove all plan state for this environment.");
|
|
80
|
+
console.log("");
|
|
81
|
+
}
|
|
82
|
+
await removeEnvironment(name);
|
|
83
|
+
console.log(`✓ Removed environment '${name}'`);
|
|
84
|
+
// Show new default if it changed
|
|
85
|
+
const updatedState = await loadState();
|
|
86
|
+
if (updatedState.defaultEnvironment &&
|
|
87
|
+
updatedState.defaultEnvironment !== name) {
|
|
88
|
+
console.log(` New default: ${updatedState.defaultEnvironment}`);
|
|
89
|
+
}
|
|
90
|
+
console.log("");
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error(`Error: ${error.message}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Set default environment
|
|
99
|
+
*/
|
|
100
|
+
export async function executeEnvDefault(name) {
|
|
101
|
+
try {
|
|
102
|
+
const state = await loadState();
|
|
103
|
+
// Check if environment exists
|
|
104
|
+
if (!(name in state.environments)) {
|
|
105
|
+
console.error(`Error: Environment '${name}' does not exist`);
|
|
106
|
+
console.log("");
|
|
107
|
+
console.log("Available environments:");
|
|
108
|
+
Object.keys(state.environments).forEach((env) => {
|
|
109
|
+
console.log(` - ${env}`);
|
|
110
|
+
});
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
await setDefaultEnvironment(name);
|
|
114
|
+
console.log(`✓ Set '${name}' as default environment`);
|
|
115
|
+
console.log("");
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.error(`Error: ${error.message}`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function executeExecuteRemote(checkName: string): Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getRunnerHost } from "./configure-runner-host";
|
|
2
|
+
const axios = require("axios");
|
|
3
|
+
import * as readline from "readline";
|
|
4
|
+
export async function executeExecuteRemote(checkName) {
|
|
5
|
+
const runnerHost = getRunnerHost();
|
|
6
|
+
if (!runnerHost) {
|
|
7
|
+
console.error("ERROR: No runner host configured. Run: griffin configure-runner-host <host>");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
console.log("WARNING: Your local checks will be deployed and executed remotely.");
|
|
11
|
+
console.log("Do you want to continue? (y/n)");
|
|
12
|
+
const rl = readline.createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout,
|
|
15
|
+
});
|
|
16
|
+
rl.question("", async (answer) => {
|
|
17
|
+
rl.close();
|
|
18
|
+
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
19
|
+
console.log("Cancelled.");
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
// TODO: Implement remote execution
|
|
24
|
+
const response = await axios.post(`${runnerHost}/api/tests/${checkName}/execute`);
|
|
25
|
+
console.log(JSON.stringify(response.data, null, 2));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error("ERROR: Failed to execute remotely");
|
|
29
|
+
console.error(error.message || String(error));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a cryptographically secure API key for griffin-runner authentication.
|
|
3
|
+
*
|
|
4
|
+
* The key format is: grfn_sk_<48-character-hex-string>
|
|
5
|
+
* - grfn: Griffin prefix
|
|
6
|
+
* - sk: Secret Key
|
|
7
|
+
* - 48 hex chars: 24 random bytes = 192 bits of entropy
|
|
8
|
+
*/
|
|
9
|
+
export declare function executeGenerateKey(): Promise<void>;
|