@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
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TestPlanV1 } from "@griffin-app/griffin-ts/types";
|
|
2
|
+
export type RawTestPlan = Omit<TestPlanV1, "id" | "environment" | "project">;
|
|
3
|
+
export interface DiscoveredPlan {
|
|
4
|
+
plan: RawTestPlan;
|
|
5
|
+
filePath: string;
|
|
6
|
+
exportName: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DiscoveryResult {
|
|
9
|
+
plans: DiscoveredPlan[];
|
|
10
|
+
errors: DiscoveryError[];
|
|
11
|
+
}
|
|
12
|
+
export interface DiscoveryError {
|
|
13
|
+
filePath: string;
|
|
14
|
+
error: Error;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Discover and load test plan files from the filesystem
|
|
18
|
+
*/
|
|
19
|
+
export declare function discoverPlans(pattern: string, ignore: string[]): Promise<DiscoveryResult>;
|
|
20
|
+
/**
|
|
21
|
+
* Format discovery errors for display
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatDiscoveryErrors(errors: DiscoveryError[]): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { TestPlanV1Schema } from "@griffin-app/griffin-ts/schema";
|
|
5
|
+
import { Value } from "typebox/value";
|
|
6
|
+
import { Type } from "typebox";
|
|
7
|
+
const RawTestPlanSchema = Type.Omit(TestPlanV1Schema, [
|
|
8
|
+
"id",
|
|
9
|
+
"environment",
|
|
10
|
+
"project",
|
|
11
|
+
]);
|
|
12
|
+
/**
|
|
13
|
+
* Discover and load test plan files from the filesystem
|
|
14
|
+
*/
|
|
15
|
+
export async function discoverPlans(pattern, ignore) {
|
|
16
|
+
const plans = [];
|
|
17
|
+
const errors = [];
|
|
18
|
+
// Find all matching files
|
|
19
|
+
const files = await glob(pattern, {
|
|
20
|
+
ignore,
|
|
21
|
+
absolute: true,
|
|
22
|
+
cwd: process.cwd(),
|
|
23
|
+
});
|
|
24
|
+
console.log(`Found ${files.length} test file(s)`);
|
|
25
|
+
// Load each file
|
|
26
|
+
for (const filePath of files) {
|
|
27
|
+
try {
|
|
28
|
+
const loaded = await loadPlansFromFile(filePath);
|
|
29
|
+
plans.push(...loaded);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
errors.push({
|
|
33
|
+
filePath,
|
|
34
|
+
error: error,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { plans, errors };
|
|
39
|
+
}
|
|
40
|
+
function isPlan(value) {
|
|
41
|
+
return Value.Check(RawTestPlanSchema, value);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Load plans from a single file
|
|
45
|
+
* Supports both default and named exports
|
|
46
|
+
*/
|
|
47
|
+
async function loadPlansFromFile(filePath) {
|
|
48
|
+
const plans = [];
|
|
49
|
+
// Convert to file URL for dynamic import (works with both ESM and CJS)
|
|
50
|
+
const fileUrl = pathToFileURL(filePath).href;
|
|
51
|
+
try {
|
|
52
|
+
const module = await import(fileUrl);
|
|
53
|
+
// Check default export
|
|
54
|
+
if (module.default) {
|
|
55
|
+
if (isPlan(module.default)) {
|
|
56
|
+
plans.push({
|
|
57
|
+
plan: module.default,
|
|
58
|
+
filePath,
|
|
59
|
+
exportName: "default",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const errors = Value.Errors(RawTestPlanSchema, module.default);
|
|
64
|
+
throw new Error(`Default export is not a valid TestPlan. Got: ${JSON.stringify(errors, null, 2)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (plans.length === 0) {
|
|
68
|
+
throw new Error("No valid TestPlan exports found in file");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
throw new Error(`Failed to load ${filePath}: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
return plans;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Format discovery errors for display
|
|
78
|
+
*/
|
|
79
|
+
export function formatDiscoveryErrors(errors) {
|
|
80
|
+
if (errors.length === 0)
|
|
81
|
+
return "";
|
|
82
|
+
const lines = ["Errors during discovery:"];
|
|
83
|
+
for (const { filePath, error } of errors) {
|
|
84
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
85
|
+
lines.push(` ❌ ${relativePath}`);
|
|
86
|
+
lines.push(` ${error.message}`);
|
|
87
|
+
}
|
|
88
|
+
return lines.join("\n");
|
|
89
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { RawTestPlan } from "./discovery.js";
|
|
2
|
+
import type { TestPlanV1 } from "@griffin-app/griffin-ts/types";
|
|
3
|
+
import { NodeType } from "@griffin-app/griffin-ts/schema";
|
|
4
|
+
/**
|
|
5
|
+
* Represents a change to a single field
|
|
6
|
+
*/
|
|
7
|
+
export interface FieldChange {
|
|
8
|
+
field: string;
|
|
9
|
+
oldValue: unknown;
|
|
10
|
+
newValue: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Represents a change to a node (add/remove/modify)
|
|
14
|
+
*/
|
|
15
|
+
export interface NodeChange {
|
|
16
|
+
type: "add" | "remove" | "modify";
|
|
17
|
+
nodeId: string;
|
|
18
|
+
nodeType: NodeType;
|
|
19
|
+
summary: string;
|
|
20
|
+
fieldChanges: FieldChange[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Represents a change to an edge (add/remove)
|
|
24
|
+
*/
|
|
25
|
+
export interface EdgeChange {
|
|
26
|
+
type: "add" | "remove";
|
|
27
|
+
from: string;
|
|
28
|
+
to: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Complete set of changes between local and remote plans
|
|
32
|
+
*/
|
|
33
|
+
export interface PlanChanges {
|
|
34
|
+
hasChanges: boolean;
|
|
35
|
+
nodes: NodeChange[];
|
|
36
|
+
edges: EdgeChange[];
|
|
37
|
+
topLevel: FieldChange[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Compare two test plans and return granular changes
|
|
41
|
+
*/
|
|
42
|
+
export declare function comparePlans(local: RawTestPlan, remote: TestPlanV1): PlanChanges;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import objectHash from "object-hash";
|
|
2
|
+
/**
|
|
3
|
+
* Compare two test plans and return granular changes
|
|
4
|
+
*/
|
|
5
|
+
export function comparePlans(local, remote) {
|
|
6
|
+
const nodeChanges = compareNodes(local.nodes, remote.nodes);
|
|
7
|
+
const edgeChanges = compareEdges(local.edges, remote.edges);
|
|
8
|
+
const topLevelChanges = compareTopLevel(local, remote);
|
|
9
|
+
const hasChanges = nodeChanges.length > 0 ||
|
|
10
|
+
edgeChanges.length > 0 ||
|
|
11
|
+
topLevelChanges.length > 0;
|
|
12
|
+
return {
|
|
13
|
+
hasChanges,
|
|
14
|
+
nodes: nodeChanges,
|
|
15
|
+
edges: edgeChanges,
|
|
16
|
+
topLevel: topLevelChanges,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Compare nodes between local and remote plans
|
|
21
|
+
*/
|
|
22
|
+
function compareNodes(localNodes, remoteNodes) {
|
|
23
|
+
const changes = [];
|
|
24
|
+
// Build map of remote nodes by id
|
|
25
|
+
const remoteByID = new Map();
|
|
26
|
+
for (const node of remoteNodes) {
|
|
27
|
+
remoteByID.set(node.id, node);
|
|
28
|
+
}
|
|
29
|
+
const localIDs = new Set();
|
|
30
|
+
for (const node of localNodes) {
|
|
31
|
+
localIDs.add(node.id);
|
|
32
|
+
}
|
|
33
|
+
// Check local nodes
|
|
34
|
+
for (const local of localNodes) {
|
|
35
|
+
const remote = remoteByID.get(local.id);
|
|
36
|
+
if (!remote) {
|
|
37
|
+
// Node added
|
|
38
|
+
changes.push({
|
|
39
|
+
type: "add",
|
|
40
|
+
nodeId: local.id,
|
|
41
|
+
nodeType: local.type,
|
|
42
|
+
summary: getNodeSummary(local),
|
|
43
|
+
fieldChanges: [],
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Node exists - check for modifications
|
|
48
|
+
const fieldChanges = compareNodeFields(local, remote);
|
|
49
|
+
if (fieldChanges.length > 0) {
|
|
50
|
+
changes.push({
|
|
51
|
+
type: "modify",
|
|
52
|
+
nodeId: local.id,
|
|
53
|
+
nodeType: local.type,
|
|
54
|
+
summary: getNodeSummary(local),
|
|
55
|
+
fieldChanges,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Check for removed nodes
|
|
61
|
+
for (const remote of remoteNodes) {
|
|
62
|
+
if (!localIDs.has(remote.id)) {
|
|
63
|
+
changes.push({
|
|
64
|
+
type: "remove",
|
|
65
|
+
nodeId: remote.id,
|
|
66
|
+
nodeType: remote.type,
|
|
67
|
+
summary: getNodeSummary(remote),
|
|
68
|
+
fieldChanges: [],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return changes;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get a human-readable summary of a node
|
|
76
|
+
*/
|
|
77
|
+
function getNodeSummary(node) {
|
|
78
|
+
switch (node.type) {
|
|
79
|
+
case "ENDPOINT":
|
|
80
|
+
return `${node.method} ${formatValue(node.path)}`;
|
|
81
|
+
case "WAIT":
|
|
82
|
+
return `wait ${node.duration_ms}ms`;
|
|
83
|
+
case "ASSERTION":
|
|
84
|
+
return `${node.assertions.length} assertion(s)`;
|
|
85
|
+
default:
|
|
86
|
+
return node.type;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Format a value for display (handle VariableRef objects)
|
|
91
|
+
*/
|
|
92
|
+
function formatValue(value) {
|
|
93
|
+
if (typeof value === "string") {
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
if (value &&
|
|
97
|
+
typeof value === "object" &&
|
|
98
|
+
"$variable" in value &&
|
|
99
|
+
typeof value.$variable === "object") {
|
|
100
|
+
return `$\{${value.$variable.key}}`;
|
|
101
|
+
}
|
|
102
|
+
return JSON.stringify(value);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Compare fields within two nodes of the same type
|
|
106
|
+
*/
|
|
107
|
+
function compareNodeFields(local, remote) {
|
|
108
|
+
const changes = [];
|
|
109
|
+
// Type should match, but check anyway
|
|
110
|
+
if (local.type !== remote.type) {
|
|
111
|
+
changes.push({
|
|
112
|
+
field: "type",
|
|
113
|
+
oldValue: remote.type,
|
|
114
|
+
newValue: local.type,
|
|
115
|
+
});
|
|
116
|
+
return changes;
|
|
117
|
+
}
|
|
118
|
+
switch (local.type) {
|
|
119
|
+
case "ENDPOINT":
|
|
120
|
+
compareEndpointFields(local, remote, changes);
|
|
121
|
+
break;
|
|
122
|
+
case "WAIT":
|
|
123
|
+
compareWaitFields(local, remote, changes);
|
|
124
|
+
break;
|
|
125
|
+
case "ASSERTION":
|
|
126
|
+
compareAssertionFields(local, remote, changes);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
return changes;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Compare fields specific to Endpoint nodes
|
|
133
|
+
*/
|
|
134
|
+
function compareEndpointFields(local, remote, changes) {
|
|
135
|
+
const fields = [
|
|
136
|
+
"method",
|
|
137
|
+
"path",
|
|
138
|
+
"base",
|
|
139
|
+
"headers",
|
|
140
|
+
"body",
|
|
141
|
+
"response_format",
|
|
142
|
+
];
|
|
143
|
+
for (const field of fields) {
|
|
144
|
+
const localVal = local[field];
|
|
145
|
+
const remoteVal = remote[field];
|
|
146
|
+
if (!deepEqual(localVal, remoteVal)) {
|
|
147
|
+
changes.push({
|
|
148
|
+
field: field,
|
|
149
|
+
oldValue: remoteVal,
|
|
150
|
+
newValue: localVal,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Compare fields specific to Wait nodes
|
|
157
|
+
*/
|
|
158
|
+
function compareWaitFields(local, remote, changes) {
|
|
159
|
+
if (local.duration_ms !== remote.duration_ms) {
|
|
160
|
+
changes.push({
|
|
161
|
+
field: "duration_ms",
|
|
162
|
+
oldValue: remote.duration_ms,
|
|
163
|
+
newValue: local.duration_ms,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Compare fields specific to Assertion nodes
|
|
169
|
+
*/
|
|
170
|
+
function compareAssertionFields(local, remote, changes) {
|
|
171
|
+
if (!deepEqual(local.assertions, remote.assertions)) {
|
|
172
|
+
changes.push({
|
|
173
|
+
field: "assertions",
|
|
174
|
+
oldValue: remote.assertions,
|
|
175
|
+
newValue: local.assertions,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Compare edges between local and remote plans
|
|
181
|
+
*/
|
|
182
|
+
function compareEdges(localEdges, remoteEdges) {
|
|
183
|
+
const changes = [];
|
|
184
|
+
// Build map of remote edges by "from:to" key
|
|
185
|
+
const remoteByKey = new Map();
|
|
186
|
+
for (const edge of remoteEdges) {
|
|
187
|
+
remoteByKey.set(`${edge.from}:${edge.to}`, edge);
|
|
188
|
+
}
|
|
189
|
+
const localKeys = new Set();
|
|
190
|
+
for (const edge of localEdges) {
|
|
191
|
+
localKeys.add(`${edge.from}:${edge.to}`);
|
|
192
|
+
}
|
|
193
|
+
// Check local edges
|
|
194
|
+
for (const local of localEdges) {
|
|
195
|
+
const key = `${local.from}:${local.to}`;
|
|
196
|
+
if (!remoteByKey.has(key)) {
|
|
197
|
+
changes.push({
|
|
198
|
+
type: "add",
|
|
199
|
+
from: local.from,
|
|
200
|
+
to: local.to,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Check for removed edges
|
|
205
|
+
for (const remote of remoteEdges) {
|
|
206
|
+
const key = `${remote.from}:${remote.to}`;
|
|
207
|
+
if (!localKeys.has(key)) {
|
|
208
|
+
changes.push({
|
|
209
|
+
type: "remove",
|
|
210
|
+
from: remote.from,
|
|
211
|
+
to: remote.to,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return changes;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Compare top-level fields: frequency, version, locations
|
|
219
|
+
*/
|
|
220
|
+
function compareTopLevel(local, remote) {
|
|
221
|
+
const changes = [];
|
|
222
|
+
// Compare frequency
|
|
223
|
+
if (!deepEqual(local.frequency, remote.frequency)) {
|
|
224
|
+
changes.push({
|
|
225
|
+
field: "frequency",
|
|
226
|
+
oldValue: remote.frequency,
|
|
227
|
+
newValue: local.frequency,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
// Compare version
|
|
231
|
+
if (local.version !== remote.version) {
|
|
232
|
+
changes.push({
|
|
233
|
+
field: "version",
|
|
234
|
+
oldValue: remote.version,
|
|
235
|
+
newValue: local.version,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
// Compare locations (normalize empty array to undefined)
|
|
239
|
+
const localLocations = local.locations;
|
|
240
|
+
const remoteLocations = remote.locations && remote.locations.length > 0
|
|
241
|
+
? remote.locations
|
|
242
|
+
: undefined;
|
|
243
|
+
if (!deepEqual(localLocations, remoteLocations)) {
|
|
244
|
+
changes.push({
|
|
245
|
+
field: "locations",
|
|
246
|
+
oldValue: remoteLocations,
|
|
247
|
+
newValue: localLocations,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return changes;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Deep equality check using object-hash
|
|
254
|
+
*/
|
|
255
|
+
function deepEqual(a, b) {
|
|
256
|
+
return objectHash(a ?? null) === objectHash(b ?? null);
|
|
257
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Auto-detect project ID from the environment
|
|
5
|
+
*
|
|
6
|
+
* Priority:
|
|
7
|
+
* 1. package.json name field
|
|
8
|
+
* 2. Directory name
|
|
9
|
+
*/
|
|
10
|
+
export async function detectProjectId() {
|
|
11
|
+
try {
|
|
12
|
+
// Try to find package.json walking up from current directory
|
|
13
|
+
const packageJsonPath = await findPackageJson(process.cwd());
|
|
14
|
+
if (packageJsonPath) {
|
|
15
|
+
const content = await fs.readFile(packageJsonPath, "utf-8");
|
|
16
|
+
const pkg = JSON.parse(content);
|
|
17
|
+
if (pkg.name && typeof pkg.name === "string") {
|
|
18
|
+
return pkg.name;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
// Fall through to directory name
|
|
24
|
+
}
|
|
25
|
+
// Fallback to directory name
|
|
26
|
+
return path.basename(process.cwd());
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Find package.json by walking up the directory tree
|
|
30
|
+
*/
|
|
31
|
+
async function findPackageJson(startDir) {
|
|
32
|
+
let currentDir = startDir;
|
|
33
|
+
const root = path.parse(currentDir).root;
|
|
34
|
+
while (currentDir !== root) {
|
|
35
|
+
const packageJsonPath = path.join(currentDir, "package.json");
|
|
36
|
+
try {
|
|
37
|
+
await fs.access(packageJsonPath);
|
|
38
|
+
return packageJsonPath;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Not found, go up one level
|
|
42
|
+
currentDir = path.dirname(currentDir);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PlanApi, RunsApi } from "@griffin-app/griffin-hub-sdk";
|
|
2
|
+
import type { TestPlanV1 } from "@griffin-app/griffin-ts/types";
|
|
3
|
+
/**
|
|
4
|
+
* Create configured SDK API instances
|
|
5
|
+
*/
|
|
6
|
+
export declare function createSdkClients(config: {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
apiToken?: string;
|
|
9
|
+
}): {
|
|
10
|
+
planApi: PlanApi;
|
|
11
|
+
runsApi: RunsApi;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Inject projectId into a plan payload before sending to runner
|
|
15
|
+
*/
|
|
16
|
+
export declare function injectProjectId(plan: Omit<TestPlanV1, "project">, projectId: string): TestPlanV1;
|
package/dist/core/sdk.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PlanApi, RunsApi, Configuration } from "@griffin-app/griffin-hub-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Create configured SDK API instances
|
|
4
|
+
*/
|
|
5
|
+
export function createSdkClients(config) {
|
|
6
|
+
const configuration = new Configuration({
|
|
7
|
+
basePath: config.baseUrl.replace(/\/$/, ""), // Remove trailing slash
|
|
8
|
+
accessToken: config.apiToken,
|
|
9
|
+
});
|
|
10
|
+
return {
|
|
11
|
+
planApi: new PlanApi(configuration),
|
|
12
|
+
runsApi: new RunsApi(configuration),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Inject projectId into a plan payload before sending to runner
|
|
17
|
+
*/
|
|
18
|
+
export function injectProjectId(plan, projectId) {
|
|
19
|
+
return {
|
|
20
|
+
...plan,
|
|
21
|
+
project: projectId,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type StateFile, type EnvironmentConfig } from "../schemas/state.js";
|
|
2
|
+
export declare const STATE_DIR = ".griffin";
|
|
3
|
+
export declare const STATE_FILE = "state.json";
|
|
4
|
+
/**
|
|
5
|
+
* Get the state directory path (in current working directory)
|
|
6
|
+
*/
|
|
7
|
+
export declare function getStateDirPath(): string;
|
|
8
|
+
/**
|
|
9
|
+
* Get the state file path
|
|
10
|
+
*/
|
|
11
|
+
export declare function getStateFilePath(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Check if state file exists
|
|
14
|
+
*/
|
|
15
|
+
export declare function stateExists(): Promise<boolean>;
|
|
16
|
+
/**
|
|
17
|
+
* Load state file from disk
|
|
18
|
+
* Throws if file doesn't exist or is invalid
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadState(): Promise<StateFile>;
|
|
21
|
+
/**
|
|
22
|
+
* Save state file to disk
|
|
23
|
+
*/
|
|
24
|
+
export declare function saveState(state: StateFile): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Initialize a new state file
|
|
27
|
+
*/
|
|
28
|
+
export declare function initState(projectId: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Add or update an environment
|
|
31
|
+
*/
|
|
32
|
+
export declare function addEnvironment(name: string, config: EnvironmentConfig): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Remove an environment
|
|
35
|
+
*/
|
|
36
|
+
export declare function removeEnvironment(name: string): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Set the default environment
|
|
39
|
+
*/
|
|
40
|
+
export declare function setDefaultEnvironment(name: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Get the current environment name (from flag, env var, or default)
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveEnvironment(envFlag?: string): Promise<string>;
|
|
45
|
+
/**
|
|
46
|
+
* Get environment configuration
|
|
47
|
+
*/
|
|
48
|
+
export declare function getEnvironment(name: string): Promise<EnvironmentConfig>;
|
|
49
|
+
/**
|
|
50
|
+
* Get the project ID
|
|
51
|
+
*/
|
|
52
|
+
export declare function getProjectId(): Promise<string>;
|