@eide/foir-cli 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2955,7 +2955,7 @@ function createOperationsMethods(client) {
2955
2955
  allowedRoles: params.allowedRoles ?? [],
2956
2956
  precondition: params.precondition,
2957
2957
  configId: params.configId,
2958
- supportsAsyncCallback: params.supportsAsyncCallback,
2958
+ supportsAsync: params.supportsAsync,
2959
2959
  callbackTtlSeconds: params.callbackTtlSeconds
2960
2960
  })
2961
2961
  );
@@ -2976,7 +2976,7 @@ function createOperationsMethods(client) {
2976
2976
  callbackTimeoutRetryPolicy: params.callbackTimeoutRetryPolicy,
2977
2977
  precondition: params.precondition,
2978
2978
  isActive: params.isActive,
2979
- supportsAsyncCallback: params.supportsAsyncCallback,
2979
+ supportsAsync: params.supportsAsync,
2980
2980
  callbackTtlSeconds: params.callbackTtlSeconds
2981
2981
  })
2982
2982
  );
@@ -4880,7 +4880,7 @@ import { resolve as resolve4 } from "path";
4880
4880
  function zeroCounts() {
4881
4881
  return { created: 0, updated: 0, deleted: 0 };
4882
4882
  }
4883
- async function reconcileConfig(client, configId, manifest) {
4883
+ async function reconcileConfig(client, configId, manifest, options = {}) {
4884
4884
  const summary = {
4885
4885
  models: zeroCounts(),
4886
4886
  operations: zeroCounts(),
@@ -4901,7 +4901,13 @@ async function reconcileConfig(client, configId, manifest) {
4901
4901
  await reconcileAuthProviders(client, manifest.authProviders ?? [], summary);
4902
4902
  await reconcilePlacements(client, configId, manifest.placements ?? [], summary);
4903
4903
  await reconcileProfileSchema(client, manifest, summary);
4904
- await reconcileApiKeys(client, manifest.key, manifest.apiKeys ?? [], summary);
4904
+ await reconcileApiKeys(
4905
+ client,
4906
+ manifest.key,
4907
+ manifest.apiKeys ?? [],
4908
+ summary,
4909
+ options.rotateKeys ?? false
4910
+ );
4905
4911
  return summary;
4906
4912
  }
4907
4913
  async function reconcileModels(client, configId, models, summary) {
@@ -4973,8 +4979,8 @@ async function reconcileOperations(client, configId, operations, operationBaseUr
4973
4979
  }
4974
4980
  const ex = existingByKey.get(op.key);
4975
4981
  const endpoint = resolveEndpoint(op.endpoint, operationBaseUrl);
4976
- const supportsAsyncCallback = op.mode === "async";
4977
- if (supportsAsyncCallback && op.timeoutMs && op.timeoutMs > 1e4) {
4982
+ const supportsAsync = op.mode === "async";
4983
+ if (supportsAsync && op.timeoutMs && op.timeoutMs > 1e4) {
4978
4984
  console.warn(
4979
4985
  `\u26A0 operation "${op.key}": mode=async but timeoutMs=${op.timeoutMs} \u2014 ack should return in <10s; long timeouts mask slow extensions`
4980
4986
  );
@@ -4994,7 +5000,7 @@ async function reconcileOperations(client, configId, operations, operationBaseUr
4994
5000
  callbackTimeoutRetryPolicy: op.callbackTimeoutRetryPolicy ?? empty,
4995
5001
  precondition: op.precondition ?? empty,
4996
5002
  isActive: op.isActive,
4997
- supportsAsyncCallback,
5003
+ supportsAsync,
4998
5004
  callbackTtlSeconds: op.callbackTtlSeconds
4999
5005
  });
5000
5006
  summary.operations.updated++;
@@ -5015,7 +5021,7 @@ async function reconcileOperations(client, configId, operations, operationBaseUr
5015
5021
  allowedRoles: op.allowedRoles,
5016
5022
  precondition: op.precondition,
5017
5023
  configId,
5018
- supportsAsyncCallback,
5024
+ supportsAsync,
5019
5025
  callbackTtlSeconds: op.callbackTtlSeconds
5020
5026
  });
5021
5027
  summary.operations.created++;
@@ -5242,7 +5248,7 @@ async function reconcileProfileSchema(client, manifest, summary) {
5242
5248
  });
5243
5249
  summary.profileSchemaUpdated = true;
5244
5250
  }
5245
- async function reconcileApiKeys(client, configKey, apiKeys, summary) {
5251
+ async function reconcileApiKeys(client, configKey, apiKeys, summary, rotateKeys) {
5246
5252
  if (apiKeys.length === 0) return;
5247
5253
  const existing = await client.identity.listApiKeys({ limit: 200 });
5248
5254
  const existingByName = new Map(
@@ -5264,8 +5270,10 @@ async function reconcileApiKeys(client, configKey, apiKeys, summary) {
5264
5270
  updateScopes: true
5265
5271
  });
5266
5272
  }
5267
- const rotated = await client.identity.rotateApiKey(existingKey.id);
5268
- rawKey = rotated?.apiKey?.rawKey;
5273
+ if (rotateKeys) {
5274
+ const rotated = await client.identity.rotateApiKey(existingKey.id);
5275
+ rawKey = rotated?.apiKey?.rawKey;
5276
+ }
5269
5277
  } else {
5270
5278
  const result = await client.identity.createApiKey({
5271
5279
  name: key.name,
@@ -5603,23 +5611,6 @@ function syncEnvVar(envPath, key, value) {
5603
5611
  `, "utf-8");
5604
5612
  return "written";
5605
5613
  }
5606
- function writeEnvVar(envPath, key, value) {
5607
- let content = "";
5608
- if (existsSync4(envPath)) {
5609
- content = readFileSync(envPath, "utf-8");
5610
- const regex = new RegExp(`^${key}=`, "m");
5611
- if (regex.test(content)) {
5612
- return false;
5613
- }
5614
- }
5615
- if (content && !content.endsWith("\n")) {
5616
- content += "\n";
5617
- }
5618
- content += `${key}=${value}
5619
- `;
5620
- writeFileSync2(envPath, content, "utf-8");
5621
- return true;
5622
- }
5623
5614
  function printSummary(summary) {
5624
5615
  const lines = [];
5625
5616
  const fmt = (label, c) => {
@@ -5645,7 +5636,11 @@ function printSummary(summary) {
5645
5636
  }
5646
5637
  }
5647
5638
  function registerPushCommand(program2, globalOpts) {
5648
- program2.command("push").description("Push foir.config.ts to the platform").option("--config <path>", "Path to config file (default: auto-discover)").option("--force", "Force reinstall (delete and recreate)", false).option("--env <path>", "Path to .env file (default: .env)").action(
5639
+ program2.command("push").description("Push foir.config.ts to the platform").option("--config <path>", "Path to config file (default: auto-discover)").option("--force", "Force reinstall (delete and recreate)", false).option(
5640
+ "--rotate-keys",
5641
+ "Rotate existing API keys and rewrite their values in .env",
5642
+ false
5643
+ ).option("--env <path>", "Path to .env file (default: .env)").action(
5649
5644
  withErrorHandler(
5650
5645
  globalOpts,
5651
5646
  async (opts) => {
@@ -5685,7 +5680,9 @@ function registerPushCommand(program2, globalOpts) {
5685
5680
  }
5686
5681
  const configId = applyResult.id;
5687
5682
  console.log(chalk6.dim("Reconciling resources..."));
5688
- const summary = await reconcileConfig(client, configId, config2);
5683
+ const summary = await reconcileConfig(client, configId, config2, {
5684
+ rotateKeys: opts.rotateKeys ?? false
5685
+ });
5689
5686
  console.log();
5690
5687
  console.log(chalk6.green("\u2713 Config applied successfully"));
5691
5688
  console.log(` Config ID: ${chalk6.cyan(configId)}`);
@@ -5724,14 +5721,18 @@ function registerPushCommand(program2, globalOpts) {
5724
5721
  if (envWrites.length > 0) {
5725
5722
  console.log();
5726
5723
  for (const { key, value, label } of envWrites) {
5727
- const written = writeEnvVar(envPath, key, value);
5728
- if (written) {
5724
+ const result = syncEnvVar(envPath, key, value);
5725
+ if (result === "unchanged") {
5729
5726
  console.log(
5730
- chalk6.green(`\u2713 ${label}`) + chalk6.dim(` \u2192 ${key} written to ${envPath}`)
5727
+ chalk6.dim(` ${label}: ${key} already up to date in ${envPath}, skipped`)
5728
+ );
5729
+ } else if (result === "replaced") {
5730
+ console.log(
5731
+ chalk6.yellow(`\u27F3 ${label}`) + chalk6.dim(` \u2192 ${key} updated in ${envPath}`)
5731
5732
  );
5732
5733
  } else {
5733
5734
  console.log(
5734
- chalk6.dim(` ${label}: ${key} already exists in ${envPath}, skipped`)
5735
+ chalk6.green(`\u2713 ${label}`) + chalk6.dim(` \u2192 ${key} written to ${envPath}`)
5735
5736
  );
5736
5737
  }
5737
5738
  }
@@ -112,7 +112,7 @@ interface ApplyConfigOperationInput {
112
112
  * in 2026-04 — every ASYNC dispatch now carries a scoped callback token
113
113
  * and the platform tracks the execution to terminal state.
114
114
  *
115
- * Default: `sync`. The CLI maps this to `supportsAsyncCallback` on the
115
+ * Default: `sync`. The CLI maps this to `supportsAsync` on the
116
116
  * platform Operation row — the operation can still be dispatched in SYNC
117
117
  * mode at runtime when callers want a blocking call.
118
118
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eide/foir-cli",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Universal platform CLI for Foir platform",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -50,7 +50,7 @@
50
50
  "@bufbuild/protovalidate": "^1.1.1",
51
51
  "@connectrpc/connect": "^2.0.0",
52
52
  "@connectrpc/connect-node": "^2.0.0",
53
- "@eide/foir-proto-ts": "^0.16.0",
53
+ "@eide/foir-proto-ts": "^0.17.0",
54
54
  "chalk": "^5.3.0",
55
55
  "commander": "^12.1.0",
56
56
  "dotenv": "^16.4.5",