@ldlework/workmark 1.3.1 → 1.4.1
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/README.md +22 -24
- package/dist/cli.js +9 -2
- package/dist/lib/define.d.ts +71 -2
- package/dist/lib/define.js +87 -1
- package/dist/lib/helpers.d.ts +3 -0
- package/dist/lib/helpers.js +20 -2
- package/dist/lib/load.d.ts +3 -2
- package/dist/lib/load.js +428 -55
- package/dist/lib/parse.d.ts +2 -4
- package/dist/lib/parse.js +42 -10
- package/dist/lib/project.d.ts +10 -5
- package/dist/lib/project.js +45 -12
- package/dist/lib/registry.d.ts +15 -0
- package/dist/lib/registry.js +64 -0
- package/dist/lib/types.d.ts +86 -35
- package/dist/lib/types.js +5 -1
- package/dist/lib/workspace.d.ts +6 -3
- package/dist/lib/workspace.js +61 -24
- package/package.json +1 -1
- package/src/cli.ts +8 -2
- package/src/lib/define.ts +180 -2
- package/src/lib/helpers.ts +24 -2
- package/src/lib/load.ts +556 -60
- package/src/lib/parse.ts +47 -10
- package/src/lib/project.ts +58 -13
- package/src/lib/registry.ts +80 -0
- package/src/lib/types.ts +99 -41
- package/src/lib/workspace.ts +67 -30
package/src/lib/define.ts
CHANGED
|
@@ -1,6 +1,184 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type {
|
|
3
|
+
BaseCtx,
|
|
4
|
+
FromArgsResolver,
|
|
5
|
+
FromWorkspaceResolver,
|
|
6
|
+
HandlerReturn,
|
|
7
|
+
IWorkspace,
|
|
8
|
+
NeedsCtx,
|
|
9
|
+
ProjectDef,
|
|
10
|
+
ReduceFn,
|
|
11
|
+
RunOptions,
|
|
12
|
+
SchemaFields,
|
|
13
|
+
SelectMode,
|
|
14
|
+
Trait,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
import { FROM_ARGS, FROM_WORKSPACE } from "./types.js";
|
|
17
|
+
import { registerAmbient } from "./registry.js";
|
|
18
|
+
|
|
19
|
+
// ---- defineTrait -------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/** Declare a trait: a named schema that projects fulfill and commands require. */
|
|
22
|
+
export function defineTrait<N extends string, S extends z.ZodType>(def: {
|
|
23
|
+
name: N;
|
|
24
|
+
schema: S;
|
|
25
|
+
description?: string;
|
|
26
|
+
}): Trait<N, z.output<S>> {
|
|
27
|
+
const trait = {
|
|
28
|
+
name: def.name,
|
|
29
|
+
schema: def.schema,
|
|
30
|
+
description: def.description,
|
|
31
|
+
} as Trait<N, z.output<S>>;
|
|
32
|
+
// Register into ambient registry (installed by the loader during trait import).
|
|
33
|
+
registerAmbient(trait);
|
|
34
|
+
return trait;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---- defineProject -----------------------------------------------------
|
|
2
38
|
|
|
3
|
-
/** Define a project. Identity function for type safety. */
|
|
4
39
|
export function defineProject(def: ProjectDef): ProjectDef {
|
|
5
40
|
return def;
|
|
6
41
|
}
|
|
42
|
+
|
|
43
|
+
// ---- Type inference machinery -----------------------------------------
|
|
44
|
+
|
|
45
|
+
type TraitsOf<N extends readonly Trait<string, unknown>[]> = {
|
|
46
|
+
[K in N[number] as K["name"]]: K extends Trait<string, infer T> ? T : never;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type InferField<F> = F extends z.ZodType ? z.output<F> : unknown;
|
|
50
|
+
|
|
51
|
+
type InferFields<R extends SchemaFields | undefined> = R extends SchemaFields
|
|
52
|
+
? { [K in keyof R]: InferField<R[K]> }
|
|
53
|
+
: Record<string, never>;
|
|
54
|
+
|
|
55
|
+
type CtxFor<N extends readonly Trait<string, unknown>[]> = N extends readonly []
|
|
56
|
+
? BaseCtx
|
|
57
|
+
: NeedsCtx<TraitsOf<N>>;
|
|
58
|
+
|
|
59
|
+
// ---- cmd() -------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
export interface StaticCommandDef {
|
|
62
|
+
needs?: readonly Trait[];
|
|
63
|
+
select?: SelectMode;
|
|
64
|
+
for?: string;
|
|
65
|
+
run?: RunOptions;
|
|
66
|
+
args?: SchemaFields;
|
|
67
|
+
flags?: SchemaFields;
|
|
68
|
+
meta?: { name?: string; label?: string; description?: string };
|
|
69
|
+
handler: (args: Record<string, unknown>, ctx: BaseCtx | NeedsCtx<Record<string, unknown>>) => HandlerReturn | Promise<HandlerReturn>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Declare a command. `needs`, `args`, and `flags` are inferred for handler
|
|
73
|
+
* typing; the framework manages the auto-generated `project` arg separately.
|
|
74
|
+
*
|
|
75
|
+
* `for`: bind the command to a named project. No project arg is exposed on any
|
|
76
|
+
* surface; `ctx.project` is the bound project. Validated at load.
|
|
77
|
+
*/
|
|
78
|
+
export function cmd<
|
|
79
|
+
const N extends readonly Trait<string, unknown>[] = readonly [],
|
|
80
|
+
const A extends SchemaFields = Record<string, never>,
|
|
81
|
+
const F extends SchemaFields = Record<string, never>,
|
|
82
|
+
>(def: {
|
|
83
|
+
needs?: N;
|
|
84
|
+
select?: SelectMode;
|
|
85
|
+
for?: string;
|
|
86
|
+
run?: RunOptions;
|
|
87
|
+
args?: A;
|
|
88
|
+
flags?: F;
|
|
89
|
+
meta?: { name?: string; label?: string; description?: string };
|
|
90
|
+
handler: (
|
|
91
|
+
args: InferFields<A> & InferFields<F>,
|
|
92
|
+
ctx: CtxFor<N>,
|
|
93
|
+
) => HandlerReturn | Promise<HandlerReturn>;
|
|
94
|
+
}): StaticCommandDef {
|
|
95
|
+
return def as unknown as StaticCommandDef;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const defineCommand = cmd;
|
|
99
|
+
|
|
100
|
+
// ---- fromWorkspace ------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
/** Declare a schema that depends on workspace state. The framework resolves
|
|
103
|
+
* the marker during command load and substitutes the returned zod schema. */
|
|
104
|
+
export function fromWorkspace(resolver: FromWorkspaceResolver): z.ZodType {
|
|
105
|
+
const marker = z.lazy(() => {
|
|
106
|
+
throw new Error(
|
|
107
|
+
"fromWorkspace marker used at runtime — the framework should have replaced it at load time",
|
|
108
|
+
);
|
|
109
|
+
}) as z.ZodType & { [FROM_WORKSPACE]?: FromWorkspaceResolver };
|
|
110
|
+
marker[FROM_WORKSPACE] = resolver;
|
|
111
|
+
return marker;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Declare a schema whose shape depends on other args supplied at invocation.
|
|
115
|
+
* The framework resolves this per-call, AFTER other args are parsed. The
|
|
116
|
+
* field's load-time JSON Schema is permissive (type-agnostic) — validation
|
|
117
|
+
* runs at invocation with the resolved schema. */
|
|
118
|
+
export function fromArgs(resolver: FromArgsResolver): z.ZodType {
|
|
119
|
+
const marker = z.any() as z.ZodType & { [FROM_ARGS]?: FromArgsResolver };
|
|
120
|
+
marker[FROM_ARGS] = resolver;
|
|
121
|
+
return marker;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Shortcut: enum of project names fulfilling a trait. */
|
|
125
|
+
export function projectsOf(trait: Trait): z.ZodType {
|
|
126
|
+
return fromWorkspace((ws) => {
|
|
127
|
+
const names = ws.withTrait(trait).map((p) => p.name);
|
|
128
|
+
if (names.length === 0) {
|
|
129
|
+
throw new Error(`No projects fulfill trait "${trait.name}"`);
|
|
130
|
+
}
|
|
131
|
+
return z.enum(names as [string, ...string[]]);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Build a schema whose shape depends on a specific project's trait data.
|
|
136
|
+
*
|
|
137
|
+
* Usage: `traitField(docker, d => z.enum(d.buildable)).forProject("ghost")`.
|
|
138
|
+
*/
|
|
139
|
+
export function traitField<T extends Trait<string, unknown>>(
|
|
140
|
+
trait: T,
|
|
141
|
+
selector: (data: unknown) => z.ZodType,
|
|
142
|
+
): TraitFieldBuilder {
|
|
143
|
+
return {
|
|
144
|
+
forProject(name: string): z.ZodType {
|
|
145
|
+
return fromWorkspace((ws) => {
|
|
146
|
+
const project = ws.get(name);
|
|
147
|
+
if (!project.hasTrait(trait)) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`traitField(${trait.name}).forProject("${name}") — project does not fulfill trait "${trait.name}"`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
const data = project.trait(trait as Trait<string, unknown>);
|
|
153
|
+
return selector(data);
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
fromArg(argName: string): z.ZodType {
|
|
157
|
+
return fromArgs((ws, args) => {
|
|
158
|
+
const target = args[argName];
|
|
159
|
+
if (typeof target !== "string") {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`traitField(${trait.name}).fromArg("${argName}") — expected arg "${argName}" to be a project name string, got ${typeof target}`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const project = ws.get(target);
|
|
165
|
+
if (!project.hasTrait(trait)) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`traitField(${trait.name}).fromArg("${argName}") — project "${target}" does not fulfill trait "${trait.name}"`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
const data = project.trait(trait as Trait<string, unknown>);
|
|
171
|
+
return selector(data);
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface TraitFieldBuilder {
|
|
178
|
+
forProject(name: string): z.ZodType;
|
|
179
|
+
fromArg(argName: string): z.ZodType;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ---- Re-export select and run option types -----------------------------
|
|
183
|
+
|
|
184
|
+
export type { SelectMode, RunOptions, ReduceFn };
|
package/src/lib/helpers.ts
CHANGED
|
@@ -35,16 +35,18 @@ export function fail(error: unknown): CallToolResult {
|
|
|
35
35
|
export interface ExecOptions {
|
|
36
36
|
cwd: string;
|
|
37
37
|
timeout?: number; // ms, default 120_000
|
|
38
|
+
env?: Record<string, string>;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export function execRaw(command: string, opts: ExecOptions): string {
|
|
41
|
-
const { cwd, timeout = 120_000 } = opts;
|
|
42
|
+
const { cwd, timeout = 120_000, env } = opts;
|
|
42
43
|
return execSync(command, {
|
|
43
44
|
cwd,
|
|
44
45
|
timeout,
|
|
45
46
|
encoding: "utf-8",
|
|
46
47
|
stdio: ["ignore", "pipe", "pipe"],
|
|
47
48
|
maxBuffer: 1024 * 1024 * 10,
|
|
49
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
48
50
|
});
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -62,16 +64,36 @@ export async function execAsyncRaw(
|
|
|
62
64
|
command: string,
|
|
63
65
|
opts: ExecOptions,
|
|
64
66
|
): Promise<string> {
|
|
65
|
-
const { cwd, timeout = 120_000 } = opts;
|
|
67
|
+
const { cwd, timeout = 120_000, env } = opts;
|
|
66
68
|
const { stdout } = await execPromise(command, {
|
|
67
69
|
cwd,
|
|
68
70
|
timeout,
|
|
69
71
|
encoding: "utf-8",
|
|
70
72
|
maxBuffer: 1024 * 1024 * 10,
|
|
73
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
71
74
|
});
|
|
72
75
|
return stdout;
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
/** Run a sequence of shell commands. Fails fast; concatenates outputs. */
|
|
79
|
+
export async function shSeq(
|
|
80
|
+
commands: readonly string[],
|
|
81
|
+
opts: ExecOptions,
|
|
82
|
+
): Promise<CallToolResult> {
|
|
83
|
+
const outputs: string[] = [];
|
|
84
|
+
for (const cmd of commands) {
|
|
85
|
+
try {
|
|
86
|
+
outputs.push(await execAsyncRaw(cmd, opts));
|
|
87
|
+
} catch (e) {
|
|
88
|
+
const prior = outputs.join("\n");
|
|
89
|
+
const err = e instanceof Error ? (e as Error & { stderr?: string; stdout?: string }) : null;
|
|
90
|
+
const errText = err?.stderr || err?.stdout || (err?.message ?? String(e));
|
|
91
|
+
return fail(prior ? `${prior}\n${errText}` : errText);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return ok(outputs.join("\n"));
|
|
95
|
+
}
|
|
96
|
+
|
|
75
97
|
export async function execAsync(
|
|
76
98
|
command: string,
|
|
77
99
|
opts: ExecOptions,
|