@griffin-app/griffin-cli 1.0.2 → 1.0.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.
@@ -1,7 +1,6 @@
1
- import type { TestPlanV1 } from "@griffin-app/griffin-ts/types";
2
- export type RawTestPlan = Omit<TestPlanV1, "id" | "environment" | "project">;
1
+ import type { PlanDSL } from "@griffin-app/griffin-ts/types";
3
2
  export interface DiscoveredPlan {
4
- plan: RawTestPlan;
3
+ plan: PlanDSL;
5
4
  filePath: string;
6
5
  exportName: string;
7
6
  }
@@ -1,14 +1,8 @@
1
1
  import { glob } from "glob";
2
2
  import path from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
- import { TestPlanV1Schema } from "@griffin-app/griffin-ts/schema";
4
+ import { PlanDSLSchema } from "@griffin-app/griffin-ts/schema";
5
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
6
  /**
13
7
  * Discover and load test plan files from the filesystem
14
8
  */
@@ -21,7 +15,6 @@ export async function discoverPlans(pattern, ignore) {
21
15
  absolute: true,
22
16
  cwd: process.cwd(),
23
17
  });
24
- console.log(`Found ${files.length} test file(s)`);
25
18
  // Load each file
26
19
  for (const filePath of files) {
27
20
  try {
@@ -38,7 +31,7 @@ export async function discoverPlans(pattern, ignore) {
38
31
  return { plans, errors };
39
32
  }
40
33
  function isPlan(value) {
41
- return Value.Check(RawTestPlanSchema, value);
34
+ return Value.Check(PlanDSLSchema, value);
42
35
  }
43
36
  /**
44
37
  * Load plans from a single file
@@ -60,7 +53,7 @@ async function loadPlansFromFile(filePath) {
60
53
  });
61
54
  }
62
55
  else {
63
- const errors = Value.Errors(RawTestPlanSchema, module.default);
56
+ const errors = Value.Errors(PlanDSLSchema, module.default);
64
57
  throw new Error(`Default export is not a valid TestPlan. Got: ${JSON.stringify(errors, null, 2)}`);
65
58
  }
66
59
  }
@@ -1,5 +1,5 @@
1
- import type { RawTestPlan } from "./discovery.js";
2
- import type { TestPlanV1 } from "@griffin-app/griffin-ts/types";
1
+ import type { PlanDSL } from "@griffin-app/griffin-ts/types";
2
+ import { PlanV1 } from "@griffin-app/griffin-hub-sdk";
3
3
  import { NodeType } from "@griffin-app/griffin-ts/schema";
4
4
  /**
5
5
  * Represents a change to a single field
@@ -15,7 +15,7 @@ export interface FieldChange {
15
15
  export interface NodeChange {
16
16
  type: "add" | "remove" | "modify";
17
17
  nodeId: string;
18
- nodeType: NodeType;
18
+ nodeType: NodeType.ASSERTION | NodeType.ENDPOINT | NodeType.WAIT;
19
19
  summary: string;
20
20
  fieldChanges: FieldChange[];
21
21
  }
@@ -39,4 +39,4 @@ export interface PlanChanges {
39
39
  /**
40
40
  * Compare two test plans and return granular changes
41
41
  */
42
- export declare function comparePlans(local: RawTestPlan, remote: TestPlanV1): PlanChanges;
42
+ export declare function comparePlans(local: PlanDSL, remote: PlanV1): PlanChanges;
@@ -1,4 +1,5 @@
1
1
  import objectHash from "object-hash";
2
+ import { NodeType } from "@griffin-app/griffin-ts/schema";
2
3
  /**
3
4
  * Compare two test plans and return granular changes
4
5
  */
@@ -116,13 +117,13 @@ function compareNodeFields(local, remote) {
116
117
  return changes;
117
118
  }
118
119
  switch (local.type) {
119
- case "ENDPOINT":
120
+ case NodeType.ENDPOINT:
120
121
  compareEndpointFields(local, remote, changes);
121
122
  break;
122
- case "WAIT":
123
+ case NodeType.WAIT:
123
124
  compareWaitFields(local, remote, changes);
124
125
  break;
125
- case "ASSERTION":
126
+ case NodeType.ASSERTION:
126
127
  compareAssertionFields(local, remote, changes);
127
128
  break;
128
129
  }
@@ -1,16 +1,8 @@
1
- import { PlanApi, RunsApi } from "@griffin-app/griffin-hub-sdk";
2
- import type { TestPlanV1 } from "@griffin-app/griffin-ts/types";
1
+ import { GriffinHubSdk } from "@griffin-app/griffin-hub-sdk";
3
2
  /**
4
3
  * Create configured SDK API instances
5
4
  */
6
- export declare function createSdkClients(config: {
5
+ export declare function createSdk(config: {
7
6
  baseUrl: string;
8
7
  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;
8
+ }): GriffinHubSdk;
package/dist/core/sdk.js CHANGED
@@ -1,23 +1,19 @@
1
- import { PlanApi, RunsApi, Configuration } from "@griffin-app/griffin-hub-sdk";
1
+ import { GriffinHubSdk } from "@griffin-app/griffin-hub-sdk";
2
+ import { createClient } from "@griffin-app/griffin-hub-sdk/client";
2
3
  /**
3
4
  * Create configured SDK API instances
4
5
  */
5
- export function createSdkClients(config) {
6
- const configuration = new Configuration({
7
- basePath: config.baseUrl.replace(/\/$/, ""), // Remove trailing slash
8
- accessToken: config.apiToken,
6
+ export function createSdk(config) {
7
+ const client = createClient({
8
+ baseURL: config.baseUrl,
9
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
- };
10
+ client.setConfig({
11
+ auth() {
12
+ return config.apiToken;
13
+ },
14
+ });
15
+ const sdk = new GriffinHubSdk({
16
+ client: client,
17
+ });
18
+ return sdk;
23
19
  }
@@ -46,6 +46,17 @@ function isVariableRef(value) {
46
46
  return (typeof varData.key === "string" &&
47
47
  (varData.template === undefined || typeof varData.template === "string"));
48
48
  }
49
+ function isStringLiteral(value) {
50
+ if (typeof value !== "object" || value === null) {
51
+ return false;
52
+ }
53
+ const obj = value;
54
+ if (!("$literal" in obj) ||
55
+ typeof obj.$literal !== "string") {
56
+ return false;
57
+ }
58
+ return true;
59
+ }
49
60
  /**
50
61
  * Resolve a single variable reference to its string value.
51
62
  *
@@ -83,6 +94,9 @@ export function resolveVariablesInPlan(obj, variables) {
83
94
  if (isVariableRef(obj)) {
84
95
  return resolveVariable(obj, variables);
85
96
  }
97
+ if (isStringLiteral(obj)) {
98
+ return obj.$literal;
99
+ }
86
100
  // Recursively process arrays
87
101
  if (Array.isArray(obj)) {
88
102
  return obj.map((item) => resolveVariablesInPlan(item, variables));
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export { getStateDirPath, getStateFilePath, stateExists, loadState, saveState, i
7
7
  export { discoverPlans, formatDiscoveryErrors } from "./core/discovery.js";
8
8
  export { computeDiff, formatDiff, formatDiffJson } from "./core/diff.js";
9
9
  export { applyDiff, formatApplyResult } from "./core/apply.js";
10
- export { createSdkClients, injectProjectId } from "./core/sdk.js";
10
+ export { createSdk } from "./core/sdk.js";
11
11
  export { detectProjectId } from "./core/project.js";
12
12
  export { executeInit } from "./commands/init.js";
13
13
  export { executeValidate } from "./commands/validate.js";
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ export { getStateDirPath, getStateFilePath, stateExists, loadState, saveState, i
4
4
  export { discoverPlans, formatDiscoveryErrors } from "./core/discovery.js";
5
5
  export { computeDiff, formatDiff, formatDiffJson } from "./core/diff.js";
6
6
  export { applyDiff, formatApplyResult } from "./core/apply.js";
7
- export { createSdkClients, injectProjectId } from "./core/sdk.js";
7
+ export { createSdk } from "./core/sdk.js";
8
8
  export { detectProjectId } from "./core/project.js";
9
9
  // Export command executors (for programmatic use)
10
10
  export { executeInit } from "./commands/init.js";
@@ -0,0 +1,3 @@
1
+ import { PlanV1 } from "@griffin-app/griffin-hub-sdk";
2
+ import { PlanDSL } from "@griffin-app/griffin-ts/types";
3
+ export declare function resolvePlan(plan: PlanDSL, projectId: string, envName: string, variables: Record<string, string>): Omit<PlanV1, "id">;
@@ -0,0 +1,9 @@
1
+ import { resolveVariablesInPlan } from "./core/variables.js";
2
+ export function resolvePlan(plan, projectId, envName, variables) {
3
+ const resolvedPlan = resolveVariablesInPlan(plan, variables);
4
+ return {
5
+ ...resolvedPlan,
6
+ project: projectId,
7
+ environment: envName,
8
+ };
9
+ }
@@ -6,9 +6,7 @@ import { Type } from "typebox";
6
6
  * Note: The hub is now the source of truth for plans.
7
7
  * This file only stores configuration (project, environments, runner connection).
8
8
  */
9
- export const EnvironmentConfigSchema = Type.Object({
10
- // Empty for now - may be used for environment-specific config in the future
11
- });
9
+ export const EnvironmentConfigSchema = Type.Object({});
12
10
  export const RunnerConfigSchema = Type.Object({
13
11
  baseUrl: Type.String(),
14
12
  apiToken: Type.Optional(Type.String()),
@@ -1,16 +1,19 @@
1
1
  import "tsx";
2
2
  import { Value } from "typebox/value";
3
3
  import { executePlanV1, AxiosAdapter, EnvSecretProvider, SecretProviderRegistry, } from "@griffin-app/griffin-plan-executor";
4
- import { TestPlanV1Schema } from "@griffin-app/griffin-ts/schema";
5
- import { Type } from "typebox";
4
+ import { PlanDSLSchema } from "@griffin-app/griffin-ts/schema";
6
5
  import { randomUUID } from "crypto";
7
- import { loadVariables, resolveVariablesInPlan } from "./core/variables.js";
6
+ import { loadVariables } from "./core/variables.js";
8
7
  import { getProjectId } from "./core/state.js";
9
- const RawTestSchema = Type.Omit(TestPlanV1Schema, [
10
- "id",
11
- "environment",
12
- "project",
13
- ]);
8
+ import { resolvePlan } from "./resolve.js";
9
+ import { terminal } from "./utils/terminal.js";
10
+ function validateDsl(plan) {
11
+ const errors = Value.Errors(PlanDSLSchema, plan);
12
+ if (errors.length > 0) {
13
+ throw new Error(`Invalid plan: ${JSON.stringify([...errors], null, 2)}`);
14
+ }
15
+ return plan;
16
+ }
14
17
  /**
15
18
  * Runs a TypeScript test file and executes the resulting JSON plan.
16
19
  */
@@ -18,31 +21,24 @@ export async function runTestFile(filePath, envName) {
18
21
  const variables = await loadVariables(envName);
19
22
  const projectId = await getProjectId();
20
23
  const defaultExport = await import(filePath);
21
- const rawPlan = defaultExport.default;
22
- console.log(`Project ID: ${projectId}`);
23
- // Resolve all variable references in the plan
24
- const resolvedPlan = resolveVariablesInPlan(rawPlan, variables);
24
+ const rawPlan = validateDsl(defaultExport.default);
25
+ terminal.dim(`Project ID: ${projectId}`);
26
+ const resolvedPlan = resolvePlan(rawPlan, projectId, envName, variables);
25
27
  const secretRegistry = new SecretProviderRegistry();
26
28
  secretRegistry.register(new EnvSecretProvider());
27
29
  try {
28
- const parsedPlan = Value.Parse(RawTestSchema, resolvedPlan);
29
- const syntheticPlan = {
30
- ...parsedPlan,
30
+ const result = await executePlanV1({
31
+ ...resolvedPlan,
31
32
  id: randomUUID(),
32
- project: projectId,
33
- environment: envName,
34
- };
35
- const result = await executePlanV1(syntheticPlan, "default-org", {
33
+ }, "default-org", {
36
34
  mode: "local",
37
35
  httpClient: new AxiosAdapter(),
38
36
  secretRegistry: secretRegistry,
39
37
  });
40
- console.log(JSON.stringify(result, null, 2));
41
38
  return result;
42
39
  }
43
40
  catch (error) {
44
- const errors = Value.Errors(RawTestSchema, resolvedPlan);
45
- throw new Error(`Invalid plan: ${JSON.stringify([...errors], null, 2)}: ${error}`);
41
+ throw new Error(`Error executing plan: ${error instanceof Error ? error.message : String(error)}`);
46
42
  }
47
43
  }
48
44
  //function findWorkspaceRoot(): string {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @deprecated Use terminal from "./terminal.js" instead
3
+ * This file is kept for backwards compatibility
4
+ */
5
+ export { terminal as console, colors } from "./terminal.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @deprecated Use terminal from "./terminal.js" instead
3
+ * This file is kept for backwards compatibility
4
+ */
5
+ export { terminal as console, colors } from "./terminal.js";
@@ -0,0 +1,100 @@
1
+ import { Ora } from "ora";
2
+ import Table from "cli-table3";
3
+ /**
4
+ * Color utilities
5
+ */
6
+ export declare const colors: {
7
+ readonly cyan: import("picocolors/types").Formatter;
8
+ readonly green: import("picocolors/types").Formatter;
9
+ readonly yellow: import("picocolors/types").Formatter;
10
+ readonly red: import("picocolors/types").Formatter;
11
+ readonly blue: import("picocolors/types").Formatter;
12
+ readonly magenta: import("picocolors/types").Formatter;
13
+ readonly gray: import("picocolors/types").Formatter;
14
+ readonly dim: import("picocolors/types").Formatter;
15
+ readonly bold: import("picocolors/types").Formatter;
16
+ readonly underline: import("picocolors/types").Formatter;
17
+ };
18
+ /**
19
+ * Unified Terminal interface for all CLI interactions
20
+ */
21
+ export declare class Terminal {
22
+ /**
23
+ * Log info message with icon
24
+ */
25
+ info(message: string): void;
26
+ /**
27
+ * Log success message with icon
28
+ */
29
+ success(message: string): void;
30
+ /**
31
+ * Log warning message with icon
32
+ */
33
+ warn(message: string): void;
34
+ /**
35
+ * Log error message with icon to stderr
36
+ */
37
+ error(message: string): void;
38
+ /**
39
+ * Log plain message (no styling)
40
+ */
41
+ log(message: string): void;
42
+ /**
43
+ * Log dimmed/secondary message
44
+ */
45
+ dim(message: string): void;
46
+ /**
47
+ * Log a blank line
48
+ */
49
+ blank(): void;
50
+ /**
51
+ * Create a spinner for async operations
52
+ */
53
+ spinner(text: string): Ora;
54
+ /**
55
+ * Ask for confirmation (yes/no)
56
+ */
57
+ confirm(message: string, initial?: boolean): Promise<boolean>;
58
+ /**
59
+ * Prompt user to select from a list
60
+ */
61
+ select<T extends string>(message: string, choices: T[]): Promise<T>;
62
+ /**
63
+ * Prompt user for text input
64
+ */
65
+ input(message: string, initial?: string): Promise<string>;
66
+ /**
67
+ * Create a formatted table
68
+ */
69
+ table(options?: {
70
+ head?: string[];
71
+ colWidths?: number[];
72
+ }): Table.Table;
73
+ /**
74
+ * Print a formatted table
75
+ */
76
+ printTable(data: Array<Record<string, any>>, columns?: string[]): void;
77
+ /**
78
+ * Access to color functions
79
+ */
80
+ get colors(): {
81
+ readonly cyan: import("picocolors/types").Formatter;
82
+ readonly green: import("picocolors/types").Formatter;
83
+ readonly yellow: import("picocolors/types").Formatter;
84
+ readonly red: import("picocolors/types").Formatter;
85
+ readonly blue: import("picocolors/types").Formatter;
86
+ readonly magenta: import("picocolors/types").Formatter;
87
+ readonly gray: import("picocolors/types").Formatter;
88
+ readonly dim: import("picocolors/types").Formatter;
89
+ readonly bold: import("picocolors/types").Formatter;
90
+ readonly underline: import("picocolors/types").Formatter;
91
+ };
92
+ /**
93
+ * Exit process with error code
94
+ */
95
+ exit(code: number): never;
96
+ }
97
+ /**
98
+ * Default terminal instance
99
+ */
100
+ export declare const terminal: Terminal;
@@ -0,0 +1,148 @@
1
+ import pc from "picocolors";
2
+ import ora from "ora";
3
+ import enquirer from "enquirer";
4
+ import Table from "cli-table3";
5
+ /**
6
+ * Color utilities
7
+ */
8
+ export const colors = {
9
+ cyan: pc.cyan,
10
+ green: pc.green,
11
+ yellow: pc.yellow,
12
+ red: pc.red,
13
+ blue: pc.blue,
14
+ magenta: pc.magenta,
15
+ gray: pc.gray,
16
+ dim: pc.dim,
17
+ bold: pc.bold,
18
+ underline: pc.underline,
19
+ };
20
+ /**
21
+ * Unified Terminal interface for all CLI interactions
22
+ */
23
+ export class Terminal {
24
+ /**
25
+ * Log info message with icon
26
+ */
27
+ info(message) {
28
+ console.log(pc.blue("ℹ"), message);
29
+ }
30
+ /**
31
+ * Log success message with icon
32
+ */
33
+ success(message) {
34
+ console.log(pc.green("✔"), message);
35
+ }
36
+ /**
37
+ * Log warning message with icon
38
+ */
39
+ warn(message) {
40
+ console.log(pc.yellow("⚠"), message);
41
+ }
42
+ /**
43
+ * Log error message with icon to stderr
44
+ */
45
+ error(message) {
46
+ console.error(pc.red("✖"), message);
47
+ }
48
+ /**
49
+ * Log plain message (no styling)
50
+ */
51
+ log(message) {
52
+ console.log(message);
53
+ }
54
+ /**
55
+ * Log dimmed/secondary message
56
+ */
57
+ dim(message) {
58
+ console.log(pc.dim(message));
59
+ }
60
+ /**
61
+ * Log a blank line
62
+ */
63
+ blank() {
64
+ console.log("");
65
+ }
66
+ /**
67
+ * Create a spinner for async operations
68
+ */
69
+ spinner(text) {
70
+ return ora(text);
71
+ }
72
+ /**
73
+ * Ask for confirmation (yes/no)
74
+ */
75
+ async confirm(message, initial = false) {
76
+ const response = await enquirer.prompt({
77
+ type: "confirm",
78
+ name: "confirmed",
79
+ message,
80
+ initial,
81
+ });
82
+ return response.confirmed;
83
+ }
84
+ /**
85
+ * Prompt user to select from a list
86
+ */
87
+ async select(message, choices) {
88
+ const response = await enquirer.prompt({
89
+ type: "select",
90
+ name: "selected",
91
+ message,
92
+ choices,
93
+ });
94
+ return response.selected;
95
+ }
96
+ /**
97
+ * Prompt user for text input
98
+ */
99
+ async input(message, initial) {
100
+ const response = await enquirer.prompt({
101
+ type: "input",
102
+ name: "value",
103
+ message,
104
+ initial,
105
+ });
106
+ return response.value;
107
+ }
108
+ /**
109
+ * Create a formatted table
110
+ */
111
+ table(options) {
112
+ return new Table({
113
+ head: options?.head,
114
+ style: { head: ["cyan"] },
115
+ });
116
+ }
117
+ /**
118
+ * Print a formatted table
119
+ */
120
+ printTable(data, columns) {
121
+ if (data.length === 0) {
122
+ this.dim("(no data)");
123
+ return;
124
+ }
125
+ const cols = columns || Object.keys(data[0]);
126
+ const table = this.table({ head: cols });
127
+ for (const row of data) {
128
+ table.push(cols.map((col) => row[col]?.toString() || ""));
129
+ }
130
+ console.log(table.toString());
131
+ }
132
+ /**
133
+ * Access to color functions
134
+ */
135
+ get colors() {
136
+ return colors;
137
+ }
138
+ /**
139
+ * Exit process with error code
140
+ */
141
+ exit(code) {
142
+ process.exit(code);
143
+ }
144
+ }
145
+ /**
146
+ * Default terminal instance
147
+ */
148
+ export const terminal = new Terminal();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@griffin-app/griffin-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "CLI tool for running and managing griffin API tests",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,12 +23,16 @@
23
23
  "author": "",
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
- "@griffin-app/griffin-hub-sdk": "1.0.1",
27
- "@griffin-app/griffin-plan-executor": "0.1.8",
28
- "@griffin-app/griffin-ts": "0.1.7",
26
+ "@griffin-app/griffin-hub-sdk": "1.0.2",
27
+ "@griffin-app/griffin-plan-executor": "0.1.10",
28
+ "@griffin-app/griffin-ts": "0.1.8",
29
+ "cli-table3": "^0.6.5",
29
30
  "commander": "^12.1.0",
31
+ "enquirer": "^2.4.1",
30
32
  "glob": "^11.0.0",
31
33
  "object-hash": "^3.0.0",
34
+ "ora": "^8.1.1",
35
+ "picocolors": "^1.1.1",
32
36
  "typebox": "^1.0.78",
33
37
  "yaml": "^2.8.2"
34
38
  },