@procedures/playground 0.0.0-stage → 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/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +694 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +136 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -3
- package/src/index.ts +219 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Mark Russell
|
|
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
CHANGED
|
@@ -1,3 +1,695 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @procedures/playground
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@procedures/playground)
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
|
|
8
|
+
> Type-safe aggregation building for procedure compositions
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
`client-playground` provides a fluent TypeScript API for composing procedure references with full type safety and autocomplete. Write aggregations as executable TypeScript files (`.play.ts`) that can be run directly with `tsx` or `ts-node`. Perfect for testing procedure compositions, building complex workflows, and exploring procedure APIs interactively.
|
|
13
|
+
|
|
14
|
+
### Key Features
|
|
15
|
+
|
|
16
|
+
- **Type-Safe Builder API**: Fluent interface with full TypeScript autocomplete
|
|
17
|
+
- **Executable Scripts**: Write `.play.ts` files that run directly
|
|
18
|
+
- **Output References**: Reference previous stage outputs with `$ref` syntax
|
|
19
|
+
- **Named Stages**: Name stages for later reference in the execution DAG
|
|
20
|
+
- **Execution Control**: Control when procedures execute (`$immediate`, `$never`, `$parent`)
|
|
21
|
+
- **Local Transport**: Uses in-process transport for fast execution
|
|
22
|
+
- **Zero Configuration**: Auto-discovers registered procedures
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
```mermaid
|
|
27
|
+
graph TB
|
|
28
|
+
subgraph "Playground File (.play.ts)"
|
|
29
|
+
BUILDER[ProcBuilder API]
|
|
30
|
+
REF[Output References]
|
|
31
|
+
RUN[run() Executor]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
subgraph "Runtime"
|
|
35
|
+
LOCAL[Local Transport]
|
|
36
|
+
REGISTRY[Procedure Registry]
|
|
37
|
+
EXEC[Execution Engine]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
subgraph "Procedures"
|
|
41
|
+
PROC1[git.status]
|
|
42
|
+
PROC2[git.add]
|
|
43
|
+
PROC3[pnpm.install]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
BUILDER --> REF
|
|
47
|
+
REF --> RUN
|
|
48
|
+
RUN --> LOCAL
|
|
49
|
+
LOCAL --> REGISTRY
|
|
50
|
+
REGISTRY --> EXEC
|
|
51
|
+
EXEC --> PROC1
|
|
52
|
+
EXEC --> PROC2
|
|
53
|
+
EXEC --> PROC3
|
|
54
|
+
|
|
55
|
+
style BUILDER fill:#7ed321
|
|
56
|
+
style RUN fill:#f5a623
|
|
57
|
+
style LOCAL fill:#50e3c2
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Execution Flow
|
|
61
|
+
|
|
62
|
+
```mermaid
|
|
63
|
+
sequenceDiagram
|
|
64
|
+
participant Script as playground.play.ts
|
|
65
|
+
participant Builder as proc()
|
|
66
|
+
participant Executor as run()
|
|
67
|
+
participant Registry as PROCEDURE_REGISTRY
|
|
68
|
+
participant Proc as Procedure Handler
|
|
69
|
+
|
|
70
|
+
Script->>Builder: proc(["git", "status"]).input({ cwd: "." })
|
|
71
|
+
Builder-->>Script: ProcBuilder with .ref
|
|
72
|
+
Script->>Executor: run(procRef)
|
|
73
|
+
Executor->>Registry: Discover all procedures
|
|
74
|
+
Registry-->>Executor: Registered procedures
|
|
75
|
+
Executor->>Executor: Create local transport
|
|
76
|
+
Executor->>Executor: Sync registry to transport
|
|
77
|
+
Executor->>Proc: Execute procedure
|
|
78
|
+
Proc-->>Executor: Result
|
|
79
|
+
Executor->>Executor: Print result (if print: true)
|
|
80
|
+
Executor-->>Script: Typed result
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Installation
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm install @procedures/playground
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Or with pnpm:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pnpm add @procedures/playground
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Dependencies
|
|
96
|
+
|
|
97
|
+
This package depends on:
|
|
98
|
+
- `@procedures/client` - Core client/procedure system (dynamically imported)
|
|
99
|
+
|
|
100
|
+
### Runtime Requirements
|
|
101
|
+
|
|
102
|
+
- `tsx` or `ts-node` - For executing `.play.ts` files
|
|
103
|
+
- Node.js >= 22.0.0
|
|
104
|
+
|
|
105
|
+
## Usage
|
|
106
|
+
|
|
107
|
+
### Quick Start
|
|
108
|
+
|
|
109
|
+
Create a playground file:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// my-workflow.play.ts
|
|
113
|
+
import { proc, ref, run } from "@procedures/playground";
|
|
114
|
+
|
|
115
|
+
// Execute a simple procedure
|
|
116
|
+
run(
|
|
117
|
+
proc(["git", "status"]).input({ cwd: "." }).ref
|
|
118
|
+
);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Run it:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npx tsx my-workflow.play.ts
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Output:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"branch": "main",
|
|
132
|
+
"ahead": 0,
|
|
133
|
+
"behind": 0,
|
|
134
|
+
"files": [],
|
|
135
|
+
"clean": true
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Builder API
|
|
140
|
+
|
|
141
|
+
The `proc()` builder provides a fluent interface:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { proc } from "@procedures/playground";
|
|
145
|
+
|
|
146
|
+
// Simple procedure reference
|
|
147
|
+
const gitStatus = proc(["git", "status"])
|
|
148
|
+
.input({ cwd: "/path/to/repo" })
|
|
149
|
+
.ref;
|
|
150
|
+
|
|
151
|
+
// Named stage for reference
|
|
152
|
+
const hasChanges = proc(["git", "hasChanges"])
|
|
153
|
+
.input({ cwd: "." })
|
|
154
|
+
.name("changes") // Can be referenced as $ref: "changes"
|
|
155
|
+
.ref;
|
|
156
|
+
|
|
157
|
+
// With execution control
|
|
158
|
+
const conditionalAdd = proc(["git", "add"])
|
|
159
|
+
.input({ all: true })
|
|
160
|
+
.when("$parent") // Execute in parent context
|
|
161
|
+
.ref;
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Output References
|
|
165
|
+
|
|
166
|
+
Reference outputs from named stages:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { proc, ref, run } from "@procedures/playground";
|
|
170
|
+
|
|
171
|
+
run(
|
|
172
|
+
proc(["dag", "traverse"]).input({
|
|
173
|
+
visit: [
|
|
174
|
+
// Stage 1: Check for changes
|
|
175
|
+
proc(["git", "hasChanges"])
|
|
176
|
+
.name("changes")
|
|
177
|
+
.ref,
|
|
178
|
+
|
|
179
|
+
// Stage 2: Conditional on stage 1
|
|
180
|
+
proc(["client", "conditional"]).input({
|
|
181
|
+
condition: ref("changes.value"), // Reference "changes" output
|
|
182
|
+
then: proc(["git", "add"]).input({ all: true }).ref,
|
|
183
|
+
}).ref,
|
|
184
|
+
],
|
|
185
|
+
}).ref
|
|
186
|
+
);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Reference Syntax
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
ref("changes.value") // Reference the .value field of "changes" output
|
|
193
|
+
ref("$last") // Reference the previous step's output
|
|
194
|
+
ref("$last.success") // Reference the .success field of previous output
|
|
195
|
+
ref("install.packages") // Deep property access
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Complex Workflows
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { proc, ref, run } from "@procedures/playground";
|
|
202
|
+
|
|
203
|
+
// Multi-step build and deploy workflow
|
|
204
|
+
run(
|
|
205
|
+
proc(["dag", "traverse"]).input({
|
|
206
|
+
visit: [
|
|
207
|
+
// 1. Install dependencies
|
|
208
|
+
proc(["pnpm", "install"])
|
|
209
|
+
.name("install")
|
|
210
|
+
.ref,
|
|
211
|
+
|
|
212
|
+
// 2. Run tests
|
|
213
|
+
proc(["shell", "exec"])
|
|
214
|
+
.input({ command: "npm test" })
|
|
215
|
+
.name("test")
|
|
216
|
+
.ref,
|
|
217
|
+
|
|
218
|
+
// 3. Build only if tests pass
|
|
219
|
+
proc(["client", "conditional"]).input({
|
|
220
|
+
condition: ref("test.exitCode"), // 0 = success
|
|
221
|
+
operator: "equals",
|
|
222
|
+
value: 0,
|
|
223
|
+
then: proc(["shell", "exec"])
|
|
224
|
+
.input({ command: "npm run build" })
|
|
225
|
+
.name("build")
|
|
226
|
+
.ref,
|
|
227
|
+
}).ref,
|
|
228
|
+
|
|
229
|
+
// 4. Deploy if build succeeded
|
|
230
|
+
proc(["client", "conditional"]).input({
|
|
231
|
+
condition: ref("build.success"),
|
|
232
|
+
then: proc(["docker", "build"]).input({
|
|
233
|
+
context: ".",
|
|
234
|
+
tag: "my-app:latest",
|
|
235
|
+
}).ref,
|
|
236
|
+
}).ref,
|
|
237
|
+
],
|
|
238
|
+
}).ref
|
|
239
|
+
);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Running Without Printing
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { run } from "@procedures/playground";
|
|
246
|
+
|
|
247
|
+
// Get result without printing
|
|
248
|
+
const result = await run(
|
|
249
|
+
proc(["git", "status"]).ref,
|
|
250
|
+
{ print: false }
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
console.log(`Current branch: ${result.branch}`);
|
|
254
|
+
|
|
255
|
+
// Custom formatting
|
|
256
|
+
const data = await run(
|
|
257
|
+
proc(["git", "log"]).input({ count: 5 }).ref,
|
|
258
|
+
{ print: true, format: "text" }
|
|
259
|
+
);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## API Reference
|
|
263
|
+
|
|
264
|
+
### `proc(path: ProcedurePath): ProcBuilder`
|
|
265
|
+
|
|
266
|
+
Create a procedure reference builder.
|
|
267
|
+
|
|
268
|
+
**Parameters:**
|
|
269
|
+
- `path: string[]` - Procedure path (e.g., `["git", "status"]`)
|
|
270
|
+
|
|
271
|
+
**Returns:** `ProcBuilder` with methods:
|
|
272
|
+
|
|
273
|
+
#### `.input<T>(input: T): ProcBuilder<T>`
|
|
274
|
+
|
|
275
|
+
Set the input for this procedure.
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
proc(["git", "add"])
|
|
279
|
+
.input({ files: ["file.ts"], all: false })
|
|
280
|
+
.ref
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
#### `.name(name: string): ProcBuilder`
|
|
284
|
+
|
|
285
|
+
Name this stage for later reference with `$ref`.
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
proc(["git", "hasChanges"])
|
|
289
|
+
.name("changes")
|
|
290
|
+
.ref
|
|
291
|
+
|
|
292
|
+
// Later:
|
|
293
|
+
ref("changes.value")
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
#### `.when(when: string): ProcBuilder`
|
|
297
|
+
|
|
298
|
+
Set when this reference should be executed.
|
|
299
|
+
|
|
300
|
+
**Values:**
|
|
301
|
+
- `"$immediate"` - Execute immediately
|
|
302
|
+
- `"$never"` - Never execute (for testing)
|
|
303
|
+
- `"$parent"` - Execute in parent context
|
|
304
|
+
- Custom context name
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
proc(["git", "add"])
|
|
308
|
+
.when("$parent")
|
|
309
|
+
.ref
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
#### `.ref: ProcedureRefJson`
|
|
313
|
+
|
|
314
|
+
Get the procedure reference object.
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
const procRef = proc(["git", "status"])
|
|
318
|
+
.input({ cwd: "." })
|
|
319
|
+
.ref;
|
|
320
|
+
|
|
321
|
+
// procRef is:
|
|
322
|
+
// {
|
|
323
|
+
// $proc: ["git", "status"],
|
|
324
|
+
// input: { cwd: "." }
|
|
325
|
+
// }
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### `ref(path: string): OutputRef`
|
|
329
|
+
|
|
330
|
+
Create an output reference.
|
|
331
|
+
|
|
332
|
+
**Parameters:**
|
|
333
|
+
- `path: string` - Reference path (e.g., `"stageName.value"` or `"$last.value"`)
|
|
334
|
+
|
|
335
|
+
**Returns:** `OutputRef` object
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
const outputRef = ref("changes.value");
|
|
339
|
+
// { $ref: "changes.value" }
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### `run<T>(procedureRef: ProcedureRefJson, options?: RunOptions): Promise<T>`
|
|
343
|
+
|
|
344
|
+
Execute a procedure reference.
|
|
345
|
+
|
|
346
|
+
**Parameters:**
|
|
347
|
+
- `procedureRef: ProcedureRefJson` - Procedure reference to execute
|
|
348
|
+
- `options?: RunOptions` - Execution options
|
|
349
|
+
|
|
350
|
+
**Options:**
|
|
351
|
+
```typescript
|
|
352
|
+
interface RunOptions {
|
|
353
|
+
print?: boolean; // Print result (default: true)
|
|
354
|
+
format?: "json" | "text"; // Output format (default: "json")
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Returns:** `Promise<T>` - Typed result
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// With printing (default)
|
|
362
|
+
await run(proc(["git", "status"]).ref);
|
|
363
|
+
|
|
364
|
+
// Without printing
|
|
365
|
+
const result = await run(
|
|
366
|
+
proc(["git", "status"]).ref,
|
|
367
|
+
{ print: false }
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
// Text format
|
|
371
|
+
await run(
|
|
372
|
+
proc(["shell", "exec"]).input({ command: "ls" }).ref,
|
|
373
|
+
{ format: "text" }
|
|
374
|
+
);
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Types
|
|
378
|
+
|
|
379
|
+
### ProcBuilder
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
interface ProcBuilder<TInput = unknown> {
|
|
383
|
+
input<T>(input: T): ProcBuilder<T>;
|
|
384
|
+
name(name: string): ProcBuilder<TInput>;
|
|
385
|
+
when(when: string): ProcBuilder<TInput>;
|
|
386
|
+
readonly ref: ProcedureRefJson<TInput>;
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### ProcedureRefJson
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
interface ProcedureRefJson<TInput = unknown> {
|
|
394
|
+
$proc: ProcedurePath;
|
|
395
|
+
input: TInput;
|
|
396
|
+
$name?: string;
|
|
397
|
+
$when?: string;
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### OutputRef
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
interface OutputRef {
|
|
405
|
+
readonly $ref: string;
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Examples
|
|
410
|
+
|
|
411
|
+
### Git Workflow
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
// git-workflow.play.ts
|
|
415
|
+
import { proc, ref, run } from "@procedures/playground";
|
|
416
|
+
|
|
417
|
+
run(
|
|
418
|
+
proc(["dag", "traverse"]).input({
|
|
419
|
+
visit: [
|
|
420
|
+
// Check for changes
|
|
421
|
+
proc(["git", "hasChanges"])
|
|
422
|
+
.name("changes")
|
|
423
|
+
.ref,
|
|
424
|
+
|
|
425
|
+
// Stage all if changes exist
|
|
426
|
+
proc(["client", "conditional"]).input({
|
|
427
|
+
condition: ref("changes.value"),
|
|
428
|
+
then: proc(["git", "add"]).input({ all: true }).ref,
|
|
429
|
+
}).ref,
|
|
430
|
+
|
|
431
|
+
// Commit
|
|
432
|
+
proc(["git", "commit"]).input({
|
|
433
|
+
message: "Automated commit from playground",
|
|
434
|
+
}).ref,
|
|
435
|
+
],
|
|
436
|
+
}).ref
|
|
437
|
+
);
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Build Pipeline
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
// build.play.ts
|
|
444
|
+
import { proc, run } from "@procedures/playground";
|
|
445
|
+
|
|
446
|
+
run(
|
|
447
|
+
proc(["dag", "traverse"]).input({
|
|
448
|
+
visit: [
|
|
449
|
+
proc(["pnpm", "install"]).ref,
|
|
450
|
+
proc(["shell", "exec"]).input({ command: "npm run lint" }).ref,
|
|
451
|
+
proc(["shell", "exec"]).input({ command: "npm test" }).ref,
|
|
452
|
+
proc(["shell", "exec"]).input({ command: "npm run build" }).ref,
|
|
453
|
+
],
|
|
454
|
+
}).ref
|
|
455
|
+
);
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Docker Deployment
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
// deploy.play.ts
|
|
462
|
+
import { proc, ref, run } from "@procedures/playground";
|
|
463
|
+
|
|
464
|
+
run(
|
|
465
|
+
proc(["dag", "traverse"]).input({
|
|
466
|
+
visit: [
|
|
467
|
+
// Build image
|
|
468
|
+
proc(["docker", "build"]).input({
|
|
469
|
+
context: ".",
|
|
470
|
+
tag: "my-app:latest",
|
|
471
|
+
}).name("build").ref,
|
|
472
|
+
|
|
473
|
+
// Run container
|
|
474
|
+
proc(["docker", "run"]).input({
|
|
475
|
+
image: "my-app:latest",
|
|
476
|
+
name: "my-app-container",
|
|
477
|
+
ports: ["3000:3000"],
|
|
478
|
+
detach: true,
|
|
479
|
+
}).ref,
|
|
480
|
+
|
|
481
|
+
// Health check
|
|
482
|
+
proc(["shell", "exec"]).input({
|
|
483
|
+
command: "curl http://localhost:3000/health",
|
|
484
|
+
}).ref,
|
|
485
|
+
],
|
|
486
|
+
}).ref
|
|
487
|
+
);
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Testing Procedures
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
// test-procedures.play.ts
|
|
494
|
+
import { proc, run } from "@procedures/playground";
|
|
495
|
+
|
|
496
|
+
// Test git status
|
|
497
|
+
console.log("=== Testing git.status ===");
|
|
498
|
+
await run(proc(["git", "status"]).input({ cwd: "." }).ref);
|
|
499
|
+
|
|
500
|
+
// Test pnpm store path
|
|
501
|
+
console.log("\n=== Testing pnpm.store.path ===");
|
|
502
|
+
await run(proc(["pnpm", "store", "path"]).ref);
|
|
503
|
+
|
|
504
|
+
// Test shell execution
|
|
505
|
+
console.log("\n=== Testing shell.exec ===");
|
|
506
|
+
await run(proc(["shell", "exec"]).input({ command: "echo 'Hello from playground!'" }).ref);
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Conditional Logic
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
// conditional.play.ts
|
|
513
|
+
import { proc, ref, run } from "@procedures/playground";
|
|
514
|
+
|
|
515
|
+
run(
|
|
516
|
+
proc(["dag", "traverse"]).input({
|
|
517
|
+
visit: [
|
|
518
|
+
// Get environment
|
|
519
|
+
proc(["shell", "exec"])
|
|
520
|
+
.input({ command: "echo $NODE_ENV" })
|
|
521
|
+
.name("env")
|
|
522
|
+
.ref,
|
|
523
|
+
|
|
524
|
+
// Install based on environment
|
|
525
|
+
proc(["client", "conditional"]).input({
|
|
526
|
+
condition: ref("env.stdout"),
|
|
527
|
+
operator: "contains",
|
|
528
|
+
value: "production",
|
|
529
|
+
then: proc(["pnpm", "install"]).input({ production: true }).ref,
|
|
530
|
+
else: proc(["pnpm", "install"]).ref,
|
|
531
|
+
}).ref,
|
|
532
|
+
],
|
|
533
|
+
}).ref
|
|
534
|
+
);
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## Use Cases
|
|
538
|
+
|
|
539
|
+
### 1. Interactive Procedure Testing
|
|
540
|
+
|
|
541
|
+
Quickly test procedure calls without setting up a full client:
|
|
542
|
+
|
|
543
|
+
```typescript
|
|
544
|
+
// test-docker.play.ts
|
|
545
|
+
import { proc, run } from "@procedures/playground";
|
|
546
|
+
|
|
547
|
+
await run(proc(["docker", "ps"]).input({ all: true }).ref);
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
```bash
|
|
551
|
+
npx tsx test-docker.play.ts
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### 2. Complex Workflow Prototyping
|
|
555
|
+
|
|
556
|
+
Prototype multi-step workflows before integrating into apps:
|
|
557
|
+
|
|
558
|
+
```typescript
|
|
559
|
+
// prototype-workflow.play.ts
|
|
560
|
+
import { proc, ref, run } from "@procedures/playground";
|
|
561
|
+
|
|
562
|
+
// Test the entire workflow
|
|
563
|
+
run(proc(["dag", "traverse"]).input({ visit: [...] }).ref);
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### 3. CI/CD Scripts
|
|
567
|
+
|
|
568
|
+
Write deployment scripts as executable TypeScript:
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
// deploy.play.ts
|
|
572
|
+
run(proc(["dag", "traverse"]).input({
|
|
573
|
+
visit: [
|
|
574
|
+
proc(["git", "pull"]).ref,
|
|
575
|
+
proc(["pnpm", "install"]).ref,
|
|
576
|
+
proc(["shell", "exec"]).input({ command: "npm run build" }).ref,
|
|
577
|
+
proc(["docker", "build"]).input({ context: ".", tag: "app:latest" }).ref,
|
|
578
|
+
proc(["docker", "compose", "up"]).input({ detach: true }).ref,
|
|
579
|
+
],
|
|
580
|
+
}).ref);
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### 4. Development Automation
|
|
584
|
+
|
|
585
|
+
Automate common development tasks:
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
// setup.play.ts
|
|
589
|
+
// Run: npx tsx setup.play.ts
|
|
590
|
+
import { proc, run } from "@procedures/playground";
|
|
591
|
+
|
|
592
|
+
run(proc(["dag", "traverse"]).input({
|
|
593
|
+
visit: [
|
|
594
|
+
proc(["git", "checkout"]).input({ branch: "main" }).ref,
|
|
595
|
+
proc(["git", "pull"]).ref,
|
|
596
|
+
proc(["pnpm", "install"]).ref,
|
|
597
|
+
proc(["shell", "exec"]).input({ command: "npm run setup:env" }).ref,
|
|
598
|
+
],
|
|
599
|
+
}).ref);
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
## Advanced Usage
|
|
603
|
+
|
|
604
|
+
### Custom Transport
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
import { run } from "@procedures/playground";
|
|
608
|
+
import { LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
|
|
609
|
+
|
|
610
|
+
// run() uses LocalTransport internally
|
|
611
|
+
// The registry is synced automatically
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Error Handling
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
import { proc, run } from "@procedures/playground";
|
|
618
|
+
|
|
619
|
+
try {
|
|
620
|
+
const result = await run(
|
|
621
|
+
proc(["nonexistent", "procedure"]).ref,
|
|
622
|
+
{ print: false }
|
|
623
|
+
);
|
|
624
|
+
} catch (error) {
|
|
625
|
+
console.error("Execution failed:", error.message);
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### TypeScript Integration
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
import { proc, run } from "@procedures/playground";
|
|
633
|
+
import type { GitStatusOutput } from "@procedures/git";
|
|
634
|
+
|
|
635
|
+
// Typed result
|
|
636
|
+
const status = await run<GitStatusOutput>(
|
|
637
|
+
proc(["git", "status"]).input({ cwd: "." }).ref,
|
|
638
|
+
{ print: false }
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
console.log(`Branch: ${status.branch}`);
|
|
642
|
+
console.log(`Clean: ${status.clean}`);
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
## Performance Considerations
|
|
646
|
+
|
|
647
|
+
- **Local Transport**: Uses in-process transport for minimal overhead
|
|
648
|
+
- **Registry Sync**: Procedures synced once per `run()` call
|
|
649
|
+
- **No Network**: All execution is local - no HTTP/WebSocket overhead
|
|
650
|
+
- **Fast Iteration**: Changes to `.play.ts` files are immediate (no build step)
|
|
651
|
+
|
|
652
|
+
## Comparison with Alternatives
|
|
653
|
+
|
|
654
|
+
### vs. Direct Client Usage
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
// Direct client - more boilerplate
|
|
658
|
+
import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
|
|
659
|
+
const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
|
|
660
|
+
const result = await client.exec(["git", "status"], { cwd: "." });
|
|
661
|
+
|
|
662
|
+
// Playground - concise
|
|
663
|
+
import { proc, run } from "@procedures/playground";
|
|
664
|
+
const result = await run(proc(["git", "status"]).input({ cwd: "." }).ref);
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### vs. Shell Scripts
|
|
668
|
+
|
|
669
|
+
```bash
|
|
670
|
+
# Shell script - no type safety
|
|
671
|
+
git status
|
|
672
|
+
pnpm install
|
|
673
|
+
npm test
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
```typescript
|
|
677
|
+
// Playground - type-safe, composable
|
|
678
|
+
run(proc(["dag", "traverse"]).input({
|
|
679
|
+
visit: [
|
|
680
|
+
proc(["git", "status"]).ref,
|
|
681
|
+
proc(["pnpm", "install"]).ref,
|
|
682
|
+
proc(["shell", "exec"]).input({ command: "npm test" }).ref,
|
|
683
|
+
],
|
|
684
|
+
}).ref);
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
## License
|
|
688
|
+
|
|
689
|
+
MIT
|
|
690
|
+
|
|
691
|
+
## Related Packages
|
|
692
|
+
|
|
693
|
+
- `@procedures/client` - Core client/procedure system
|
|
694
|
+
- `@procedures/bundle-dev` - Development bundle with procedures
|
|
695
|
+
- All `client-*` packages - Provide procedures to use in playground
|