runwork 0.10.0 → 0.10.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/dist/commands/__tests__/info-merge.test.d.ts +1 -0
- package/dist/commands/__tests__/info-merge.test.js +55 -0
- package/dist/commands/info.d.ts +141 -0
- package/dist/commands/info.js +29 -7
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { __testing } from '../info.js';
|
|
3
|
+
const { mergeSources, padName } = __testing;
|
|
4
|
+
describe('mergeSources — bad data resilience', () => {
|
|
5
|
+
it('drops blueprint entries with missing name', () => {
|
|
6
|
+
const map = mergeSources([
|
|
7
|
+
{ name: 'good', extra: {} },
|
|
8
|
+
{ name: undefined, extra: {} },
|
|
9
|
+
{ name: '', extra: {} },
|
|
10
|
+
], [], (item) => item.name ?? '', 'app-1');
|
|
11
|
+
expect(Array.from(map.keys())).toEqual(['good']);
|
|
12
|
+
});
|
|
13
|
+
it('drops server items whose name resolver returns undefined or empty', () => {
|
|
14
|
+
const map = mergeSources([], [
|
|
15
|
+
{ name: 'good', appId: 'app-1', appName: 'My App', deploymentMode: 'preview' },
|
|
16
|
+
{ name: undefined, appId: 'app-1', appName: 'My App', deploymentMode: 'preview' },
|
|
17
|
+
{ name: '', appId: 'app-1', appName: 'My App', deploymentMode: 'preview' },
|
|
18
|
+
], (item) => item.name ?? '', 'app-1');
|
|
19
|
+
expect(Array.from(map.keys())).toEqual(['good']);
|
|
20
|
+
});
|
|
21
|
+
it('skips server items belonging to other apps', () => {
|
|
22
|
+
const map = mergeSources([], [
|
|
23
|
+
{ name: 'mine', appId: 'app-1', appName: 'A', deploymentMode: 'preview' },
|
|
24
|
+
{ name: 'theirs', appId: 'app-2', appName: 'B', deploymentMode: 'preview' },
|
|
25
|
+
], (item) => item.name ?? '', 'app-1');
|
|
26
|
+
expect(Array.from(map.keys())).toEqual(['mine']);
|
|
27
|
+
});
|
|
28
|
+
it('merges blueprint and server entries by name', () => {
|
|
29
|
+
const map = mergeSources([{ name: 'agent-x', extra: { type: 'task' } }], [
|
|
30
|
+
{ name: 'agent-x', appId: 'app-1', appName: 'A', deploymentMode: 'production' },
|
|
31
|
+
{ name: 'agent-y', appId: 'app-1', appName: 'A', deploymentMode: 'preview' },
|
|
32
|
+
], (item) => item.name ?? '', 'app-1');
|
|
33
|
+
const x = map.get('agent-x');
|
|
34
|
+
const y = map.get('agent-y');
|
|
35
|
+
expect(x?.sources).toEqual(['blueprint', 'production']);
|
|
36
|
+
expect(x?.blueprintExtra).toEqual({ type: 'task' });
|
|
37
|
+
expect(y?.sources).toEqual(['preview']);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe('padName — defensive printer', () => {
|
|
41
|
+
it('pads valid names to the requested width', () => {
|
|
42
|
+
expect(padName('foo', 8)).toBe('foo ');
|
|
43
|
+
});
|
|
44
|
+
it('returns a placeholder for undefined / null / empty inputs', () => {
|
|
45
|
+
expect(padName(undefined, 12)).toContain('(unnamed)');
|
|
46
|
+
expect(padName(null, 12)).toContain('(unnamed)');
|
|
47
|
+
expect(padName('', 12)).toContain('(unnamed)');
|
|
48
|
+
// Placeholder is also padded to the requested width.
|
|
49
|
+
expect(padName(undefined, 12).length).toBe(12);
|
|
50
|
+
});
|
|
51
|
+
it('does not throw on non-string inputs', () => {
|
|
52
|
+
expect(() => padName(42, 8)).not.toThrow();
|
|
53
|
+
expect(() => padName({}, 8)).not.toThrow();
|
|
54
|
+
});
|
|
55
|
+
});
|
package/dist/commands/info.d.ts
CHANGED
|
@@ -1,3 +1,144 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import type { WorkspaceAllData } from '../types.js';
|
|
3
|
+
interface BlueprintEntity {
|
|
4
|
+
entityName: string;
|
|
5
|
+
schema?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
interface BlueprintSchedule {
|
|
8
|
+
name: string;
|
|
9
|
+
schedule?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
interface BlueprintWorkflow {
|
|
13
|
+
name: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
}
|
|
16
|
+
interface BlueprintAgent {
|
|
17
|
+
name: string;
|
|
18
|
+
type?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
}
|
|
21
|
+
interface BlueprintEndpoint {
|
|
22
|
+
path: string;
|
|
23
|
+
method?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
}
|
|
26
|
+
interface BlueprintComponent {
|
|
27
|
+
componentName: string;
|
|
28
|
+
}
|
|
29
|
+
interface Blueprint {
|
|
30
|
+
entities?: BlueprintEntity[];
|
|
31
|
+
schedules?: BlueprintSchedule[];
|
|
32
|
+
scheduledJobs?: BlueprintSchedule[];
|
|
33
|
+
workflows?: BlueprintWorkflow[];
|
|
34
|
+
agents?: BlueprintAgent[];
|
|
35
|
+
endpoints?: BlueprintEndpoint[];
|
|
36
|
+
publicEndpoints?: BlueprintEndpoint[];
|
|
37
|
+
components?: BlueprintComponent[];
|
|
38
|
+
fileStorage?: boolean | {
|
|
39
|
+
enabled?: boolean;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
interface MergedEntity {
|
|
43
|
+
name: string;
|
|
44
|
+
sources: string[];
|
|
45
|
+
schema?: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
interface MergedScheduledJob {
|
|
48
|
+
name: string;
|
|
49
|
+
sources: string[];
|
|
50
|
+
schedule?: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
}
|
|
53
|
+
interface MergedWorkflow {
|
|
54
|
+
name: string;
|
|
55
|
+
sources: string[];
|
|
56
|
+
description?: string;
|
|
57
|
+
}
|
|
58
|
+
interface MergedAgent {
|
|
59
|
+
name: string;
|
|
60
|
+
sources: string[];
|
|
61
|
+
type?: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
}
|
|
64
|
+
interface MergedEndpoint {
|
|
65
|
+
path: string;
|
|
66
|
+
method?: string;
|
|
67
|
+
sources: string[];
|
|
68
|
+
description?: string;
|
|
69
|
+
}
|
|
70
|
+
interface MergedComponent {
|
|
71
|
+
name: string;
|
|
72
|
+
sources: string[];
|
|
73
|
+
}
|
|
74
|
+
interface MergedFileStorage {
|
|
75
|
+
enabled: boolean;
|
|
76
|
+
sources: string[];
|
|
77
|
+
}
|
|
78
|
+
interface InfoOutput {
|
|
79
|
+
app: {
|
|
80
|
+
id: string;
|
|
81
|
+
name: string;
|
|
82
|
+
slug: string;
|
|
83
|
+
} | null;
|
|
84
|
+
workspace: {
|
|
85
|
+
id: string;
|
|
86
|
+
name: string;
|
|
87
|
+
};
|
|
88
|
+
preview: {
|
|
89
|
+
url: string | null;
|
|
90
|
+
active: boolean;
|
|
91
|
+
};
|
|
92
|
+
production: {
|
|
93
|
+
url: null;
|
|
94
|
+
deployed: boolean;
|
|
95
|
+
};
|
|
96
|
+
integrations: Array<{
|
|
97
|
+
id: string;
|
|
98
|
+
status: string;
|
|
99
|
+
}> | null;
|
|
100
|
+
registries: {
|
|
101
|
+
entities: MergedEntity[];
|
|
102
|
+
workflows: MergedWorkflow[];
|
|
103
|
+
scheduledJobs: MergedScheduledJob[];
|
|
104
|
+
agents: MergedAgent[];
|
|
105
|
+
publicEndpoints: MergedEndpoint[];
|
|
106
|
+
components: MergedComponent[];
|
|
107
|
+
fileStorage: MergedFileStorage;
|
|
108
|
+
} | null;
|
|
109
|
+
cli: {
|
|
110
|
+
version: string;
|
|
111
|
+
commands: Array<{
|
|
112
|
+
name: string;
|
|
113
|
+
description: string;
|
|
114
|
+
}>;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
interface ServerRegistryItem {
|
|
118
|
+
appId: string;
|
|
119
|
+
appName: string;
|
|
120
|
+
deploymentMode?: 'preview' | 'production';
|
|
121
|
+
}
|
|
122
|
+
declare function mergeSources<T extends ServerRegistryItem>(blueprintItems: Array<{
|
|
123
|
+
name: string;
|
|
124
|
+
extra?: Record<string, unknown>;
|
|
125
|
+
}>, serverItems: T[], getServerName: (item: T) => string, appId: string): Map<string, {
|
|
126
|
+
sources: string[];
|
|
127
|
+
serverItem?: T;
|
|
128
|
+
blueprintExtra?: Record<string, unknown>;
|
|
129
|
+
}>;
|
|
130
|
+
declare function buildRegistries(blueprint: Blueprint | null, serverData: WorkspaceAllData | null, appId: string): InfoOutput['registries'];
|
|
131
|
+
/**
|
|
132
|
+
* Pad a possibly-missing string to a fixed width. Returns a visible
|
|
133
|
+
* placeholder when the value isn't a usable string so the human-readable
|
|
134
|
+
* printer can never crash on an unexpectedly-undefined field.
|
|
135
|
+
*/
|
|
136
|
+
declare function padName(value: unknown, width: number): string;
|
|
2
137
|
export declare function formatSources(sources: string[]): string;
|
|
138
|
+
export declare const __testing: {
|
|
139
|
+
mergeSources: typeof mergeSources;
|
|
140
|
+
padName: typeof padName;
|
|
141
|
+
buildRegistries: typeof buildRegistries;
|
|
142
|
+
};
|
|
3
143
|
export declare const infoCommand: Command;
|
|
144
|
+
export {};
|
package/dist/commands/info.js
CHANGED
|
@@ -32,13 +32,22 @@ function readBlueprint(cwd) {
|
|
|
32
32
|
}
|
|
33
33
|
function mergeSources(blueprintItems, serverItems, getServerName, appId) {
|
|
34
34
|
const map = new Map();
|
|
35
|
+
// Skip nameless entries so we never store an `undefined` key. A bad
|
|
36
|
+
// blueprint or a partially-populated server-side row would otherwise
|
|
37
|
+
// produce a registry entry with `name === undefined`, which then crashes
|
|
38
|
+
// the human-readable printer at `a.name.padEnd(24)` and yields ugly
|
|
39
|
+
// `"undefined": ...` keys in JSON output.
|
|
35
40
|
for (const bp of blueprintItems) {
|
|
41
|
+
if (typeof bp.name !== 'string' || bp.name.length === 0)
|
|
42
|
+
continue;
|
|
36
43
|
map.set(bp.name, { sources: ['blueprint'], blueprintExtra: bp.extra });
|
|
37
44
|
}
|
|
38
45
|
for (const serverItem of serverItems) {
|
|
39
46
|
if (serverItem.appId !== appId)
|
|
40
47
|
continue;
|
|
41
48
|
const name = getServerName(serverItem);
|
|
49
|
+
if (typeof name !== 'string' || name.length === 0)
|
|
50
|
+
continue;
|
|
42
51
|
const mode = serverItem.deploymentMode;
|
|
43
52
|
const source = mode === 'production' ? 'production' : 'preview';
|
|
44
53
|
const existing = map.get(name);
|
|
@@ -171,6 +180,16 @@ function getCliCommands(command) {
|
|
|
171
180
|
return { name, description: cmd.description() };
|
|
172
181
|
});
|
|
173
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Pad a possibly-missing string to a fixed width. Returns a visible
|
|
185
|
+
* placeholder when the value isn't a usable string so the human-readable
|
|
186
|
+
* printer can never crash on an unexpectedly-undefined field.
|
|
187
|
+
*/
|
|
188
|
+
function padName(value, width) {
|
|
189
|
+
if (typeof value === 'string' && value.length > 0)
|
|
190
|
+
return value.padEnd(width);
|
|
191
|
+
return '(unnamed)'.padEnd(width);
|
|
192
|
+
}
|
|
174
193
|
export function formatSources(sources) {
|
|
175
194
|
return sources
|
|
176
195
|
.map(s => {
|
|
@@ -240,7 +259,7 @@ function printHumanOutput(data) {
|
|
|
240
259
|
if (reg.entities.length > 0) {
|
|
241
260
|
console.log(` ${bold('Entities:')}`);
|
|
242
261
|
for (const e of reg.entities) {
|
|
243
|
-
console.log(` ${e.name
|
|
262
|
+
console.log(` ${padName(e.name, 24)} ${formatSources(e.sources)}`);
|
|
244
263
|
}
|
|
245
264
|
console.log('');
|
|
246
265
|
}
|
|
@@ -248,7 +267,7 @@ function printHumanOutput(data) {
|
|
|
248
267
|
console.log(` ${bold('Workflows:')}`);
|
|
249
268
|
for (const w of reg.workflows) {
|
|
250
269
|
const desc = w.description ? ` ${dim(w.description)}` : '';
|
|
251
|
-
console.log(` ${w.name
|
|
270
|
+
console.log(` ${padName(w.name, 24)} ${formatSources(w.sources)}${desc}`);
|
|
252
271
|
}
|
|
253
272
|
console.log('');
|
|
254
273
|
}
|
|
@@ -256,7 +275,7 @@ function printHumanOutput(data) {
|
|
|
256
275
|
console.log(` ${bold('Scheduled Jobs:')}`);
|
|
257
276
|
for (const s of reg.scheduledJobs) {
|
|
258
277
|
const schedule = s.schedule ? ` ${gray(s.schedule)}` : '';
|
|
259
|
-
console.log(` ${s.name
|
|
278
|
+
console.log(` ${padName(s.name, 24)} ${formatSources(s.sources)}${schedule}`);
|
|
260
279
|
}
|
|
261
280
|
console.log('');
|
|
262
281
|
}
|
|
@@ -264,22 +283,22 @@ function printHumanOutput(data) {
|
|
|
264
283
|
console.log(` ${bold('Agents:')}`);
|
|
265
284
|
for (const a of reg.agents) {
|
|
266
285
|
const type = a.type ? ` ${dim(a.type)}` : '';
|
|
267
|
-
console.log(` ${a.name
|
|
286
|
+
console.log(` ${padName(a.name, 24)} ${formatSources(a.sources)}${type}`);
|
|
268
287
|
}
|
|
269
288
|
console.log('');
|
|
270
289
|
}
|
|
271
290
|
if (reg.publicEndpoints.length > 0) {
|
|
272
291
|
console.log(` ${bold('Public Endpoints:')}`);
|
|
273
292
|
for (const e of reg.publicEndpoints) {
|
|
274
|
-
const label = e.method ? `${e.method} ${e.path}` : e.path;
|
|
275
|
-
console.log(` ${label
|
|
293
|
+
const label = e.method && e.path ? `${e.method} ${e.path}` : (e.path || '');
|
|
294
|
+
console.log(` ${padName(label, 24)} ${formatSources(e.sources)}`);
|
|
276
295
|
}
|
|
277
296
|
console.log('');
|
|
278
297
|
}
|
|
279
298
|
if (reg.components.length > 0) {
|
|
280
299
|
console.log(` ${bold('Components:')}`);
|
|
281
300
|
for (const c of reg.components) {
|
|
282
|
-
console.log(` ${c.name
|
|
301
|
+
console.log(` ${padName(c.name, 24)} ${formatSources(c.sources)}`);
|
|
283
302
|
}
|
|
284
303
|
console.log('');
|
|
285
304
|
}
|
|
@@ -298,6 +317,9 @@ function printHumanOutput(data) {
|
|
|
298
317
|
console.log(dim(` CLI version: ${data.cli.version}`));
|
|
299
318
|
console.log('');
|
|
300
319
|
}
|
|
320
|
+
// Internal helpers exposed for unit tests; do not import from anywhere
|
|
321
|
+
// other than `__tests__/`.
|
|
322
|
+
export const __testing = { mergeSources, padName, buildRegistries };
|
|
301
323
|
export const infoCommand = new Command('info')
|
|
302
324
|
.description('Show app context, registries, and CLI reference (use --json for agent discovery)')
|
|
303
325
|
.action(async (_opts, command) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.10.
|
|
1
|
+
export declare const VERSION = "0.10.1";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by scripts/embed-types.ts -- do not edit
|
|
2
|
-
export const VERSION = "0.10.
|
|
2
|
+
export const VERSION = "0.10.1";
|
package/package.json
CHANGED