@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 CHANGED
@@ -169,6 +169,7 @@ codemie --help
169
169
  # Deploy resources
170
170
  codemie deploy
171
171
  codemie deploy --prune
172
+ codemie deploy --skip-datasources
172
173
 
173
174
  # Other commands
174
175
  codemie backup
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.630"};
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 findSkillByName(client, name) {
2500
- const skill = await findResourceByName((params) => client.skills.list(params), name, "skill");
2501
- return skill?.id || null;
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
- if (error instanceof Error) {
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(client, skill.name);
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
- if (error instanceof Error) {
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
- if (error instanceof Error) {
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
- if (existingState) {
3656
- const existsOnPlatform = await checkWorkflowExists(client, workflow.name, stateManager);
3657
- if (existsOnPlatform) {
3658
- const hasChanged = existingState.workflowYamlChecksum !== workflowYamlChecksum || existingState.configChecksum !== configChecksum;
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 || "Sequential",
3692
- shared: workflow.shared ?? true,
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
- const result = await client.workflows.create(apiParams);
3701
- const workflowId = result.data?.id || result.id;
3702
- if (!workflowId) {
3703
- throw new Error("Workflow created but ID not returned");
3704
- }
3705
- logger.info(`\u2713 Created workflow: ${workflow.name} (${workflowId})`);
3706
- stateManager.updateWorkflowState(workflow.name, workflowId, workflowYamlChecksum, configChecksum);
3707
- stats.created++;
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
- logger.info(` Creating new workflow...`);
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 result = await client.workflows.create(apiParams);
3726
- const workflowId = result.data?.id || result.id;
3727
- if (!workflowId) {
3728
- throw new Error("Workflow created but ID not returned");
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
- if (error instanceof Error) {
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 orphaned = cleanupManager.findOrphanedResources(config);
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
- const datasourceStats = await deployDatasources(config, client, stateManager);
3810
- created += datasourceStats.created;
3811
- updated += datasourceStats.updated;
3812
- unchanged += datasourceStats.unchanged;
3813
- failed += datasourceStats.failed;
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 orphaned = cleanupManager.findOrphanedResources(config);
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(config.resources.workflows, stateManager, client, appConfig.rootDir);
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
- const { changes, summary } = await previewChanges(appConfig, client);
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("Deploying Codemie resources", () => main2({ appConfig, prune: options.prune }));
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("Generating Codemie preview", () => main6({ appConfig }));
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: WorkflowAssistantReference[];
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): Promise<{
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 findSkillByName(client, name) {
1930
- const skill = await findResourceByName((params) => client.skills.list(params), name, "skill");
1931
- return skill?.id || null;
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
- if (error instanceof Error) {
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(client, skill.name);
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
- if (error instanceof Error) {
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
- if (error instanceof Error) {
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
- if (existingState) {
2634
- const existsOnPlatform = await checkWorkflowExists(client, workflow.name, stateManager);
2635
- if (existsOnPlatform) {
2636
- const hasChanged = existingState.workflowYamlChecksum !== workflowYamlChecksum || existingState.configChecksum !== configChecksum;
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 || "Sequential",
2670
- shared: workflow.shared ?? true,
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
- const result = await client.workflows.create(apiParams);
2679
- const workflowId = result.data?.id || result.id;
2680
- if (!workflowId) {
2681
- throw new Error("Workflow created but ID not returned");
2682
- }
2683
- logger.info(`\u2713 Created workflow: ${workflow.name} (${workflowId})`);
2684
- stateManager.updateWorkflowState(workflow.name, workflowId, workflowYamlChecksum, configChecksum);
2685
- stats.created++;
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
- logger.info(` Creating new workflow...`);
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 result = await client.workflows.create(apiParams);
2704
- const workflowId = result.data?.id || result.id;
2705
- if (!workflowId) {
2706
- throw new Error("Workflow created but ID not returned");
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
- if (error instanceof Error) {
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 orphaned = cleanupManager.findOrphanedResources(config);
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
- const datasourceStats = await deployDatasources(config, client, stateManager);
2788
- created += datasourceStats.created;
2789
- updated += datasourceStats.updated;
2790
- unchanged += datasourceStats.unchanged;
2791
- failed += datasourceStats.failed;
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 orphaned = cleanupManager.findOrphanedResources(config);
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(config.resources.workflows, stateManager, client, appConfig.rootDir);
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.630",
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",