@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,151 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Value } from "typebox/value";
|
|
4
|
+
import { StateFileSchema, createEmptyState, } from "../schemas/state.js";
|
|
5
|
+
export const STATE_DIR = ".griffin";
|
|
6
|
+
export const STATE_FILE = "state.json";
|
|
7
|
+
/**
|
|
8
|
+
* Get the state directory path (in current working directory)
|
|
9
|
+
*/
|
|
10
|
+
export function getStateDirPath() {
|
|
11
|
+
return path.join(process.cwd(), STATE_DIR);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get the state file path
|
|
15
|
+
*/
|
|
16
|
+
export function getStateFilePath() {
|
|
17
|
+
return path.join(getStateDirPath(), STATE_FILE);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check if state file exists
|
|
21
|
+
*/
|
|
22
|
+
export async function stateExists() {
|
|
23
|
+
try {
|
|
24
|
+
await fs.access(getStateFilePath());
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Load state file from disk
|
|
33
|
+
* Throws if file doesn't exist or is invalid
|
|
34
|
+
*/
|
|
35
|
+
export async function loadState() {
|
|
36
|
+
const stateFilePath = getStateFilePath();
|
|
37
|
+
try {
|
|
38
|
+
const content = await fs.readFile(stateFilePath, "utf-8");
|
|
39
|
+
const data = JSON.parse(content);
|
|
40
|
+
// Check if it's v1
|
|
41
|
+
if (Value.Check(StateFileSchema, data)) {
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
44
|
+
// Invalid schema
|
|
45
|
+
const errors = [...Value.Errors(StateFileSchema, data)];
|
|
46
|
+
throw new Error(`Invalid state file schema:\n${errors.map((e) => ` - ${e.path || "unknown"}: ${e.message}`).join("\n")}`);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error.code === "ENOENT") {
|
|
50
|
+
throw new Error(`State file not found. Run 'griffin init' first.\nExpected: ${stateFilePath}`);
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Save state file to disk
|
|
57
|
+
*/
|
|
58
|
+
export async function saveState(state) {
|
|
59
|
+
const stateDirPath = getStateDirPath();
|
|
60
|
+
const stateFilePath = getStateFilePath();
|
|
61
|
+
// Ensure directory exists
|
|
62
|
+
await fs.mkdir(stateDirPath, { recursive: true });
|
|
63
|
+
// Validate before saving
|
|
64
|
+
if (!Value.Check(StateFileSchema, state)) {
|
|
65
|
+
const errors = [...Value.Errors(StateFileSchema, state)];
|
|
66
|
+
throw new Error(`Invalid state data:\n${errors.map((e) => ` - ${e.path || "unknown"}: ${e.message}`).join("\n")}`);
|
|
67
|
+
}
|
|
68
|
+
// Write with pretty formatting
|
|
69
|
+
await fs.writeFile(stateFilePath, JSON.stringify(state, null, 2), "utf-8");
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Initialize a new state file
|
|
73
|
+
*/
|
|
74
|
+
export async function initState(projectId) {
|
|
75
|
+
if (await stateExists()) {
|
|
76
|
+
throw new Error(`State file already exists: ${getStateFilePath()}\nUse 'griffin plan' to see current state.`);
|
|
77
|
+
}
|
|
78
|
+
const state = createEmptyState(projectId);
|
|
79
|
+
await saveState(state);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Add or update an environment
|
|
83
|
+
*/
|
|
84
|
+
export async function addEnvironment(name, config) {
|
|
85
|
+
const state = await loadState();
|
|
86
|
+
state.environments[name] = config;
|
|
87
|
+
// Set as default if it's the first environment
|
|
88
|
+
if (Object.keys(state.environments).length === 1) {
|
|
89
|
+
state.defaultEnvironment = name;
|
|
90
|
+
}
|
|
91
|
+
await saveState(state);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Remove an environment
|
|
95
|
+
*/
|
|
96
|
+
export async function removeEnvironment(name) {
|
|
97
|
+
const state = await loadState();
|
|
98
|
+
if (!(name in state.environments)) {
|
|
99
|
+
throw new Error(`Environment '${name}' does not exist`);
|
|
100
|
+
}
|
|
101
|
+
delete state.environments[name];
|
|
102
|
+
// Update default if we removed it
|
|
103
|
+
if (state.defaultEnvironment === name) {
|
|
104
|
+
const remaining = Object.keys(state.environments);
|
|
105
|
+
state.defaultEnvironment = remaining.length > 0 ? remaining[0] : undefined;
|
|
106
|
+
}
|
|
107
|
+
await saveState(state);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Set the default environment
|
|
111
|
+
*/
|
|
112
|
+
export async function setDefaultEnvironment(name) {
|
|
113
|
+
const state = await loadState();
|
|
114
|
+
if (!(name in state.environments)) {
|
|
115
|
+
throw new Error(`Environment '${name}' does not exist`);
|
|
116
|
+
}
|
|
117
|
+
state.defaultEnvironment = name;
|
|
118
|
+
await saveState(state);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get the current environment name (from flag, env var, or default)
|
|
122
|
+
*/
|
|
123
|
+
export async function resolveEnvironment(envFlag) {
|
|
124
|
+
const state = await loadState();
|
|
125
|
+
// Priority: CLI flag > ENV var > default > error
|
|
126
|
+
const envName = envFlag || process.env.GRIFFIN_ENV || state.defaultEnvironment;
|
|
127
|
+
if (!envName) {
|
|
128
|
+
throw new Error("No environment specified. Use --env flag, GRIFFIN_ENV env var, or set a default with 'griffin env default <name>'");
|
|
129
|
+
}
|
|
130
|
+
if (!(envName in state.environments)) {
|
|
131
|
+
throw new Error(`Environment '${envName}' not found. Available: ${Object.keys(state.environments).join(", ")}`);
|
|
132
|
+
}
|
|
133
|
+
return envName;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get environment configuration
|
|
137
|
+
*/
|
|
138
|
+
export async function getEnvironment(name) {
|
|
139
|
+
const state = await loadState();
|
|
140
|
+
if (!(name in state.environments)) {
|
|
141
|
+
throw new Error(`Environment '${name}' does not exist`);
|
|
142
|
+
}
|
|
143
|
+
return state.environments[name];
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get the project ID
|
|
147
|
+
*/
|
|
148
|
+
export async function getProjectId() {
|
|
149
|
+
const state = await loadState();
|
|
150
|
+
return state.projectId;
|
|
151
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load variables from variables.yaml for a specific environment.
|
|
3
|
+
*
|
|
4
|
+
* @param envName - The environment name to load variables for
|
|
5
|
+
* @returns Record of variable key-value pairs
|
|
6
|
+
* @throws Error if variables.yaml doesn't exist or environment not found
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadVariables(envName: string): Promise<Record<string, string>>;
|
|
9
|
+
/**
|
|
10
|
+
* Recursively walk a plan object and resolve all variable references.
|
|
11
|
+
*
|
|
12
|
+
* This function deeply traverses the plan structure, replacing any
|
|
13
|
+
* { $variable: { key, template? } } objects with resolved string values.
|
|
14
|
+
*
|
|
15
|
+
* @param obj - The plan object or value to resolve
|
|
16
|
+
* @param variables - Map of variable key-value pairs
|
|
17
|
+
* @returns The plan with all variables resolved to strings
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveVariablesInPlan(obj: unknown, variables: Record<string, string>): unknown;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
/**
|
|
5
|
+
* Load variables from variables.yaml for a specific environment.
|
|
6
|
+
*
|
|
7
|
+
* @param envName - The environment name to load variables for
|
|
8
|
+
* @returns Record of variable key-value pairs
|
|
9
|
+
* @throws Error if variables.yaml doesn't exist or environment not found
|
|
10
|
+
*/
|
|
11
|
+
export async function loadVariables(envName) {
|
|
12
|
+
const yamlPath = path.join(process.cwd(), "variables.yaml");
|
|
13
|
+
try {
|
|
14
|
+
const content = await fs.readFile(yamlPath, "utf-8");
|
|
15
|
+
const config = parse(content);
|
|
16
|
+
if (!config.environments) {
|
|
17
|
+
throw new Error(`Invalid variables.yaml: missing "environments" key.\nExpected format:\nenvironments:\n ${envName}:\n key: value`);
|
|
18
|
+
}
|
|
19
|
+
if (!config.environments[envName]) {
|
|
20
|
+
const availableEnvs = Object.keys(config.environments);
|
|
21
|
+
throw new Error(`Environment "${envName}" not found in variables.yaml.\nAvailable environments: ${availableEnvs.join(", ")}`);
|
|
22
|
+
}
|
|
23
|
+
return config.environments[envName];
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code === "ENOENT") {
|
|
27
|
+
throw new Error(`variables.yaml not found in ${process.cwd()}.\nCreate one with:\nenvironments:\n ${envName}:\n my-variable: my-value`);
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if a value is a variable reference.
|
|
34
|
+
*/
|
|
35
|
+
function isVariableRef(value) {
|
|
36
|
+
if (typeof value !== "object" || value === null) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const obj = value;
|
|
40
|
+
if (!("$variable" in obj) ||
|
|
41
|
+
typeof obj.$variable !== "object" ||
|
|
42
|
+
obj.$variable === null) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const varData = obj.$variable;
|
|
46
|
+
return (typeof varData.key === "string" &&
|
|
47
|
+
(varData.template === undefined || typeof varData.template === "string"));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a single variable reference to its string value.
|
|
51
|
+
*
|
|
52
|
+
* @param varRef - Variable reference to resolve
|
|
53
|
+
* @param variables - Map of variable key-value pairs
|
|
54
|
+
* @returns Resolved string value
|
|
55
|
+
*/
|
|
56
|
+
function resolveVariable(varRef, variables) {
|
|
57
|
+
const { key, template } = varRef.$variable;
|
|
58
|
+
const value = variables[key];
|
|
59
|
+
if (value === undefined) {
|
|
60
|
+
const availableKeys = Object.keys(variables);
|
|
61
|
+
throw new Error(`Variable "${key}" not found.\nAvailable variables: ${availableKeys.join(", ")}`);
|
|
62
|
+
}
|
|
63
|
+
// If no template, return the value directly
|
|
64
|
+
if (!template) {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
// Replace ${key} with the value in the template
|
|
68
|
+
const placeholder = `\${${key}}`;
|
|
69
|
+
return template.replace(placeholder, value);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Recursively walk a plan object and resolve all variable references.
|
|
73
|
+
*
|
|
74
|
+
* This function deeply traverses the plan structure, replacing any
|
|
75
|
+
* { $variable: { key, template? } } objects with resolved string values.
|
|
76
|
+
*
|
|
77
|
+
* @param obj - The plan object or value to resolve
|
|
78
|
+
* @param variables - Map of variable key-value pairs
|
|
79
|
+
* @returns The plan with all variables resolved to strings
|
|
80
|
+
*/
|
|
81
|
+
export function resolveVariablesInPlan(obj, variables) {
|
|
82
|
+
// Check if this is a variable reference
|
|
83
|
+
if (isVariableRef(obj)) {
|
|
84
|
+
return resolveVariable(obj, variables);
|
|
85
|
+
}
|
|
86
|
+
// Recursively process arrays
|
|
87
|
+
if (Array.isArray(obj)) {
|
|
88
|
+
return obj.map((item) => resolveVariablesInPlan(item, variables));
|
|
89
|
+
}
|
|
90
|
+
// Recursively process objects
|
|
91
|
+
if (typeof obj === "object" && obj !== null) {
|
|
92
|
+
const result = {};
|
|
93
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
94
|
+
result[key] = resolveVariablesInPlan(value, variables);
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
// Return primitives as-is
|
|
99
|
+
return obj;
|
|
100
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type { StateFile, RunnerConfig } from "./schemas/state.js";
|
|
2
|
+
export type { DiscoveredPlan, DiscoveryResult, DiscoveryError, } from "./core/discovery.js";
|
|
3
|
+
export type { DiffAction, DiffResult } from "./core/diff.js";
|
|
4
|
+
export type { ApplyResult, ApplyAction, ApplyError } from "./core/apply.js";
|
|
5
|
+
export { createEmptyState, StateFileSchema, RunnerConfigSchema, } from "./schemas/state.js";
|
|
6
|
+
export { getStateDirPath, getStateFilePath, stateExists, loadState, saveState, initState, addEnvironment, removeEnvironment, setDefaultEnvironment, resolveEnvironment, getEnvironment, } from "./core/state.js";
|
|
7
|
+
export { discoverPlans, formatDiscoveryErrors } from "./core/discovery.js";
|
|
8
|
+
export { computeDiff, formatDiff, formatDiffJson } from "./core/diff.js";
|
|
9
|
+
export { applyDiff, formatApplyResult } from "./core/apply.js";
|
|
10
|
+
export { createSdkClients, injectProjectId } from "./core/sdk.js";
|
|
11
|
+
export { detectProjectId } from "./core/project.js";
|
|
12
|
+
export { executeInit } from "./commands/init.js";
|
|
13
|
+
export { executeValidate } from "./commands/validate.js";
|
|
14
|
+
export { executeGenerateKey } from "./commands/generate-key.js";
|
|
15
|
+
export { executeRunLocal } from "./commands/local/run.js";
|
|
16
|
+
export { executeConnect } from "./commands/hub/connect.js";
|
|
17
|
+
export { executeStatus } from "./commands/hub/status.js";
|
|
18
|
+
export { executeRuns } from "./commands/hub/runs.js";
|
|
19
|
+
export { executePlan } from "./commands/hub/plan.js";
|
|
20
|
+
export { executeApply } from "./commands/hub/apply.js";
|
|
21
|
+
export { executeRun } from "./commands/hub/run.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Export core functions
|
|
2
|
+
export { createEmptyState, StateFileSchema, RunnerConfigSchema, } from "./schemas/state.js";
|
|
3
|
+
export { getStateDirPath, getStateFilePath, stateExists, loadState, saveState, initState, addEnvironment, removeEnvironment, setDefaultEnvironment, resolveEnvironment, getEnvironment, } from "./core/state.js";
|
|
4
|
+
export { discoverPlans, formatDiscoveryErrors } from "./core/discovery.js";
|
|
5
|
+
export { computeDiff, formatDiff, formatDiffJson } from "./core/diff.js";
|
|
6
|
+
export { applyDiff, formatApplyResult } from "./core/apply.js";
|
|
7
|
+
export { createSdkClients, injectProjectId } from "./core/sdk.js";
|
|
8
|
+
export { detectProjectId } from "./core/project.js";
|
|
9
|
+
// Export command executors (for programmatic use)
|
|
10
|
+
export { executeInit } from "./commands/init.js";
|
|
11
|
+
export { executeValidate } from "./commands/validate.js";
|
|
12
|
+
export { executeGenerateKey } from "./commands/generate-key.js";
|
|
13
|
+
// Local commands
|
|
14
|
+
export { executeRunLocal } from "./commands/local/run.js";
|
|
15
|
+
// Hub commands
|
|
16
|
+
export { executeConnect } from "./commands/hub/connect.js";
|
|
17
|
+
export { executeStatus } from "./commands/hub/status.js";
|
|
18
|
+
export { executeRuns } from "./commands/hub/runs.js";
|
|
19
|
+
export { executePlan } from "./commands/hub/plan.js";
|
|
20
|
+
export { executeApply } from "./commands/hub/apply.js";
|
|
21
|
+
export { executeRun } from "./commands/hub/run.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Type, type Static } from "typebox";
|
|
2
|
+
/**
|
|
3
|
+
* State file schema for tracking project configuration
|
|
4
|
+
* Stored in .griffin/state.json
|
|
5
|
+
*
|
|
6
|
+
* Note: The hub is now the source of truth for plans.
|
|
7
|
+
* This file only stores configuration (project, environments, runner connection).
|
|
8
|
+
*/
|
|
9
|
+
export declare const EnvironmentConfigSchema: Type.TObject<{}>;
|
|
10
|
+
export type EnvironmentConfig = Static<typeof EnvironmentConfigSchema>;
|
|
11
|
+
export declare const RunnerConfigSchema: Type.TObject<{
|
|
12
|
+
baseUrl: Type.TString;
|
|
13
|
+
apiToken: Type.TOptional<Type.TString>;
|
|
14
|
+
}>;
|
|
15
|
+
export type RunnerConfig = Static<typeof RunnerConfigSchema>;
|
|
16
|
+
export declare const DiscoveryConfigSchema: Type.TObject<{
|
|
17
|
+
pattern: Type.TOptional<Type.TString>;
|
|
18
|
+
ignore: Type.TOptional<Type.TArray<Type.TString>>;
|
|
19
|
+
}>;
|
|
20
|
+
export type DiscoveryConfig = Static<typeof DiscoveryConfigSchema>;
|
|
21
|
+
export declare const StateFileSchema: Type.TObject<{
|
|
22
|
+
stateVersion: Type.TLiteral<1>;
|
|
23
|
+
projectId: Type.TString;
|
|
24
|
+
environments: Type.TRecord<"^.*$", Type.TObject<{}>>;
|
|
25
|
+
defaultEnvironment: Type.TOptional<Type.TString>;
|
|
26
|
+
runner: Type.TOptional<Type.TObject<{
|
|
27
|
+
baseUrl: Type.TString;
|
|
28
|
+
apiToken: Type.TOptional<Type.TString>;
|
|
29
|
+
}>>;
|
|
30
|
+
discovery: Type.TOptional<Type.TObject<{
|
|
31
|
+
pattern: Type.TOptional<Type.TString>;
|
|
32
|
+
ignore: Type.TOptional<Type.TArray<Type.TString>>;
|
|
33
|
+
}>>;
|
|
34
|
+
}>;
|
|
35
|
+
export type StateFile = Static<typeof StateFileSchema>;
|
|
36
|
+
/**
|
|
37
|
+
* Create an empty state file
|
|
38
|
+
*/
|
|
39
|
+
export declare function createEmptyState(projectId: string): StateFile;
|
|
40
|
+
/**
|
|
41
|
+
* Create state file with a default local environment
|
|
42
|
+
*/
|
|
43
|
+
export declare function createStateWithDefaultEnv(projectId: string): StateFile;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
/**
|
|
3
|
+
* State file schema for tracking project configuration
|
|
4
|
+
* Stored in .griffin/state.json
|
|
5
|
+
*
|
|
6
|
+
* Note: The hub is now the source of truth for plans.
|
|
7
|
+
* This file only stores configuration (project, environments, runner connection).
|
|
8
|
+
*/
|
|
9
|
+
export const EnvironmentConfigSchema = Type.Object({
|
|
10
|
+
// Empty for now - may be used for environment-specific config in the future
|
|
11
|
+
});
|
|
12
|
+
export const RunnerConfigSchema = Type.Object({
|
|
13
|
+
baseUrl: Type.String(),
|
|
14
|
+
apiToken: Type.Optional(Type.String()),
|
|
15
|
+
});
|
|
16
|
+
export const DiscoveryConfigSchema = Type.Object({
|
|
17
|
+
pattern: Type.Optional(Type.String()),
|
|
18
|
+
ignore: Type.Optional(Type.Array(Type.String())),
|
|
19
|
+
});
|
|
20
|
+
// State schema (hub is source of truth for plans)
|
|
21
|
+
export const StateFileSchema = Type.Object({
|
|
22
|
+
stateVersion: Type.Literal(1),
|
|
23
|
+
projectId: Type.String(),
|
|
24
|
+
// Environment configuration
|
|
25
|
+
environments: Type.Record(Type.String(), EnvironmentConfigSchema),
|
|
26
|
+
defaultEnvironment: Type.Optional(Type.String()),
|
|
27
|
+
// Runner connection (for remote execution)
|
|
28
|
+
runner: Type.Optional(RunnerConfigSchema),
|
|
29
|
+
// Discovery settings
|
|
30
|
+
discovery: Type.Optional(DiscoveryConfigSchema),
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Create an empty state file
|
|
34
|
+
*/
|
|
35
|
+
export function createEmptyState(projectId) {
|
|
36
|
+
return {
|
|
37
|
+
stateVersion: 1,
|
|
38
|
+
projectId,
|
|
39
|
+
environments: {},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create state file with a default local environment
|
|
44
|
+
*/
|
|
45
|
+
export function createStateWithDefaultEnv(projectId) {
|
|
46
|
+
return {
|
|
47
|
+
stateVersion: 1,
|
|
48
|
+
projectId,
|
|
49
|
+
environments: {
|
|
50
|
+
local: {},
|
|
51
|
+
},
|
|
52
|
+
defaultEnvironment: "local",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { glob } from "glob";
|
|
3
|
+
/**
|
|
4
|
+
* Discovers test files in __griffin__ directories.
|
|
5
|
+
*/
|
|
6
|
+
export function findTestFiles(basePath = ".") {
|
|
7
|
+
const absolutePath = path.resolve(basePath);
|
|
8
|
+
// Find all __griffin__ directories
|
|
9
|
+
const griffinDirs = findgriffinDirectories(absolutePath);
|
|
10
|
+
// Find all .ts files in those directories
|
|
11
|
+
const testFiles = [];
|
|
12
|
+
for (const griffinDir of griffinDirs) {
|
|
13
|
+
const tsFiles = findTsFiles(griffinDir);
|
|
14
|
+
testFiles.push(...tsFiles);
|
|
15
|
+
}
|
|
16
|
+
return testFiles;
|
|
17
|
+
}
|
|
18
|
+
function findgriffinDirectories(basePath) {
|
|
19
|
+
const pattern = path.join(basePath, "**", "__griffin__");
|
|
20
|
+
return glob.sync(pattern, { absolute: true });
|
|
21
|
+
}
|
|
22
|
+
function findTsFiles(griffinDir) {
|
|
23
|
+
const pattern = path.join(griffinDir, "*.ts");
|
|
24
|
+
return glob.sync(pattern, { absolute: true });
|
|
25
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import "tsx";
|
|
2
|
+
import { Value } from "typebox/value";
|
|
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";
|
|
6
|
+
import { randomUUID } from "crypto";
|
|
7
|
+
import { loadVariables, resolveVariablesInPlan } from "./core/variables.js";
|
|
8
|
+
import { getProjectId } from "./core/state.js";
|
|
9
|
+
const RawTestSchema = Type.Omit(TestPlanV1Schema, [
|
|
10
|
+
"id",
|
|
11
|
+
"environment",
|
|
12
|
+
"project",
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* Runs a TypeScript test file and executes the resulting JSON plan.
|
|
16
|
+
*/
|
|
17
|
+
export async function runTestFile(filePath, envName) {
|
|
18
|
+
const variables = await loadVariables(envName);
|
|
19
|
+
const projectId = await getProjectId();
|
|
20
|
+
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);
|
|
25
|
+
const secretRegistry = new SecretProviderRegistry();
|
|
26
|
+
secretRegistry.register(new EnvSecretProvider());
|
|
27
|
+
try {
|
|
28
|
+
const parsedPlan = Value.Parse(RawTestSchema, resolvedPlan);
|
|
29
|
+
const syntheticPlan = {
|
|
30
|
+
...parsedPlan,
|
|
31
|
+
id: randomUUID(),
|
|
32
|
+
project: projectId,
|
|
33
|
+
environment: envName,
|
|
34
|
+
};
|
|
35
|
+
const result = await executePlanV1(syntheticPlan, "default-org", {
|
|
36
|
+
mode: "local",
|
|
37
|
+
httpClient: new AxiosAdapter(),
|
|
38
|
+
secretRegistry: secretRegistry,
|
|
39
|
+
});
|
|
40
|
+
console.log(JSON.stringify(result, null, 2));
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
const errors = Value.Errors(RawTestSchema, resolvedPlan);
|
|
45
|
+
throw new Error(`Invalid plan: ${JSON.stringify([...errors], null, 2)}: ${error}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//function findWorkspaceRoot(): string {
|
|
49
|
+
// let current = process.cwd();
|
|
50
|
+
// while (current !== path.dirname(current)) {
|
|
51
|
+
// const testCliPath = path.join(current, "griffin-cli");
|
|
52
|
+
// const testSystemPath = path.join(current, "griffin-ts");
|
|
53
|
+
// if (fs.existsSync(testCliPath) && fs.existsSync(testSystemPath)) {
|
|
54
|
+
// return current;
|
|
55
|
+
// }
|
|
56
|
+
// current = path.dirname(current);
|
|
57
|
+
// }
|
|
58
|
+
// return process.cwd();
|
|
59
|
+
//}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@griffin-app/griffin-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "CLI tool for running and managing griffin API tests",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,13 +23,14 @@
|
|
|
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
29
|
"commander": "^12.1.0",
|
|
27
30
|
"glob": "^11.0.0",
|
|
28
|
-
"griffin": "file:../griffin-ts",
|
|
29
|
-
"griffin-hub-sdk": "file:../griffin-hub-sdk",
|
|
30
|
-
"griffin-plan-executor": "file:../griffin-plan-executor",
|
|
31
31
|
"object-hash": "^3.0.0",
|
|
32
|
-
"typebox": "^1.0.78"
|
|
32
|
+
"typebox": "^1.0.78",
|
|
33
|
+
"yaml": "^2.8.2"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"@types/node": "^22.10.5",
|