skedyul 0.1.9 → 0.1.10

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.
@@ -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
+ }
@@ -198,7 +198,6 @@ async function invokeCommand(args) {
198
198
  if (estimateMode) {
199
199
  console.error('Mode: estimate (billing only)');
200
200
  }
201
- console.error('');
202
201
  try {
203
202
  const handler = tool.handler;
204
203
  const result = await handler({
@@ -0,0 +1 @@
1
+ export declare function validateCommand(args: string[]): Promise<void>;
@@ -0,0 +1,269 @@
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
+ runtime: config.runtime,
181
+ tools: config.tools,
182
+ workflows: config.workflows,
183
+ globalEnvKeys: envKeys.global,
184
+ installEnvKeys: envKeys.install,
185
+ requiredInstallEnvKeys: requiredInstallKeys,
186
+ appModels: config.install?.appModels?.map((m) => m.entityHandle) || [],
187
+ },
188
+ errors: validation.errors,
189
+ warnings,
190
+ };
191
+ if (jsonOutput) {
192
+ console.log(JSON.stringify(result, null, 2));
193
+ process.exit(validation.valid ? 0 : 1);
194
+ }
195
+ // Human-readable output
196
+ console.log('');
197
+ console.log(`📦 ${config.name}${config.version ? ` v${config.version}` : ''}`);
198
+ console.log(` ${configPath}`);
199
+ console.log('');
200
+ if (verbose) {
201
+ console.log('Configuration:');
202
+ console.log(` Compute Layer: ${config.computeLayer || 'dedicated (default)'}`);
203
+ console.log(` Runtime: ${config.runtime || 'node-22 (default)'}`);
204
+ console.log(` Tools: ${config.tools || './src/registry.ts (default)'}`);
205
+ console.log(` Workflows: ${config.workflows || './workflows (default)'}`);
206
+ console.log('');
207
+ if (envKeys.global.length > 0) {
208
+ console.log('Global Environment Variables:');
209
+ for (const key of envKeys.global) {
210
+ const def = config.env?.[key];
211
+ const required = def?.required ? ' (required)' : '';
212
+ const visibility = def?.visibility === 'encrypted' ? ' 🔒' : '';
213
+ console.log(` ${key}${required}${visibility}`);
214
+ if (def?.label)
215
+ console.log(` └─ ${def.label}`);
216
+ }
217
+ console.log('');
218
+ }
219
+ if (envKeys.install.length > 0) {
220
+ console.log('Install Environment Variables:');
221
+ for (const key of envKeys.install) {
222
+ const def = config.install?.env?.[key];
223
+ const required = def?.required ? ' (required)' : '';
224
+ const visibility = def?.visibility === 'encrypted' ? ' 🔒' : '';
225
+ console.log(` ${key}${required}${visibility}`);
226
+ if (def?.label)
227
+ console.log(` └─ ${def.label}`);
228
+ }
229
+ console.log('');
230
+ }
231
+ if (config.install?.appModels && config.install.appModels.length > 0) {
232
+ console.log('App Models:');
233
+ for (const model of config.install.appModels) {
234
+ console.log(` ${model.entityHandle}: ${model.label}`);
235
+ }
236
+ console.log('');
237
+ }
238
+ }
239
+ // Show errors
240
+ if (validation.errors.length > 0) {
241
+ console.log('❌ Validation Errors:');
242
+ for (const error of validation.errors) {
243
+ console.log(` • ${error}`);
244
+ }
245
+ console.log('');
246
+ }
247
+ // Show warnings
248
+ if (warnings.length > 0) {
249
+ console.log('⚠️ Warnings:');
250
+ for (const warning of warnings) {
251
+ console.log(` • ${warning}`);
252
+ }
253
+ console.log('');
254
+ }
255
+ // Final status
256
+ if (validation.valid) {
257
+ console.log('✅ Config is valid');
258
+ if (!verbose) {
259
+ console.log(` ${envKeys.global.length} global env vars, ${envKeys.install.length} install env vars`);
260
+ if (requiredInstallKeys.length > 0) {
261
+ console.log(` ${requiredInstallKeys.length} required install vars: ${requiredInstallKeys.join(', ')}`);
262
+ }
263
+ }
264
+ }
265
+ else {
266
+ console.log('❌ Config has errors');
267
+ process.exit(1);
268
+ }
269
+ }
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.`);
@@ -0,0 +1,116 @@
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 type RuntimeType = 'node-22' | 'node-20' | 'node-18';
40
+ export interface SkedyulConfig {
41
+ /** App name */
42
+ name: string;
43
+ /** App version (semver) */
44
+ version?: string;
45
+ /** App description */
46
+ description?: string;
47
+ /** Compute layer: 'serverless' (Lambda) or 'dedicated' (ECS/Docker) */
48
+ computeLayer?: ComputeLayerType;
49
+ /** Runtime environment */
50
+ runtime?: RuntimeType;
51
+ /** Path to the tool registry file (default: './src/registry.ts') */
52
+ tools?: string;
53
+ /** Path to the workflows directory (default: './workflows') */
54
+ workflows?: string;
55
+ /**
56
+ * Global/version-level environment variables.
57
+ * These are baked into the container and are the same for all installations.
58
+ * Use for configuration that doesn't change per-install.
59
+ */
60
+ env?: EnvSchema;
61
+ /**
62
+ * Install-time configuration.
63
+ * Defines what users need to configure when installing the app.
64
+ */
65
+ install?: InstallConfig;
66
+ }
67
+ /**
68
+ * Define a Skedyul app configuration with full type safety.
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * // skedyul.config.ts
73
+ * import { defineConfig } from 'skedyul'
74
+ *
75
+ * export default defineConfig({
76
+ * name: 'My App',
77
+ * computeLayer: 'dedicated',
78
+ * tools: './src/registry.ts',
79
+ * env: {
80
+ * LOG_LEVEL: { label: 'Log Level', default: 'info' },
81
+ * },
82
+ * install: {
83
+ * env: {
84
+ * API_KEY: { label: 'API Key', required: true, visibility: 'encrypted' },
85
+ * },
86
+ * },
87
+ * })
88
+ * ```
89
+ */
90
+ export declare function defineConfig(config: SkedyulConfig): SkedyulConfig;
91
+ /**
92
+ * Default config file names to search for
93
+ */
94
+ export declare const CONFIG_FILE_NAMES: string[];
95
+ /**
96
+ * Load a Skedyul config from a file path
97
+ */
98
+ export declare function loadConfig(configPath: string): Promise<SkedyulConfig>;
99
+ /**
100
+ * Validate a config object
101
+ */
102
+ export declare function validateConfig(config: SkedyulConfig): {
103
+ valid: boolean;
104
+ errors: string[];
105
+ };
106
+ /**
107
+ * Get all required install env keys from a config
108
+ */
109
+ export declare function getRequiredInstallEnvKeys(config: SkedyulConfig): string[];
110
+ /**
111
+ * Get all env keys (both global and install) from a config
112
+ */
113
+ export declare function getAllEnvKeys(config: SkedyulConfig): {
114
+ global: string[];
115
+ install: string[];
116
+ };
package/dist/config.js ADDED
@@ -0,0 +1,233 @@
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 runtime
176
+ if (config.runtime && !['node-22', 'node-20', 'node-18'].includes(config.runtime)) {
177
+ errors.push(`Invalid runtime: ${config.runtime}. Must be 'node-22', 'node-20', or 'node-18'`);
178
+ }
179
+ // Validate env schema
180
+ if (config.env) {
181
+ for (const [key, def] of Object.entries(config.env)) {
182
+ if (!def.label) {
183
+ errors.push(`env.${key}: Missing required field 'label'`);
184
+ }
185
+ if (def.visibility && !['visible', 'encrypted'].includes(def.visibility)) {
186
+ errors.push(`env.${key}: Invalid visibility '${def.visibility}'`);
187
+ }
188
+ }
189
+ }
190
+ // Validate install.env schema
191
+ if (config.install?.env) {
192
+ for (const [key, def] of Object.entries(config.install.env)) {
193
+ if (!def.label) {
194
+ errors.push(`install.env.${key}: Missing required field 'label'`);
195
+ }
196
+ if (def.visibility && !['visible', 'encrypted'].includes(def.visibility)) {
197
+ errors.push(`install.env.${key}: Invalid visibility '${def.visibility}'`);
198
+ }
199
+ }
200
+ }
201
+ // Validate appModels
202
+ if (config.install?.appModels) {
203
+ for (let i = 0; i < config.install.appModels.length; i++) {
204
+ const model = config.install.appModels[i];
205
+ if (!model.entityHandle) {
206
+ errors.push(`install.appModels[${i}]: Missing required field 'entityHandle'`);
207
+ }
208
+ if (!model.label) {
209
+ errors.push(`install.appModels[${i}]: Missing required field 'label'`);
210
+ }
211
+ }
212
+ }
213
+ return { valid: errors.length === 0, errors };
214
+ }
215
+ /**
216
+ * Get all required install env keys from a config
217
+ */
218
+ function getRequiredInstallEnvKeys(config) {
219
+ if (!config.install?.env)
220
+ return [];
221
+ return Object.entries(config.install.env)
222
+ .filter(([, def]) => def.required)
223
+ .map(([key]) => key);
224
+ }
225
+ /**
226
+ * Get all env keys (both global and install) from a config
227
+ */
228
+ function getAllEnvKeys(config) {
229
+ return {
230
+ global: config.env ? Object.keys(config.env) : [],
231
+ install: config.install?.env ? Object.keys(config.install.env) : [],
232
+ };
233
+ }
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, RuntimeType, } 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skedyul",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "The Skedyul SDK for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",