@stoneforge/quarry 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 +13 -0
- package/README.md +160 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +8 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/quarry-api.d.ts +268 -0
- package/dist/api/quarry-api.d.ts.map +1 -0
- package/dist/api/quarry-api.js +3905 -0
- package/dist/api/quarry-api.js.map +1 -0
- package/dist/api/types.d.ts +1359 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +204 -0
- package/dist/api/types.js.map +1 -0
- package/dist/bin/sf.d.ts +3 -0
- package/dist/bin/sf.d.ts.map +1 -0
- package/dist/bin/sf.js +9 -0
- package/dist/bin/sf.js.map +1 -0
- package/dist/cli/commands/admin.d.ts +11 -0
- package/dist/cli/commands/admin.d.ts.map +1 -0
- package/dist/cli/commands/admin.js +465 -0
- package/dist/cli/commands/admin.js.map +1 -0
- package/dist/cli/commands/alias.d.ts +8 -0
- package/dist/cli/commands/alias.d.ts.map +1 -0
- package/dist/cli/commands/alias.js +70 -0
- package/dist/cli/commands/alias.js.map +1 -0
- package/dist/cli/commands/channel.d.ts +13 -0
- package/dist/cli/commands/channel.d.ts.map +1 -0
- package/dist/cli/commands/channel.js +680 -0
- package/dist/cli/commands/channel.js.map +1 -0
- package/dist/cli/commands/completion.d.ts +8 -0
- package/dist/cli/commands/completion.d.ts.map +1 -0
- package/dist/cli/commands/completion.js +87 -0
- package/dist/cli/commands/completion.js.map +1 -0
- package/dist/cli/commands/config.d.ts +12 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +242 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/crud.d.ts +64 -0
- package/dist/cli/commands/crud.d.ts.map +1 -0
- package/dist/cli/commands/crud.js +805 -0
- package/dist/cli/commands/crud.js.map +1 -0
- package/dist/cli/commands/dep.d.ts +16 -0
- package/dist/cli/commands/dep.d.ts.map +1 -0
- package/dist/cli/commands/dep.js +499 -0
- package/dist/cli/commands/dep.js.map +1 -0
- package/dist/cli/commands/document.d.ts +12 -0
- package/dist/cli/commands/document.d.ts.map +1 -0
- package/dist/cli/commands/document.js +1039 -0
- package/dist/cli/commands/document.js.map +1 -0
- package/dist/cli/commands/embeddings.d.ts +12 -0
- package/dist/cli/commands/embeddings.d.ts.map +1 -0
- package/dist/cli/commands/embeddings.js +273 -0
- package/dist/cli/commands/embeddings.js.map +1 -0
- package/dist/cli/commands/entity.d.ts +16 -0
- package/dist/cli/commands/entity.d.ts.map +1 -0
- package/dist/cli/commands/entity.js +522 -0
- package/dist/cli/commands/entity.js.map +1 -0
- package/dist/cli/commands/gc.d.ts +10 -0
- package/dist/cli/commands/gc.d.ts.map +1 -0
- package/dist/cli/commands/gc.js +257 -0
- package/dist/cli/commands/gc.js.map +1 -0
- package/dist/cli/commands/help.d.ts +11 -0
- package/dist/cli/commands/help.d.ts.map +1 -0
- package/dist/cli/commands/help.js +169 -0
- package/dist/cli/commands/help.js.map +1 -0
- package/dist/cli/commands/history.d.ts +9 -0
- package/dist/cli/commands/history.d.ts.map +1 -0
- package/dist/cli/commands/history.js +160 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/identity.d.ts +18 -0
- package/dist/cli/commands/identity.d.ts.map +1 -0
- package/dist/cli/commands/identity.js +698 -0
- package/dist/cli/commands/identity.js.map +1 -0
- package/dist/cli/commands/inbox.d.ts +20 -0
- package/dist/cli/commands/inbox.d.ts.map +1 -0
- package/dist/cli/commands/inbox.js +493 -0
- package/dist/cli/commands/inbox.js.map +1 -0
- package/dist/cli/commands/init.d.ts +20 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +144 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/install.d.ts +9 -0
- package/dist/cli/commands/install.d.ts.map +1 -0
- package/dist/cli/commands/install.js +200 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/library.d.ts +12 -0
- package/dist/cli/commands/library.d.ts.map +1 -0
- package/dist/cli/commands/library.js +665 -0
- package/dist/cli/commands/library.js.map +1 -0
- package/dist/cli/commands/message.d.ts +11 -0
- package/dist/cli/commands/message.d.ts.map +1 -0
- package/dist/cli/commands/message.js +608 -0
- package/dist/cli/commands/message.js.map +1 -0
- package/dist/cli/commands/plan.d.ts +17 -0
- package/dist/cli/commands/plan.d.ts.map +1 -0
- package/dist/cli/commands/plan.js +698 -0
- package/dist/cli/commands/plan.js.map +1 -0
- package/dist/cli/commands/playbook.d.ts +12 -0
- package/dist/cli/commands/playbook.d.ts.map +1 -0
- package/dist/cli/commands/playbook.js +730 -0
- package/dist/cli/commands/playbook.js.map +1 -0
- package/dist/cli/commands/reset.d.ts +12 -0
- package/dist/cli/commands/reset.d.ts.map +1 -0
- package/dist/cli/commands/reset.js +306 -0
- package/dist/cli/commands/reset.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +11 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +106 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +8 -0
- package/dist/cli/commands/stats.d.ts.map +1 -0
- package/dist/cli/commands/stats.js +82 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +14 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +370 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/commands/task.d.ts +25 -0
- package/dist/cli/commands/task.d.ts.map +1 -0
- package/dist/cli/commands/task.js +1153 -0
- package/dist/cli/commands/task.js.map +1 -0
- package/dist/cli/commands/team.d.ts +13 -0
- package/dist/cli/commands/team.d.ts.map +1 -0
- package/dist/cli/commands/team.js +471 -0
- package/dist/cli/commands/team.js.map +1 -0
- package/dist/cli/commands/workflow.d.ts +16 -0
- package/dist/cli/commands/workflow.d.ts.map +1 -0
- package/dist/cli/commands/workflow.js +753 -0
- package/dist/cli/commands/workflow.js.map +1 -0
- package/dist/cli/completion.d.ts +28 -0
- package/dist/cli/completion.d.ts.map +1 -0
- package/dist/cli/completion.js +295 -0
- package/dist/cli/completion.js.map +1 -0
- package/dist/cli/db.d.ts +38 -0
- package/dist/cli/db.d.ts.map +1 -0
- package/dist/cli/db.js +90 -0
- package/dist/cli/db.js.map +1 -0
- package/dist/cli/formatter.d.ts +87 -0
- package/dist/cli/formatter.d.ts.map +1 -0
- package/dist/cli/formatter.js +464 -0
- package/dist/cli/formatter.js.map +1 -0
- package/dist/cli/index.d.ts +33 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +38 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/parser.d.ts +45 -0
- package/dist/cli/parser.d.ts.map +1 -0
- package/dist/cli/parser.js +256 -0
- package/dist/cli/parser.js.map +1 -0
- package/dist/cli/plugin-loader.d.ts +39 -0
- package/dist/cli/plugin-loader.d.ts.map +1 -0
- package/dist/cli/plugin-loader.js +165 -0
- package/dist/cli/plugin-loader.js.map +1 -0
- package/dist/cli/plugin-registry.d.ts +50 -0
- package/dist/cli/plugin-registry.d.ts.map +1 -0
- package/dist/cli/plugin-registry.js +206 -0
- package/dist/cli/plugin-registry.js.map +1 -0
- package/dist/cli/plugin-types.d.ts +106 -0
- package/dist/cli/plugin-types.d.ts.map +1 -0
- package/dist/cli/plugin-types.js +103 -0
- package/dist/cli/plugin-types.js.map +1 -0
- package/dist/cli/runner.d.ts +35 -0
- package/dist/cli/runner.d.ts.map +1 -0
- package/dist/cli/runner.js +340 -0
- package/dist/cli/runner.js.map +1 -0
- package/dist/cli/suggest.d.ts +15 -0
- package/dist/cli/suggest.d.ts.map +1 -0
- package/dist/cli/suggest.js +49 -0
- package/dist/cli/suggest.js.map +1 -0
- package/dist/cli/types.d.ts +138 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +63 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/config/config.d.ts +86 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +348 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/defaults.d.ts +66 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +114 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/duration.d.ts +75 -0
- package/dist/config/duration.d.ts.map +1 -0
- package/dist/config/duration.js +190 -0
- package/dist/config/duration.js.map +1 -0
- package/dist/config/env.d.ts +67 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +207 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/file.d.ts +97 -0
- package/dist/config/file.d.ts.map +1 -0
- package/dist/config/file.js +365 -0
- package/dist/config/file.js.map +1 -0
- package/dist/config/index.d.ts +35 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +41 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/merge.d.ts +53 -0
- package/dist/config/merge.d.ts.map +1 -0
- package/dist/config/merge.js +226 -0
- package/dist/config/merge.js.map +1 -0
- package/dist/config/types.d.ts +257 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +72 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/validation.d.ts +55 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +251 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/http/index.d.ts +8 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +12 -0
- package/dist/http/index.js.map +1 -0
- package/dist/http/sync-handlers.d.ts +162 -0
- package/dist/http/sync-handlers.d.ts.map +1 -0
- package/dist/http/sync-handlers.js +271 -0
- package/dist/http/sync-handlers.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.d.ts +34 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3329 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/static.d.ts +18 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +71 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/ws/broadcaster.d.ts +8 -0
- package/dist/server/ws/broadcaster.d.ts.map +1 -0
- package/dist/server/ws/broadcaster.js +7 -0
- package/dist/server/ws/broadcaster.js.map +1 -0
- package/dist/server/ws/handler.d.ts +55 -0
- package/dist/server/ws/handler.d.ts.map +1 -0
- package/dist/server/ws/handler.js +160 -0
- package/dist/server/ws/handler.js.map +1 -0
- package/dist/services/blocked-cache.d.ts +297 -0
- package/dist/services/blocked-cache.d.ts.map +1 -0
- package/dist/services/blocked-cache.js +755 -0
- package/dist/services/blocked-cache.js.map +1 -0
- package/dist/services/dependency.d.ts +205 -0
- package/dist/services/dependency.d.ts.map +1 -0
- package/dist/services/dependency.js +566 -0
- package/dist/services/dependency.js.map +1 -0
- package/dist/services/embeddings/fusion.d.ts +33 -0
- package/dist/services/embeddings/fusion.d.ts.map +1 -0
- package/dist/services/embeddings/fusion.js +34 -0
- package/dist/services/embeddings/fusion.js.map +1 -0
- package/dist/services/embeddings/index.d.ts +12 -0
- package/dist/services/embeddings/index.d.ts.map +1 -0
- package/dist/services/embeddings/index.js +10 -0
- package/dist/services/embeddings/index.js.map +1 -0
- package/dist/services/embeddings/local-provider.d.ts +31 -0
- package/dist/services/embeddings/local-provider.d.ts.map +1 -0
- package/dist/services/embeddings/local-provider.js +80 -0
- package/dist/services/embeddings/local-provider.js.map +1 -0
- package/dist/services/embeddings/service.d.ts +76 -0
- package/dist/services/embeddings/service.d.ts.map +1 -0
- package/dist/services/embeddings/service.js +153 -0
- package/dist/services/embeddings/service.js.map +1 -0
- package/dist/services/embeddings/types.d.ts +70 -0
- package/dist/services/embeddings/types.d.ts.map +1 -0
- package/dist/services/embeddings/types.js +8 -0
- package/dist/services/embeddings/types.js.map +1 -0
- package/dist/services/id-length-cache.d.ts +156 -0
- package/dist/services/id-length-cache.d.ts.map +1 -0
- package/dist/services/id-length-cache.js +197 -0
- package/dist/services/id-length-cache.js.map +1 -0
- package/dist/services/inbox.d.ts +147 -0
- package/dist/services/inbox.d.ts.map +1 -0
- package/dist/services/inbox.js +428 -0
- package/dist/services/inbox.js.map +1 -0
- package/dist/services/index.d.ts +10 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +10 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/priority-service.d.ts +145 -0
- package/dist/services/priority-service.d.ts.map +1 -0
- package/dist/services/priority-service.js +272 -0
- package/dist/services/priority-service.js.map +1 -0
- package/dist/services/search-utils.d.ts +47 -0
- package/dist/services/search-utils.d.ts.map +1 -0
- package/dist/services/search-utils.js +83 -0
- package/dist/services/search-utils.js.map +1 -0
- package/dist/sync/hash.d.ts +48 -0
- package/dist/sync/hash.d.ts.map +1 -0
- package/dist/sync/hash.js +136 -0
- package/dist/sync/hash.js.map +1 -0
- package/dist/sync/index.d.ts +11 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +16 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/merge.d.ts +80 -0
- package/dist/sync/merge.d.ts.map +1 -0
- package/dist/sync/merge.js +310 -0
- package/dist/sync/merge.js.map +1 -0
- package/dist/sync/serialization.d.ts +132 -0
- package/dist/sync/serialization.d.ts.map +1 -0
- package/dist/sync/serialization.js +306 -0
- package/dist/sync/serialization.js.map +1 -0
- package/dist/sync/service.d.ts +102 -0
- package/dist/sync/service.d.ts.map +1 -0
- package/dist/sync/service.js +493 -0
- package/dist/sync/service.js.map +1 -0
- package/dist/sync/types.d.ts +275 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +76 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/systems/identity.d.ts +479 -0
- package/dist/systems/identity.d.ts.map +1 -0
- package/dist/systems/identity.js +817 -0
- package/dist/systems/identity.js.map +1 -0
- package/dist/systems/index.d.ts +8 -0
- package/dist/systems/index.d.ts.map +1 -0
- package/dist/systems/index.js +29 -0
- package/dist/systems/index.js.map +1 -0
- package/package.json +121 -0
- package/web/assets/charts-vendor-D1YcbGux.js +55 -0
- package/web/assets/dnd-vendor-DmxE-_ZH.js +5 -0
- package/web/assets/editor-vendor-BxraAWts.js +279 -0
- package/web/assets/index-B77vv208.js +341 -0
- package/web/assets/index-CF_XnVLh.css +1 -0
- package/web/assets/router-vendor-BCKpRBrB.js +41 -0
- package/web/assets/ui-vendor-DUahGnbT.js +45 -0
- package/web/assets/utils-vendor-CfYKiENT.js +813 -0
- package/web/favicon.ico +0 -0
- package/web/index.html +23 -0
- package/web/logo.png +0 -0
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playbook Commands - Collection command interface for playbooks
|
|
3
|
+
*
|
|
4
|
+
* Provides CLI commands for playbook operations:
|
|
5
|
+
* - playbook list: List playbooks with filtering
|
|
6
|
+
* - playbook show: Show playbook details
|
|
7
|
+
* - playbook validate: Validate playbook structure
|
|
8
|
+
* - playbook create: Create a new playbook
|
|
9
|
+
*/
|
|
10
|
+
import { success, failure, ExitCode } from '../types.js';
|
|
11
|
+
import { getFormatter, getOutputMode } from '../formatter.js';
|
|
12
|
+
import { createPlaybook, validatePlaybook, validateSteps, validateVariables, validateNoCircularInheritance, resolveInheritanceChain, VariableType, } from '@stoneforge/core';
|
|
13
|
+
import { validateCreateWorkflow } from '@stoneforge/core';
|
|
14
|
+
import { suggestCommands } from '../suggest.js';
|
|
15
|
+
import { resolveActor, createAPI } from '../db.js';
|
|
16
|
+
const playbookListOptions = [
|
|
17
|
+
{
|
|
18
|
+
name: 'limit',
|
|
19
|
+
short: 'l',
|
|
20
|
+
description: 'Maximum number of results',
|
|
21
|
+
hasValue: true,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
async function playbookListHandler(_args, options) {
|
|
25
|
+
const { api, error } = createAPI(options);
|
|
26
|
+
if (error) {
|
|
27
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
// Build filter
|
|
31
|
+
const filter = {
|
|
32
|
+
type: 'playbook',
|
|
33
|
+
};
|
|
34
|
+
// Limit
|
|
35
|
+
if (options.limit) {
|
|
36
|
+
const limit = parseInt(options.limit, 10);
|
|
37
|
+
if (isNaN(limit) || limit < 1) {
|
|
38
|
+
return failure('Limit must be a positive number', ExitCode.VALIDATION);
|
|
39
|
+
}
|
|
40
|
+
filter.limit = limit;
|
|
41
|
+
}
|
|
42
|
+
const result = await api.listPaginated(filter);
|
|
43
|
+
const items = result.items;
|
|
44
|
+
const mode = getOutputMode(options);
|
|
45
|
+
const formatter = getFormatter(mode);
|
|
46
|
+
if (mode === 'json') {
|
|
47
|
+
return success(items);
|
|
48
|
+
}
|
|
49
|
+
if (mode === 'quiet') {
|
|
50
|
+
return success(items.map((p) => p.id).join('\n'));
|
|
51
|
+
}
|
|
52
|
+
if (items.length === 0) {
|
|
53
|
+
return success(null, 'No playbooks found');
|
|
54
|
+
}
|
|
55
|
+
// Build table
|
|
56
|
+
const headers = ['ID', 'NAME', 'TITLE', 'VERSION', 'STEPS', 'CREATED'];
|
|
57
|
+
const rows = items.map((p) => [
|
|
58
|
+
p.id,
|
|
59
|
+
p.name,
|
|
60
|
+
p.title.length > 30 ? p.title.substring(0, 27) + '...' : p.title,
|
|
61
|
+
`v${p.version}`,
|
|
62
|
+
String(p.steps.length),
|
|
63
|
+
p.createdAt.split('T')[0],
|
|
64
|
+
]);
|
|
65
|
+
const table = formatter.table(headers, rows);
|
|
66
|
+
const summary = `\nShowing ${items.length} of ${result.total} playbooks`;
|
|
67
|
+
return success(items, table + summary);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
71
|
+
return failure(`Failed to list playbooks: ${message}`, ExitCode.GENERAL_ERROR);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const playbookListCommand = {
|
|
75
|
+
name: 'list',
|
|
76
|
+
description: 'List playbooks',
|
|
77
|
+
usage: 'sf playbook list [options]',
|
|
78
|
+
help: `List playbooks with optional filtering.
|
|
79
|
+
|
|
80
|
+
Options:
|
|
81
|
+
-l, --limit <n> Maximum results
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
sf playbook list
|
|
85
|
+
sf playbook list --limit 10`,
|
|
86
|
+
options: playbookListOptions,
|
|
87
|
+
handler: playbookListHandler,
|
|
88
|
+
};
|
|
89
|
+
const playbookShowOptions = [
|
|
90
|
+
{
|
|
91
|
+
name: 'steps',
|
|
92
|
+
short: 's',
|
|
93
|
+
description: 'Include step definitions',
|
|
94
|
+
hasValue: false,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'variables',
|
|
98
|
+
short: 'v',
|
|
99
|
+
description: 'Include variable definitions',
|
|
100
|
+
hasValue: false,
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
async function playbookShowHandler(args, options) {
|
|
104
|
+
const [nameOrId] = args;
|
|
105
|
+
if (!nameOrId) {
|
|
106
|
+
return failure('Usage: sf playbook show <name|id>', ExitCode.INVALID_ARGUMENTS);
|
|
107
|
+
}
|
|
108
|
+
const { api, error } = createAPI(options);
|
|
109
|
+
if (error) {
|
|
110
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
let playbook = null;
|
|
114
|
+
// First try as ID
|
|
115
|
+
if (nameOrId.startsWith('el-')) {
|
|
116
|
+
playbook = await api.get(nameOrId);
|
|
117
|
+
}
|
|
118
|
+
// If not found by ID, search by name
|
|
119
|
+
if (!playbook) {
|
|
120
|
+
const allPlaybooks = await api.list({ type: 'playbook' });
|
|
121
|
+
playbook = allPlaybooks.find((p) => p.name === nameOrId) || null;
|
|
122
|
+
}
|
|
123
|
+
if (!playbook) {
|
|
124
|
+
return failure(`Playbook not found: ${nameOrId}`, ExitCode.NOT_FOUND);
|
|
125
|
+
}
|
|
126
|
+
if (playbook.type !== 'playbook') {
|
|
127
|
+
return failure(`Element ${nameOrId} is not a playbook (type: ${playbook.type})`, ExitCode.VALIDATION);
|
|
128
|
+
}
|
|
129
|
+
const mode = getOutputMode(options);
|
|
130
|
+
const formatter = getFormatter(mode);
|
|
131
|
+
if (mode === 'json') {
|
|
132
|
+
return success(playbook);
|
|
133
|
+
}
|
|
134
|
+
if (mode === 'quiet') {
|
|
135
|
+
return success(playbook.id);
|
|
136
|
+
}
|
|
137
|
+
// Human-readable output
|
|
138
|
+
let output = formatter.element(playbook);
|
|
139
|
+
// Add playbook-specific info
|
|
140
|
+
output += '\n\n--- Playbook Info ---\n';
|
|
141
|
+
output += `Name: ${playbook.name}\n`;
|
|
142
|
+
output += `Version: ${playbook.version}\n`;
|
|
143
|
+
output += `Steps: ${playbook.steps.length}\n`;
|
|
144
|
+
output += `Variables: ${playbook.variables.length}\n`;
|
|
145
|
+
if (playbook.extends && playbook.extends.length > 0) {
|
|
146
|
+
output += `Extends: ${playbook.extends.join(', ')}\n`;
|
|
147
|
+
}
|
|
148
|
+
// Show steps if requested
|
|
149
|
+
if (options.steps && playbook.steps.length > 0) {
|
|
150
|
+
output += '\n--- Steps ---\n';
|
|
151
|
+
const stepHeaders = ['ID', 'TITLE', 'DEPENDS ON'];
|
|
152
|
+
const stepRows = playbook.steps.map((s) => [
|
|
153
|
+
s.id,
|
|
154
|
+
s.title.length > 40 ? s.title.substring(0, 37) + '...' : s.title,
|
|
155
|
+
s.dependsOn?.join(', ') || '-',
|
|
156
|
+
]);
|
|
157
|
+
output += formatter.table(stepHeaders, stepRows);
|
|
158
|
+
}
|
|
159
|
+
// Show variables if requested
|
|
160
|
+
if (options.variables && playbook.variables.length > 0) {
|
|
161
|
+
output += '\n--- Variables ---\n';
|
|
162
|
+
const varHeaders = ['NAME', 'TYPE', 'REQUIRED', 'DEFAULT'];
|
|
163
|
+
const varRows = playbook.variables.map((v) => [
|
|
164
|
+
v.name,
|
|
165
|
+
v.type,
|
|
166
|
+
v.required ? 'yes' : 'no',
|
|
167
|
+
v.default !== undefined ? String(v.default) : '-',
|
|
168
|
+
]);
|
|
169
|
+
output += formatter.table(varHeaders, varRows);
|
|
170
|
+
}
|
|
171
|
+
return success(playbook, output);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
175
|
+
return failure(`Failed to show playbook: ${message}`, ExitCode.GENERAL_ERROR);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const playbookShowCommand = {
|
|
179
|
+
name: 'show',
|
|
180
|
+
description: 'Show playbook details',
|
|
181
|
+
usage: 'sf playbook show <name|id> [options]',
|
|
182
|
+
help: `Display detailed information about a playbook.
|
|
183
|
+
|
|
184
|
+
Arguments:
|
|
185
|
+
name|id Playbook name or identifier
|
|
186
|
+
|
|
187
|
+
Options:
|
|
188
|
+
-s, --steps Include step definitions
|
|
189
|
+
-v, --variables Include variable definitions
|
|
190
|
+
|
|
191
|
+
Examples:
|
|
192
|
+
sf playbook show deploy
|
|
193
|
+
sf playbook show el-abc123 --steps --variables
|
|
194
|
+
sf playbook show deploy --json`,
|
|
195
|
+
options: playbookShowOptions,
|
|
196
|
+
handler: playbookShowHandler,
|
|
197
|
+
};
|
|
198
|
+
const playbookValidateOptions = [
|
|
199
|
+
{
|
|
200
|
+
name: 'var',
|
|
201
|
+
description: 'Set variable for create-time validation (name=value, can be repeated)',
|
|
202
|
+
hasValue: true,
|
|
203
|
+
array: true,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'create',
|
|
207
|
+
short: 'c',
|
|
208
|
+
description: 'Perform create-time validation (validates variables can be resolved)',
|
|
209
|
+
hasValue: false,
|
|
210
|
+
},
|
|
211
|
+
];
|
|
212
|
+
/**
|
|
213
|
+
* Parses variable arguments in name=value format
|
|
214
|
+
*/
|
|
215
|
+
function parseVariableArgs(varArgs) {
|
|
216
|
+
const variables = {};
|
|
217
|
+
if (!varArgs)
|
|
218
|
+
return variables;
|
|
219
|
+
const args = Array.isArray(varArgs) ? varArgs : [varArgs];
|
|
220
|
+
for (const varArg of args) {
|
|
221
|
+
const eqIndex = varArg.indexOf('=');
|
|
222
|
+
if (eqIndex === -1) {
|
|
223
|
+
throw new Error(`Invalid variable format: ${varArg}. Use name=value`);
|
|
224
|
+
}
|
|
225
|
+
const name = varArg.slice(0, eqIndex);
|
|
226
|
+
const value = varArg.slice(eqIndex + 1);
|
|
227
|
+
// Try to parse as JSON for boolean/number types, otherwise use as string
|
|
228
|
+
try {
|
|
229
|
+
if (value === 'true') {
|
|
230
|
+
variables[name] = true;
|
|
231
|
+
}
|
|
232
|
+
else if (value === 'false') {
|
|
233
|
+
variables[name] = false;
|
|
234
|
+
}
|
|
235
|
+
else if (!isNaN(Number(value)) && value.trim() !== '') {
|
|
236
|
+
variables[name] = Number(value);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
variables[name] = value;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
variables[name] = value;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return variables;
|
|
247
|
+
}
|
|
248
|
+
async function playbookValidateHandler(args, options) {
|
|
249
|
+
const [nameOrId] = args;
|
|
250
|
+
if (!nameOrId) {
|
|
251
|
+
return failure('Usage: sf playbook validate <name|id> [--var name=value] [--create]', ExitCode.INVALID_ARGUMENTS);
|
|
252
|
+
}
|
|
253
|
+
const { api, error } = createAPI(options);
|
|
254
|
+
if (error) {
|
|
255
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
let playbook = null;
|
|
259
|
+
// First try as ID
|
|
260
|
+
if (nameOrId.startsWith('el-')) {
|
|
261
|
+
playbook = await api.get(nameOrId);
|
|
262
|
+
}
|
|
263
|
+
// If not found by ID, search by name
|
|
264
|
+
if (!playbook) {
|
|
265
|
+
const allPlaybooks = await api.list({ type: 'playbook' });
|
|
266
|
+
playbook = allPlaybooks.find((p) => p.name === nameOrId) || null;
|
|
267
|
+
}
|
|
268
|
+
if (!playbook) {
|
|
269
|
+
return failure(`Playbook not found: ${nameOrId}`, ExitCode.NOT_FOUND);
|
|
270
|
+
}
|
|
271
|
+
if (playbook.type !== 'playbook') {
|
|
272
|
+
return failure(`Element ${nameOrId} is not a playbook (type: ${playbook.type})`, ExitCode.VALIDATION);
|
|
273
|
+
}
|
|
274
|
+
const issues = [];
|
|
275
|
+
// Validate playbook structure
|
|
276
|
+
try {
|
|
277
|
+
validatePlaybook(playbook);
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
281
|
+
issues.push(`Structure: ${message}`);
|
|
282
|
+
}
|
|
283
|
+
// Validate steps
|
|
284
|
+
try {
|
|
285
|
+
validateSteps(playbook.steps);
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
289
|
+
issues.push(`Steps: ${message}`);
|
|
290
|
+
}
|
|
291
|
+
// Validate variables
|
|
292
|
+
try {
|
|
293
|
+
validateVariables(playbook.variables);
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
297
|
+
issues.push(`Variables: ${message}`);
|
|
298
|
+
}
|
|
299
|
+
// Check for circular dependencies in steps
|
|
300
|
+
const stepIds = new Set(playbook.steps.map((s) => s.id));
|
|
301
|
+
for (const step of playbook.steps) {
|
|
302
|
+
if (step.dependsOn) {
|
|
303
|
+
for (const depId of step.dependsOn) {
|
|
304
|
+
if (!stepIds.has(depId)) {
|
|
305
|
+
issues.push(`Step '${step.id}' depends on unknown step '${depId}'`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// Check for unused variables in templates
|
|
311
|
+
const definedVars = new Set(playbook.variables.map((v) => v.name));
|
|
312
|
+
const varPattern = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
|
|
313
|
+
for (const step of playbook.steps) {
|
|
314
|
+
let match;
|
|
315
|
+
const pattern = new RegExp(varPattern.source, 'g');
|
|
316
|
+
while ((match = pattern.exec(step.title)) !== null) {
|
|
317
|
+
if (!definedVars.has(match[1])) {
|
|
318
|
+
issues.push(`Step '${step.id}' uses undefined variable '${match[1]}' in title`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (step.description) {
|
|
322
|
+
const descPattern = new RegExp(varPattern.source, 'g');
|
|
323
|
+
while ((match = descPattern.exec(step.description)) !== null) {
|
|
324
|
+
if (!definedVars.has(match[1])) {
|
|
325
|
+
issues.push(`Step '${step.id}' uses undefined variable '${match[1]}' in description`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (step.condition) {
|
|
330
|
+
const condPattern = new RegExp(varPattern.source, 'g');
|
|
331
|
+
while ((match = condPattern.exec(step.condition)) !== null) {
|
|
332
|
+
if (!definedVars.has(match[1])) {
|
|
333
|
+
issues.push(`Step '${step.id}' uses undefined variable '${match[1]}' in condition`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// Check for circular inheritance (always, not just create-time)
|
|
339
|
+
if (playbook.extends && playbook.extends.length > 0) {
|
|
340
|
+
const allPlaybooks = await api.list({ type: 'playbook' });
|
|
341
|
+
const playbookLoader = (name) => {
|
|
342
|
+
return allPlaybooks.find((p) => p.name.toLowerCase() === name.toLowerCase());
|
|
343
|
+
};
|
|
344
|
+
try {
|
|
345
|
+
await resolveInheritanceChain(playbook, playbookLoader);
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
349
|
+
issues.push(`Inheritance: ${message}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Create-time validation - run if --create flag is set or if --var is provided
|
|
353
|
+
const shouldDoCreateValidation = options.create || options.var;
|
|
354
|
+
let createValidationResult;
|
|
355
|
+
if (shouldDoCreateValidation) {
|
|
356
|
+
// Parse provided variables
|
|
357
|
+
let providedVars = {};
|
|
358
|
+
try {
|
|
359
|
+
providedVars = parseVariableArgs(options.var);
|
|
360
|
+
}
|
|
361
|
+
catch (err) {
|
|
362
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
363
|
+
return failure(message, ExitCode.VALIDATION);
|
|
364
|
+
}
|
|
365
|
+
// Create a playbook loader that fetches from the database
|
|
366
|
+
const allPlaybooks = await api.list({ type: 'playbook' });
|
|
367
|
+
const playbookLoader = (name) => {
|
|
368
|
+
return allPlaybooks.find((p) => p.name.toLowerCase() === name.toLowerCase());
|
|
369
|
+
};
|
|
370
|
+
// Validate create-time variables
|
|
371
|
+
createValidationResult = await validateCreateWorkflow(playbook, providedVars, playbookLoader);
|
|
372
|
+
if (!createValidationResult.valid) {
|
|
373
|
+
issues.push(`Create-time: ${createValidationResult.error}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const mode = getOutputMode(options);
|
|
377
|
+
// Build JSON result
|
|
378
|
+
const jsonResult = {
|
|
379
|
+
valid: issues.length === 0,
|
|
380
|
+
issues,
|
|
381
|
+
playbook: { id: playbook.id, name: playbook.name },
|
|
382
|
+
};
|
|
383
|
+
// Add create-time validation details if performed
|
|
384
|
+
if (shouldDoCreateValidation && createValidationResult) {
|
|
385
|
+
jsonResult.createValidation = {
|
|
386
|
+
performed: true,
|
|
387
|
+
valid: createValidationResult.valid,
|
|
388
|
+
...(createValidationResult.valid && {
|
|
389
|
+
resolvedVariables: createValidationResult.resolvedVariables,
|
|
390
|
+
includedSteps: createValidationResult.includedSteps?.map((s) => s.id),
|
|
391
|
+
skippedSteps: createValidationResult.skippedSteps,
|
|
392
|
+
}),
|
|
393
|
+
...(createValidationResult.error && { error: createValidationResult.error }),
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
if (mode === 'json') {
|
|
397
|
+
return success(jsonResult);
|
|
398
|
+
}
|
|
399
|
+
if (mode === 'quiet') {
|
|
400
|
+
return success(issues.length === 0 ? 'valid' : 'invalid');
|
|
401
|
+
}
|
|
402
|
+
// Human-readable output
|
|
403
|
+
let output = '';
|
|
404
|
+
if (issues.length === 0) {
|
|
405
|
+
output = `Playbook '${playbook.name}' is valid`;
|
|
406
|
+
// Add create-time details if validation was performed
|
|
407
|
+
if (shouldDoCreateValidation && createValidationResult?.valid) {
|
|
408
|
+
output += '\n\n--- Create-time Validation ---';
|
|
409
|
+
output += '\nVariables resolved successfully';
|
|
410
|
+
if (createValidationResult.includedSteps && createValidationResult.includedSteps.length > 0) {
|
|
411
|
+
output += `\nIncluded steps: ${createValidationResult.includedSteps.map((s) => s.id).join(', ')}`;
|
|
412
|
+
}
|
|
413
|
+
if (createValidationResult.skippedSteps && createValidationResult.skippedSteps.length > 0) {
|
|
414
|
+
output += `\nSkipped steps: ${createValidationResult.skippedSteps.join(', ')}`;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
const issueList = issues.map((i, idx) => ` ${idx + 1}. ${i}`).join('\n');
|
|
420
|
+
output = `Playbook '${playbook.name}' has ${issues.length} issue(s):\n${issueList}`;
|
|
421
|
+
}
|
|
422
|
+
return success(jsonResult, output);
|
|
423
|
+
}
|
|
424
|
+
catch (err) {
|
|
425
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
426
|
+
return failure(`Failed to validate playbook: ${message}`, ExitCode.GENERAL_ERROR);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const playbookValidateCommand = {
|
|
430
|
+
name: 'validate',
|
|
431
|
+
description: 'Validate playbook structure and create-time variables',
|
|
432
|
+
usage: 'sf playbook validate <name|id> [--var name=value] [--create]',
|
|
433
|
+
help: `Validate a playbook's structure and optionally test create-time variable resolution.
|
|
434
|
+
|
|
435
|
+
Structure Checks:
|
|
436
|
+
- Required fields are present
|
|
437
|
+
- Step IDs are unique
|
|
438
|
+
- Step dependencies reference existing steps
|
|
439
|
+
- Variables used in templates are defined
|
|
440
|
+
- No circular dependencies
|
|
441
|
+
|
|
442
|
+
Create-time Checks (with --create or --var):
|
|
443
|
+
- All required variables are provided
|
|
444
|
+
- Variable values match their declared types
|
|
445
|
+
- Enum values are within allowed list
|
|
446
|
+
- Variable substitution completes without errors
|
|
447
|
+
- Condition evaluation succeeds
|
|
448
|
+
|
|
449
|
+
Arguments:
|
|
450
|
+
name|id Playbook name or identifier
|
|
451
|
+
|
|
452
|
+
Options:
|
|
453
|
+
--var <name=value> Set variable for create-time validation (can be repeated)
|
|
454
|
+
-c, --create Perform create-time validation
|
|
455
|
+
|
|
456
|
+
Examples:
|
|
457
|
+
sf playbook validate deploy
|
|
458
|
+
sf playbook validate deploy --create
|
|
459
|
+
sf playbook validate deploy --var env=production --var debug=true
|
|
460
|
+
sf playbook validate el-abc123 --var version=1.0.0`,
|
|
461
|
+
options: playbookValidateOptions,
|
|
462
|
+
handler: playbookValidateHandler,
|
|
463
|
+
};
|
|
464
|
+
const playbookCreateOptions = [
|
|
465
|
+
{
|
|
466
|
+
name: 'name',
|
|
467
|
+
short: 'n',
|
|
468
|
+
description: 'Playbook name (unique identifier, required)',
|
|
469
|
+
hasValue: true,
|
|
470
|
+
required: true,
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: 'title',
|
|
474
|
+
short: 't',
|
|
475
|
+
description: 'Playbook title (display name, required)',
|
|
476
|
+
hasValue: true,
|
|
477
|
+
required: true,
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: 'step',
|
|
481
|
+
short: 's',
|
|
482
|
+
description: 'Add step (format: id:title[:dependsOn,...], can be repeated)',
|
|
483
|
+
hasValue: true,
|
|
484
|
+
array: true,
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: 'variable',
|
|
488
|
+
short: 'v',
|
|
489
|
+
description: 'Add variable (format: name:type[:default][:required], can be repeated)',
|
|
490
|
+
hasValue: true,
|
|
491
|
+
array: true,
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
name: 'extends',
|
|
495
|
+
short: 'e',
|
|
496
|
+
description: 'Extend playbook (can be repeated)',
|
|
497
|
+
hasValue: true,
|
|
498
|
+
array: true,
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: 'tag',
|
|
502
|
+
description: 'Add tag (can be repeated)',
|
|
503
|
+
hasValue: true,
|
|
504
|
+
array: true,
|
|
505
|
+
},
|
|
506
|
+
];
|
|
507
|
+
function parseStep(stepArg) {
|
|
508
|
+
const parts = stepArg.split(':');
|
|
509
|
+
if (parts.length < 2) {
|
|
510
|
+
throw new Error(`Invalid step format: ${stepArg}. Expected id:title[:dependsOn,...]`);
|
|
511
|
+
}
|
|
512
|
+
const id = parts[0].trim();
|
|
513
|
+
const title = parts[1].trim();
|
|
514
|
+
const dependsOn = parts.length > 2 ? parts[2].split(',').map((d) => d.trim()).filter(Boolean) : undefined;
|
|
515
|
+
return {
|
|
516
|
+
id,
|
|
517
|
+
title,
|
|
518
|
+
...(dependsOn && dependsOn.length > 0 && { dependsOn }),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function parseVariable(varArg) {
|
|
522
|
+
const parts = varArg.split(':');
|
|
523
|
+
if (parts.length < 2) {
|
|
524
|
+
throw new Error(`Invalid variable format: ${varArg}. Expected name:type[:default][:required]`);
|
|
525
|
+
}
|
|
526
|
+
const name = parts[0].trim();
|
|
527
|
+
const type = parts[1].trim();
|
|
528
|
+
if (!Object.values(VariableType).includes(type)) {
|
|
529
|
+
throw new Error(`Invalid variable type: ${type}. Must be one of: ${Object.values(VariableType).join(', ')}`);
|
|
530
|
+
}
|
|
531
|
+
let defaultValue;
|
|
532
|
+
let required = true;
|
|
533
|
+
if (parts.length > 2) {
|
|
534
|
+
const defaultStr = parts[2].trim();
|
|
535
|
+
if (defaultStr) {
|
|
536
|
+
switch (type) {
|
|
537
|
+
case VariableType.STRING:
|
|
538
|
+
defaultValue = defaultStr;
|
|
539
|
+
break;
|
|
540
|
+
case VariableType.NUMBER:
|
|
541
|
+
defaultValue = parseFloat(defaultStr);
|
|
542
|
+
if (isNaN(defaultValue)) {
|
|
543
|
+
throw new Error(`Invalid number default for variable '${name}': ${defaultStr}`);
|
|
544
|
+
}
|
|
545
|
+
break;
|
|
546
|
+
case VariableType.BOOLEAN:
|
|
547
|
+
if (defaultStr !== 'true' && defaultStr !== 'false') {
|
|
548
|
+
throw new Error(`Invalid boolean default for variable '${name}': ${defaultStr}. Must be 'true' or 'false'`);
|
|
549
|
+
}
|
|
550
|
+
defaultValue = defaultStr === 'true';
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (parts.length > 3) {
|
|
556
|
+
required = parts[3].trim().toLowerCase() !== 'false';
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
name,
|
|
560
|
+
type,
|
|
561
|
+
required,
|
|
562
|
+
...(defaultValue !== undefined && { default: defaultValue }),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
async function playbookCreateHandler(_args, options) {
|
|
566
|
+
if (!options.name) {
|
|
567
|
+
return failure('--name is required for creating a playbook', ExitCode.INVALID_ARGUMENTS);
|
|
568
|
+
}
|
|
569
|
+
if (!options.title) {
|
|
570
|
+
return failure('--title is required for creating a playbook', ExitCode.INVALID_ARGUMENTS);
|
|
571
|
+
}
|
|
572
|
+
const { api, error } = createAPI(options, true);
|
|
573
|
+
if (error) {
|
|
574
|
+
return failure(error, ExitCode.GENERAL_ERROR);
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
const actor = resolveActor(options);
|
|
578
|
+
// Parse steps
|
|
579
|
+
const steps = [];
|
|
580
|
+
if (options.step) {
|
|
581
|
+
const stepArgs = Array.isArray(options.step) ? options.step : [options.step];
|
|
582
|
+
for (const stepArg of stepArgs) {
|
|
583
|
+
try {
|
|
584
|
+
steps.push(parseStep(stepArg));
|
|
585
|
+
}
|
|
586
|
+
catch (err) {
|
|
587
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
588
|
+
return failure(message, ExitCode.VALIDATION);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// Parse variables
|
|
593
|
+
const variables = [];
|
|
594
|
+
if (options.variable) {
|
|
595
|
+
const varArgs = Array.isArray(options.variable) ? options.variable : [options.variable];
|
|
596
|
+
for (const varArg of varArgs) {
|
|
597
|
+
try {
|
|
598
|
+
variables.push(parseVariable(varArg));
|
|
599
|
+
}
|
|
600
|
+
catch (err) {
|
|
601
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
602
|
+
return failure(message, ExitCode.VALIDATION);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
// Parse extends
|
|
607
|
+
let extendsPlaybooks;
|
|
608
|
+
if (options.extends) {
|
|
609
|
+
extendsPlaybooks = Array.isArray(options.extends) ? options.extends : [options.extends];
|
|
610
|
+
}
|
|
611
|
+
// Handle tags
|
|
612
|
+
let tags;
|
|
613
|
+
if (options.tag) {
|
|
614
|
+
tags = Array.isArray(options.tag) ? options.tag : [options.tag];
|
|
615
|
+
}
|
|
616
|
+
// Validate that creating this playbook won't create circular inheritance
|
|
617
|
+
if (extendsPlaybooks && extendsPlaybooks.length > 0) {
|
|
618
|
+
const allPlaybooks = await api.list({ type: 'playbook' });
|
|
619
|
+
const playbookLoader = (name) => {
|
|
620
|
+
return allPlaybooks.find((p) => p.name.toLowerCase() === name.toLowerCase());
|
|
621
|
+
};
|
|
622
|
+
const cycleCheck = await validateNoCircularInheritance(options.name, extendsPlaybooks, playbookLoader);
|
|
623
|
+
if (!cycleCheck.valid) {
|
|
624
|
+
return failure(cycleCheck.error, ExitCode.VALIDATION);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const input = {
|
|
628
|
+
name: options.name,
|
|
629
|
+
title: options.title,
|
|
630
|
+
createdBy: actor,
|
|
631
|
+
steps,
|
|
632
|
+
variables,
|
|
633
|
+
...(extendsPlaybooks && { extends: extendsPlaybooks }),
|
|
634
|
+
...(tags && { tags }),
|
|
635
|
+
};
|
|
636
|
+
const playbook = await createPlaybook(input);
|
|
637
|
+
const created = await api.create(playbook);
|
|
638
|
+
const mode = getOutputMode(options);
|
|
639
|
+
if (mode === 'quiet') {
|
|
640
|
+
return success(created.id);
|
|
641
|
+
}
|
|
642
|
+
return success(created, `Created playbook ${created.id} (${options.name})`);
|
|
643
|
+
}
|
|
644
|
+
catch (err) {
|
|
645
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
646
|
+
return failure(`Failed to create playbook: ${message}`, ExitCode.GENERAL_ERROR);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const playbookCreateCommand = {
|
|
650
|
+
name: 'create',
|
|
651
|
+
description: 'Create a new playbook',
|
|
652
|
+
usage: 'sf playbook create --name <name> --title <title> [options]',
|
|
653
|
+
help: `Create a new playbook template.
|
|
654
|
+
|
|
655
|
+
Options:
|
|
656
|
+
-n, --name <name> Playbook name (unique identifier, required)
|
|
657
|
+
-t, --title <title> Playbook title (display name, required)
|
|
658
|
+
-s, --step <spec> Add step (format: id:title[:dependsOn,...])
|
|
659
|
+
-v, --variable <spec> Add variable (format: name:type[:default][:required])
|
|
660
|
+
-e, --extends <name> Extend playbook (can be repeated)
|
|
661
|
+
--tag <tag> Add tag (can be repeated)
|
|
662
|
+
|
|
663
|
+
Step format:
|
|
664
|
+
id:title Basic step
|
|
665
|
+
id:title:dep1,dep2 Step with dependencies
|
|
666
|
+
|
|
667
|
+
Variable format:
|
|
668
|
+
name:type Required variable
|
|
669
|
+
name:type:default Variable with default
|
|
670
|
+
name:type:default:false Optional variable with default
|
|
671
|
+
|
|
672
|
+
Examples:
|
|
673
|
+
sf playbook create --name deploy --title "Deployment Process"
|
|
674
|
+
sf playbook create -n deploy -t "Deploy" -s "build:Build app" -s "test:Run tests:build"
|
|
675
|
+
sf playbook create -n deploy -t "Deploy" -v "env:string" -v "debug:boolean:false:false"`,
|
|
676
|
+
options: playbookCreateOptions,
|
|
677
|
+
handler: playbookCreateHandler,
|
|
678
|
+
};
|
|
679
|
+
// ============================================================================
|
|
680
|
+
// Playbook Root Command
|
|
681
|
+
// ============================================================================
|
|
682
|
+
export const playbookCommand = {
|
|
683
|
+
name: 'playbook',
|
|
684
|
+
description: 'Manage playbooks (workflow templates)',
|
|
685
|
+
usage: 'sf playbook <subcommand> [options]',
|
|
686
|
+
help: `Manage playbooks - templates for creating workflows.
|
|
687
|
+
|
|
688
|
+
Playbooks define reusable sequences of tasks with variables, conditions,
|
|
689
|
+
and dependencies. They can be instantiated as workflows using 'sf workflow create'.
|
|
690
|
+
|
|
691
|
+
Subcommands:
|
|
692
|
+
list List playbooks
|
|
693
|
+
show Show playbook details
|
|
694
|
+
validate Validate playbook structure
|
|
695
|
+
create Create a new playbook
|
|
696
|
+
|
|
697
|
+
Examples:
|
|
698
|
+
sf playbook list
|
|
699
|
+
sf playbook show deploy --steps --variables
|
|
700
|
+
sf playbook validate deploy
|
|
701
|
+
sf playbook create --name deploy --title "Deployment"`,
|
|
702
|
+
subcommands: {
|
|
703
|
+
list: playbookListCommand,
|
|
704
|
+
show: playbookShowCommand,
|
|
705
|
+
validate: playbookValidateCommand,
|
|
706
|
+
create: playbookCreateCommand,
|
|
707
|
+
// Aliases (hidden from --help via dedup in getCommandHelp)
|
|
708
|
+
new: playbookCreateCommand,
|
|
709
|
+
add: playbookCreateCommand,
|
|
710
|
+
ls: playbookListCommand,
|
|
711
|
+
get: playbookShowCommand,
|
|
712
|
+
view: playbookShowCommand,
|
|
713
|
+
},
|
|
714
|
+
handler: async (args, options) => {
|
|
715
|
+
// Default to list if no subcommand
|
|
716
|
+
if (args.length === 0) {
|
|
717
|
+
return playbookListHandler(args, options);
|
|
718
|
+
}
|
|
719
|
+
// Show "did you mean?" for unknown subcommands
|
|
720
|
+
const subNames = Object.keys(playbookCommand.subcommands);
|
|
721
|
+
const suggestions = suggestCommands(args[0], subNames);
|
|
722
|
+
let msg = `Unknown subcommand: ${args[0]}`;
|
|
723
|
+
if (suggestions.length > 0) {
|
|
724
|
+
msg += `\n\nDid you mean?\n${suggestions.map(s => ` ${s}`).join('\n')}`;
|
|
725
|
+
}
|
|
726
|
+
msg += '\n\nRun "sf playbook --help" to see available subcommands.';
|
|
727
|
+
return failure(msg, ExitCode.INVALID_ARGUMENTS);
|
|
728
|
+
},
|
|
729
|
+
};
|
|
730
|
+
//# sourceMappingURL=playbook.js.map
|