@fluid-app/fluid-cli-localization 0.1.410 → 0.1.412

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,17 @@ to and whether enrollment asks for it (`collected`, `inDraft`, `missing` or
169
169
  whole save and the command says so. `--skip-sensitive` creates the rest and
170
170
  lists the ones it left out.
171
171
 
172
+ ## `fluid markets` ordering and currencies
173
+
174
+ | Command | What it does |
175
+ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
176
+ | `reorder <market...>` | Sets the full market order (ids or names, every market once). The first becomes the fallback. `--preview` writes nothing; `--yes` skips the prompt. |
177
+ | `set-fallback <market>` | Makes a market the fallback. Refused, with the countries listed, while the fallback prices countries that have no market. `--preview` writes nothing; `--yes`. |
178
+ | `currency add <market> <currency>` | Adds a currency to a market. Adding one it already has is a no-op. |
179
+ | `currency remove <market> <currency>` | Removes a currency from a market. Refused with the counts while it is billed, priced, or the market's last. `--yes` skips the prompt. |
180
+
181
+ Every API refusal prints its code and details. Needs the market-config release.
182
+
172
183
  ## `fluid translations`
173
184
 
174
185
  | Command | What it does |
package/dist/index.d.mts CHANGED
@@ -6,7 +6,7 @@ declare function registerCountriesCommands(parent: Command, ctx: CommandContext)
6
6
  //#endregion
7
7
  //#region src/markets/commands.d.ts
8
8
  /**
9
- * Register `fluid markets list|add-country|remove-country|rename|create|move|delete` on `parent`.
9
+ * Register `fluid markets list|add-country|remove-country|rename|create|move|delete|reorder|set-fallback|currency add|currency remove` on `parent`.
10
10
  *
11
11
  * @param parent - the `markets` command group
12
12
  * @param ctx - the CLI command context
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/countries/commands.ts","../src/markets/commands.ts","../src/translations/commands.ts","../src/index.ts"],"mappings":";;;;iBAiMgB,yBAAA,CACd,MAAA,EAAQ,OAAA,EACR,GAAA,EAAK,cAAA;;;;;AAFP;;;;iBC9HgB,uBAAA,CACd,MAAA,EAAQ,OAAA,EACR,GAAA,EAAK,cAAA;;;iBC+BS,4BAAA,CACd,MAAA,EAAQ,OAAA,EACR,GAAA,EAAK,cAAA;;;AF2FP;;;;;;AAAA,cGhLM,MAAA,EAAQ,WAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/countries/commands.ts","../src/markets/commands.ts","../src/translations/commands.ts","../src/index.ts"],"mappings":";;;;iBAiMgB,yBAAA,CACd,MAAA,EAAQ,OAAA,EACR,GAAA,EAAK,cAAA;;;;;AAFP;;;;iBCnHgB,uBAAA,CACd,MAAA,EAAQ,OAAA,EACR,GAAA,EAAK,cAAA;;;iBCoBS,4BAAA,CACd,MAAA,EAAQ,OAAA,EACR,GAAA,EAAK,cAAA;;;AF2FP;;;;;;AAAA,cGhLM,MAAA,EAAQ,WAAA"}
package/dist/index.mjs CHANGED
@@ -4162,10 +4162,39 @@ function registerCountriesCommands(parent, ctx) {
4162
4162
  //#region src/markets/api.ts
4163
4163
  const MARKETS_PATH = "/api/company/v2026-10/markets";
4164
4164
  const MARKET_CONFIG_DISABLED_MESSAGE = "Markets configuration is not enabled for this company.";
4165
+ function listed(value) {
4166
+ if (Array.isArray(value)) return value.map(listed).join(", ");
4167
+ if (value && typeof value === "object") return Object.entries(value).map(([k, v]) => `${k} ${listed(v)}`).join(", ");
4168
+ return String(value);
4169
+ }
4170
+ function summaryOf(code, details) {
4171
+ const has = (key) => details[key] !== void 0;
4172
+ switch (code) {
4173
+ case "fallback_would_unprice": return has("isos") ? `would leave ${listed(details["isos"])} unpriced` : void 0;
4174
+ case "currency_billed": return has("countries") ? `billed in ${listed(details["countries"])}` : void 0;
4175
+ case "currency_has_prices": {
4176
+ const parts = [
4177
+ ["prices per tier", "prices_per_tier"],
4178
+ ["bundle prices", "bundle_prices"],
4179
+ ["adjusted price lists", "adjusted_price_lists"]
4180
+ ].flatMap(([label, key]) => {
4181
+ const value = details[key];
4182
+ return !value || Array.isArray(value) && value.length === 0 || typeof value === "object" && Object.keys(value).length === 0 ? [] : [`${label}: ${listed(value)}`];
4183
+ });
4184
+ return parts.length > 0 ? parts.join("; ") : void 0;
4185
+ }
4186
+ case "positions_mismatch": {
4187
+ const parts = Object.entries(details).map(([key, value]) => `${key.replace(/_/g, " ")}: ${listed(value)}`);
4188
+ return parts.length > 0 ? parts.join("; ") : void 0;
4189
+ }
4190
+ default: return;
4191
+ }
4192
+ }
4165
4193
  /**
4166
4194
  * The error to show for a failed markets call: the server's `message`, a
4167
4195
  * fixed text for a disabled release, and a retry hint for `retry_later`.
4168
- * Keeps the HTTP status so the failure report carries it.
4196
+ * Known refusal codes with details print a readable summary; others append
4197
+ * the details as JSON. Keeps the HTTP status, `code` and raw `details` on the error.
4169
4198
  */
4170
4199
  function marketsFailure(err, withCode = false) {
4171
4200
  const status = statusOf(err);
@@ -4180,8 +4209,14 @@ function marketsFailure(err, withCode = false) {
4180
4209
  } else if (code === "retry_later") message = "The company is busy. Try again.";
4181
4210
  else message = errorDetail(err).message;
4182
4211
  const details = body?.["details"];
4183
- if (details && typeof details === "object" && Object.keys(details).length > 0) message = `${message} (${JSON.stringify(details)})`;
4184
- const failure = new LocalizationError(message);
4212
+ if (details && typeof details === "object" && Object.keys(details).length > 0) {
4213
+ const summary = summaryOf(code, details);
4214
+ message = summary ? `${message.replace(` [${code}]`, "")} (${code}): ${summary}` : `${message} (${JSON.stringify(details)})`;
4215
+ }
4216
+ const failure = Object.assign(new LocalizationError(message), {
4217
+ ...code ? { code } : {},
4218
+ ...details ? { details } : {}
4219
+ });
4185
4220
  return status === void 0 ? failure : Object.assign(failure, { status });
4186
4221
  }
4187
4222
  async function guarded(call, withCode = false) {
@@ -4234,12 +4269,40 @@ function previewMarketDeletion(client, id) {
4234
4269
  async function deleteMarket(client, id, confirmName) {
4235
4270
  await guarded(() => client.delete(`${MARKETS_PATH}/${encodeURIComponent(id)}?confirm_name=${encodeURIComponent(confirmName)}`), true);
4236
4271
  }
4272
+ /** GET /api/company/v2026-10/markets/positions/preview: what putting the markets in this order would change. */
4273
+ function previewReorder(client, ids) {
4274
+ const query = ids.map((id) => `market_ids[]=${encodeURIComponent(id)}`).join("&");
4275
+ return guarded(() => client.get(`${MARKETS_PATH}/positions/preview?${query}`), true);
4276
+ }
4277
+ /** PUT /api/company/v2026-10/markets/positions: sets the full market order; the first market becomes the fallback. Returns the markets. */
4278
+ function reorderMarkets(client, ids) {
4279
+ return guarded(() => client.put(`${MARKETS_PATH}/positions`, { market_ids: ids }), true);
4280
+ }
4281
+ /** GET /api/company/v2026-10/markets/:id/fallback/preview: what making the market the fallback would change. */
4282
+ function previewFallback(client, id) {
4283
+ return guarded(() => client.get(`${MARKETS_PATH}/${encodeURIComponent(id)}/fallback/preview`), true);
4284
+ }
4285
+ /** POST /api/company/v2026-10/markets/:id/fallback: makes the market the fallback. Sends no body. Returns the markets. */
4286
+ function setFallback(client, id) {
4287
+ return guarded(() => client.post(`${MARKETS_PATH}/${encodeURIComponent(id)}/fallback`), true);
4288
+ }
4289
+ /** POST /api/company/v2026-10/markets/:id/currencies: adds a currency to the market. */
4290
+ function addMarketCurrency(client, id, code) {
4291
+ return guarded(() => client.post(`${MARKETS_PATH}/${encodeURIComponent(id)}/currencies`, { currency: code }), true);
4292
+ }
4293
+ /** DELETE /api/company/v2026-10/markets/:id/currencies/:iso: removes an unused currency from the market. */
4294
+ async function removeMarketCurrency(client, id, code) {
4295
+ await guarded(() => client.delete(`${MARKETS_PATH}/${encodeURIComponent(id)}/currencies/${encodeURIComponent(code)}`), true);
4296
+ }
4237
4297
  //#endregion
4238
4298
  //#region src/markets/commands.ts
4239
4299
  const MAX_CREATE_COUNTRIES = 10;
4240
4300
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4241
4301
  async function resolveMarketId(api, ref) {
4242
4302
  const { markets } = await listMarkets(api);
4303
+ return marketIdIn(markets, ref);
4304
+ }
4305
+ function marketIdIn(markets, ref) {
4243
4306
  const byId = markets.find((m) => m.id.toLowerCase() === ref.toLowerCase());
4244
4307
  if (byId) return byId.id;
4245
4308
  if (UUID.test(ref)) throw new LocalizationError(`No market with id ${ref}.`);
@@ -4258,7 +4321,7 @@ function currencyCodeOf(raw) {
4258
4321
  return code;
4259
4322
  }
4260
4323
  /**
4261
- * Register `fluid markets list|add-country|remove-country|rename|create|move|delete` on `parent`.
4324
+ * Register `fluid markets list|add-country|remove-country|rename|create|move|delete|reorder|set-fallback|currency add|currency remove` on `parent`.
4262
4325
  *
4263
4326
  * @param parent - the `markets` command group
4264
4327
  * @param ctx - the CLI command context
@@ -4324,6 +4387,66 @@ function registerMarketsCommands(parent, ctx) {
4324
4387
  deleted: true
4325
4388
  });
4326
4389
  }));
4390
+ parent.command("reorder <market...>").description("Set the full market order (ids or names, every market once). The first market becomes the fallback").option("--preview", "Show what the new order would change; write nothing", false).option("--yes", "Confirm without a prompt", false).action((refs, opts) => run(async () => {
4391
+ const api = await client$1(ctx);
4392
+ const { markets } = await listMarkets(api);
4393
+ const ids = refs.map((ref) => marketIdIn(markets, ref));
4394
+ const repeated = refs.filter((_, i) => ids.indexOf(ids[i]) !== i);
4395
+ if (repeated.length > 0) throw new LocalizationError(`Each market can be listed once: ${repeated.join(", ")} repeated.`);
4396
+ const missing = markets.filter((m) => !ids.includes(m.id));
4397
+ if (missing.length > 0) throw new LocalizationError(`The order must list every market. Missing: ${missing.map((m) => m.name).join(", ")}.`);
4398
+ if (opts.preview) {
4399
+ ctx.output(await previewReorder(api, ids));
4400
+ return;
4401
+ }
4402
+ const current = markets.some((m) => m.is_fallback !== void 0) ? markets.find((m) => m.is_fallback) : markets[0];
4403
+ if (!opts.yes && process.stdin.isTTY) ctx.output(await previewReorder(api, ids));
4404
+ if (current && current.id !== ids[0]) await confirmChange(`Reorder markets? ${markets.find((m) => m.id === ids[0]).name} becomes the fallback.`, opts.yes);
4405
+ ctx.output(await reorderMarkets(api, ids));
4406
+ }));
4407
+ parent.command("set-fallback <market>").description("Make a market the fallback. Refused while the fallback prices countries that have no market").option("--preview", "Show what the change would affect; write nothing", false).option("--yes", "Confirm without a prompt", false).action((ref, opts) => run(async () => {
4408
+ const api = await client$1(ctx);
4409
+ const id = await resolveMarketId(api, ref);
4410
+ const preview = await previewFallback(api, id);
4411
+ if (!preview.fallback && !preview.proposed_fallback) {
4412
+ ctx.output({ message: "No fallback is configured." });
4413
+ return;
4414
+ }
4415
+ const blocked = preview.unpriced_countries ?? [];
4416
+ if (blocked.length > 0) {
4417
+ if (opts.preview) ctx.output(preview);
4418
+ throw new LocalizationError(`Cannot change the fallback while these countries are priced through it: ${blocked.join(", ")}. Assign them to a market first.`);
4419
+ }
4420
+ if (opts.preview) {
4421
+ ctx.output(preview);
4422
+ return;
4423
+ }
4424
+ if (!opts.yes && process.stdin.isTTY) ctx.output(preview);
4425
+ await confirmChange(`Make ${ref} the fallback market?`, opts.yes);
4426
+ ctx.output(await setFallback(api, id));
4427
+ }));
4428
+ const currency = parent.command("currency").description("Add or remove a currency on a market");
4429
+ currency.command("add <market> <currency>").description("Add a currency to a market. Adding one it has is a no-op").action((ref, raw) => run(async () => {
4430
+ const code = currencyCodeOf(raw);
4431
+ const api = await client$1(ctx);
4432
+ const written = await addMarketCurrency(api, await resolveMarketId(api, ref), code);
4433
+ ctx.output({
4434
+ ...written,
4435
+ note: written.outcome === "added" ? `${code} added.` : `${code} already present.`
4436
+ });
4437
+ }));
4438
+ currency.command("remove <market> <currency>").description("Remove a currency from a market. Refused while it is billed, priced, or the market's last").option("--yes", "Confirm without a prompt", false).action((ref, raw, opts) => run(async () => {
4439
+ const code = currencyCodeOf(raw);
4440
+ const api = await client$1(ctx);
4441
+ const id = await resolveMarketId(api, ref);
4442
+ await confirmChange(`Remove ${code} from market ${ref}?`, opts.yes);
4443
+ await removeMarketCurrency(api, id, code);
4444
+ ctx.output({
4445
+ market_id: id,
4446
+ currency: code,
4447
+ removed: true
4448
+ });
4449
+ }));
4327
4450
  }
4328
4451
  //#endregion
4329
4452
  //#region src/translations/commands.ts