skedyul 0.1.9 → 0.1.11
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/cli/commands/diff.d.ts +1 -0
- package/dist/cli/commands/diff.js +295 -0
- package/dist/cli/commands/invoke.js +0 -1
- package/dist/cli/commands/validate.d.ts +1 -0
- package/dist/cli/commands/validate.js +267 -0
- package/dist/cli/index.js +14 -0
- package/dist/config.d.ts +113 -0
- package/dist/config.js +229 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -1
- package/dist/server.js +62 -27
- package/dist/types.d.ts +3 -6
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function diffCommand(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.diffCommand = diffCommand;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const utils_1 = require("../utils");
|
|
40
|
+
const config_1 = require("../../config");
|
|
41
|
+
function printHelp() {
|
|
42
|
+
console.log(`
|
|
43
|
+
skedyul dev diff - Show what would change on deploy
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
skedyul dev diff [options]
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
--config, -c Path to config file (default: auto-detect skedyul.config.ts)
|
|
50
|
+
--registry, -r Path to registry file to compare tools
|
|
51
|
+
--json Output as JSON
|
|
52
|
+
--help, -h Show this help message
|
|
53
|
+
|
|
54
|
+
Description:
|
|
55
|
+
Compares your local skedyul.config.ts with the currently deployed configuration.
|
|
56
|
+
Shows changes in environment variables, tools, and workflows that would be
|
|
57
|
+
applied when deploying a new version.
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
# Show diff for config in current directory
|
|
61
|
+
skedyul dev diff
|
|
62
|
+
|
|
63
|
+
# Show diff with specific config
|
|
64
|
+
skedyul dev diff --config ./skedyul.config.ts
|
|
65
|
+
|
|
66
|
+
# Compare against specific registry
|
|
67
|
+
skedyul dev diff --registry ./dist/registry.js
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
function findConfigFile(startDir) {
|
|
71
|
+
for (const fileName of config_1.CONFIG_FILE_NAMES) {
|
|
72
|
+
const filePath = path.join(startDir, fileName);
|
|
73
|
+
if (fs.existsSync(filePath)) {
|
|
74
|
+
return filePath;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function compareEnvSchemas(newEnv, oldEnv) {
|
|
80
|
+
const newKeys = newEnv ? Object.keys(newEnv) : [];
|
|
81
|
+
const oldKeys = oldEnv ? Object.keys(oldEnv) : [];
|
|
82
|
+
const added = newKeys.filter((k) => !oldKeys.includes(k));
|
|
83
|
+
const removed = oldKeys.filter((k) => !newKeys.includes(k));
|
|
84
|
+
const changed = [];
|
|
85
|
+
// Check for changes in existing keys
|
|
86
|
+
for (const key of newKeys) {
|
|
87
|
+
if (oldKeys.includes(key)) {
|
|
88
|
+
const newDef = newEnv?.[key];
|
|
89
|
+
const oldDef = oldEnv?.[key];
|
|
90
|
+
const changes = [];
|
|
91
|
+
if (newDef?.required !== oldDef?.required) {
|
|
92
|
+
changes.push(newDef?.required
|
|
93
|
+
? 'now required'
|
|
94
|
+
: 'no longer required');
|
|
95
|
+
}
|
|
96
|
+
if (newDef?.visibility !== oldDef?.visibility) {
|
|
97
|
+
changes.push(`visibility: ${oldDef?.visibility || 'visible'} → ${newDef?.visibility || 'visible'}`);
|
|
98
|
+
}
|
|
99
|
+
if (newDef?.label !== oldDef?.label) {
|
|
100
|
+
changes.push(`label changed`);
|
|
101
|
+
}
|
|
102
|
+
if (changes.length > 0) {
|
|
103
|
+
changed.push({ key, changes });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { added, removed, changed };
|
|
108
|
+
}
|
|
109
|
+
async function diffCommand(args) {
|
|
110
|
+
const { flags } = (0, utils_1.parseArgs)(args);
|
|
111
|
+
if (flags.help || flags.h) {
|
|
112
|
+
printHelp();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const jsonOutput = Boolean(flags.json);
|
|
116
|
+
// Find config file
|
|
117
|
+
let configPath = (flags.config || flags.c);
|
|
118
|
+
if (!configPath) {
|
|
119
|
+
const foundConfig = findConfigFile(process.cwd());
|
|
120
|
+
if (!foundConfig) {
|
|
121
|
+
if (jsonOutput) {
|
|
122
|
+
console.log(JSON.stringify({ error: 'No config file found' }));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.error('❌ No config file found');
|
|
126
|
+
console.error(` Create one of: ${config_1.CONFIG_FILE_NAMES.join(', ')}`);
|
|
127
|
+
}
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
configPath = foundConfig;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
configPath = path.resolve(process.cwd(), configPath);
|
|
134
|
+
}
|
|
135
|
+
if (!fs.existsSync(configPath)) {
|
|
136
|
+
if (jsonOutput) {
|
|
137
|
+
console.log(JSON.stringify({ error: `Config file not found: ${configPath}` }));
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.error(`❌ Config file not found: ${configPath}`);
|
|
141
|
+
}
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
// Load config
|
|
145
|
+
let config;
|
|
146
|
+
try {
|
|
147
|
+
config = await (0, config_1.loadConfig)(configPath);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (jsonOutput) {
|
|
151
|
+
console.log(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
console.error(`❌ Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
|
|
155
|
+
}
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
// Validate config
|
|
159
|
+
const validation = (0, config_1.validateConfig)(config);
|
|
160
|
+
if (!validation.valid) {
|
|
161
|
+
if (jsonOutput) {
|
|
162
|
+
console.log(JSON.stringify({ error: 'Invalid config', errors: validation.errors }));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.error('❌ Config validation failed:');
|
|
166
|
+
for (const err of validation.errors) {
|
|
167
|
+
console.error(` • ${err}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
// For now, we compare against "no previous config" (new deploy scenario)
|
|
173
|
+
// In a full implementation, this would fetch the current deployed config from the server
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
175
|
+
const previousConfig = undefined;
|
|
176
|
+
// Compare env schemas
|
|
177
|
+
const globalEnvDiff = compareEnvSchemas(config.env, previousConfig?.env);
|
|
178
|
+
const installEnvDiff = compareEnvSchemas(config.install?.env, previousConfig?.install?.env);
|
|
179
|
+
// Compare tools if registry path provided
|
|
180
|
+
let toolsDiff;
|
|
181
|
+
const registryPath = (flags.registry || flags.r);
|
|
182
|
+
if (registryPath) {
|
|
183
|
+
try {
|
|
184
|
+
const registry = await (0, utils_1.loadRegistry)(path.resolve(process.cwd(), registryPath));
|
|
185
|
+
const toolNames = Object.values(registry).map((t) => t.name);
|
|
186
|
+
// For now, show all tools as "added" since we don't have previous state
|
|
187
|
+
toolsDiff = {
|
|
188
|
+
added: toolNames,
|
|
189
|
+
removed: [],
|
|
190
|
+
total: toolNames.length,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
// Ignore registry loading errors for diff
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const result = {
|
|
198
|
+
configPath,
|
|
199
|
+
globalEnv: globalEnvDiff,
|
|
200
|
+
installEnv: installEnvDiff,
|
|
201
|
+
tools: toolsDiff,
|
|
202
|
+
summary: {
|
|
203
|
+
hasChanges: globalEnvDiff.added.length > 0 ||
|
|
204
|
+
globalEnvDiff.removed.length > 0 ||
|
|
205
|
+
globalEnvDiff.changed.length > 0 ||
|
|
206
|
+
installEnvDiff.added.length > 0 ||
|
|
207
|
+
installEnvDiff.removed.length > 0 ||
|
|
208
|
+
installEnvDiff.changed.length > 0 ||
|
|
209
|
+
(toolsDiff?.added.length ?? 0) > 0 ||
|
|
210
|
+
(toolsDiff?.removed.length ?? 0) > 0,
|
|
211
|
+
globalEnvChanges: globalEnvDiff.added.length +
|
|
212
|
+
globalEnvDiff.removed.length +
|
|
213
|
+
globalEnvDiff.changed.length,
|
|
214
|
+
installEnvChanges: installEnvDiff.added.length +
|
|
215
|
+
installEnvDiff.removed.length +
|
|
216
|
+
installEnvDiff.changed.length,
|
|
217
|
+
toolChanges: (toolsDiff?.added.length ?? 0) + (toolsDiff?.removed.length ?? 0),
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
if (jsonOutput) {
|
|
221
|
+
console.log(JSON.stringify(result, null, 2));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// Human-readable output
|
|
225
|
+
console.log('');
|
|
226
|
+
console.log(`📦 ${config.name}${config.version ? ` v${config.version}` : ''}`);
|
|
227
|
+
console.log(` ${configPath}`);
|
|
228
|
+
console.log('');
|
|
229
|
+
// Note about comparison
|
|
230
|
+
if (previousConfig === undefined) {
|
|
231
|
+
console.log('ℹ️ Comparing against empty state (new deployment)');
|
|
232
|
+
console.log('');
|
|
233
|
+
}
|
|
234
|
+
// Global env diff
|
|
235
|
+
if (result.summary.globalEnvChanges > 0) {
|
|
236
|
+
console.log('Global Environment Variables:');
|
|
237
|
+
for (const key of globalEnvDiff.added) {
|
|
238
|
+
const def = config.env?.[key];
|
|
239
|
+
const required = def?.required ? ' (required)' : '';
|
|
240
|
+
console.log(` + ${key}${required}`);
|
|
241
|
+
}
|
|
242
|
+
for (const key of globalEnvDiff.removed) {
|
|
243
|
+
console.log(` - ${key}`);
|
|
244
|
+
}
|
|
245
|
+
for (const item of globalEnvDiff.changed) {
|
|
246
|
+
console.log(` ~ ${item.key}: ${item.changes.join(', ')}`);
|
|
247
|
+
}
|
|
248
|
+
console.log('');
|
|
249
|
+
}
|
|
250
|
+
// Install env diff
|
|
251
|
+
if (result.summary.installEnvChanges > 0) {
|
|
252
|
+
console.log('Install Environment Variables:');
|
|
253
|
+
for (const key of installEnvDiff.added) {
|
|
254
|
+
const def = config.install?.env?.[key];
|
|
255
|
+
const required = def?.required ? ' (required)' : '';
|
|
256
|
+
console.log(` + ${key}${required}`);
|
|
257
|
+
}
|
|
258
|
+
for (const key of installEnvDiff.removed) {
|
|
259
|
+
console.log(` - ${key}`);
|
|
260
|
+
}
|
|
261
|
+
for (const item of installEnvDiff.changed) {
|
|
262
|
+
console.log(` ~ ${item.key}: ${item.changes.join(', ')}`);
|
|
263
|
+
}
|
|
264
|
+
console.log('');
|
|
265
|
+
}
|
|
266
|
+
// Tools diff
|
|
267
|
+
if (toolsDiff && result.summary.toolChanges > 0) {
|
|
268
|
+
console.log('Tools:');
|
|
269
|
+
for (const name of toolsDiff.added) {
|
|
270
|
+
console.log(` + ${name}`);
|
|
271
|
+
}
|
|
272
|
+
for (const name of toolsDiff.removed) {
|
|
273
|
+
console.log(` - ${name}`);
|
|
274
|
+
}
|
|
275
|
+
console.log('');
|
|
276
|
+
}
|
|
277
|
+
// Summary
|
|
278
|
+
if (result.summary.hasChanges) {
|
|
279
|
+
console.log('Summary:');
|
|
280
|
+
if (result.summary.globalEnvChanges > 0) {
|
|
281
|
+
console.log(` • ${result.summary.globalEnvChanges} global env var change(s)`);
|
|
282
|
+
}
|
|
283
|
+
if (result.summary.installEnvChanges > 0) {
|
|
284
|
+
console.log(` • ${result.summary.installEnvChanges} install env var change(s)`);
|
|
285
|
+
}
|
|
286
|
+
if (result.summary.toolChanges > 0) {
|
|
287
|
+
console.log(` • ${result.summary.toolChanges} tool change(s)`);
|
|
288
|
+
}
|
|
289
|
+
console.log('');
|
|
290
|
+
console.log('✅ Ready to deploy');
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
console.log('✅ No changes detected');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function validateCommand(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.validateCommand = validateCommand;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const utils_1 = require("../utils");
|
|
40
|
+
const config_1 = require("../../config");
|
|
41
|
+
function printHelp() {
|
|
42
|
+
console.log(`
|
|
43
|
+
skedyul dev validate - Validate skedyul.config.ts
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
skedyul dev validate [options]
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
--config, -c Path to config file (default: auto-detect skedyul.config.ts)
|
|
50
|
+
--verbose, -v Show detailed validation output
|
|
51
|
+
--json Output as JSON
|
|
52
|
+
--help, -h Show this help message
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
# Validate config in current directory
|
|
56
|
+
skedyul dev validate
|
|
57
|
+
|
|
58
|
+
# Validate specific config file
|
|
59
|
+
skedyul dev validate --config ./skedyul.config.ts
|
|
60
|
+
|
|
61
|
+
# Verbose output
|
|
62
|
+
skedyul dev validate --verbose
|
|
63
|
+
`);
|
|
64
|
+
}
|
|
65
|
+
function findConfigFile(startDir) {
|
|
66
|
+
for (const fileName of config_1.CONFIG_FILE_NAMES) {
|
|
67
|
+
const filePath = path.join(startDir, fileName);
|
|
68
|
+
if (fs.existsSync(filePath)) {
|
|
69
|
+
return filePath;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
async function validateCommand(args) {
|
|
75
|
+
const { flags } = (0, utils_1.parseArgs)(args);
|
|
76
|
+
if (flags.help || flags.h) {
|
|
77
|
+
printHelp();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const verbose = Boolean(flags.verbose || flags.v);
|
|
81
|
+
const jsonOutput = Boolean(flags.json);
|
|
82
|
+
// Find config file
|
|
83
|
+
let configPath = (flags.config || flags.c);
|
|
84
|
+
if (!configPath) {
|
|
85
|
+
const foundConfig = findConfigFile(process.cwd());
|
|
86
|
+
if (!foundConfig) {
|
|
87
|
+
const result = {
|
|
88
|
+
valid: false,
|
|
89
|
+
configPath: '',
|
|
90
|
+
errors: [`No config file found. Create one of: ${config_1.CONFIG_FILE_NAMES.join(', ')}`],
|
|
91
|
+
warnings: [],
|
|
92
|
+
};
|
|
93
|
+
if (jsonOutput) {
|
|
94
|
+
console.log(JSON.stringify(result, null, 2));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.error('❌ No config file found');
|
|
98
|
+
console.error(` Create one of: ${config_1.CONFIG_FILE_NAMES.join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
configPath = foundConfig;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
configPath = path.resolve(process.cwd(), configPath);
|
|
106
|
+
}
|
|
107
|
+
if (!fs.existsSync(configPath)) {
|
|
108
|
+
const result = {
|
|
109
|
+
valid: false,
|
|
110
|
+
configPath,
|
|
111
|
+
errors: [`Config file not found: ${configPath}`],
|
|
112
|
+
warnings: [],
|
|
113
|
+
};
|
|
114
|
+
if (jsonOutput) {
|
|
115
|
+
console.log(JSON.stringify(result, null, 2));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.error(`❌ Config file not found: ${configPath}`);
|
|
119
|
+
}
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
// Load and validate config
|
|
123
|
+
const warnings = [];
|
|
124
|
+
let config;
|
|
125
|
+
try {
|
|
126
|
+
config = await (0, config_1.loadConfig)(configPath);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
const result = {
|
|
130
|
+
valid: false,
|
|
131
|
+
configPath,
|
|
132
|
+
errors: [error instanceof Error ? error.message : String(error)],
|
|
133
|
+
warnings: [],
|
|
134
|
+
};
|
|
135
|
+
if (jsonOutput) {
|
|
136
|
+
console.log(JSON.stringify(result, null, 2));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.error(`❌ Failed to load config: ${result.errors[0]}`);
|
|
140
|
+
}
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
// Validate schema
|
|
144
|
+
const validation = (0, config_1.validateConfig)(config);
|
|
145
|
+
// Check for common issues (warnings)
|
|
146
|
+
if (!config.computeLayer) {
|
|
147
|
+
warnings.push('No computeLayer specified. Will default to "dedicated".');
|
|
148
|
+
}
|
|
149
|
+
if (!config.tools) {
|
|
150
|
+
warnings.push('No tools path specified. Will default to "./src/registry.ts".');
|
|
151
|
+
}
|
|
152
|
+
if (!config.workflows) {
|
|
153
|
+
warnings.push('No workflows path specified. Will default to "./workflows".');
|
|
154
|
+
}
|
|
155
|
+
// Check if tools file exists
|
|
156
|
+
const toolsPath = config.tools || './src/registry.ts';
|
|
157
|
+
const absoluteToolsPath = path.resolve(path.dirname(configPath), toolsPath);
|
|
158
|
+
if (!fs.existsSync(absoluteToolsPath)) {
|
|
159
|
+
// Check for .js variant
|
|
160
|
+
const jsToolsPath = absoluteToolsPath.replace(/\.ts$/, '.js');
|
|
161
|
+
if (!fs.existsSync(jsToolsPath)) {
|
|
162
|
+
warnings.push(`Tools file not found: ${toolsPath}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Check if workflows directory exists
|
|
166
|
+
const workflowsPath = config.workflows || './workflows';
|
|
167
|
+
const absoluteWorkflowsPath = path.resolve(path.dirname(configPath), workflowsPath);
|
|
168
|
+
if (!fs.existsSync(absoluteWorkflowsPath)) {
|
|
169
|
+
warnings.push(`Workflows directory not found: ${workflowsPath}`);
|
|
170
|
+
}
|
|
171
|
+
const envKeys = (0, config_1.getAllEnvKeys)(config);
|
|
172
|
+
const requiredInstallKeys = (0, config_1.getRequiredInstallEnvKeys)(config);
|
|
173
|
+
const result = {
|
|
174
|
+
valid: validation.valid,
|
|
175
|
+
configPath,
|
|
176
|
+
config: {
|
|
177
|
+
name: config.name,
|
|
178
|
+
version: config.version,
|
|
179
|
+
computeLayer: config.computeLayer,
|
|
180
|
+
tools: config.tools,
|
|
181
|
+
workflows: config.workflows,
|
|
182
|
+
globalEnvKeys: envKeys.global,
|
|
183
|
+
installEnvKeys: envKeys.install,
|
|
184
|
+
requiredInstallEnvKeys: requiredInstallKeys,
|
|
185
|
+
appModels: config.install?.appModels?.map((m) => m.entityHandle) || [],
|
|
186
|
+
},
|
|
187
|
+
errors: validation.errors,
|
|
188
|
+
warnings,
|
|
189
|
+
};
|
|
190
|
+
if (jsonOutput) {
|
|
191
|
+
console.log(JSON.stringify(result, null, 2));
|
|
192
|
+
process.exit(validation.valid ? 0 : 1);
|
|
193
|
+
}
|
|
194
|
+
// Human-readable output
|
|
195
|
+
console.log('');
|
|
196
|
+
console.log(`📦 ${config.name}${config.version ? ` v${config.version}` : ''}`);
|
|
197
|
+
console.log(` ${configPath}`);
|
|
198
|
+
console.log('');
|
|
199
|
+
if (verbose) {
|
|
200
|
+
console.log('Configuration:');
|
|
201
|
+
console.log(` Compute Layer: ${config.computeLayer || 'dedicated (default)'}`);
|
|
202
|
+
console.log(` Tools: ${config.tools || './src/registry.ts (default)'}`);
|
|
203
|
+
console.log(` Workflows: ${config.workflows || './workflows (default)'}`);
|
|
204
|
+
console.log('');
|
|
205
|
+
if (envKeys.global.length > 0) {
|
|
206
|
+
console.log('Global Environment Variables:');
|
|
207
|
+
for (const key of envKeys.global) {
|
|
208
|
+
const def = config.env?.[key];
|
|
209
|
+
const required = def?.required ? ' (required)' : '';
|
|
210
|
+
const visibility = def?.visibility === 'encrypted' ? ' 🔒' : '';
|
|
211
|
+
console.log(` ${key}${required}${visibility}`);
|
|
212
|
+
if (def?.label)
|
|
213
|
+
console.log(` └─ ${def.label}`);
|
|
214
|
+
}
|
|
215
|
+
console.log('');
|
|
216
|
+
}
|
|
217
|
+
if (envKeys.install.length > 0) {
|
|
218
|
+
console.log('Install Environment Variables:');
|
|
219
|
+
for (const key of envKeys.install) {
|
|
220
|
+
const def = config.install?.env?.[key];
|
|
221
|
+
const required = def?.required ? ' (required)' : '';
|
|
222
|
+
const visibility = def?.visibility === 'encrypted' ? ' 🔒' : '';
|
|
223
|
+
console.log(` ${key}${required}${visibility}`);
|
|
224
|
+
if (def?.label)
|
|
225
|
+
console.log(` └─ ${def.label}`);
|
|
226
|
+
}
|
|
227
|
+
console.log('');
|
|
228
|
+
}
|
|
229
|
+
if (config.install?.appModels && config.install.appModels.length > 0) {
|
|
230
|
+
console.log('App Models:');
|
|
231
|
+
for (const model of config.install.appModels) {
|
|
232
|
+
console.log(` ${model.entityHandle}: ${model.label}`);
|
|
233
|
+
}
|
|
234
|
+
console.log('');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Show errors
|
|
238
|
+
if (validation.errors.length > 0) {
|
|
239
|
+
console.log('❌ Validation Errors:');
|
|
240
|
+
for (const error of validation.errors) {
|
|
241
|
+
console.log(` • ${error}`);
|
|
242
|
+
}
|
|
243
|
+
console.log('');
|
|
244
|
+
}
|
|
245
|
+
// Show warnings
|
|
246
|
+
if (warnings.length > 0) {
|
|
247
|
+
console.log('⚠️ Warnings:');
|
|
248
|
+
for (const warning of warnings) {
|
|
249
|
+
console.log(` • ${warning}`);
|
|
250
|
+
}
|
|
251
|
+
console.log('');
|
|
252
|
+
}
|
|
253
|
+
// Final status
|
|
254
|
+
if (validation.valid) {
|
|
255
|
+
console.log('✅ Config is valid');
|
|
256
|
+
if (!verbose) {
|
|
257
|
+
console.log(` ${envKeys.global.length} global env vars, ${envKeys.install.length} install env vars`);
|
|
258
|
+
if (requiredInstallKeys.length > 0) {
|
|
259
|
+
console.log(` ${requiredInstallKeys.length} required install vars: ${requiredInstallKeys.join(', ')}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
console.log('❌ Config has errors');
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -4,6 +4,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const invoke_1 = require("./commands/invoke");
|
|
5
5
|
const tools_1 = require("./commands/tools");
|
|
6
6
|
const serve_1 = require("./commands/serve");
|
|
7
|
+
const validate_1 = require("./commands/validate");
|
|
8
|
+
const diff_1 = require("./commands/diff");
|
|
7
9
|
const args = process.argv.slice(2);
|
|
8
10
|
function printUsage() {
|
|
9
11
|
console.log(`
|
|
@@ -16,6 +18,8 @@ Commands:
|
|
|
16
18
|
dev invoke <tool> Invoke a tool from the registry
|
|
17
19
|
dev tools List all tools in the registry
|
|
18
20
|
dev serve Start a local MCP server
|
|
21
|
+
dev validate Validate skedyul.config.ts
|
|
22
|
+
dev diff Show what would change on deploy
|
|
19
23
|
|
|
20
24
|
Run 'skedyul dev <command> --help' for more information on a command.
|
|
21
25
|
`);
|
|
@@ -31,11 +35,15 @@ Commands:
|
|
|
31
35
|
invoke <tool> Invoke a tool from the registry
|
|
32
36
|
tools List all tools in the registry
|
|
33
37
|
serve Start a local MCP server
|
|
38
|
+
validate Validate skedyul.config.ts
|
|
39
|
+
diff Show what would change on deploy
|
|
34
40
|
|
|
35
41
|
Examples:
|
|
36
42
|
skedyul dev invoke my_tool --registry ./dist/registry.js --args '{"key": "value"}'
|
|
37
43
|
skedyul dev tools --registry ./dist/registry.js
|
|
38
44
|
skedyul dev serve --registry ./dist/registry.js --port 3001
|
|
45
|
+
skedyul dev validate
|
|
46
|
+
skedyul dev diff
|
|
39
47
|
|
|
40
48
|
Options:
|
|
41
49
|
--help, -h Show help for a command
|
|
@@ -68,6 +76,12 @@ async function main() {
|
|
|
68
76
|
case 'serve':
|
|
69
77
|
await (0, serve_1.serveCommand)(subArgs);
|
|
70
78
|
break;
|
|
79
|
+
case 'validate':
|
|
80
|
+
await (0, validate_1.validateCommand)(subArgs);
|
|
81
|
+
break;
|
|
82
|
+
case 'diff':
|
|
83
|
+
await (0, diff_1.diffCommand)(subArgs);
|
|
84
|
+
break;
|
|
71
85
|
default:
|
|
72
86
|
console.error(`Unknown dev command: ${subCommand}`);
|
|
73
87
|
console.error(`Run 'skedyul dev --help' for usage information.`);
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export type EnvVisibility = 'visible' | 'encrypted';
|
|
2
|
+
export interface EnvVariableDefinition {
|
|
3
|
+
/** Human-readable label for the variable */
|
|
4
|
+
label: string;
|
|
5
|
+
/** Whether this variable is required */
|
|
6
|
+
required?: boolean;
|
|
7
|
+
/** Visibility setting (encrypted values are hidden in UI) */
|
|
8
|
+
visibility?: EnvVisibility;
|
|
9
|
+
/** Default value if not provided */
|
|
10
|
+
default?: string;
|
|
11
|
+
/** Description/help text */
|
|
12
|
+
description?: string;
|
|
13
|
+
/** Placeholder text for input fields */
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
}
|
|
16
|
+
export type EnvSchema = Record<string, EnvVariableDefinition>;
|
|
17
|
+
export interface AppModelDefinition {
|
|
18
|
+
/** Unique handle for the entity (e.g., 'client', 'patient') */
|
|
19
|
+
entityHandle: string;
|
|
20
|
+
/** Human-readable label */
|
|
21
|
+
label: string;
|
|
22
|
+
/** Description of what this model represents */
|
|
23
|
+
description?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface InstallConfig {
|
|
26
|
+
/**
|
|
27
|
+
* Per-install environment variables.
|
|
28
|
+
* These are configured by the user when installing the app.
|
|
29
|
+
* Values are stored per-installation and can differ between installs.
|
|
30
|
+
*/
|
|
31
|
+
env?: EnvSchema;
|
|
32
|
+
/**
|
|
33
|
+
* Model mappings required for this app.
|
|
34
|
+
* Users will map these to their CRM models during installation.
|
|
35
|
+
*/
|
|
36
|
+
appModels?: AppModelDefinition[];
|
|
37
|
+
}
|
|
38
|
+
export type ComputeLayerType = 'serverless' | 'dedicated';
|
|
39
|
+
export interface SkedyulConfig {
|
|
40
|
+
/** App name */
|
|
41
|
+
name: string;
|
|
42
|
+
/** App version (semver) */
|
|
43
|
+
version?: string;
|
|
44
|
+
/** App description */
|
|
45
|
+
description?: string;
|
|
46
|
+
/** Compute layer: 'serverless' (Lambda) or 'dedicated' (ECS/Docker) */
|
|
47
|
+
computeLayer?: ComputeLayerType;
|
|
48
|
+
/** Path to the tool registry file (default: './src/registry.ts') */
|
|
49
|
+
tools?: string;
|
|
50
|
+
/** Path to the workflows directory (default: './workflows') */
|
|
51
|
+
workflows?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Global/version-level environment variables.
|
|
54
|
+
* These are baked into the container and are the same for all installations.
|
|
55
|
+
* Use for configuration that doesn't change per-install.
|
|
56
|
+
*/
|
|
57
|
+
env?: EnvSchema;
|
|
58
|
+
/**
|
|
59
|
+
* Install-time configuration.
|
|
60
|
+
* Defines what users need to configure when installing the app.
|
|
61
|
+
*/
|
|
62
|
+
install?: InstallConfig;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Define a Skedyul app configuration with full type safety.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* // skedyul.config.ts
|
|
70
|
+
* import { defineConfig } from 'skedyul'
|
|
71
|
+
*
|
|
72
|
+
* export default defineConfig({
|
|
73
|
+
* name: 'My App',
|
|
74
|
+
* computeLayer: 'dedicated',
|
|
75
|
+
* tools: './src/registry.ts',
|
|
76
|
+
* env: {
|
|
77
|
+
* LOG_LEVEL: { label: 'Log Level', default: 'info' },
|
|
78
|
+
* },
|
|
79
|
+
* install: {
|
|
80
|
+
* env: {
|
|
81
|
+
* API_KEY: { label: 'API Key', required: true, visibility: 'encrypted' },
|
|
82
|
+
* },
|
|
83
|
+
* },
|
|
84
|
+
* })
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export declare function defineConfig(config: SkedyulConfig): SkedyulConfig;
|
|
88
|
+
/**
|
|
89
|
+
* Default config file names to search for
|
|
90
|
+
*/
|
|
91
|
+
export declare const CONFIG_FILE_NAMES: string[];
|
|
92
|
+
/**
|
|
93
|
+
* Load a Skedyul config from a file path
|
|
94
|
+
*/
|
|
95
|
+
export declare function loadConfig(configPath: string): Promise<SkedyulConfig>;
|
|
96
|
+
/**
|
|
97
|
+
* Validate a config object
|
|
98
|
+
*/
|
|
99
|
+
export declare function validateConfig(config: SkedyulConfig): {
|
|
100
|
+
valid: boolean;
|
|
101
|
+
errors: string[];
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Get all required install env keys from a config
|
|
105
|
+
*/
|
|
106
|
+
export declare function getRequiredInstallEnvKeys(config: SkedyulConfig): string[];
|
|
107
|
+
/**
|
|
108
|
+
* Get all env keys (both global and install) from a config
|
|
109
|
+
*/
|
|
110
|
+
export declare function getAllEnvKeys(config: SkedyulConfig): {
|
|
111
|
+
global: string[];
|
|
112
|
+
install: string[];
|
|
113
|
+
};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CONFIG_FILE_NAMES = void 0;
|
|
37
|
+
exports.defineConfig = defineConfig;
|
|
38
|
+
exports.loadConfig = loadConfig;
|
|
39
|
+
exports.validateConfig = validateConfig;
|
|
40
|
+
exports.getRequiredInstallEnvKeys = getRequiredInstallEnvKeys;
|
|
41
|
+
exports.getAllEnvKeys = getAllEnvKeys;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
// Helper Function
|
|
47
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* Define a Skedyul app configuration with full type safety.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // skedyul.config.ts
|
|
54
|
+
* import { defineConfig } from 'skedyul'
|
|
55
|
+
*
|
|
56
|
+
* export default defineConfig({
|
|
57
|
+
* name: 'My App',
|
|
58
|
+
* computeLayer: 'dedicated',
|
|
59
|
+
* tools: './src/registry.ts',
|
|
60
|
+
* env: {
|
|
61
|
+
* LOG_LEVEL: { label: 'Log Level', default: 'info' },
|
|
62
|
+
* },
|
|
63
|
+
* install: {
|
|
64
|
+
* env: {
|
|
65
|
+
* API_KEY: { label: 'API Key', required: true, visibility: 'encrypted' },
|
|
66
|
+
* },
|
|
67
|
+
* },
|
|
68
|
+
* })
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
function defineConfig(config) {
|
|
72
|
+
return config;
|
|
73
|
+
}
|
|
74
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
75
|
+
// Config Loading Utilities
|
|
76
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
77
|
+
/**
|
|
78
|
+
* Default config file names to search for
|
|
79
|
+
*/
|
|
80
|
+
exports.CONFIG_FILE_NAMES = [
|
|
81
|
+
'skedyul.config.ts',
|
|
82
|
+
'skedyul.config.js',
|
|
83
|
+
'skedyul.config.mjs',
|
|
84
|
+
'skedyul.config.cjs',
|
|
85
|
+
];
|
|
86
|
+
/**
|
|
87
|
+
* Transpile a TypeScript config file to JavaScript
|
|
88
|
+
*/
|
|
89
|
+
async function transpileTypeScript(filePath) {
|
|
90
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
91
|
+
// Simple transpilation: remove type annotations and convert to CommonJS
|
|
92
|
+
// For more complex configs, users should pre-compile or use a JS config
|
|
93
|
+
let transpiled = content
|
|
94
|
+
// Remove import type statements
|
|
95
|
+
.replace(/import\s+type\s+\{[^}]+\}\s+from\s+['"][^'"]+['"]\s*;?\n?/g, '')
|
|
96
|
+
// Convert import { defineConfig } from 'skedyul' to require
|
|
97
|
+
.replace(/import\s+\{\s*defineConfig\s*\}\s+from\s+['"]skedyul['"]\s*;?\n?/g, '')
|
|
98
|
+
// Remove type annotations like : SkedyulConfig
|
|
99
|
+
.replace(/:\s*SkedyulConfig/g, '')
|
|
100
|
+
// Convert export default to module.exports
|
|
101
|
+
.replace(/export\s+default\s+/, 'module.exports = ')
|
|
102
|
+
// Replace defineConfig() wrapper with just the object
|
|
103
|
+
.replace(/defineConfig\s*\(\s*\{/, '{')
|
|
104
|
+
// Remove the closing paren from defineConfig
|
|
105
|
+
.replace(/\}\s*\)\s*;?\s*$/, '}');
|
|
106
|
+
return transpiled;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Load a Skedyul config from a file path
|
|
110
|
+
*/
|
|
111
|
+
async function loadConfig(configPath) {
|
|
112
|
+
const absolutePath = path.resolve(configPath);
|
|
113
|
+
if (!fs.existsSync(absolutePath)) {
|
|
114
|
+
throw new Error(`Config file not found: ${absolutePath}`);
|
|
115
|
+
}
|
|
116
|
+
const isTypeScript = absolutePath.endsWith('.ts');
|
|
117
|
+
try {
|
|
118
|
+
let moduleToLoad = absolutePath;
|
|
119
|
+
if (isTypeScript) {
|
|
120
|
+
// Transpile TypeScript to a temp JS file
|
|
121
|
+
const transpiled = await transpileTypeScript(absolutePath);
|
|
122
|
+
const tempDir = os.tmpdir();
|
|
123
|
+
const tempFile = path.join(tempDir, `skedyul-config-${Date.now()}.js`);
|
|
124
|
+
fs.writeFileSync(tempFile, transpiled);
|
|
125
|
+
moduleToLoad = tempFile;
|
|
126
|
+
try {
|
|
127
|
+
const module = require(moduleToLoad);
|
|
128
|
+
const config = module.default || module;
|
|
129
|
+
if (!config || typeof config !== 'object') {
|
|
130
|
+
throw new Error('Config file must export a configuration object');
|
|
131
|
+
}
|
|
132
|
+
if (!config.name || typeof config.name !== 'string') {
|
|
133
|
+
throw new Error('Config must have a "name" property');
|
|
134
|
+
}
|
|
135
|
+
return config;
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
// Clean up temp file
|
|
139
|
+
try {
|
|
140
|
+
fs.unlinkSync(tempFile);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Ignore cleanup errors
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// For JS files, use dynamic import
|
|
148
|
+
const module = await Promise.resolve(`${moduleToLoad}`).then(s => __importStar(require(s)));
|
|
149
|
+
const config = module.default || module;
|
|
150
|
+
if (!config || typeof config !== 'object') {
|
|
151
|
+
throw new Error('Config file must export a configuration object');
|
|
152
|
+
}
|
|
153
|
+
if (!config.name || typeof config.name !== 'string') {
|
|
154
|
+
throw new Error('Config must have a "name" property');
|
|
155
|
+
}
|
|
156
|
+
return config;
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
throw new Error(`Failed to load config from ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Validate a config object
|
|
164
|
+
*/
|
|
165
|
+
function validateConfig(config) {
|
|
166
|
+
const errors = [];
|
|
167
|
+
// Required fields
|
|
168
|
+
if (!config.name) {
|
|
169
|
+
errors.push('Missing required field: name');
|
|
170
|
+
}
|
|
171
|
+
// Validate computeLayer
|
|
172
|
+
if (config.computeLayer && !['serverless', 'dedicated'].includes(config.computeLayer)) {
|
|
173
|
+
errors.push(`Invalid computeLayer: ${config.computeLayer}. Must be 'serverless' or 'dedicated'`);
|
|
174
|
+
}
|
|
175
|
+
// Validate env schema
|
|
176
|
+
if (config.env) {
|
|
177
|
+
for (const [key, def] of Object.entries(config.env)) {
|
|
178
|
+
if (!def.label) {
|
|
179
|
+
errors.push(`env.${key}: Missing required field 'label'`);
|
|
180
|
+
}
|
|
181
|
+
if (def.visibility && !['visible', 'encrypted'].includes(def.visibility)) {
|
|
182
|
+
errors.push(`env.${key}: Invalid visibility '${def.visibility}'`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Validate install.env schema
|
|
187
|
+
if (config.install?.env) {
|
|
188
|
+
for (const [key, def] of Object.entries(config.install.env)) {
|
|
189
|
+
if (!def.label) {
|
|
190
|
+
errors.push(`install.env.${key}: Missing required field 'label'`);
|
|
191
|
+
}
|
|
192
|
+
if (def.visibility && !['visible', 'encrypted'].includes(def.visibility)) {
|
|
193
|
+
errors.push(`install.env.${key}: Invalid visibility '${def.visibility}'`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Validate appModels
|
|
198
|
+
if (config.install?.appModels) {
|
|
199
|
+
for (let i = 0; i < config.install.appModels.length; i++) {
|
|
200
|
+
const model = config.install.appModels[i];
|
|
201
|
+
if (!model.entityHandle) {
|
|
202
|
+
errors.push(`install.appModels[${i}]: Missing required field 'entityHandle'`);
|
|
203
|
+
}
|
|
204
|
+
if (!model.label) {
|
|
205
|
+
errors.push(`install.appModels[${i}]: Missing required field 'label'`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return { valid: errors.length === 0, errors };
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get all required install env keys from a config
|
|
213
|
+
*/
|
|
214
|
+
function getRequiredInstallEnvKeys(config) {
|
|
215
|
+
if (!config.install?.env)
|
|
216
|
+
return [];
|
|
217
|
+
return Object.entries(config.install.env)
|
|
218
|
+
.filter(([, def]) => def.required)
|
|
219
|
+
.map(([key]) => key);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Get all env keys (both global and install) from a config
|
|
223
|
+
*/
|
|
224
|
+
function getAllEnvKeys(config) {
|
|
225
|
+
return {
|
|
226
|
+
global: config.env ? Object.keys(config.env) : [],
|
|
227
|
+
install: config.install?.env ? Object.keys(config.install.env) : [],
|
|
228
|
+
};
|
|
229
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export * from './types';
|
|
2
2
|
export { server } from './server';
|
|
3
3
|
export { workplace, communicationChannel } from './core/client';
|
|
4
|
+
export { defineConfig, loadConfig, validateConfig, getRequiredInstallEnvKeys, getAllEnvKeys, CONFIG_FILE_NAMES, } from './config';
|
|
5
|
+
export type { SkedyulConfig, EnvVariableDefinition, EnvSchema, EnvVisibility, InstallConfig, AppModelDefinition, ComputeLayerType, } from './config';
|
package/dist/index.js
CHANGED
|
@@ -14,10 +14,17 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.communicationChannel = exports.workplace = exports.server = void 0;
|
|
17
|
+
exports.CONFIG_FILE_NAMES = exports.getAllEnvKeys = exports.getRequiredInstallEnvKeys = exports.validateConfig = exports.loadConfig = exports.defineConfig = exports.communicationChannel = exports.workplace = exports.server = void 0;
|
|
18
18
|
__exportStar(require("./types"), exports);
|
|
19
19
|
var server_1 = require("./server");
|
|
20
20
|
Object.defineProperty(exports, "server", { enumerable: true, get: function () { return server_1.server; } });
|
|
21
21
|
var client_1 = require("./core/client");
|
|
22
22
|
Object.defineProperty(exports, "workplace", { enumerable: true, get: function () { return client_1.workplace; } });
|
|
23
23
|
Object.defineProperty(exports, "communicationChannel", { enumerable: true, get: function () { return client_1.communicationChannel; } });
|
|
24
|
+
var config_1 = require("./config");
|
|
25
|
+
Object.defineProperty(exports, "defineConfig", { enumerable: true, get: function () { return config_1.defineConfig; } });
|
|
26
|
+
Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return config_1.loadConfig; } });
|
|
27
|
+
Object.defineProperty(exports, "validateConfig", { enumerable: true, get: function () { return config_1.validateConfig; } });
|
|
28
|
+
Object.defineProperty(exports, "getRequiredInstallEnvKeys", { enumerable: true, get: function () { return config_1.getRequiredInstallEnvKeys; } });
|
|
29
|
+
Object.defineProperty(exports, "getAllEnvKeys", { enumerable: true, get: function () { return config_1.getAllEnvKeys; } });
|
|
30
|
+
Object.defineProperty(exports, "CONFIG_FILE_NAMES", { enumerable: true, get: function () { return config_1.CONFIG_FILE_NAMES; } });
|
package/dist/server.js
CHANGED
|
@@ -338,27 +338,15 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
338
338
|
});
|
|
339
339
|
const billing = normalizeBilling(functionResult.billing);
|
|
340
340
|
return {
|
|
341
|
-
|
|
342
|
-
{
|
|
343
|
-
type: 'text',
|
|
344
|
-
text: JSON.stringify(functionResult.output),
|
|
345
|
-
},
|
|
346
|
-
],
|
|
341
|
+
output: functionResult.output,
|
|
347
342
|
billing,
|
|
348
343
|
};
|
|
349
344
|
}
|
|
350
345
|
catch (error) {
|
|
351
346
|
return {
|
|
352
|
-
|
|
353
|
-
{
|
|
354
|
-
type: 'text',
|
|
355
|
-
text: JSON.stringify({
|
|
356
|
-
error: error instanceof Error ? error.message : String(error ?? ''),
|
|
357
|
-
}),
|
|
358
|
-
},
|
|
359
|
-
],
|
|
347
|
+
output: null,
|
|
360
348
|
billing: { credits: 0 },
|
|
361
|
-
|
|
349
|
+
error: error instanceof Error ? error.message : String(error ?? ''),
|
|
362
350
|
};
|
|
363
351
|
}
|
|
364
352
|
finally {
|
|
@@ -443,21 +431,41 @@ function createSkedyulServer(config, registry) {
|
|
|
443
431
|
const toolName = tool.name || toolKey;
|
|
444
432
|
const inputZodSchema = getZodSchema(tool.inputs);
|
|
445
433
|
const outputZodSchema = getZodSchema(tool.outputSchema);
|
|
434
|
+
const hasOutputSchema = Boolean(outputZodSchema);
|
|
446
435
|
mcpServer.registerTool(toolName, {
|
|
447
436
|
title: toolName,
|
|
448
437
|
description: tool.description,
|
|
449
438
|
inputSchema: inputZodSchema,
|
|
450
439
|
outputSchema: outputZodSchema,
|
|
451
440
|
}, async (args) => {
|
|
452
|
-
|
|
441
|
+
// Support both formats:
|
|
442
|
+
// 1. Skedyul format: { inputs: {...}, env: {...} }
|
|
443
|
+
// 2. Standard MCP format: { ...directArgs }
|
|
444
|
+
const rawArgs = args;
|
|
445
|
+
const hasSkedyulFormat = 'inputs' in rawArgs || 'env' in rawArgs;
|
|
446
|
+
const toolInputs = hasSkedyulFormat ? (rawArgs.inputs ?? {}) : rawArgs;
|
|
447
|
+
const toolEnv = hasSkedyulFormat ? rawArgs.env : undefined;
|
|
448
|
+
const validatedInputs = inputZodSchema ? inputZodSchema.parse(toolInputs) : toolInputs;
|
|
453
449
|
const result = await callTool(toolKey, {
|
|
454
|
-
inputs:
|
|
450
|
+
inputs: validatedInputs,
|
|
451
|
+
env: toolEnv,
|
|
455
452
|
});
|
|
453
|
+
// Handle error case
|
|
454
|
+
if (result.error) {
|
|
455
|
+
const errorOutput = { error: result.error };
|
|
456
|
+
return {
|
|
457
|
+
content: [{ type: 'text', text: JSON.stringify(errorOutput) }],
|
|
458
|
+
structuredContent: errorOutput,
|
|
459
|
+
isError: true,
|
|
460
|
+
billing: result.billing,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
// Transform internal format to MCP protocol format
|
|
464
|
+
const outputData = result.output;
|
|
456
465
|
return {
|
|
457
|
-
content: result.
|
|
458
|
-
structuredContent:
|
|
459
|
-
|
|
460
|
-
: JSON.parse(result.content[0]?.text ?? '{}'),
|
|
466
|
+
content: [{ type: 'text', text: JSON.stringify(result.output) }],
|
|
467
|
+
structuredContent: outputData ?? undefined,
|
|
468
|
+
billing: result.billing,
|
|
461
469
|
};
|
|
462
470
|
});
|
|
463
471
|
}
|
|
@@ -771,7 +779,13 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
771
779
|
}
|
|
772
780
|
else if (rpcMethod === 'tools/call') {
|
|
773
781
|
const toolName = params?.name;
|
|
774
|
-
|
|
782
|
+
// Support both formats:
|
|
783
|
+
// 1. Skedyul format: { inputs: {...}, env: {...} }
|
|
784
|
+
// 2. Standard MCP format: { ...directArgs }
|
|
785
|
+
const rawArgs = (params?.arguments ?? {});
|
|
786
|
+
const hasSkedyulFormat = 'inputs' in rawArgs || 'env' in rawArgs;
|
|
787
|
+
const toolInputs = hasSkedyulFormat ? (rawArgs.inputs ?? {}) : rawArgs;
|
|
788
|
+
const toolEnv = hasSkedyulFormat ? rawArgs.env : undefined;
|
|
775
789
|
// Find tool by name (check both registry key and tool.name)
|
|
776
790
|
let toolKey = null;
|
|
777
791
|
let tool = null;
|
|
@@ -794,12 +808,33 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
|
|
|
794
808
|
}
|
|
795
809
|
try {
|
|
796
810
|
const inputSchema = getZodSchema(tool.inputs);
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
811
|
+
const outputSchema = getZodSchema(tool.outputSchema);
|
|
812
|
+
const hasOutputSchema = Boolean(outputSchema);
|
|
813
|
+
const validatedInputs = inputSchema
|
|
814
|
+
? inputSchema.parse(toolInputs)
|
|
815
|
+
: toolInputs;
|
|
816
|
+
const toolResult = await callTool(toolKey, {
|
|
817
|
+
inputs: validatedInputs,
|
|
818
|
+
env: toolEnv,
|
|
802
819
|
});
|
|
820
|
+
// Transform internal format to MCP protocol format
|
|
821
|
+
if (toolResult.error) {
|
|
822
|
+
const errorOutput = { error: toolResult.error };
|
|
823
|
+
result = {
|
|
824
|
+
content: [{ type: 'text', text: JSON.stringify(errorOutput) }],
|
|
825
|
+
structuredContent: errorOutput,
|
|
826
|
+
isError: true,
|
|
827
|
+
billing: toolResult.billing,
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
const outputData = toolResult.output;
|
|
832
|
+
result = {
|
|
833
|
+
content: [{ type: 'text', text: JSON.stringify(toolResult.output) }],
|
|
834
|
+
structuredContent: outputData ?? undefined,
|
|
835
|
+
billing: toolResult.billing,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
803
838
|
}
|
|
804
839
|
catch (validationError) {
|
|
805
840
|
return createResponse(200, {
|
package/dist/types.d.ts
CHANGED
|
@@ -97,12 +97,9 @@ export interface APIGatewayProxyResult {
|
|
|
97
97
|
body: string;
|
|
98
98
|
}
|
|
99
99
|
export interface ToolCallResponse {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}[];
|
|
104
|
-
billing?: BillingInfo;
|
|
105
|
-
isError?: boolean;
|
|
100
|
+
output: unknown;
|
|
101
|
+
billing: BillingInfo;
|
|
102
|
+
error?: string;
|
|
106
103
|
}
|
|
107
104
|
export interface DedicatedServerInstance {
|
|
108
105
|
listen(port?: number): Promise<void>;
|