@codemieai/cdk 0.1.630 → 0.1.632
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cli/index.js +205 -126
- package/dist/index.d.ts +5 -2
- package/dist/index.js +188 -120
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/cli/index.js
CHANGED
|
@@ -1023,7 +1023,7 @@ var init_backupTransformers = __esm({
|
|
|
1023
1023
|
|
|
1024
1024
|
// package.json
|
|
1025
1025
|
var package_default = {
|
|
1026
|
-
version: "0.1.
|
|
1026
|
+
version: "0.1.632"};
|
|
1027
1027
|
var appConfigSchema = z.object({
|
|
1028
1028
|
rootDir: z.string(),
|
|
1029
1029
|
codemieConfig: z.string(),
|
|
@@ -2442,6 +2442,25 @@ async function main(options) {
|
|
|
2442
2442
|
}
|
|
2443
2443
|
}
|
|
2444
2444
|
|
|
2445
|
+
// src/lib/apiErrorLogger.ts
|
|
2446
|
+
init_logger();
|
|
2447
|
+
function logApiErrorDetails(error) {
|
|
2448
|
+
if (error instanceof Error) {
|
|
2449
|
+
logger.error(` ${error.message}`);
|
|
2450
|
+
logger.debug(` Stack:`, error.stack);
|
|
2451
|
+
if ("statusCode" in error) {
|
|
2452
|
+
logger.error(` Status: ${String(error.statusCode)}`);
|
|
2453
|
+
logger.error(` Data: ${JSON.stringify("response" in error ? error.response : void 0, null, 2)}`);
|
|
2454
|
+
} else if ("response" in error) {
|
|
2455
|
+
const axiosError = error;
|
|
2456
|
+
logger.error(` Status: ${axiosError.response?.status}`);
|
|
2457
|
+
logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
|
|
2458
|
+
}
|
|
2459
|
+
} else {
|
|
2460
|
+
logger.error(` ${String(error)}`);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2445
2464
|
// src/lib/paginationUtils.ts
|
|
2446
2465
|
init_logger();
|
|
2447
2466
|
async function findResourceByName(listFn, name, resourceType, options) {
|
|
@@ -2496,9 +2515,96 @@ async function findAssistantByName(client, name, options) {
|
|
|
2496
2515
|
}
|
|
2497
2516
|
return null;
|
|
2498
2517
|
}
|
|
2499
|
-
async function
|
|
2500
|
-
const
|
|
2501
|
-
|
|
2518
|
+
async function findWorkflowByName(client, name, project) {
|
|
2519
|
+
const perPage = 100;
|
|
2520
|
+
const matches = [];
|
|
2521
|
+
let totalChecked = 0;
|
|
2522
|
+
let page = 0;
|
|
2523
|
+
while (true) {
|
|
2524
|
+
const workflows = await client.workflows.list({ page, per_page: perPage, projects: [project] }) ?? [];
|
|
2525
|
+
totalChecked += workflows.length;
|
|
2526
|
+
for (const workflow of workflows) {
|
|
2527
|
+
if (workflow.name === name && workflow.id) {
|
|
2528
|
+
matches.push(workflow.id);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
if (workflows.length < perPage) {
|
|
2532
|
+
break;
|
|
2533
|
+
}
|
|
2534
|
+
page++;
|
|
2535
|
+
if (page > 1e3) {
|
|
2536
|
+
throw new Error(
|
|
2537
|
+
`Workflow lookup for "${name}" in project "${project}" stopped after 1000 pages (${totalChecked} resources checked); refusing to create a possible duplicate`
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
if (matches.length > 1) {
|
|
2542
|
+
const ids = [...matches].sort((a, b) => a.localeCompare(b)).join(", ");
|
|
2543
|
+
throw new Error(`Multiple workflows named "${name}" in project "${project}": ${ids}`);
|
|
2544
|
+
}
|
|
2545
|
+
if (matches.length === 0) {
|
|
2546
|
+
logger.warn(
|
|
2547
|
+
` \u26A0\uFE0F workflow "${name}" not found after checking ${totalChecked} resources across ${page + 1} page(s)`
|
|
2548
|
+
);
|
|
2549
|
+
return null;
|
|
2550
|
+
}
|
|
2551
|
+
return matches[0];
|
|
2552
|
+
}
|
|
2553
|
+
var SkillNameIndex = class {
|
|
2554
|
+
constructor(client) {
|
|
2555
|
+
this.client = client;
|
|
2556
|
+
}
|
|
2557
|
+
client;
|
|
2558
|
+
ids = /* @__PURE__ */ new Map();
|
|
2559
|
+
built = false;
|
|
2560
|
+
scanned = { skills: 0, pages: 0 };
|
|
2561
|
+
async find(name) {
|
|
2562
|
+
const known = this.ids.get(name);
|
|
2563
|
+
if (known) {
|
|
2564
|
+
return known;
|
|
2565
|
+
}
|
|
2566
|
+
if (!this.built) {
|
|
2567
|
+
await this.build();
|
|
2568
|
+
const id = this.ids.get(name);
|
|
2569
|
+
if (id) {
|
|
2570
|
+
return id;
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
logger.warn(
|
|
2574
|
+
` \u26A0\uFE0F skill "${name}" not found after checking ${this.scanned.skills} resources across ${this.scanned.pages} page(s)`
|
|
2575
|
+
);
|
|
2576
|
+
return null;
|
|
2577
|
+
}
|
|
2578
|
+
record(name, id) {
|
|
2579
|
+
this.ids.set(name, id);
|
|
2580
|
+
}
|
|
2581
|
+
async build() {
|
|
2582
|
+
const perPage = 100;
|
|
2583
|
+
let page = 0;
|
|
2584
|
+
let totalChecked = 0;
|
|
2585
|
+
while (true) {
|
|
2586
|
+
const skills = await this.client.skills.list({ page, per_page: perPage }) ?? [];
|
|
2587
|
+
totalChecked += skills.length;
|
|
2588
|
+
for (const skill of skills) {
|
|
2589
|
+
if (skill.name && skill.id && !this.ids.has(skill.name)) {
|
|
2590
|
+
this.ids.set(skill.name, skill.id);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
if (skills.length < perPage) {
|
|
2594
|
+
break;
|
|
2595
|
+
}
|
|
2596
|
+
page++;
|
|
2597
|
+
if (page > 1e3) {
|
|
2598
|
+
logger.warn(` \u26A0\uFE0F Stopped pagination after 1000 pages (${totalChecked} resources checked)`);
|
|
2599
|
+
break;
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
this.built = true;
|
|
2603
|
+
this.scanned = { skills: totalChecked, pages: page + 1 };
|
|
2604
|
+
}
|
|
2605
|
+
};
|
|
2606
|
+
function findSkillByName(index, name) {
|
|
2607
|
+
return index.find(name);
|
|
2502
2608
|
}
|
|
2503
2609
|
|
|
2504
2610
|
// src/lib/assistantHelpers.ts
|
|
@@ -3331,20 +3437,7 @@ async function deployAssistants(config, client, loader, stateManager) {
|
|
|
3331
3437
|
logger.info("");
|
|
3332
3438
|
} catch (error) {
|
|
3333
3439
|
logger.error(` \u274C Failed to deploy ${assistant.name}:`);
|
|
3334
|
-
|
|
3335
|
-
logger.error(` ${error.message}`);
|
|
3336
|
-
logger.debug(` Stack:`, error.stack);
|
|
3337
|
-
if ("statusCode" in error) {
|
|
3338
|
-
logger.error(` Status: ${String(error.statusCode)}`);
|
|
3339
|
-
logger.error(` Data: ${JSON.stringify("response" in error ? error.response : void 0, null, 2)}`);
|
|
3340
|
-
} else if ("response" in error) {
|
|
3341
|
-
const axiosError = error;
|
|
3342
|
-
logger.error(` Status: ${axiosError.response?.status}`);
|
|
3343
|
-
logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
|
|
3344
|
-
}
|
|
3345
|
-
} else {
|
|
3346
|
-
logger.error(` ${String(error)}`);
|
|
3347
|
-
}
|
|
3440
|
+
logApiErrorDetails(error);
|
|
3348
3441
|
logger.info("");
|
|
3349
3442
|
stats.failed++;
|
|
3350
3443
|
}
|
|
@@ -3357,6 +3450,7 @@ async function deploySkills(config, client, loader, stateManager) {
|
|
|
3357
3450
|
if (!config.resources.skills) {
|
|
3358
3451
|
return stats;
|
|
3359
3452
|
}
|
|
3453
|
+
const skillIndex = new SkillNameIndex(client);
|
|
3360
3454
|
for (const skill of config.resources.skills) {
|
|
3361
3455
|
try {
|
|
3362
3456
|
logger.info(`Processing: ${skill.name}`);
|
|
@@ -3407,6 +3501,7 @@ async function deploySkills(config, client, loader, stateManager) {
|
|
|
3407
3501
|
categories: skill.categories
|
|
3408
3502
|
});
|
|
3409
3503
|
logger.info(`\u2713 Created skill: ${skill.name} (${result.id})`);
|
|
3504
|
+
skillIndex.record(skill.name, result.id);
|
|
3410
3505
|
stateManager.updateSkillState(skill.name, result.id, content, skill);
|
|
3411
3506
|
stats.created++;
|
|
3412
3507
|
}
|
|
@@ -3435,6 +3530,7 @@ async function deploySkills(config, client, loader, stateManager) {
|
|
|
3435
3530
|
});
|
|
3436
3531
|
skillId = result.id;
|
|
3437
3532
|
logger.info(`\u2713 Created skill: ${skill.name} (${skillId})`);
|
|
3533
|
+
skillIndex.record(skill.name, skillId);
|
|
3438
3534
|
stats.created++;
|
|
3439
3535
|
} catch (createError) {
|
|
3440
3536
|
const msg = createError instanceof Error ? createError.message : String(createError);
|
|
@@ -3442,7 +3538,7 @@ async function deploySkills(config, client, loader, stateManager) {
|
|
|
3442
3538
|
throw createError;
|
|
3443
3539
|
}
|
|
3444
3540
|
logger.warn(` \u26A0\uFE0F Skill already exists on platform (no local state). Fetching ID to recover...`);
|
|
3445
|
-
const existingId = await findSkillByName(
|
|
3541
|
+
const existingId = await findSkillByName(skillIndex, skill.name);
|
|
3446
3542
|
if (!existingId) {
|
|
3447
3543
|
throw new Error(`Skill "${skill.name}" reported as existing but could not be found by name.`);
|
|
3448
3544
|
}
|
|
@@ -3462,21 +3558,7 @@ async function deploySkills(config, client, loader, stateManager) {
|
|
|
3462
3558
|
logger.info("");
|
|
3463
3559
|
} catch (error) {
|
|
3464
3560
|
logger.error(` \u274C Failed to deploy ${skill.name}:`);
|
|
3465
|
-
|
|
3466
|
-
logger.error(` ${error.message}`);
|
|
3467
|
-
logger.debug(` Stack:`, error.stack);
|
|
3468
|
-
if ("statusCode" in error) {
|
|
3469
|
-
const apiError = error;
|
|
3470
|
-
logger.error(` Status: ${apiError.statusCode}`);
|
|
3471
|
-
logger.error(` Data: ${JSON.stringify(apiError.response, null, 2)}`);
|
|
3472
|
-
} else if ("response" in error) {
|
|
3473
|
-
const axiosError = error;
|
|
3474
|
-
logger.error(` Status: ${axiosError.response?.status}`);
|
|
3475
|
-
logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
|
|
3476
|
-
}
|
|
3477
|
-
} else {
|
|
3478
|
-
logger.error(` ${String(error)}`);
|
|
3479
|
-
}
|
|
3561
|
+
logApiErrorDetails(error);
|
|
3480
3562
|
logger.info("");
|
|
3481
3563
|
stats.failed++;
|
|
3482
3564
|
}
|
|
@@ -3576,17 +3658,7 @@ async function deployDatasources(config, client, stateManager) {
|
|
|
3576
3658
|
logger.info("");
|
|
3577
3659
|
} catch (error) {
|
|
3578
3660
|
logger.error(` \u274C Failed to deploy ${datasource.name}:`);
|
|
3579
|
-
|
|
3580
|
-
logger.error(` ${error.message}`);
|
|
3581
|
-
logger.debug(` Stack:`, error.stack);
|
|
3582
|
-
if ("response" in error) {
|
|
3583
|
-
const axiosError = error;
|
|
3584
|
-
logger.error(` Status: ${axiosError.response?.status}`);
|
|
3585
|
-
logger.error(` Data:`, JSON.stringify(axiosError.response?.data, null, 2));
|
|
3586
|
-
}
|
|
3587
|
-
} else {
|
|
3588
|
-
logger.error(` ${String(error)}`);
|
|
3589
|
-
}
|
|
3661
|
+
logApiErrorDetails(error);
|
|
3590
3662
|
logger.info("");
|
|
3591
3663
|
stats.failed++;
|
|
3592
3664
|
}
|
|
@@ -3609,7 +3681,7 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
|
|
|
3609
3681
|
const yamlConfigContent = fs6.readFileSync(yamlPath, "utf8");
|
|
3610
3682
|
const workflowYaml = yaml5.parse(yamlConfigContent);
|
|
3611
3683
|
const referencesToResolve = [];
|
|
3612
|
-
for (const assistant of workflowYaml.assistants) {
|
|
3684
|
+
for (const assistant of workflowYaml.assistants ?? []) {
|
|
3613
3685
|
const isInline = !assistant.assistant_name && assistant.model && assistant.system_prompt;
|
|
3614
3686
|
if (isInline) {
|
|
3615
3687
|
logger.info(` Skipping inline assistant: ${assistant.id}`);
|
|
@@ -3652,44 +3724,17 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
|
|
|
3652
3724
|
const workflowYamlChecksum = calculateChecksum(yamlConfigContent);
|
|
3653
3725
|
const configChecksum = calculateWorkflowConfigChecksum(workflow);
|
|
3654
3726
|
const existingState = stateManager.getWorkflowState(workflow.name);
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
if (hasChanged) {
|
|
3660
|
-
const apiParams = {
|
|
3661
|
-
project: config.project.name,
|
|
3662
|
-
name: workflow.name,
|
|
3663
|
-
description: workflow.description,
|
|
3664
|
-
yaml_config: yamlConfig,
|
|
3665
|
-
mode: workflow.mode,
|
|
3666
|
-
shared: workflow.shared,
|
|
3667
|
-
icon_url: workflow.icon_url
|
|
3668
|
-
};
|
|
3669
|
-
if (process.env.DEBUG_API) {
|
|
3670
|
-
logger.debug("\n=== DEBUG: API Params ===");
|
|
3671
|
-
logger.debug(JSON.stringify(apiParams, null, 2));
|
|
3672
|
-
logger.debug("=========================\n");
|
|
3673
|
-
}
|
|
3674
|
-
logger.info(` Updating workflow (ID: ${existingState.id})...`);
|
|
3675
|
-
await client.workflows.update(existingState.id, apiParams);
|
|
3676
|
-
logger.info(`\u2713 Updated workflow: ${workflow.name} (${existingState.id})`);
|
|
3677
|
-
stateManager.updateWorkflowState(workflow.name, existingState.id, workflowYamlChecksum, configChecksum);
|
|
3678
|
-
stats.updated++;
|
|
3679
|
-
} else {
|
|
3680
|
-
logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
|
|
3681
|
-
stats.unchanged++;
|
|
3682
|
-
}
|
|
3683
|
-
} else {
|
|
3684
|
-
logger.info(` \u26A0\uFE0F Workflow ID from state not found on platform, will create new`);
|
|
3685
|
-
logger.info(` Creating new workflow...`);
|
|
3727
|
+
const existsOnPlatform = existingState ? await checkWorkflowExists(client, workflow.name, stateManager) : false;
|
|
3728
|
+
if (existingState && existsOnPlatform) {
|
|
3729
|
+
const hasChanged = existingState.workflowYamlChecksum !== workflowYamlChecksum || existingState.configChecksum !== configChecksum;
|
|
3730
|
+
if (hasChanged) {
|
|
3686
3731
|
const apiParams = {
|
|
3687
3732
|
project: config.project.name,
|
|
3688
3733
|
name: workflow.name,
|
|
3689
3734
|
description: workflow.description,
|
|
3690
3735
|
yaml_config: yamlConfig,
|
|
3691
|
-
mode: workflow.mode
|
|
3692
|
-
shared: workflow.shared
|
|
3736
|
+
mode: workflow.mode,
|
|
3737
|
+
shared: workflow.shared,
|
|
3693
3738
|
icon_url: workflow.icon_url
|
|
3694
3739
|
};
|
|
3695
3740
|
if (process.env.DEBUG_API) {
|
|
@@ -3697,17 +3742,19 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
|
|
|
3697
3742
|
logger.debug(JSON.stringify(apiParams, null, 2));
|
|
3698
3743
|
logger.debug("=========================\n");
|
|
3699
3744
|
}
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
stats.
|
|
3745
|
+
logger.info(` Updating workflow (ID: ${existingState.id})...`);
|
|
3746
|
+
await client.workflows.update(existingState.id, apiParams);
|
|
3747
|
+
logger.info(`\u2713 Updated workflow: ${workflow.name} (${existingState.id})`);
|
|
3748
|
+
stateManager.updateWorkflowState(workflow.name, existingState.id, workflowYamlChecksum, configChecksum);
|
|
3749
|
+
stats.updated++;
|
|
3750
|
+
} else {
|
|
3751
|
+
logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
|
|
3752
|
+
stats.unchanged++;
|
|
3708
3753
|
}
|
|
3709
3754
|
} else {
|
|
3710
|
-
|
|
3755
|
+
if (existingState) {
|
|
3756
|
+
logger.info(` \u26A0\uFE0F Workflow ID from state not found on platform, looking for a same-name workflow`);
|
|
3757
|
+
}
|
|
3711
3758
|
const apiParams = {
|
|
3712
3759
|
project: config.project.name,
|
|
3713
3760
|
name: workflow.name,
|
|
@@ -3722,29 +3769,28 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
|
|
|
3722
3769
|
logger.debug(JSON.stringify(apiParams, null, 2));
|
|
3723
3770
|
logger.debug("=========================\n");
|
|
3724
3771
|
}
|
|
3725
|
-
const
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3772
|
+
const foundId = await findWorkflowByName(client, workflow.name, config.project.name);
|
|
3773
|
+
if (foundId) {
|
|
3774
|
+
await client.workflows.update(foundId, apiParams);
|
|
3775
|
+
logger.info(`\u2713 Adopted existing workflow ${workflow.name} (${foundId})`);
|
|
3776
|
+
stateManager.updateWorkflowState(workflow.name, foundId, workflowYamlChecksum, configChecksum);
|
|
3777
|
+
stats.updated++;
|
|
3778
|
+
} else {
|
|
3779
|
+
logger.info(` Creating new workflow...`);
|
|
3780
|
+
const result = await client.workflows.create(apiParams);
|
|
3781
|
+
const workflowId = result.data?.id || result.id;
|
|
3782
|
+
if (!workflowId) {
|
|
3783
|
+
throw new Error("Workflow created but ID not returned");
|
|
3784
|
+
}
|
|
3785
|
+
logger.info(`\u2713 Created workflow: ${workflow.name} (${workflowId})`);
|
|
3786
|
+
stateManager.updateWorkflowState(workflow.name, workflowId, workflowYamlChecksum, configChecksum);
|
|
3787
|
+
stats.created++;
|
|
3729
3788
|
}
|
|
3730
|
-
logger.info(`\u2713 Created workflow: ${workflow.name} (${workflowId})`);
|
|
3731
|
-
stateManager.updateWorkflowState(workflow.name, workflowId, workflowYamlChecksum, configChecksum);
|
|
3732
|
-
stats.created++;
|
|
3733
3789
|
}
|
|
3734
3790
|
logger.info("");
|
|
3735
3791
|
} catch (error) {
|
|
3736
3792
|
logger.error(` \u274C Failed to deploy ${workflow.name}:`);
|
|
3737
|
-
|
|
3738
|
-
logger.error(` ${error.message}`);
|
|
3739
|
-
logger.debug(` Stack:`, error.stack);
|
|
3740
|
-
if ("response" in error) {
|
|
3741
|
-
const axiosError = error;
|
|
3742
|
-
logger.error(` Status: ${axiosError.response?.status}`);
|
|
3743
|
-
logger.error(` Data:`, JSON.stringify(axiosError.response?.data, null, 2));
|
|
3744
|
-
}
|
|
3745
|
-
} else {
|
|
3746
|
-
logger.error(` ${String(error)}`);
|
|
3747
|
-
}
|
|
3793
|
+
logApiErrorDetails(error);
|
|
3748
3794
|
logger.info("");
|
|
3749
3795
|
stats.failed++;
|
|
3750
3796
|
}
|
|
@@ -3754,7 +3800,7 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
|
|
|
3754
3800
|
async function deployResources(options) {
|
|
3755
3801
|
logger.info("\u{1F680} Deploying resources to Codemie...\n");
|
|
3756
3802
|
try {
|
|
3757
|
-
const { appConfig, prune = false } = options;
|
|
3803
|
+
const { appConfig, prune = false, skipDatasources = false } = options;
|
|
3758
3804
|
const loader = new CodemieConfigLoader(appConfig);
|
|
3759
3805
|
const stateManager = new StateManager(appConfig);
|
|
3760
3806
|
logger.info("\u{1F4C4} Loading configuration...");
|
|
@@ -3766,7 +3812,12 @@ async function deployResources(options) {
|
|
|
3766
3812
|
logger.info("\u2713 Connected to Codemie API\n");
|
|
3767
3813
|
logger.info("\u{1F9F9} Checking for orphaned resources...");
|
|
3768
3814
|
const cleanupManager = new CleanupManager(client, stateManager);
|
|
3769
|
-
const
|
|
3815
|
+
const foundOrphaned = cleanupManager.findOrphanedResources(config);
|
|
3816
|
+
const orphaned = skipDatasources ? { ...foundOrphaned, datasources: [] } : foundOrphaned;
|
|
3817
|
+
const skippedOrphanedDatasources = foundOrphaned.datasources.length - orphaned.datasources.length;
|
|
3818
|
+
if (skippedOrphanedDatasources > 0) {
|
|
3819
|
+
logger.info(` \u23ED\uFE0F ${skippedOrphanedDatasources} orphaned datasource(s) skipped (--skip-datasources)`);
|
|
3820
|
+
}
|
|
3770
3821
|
const totalOrphaned = orphaned.assistants.length + orphaned.datasources.length + orphaned.workflows.length + orphaned.skills.length;
|
|
3771
3822
|
let deleted = 0;
|
|
3772
3823
|
if (totalOrphaned > 0) {
|
|
@@ -3806,11 +3857,17 @@ async function deployResources(options) {
|
|
|
3806
3857
|
let updated = 0;
|
|
3807
3858
|
let unchanged = 0;
|
|
3808
3859
|
let failed = 0;
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3860
|
+
if (skipDatasources) {
|
|
3861
|
+
logger.info("\u{1F4CA} Processing datasources...\n");
|
|
3862
|
+
logger.info(` \u23ED\uFE0F Skipped (--skip-datasources): ${config.resources.datasources?.length ?? 0} datasource(s)
|
|
3863
|
+
`);
|
|
3864
|
+
} else {
|
|
3865
|
+
const datasourceStats = await deployDatasources(config, client, stateManager);
|
|
3866
|
+
created += datasourceStats.created;
|
|
3867
|
+
updated += datasourceStats.updated;
|
|
3868
|
+
unchanged += datasourceStats.unchanged;
|
|
3869
|
+
failed += datasourceStats.failed;
|
|
3870
|
+
}
|
|
3814
3871
|
const skillStats = await deploySkills(config, client, loader, stateManager);
|
|
3815
3872
|
created += skillStats.created;
|
|
3816
3873
|
updated += skillStats.updated;
|
|
@@ -4613,7 +4670,7 @@ init_logger();
|
|
|
4613
4670
|
init_checksumUtils();
|
|
4614
4671
|
init_codemieConfigChecksums();
|
|
4615
4672
|
init_converters();
|
|
4616
|
-
async function previewResource(resource, resourceType, getState, checkExists, calculateChecksums) {
|
|
4673
|
+
async function previewResource(resource, resourceType, getState, checkExists, calculateChecksums, staleDetails) {
|
|
4617
4674
|
const existingState = getState(resource.name);
|
|
4618
4675
|
if (existingState) {
|
|
4619
4676
|
const existsOnPlatform = await checkExists();
|
|
@@ -4634,7 +4691,7 @@ async function previewResource(resource, resourceType, getState, checkExists, ca
|
|
|
4634
4691
|
type: "create",
|
|
4635
4692
|
resourceType,
|
|
4636
4693
|
name: resource.name,
|
|
4637
|
-
details: `ID from state not found on platform, will create new`
|
|
4694
|
+
details: staleDetails ?? `ID from state not found on platform, will create new`
|
|
4638
4695
|
};
|
|
4639
4696
|
}
|
|
4640
4697
|
} else {
|
|
@@ -4833,8 +4890,10 @@ async function previewDatasources(datasources, stateManager, client) {
|
|
|
4833
4890
|
}
|
|
4834
4891
|
return changes;
|
|
4835
4892
|
}
|
|
4836
|
-
async function previewWorkflows(workflows, stateManager, client, rootDir = process.cwd()) {
|
|
4893
|
+
async function previewWorkflows(workflows, stateManager, client, rootDir = process.cwd(), project) {
|
|
4837
4894
|
const changes = [];
|
|
4895
|
+
const projectClause = project ? ` in project ${project}` : "";
|
|
4896
|
+
const staleDetails = `ID from state not found; will adopt a same-name workflow${projectClause} if one exists, otherwise create`;
|
|
4838
4897
|
for (const workflow of workflows) {
|
|
4839
4898
|
const yamlPath = path6.join(rootDir, workflow.definition);
|
|
4840
4899
|
if (!fs6.existsSync(yamlPath)) {
|
|
@@ -4855,7 +4914,8 @@ async function previewWorkflows(workflows, stateManager, client, rootDir = proce
|
|
|
4855
4914
|
hasChanged,
|
|
4856
4915
|
...hasChanged ? { updateDetails: "Workflow YAML or configuration changed" } : {}
|
|
4857
4916
|
};
|
|
4858
|
-
}
|
|
4917
|
+
},
|
|
4918
|
+
staleDetails
|
|
4859
4919
|
);
|
|
4860
4920
|
if (!change.details && change.type === "create") {
|
|
4861
4921
|
change.details = `Mode: ${workflow.mode}`;
|
|
@@ -4866,14 +4926,16 @@ async function previewWorkflows(workflows, stateManager, client, rootDir = proce
|
|
|
4866
4926
|
}
|
|
4867
4927
|
|
|
4868
4928
|
// src/preview.ts
|
|
4869
|
-
async function previewChanges(appConfig, existingClient) {
|
|
4929
|
+
async function previewChanges(appConfig, existingClient, options) {
|
|
4870
4930
|
const loader = new CodemieConfigLoader(appConfig);
|
|
4871
4931
|
const stateManager = new StateManager(appConfig);
|
|
4872
4932
|
const config = loader.loadConfig();
|
|
4873
4933
|
const client = existingClient || await createClient(config);
|
|
4874
4934
|
const changes = [];
|
|
4875
4935
|
const cleanupManager = new CleanupManager(client, stateManager);
|
|
4876
|
-
const
|
|
4936
|
+
const skipDatasources = options?.skipDatasources === true;
|
|
4937
|
+
const foundOrphaned = cleanupManager.findOrphanedResources(config);
|
|
4938
|
+
const orphaned = skipDatasources ? { ...foundOrphaned, datasources: [] } : foundOrphaned;
|
|
4877
4939
|
changes.push(...previewOrphanedResources(orphaned, stateManager));
|
|
4878
4940
|
if (config.resources.skills) {
|
|
4879
4941
|
const skillChanges = await previewSkills(config.resources.skills, loader, stateManager, client);
|
|
@@ -4883,12 +4945,18 @@ async function previewChanges(appConfig, existingClient) {
|
|
|
4883
4945
|
const assistantChanges = await previewAssistants(config.resources.assistants, loader, stateManager, client, config);
|
|
4884
4946
|
changes.push(...assistantChanges);
|
|
4885
4947
|
}
|
|
4886
|
-
if (config.resources.datasources) {
|
|
4948
|
+
if (config.resources.datasources && !skipDatasources) {
|
|
4887
4949
|
const datasourceChanges = await previewDatasources(config.resources.datasources, stateManager, client);
|
|
4888
4950
|
changes.push(...datasourceChanges);
|
|
4889
4951
|
}
|
|
4890
4952
|
if (config.resources.workflows) {
|
|
4891
|
-
const workflowChanges = await previewWorkflows(
|
|
4953
|
+
const workflowChanges = await previewWorkflows(
|
|
4954
|
+
config.resources.workflows,
|
|
4955
|
+
stateManager,
|
|
4956
|
+
client,
|
|
4957
|
+
appConfig.rootDir,
|
|
4958
|
+
config.project.name
|
|
4959
|
+
);
|
|
4892
4960
|
changes.push(...workflowChanges);
|
|
4893
4961
|
}
|
|
4894
4962
|
const toDelete = changes.filter((c) => c.type === "delete");
|
|
@@ -4927,7 +4995,12 @@ async function main6(options) {
|
|
|
4927
4995
|
}
|
|
4928
4996
|
logger.info("");
|
|
4929
4997
|
const client = await createClient(config);
|
|
4930
|
-
|
|
4998
|
+
if (options.skipDatasources) {
|
|
4999
|
+
logger.info("\u23ED\uFE0F Datasources excluded from preview (--skip-datasources)\n");
|
|
5000
|
+
}
|
|
5001
|
+
const { changes, summary } = await previewChanges(appConfig, client, {
|
|
5002
|
+
skipDatasources: options.skipDatasources
|
|
5003
|
+
});
|
|
4931
5004
|
logPreviewSummary(changes, summary);
|
|
4932
5005
|
process.exit(0);
|
|
4933
5006
|
} catch (error) {
|
|
@@ -5305,10 +5378,13 @@ program.command("init").description("Initialize a new Codemie IaC project").acti
|
|
|
5305
5378
|
const appConfig = loadAppConfig();
|
|
5306
5379
|
void main5({ appConfig });
|
|
5307
5380
|
});
|
|
5308
|
-
program.command("deploy").description("Deploy resources to Codemie platform").option("-p, --prune", "Delete orphaned resources").option("-c, --config <path>", "Configuration file").action(async (options) => {
|
|
5381
|
+
program.command("deploy").description("Deploy resources to Codemie platform").option("-p, --prune", "Delete orphaned resources").option("--skip-datasources", "Skip the datasource stage entirely (no create/update/delete, no indexing)").option("-c, --config <path>", "Configuration file").action(async (options) => {
|
|
5309
5382
|
const configPath = options.config ? path6__default.resolve(options.config) : void 0;
|
|
5310
5383
|
const appConfig = loadAppConfig(configPath);
|
|
5311
|
-
await runCommandWithSpinner(
|
|
5384
|
+
await runCommandWithSpinner(
|
|
5385
|
+
"Deploying Codemie resources",
|
|
5386
|
+
() => main2({ appConfig, prune: options.prune, skipDatasources: options.skipDatasources })
|
|
5387
|
+
);
|
|
5312
5388
|
});
|
|
5313
5389
|
program.command("backup").description("Backup all resources from Codemie platform").option("-c, --config <path>", "Configuration file").action(async (options) => {
|
|
5314
5390
|
const configPath = options.config ? path6__default.resolve(options.config) : void 0;
|
|
@@ -5320,10 +5396,13 @@ program.command("destroy").description("Destroy all IaC-managed resources").opti
|
|
|
5320
5396
|
const appConfig = loadAppConfig(configPath);
|
|
5321
5397
|
await runCommandWithSpinner("Destroying Codemie resources", () => main3({ appConfig, force: options.force }));
|
|
5322
5398
|
});
|
|
5323
|
-
program.command("preview").description("Preview changes without applying them").option("-c, --config <path>", "Configuration file").action(async (options) => {
|
|
5399
|
+
program.command("preview").description("Preview changes without applying them").option("--skip-datasources", "Exclude datasources from preview (mirrors deploy --skip-datasources)").option("-c, --config <path>", "Configuration file").action(async (options) => {
|
|
5324
5400
|
const appConfigPath = options.config ? path6__default.resolve(options.config) : void 0;
|
|
5325
5401
|
const appConfig = loadAppConfig(appConfigPath);
|
|
5326
|
-
await runCommandWithSpinner(
|
|
5402
|
+
await runCommandWithSpinner(
|
|
5403
|
+
"Generating Codemie preview",
|
|
5404
|
+
() => main6({ appConfig, skipDatasources: options.skipDatasources })
|
|
5405
|
+
);
|
|
5327
5406
|
});
|
|
5328
5407
|
program.command("validate").description("Validate configuration").option("-a, --check-api", "Check for API conflicts").option("-c, --config <path>", "Configuration file").action(async (options) => {
|
|
5329
5408
|
const configPath = options.config ? path6__default.resolve(options.config) : void 0;
|
package/dist/index.d.ts
CHANGED
|
@@ -251,7 +251,7 @@ interface SkillState {
|
|
|
251
251
|
configChecksum: string;
|
|
252
252
|
}
|
|
253
253
|
interface WorkflowYaml {
|
|
254
|
-
assistants
|
|
254
|
+
assistants?: WorkflowAssistantReference[];
|
|
255
255
|
states: WorkflowStateDefinition[];
|
|
256
256
|
}
|
|
257
257
|
interface WorkflowAssistantReference {
|
|
@@ -601,6 +601,7 @@ declare function deployWorkflows(config: ReturnType<CodemieConfigLoader['loadCon
|
|
|
601
601
|
declare function deployResources(options: {
|
|
602
602
|
appConfig: AppConfig;
|
|
603
603
|
prune?: boolean;
|
|
604
|
+
skipDatasources?: boolean;
|
|
604
605
|
}): Promise<void>;
|
|
605
606
|
|
|
606
607
|
declare function validateConfig(options: {
|
|
@@ -611,7 +612,9 @@ declare function validateConfig(options: {
|
|
|
611
612
|
errors: string[];
|
|
612
613
|
}>;
|
|
613
614
|
|
|
614
|
-
declare function previewChanges(appConfig: AppConfig, existingClient?: CodeMieClient
|
|
615
|
+
declare function previewChanges(appConfig: AppConfig, existingClient?: CodeMieClient, options?: {
|
|
616
|
+
skipDatasources?: boolean;
|
|
617
|
+
}): Promise<{
|
|
615
618
|
changes: Change[];
|
|
616
619
|
summary: {
|
|
617
620
|
deleted: number;
|
package/dist/index.js
CHANGED
|
@@ -1872,6 +1872,25 @@ var CleanupManager = class {
|
|
|
1872
1872
|
// src/index.ts
|
|
1873
1873
|
init_logger();
|
|
1874
1874
|
|
|
1875
|
+
// src/lib/apiErrorLogger.ts
|
|
1876
|
+
init_logger();
|
|
1877
|
+
function logApiErrorDetails(error) {
|
|
1878
|
+
if (error instanceof Error) {
|
|
1879
|
+
logger.error(` ${error.message}`);
|
|
1880
|
+
logger.debug(` Stack:`, error.stack);
|
|
1881
|
+
if ("statusCode" in error) {
|
|
1882
|
+
logger.error(` Status: ${String(error.statusCode)}`);
|
|
1883
|
+
logger.error(` Data: ${JSON.stringify("response" in error ? error.response : void 0, null, 2)}`);
|
|
1884
|
+
} else if ("response" in error) {
|
|
1885
|
+
const axiosError = error;
|
|
1886
|
+
logger.error(` Status: ${axiosError.response?.status}`);
|
|
1887
|
+
logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
|
|
1888
|
+
}
|
|
1889
|
+
} else {
|
|
1890
|
+
logger.error(` ${String(error)}`);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1875
1894
|
// src/lib/paginationUtils.ts
|
|
1876
1895
|
init_logger();
|
|
1877
1896
|
async function findResourceByName(listFn, name, resourceType, options) {
|
|
@@ -1926,9 +1945,96 @@ async function findAssistantByName(client, name, options) {
|
|
|
1926
1945
|
}
|
|
1927
1946
|
return null;
|
|
1928
1947
|
}
|
|
1929
|
-
async function
|
|
1930
|
-
const
|
|
1931
|
-
|
|
1948
|
+
async function findWorkflowByName(client, name, project) {
|
|
1949
|
+
const perPage = 100;
|
|
1950
|
+
const matches = [];
|
|
1951
|
+
let totalChecked = 0;
|
|
1952
|
+
let page = 0;
|
|
1953
|
+
while (true) {
|
|
1954
|
+
const workflows = await client.workflows.list({ page, per_page: perPage, projects: [project] }) ?? [];
|
|
1955
|
+
totalChecked += workflows.length;
|
|
1956
|
+
for (const workflow of workflows) {
|
|
1957
|
+
if (workflow.name === name && workflow.id) {
|
|
1958
|
+
matches.push(workflow.id);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
if (workflows.length < perPage) {
|
|
1962
|
+
break;
|
|
1963
|
+
}
|
|
1964
|
+
page++;
|
|
1965
|
+
if (page > 1e3) {
|
|
1966
|
+
throw new Error(
|
|
1967
|
+
`Workflow lookup for "${name}" in project "${project}" stopped after 1000 pages (${totalChecked} resources checked); refusing to create a possible duplicate`
|
|
1968
|
+
);
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
if (matches.length > 1) {
|
|
1972
|
+
const ids = [...matches].sort((a, b) => a.localeCompare(b)).join(", ");
|
|
1973
|
+
throw new Error(`Multiple workflows named "${name}" in project "${project}": ${ids}`);
|
|
1974
|
+
}
|
|
1975
|
+
if (matches.length === 0) {
|
|
1976
|
+
logger.warn(
|
|
1977
|
+
` \u26A0\uFE0F workflow "${name}" not found after checking ${totalChecked} resources across ${page + 1} page(s)`
|
|
1978
|
+
);
|
|
1979
|
+
return null;
|
|
1980
|
+
}
|
|
1981
|
+
return matches[0];
|
|
1982
|
+
}
|
|
1983
|
+
var SkillNameIndex = class {
|
|
1984
|
+
constructor(client) {
|
|
1985
|
+
this.client = client;
|
|
1986
|
+
}
|
|
1987
|
+
client;
|
|
1988
|
+
ids = /* @__PURE__ */ new Map();
|
|
1989
|
+
built = false;
|
|
1990
|
+
scanned = { skills: 0, pages: 0 };
|
|
1991
|
+
async find(name) {
|
|
1992
|
+
const known = this.ids.get(name);
|
|
1993
|
+
if (known) {
|
|
1994
|
+
return known;
|
|
1995
|
+
}
|
|
1996
|
+
if (!this.built) {
|
|
1997
|
+
await this.build();
|
|
1998
|
+
const id = this.ids.get(name);
|
|
1999
|
+
if (id) {
|
|
2000
|
+
return id;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
logger.warn(
|
|
2004
|
+
` \u26A0\uFE0F skill "${name}" not found after checking ${this.scanned.skills} resources across ${this.scanned.pages} page(s)`
|
|
2005
|
+
);
|
|
2006
|
+
return null;
|
|
2007
|
+
}
|
|
2008
|
+
record(name, id) {
|
|
2009
|
+
this.ids.set(name, id);
|
|
2010
|
+
}
|
|
2011
|
+
async build() {
|
|
2012
|
+
const perPage = 100;
|
|
2013
|
+
let page = 0;
|
|
2014
|
+
let totalChecked = 0;
|
|
2015
|
+
while (true) {
|
|
2016
|
+
const skills = await this.client.skills.list({ page, per_page: perPage }) ?? [];
|
|
2017
|
+
totalChecked += skills.length;
|
|
2018
|
+
for (const skill of skills) {
|
|
2019
|
+
if (skill.name && skill.id && !this.ids.has(skill.name)) {
|
|
2020
|
+
this.ids.set(skill.name, skill.id);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
if (skills.length < perPage) {
|
|
2024
|
+
break;
|
|
2025
|
+
}
|
|
2026
|
+
page++;
|
|
2027
|
+
if (page > 1e3) {
|
|
2028
|
+
logger.warn(` \u26A0\uFE0F Stopped pagination after 1000 pages (${totalChecked} resources checked)`);
|
|
2029
|
+
break;
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
this.built = true;
|
|
2033
|
+
this.scanned = { skills: totalChecked, pages: page + 1 };
|
|
2034
|
+
}
|
|
2035
|
+
};
|
|
2036
|
+
function findSkillByName(index, name) {
|
|
2037
|
+
return index.find(name);
|
|
1932
2038
|
}
|
|
1933
2039
|
|
|
1934
2040
|
// src/lib/assistantHelpers.ts
|
|
@@ -2309,20 +2415,7 @@ async function deployAssistants(config, client, loader, stateManager) {
|
|
|
2309
2415
|
logger.info("");
|
|
2310
2416
|
} catch (error) {
|
|
2311
2417
|
logger.error(` \u274C Failed to deploy ${assistant.name}:`);
|
|
2312
|
-
|
|
2313
|
-
logger.error(` ${error.message}`);
|
|
2314
|
-
logger.debug(` Stack:`, error.stack);
|
|
2315
|
-
if ("statusCode" in error) {
|
|
2316
|
-
logger.error(` Status: ${String(error.statusCode)}`);
|
|
2317
|
-
logger.error(` Data: ${JSON.stringify("response" in error ? error.response : void 0, null, 2)}`);
|
|
2318
|
-
} else if ("response" in error) {
|
|
2319
|
-
const axiosError = error;
|
|
2320
|
-
logger.error(` Status: ${axiosError.response?.status}`);
|
|
2321
|
-
logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
|
|
2322
|
-
}
|
|
2323
|
-
} else {
|
|
2324
|
-
logger.error(` ${String(error)}`);
|
|
2325
|
-
}
|
|
2418
|
+
logApiErrorDetails(error);
|
|
2326
2419
|
logger.info("");
|
|
2327
2420
|
stats.failed++;
|
|
2328
2421
|
}
|
|
@@ -2335,6 +2428,7 @@ async function deploySkills(config, client, loader, stateManager) {
|
|
|
2335
2428
|
if (!config.resources.skills) {
|
|
2336
2429
|
return stats;
|
|
2337
2430
|
}
|
|
2431
|
+
const skillIndex = new SkillNameIndex(client);
|
|
2338
2432
|
for (const skill of config.resources.skills) {
|
|
2339
2433
|
try {
|
|
2340
2434
|
logger.info(`Processing: ${skill.name}`);
|
|
@@ -2385,6 +2479,7 @@ async function deploySkills(config, client, loader, stateManager) {
|
|
|
2385
2479
|
categories: skill.categories
|
|
2386
2480
|
});
|
|
2387
2481
|
logger.info(`\u2713 Created skill: ${skill.name} (${result.id})`);
|
|
2482
|
+
skillIndex.record(skill.name, result.id);
|
|
2388
2483
|
stateManager.updateSkillState(skill.name, result.id, content, skill);
|
|
2389
2484
|
stats.created++;
|
|
2390
2485
|
}
|
|
@@ -2413,6 +2508,7 @@ async function deploySkills(config, client, loader, stateManager) {
|
|
|
2413
2508
|
});
|
|
2414
2509
|
skillId = result.id;
|
|
2415
2510
|
logger.info(`\u2713 Created skill: ${skill.name} (${skillId})`);
|
|
2511
|
+
skillIndex.record(skill.name, skillId);
|
|
2416
2512
|
stats.created++;
|
|
2417
2513
|
} catch (createError) {
|
|
2418
2514
|
const msg = createError instanceof Error ? createError.message : String(createError);
|
|
@@ -2420,7 +2516,7 @@ async function deploySkills(config, client, loader, stateManager) {
|
|
|
2420
2516
|
throw createError;
|
|
2421
2517
|
}
|
|
2422
2518
|
logger.warn(` \u26A0\uFE0F Skill already exists on platform (no local state). Fetching ID to recover...`);
|
|
2423
|
-
const existingId = await findSkillByName(
|
|
2519
|
+
const existingId = await findSkillByName(skillIndex, skill.name);
|
|
2424
2520
|
if (!existingId) {
|
|
2425
2521
|
throw new Error(`Skill "${skill.name}" reported as existing but could not be found by name.`);
|
|
2426
2522
|
}
|
|
@@ -2440,21 +2536,7 @@ async function deploySkills(config, client, loader, stateManager) {
|
|
|
2440
2536
|
logger.info("");
|
|
2441
2537
|
} catch (error) {
|
|
2442
2538
|
logger.error(` \u274C Failed to deploy ${skill.name}:`);
|
|
2443
|
-
|
|
2444
|
-
logger.error(` ${error.message}`);
|
|
2445
|
-
logger.debug(` Stack:`, error.stack);
|
|
2446
|
-
if ("statusCode" in error) {
|
|
2447
|
-
const apiError = error;
|
|
2448
|
-
logger.error(` Status: ${apiError.statusCode}`);
|
|
2449
|
-
logger.error(` Data: ${JSON.stringify(apiError.response, null, 2)}`);
|
|
2450
|
-
} else if ("response" in error) {
|
|
2451
|
-
const axiosError = error;
|
|
2452
|
-
logger.error(` Status: ${axiosError.response?.status}`);
|
|
2453
|
-
logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
|
|
2454
|
-
}
|
|
2455
|
-
} else {
|
|
2456
|
-
logger.error(` ${String(error)}`);
|
|
2457
|
-
}
|
|
2539
|
+
logApiErrorDetails(error);
|
|
2458
2540
|
logger.info("");
|
|
2459
2541
|
stats.failed++;
|
|
2460
2542
|
}
|
|
@@ -2554,17 +2636,7 @@ async function deployDatasources(config, client, stateManager) {
|
|
|
2554
2636
|
logger.info("");
|
|
2555
2637
|
} catch (error) {
|
|
2556
2638
|
logger.error(` \u274C Failed to deploy ${datasource.name}:`);
|
|
2557
|
-
|
|
2558
|
-
logger.error(` ${error.message}`);
|
|
2559
|
-
logger.debug(` Stack:`, error.stack);
|
|
2560
|
-
if ("response" in error) {
|
|
2561
|
-
const axiosError = error;
|
|
2562
|
-
logger.error(` Status: ${axiosError.response?.status}`);
|
|
2563
|
-
logger.error(` Data:`, JSON.stringify(axiosError.response?.data, null, 2));
|
|
2564
|
-
}
|
|
2565
|
-
} else {
|
|
2566
|
-
logger.error(` ${String(error)}`);
|
|
2567
|
-
}
|
|
2639
|
+
logApiErrorDetails(error);
|
|
2568
2640
|
logger.info("");
|
|
2569
2641
|
stats.failed++;
|
|
2570
2642
|
}
|
|
@@ -2587,7 +2659,7 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
|
|
|
2587
2659
|
const yamlConfigContent = fs.readFileSync(yamlPath, "utf8");
|
|
2588
2660
|
const workflowYaml = yaml6.parse(yamlConfigContent);
|
|
2589
2661
|
const referencesToResolve = [];
|
|
2590
|
-
for (const assistant of workflowYaml.assistants) {
|
|
2662
|
+
for (const assistant of workflowYaml.assistants ?? []) {
|
|
2591
2663
|
const isInline = !assistant.assistant_name && assistant.model && assistant.system_prompt;
|
|
2592
2664
|
if (isInline) {
|
|
2593
2665
|
logger.info(` Skipping inline assistant: ${assistant.id}`);
|
|
@@ -2630,44 +2702,17 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
|
|
|
2630
2702
|
const workflowYamlChecksum = calculateChecksum(yamlConfigContent);
|
|
2631
2703
|
const configChecksum = calculateWorkflowConfigChecksum(workflow);
|
|
2632
2704
|
const existingState = stateManager.getWorkflowState(workflow.name);
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
if (hasChanged) {
|
|
2638
|
-
const apiParams = {
|
|
2639
|
-
project: config.project.name,
|
|
2640
|
-
name: workflow.name,
|
|
2641
|
-
description: workflow.description,
|
|
2642
|
-
yaml_config: yamlConfig,
|
|
2643
|
-
mode: workflow.mode,
|
|
2644
|
-
shared: workflow.shared,
|
|
2645
|
-
icon_url: workflow.icon_url
|
|
2646
|
-
};
|
|
2647
|
-
if (process.env.DEBUG_API) {
|
|
2648
|
-
logger.debug("\n=== DEBUG: API Params ===");
|
|
2649
|
-
logger.debug(JSON.stringify(apiParams, null, 2));
|
|
2650
|
-
logger.debug("=========================\n");
|
|
2651
|
-
}
|
|
2652
|
-
logger.info(` Updating workflow (ID: ${existingState.id})...`);
|
|
2653
|
-
await client.workflows.update(existingState.id, apiParams);
|
|
2654
|
-
logger.info(`\u2713 Updated workflow: ${workflow.name} (${existingState.id})`);
|
|
2655
|
-
stateManager.updateWorkflowState(workflow.name, existingState.id, workflowYamlChecksum, configChecksum);
|
|
2656
|
-
stats.updated++;
|
|
2657
|
-
} else {
|
|
2658
|
-
logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
|
|
2659
|
-
stats.unchanged++;
|
|
2660
|
-
}
|
|
2661
|
-
} else {
|
|
2662
|
-
logger.info(` \u26A0\uFE0F Workflow ID from state not found on platform, will create new`);
|
|
2663
|
-
logger.info(` Creating new workflow...`);
|
|
2705
|
+
const existsOnPlatform = existingState ? await checkWorkflowExists(client, workflow.name, stateManager) : false;
|
|
2706
|
+
if (existingState && existsOnPlatform) {
|
|
2707
|
+
const hasChanged = existingState.workflowYamlChecksum !== workflowYamlChecksum || existingState.configChecksum !== configChecksum;
|
|
2708
|
+
if (hasChanged) {
|
|
2664
2709
|
const apiParams = {
|
|
2665
2710
|
project: config.project.name,
|
|
2666
2711
|
name: workflow.name,
|
|
2667
2712
|
description: workflow.description,
|
|
2668
2713
|
yaml_config: yamlConfig,
|
|
2669
|
-
mode: workflow.mode
|
|
2670
|
-
shared: workflow.shared
|
|
2714
|
+
mode: workflow.mode,
|
|
2715
|
+
shared: workflow.shared,
|
|
2671
2716
|
icon_url: workflow.icon_url
|
|
2672
2717
|
};
|
|
2673
2718
|
if (process.env.DEBUG_API) {
|
|
@@ -2675,17 +2720,19 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
|
|
|
2675
2720
|
logger.debug(JSON.stringify(apiParams, null, 2));
|
|
2676
2721
|
logger.debug("=========================\n");
|
|
2677
2722
|
}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
stats.
|
|
2723
|
+
logger.info(` Updating workflow (ID: ${existingState.id})...`);
|
|
2724
|
+
await client.workflows.update(existingState.id, apiParams);
|
|
2725
|
+
logger.info(`\u2713 Updated workflow: ${workflow.name} (${existingState.id})`);
|
|
2726
|
+
stateManager.updateWorkflowState(workflow.name, existingState.id, workflowYamlChecksum, configChecksum);
|
|
2727
|
+
stats.updated++;
|
|
2728
|
+
} else {
|
|
2729
|
+
logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
|
|
2730
|
+
stats.unchanged++;
|
|
2686
2731
|
}
|
|
2687
2732
|
} else {
|
|
2688
|
-
|
|
2733
|
+
if (existingState) {
|
|
2734
|
+
logger.info(` \u26A0\uFE0F Workflow ID from state not found on platform, looking for a same-name workflow`);
|
|
2735
|
+
}
|
|
2689
2736
|
const apiParams = {
|
|
2690
2737
|
project: config.project.name,
|
|
2691
2738
|
name: workflow.name,
|
|
@@ -2700,29 +2747,28 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
|
|
|
2700
2747
|
logger.debug(JSON.stringify(apiParams, null, 2));
|
|
2701
2748
|
logger.debug("=========================\n");
|
|
2702
2749
|
}
|
|
2703
|
-
const
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2750
|
+
const foundId = await findWorkflowByName(client, workflow.name, config.project.name);
|
|
2751
|
+
if (foundId) {
|
|
2752
|
+
await client.workflows.update(foundId, apiParams);
|
|
2753
|
+
logger.info(`\u2713 Adopted existing workflow ${workflow.name} (${foundId})`);
|
|
2754
|
+
stateManager.updateWorkflowState(workflow.name, foundId, workflowYamlChecksum, configChecksum);
|
|
2755
|
+
stats.updated++;
|
|
2756
|
+
} else {
|
|
2757
|
+
logger.info(` Creating new workflow...`);
|
|
2758
|
+
const result = await client.workflows.create(apiParams);
|
|
2759
|
+
const workflowId = result.data?.id || result.id;
|
|
2760
|
+
if (!workflowId) {
|
|
2761
|
+
throw new Error("Workflow created but ID not returned");
|
|
2762
|
+
}
|
|
2763
|
+
logger.info(`\u2713 Created workflow: ${workflow.name} (${workflowId})`);
|
|
2764
|
+
stateManager.updateWorkflowState(workflow.name, workflowId, workflowYamlChecksum, configChecksum);
|
|
2765
|
+
stats.created++;
|
|
2707
2766
|
}
|
|
2708
|
-
logger.info(`\u2713 Created workflow: ${workflow.name} (${workflowId})`);
|
|
2709
|
-
stateManager.updateWorkflowState(workflow.name, workflowId, workflowYamlChecksum, configChecksum);
|
|
2710
|
-
stats.created++;
|
|
2711
2767
|
}
|
|
2712
2768
|
logger.info("");
|
|
2713
2769
|
} catch (error) {
|
|
2714
2770
|
logger.error(` \u274C Failed to deploy ${workflow.name}:`);
|
|
2715
|
-
|
|
2716
|
-
logger.error(` ${error.message}`);
|
|
2717
|
-
logger.debug(` Stack:`, error.stack);
|
|
2718
|
-
if ("response" in error) {
|
|
2719
|
-
const axiosError = error;
|
|
2720
|
-
logger.error(` Status: ${axiosError.response?.status}`);
|
|
2721
|
-
logger.error(` Data:`, JSON.stringify(axiosError.response?.data, null, 2));
|
|
2722
|
-
}
|
|
2723
|
-
} else {
|
|
2724
|
-
logger.error(` ${String(error)}`);
|
|
2725
|
-
}
|
|
2771
|
+
logApiErrorDetails(error);
|
|
2726
2772
|
logger.info("");
|
|
2727
2773
|
stats.failed++;
|
|
2728
2774
|
}
|
|
@@ -2732,7 +2778,7 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
|
|
|
2732
2778
|
async function deployResources(options) {
|
|
2733
2779
|
logger.info("\u{1F680} Deploying resources to Codemie...\n");
|
|
2734
2780
|
try {
|
|
2735
|
-
const { appConfig, prune = false } = options;
|
|
2781
|
+
const { appConfig, prune = false, skipDatasources = false } = options;
|
|
2736
2782
|
const loader = new CodemieConfigLoader(appConfig);
|
|
2737
2783
|
const stateManager = new StateManager(appConfig);
|
|
2738
2784
|
logger.info("\u{1F4C4} Loading configuration...");
|
|
@@ -2744,7 +2790,12 @@ async function deployResources(options) {
|
|
|
2744
2790
|
logger.info("\u2713 Connected to Codemie API\n");
|
|
2745
2791
|
logger.info("\u{1F9F9} Checking for orphaned resources...");
|
|
2746
2792
|
const cleanupManager = new CleanupManager(client, stateManager);
|
|
2747
|
-
const
|
|
2793
|
+
const foundOrphaned = cleanupManager.findOrphanedResources(config);
|
|
2794
|
+
const orphaned = skipDatasources ? { ...foundOrphaned, datasources: [] } : foundOrphaned;
|
|
2795
|
+
const skippedOrphanedDatasources = foundOrphaned.datasources.length - orphaned.datasources.length;
|
|
2796
|
+
if (skippedOrphanedDatasources > 0) {
|
|
2797
|
+
logger.info(` \u23ED\uFE0F ${skippedOrphanedDatasources} orphaned datasource(s) skipped (--skip-datasources)`);
|
|
2798
|
+
}
|
|
2748
2799
|
const totalOrphaned = orphaned.assistants.length + orphaned.datasources.length + orphaned.workflows.length + orphaned.skills.length;
|
|
2749
2800
|
let deleted = 0;
|
|
2750
2801
|
if (totalOrphaned > 0) {
|
|
@@ -2784,11 +2835,17 @@ async function deployResources(options) {
|
|
|
2784
2835
|
let updated = 0;
|
|
2785
2836
|
let unchanged = 0;
|
|
2786
2837
|
let failed = 0;
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2838
|
+
if (skipDatasources) {
|
|
2839
|
+
logger.info("\u{1F4CA} Processing datasources...\n");
|
|
2840
|
+
logger.info(` \u23ED\uFE0F Skipped (--skip-datasources): ${config.resources.datasources?.length ?? 0} datasource(s)
|
|
2841
|
+
`);
|
|
2842
|
+
} else {
|
|
2843
|
+
const datasourceStats = await deployDatasources(config, client, stateManager);
|
|
2844
|
+
created += datasourceStats.created;
|
|
2845
|
+
updated += datasourceStats.updated;
|
|
2846
|
+
unchanged += datasourceStats.unchanged;
|
|
2847
|
+
failed += datasourceStats.failed;
|
|
2848
|
+
}
|
|
2792
2849
|
const skillStats = await deploySkills(config, client, loader, stateManager);
|
|
2793
2850
|
created += skillStats.created;
|
|
2794
2851
|
updated += skillStats.updated;
|
|
@@ -3113,7 +3170,7 @@ init_logger();
|
|
|
3113
3170
|
init_checksumUtils();
|
|
3114
3171
|
init_codemieConfigChecksums();
|
|
3115
3172
|
init_converters();
|
|
3116
|
-
async function previewResource(resource, resourceType, getState, checkExists, calculateChecksums) {
|
|
3173
|
+
async function previewResource(resource, resourceType, getState, checkExists, calculateChecksums, staleDetails) {
|
|
3117
3174
|
const existingState = getState(resource.name);
|
|
3118
3175
|
if (existingState) {
|
|
3119
3176
|
const existsOnPlatform = await checkExists();
|
|
@@ -3134,7 +3191,7 @@ async function previewResource(resource, resourceType, getState, checkExists, ca
|
|
|
3134
3191
|
type: "create",
|
|
3135
3192
|
resourceType,
|
|
3136
3193
|
name: resource.name,
|
|
3137
|
-
details: `ID from state not found on platform, will create new`
|
|
3194
|
+
details: staleDetails ?? `ID from state not found on platform, will create new`
|
|
3138
3195
|
};
|
|
3139
3196
|
}
|
|
3140
3197
|
} else {
|
|
@@ -3333,8 +3390,10 @@ async function previewDatasources(datasources, stateManager, client) {
|
|
|
3333
3390
|
}
|
|
3334
3391
|
return changes;
|
|
3335
3392
|
}
|
|
3336
|
-
async function previewWorkflows(workflows, stateManager, client, rootDir = process.cwd()) {
|
|
3393
|
+
async function previewWorkflows(workflows, stateManager, client, rootDir = process.cwd(), project) {
|
|
3337
3394
|
const changes = [];
|
|
3395
|
+
const projectClause = project ? ` in project ${project}` : "";
|
|
3396
|
+
const staleDetails = `ID from state not found; will adopt a same-name workflow${projectClause} if one exists, otherwise create`;
|
|
3338
3397
|
for (const workflow of workflows) {
|
|
3339
3398
|
const yamlPath = path.join(rootDir, workflow.definition);
|
|
3340
3399
|
if (!fs.existsSync(yamlPath)) {
|
|
@@ -3355,7 +3414,8 @@ async function previewWorkflows(workflows, stateManager, client, rootDir = proce
|
|
|
3355
3414
|
hasChanged,
|
|
3356
3415
|
...hasChanged ? { updateDetails: "Workflow YAML or configuration changed" } : {}
|
|
3357
3416
|
};
|
|
3358
|
-
}
|
|
3417
|
+
},
|
|
3418
|
+
staleDetails
|
|
3359
3419
|
);
|
|
3360
3420
|
if (!change.details && change.type === "create") {
|
|
3361
3421
|
change.details = `Mode: ${workflow.mode}`;
|
|
@@ -3366,14 +3426,16 @@ async function previewWorkflows(workflows, stateManager, client, rootDir = proce
|
|
|
3366
3426
|
}
|
|
3367
3427
|
|
|
3368
3428
|
// src/preview.ts
|
|
3369
|
-
async function previewChanges(appConfig, existingClient) {
|
|
3429
|
+
async function previewChanges(appConfig, existingClient, options) {
|
|
3370
3430
|
const loader = new CodemieConfigLoader(appConfig);
|
|
3371
3431
|
const stateManager = new StateManager(appConfig);
|
|
3372
3432
|
const config = loader.loadConfig();
|
|
3373
3433
|
const client = existingClient || await createClient(config);
|
|
3374
3434
|
const changes = [];
|
|
3375
3435
|
const cleanupManager = new CleanupManager(client, stateManager);
|
|
3376
|
-
const
|
|
3436
|
+
const skipDatasources = options?.skipDatasources === true;
|
|
3437
|
+
const foundOrphaned = cleanupManager.findOrphanedResources(config);
|
|
3438
|
+
const orphaned = skipDatasources ? { ...foundOrphaned, datasources: [] } : foundOrphaned;
|
|
3377
3439
|
changes.push(...previewOrphanedResources(orphaned, stateManager));
|
|
3378
3440
|
if (config.resources.skills) {
|
|
3379
3441
|
const skillChanges = await previewSkills(config.resources.skills, loader, stateManager, client);
|
|
@@ -3383,12 +3445,18 @@ async function previewChanges(appConfig, existingClient) {
|
|
|
3383
3445
|
const assistantChanges = await previewAssistants(config.resources.assistants, loader, stateManager, client, config);
|
|
3384
3446
|
changes.push(...assistantChanges);
|
|
3385
3447
|
}
|
|
3386
|
-
if (config.resources.datasources) {
|
|
3448
|
+
if (config.resources.datasources && !skipDatasources) {
|
|
3387
3449
|
const datasourceChanges = await previewDatasources(config.resources.datasources, stateManager, client);
|
|
3388
3450
|
changes.push(...datasourceChanges);
|
|
3389
3451
|
}
|
|
3390
3452
|
if (config.resources.workflows) {
|
|
3391
|
-
const workflowChanges = await previewWorkflows(
|
|
3453
|
+
const workflowChanges = await previewWorkflows(
|
|
3454
|
+
config.resources.workflows,
|
|
3455
|
+
stateManager,
|
|
3456
|
+
client,
|
|
3457
|
+
appConfig.rootDir,
|
|
3458
|
+
config.project.name
|
|
3459
|
+
);
|
|
3392
3460
|
changes.push(...workflowChanges);
|
|
3393
3461
|
}
|
|
3394
3462
|
const toDelete = changes.filter((c) => c.type === "delete");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codemieai/cdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.632",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Infrastructure as Code solution for managing Codemie AI assistants, datasources, and workflows through declarative YAML configuration",
|
|
6
6
|
"main": "./dist/index.js",
|