flowspec 0.1.0
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/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/config.d.ts +54 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +102 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +181 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +41 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +173 -0
- package/dist/init.js.map +1 -0
- package/dist/parser.d.ts +16 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +56 -0
- package/dist/parser.js.map +1 -0
- package/dist/reporter.d.ts +22 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +80 -0
- package/dist/reporter.js.map +1 -0
- package/dist/runner.d.ts +24 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +418 -0
- package/dist/runner.js.map +1 -0
- package/dist/types.d.ts +512 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +78 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Josh Owens
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# FlowSpec
|
|
2
|
+
|
|
3
|
+
Immutable user flow specifications for the age of agentic coding.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
AI coding agents can modify both implementation and tests. When a test fails, the agent might "fix" the test instead of fixing the bug. This breaks the feedback loop that catches regressions.
|
|
8
|
+
|
|
9
|
+
## The Solution
|
|
10
|
+
|
|
11
|
+
FlowSpec separates **what your app should do** (immutable specs) from **how it does it** (agent-modifiable code).
|
|
12
|
+
|
|
13
|
+
- Write user flows in simple YAML
|
|
14
|
+
- Use human-readable labels (accessibility-first, a la React Testing Library)
|
|
15
|
+
- Protect specs from agent modification via Claude Code hooks
|
|
16
|
+
- Run deterministically in CI or interactively with an agent
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Install globally
|
|
22
|
+
npm install -g flowspec
|
|
23
|
+
|
|
24
|
+
# Or with bun
|
|
25
|
+
bun add -g flowspec
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Requires [agent-browser](https://github.com/anthropics/agent-browser) for browser automation.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Initialize a New Project
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Scaffold a new FlowSpec project
|
|
36
|
+
flowspec init
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This creates:
|
|
40
|
+
- `flowspec.config.yaml` - Project configuration
|
|
41
|
+
- `specs/example.flow.yaml` - Sample flow to get started
|
|
42
|
+
- `.claude/settings.local.json` - Hooks to protect specs from AI modification
|
|
43
|
+
- Updates `package.json` with `test:e2e` script
|
|
44
|
+
|
|
45
|
+
### Run Flows
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Run a single flow file
|
|
49
|
+
flowspec run specs/checkout.flow.yaml
|
|
50
|
+
|
|
51
|
+
# Run all flows in a directory
|
|
52
|
+
flowspec run specs/
|
|
53
|
+
|
|
54
|
+
# Specify a custom base URL
|
|
55
|
+
flowspec run specs/ --base-url http://localhost:8080
|
|
56
|
+
|
|
57
|
+
# Set assertion retry timeout (default: 5000ms)
|
|
58
|
+
flowspec run specs/ --timeout 10000
|
|
59
|
+
|
|
60
|
+
# Disable assertion retries (fail immediately)
|
|
61
|
+
flowspec run specs/ --timeout 0
|
|
62
|
+
|
|
63
|
+
# Show help
|
|
64
|
+
flowspec --help
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Configuration File
|
|
68
|
+
|
|
69
|
+
FlowSpec looks for `flowspec.config.yaml` in the current directory or parent directories:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
baseUrl: http://localhost:3000
|
|
73
|
+
timeout: 10000
|
|
74
|
+
specsDir: specs/
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
CLI options override config file values.
|
|
78
|
+
|
|
79
|
+
### Exit Codes
|
|
80
|
+
|
|
81
|
+
| Code | Meaning |
|
|
82
|
+
| ---- | ------- |
|
|
83
|
+
| 0 | All flows passed |
|
|
84
|
+
| 1 | One or more flows failed |
|
|
85
|
+
| 2 | Parse error (invalid YAML or schema) |
|
|
86
|
+
|
|
87
|
+
## Flow File Format
|
|
88
|
+
|
|
89
|
+
Flow files use YAML with a simple structure:
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
name: user-login
|
|
93
|
+
description: User can log in with valid credentials
|
|
94
|
+
steps:
|
|
95
|
+
- visit: /login
|
|
96
|
+
- fill:
|
|
97
|
+
Email: user@example.com
|
|
98
|
+
Password: secretpassword
|
|
99
|
+
- click: Sign In
|
|
100
|
+
expect:
|
|
101
|
+
- url: /dashboard
|
|
102
|
+
- visible: Welcome back
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Step Actions
|
|
106
|
+
|
|
107
|
+
| Action | Description | Example |
|
|
108
|
+
| ------ | ----------- | ------- |
|
|
109
|
+
| `visit` | Navigate to a URL (relative or absolute) | `visit: /login` |
|
|
110
|
+
| `click` | Click element by visible text | `click: "Sign In"` |
|
|
111
|
+
| `fill` | Fill form fields by label | `fill: { Email: user@example.com }` |
|
|
112
|
+
| `select` | Select dropdown option by label | `select: { Country: "United States" }` |
|
|
113
|
+
| `wait_for` | Wait for text to appear (with retry) | `wait_for: "Loading complete"` |
|
|
114
|
+
|
|
115
|
+
### Assertions
|
|
116
|
+
|
|
117
|
+
| Assertion | Description | Example |
|
|
118
|
+
| --------- | ----------- | ------- |
|
|
119
|
+
| `url` | Check current URL contains value | `url: /dashboard` |
|
|
120
|
+
| `visible` | Check text is visible on page | `visible: "Welcome back"` |
|
|
121
|
+
| `matches` | Check page content matches regex | `matches: "Order #\\d+"` |
|
|
122
|
+
| `not_visible` | Check text is NOT on page | `not_visible: "Error"` |
|
|
123
|
+
|
|
124
|
+
## Quick Example
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
# specs/checkout.flow.yaml
|
|
128
|
+
name: checkout-flow
|
|
129
|
+
description: |
|
|
130
|
+
User completes a purchase with items in cart.
|
|
131
|
+
Captures shipping/billing info and confirms order.
|
|
132
|
+
|
|
133
|
+
steps:
|
|
134
|
+
- visit: "/cart"
|
|
135
|
+
- click: "Proceed to Checkout"
|
|
136
|
+
- fill:
|
|
137
|
+
"Email": "user@example.com"
|
|
138
|
+
"Shipping Address": "123 Main St"
|
|
139
|
+
- click: "Place Order"
|
|
140
|
+
|
|
141
|
+
expect:
|
|
142
|
+
- url: "/order/confirmation"
|
|
143
|
+
- visible: "Order confirmed"
|
|
144
|
+
- matches: "Order #\\d+"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
bun install # Install dependencies
|
|
151
|
+
bun run build # Build CLI (required for bun link)
|
|
152
|
+
bun link # Link CLI locally as 'flowspec'
|
|
153
|
+
bun test # Run tests
|
|
154
|
+
bun run typecheck # Type check
|
|
155
|
+
bun run lint # Lint with Biome
|
|
156
|
+
bun run lint:fix # Auto-fix lint issues
|
|
157
|
+
bun run format # Format with Biome
|
|
158
|
+
bun run test:coverage # Run with coverage
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Project Structure
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
FlowSpec/
|
|
165
|
+
├── src/
|
|
166
|
+
│ ├── types.ts # Zod schemas for FlowSpec
|
|
167
|
+
│ ├── parser.ts # YAML parsing with Zod validation
|
|
168
|
+
│ ├── runner.ts # Flow execution via agent-browser
|
|
169
|
+
│ ├── reporter.ts # Result formatting for terminal
|
|
170
|
+
│ └── index.ts # CLI entry point
|
|
171
|
+
├── test/
|
|
172
|
+
│ ├── fixtures/
|
|
173
|
+
│ │ ├── pages/ # HTML test fixtures
|
|
174
|
+
│ │ └── flows/ # YAML flow fixtures
|
|
175
|
+
│ └── *.test.ts # Test files
|
|
176
|
+
├── docs/
|
|
177
|
+
│ └── specification.md # Full framework design
|
|
178
|
+
└── specs/ # User flow specs (immutable)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Documentation
|
|
182
|
+
|
|
183
|
+
- [Full Specification](docs/specification.md) - Complete framework design and rationale
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Schema for FlowSpec project configuration
|
|
4
|
+
*/
|
|
5
|
+
export declare const FlowSpecConfigSchema: z.ZodObject<{
|
|
6
|
+
baseUrl: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
7
|
+
timeout: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
8
|
+
specsDir: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
timeout: number;
|
|
12
|
+
specsDir: string;
|
|
13
|
+
}, {
|
|
14
|
+
baseUrl?: string | undefined;
|
|
15
|
+
timeout?: number | undefined;
|
|
16
|
+
specsDir?: string | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
export type FlowSpecConfig = z.infer<typeof FlowSpecConfigSchema>;
|
|
19
|
+
/**
|
|
20
|
+
* Default configuration values
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEFAULT_CONFIG: FlowSpecConfig;
|
|
23
|
+
/**
|
|
24
|
+
* The standard configuration file name
|
|
25
|
+
*/
|
|
26
|
+
export declare const CONFIG_FILE_NAME = "flowspec.config.yaml";
|
|
27
|
+
/**
|
|
28
|
+
* Find the configuration file by walking up the directory tree
|
|
29
|
+
* @param startDir - Directory to start searching from
|
|
30
|
+
* @returns Path to config file if found, undefined otherwise
|
|
31
|
+
*/
|
|
32
|
+
export declare function findConfigFile(startDir?: string): string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Load and parse a FlowSpec configuration file
|
|
35
|
+
* @param configPath - Path to the config file
|
|
36
|
+
* @returns Parsed configuration
|
|
37
|
+
* @throws Error if file not found or invalid
|
|
38
|
+
*/
|
|
39
|
+
export declare function loadConfigFile(configPath: string): FlowSpecConfig;
|
|
40
|
+
/**
|
|
41
|
+
* Load configuration from the default location or return defaults
|
|
42
|
+
* @param startDir - Directory to start searching from
|
|
43
|
+
* @returns Configuration (from file if found, defaults otherwise)
|
|
44
|
+
*/
|
|
45
|
+
export declare function loadConfig(startDir?: string): FlowSpecConfig;
|
|
46
|
+
/**
|
|
47
|
+
* Merge CLI options with configuration file values
|
|
48
|
+
* CLI options take precedence over config file values
|
|
49
|
+
*/
|
|
50
|
+
export declare function mergeConfig(config: FlowSpecConfig, cliOptions: {
|
|
51
|
+
baseUrl?: string;
|
|
52
|
+
timeout?: number;
|
|
53
|
+
}): FlowSpecConfig;
|
|
54
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAI/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,cAI5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,GAAE,MAAsB,GAC/B,MAAM,GAAG,SAAS,CAmBpB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAkCjE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,GAAE,MAAsB,GAAG,cAAc,CAQ3E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,cAAc,EACtB,UAAU,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,cAAc,CAMhB"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
/**
|
|
6
|
+
* Schema for FlowSpec project configuration
|
|
7
|
+
*/
|
|
8
|
+
export const FlowSpecConfigSchema = z.object({
|
|
9
|
+
baseUrl: z.string().url().optional().default("http://localhost:3000"),
|
|
10
|
+
timeout: z.number().positive().optional().default(10000),
|
|
11
|
+
specsDir: z.string().optional().default("specs/"),
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Default configuration values
|
|
15
|
+
*/
|
|
16
|
+
export const DEFAULT_CONFIG = {
|
|
17
|
+
baseUrl: "http://localhost:3000",
|
|
18
|
+
timeout: 10000,
|
|
19
|
+
specsDir: "specs/",
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The standard configuration file name
|
|
23
|
+
*/
|
|
24
|
+
export const CONFIG_FILE_NAME = "flowspec.config.yaml";
|
|
25
|
+
/**
|
|
26
|
+
* Find the configuration file by walking up the directory tree
|
|
27
|
+
* @param startDir - Directory to start searching from
|
|
28
|
+
* @returns Path to config file if found, undefined otherwise
|
|
29
|
+
*/
|
|
30
|
+
export function findConfigFile(startDir = process.cwd()) {
|
|
31
|
+
let currentDir = resolve(startDir);
|
|
32
|
+
const root = resolve("/");
|
|
33
|
+
while (currentDir !== root) {
|
|
34
|
+
const configPath = join(currentDir, CONFIG_FILE_NAME);
|
|
35
|
+
if (existsSync(configPath)) {
|
|
36
|
+
return configPath;
|
|
37
|
+
}
|
|
38
|
+
currentDir = resolve(currentDir, "..");
|
|
39
|
+
}
|
|
40
|
+
// Check root as well
|
|
41
|
+
const rootConfig = join(root, CONFIG_FILE_NAME);
|
|
42
|
+
if (existsSync(rootConfig)) {
|
|
43
|
+
return rootConfig;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Load and parse a FlowSpec configuration file
|
|
49
|
+
* @param configPath - Path to the config file
|
|
50
|
+
* @returns Parsed configuration
|
|
51
|
+
* @throws Error if file not found or invalid
|
|
52
|
+
*/
|
|
53
|
+
export function loadConfigFile(configPath) {
|
|
54
|
+
if (!existsSync(configPath)) {
|
|
55
|
+
throw new Error(`Configuration file not found: ${configPath}`);
|
|
56
|
+
}
|
|
57
|
+
const content = readFileSync(configPath, "utf-8");
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = yaml.load(content);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (error instanceof yaml.YAMLException) {
|
|
64
|
+
throw new Error(`Invalid YAML in config file: ${error.reason} at line ${error.mark?.line ?? "unknown"}`);
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
// Handle empty file case
|
|
69
|
+
if (parsed === undefined || parsed === null) {
|
|
70
|
+
return DEFAULT_CONFIG;
|
|
71
|
+
}
|
|
72
|
+
const result = FlowSpecConfigSchema.safeParse(parsed);
|
|
73
|
+
if (!result.success) {
|
|
74
|
+
const issues = result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`);
|
|
75
|
+
throw new Error(`Invalid configuration: ${issues.join("; ")}`);
|
|
76
|
+
}
|
|
77
|
+
return result.data;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Load configuration from the default location or return defaults
|
|
81
|
+
* @param startDir - Directory to start searching from
|
|
82
|
+
* @returns Configuration (from file if found, defaults otherwise)
|
|
83
|
+
*/
|
|
84
|
+
export function loadConfig(startDir = process.cwd()) {
|
|
85
|
+
const configPath = findConfigFile(startDir);
|
|
86
|
+
if (configPath) {
|
|
87
|
+
return loadConfigFile(configPath);
|
|
88
|
+
}
|
|
89
|
+
return DEFAULT_CONFIG;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Merge CLI options with configuration file values
|
|
93
|
+
* CLI options take precedence over config file values
|
|
94
|
+
*/
|
|
95
|
+
export function mergeConfig(config, cliOptions) {
|
|
96
|
+
return {
|
|
97
|
+
baseUrl: cliOptions.baseUrl ?? config.baseUrl,
|
|
98
|
+
timeout: cliOptions.timeout ?? config.timeout,
|
|
99
|
+
specsDir: config.specsDir,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { loadConfig, mergeConfig } from "./config";
|
|
5
|
+
import { formatInitResult, initProject } from "./init";
|
|
6
|
+
import { parseFlowFile } from "./parser";
|
|
7
|
+
import { formatResult, formatSummary } from "./reporter";
|
|
8
|
+
import { DEFAULT_TIMEOUT, runFlow } from "./runner";
|
|
9
|
+
function showHelp() {
|
|
10
|
+
console.log(`
|
|
11
|
+
Usage: flowspec <command> [options]
|
|
12
|
+
|
|
13
|
+
Commands:
|
|
14
|
+
init Initialize FlowSpec in the current directory
|
|
15
|
+
run <path> Run FlowSpec flow files
|
|
16
|
+
|
|
17
|
+
Run Command Options:
|
|
18
|
+
--base-url <url> Base URL for relative paths (default from config or http://localhost:3000)
|
|
19
|
+
--timeout <ms> Assertion retry timeout in milliseconds (default: ${DEFAULT_TIMEOUT})
|
|
20
|
+
--help Show help
|
|
21
|
+
|
|
22
|
+
Init Command:
|
|
23
|
+
Creates FlowSpec configuration in the current directory:
|
|
24
|
+
- flowspec.config.yaml (project settings)
|
|
25
|
+
- specs/example.flow.yaml (sample flow)
|
|
26
|
+
- .claude/settings.local.json (protects specs from AI edits)
|
|
27
|
+
|
|
28
|
+
Exit codes:
|
|
29
|
+
0 All flows passed
|
|
30
|
+
1 One or more flows failed
|
|
31
|
+
2 Parse error (invalid YAML/schema)
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
function parseArgs(args) {
|
|
35
|
+
const options = {
|
|
36
|
+
showHelp: false,
|
|
37
|
+
};
|
|
38
|
+
for (let i = 0; i < args.length; i++) {
|
|
39
|
+
const arg = args[i];
|
|
40
|
+
if (arg === "--help" || arg === "-h") {
|
|
41
|
+
options.showHelp = true;
|
|
42
|
+
}
|
|
43
|
+
else if (arg === "--base-url" && i + 1 < args.length) {
|
|
44
|
+
options.baseUrl = args[++i];
|
|
45
|
+
}
|
|
46
|
+
else if (arg === "--timeout" && i + 1 < args.length) {
|
|
47
|
+
const timeoutValue = Number.parseInt(args[++i], 10);
|
|
48
|
+
if (!Number.isNaN(timeoutValue)) {
|
|
49
|
+
options.timeout = timeoutValue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (!arg.startsWith("-") && !options.path) {
|
|
53
|
+
options.path = arg;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return options;
|
|
57
|
+
}
|
|
58
|
+
function discoverFlowFiles(path) {
|
|
59
|
+
const absolutePath = resolve(path);
|
|
60
|
+
if (!existsSync(absolutePath)) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const stats = statSync(absolutePath);
|
|
64
|
+
if (stats.isFile()) {
|
|
65
|
+
return [absolutePath];
|
|
66
|
+
}
|
|
67
|
+
if (stats.isDirectory()) {
|
|
68
|
+
const files = readdirSync(absolutePath);
|
|
69
|
+
return files
|
|
70
|
+
.filter((file) => file.endsWith(".flow.yaml"))
|
|
71
|
+
.map((file) => join(absolutePath, file))
|
|
72
|
+
.sort();
|
|
73
|
+
}
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
function parseFlowFiles(filePaths) {
|
|
77
|
+
const flows = [];
|
|
78
|
+
const errors = [];
|
|
79
|
+
for (const filePath of filePaths) {
|
|
80
|
+
try {
|
|
81
|
+
const flow = parseFlowFile(filePath);
|
|
82
|
+
flows.push({ filePath, flow });
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
errors.push({ filePath, error: message });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { flows, errors };
|
|
90
|
+
}
|
|
91
|
+
async function runFlows(parsedFlows, baseUrl, timeout) {
|
|
92
|
+
const results = [];
|
|
93
|
+
for (const { flow } of parsedFlows) {
|
|
94
|
+
const result = await runFlow(flow, { baseUrl, timeout });
|
|
95
|
+
console.log(formatResult(result));
|
|
96
|
+
results.push(result);
|
|
97
|
+
}
|
|
98
|
+
return results;
|
|
99
|
+
}
|
|
100
|
+
function handleInitCommand() {
|
|
101
|
+
const result = initProject(process.cwd());
|
|
102
|
+
console.log(formatInitResult(result));
|
|
103
|
+
process.exit(result.success ? 0 : 1);
|
|
104
|
+
}
|
|
105
|
+
async function handleRunCommand(args) {
|
|
106
|
+
const options = parseArgs(args);
|
|
107
|
+
if (options.showHelp) {
|
|
108
|
+
showHelp();
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
if (!options.path) {
|
|
112
|
+
console.error("Error: No path specified");
|
|
113
|
+
showHelp();
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
const absolutePath = resolve(options.path);
|
|
117
|
+
if (!existsSync(absolutePath)) {
|
|
118
|
+
console.error(`Error: Path not found: ${options.path}`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
// Load configuration and merge with CLI options
|
|
122
|
+
const config = loadConfig();
|
|
123
|
+
const mergedConfig = mergeConfig(config, {
|
|
124
|
+
baseUrl: options.baseUrl,
|
|
125
|
+
timeout: options.timeout,
|
|
126
|
+
});
|
|
127
|
+
const flowFiles = discoverFlowFiles(options.path);
|
|
128
|
+
if (flowFiles.length === 0) {
|
|
129
|
+
console.log("No flow files found");
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
// Parse all flow files first
|
|
133
|
+
const { flows, errors } = parseFlowFiles(flowFiles);
|
|
134
|
+
// If there are parse errors, report them and exit with code 2
|
|
135
|
+
if (errors.length > 0) {
|
|
136
|
+
for (const { filePath, error } of errors) {
|
|
137
|
+
console.error(`Error parsing ${filePath}:`);
|
|
138
|
+
console.error(` ${error}`);
|
|
139
|
+
}
|
|
140
|
+
process.exit(2);
|
|
141
|
+
}
|
|
142
|
+
// Run all flows
|
|
143
|
+
const results = await runFlows(flows, mergedConfig.baseUrl, mergedConfig.timeout);
|
|
144
|
+
// Print summary
|
|
145
|
+
console.log();
|
|
146
|
+
console.log(formatSummary(results));
|
|
147
|
+
// Exit with appropriate code
|
|
148
|
+
const allPassed = results.every((r) => r.success);
|
|
149
|
+
process.exit(allPassed ? 0 : 1);
|
|
150
|
+
}
|
|
151
|
+
async function main() {
|
|
152
|
+
// Skip first two args: "bun" and script path
|
|
153
|
+
const args = process.argv.slice(2);
|
|
154
|
+
// Handle case when no arguments
|
|
155
|
+
if (args.length === 0) {
|
|
156
|
+
showHelp();
|
|
157
|
+
process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
// Extract the command
|
|
160
|
+
const command = args[0];
|
|
161
|
+
if (command === "init") {
|
|
162
|
+
handleInitCommand();
|
|
163
|
+
}
|
|
164
|
+
else if (command === "run") {
|
|
165
|
+
await handleRunCommand(args.slice(1));
|
|
166
|
+
}
|
|
167
|
+
else if (command === "--help" || command === "-h") {
|
|
168
|
+
showHelp();
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
console.error(`Unknown command: ${command}`);
|
|
173
|
+
showHelp();
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
main().catch((error) => {
|
|
178
|
+
console.error("Unexpected error:", error.message);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
});
|
|
181
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of creating a single file during init
|
|
3
|
+
*/
|
|
4
|
+
export interface InitFileResult {
|
|
5
|
+
path: string;
|
|
6
|
+
created: boolean;
|
|
7
|
+
skipped: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Result of the init command
|
|
12
|
+
*/
|
|
13
|
+
export interface InitResult {
|
|
14
|
+
files: InitFileResult[];
|
|
15
|
+
success: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Default content for flowspec.config.yaml
|
|
19
|
+
*/
|
|
20
|
+
export declare const DEFAULT_CONFIG_CONTENT = "baseUrl: http://localhost:3000\ntimeout: 10000\nspecsDir: specs/\n";
|
|
21
|
+
/**
|
|
22
|
+
* Default content for example flow file
|
|
23
|
+
*/
|
|
24
|
+
export declare const DEFAULT_EXAMPLE_FLOW_CONTENT = "name: example-flow\ndescription: Example flow - customize this for your app\nsteps:\n - visit: /\nexpect:\n - visible: Welcome\n";
|
|
25
|
+
/**
|
|
26
|
+
* Default content for Claude settings to protect specs
|
|
27
|
+
*/
|
|
28
|
+
export declare const DEFAULT_CLAUDE_SETTINGS_CONTENT = "{\n \"hooks\": {\n \"PreToolUse\": [{\n \"matcher\": { \"tool\": [\"Edit\", \"Write\"], \"path\": \"specs/**/*.flow.yaml\" },\n \"command\": \"echo '\u274C Flow specs are immutable. Fix the implementation, not the spec.' && exit 1\"\n }]\n }\n}\n";
|
|
29
|
+
/**
|
|
30
|
+
* Initialize FlowSpec in a project directory
|
|
31
|
+
* Creates configuration files and example flow
|
|
32
|
+
*
|
|
33
|
+
* @param projectDir - Directory to initialize (defaults to cwd)
|
|
34
|
+
* @returns Result with details about each file created
|
|
35
|
+
*/
|
|
36
|
+
export declare function initProject(projectDir?: string): InitResult;
|
|
37
|
+
/**
|
|
38
|
+
* Format the init result for console output
|
|
39
|
+
*/
|
|
40
|
+
export declare function formatInitResult(result: InitResult): string;
|
|
41
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,eAAO,MAAM,sBAAsB,uEAGlC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,4BAA4B,uIAMxC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,+BAA+B,6QAQ3C,CAAC;AAiGF;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,UAAU,GAAE,MAAsB,GAAG,UAAU,CAgC1E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAuC3D"}
|