@docyrus/docyrus 0.0.2 → 0.0.3

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.
Files changed (4) hide show
  1. package/README.md +33 -0
  2. package/main.js +1164 -69
  3. package/main.js.map +4 -4
  4. package/package.json +1 -1
package/main.js CHANGED
@@ -39952,7 +39952,7 @@ function buildInputSchema(args, env, options) {
39952
39952
  // package.json
39953
39953
  var package_default = {
39954
39954
  name: "@docyrus/docyrus",
39955
- version: "0.0.2",
39955
+ version: "0.0.3",
39956
39956
  private: false,
39957
39957
  description: "Docyrus API CLI",
39958
39958
  main: "./main.js",
@@ -40428,6 +40428,8 @@ function createAuthCli(dependencies) {
40428
40428
  }
40429
40429
 
40430
40430
  // src/services/inputReader.ts
40431
+ var import_promises2 = require("node:fs/promises");
40432
+ var import_node_path4 = require("node:path");
40431
40433
  async function readStdinText() {
40432
40434
  if (process.stdin.isTTY) {
40433
40435
  return "";
@@ -40451,20 +40453,99 @@ function parseJsonData(raw, source) {
40451
40453
  });
40452
40454
  }
40453
40455
  }
40454
- async function readJsonInput(params) {
40455
- const { data, readStdin = readStdinText } = params;
40456
- let rawJson = data?.trim();
40457
- if (!rawJson) {
40458
- rawJson = (await readStdin()).trim();
40456
+ function parseCsvRow(line) {
40457
+ const values = [];
40458
+ let current = "";
40459
+ let inQuotes = false;
40460
+ for (let index = 0; index < line.length; index += 1) {
40461
+ const char = line[index];
40462
+ const next = line[index + 1];
40463
+ if (char === '"') {
40464
+ if (inQuotes && next === '"') {
40465
+ current += '"';
40466
+ index += 1;
40467
+ } else {
40468
+ inQuotes = !inQuotes;
40469
+ }
40470
+ continue;
40471
+ }
40472
+ if (char === "," && !inQuotes) {
40473
+ values.push(current.trim());
40474
+ current = "";
40475
+ continue;
40476
+ }
40477
+ current += char;
40459
40478
  }
40460
- if (!rawJson) {
40461
- throw new UserInputError("JSON input is required. Provide --data or pipe JSON via stdin.");
40479
+ if (inQuotes) {
40480
+ throw new UserInputError("Invalid CSV. Unterminated quoted value detected.");
40462
40481
  }
40463
- const parsed = parseJsonData(rawJson, data ? "--data" : "stdin");
40464
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
40465
- throw new UserInputError("Expected a JSON object.");
40482
+ values.push(current.trim());
40483
+ return values;
40484
+ }
40485
+ function parseCsvData(raw, source) {
40486
+ const trimmed = raw.trim();
40487
+ if (!trimmed) {
40488
+ throw new UserInputError(`CSV input is empty in ${source}.`);
40489
+ }
40490
+ const lines = trimmed.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
40491
+ if (lines.length < 2) {
40492
+ throw new UserInputError(`CSV input in ${source} must include a header row and at least one data row.`);
40493
+ }
40494
+ const header = parseCsvRow(lines[0]);
40495
+ if (header.length === 0 || header.some((name) => name.length === 0)) {
40496
+ throw new UserInputError(`CSV header in ${source} contains empty column names.`);
40497
+ }
40498
+ const records = [];
40499
+ for (let rowIndex = 1; rowIndex < lines.length; rowIndex += 1) {
40500
+ const rowValues = parseCsvRow(lines[rowIndex]);
40501
+ if (rowValues.length !== header.length) {
40502
+ throw new UserInputError(
40503
+ `CSV row ${rowIndex + 1} in ${source} has ${rowValues.length} values but header has ${header.length}.`
40504
+ );
40505
+ }
40506
+ const record2 = {};
40507
+ for (let columnIndex = 0; columnIndex < header.length; columnIndex += 1) {
40508
+ record2[header[columnIndex]] = rowValues[columnIndex];
40509
+ }
40510
+ records.push(record2);
40466
40511
  }
40467
- return parsed;
40512
+ return records;
40513
+ }
40514
+ async function readDataInput(params) {
40515
+ const {
40516
+ data,
40517
+ fromFile,
40518
+ readStdin = readStdinText,
40519
+ readFileFn = async (path3, encoding) => await (0, import_promises2.readFile)(path3, encoding)
40520
+ } = params;
40521
+ const trimmedData = data?.trim();
40522
+ const trimmedFromFile = fromFile?.trim();
40523
+ if (trimmedData && trimmedFromFile) {
40524
+ throw new UserInputError("Provide either --data or --from-file, not both.");
40525
+ }
40526
+ if (trimmedFromFile) {
40527
+ let content;
40528
+ try {
40529
+ content = await readFileFn(trimmedFromFile, "utf8");
40530
+ } catch (error48) {
40531
+ throw new UserInputError(`Unable to read file '${trimmedFromFile}'.`, {
40532
+ cause: error48
40533
+ });
40534
+ }
40535
+ const extension = (0, import_node_path4.extname)(trimmedFromFile).toLowerCase();
40536
+ if (extension === ".csv") {
40537
+ return parseCsvData(content, "--from-file");
40538
+ }
40539
+ return parseJsonData(content, "--from-file");
40540
+ }
40541
+ if (trimmedData) {
40542
+ return parseJsonData(trimmedData, "--data");
40543
+ }
40544
+ const stdinContent = (await readStdin()).trim();
40545
+ if (!stdinContent) {
40546
+ throw new UserInputError("JSON input is required. Provide --data, --from-file, or pipe JSON via stdin.");
40547
+ }
40548
+ return parseJsonData(stdinContent, "stdin");
40468
40549
  }
40469
40550
 
40470
40551
  // src/commands/curlCommand.ts
@@ -40610,6 +40691,42 @@ function createCurlCli(dependencies) {
40610
40691
  }
40611
40692
 
40612
40693
  // src/commands/dsCommands.ts
40694
+ var BULK_OPERATION_LIMIT = 50;
40695
+ function isRecord(value) {
40696
+ return typeof value === "object" && value !== null && !Array.isArray(value);
40697
+ }
40698
+ function toBulkPayload(payload, mode) {
40699
+ if (!Array.isArray(payload)) {
40700
+ return null;
40701
+ }
40702
+ if (payload.length === 0) {
40703
+ throw new UserInputError("Batch payload cannot be empty.");
40704
+ }
40705
+ if (payload.length > BULK_OPERATION_LIMIT) {
40706
+ throw new UserInputError(`Batch payload cannot exceed ${BULK_OPERATION_LIMIT} items.`);
40707
+ }
40708
+ const records = [];
40709
+ for (let index = 0; index < payload.length; index += 1) {
40710
+ const item = payload[index];
40711
+ if (!isRecord(item)) {
40712
+ throw new UserInputError(`Batch item at index ${index} must be a JSON object.`);
40713
+ }
40714
+ if (mode === "update") {
40715
+ const itemId = item.id;
40716
+ if (itemId === void 0 || itemId === null || typeof itemId === "string" && itemId.trim().length === 0) {
40717
+ throw new UserInputError(`Batch update item at index ${index} is missing required 'id'.`);
40718
+ }
40719
+ }
40720
+ records.push(item);
40721
+ }
40722
+ return records;
40723
+ }
40724
+ function toSinglePayload(payload) {
40725
+ if (!isRecord(payload)) {
40726
+ throw new UserInputError("Expected a JSON object for single-item operation.");
40727
+ }
40728
+ return payload;
40729
+ }
40613
40730
  function createDsCli(dependencies) {
40614
40731
  const dsCli = Cli_exports.create("ds", {
40615
40732
  description: "Data source commands",
@@ -40683,16 +40800,25 @@ function createDsCli(dependencies) {
40683
40800
  dataSourceSlug: external_exports.string().min(1)
40684
40801
  }),
40685
40802
  options: external_exports.object({
40686
- data: external_exports.string().optional().describe("JSON payload for record fields")
40803
+ data: external_exports.string().optional().describe("JSON payload for record fields"),
40804
+ fromFile: external_exports.string().optional().describe("Path to JSON or CSV payload file")
40687
40805
  }),
40688
40806
  run: async (context) => {
40689
40807
  const apiBaseUrl = await dependencies.environmentConfigService.getActiveApiBaseUrl();
40690
40808
  const apiClient = dependencies.createApiClient(apiBaseUrl);
40691
- const payload = await readJsonInput({ data: context.options.data });
40692
- const response = await apiClient.request({
40809
+ const payload = await readDataInput({
40810
+ data: context.options.data,
40811
+ fromFile: context.options.fromFile
40812
+ });
40813
+ const bulkPayload = toBulkPayload(payload, "create");
40814
+ const response = bulkPayload ? await apiClient.request({
40815
+ method: "POST",
40816
+ path: `/apps/${context.args.appSlug}/data-sources/${context.args.dataSourceSlug}/items/bulk`,
40817
+ body: bulkPayload
40818
+ }) : await apiClient.request({
40693
40819
  method: "POST",
40694
40820
  path: `/apps/${context.args.appSlug}/data-sources/${context.args.dataSourceSlug}/items`,
40695
- body: payload
40821
+ body: toSinglePayload(payload)
40696
40822
  });
40697
40823
  return await injectContext({
40698
40824
  apiBaseUrl,
@@ -40706,20 +40832,37 @@ function createDsCli(dependencies) {
40706
40832
  args: external_exports.object({
40707
40833
  appSlug: external_exports.string().min(1),
40708
40834
  dataSourceSlug: external_exports.string().min(1),
40709
- recordId: external_exports.string().min(1)
40835
+ recordId: external_exports.string().min(1).optional()
40710
40836
  }),
40711
40837
  options: external_exports.object({
40712
- data: external_exports.string().optional().describe("JSON payload for record fields")
40838
+ data: external_exports.string().optional().describe("JSON payload for record fields"),
40839
+ fromFile: external_exports.string().optional().describe("Path to JSON or CSV payload file")
40713
40840
  }),
40714
40841
  run: async (context) => {
40715
40842
  const apiBaseUrl = await dependencies.environmentConfigService.getActiveApiBaseUrl();
40716
40843
  const apiClient = dependencies.createApiClient(apiBaseUrl);
40717
- const payload = await readJsonInput({ data: context.options.data });
40718
- const response = await apiClient.request({
40719
- method: "PATCH",
40720
- path: `/apps/${context.args.appSlug}/data-sources/${context.args.dataSourceSlug}/items/${context.args.recordId}`,
40721
- body: payload
40844
+ const payload = await readDataInput({
40845
+ data: context.options.data,
40846
+ fromFile: context.options.fromFile
40722
40847
  });
40848
+ const bulkPayload = toBulkPayload(payload, "update");
40849
+ if (bulkPayload && context.args.recordId) {
40850
+ throw new UserInputError("Do not provide recordId for batch update. Include 'id' in each item instead.");
40851
+ }
40852
+ const response = bulkPayload ? await apiClient.request({
40853
+ method: "PATCH",
40854
+ path: `/apps/${context.args.appSlug}/data-sources/${context.args.dataSourceSlug}/items/bulk`,
40855
+ body: bulkPayload
40856
+ }) : await (() => {
40857
+ if (!context.args.recordId) {
40858
+ throw new UserInputError("recordId is required for single-item update.");
40859
+ }
40860
+ return apiClient.request({
40861
+ method: "PATCH",
40862
+ path: `/apps/${context.args.appSlug}/data-sources/${context.args.dataSourceSlug}/items/${context.args.recordId}`,
40863
+ body: toSinglePayload(payload)
40864
+ });
40865
+ })();
40723
40866
  return await injectContext({
40724
40867
  apiBaseUrl,
40725
40868
  authStore: dependencies.authStore,
@@ -40752,7 +40895,7 @@ function createDsCli(dependencies) {
40752
40895
  }
40753
40896
 
40754
40897
  // src/commands/discoverCommands.ts
40755
- var import_promises2 = require("node:fs/promises");
40898
+ var import_promises3 = require("node:fs/promises");
40756
40899
  var SUPPORTED_HTTP_METHODS2 = [
40757
40900
  "GET",
40758
40901
  "POST",
@@ -40763,7 +40906,7 @@ var SUPPORTED_HTTP_METHODS2 = [
40763
40906
  "OPTIONS",
40764
40907
  "TRACE"
40765
40908
  ];
40766
- function isRecord(value) {
40909
+ function isRecord2(value) {
40767
40910
  return typeof value === "object" && value !== null;
40768
40911
  }
40769
40912
  function isEnoentError(error48) {
@@ -40817,15 +40960,15 @@ function parseEndpointSelector(input) {
40817
40960
  }
40818
40961
  function getPaths(document) {
40819
40962
  const rawPaths = document.paths;
40820
- return isRecord(rawPaths) ? rawPaths : {};
40963
+ return isRecord2(rawPaths) ? rawPaths : {};
40821
40964
  }
40822
40965
  function getEntities(document) {
40823
- const components = isRecord(document.components) ? document.components : null;
40824
- if (components && isRecord(components.schemas)) {
40966
+ const components = isRecord2(document.components) ? document.components : null;
40967
+ if (components && isRecord2(components.schemas)) {
40825
40968
  return components.schemas;
40826
40969
  }
40827
40970
  const definitions = document.definitions;
40828
- if (isRecord(definitions)) {
40971
+ if (isRecord2(definitions)) {
40829
40972
  return definitions;
40830
40973
  }
40831
40974
  return {};
@@ -40845,19 +40988,19 @@ function extractNamespaces(paths) {
40845
40988
  return Array.from(namespaces.values()).sort((left, right) => left.localeCompare(right));
40846
40989
  }
40847
40990
  function getPathOperation(pathItem, method) {
40848
- if (!isRecord(pathItem)) {
40991
+ if (!isRecord2(pathItem)) {
40849
40992
  return null;
40850
40993
  }
40851
40994
  const normalizedMethod = method.toLowerCase();
40852
40995
  const operation = pathItem[normalizedMethod];
40853
- return isRecord(operation) ? operation : null;
40996
+ return isRecord2(operation) ? operation : null;
40854
40997
  }
40855
40998
  function toEndpointMethodEntries(paths, pathFilter) {
40856
40999
  const entries = [];
40857
41000
  const sortedPaths = Object.keys(paths).filter(pathFilter).sort((left, right) => left.localeCompare(right));
40858
41001
  for (const path3 of sortedPaths) {
40859
41002
  const pathItem = paths[path3];
40860
- if (!isRecord(pathItem)) {
41003
+ if (!isRecord2(pathItem)) {
40861
41004
  continue;
40862
41005
  }
40863
41006
  for (const method of SUPPORTED_HTTP_METHODS2) {
@@ -40886,7 +41029,7 @@ function parseOpenApiDocument(raw) {
40886
41029
  cause: error48
40887
41030
  });
40888
41031
  }
40889
- if (!isRecord(parsed)) {
41032
+ if (!isRecord2(parsed)) {
40890
41033
  throw new UserInputError("Stored OpenAPI spec must be a JSON object. Run 'docyrus discover api' to re-download.");
40891
41034
  }
40892
41035
  return parsed;
@@ -40897,7 +41040,7 @@ async function loadOpenApiSpec(dependencies, tenantId) {
40897
41040
  let sourceUrl;
40898
41041
  let content;
40899
41042
  try {
40900
- content = await (0, import_promises2.readFile)(filePath, "utf8");
41043
+ content = await (0, import_promises3.readFile)(filePath, "utf8");
40901
41044
  } catch (error48) {
40902
41045
  if (!isEnoentError(error48)) {
40903
41046
  throw new UserInputError("Failed to read downloaded OpenAPI spec file.", {
@@ -40909,7 +41052,7 @@ async function loadOpenApiSpec(dependencies, tenantId) {
40909
41052
  downloaded = true;
40910
41053
  sourceUrl = downloadResult.sourceUrl;
40911
41054
  filePath = downloadResult.filePath;
40912
- content = await (0, import_promises2.readFile)(filePath, "utf8");
41055
+ content = await (0, import_promises3.readFile)(filePath, "utf8");
40913
41056
  }
40914
41057
  return {
40915
41058
  filePath,
@@ -41146,6 +41289,952 @@ function createEnvCli(dependencies) {
41146
41289
  return envCli;
41147
41290
  }
41148
41291
 
41292
+ // src/services/studioPayload.ts
41293
+ var import_promises4 = require("node:fs/promises");
41294
+ var import_node_path5 = require("node:path");
41295
+ async function readStdinText2() {
41296
+ if (process.stdin.isTTY) {
41297
+ return "";
41298
+ }
41299
+ const chunks = [];
41300
+ for await (const chunk of process.stdin) {
41301
+ if (typeof chunk === "string") {
41302
+ chunks.push(Buffer.from(chunk));
41303
+ } else {
41304
+ chunks.push(Buffer.from(chunk));
41305
+ }
41306
+ }
41307
+ return Buffer.concat(chunks).toString("utf8").trim();
41308
+ }
41309
+ function isRecord3(value) {
41310
+ return typeof value === "object" && value !== null && !Array.isArray(value);
41311
+ }
41312
+ async function readStudioWriteInput(params) {
41313
+ const {
41314
+ data,
41315
+ fromFile,
41316
+ readStdin = readStdinText2,
41317
+ readFileFn = async (path3, encoding) => await (0, import_promises4.readFile)(path3, encoding)
41318
+ } = params;
41319
+ const trimmedData = data?.trim();
41320
+ const trimmedFromFile = fromFile?.trim();
41321
+ if (trimmedData && trimmedFromFile) {
41322
+ throw new UserInputError("Provide either --data or --from-file, not both.");
41323
+ }
41324
+ if (trimmedFromFile) {
41325
+ const extension = (0, import_node_path5.extname)(trimmedFromFile).toLowerCase();
41326
+ if (extension && extension !== ".json") {
41327
+ throw new UserInputError("Studio commands support only JSON files in --from-file.");
41328
+ }
41329
+ let content;
41330
+ try {
41331
+ content = await readFileFn(trimmedFromFile, "utf8");
41332
+ } catch (error48) {
41333
+ throw new UserInputError(`Unable to read file '${trimmedFromFile}'.`, {
41334
+ cause: error48
41335
+ });
41336
+ }
41337
+ return parseJsonData(content, "--from-file");
41338
+ }
41339
+ if (trimmedData) {
41340
+ return parseJsonData(trimmedData, "--data");
41341
+ }
41342
+ const stdinContent = (await readStdin()).trim();
41343
+ if (!stdinContent) {
41344
+ return {};
41345
+ }
41346
+ return parseJsonData(stdinContent, "stdin");
41347
+ }
41348
+ function ensureObjectPayload(payload, label) {
41349
+ if (!isRecord3(payload)) {
41350
+ throw new UserInputError(`${label} expects a JSON object payload.`);
41351
+ }
41352
+ return payload;
41353
+ }
41354
+ function mergeObjectWithFlags(basePayload, overrides) {
41355
+ const merged = {
41356
+ ...basePayload
41357
+ };
41358
+ for (const [key, value] of Object.entries(overrides)) {
41359
+ if (value === void 0) {
41360
+ continue;
41361
+ }
41362
+ merged[key] = value;
41363
+ }
41364
+ return merged;
41365
+ }
41366
+ function normalizeBatchPayload(payload, key) {
41367
+ if (Array.isArray(payload)) {
41368
+ return {
41369
+ [key]: payload
41370
+ };
41371
+ }
41372
+ if (isRecord3(payload)) {
41373
+ if (key in payload) {
41374
+ return payload;
41375
+ }
41376
+ if (Object.keys(payload).length === 0) {
41377
+ throw new UserInputError(`Batch payload is required. Provide --data/--from-file with '${key}'.`);
41378
+ }
41379
+ }
41380
+ throw new UserInputError(`Batch payload must be an array or object containing '${key}'.`);
41381
+ }
41382
+
41383
+ // src/services/studioResolver.ts
41384
+ function isRecord4(value) {
41385
+ return typeof value === "object" && value !== null;
41386
+ }
41387
+ function extractString(record2, key) {
41388
+ const value = record2[key];
41389
+ return typeof value === "string" && value.length > 0 ? value : void 0;
41390
+ }
41391
+ function extractArray(payload) {
41392
+ if (Array.isArray(payload)) {
41393
+ return payload.filter((item) => isRecord4(item));
41394
+ }
41395
+ if (isRecord4(payload) && Array.isArray(payload.data)) {
41396
+ return payload.data.filter((item) => isRecord4(item));
41397
+ }
41398
+ return [];
41399
+ }
41400
+ function ensureExclusiveSelector(label, idValue, slugValue) {
41401
+ if (idValue && slugValue) {
41402
+ throw new UserInputError(`Provide either --${label}Id or --${label}Slug, not both.`);
41403
+ }
41404
+ if (!idValue && !slugValue) {
41405
+ throw new UserInputError(`Provide --${label}Id or --${label}Slug.`);
41406
+ }
41407
+ }
41408
+ function normalizeOptional(value) {
41409
+ const trimmed = value?.trim();
41410
+ return trimmed && trimmed.length > 0 ? trimmed : void 0;
41411
+ }
41412
+ function resolveBySlug(label, items, slug) {
41413
+ const matches = items.filter((item) => extractString(item, "slug") === slug);
41414
+ if (matches.length === 0) {
41415
+ throw new UserInputError(`${label} slug '${slug}' was not found.`);
41416
+ }
41417
+ if (matches.length > 1) {
41418
+ const matchingIds = matches.map((item) => extractString(item, "id")).filter((id) => Boolean(id));
41419
+ throw new UserInputError(`${label} slug '${slug}' is ambiguous. Matching IDs: ${matchingIds.join(", ")}`);
41420
+ }
41421
+ const matchId = extractString(matches[0], "id");
41422
+ if (!matchId) {
41423
+ throw new UserInputError(`${label} slug '${slug}' resolved to an invalid item without id.`);
41424
+ }
41425
+ return matchId;
41426
+ }
41427
+ var StudioResolver = class {
41428
+ constructor(apiClient) {
41429
+ this.apiClient = apiClient;
41430
+ }
41431
+ #appsCache = /* @__PURE__ */ new Map();
41432
+ #dataSourcesByAppId = /* @__PURE__ */ new Map();
41433
+ #fieldsByAppAndDataSource = /* @__PURE__ */ new Map();
41434
+ async resolveAppId(options) {
41435
+ const appId = normalizeOptional(options.appId);
41436
+ const appSlug = normalizeOptional(options.appSlug);
41437
+ ensureExclusiveSelector("app", appId, appSlug);
41438
+ if (appId) {
41439
+ return appId;
41440
+ }
41441
+ const apps = await this.#getApps();
41442
+ return resolveBySlug("App", apps, appSlug);
41443
+ }
41444
+ async resolveDataSourceId(options) {
41445
+ const dataSourceId = normalizeOptional(options.dataSourceId);
41446
+ const dataSourceSlug = normalizeOptional(options.dataSourceSlug);
41447
+ ensureExclusiveSelector("dataSource", dataSourceId, dataSourceSlug);
41448
+ if (dataSourceId) {
41449
+ return dataSourceId;
41450
+ }
41451
+ const dataSources = await this.getDataSourcesForApp(options.appId);
41452
+ return resolveBySlug("Data source", dataSources, dataSourceSlug);
41453
+ }
41454
+ async resolveFieldId(options) {
41455
+ const fieldId = normalizeOptional(options.fieldId);
41456
+ const fieldSlug = normalizeOptional(options.fieldSlug);
41457
+ ensureExclusiveSelector("field", fieldId, fieldSlug);
41458
+ if (fieldId) {
41459
+ return fieldId;
41460
+ }
41461
+ const fields = await this.getFieldsForDataSource(options.appId, options.dataSourceId);
41462
+ return resolveBySlug("Field", fields, fieldSlug);
41463
+ }
41464
+ async getDataSourcesForApp(appId) {
41465
+ const cacheKey = appId;
41466
+ const cached2 = this.#dataSourcesByAppId.get(cacheKey);
41467
+ if (cached2) {
41468
+ return cached2;
41469
+ }
41470
+ const response = await this.apiClient.request({
41471
+ method: "GET",
41472
+ path: `/dev/apps/${appId}/data-sources`
41473
+ });
41474
+ const resolved = extractArray(response.data);
41475
+ this.#dataSourcesByAppId.set(cacheKey, resolved);
41476
+ return resolved;
41477
+ }
41478
+ async getFieldsForDataSource(appId, dataSourceId) {
41479
+ const cacheKey = `${appId}::${dataSourceId}`;
41480
+ const cached2 = this.#fieldsByAppAndDataSource.get(cacheKey);
41481
+ if (cached2) {
41482
+ return cached2;
41483
+ }
41484
+ const response = await this.apiClient.request({
41485
+ method: "GET",
41486
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields`
41487
+ });
41488
+ const resolved = extractArray(response.data);
41489
+ this.#fieldsByAppAndDataSource.set(cacheKey, resolved);
41490
+ return resolved;
41491
+ }
41492
+ async #getApps() {
41493
+ const cacheKey = "all";
41494
+ const cached2 = this.#appsCache.get(cacheKey);
41495
+ if (cached2) {
41496
+ return cached2;
41497
+ }
41498
+ const response = await this.apiClient.request({
41499
+ method: "GET",
41500
+ path: "/dev/apps"
41501
+ });
41502
+ const resolved = extractArray(response.data);
41503
+ this.#appsCache.set(cacheKey, resolved);
41504
+ return resolved;
41505
+ }
41506
+ };
41507
+
41508
+ // src/commands/studioCommands.ts
41509
+ function parseOptionalJsonFlag(raw, source) {
41510
+ if (!raw || raw.trim().length === 0) {
41511
+ return void 0;
41512
+ }
41513
+ return parseJsonData(raw, source);
41514
+ }
41515
+ function wrapStudioPayload(apiBaseUrl, dependencies, payload) {
41516
+ return injectContext({
41517
+ apiBaseUrl,
41518
+ authStore: dependencies.authStore,
41519
+ payload
41520
+ });
41521
+ }
41522
+ async function getStudioRunContext(dependencies) {
41523
+ const apiBaseUrl = await dependencies.environmentConfigService.getActiveApiBaseUrl();
41524
+ const apiClient = dependencies.createApiClient(apiBaseUrl);
41525
+ const resolver = new StudioResolver(apiClient);
41526
+ return {
41527
+ apiBaseUrl,
41528
+ apiClient,
41529
+ resolver
41530
+ };
41531
+ }
41532
+ function dataSourceFlags(options) {
41533
+ return {
41534
+ title: options.title,
41535
+ name: options.name,
41536
+ slug: options.slug,
41537
+ type: options.type,
41538
+ icon: options.icon,
41539
+ data_sharing: options.dataSharing,
41540
+ meta: parseOptionalJsonFlag(options.meta, "--meta")
41541
+ };
41542
+ }
41543
+ function fieldFlags(options) {
41544
+ return {
41545
+ name: options.name,
41546
+ slug: options.slug,
41547
+ type: options.type,
41548
+ read_only: options.readOnly,
41549
+ status: options.status,
41550
+ default_value: options.defaultValue,
41551
+ relation_data_source_id: options.relationDataSourceId,
41552
+ sort_order: options.sortOrder,
41553
+ tenant_enum_set_id: options.tenantEnumSetId,
41554
+ options: parseOptionalJsonFlag(options.options, "--options"),
41555
+ validations: parseOptionalJsonFlag(options.validations, "--validations")
41556
+ };
41557
+ }
41558
+ function requireNonEmptyObject(payload, label) {
41559
+ if (Object.keys(payload).length === 0) {
41560
+ throw new UserInputError(`${label} payload is empty. Provide flags, --data, or --from-file.`);
41561
+ }
41562
+ }
41563
+ function createStudioCli(dependencies) {
41564
+ const studioCli = Cli_exports.create("studio", {
41565
+ description: "Studio (dev app data source CRUD) commands",
41566
+ env: EnvSchema
41567
+ });
41568
+ studioCli.command("list-data-sources", {
41569
+ description: "List data sources for an app",
41570
+ options: external_exports.object({
41571
+ appId: external_exports.string().optional().describe("App ID"),
41572
+ appSlug: external_exports.string().optional().describe("App slug"),
41573
+ expand: external_exports.string().optional().describe("Optional comma-separated expansions, e.g. fields")
41574
+ }),
41575
+ run: async (context) => {
41576
+ const studio = await getStudioRunContext(dependencies);
41577
+ const appId = await studio.resolver.resolveAppId({
41578
+ appId: context.options.appId,
41579
+ appSlug: context.options.appSlug
41580
+ });
41581
+ const response = await studio.apiClient.request({
41582
+ method: "GET",
41583
+ path: `/dev/apps/${appId}/data-sources`,
41584
+ query: {
41585
+ expand: context.options.expand
41586
+ }
41587
+ });
41588
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41589
+ }
41590
+ });
41591
+ studioCli.command("get-data-source", {
41592
+ description: "Get a single data source",
41593
+ options: external_exports.object({
41594
+ appId: external_exports.string().optional().describe("App ID"),
41595
+ appSlug: external_exports.string().optional().describe("App slug"),
41596
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41597
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug")
41598
+ }),
41599
+ run: async (context) => {
41600
+ const studio = await getStudioRunContext(dependencies);
41601
+ const appId = await studio.resolver.resolveAppId({
41602
+ appId: context.options.appId,
41603
+ appSlug: context.options.appSlug
41604
+ });
41605
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41606
+ appId,
41607
+ dataSourceId: context.options.dataSourceId,
41608
+ dataSourceSlug: context.options.dataSourceSlug
41609
+ });
41610
+ const response = await studio.apiClient.request({
41611
+ method: "GET",
41612
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}`
41613
+ });
41614
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41615
+ }
41616
+ });
41617
+ studioCli.command("create-data-source", {
41618
+ description: "Create a data source",
41619
+ options: external_exports.object({
41620
+ appId: external_exports.string().optional().describe("App ID"),
41621
+ appSlug: external_exports.string().optional().describe("App slug"),
41622
+ data: external_exports.string().optional().describe("JSON payload"),
41623
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file"),
41624
+ title: external_exports.string().optional().describe("Data source title"),
41625
+ name: external_exports.string().optional().describe("Data source name"),
41626
+ slug: external_exports.string().optional().describe("Data source slug"),
41627
+ type: external_exports.string().optional().describe("Data source type"),
41628
+ icon: external_exports.string().optional().describe("Icon"),
41629
+ dataSharing: external_exports.string().optional().describe("Data sharing value"),
41630
+ meta: external_exports.string().optional().describe("JSON meta payload")
41631
+ }),
41632
+ run: async (context) => {
41633
+ const studio = await getStudioRunContext(dependencies);
41634
+ const appId = await studio.resolver.resolveAppId({
41635
+ appId: context.options.appId,
41636
+ appSlug: context.options.appSlug
41637
+ });
41638
+ const basePayload = ensureObjectPayload(
41639
+ await readStudioWriteInput({
41640
+ data: context.options.data,
41641
+ fromFile: context.options.fromFile
41642
+ }),
41643
+ "create-data-source"
41644
+ );
41645
+ const payload = mergeObjectWithFlags(basePayload, dataSourceFlags(context.options));
41646
+ requireNonEmptyObject(payload, "create-data-source");
41647
+ const response = await studio.apiClient.request({
41648
+ method: "POST",
41649
+ path: `/dev/apps/${appId}/data-sources`,
41650
+ body: payload
41651
+ });
41652
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41653
+ }
41654
+ });
41655
+ studioCli.command("update-data-source", {
41656
+ description: "Update a data source",
41657
+ options: external_exports.object({
41658
+ appId: external_exports.string().optional().describe("App ID"),
41659
+ appSlug: external_exports.string().optional().describe("App slug"),
41660
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41661
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41662
+ data: external_exports.string().optional().describe("JSON payload"),
41663
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file"),
41664
+ title: external_exports.string().optional().describe("Data source title"),
41665
+ name: external_exports.string().optional().describe("Data source name"),
41666
+ slug: external_exports.string().optional().describe("Data source slug"),
41667
+ type: external_exports.string().optional().describe("Data source type"),
41668
+ icon: external_exports.string().optional().describe("Icon"),
41669
+ dataSharing: external_exports.string().optional().describe("Data sharing value"),
41670
+ meta: external_exports.string().optional().describe("JSON meta payload")
41671
+ }),
41672
+ run: async (context) => {
41673
+ const studio = await getStudioRunContext(dependencies);
41674
+ const appId = await studio.resolver.resolveAppId({
41675
+ appId: context.options.appId,
41676
+ appSlug: context.options.appSlug
41677
+ });
41678
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41679
+ appId,
41680
+ dataSourceId: context.options.dataSourceId,
41681
+ dataSourceSlug: context.options.dataSourceSlug
41682
+ });
41683
+ const basePayload = ensureObjectPayload(
41684
+ await readStudioWriteInput({
41685
+ data: context.options.data,
41686
+ fromFile: context.options.fromFile
41687
+ }),
41688
+ "update-data-source"
41689
+ );
41690
+ const payload = mergeObjectWithFlags(basePayload, dataSourceFlags(context.options));
41691
+ requireNonEmptyObject(payload, "update-data-source");
41692
+ const response = await studio.apiClient.request({
41693
+ method: "PATCH",
41694
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}`,
41695
+ body: payload
41696
+ });
41697
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41698
+ }
41699
+ });
41700
+ studioCli.command("delete-data-source", {
41701
+ description: "Delete a data source",
41702
+ options: external_exports.object({
41703
+ appId: external_exports.string().optional().describe("App ID"),
41704
+ appSlug: external_exports.string().optional().describe("App slug"),
41705
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41706
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug")
41707
+ }),
41708
+ run: async (context) => {
41709
+ const studio = await getStudioRunContext(dependencies);
41710
+ const appId = await studio.resolver.resolveAppId({
41711
+ appId: context.options.appId,
41712
+ appSlug: context.options.appSlug
41713
+ });
41714
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41715
+ appId,
41716
+ dataSourceId: context.options.dataSourceId,
41717
+ dataSourceSlug: context.options.dataSourceSlug
41718
+ });
41719
+ const response = await studio.apiClient.request({
41720
+ method: "DELETE",
41721
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}`
41722
+ });
41723
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41724
+ }
41725
+ });
41726
+ studioCli.command("bulk-create-data-sources", {
41727
+ description: "Bulk create data sources",
41728
+ options: external_exports.object({
41729
+ appId: external_exports.string().optional().describe("App ID"),
41730
+ appSlug: external_exports.string().optional().describe("App slug"),
41731
+ data: external_exports.string().optional().describe("JSON payload"),
41732
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file")
41733
+ }),
41734
+ run: async (context) => {
41735
+ const studio = await getStudioRunContext(dependencies);
41736
+ const appId = await studio.resolver.resolveAppId({
41737
+ appId: context.options.appId,
41738
+ appSlug: context.options.appSlug
41739
+ });
41740
+ const batchPayload = normalizeBatchPayload(
41741
+ await readStudioWriteInput({
41742
+ data: context.options.data,
41743
+ fromFile: context.options.fromFile
41744
+ }),
41745
+ "dataSources"
41746
+ );
41747
+ const response = await studio.apiClient.request({
41748
+ method: "POST",
41749
+ path: `/dev/apps/${appId}/data-sources/bulk`,
41750
+ body: batchPayload
41751
+ });
41752
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41753
+ }
41754
+ });
41755
+ studioCli.command("list-fields", {
41756
+ description: "List fields for a data source",
41757
+ options: external_exports.object({
41758
+ appId: external_exports.string().optional().describe("App ID"),
41759
+ appSlug: external_exports.string().optional().describe("App slug"),
41760
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41761
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug")
41762
+ }),
41763
+ run: async (context) => {
41764
+ const studio = await getStudioRunContext(dependencies);
41765
+ const appId = await studio.resolver.resolveAppId({
41766
+ appId: context.options.appId,
41767
+ appSlug: context.options.appSlug
41768
+ });
41769
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41770
+ appId,
41771
+ dataSourceId: context.options.dataSourceId,
41772
+ dataSourceSlug: context.options.dataSourceSlug
41773
+ });
41774
+ const response = await studio.apiClient.request({
41775
+ method: "GET",
41776
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields`
41777
+ });
41778
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41779
+ }
41780
+ });
41781
+ studioCli.command("get-field", {
41782
+ description: "Get a single field",
41783
+ options: external_exports.object({
41784
+ appId: external_exports.string().optional().describe("App ID"),
41785
+ appSlug: external_exports.string().optional().describe("App slug"),
41786
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41787
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41788
+ fieldId: external_exports.string().optional().describe("Field ID"),
41789
+ fieldSlug: external_exports.string().optional().describe("Field slug")
41790
+ }),
41791
+ run: async (context) => {
41792
+ const studio = await getStudioRunContext(dependencies);
41793
+ const appId = await studio.resolver.resolveAppId({
41794
+ appId: context.options.appId,
41795
+ appSlug: context.options.appSlug
41796
+ });
41797
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41798
+ appId,
41799
+ dataSourceId: context.options.dataSourceId,
41800
+ dataSourceSlug: context.options.dataSourceSlug
41801
+ });
41802
+ const fieldId = await studio.resolver.resolveFieldId({
41803
+ appId,
41804
+ dataSourceId,
41805
+ fieldId: context.options.fieldId,
41806
+ fieldSlug: context.options.fieldSlug
41807
+ });
41808
+ const response = await studio.apiClient.request({
41809
+ method: "GET",
41810
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}`
41811
+ });
41812
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41813
+ }
41814
+ });
41815
+ studioCli.command("create-field", {
41816
+ description: "Create a field",
41817
+ options: external_exports.object({
41818
+ appId: external_exports.string().optional().describe("App ID"),
41819
+ appSlug: external_exports.string().optional().describe("App slug"),
41820
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41821
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41822
+ data: external_exports.string().optional().describe("JSON payload"),
41823
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file"),
41824
+ name: external_exports.string().optional().describe("Field name"),
41825
+ slug: external_exports.string().optional().describe("Field slug"),
41826
+ type: external_exports.string().optional().describe("Field type"),
41827
+ readOnly: external_exports.boolean().optional().describe("Field read only"),
41828
+ status: external_exports.number().optional().describe("Field status"),
41829
+ defaultValue: external_exports.string().optional().describe("Default value"),
41830
+ relationDataSourceId: external_exports.string().optional().describe("Relation data source ID"),
41831
+ sortOrder: external_exports.number().optional().describe("Sort order"),
41832
+ tenantEnumSetId: external_exports.string().optional().describe("Tenant enum set ID"),
41833
+ options: external_exports.string().optional().describe("JSON options"),
41834
+ validations: external_exports.string().optional().describe("JSON validations")
41835
+ }),
41836
+ run: async (context) => {
41837
+ const studio = await getStudioRunContext(dependencies);
41838
+ const appId = await studio.resolver.resolveAppId({
41839
+ appId: context.options.appId,
41840
+ appSlug: context.options.appSlug
41841
+ });
41842
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41843
+ appId,
41844
+ dataSourceId: context.options.dataSourceId,
41845
+ dataSourceSlug: context.options.dataSourceSlug
41846
+ });
41847
+ const basePayload = ensureObjectPayload(
41848
+ await readStudioWriteInput({
41849
+ data: context.options.data,
41850
+ fromFile: context.options.fromFile
41851
+ }),
41852
+ "create-field"
41853
+ );
41854
+ const payload = mergeObjectWithFlags(basePayload, fieldFlags(context.options));
41855
+ requireNonEmptyObject(payload, "create-field");
41856
+ const response = await studio.apiClient.request({
41857
+ method: "POST",
41858
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields`,
41859
+ body: payload
41860
+ });
41861
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41862
+ }
41863
+ });
41864
+ studioCli.command("update-field", {
41865
+ description: "Update a field",
41866
+ options: external_exports.object({
41867
+ appId: external_exports.string().optional().describe("App ID"),
41868
+ appSlug: external_exports.string().optional().describe("App slug"),
41869
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41870
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41871
+ fieldId: external_exports.string().optional().describe("Field ID"),
41872
+ fieldSlug: external_exports.string().optional().describe("Field slug"),
41873
+ data: external_exports.string().optional().describe("JSON payload"),
41874
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file"),
41875
+ name: external_exports.string().optional().describe("Field name"),
41876
+ slug: external_exports.string().optional().describe("Field slug"),
41877
+ type: external_exports.string().optional().describe("Field type"),
41878
+ readOnly: external_exports.boolean().optional().describe("Field read only"),
41879
+ status: external_exports.number().optional().describe("Field status"),
41880
+ defaultValue: external_exports.string().optional().describe("Default value"),
41881
+ relationDataSourceId: external_exports.string().optional().describe("Relation data source ID"),
41882
+ sortOrder: external_exports.number().optional().describe("Sort order"),
41883
+ tenantEnumSetId: external_exports.string().optional().describe("Tenant enum set ID"),
41884
+ options: external_exports.string().optional().describe("JSON options"),
41885
+ validations: external_exports.string().optional().describe("JSON validations")
41886
+ }),
41887
+ run: async (context) => {
41888
+ const studio = await getStudioRunContext(dependencies);
41889
+ const appId = await studio.resolver.resolveAppId({
41890
+ appId: context.options.appId,
41891
+ appSlug: context.options.appSlug
41892
+ });
41893
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41894
+ appId,
41895
+ dataSourceId: context.options.dataSourceId,
41896
+ dataSourceSlug: context.options.dataSourceSlug
41897
+ });
41898
+ const fieldId = await studio.resolver.resolveFieldId({
41899
+ appId,
41900
+ dataSourceId,
41901
+ fieldId: context.options.fieldId,
41902
+ fieldSlug: context.options.fieldSlug
41903
+ });
41904
+ const basePayload = ensureObjectPayload(
41905
+ await readStudioWriteInput({
41906
+ data: context.options.data,
41907
+ fromFile: context.options.fromFile
41908
+ }),
41909
+ "update-field"
41910
+ );
41911
+ const payload = mergeObjectWithFlags(basePayload, fieldFlags(context.options));
41912
+ requireNonEmptyObject(payload, "update-field");
41913
+ const response = await studio.apiClient.request({
41914
+ method: "PATCH",
41915
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}`,
41916
+ body: payload
41917
+ });
41918
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41919
+ }
41920
+ });
41921
+ studioCli.command("delete-field", {
41922
+ description: "Delete a field",
41923
+ options: external_exports.object({
41924
+ appId: external_exports.string().optional().describe("App ID"),
41925
+ appSlug: external_exports.string().optional().describe("App slug"),
41926
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41927
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41928
+ fieldId: external_exports.string().optional().describe("Field ID"),
41929
+ fieldSlug: external_exports.string().optional().describe("Field slug")
41930
+ }),
41931
+ run: async (context) => {
41932
+ const studio = await getStudioRunContext(dependencies);
41933
+ const appId = await studio.resolver.resolveAppId({
41934
+ appId: context.options.appId,
41935
+ appSlug: context.options.appSlug
41936
+ });
41937
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41938
+ appId,
41939
+ dataSourceId: context.options.dataSourceId,
41940
+ dataSourceSlug: context.options.dataSourceSlug
41941
+ });
41942
+ const fieldId = await studio.resolver.resolveFieldId({
41943
+ appId,
41944
+ dataSourceId,
41945
+ fieldId: context.options.fieldId,
41946
+ fieldSlug: context.options.fieldSlug
41947
+ });
41948
+ const response = await studio.apiClient.request({
41949
+ method: "DELETE",
41950
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}`
41951
+ });
41952
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41953
+ }
41954
+ });
41955
+ studioCli.command("create-fields-batch", {
41956
+ description: "Batch create fields",
41957
+ options: external_exports.object({
41958
+ appId: external_exports.string().optional().describe("App ID"),
41959
+ appSlug: external_exports.string().optional().describe("App slug"),
41960
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41961
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41962
+ data: external_exports.string().optional().describe("JSON payload"),
41963
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file")
41964
+ }),
41965
+ run: async (context) => {
41966
+ const studio = await getStudioRunContext(dependencies);
41967
+ const appId = await studio.resolver.resolveAppId({
41968
+ appId: context.options.appId,
41969
+ appSlug: context.options.appSlug
41970
+ });
41971
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
41972
+ appId,
41973
+ dataSourceId: context.options.dataSourceId,
41974
+ dataSourceSlug: context.options.dataSourceSlug
41975
+ });
41976
+ const payload = normalizeBatchPayload(
41977
+ await readStudioWriteInput({
41978
+ data: context.options.data,
41979
+ fromFile: context.options.fromFile
41980
+ }),
41981
+ "fields"
41982
+ );
41983
+ const response = await studio.apiClient.request({
41984
+ method: "POST",
41985
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/batch`,
41986
+ body: payload
41987
+ });
41988
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
41989
+ }
41990
+ });
41991
+ studioCli.command("update-fields-batch", {
41992
+ description: "Batch update fields",
41993
+ options: external_exports.object({
41994
+ appId: external_exports.string().optional().describe("App ID"),
41995
+ appSlug: external_exports.string().optional().describe("App slug"),
41996
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
41997
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
41998
+ data: external_exports.string().optional().describe("JSON payload"),
41999
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file")
42000
+ }),
42001
+ run: async (context) => {
42002
+ const studio = await getStudioRunContext(dependencies);
42003
+ const appId = await studio.resolver.resolveAppId({
42004
+ appId: context.options.appId,
42005
+ appSlug: context.options.appSlug
42006
+ });
42007
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
42008
+ appId,
42009
+ dataSourceId: context.options.dataSourceId,
42010
+ dataSourceSlug: context.options.dataSourceSlug
42011
+ });
42012
+ const payload = normalizeBatchPayload(
42013
+ await readStudioWriteInput({
42014
+ data: context.options.data,
42015
+ fromFile: context.options.fromFile
42016
+ }),
42017
+ "fields"
42018
+ );
42019
+ const response = await studio.apiClient.request({
42020
+ method: "PATCH",
42021
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/batch`,
42022
+ body: payload
42023
+ });
42024
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
42025
+ }
42026
+ });
42027
+ studioCli.command("delete-fields-batch", {
42028
+ description: "Batch delete fields",
42029
+ options: external_exports.object({
42030
+ appId: external_exports.string().optional().describe("App ID"),
42031
+ appSlug: external_exports.string().optional().describe("App slug"),
42032
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
42033
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
42034
+ data: external_exports.string().optional().describe("JSON payload"),
42035
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file")
42036
+ }),
42037
+ run: async (context) => {
42038
+ const studio = await getStudioRunContext(dependencies);
42039
+ const appId = await studio.resolver.resolveAppId({
42040
+ appId: context.options.appId,
42041
+ appSlug: context.options.appSlug
42042
+ });
42043
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
42044
+ appId,
42045
+ dataSourceId: context.options.dataSourceId,
42046
+ dataSourceSlug: context.options.dataSourceSlug
42047
+ });
42048
+ const payload = normalizeBatchPayload(
42049
+ await readStudioWriteInput({
42050
+ data: context.options.data,
42051
+ fromFile: context.options.fromFile
42052
+ }),
42053
+ "fieldIds"
42054
+ );
42055
+ const response = await studio.apiClient.request({
42056
+ method: "DELETE",
42057
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/batch`,
42058
+ body: payload
42059
+ });
42060
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
42061
+ }
42062
+ });
42063
+ studioCli.command("list-enums", {
42064
+ description: "List enums for a field",
42065
+ options: external_exports.object({
42066
+ appId: external_exports.string().optional().describe("App ID"),
42067
+ appSlug: external_exports.string().optional().describe("App slug"),
42068
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
42069
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
42070
+ fieldId: external_exports.string().optional().describe("Field ID"),
42071
+ fieldSlug: external_exports.string().optional().describe("Field slug")
42072
+ }),
42073
+ run: async (context) => {
42074
+ const studio = await getStudioRunContext(dependencies);
42075
+ const appId = await studio.resolver.resolveAppId({
42076
+ appId: context.options.appId,
42077
+ appSlug: context.options.appSlug
42078
+ });
42079
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
42080
+ appId,
42081
+ dataSourceId: context.options.dataSourceId,
42082
+ dataSourceSlug: context.options.dataSourceSlug
42083
+ });
42084
+ const fieldId = await studio.resolver.resolveFieldId({
42085
+ appId,
42086
+ dataSourceId,
42087
+ fieldId: context.options.fieldId,
42088
+ fieldSlug: context.options.fieldSlug
42089
+ });
42090
+ const response = await studio.apiClient.request({
42091
+ method: "GET",
42092
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}/enums`
42093
+ });
42094
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
42095
+ }
42096
+ });
42097
+ studioCli.command("create-enums", {
42098
+ description: "Create enums for a field",
42099
+ options: external_exports.object({
42100
+ appId: external_exports.string().optional().describe("App ID"),
42101
+ appSlug: external_exports.string().optional().describe("App slug"),
42102
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
42103
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
42104
+ fieldId: external_exports.string().optional().describe("Field ID"),
42105
+ fieldSlug: external_exports.string().optional().describe("Field slug"),
42106
+ data: external_exports.string().optional().describe("JSON payload"),
42107
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file"),
42108
+ enumSetId: external_exports.string().optional().describe("Enum set ID")
42109
+ }),
42110
+ run: async (context) => {
42111
+ const studio = await getStudioRunContext(dependencies);
42112
+ const appId = await studio.resolver.resolveAppId({
42113
+ appId: context.options.appId,
42114
+ appSlug: context.options.appSlug
42115
+ });
42116
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
42117
+ appId,
42118
+ dataSourceId: context.options.dataSourceId,
42119
+ dataSourceSlug: context.options.dataSourceSlug
42120
+ });
42121
+ const fieldId = await studio.resolver.resolveFieldId({
42122
+ appId,
42123
+ dataSourceId,
42124
+ fieldId: context.options.fieldId,
42125
+ fieldSlug: context.options.fieldSlug
42126
+ });
42127
+ const payload = mergeObjectWithFlags(
42128
+ normalizeBatchPayload(
42129
+ await readStudioWriteInput({
42130
+ data: context.options.data,
42131
+ fromFile: context.options.fromFile
42132
+ }),
42133
+ "enums"
42134
+ ),
42135
+ {
42136
+ enumSetId: context.options.enumSetId
42137
+ }
42138
+ );
42139
+ const response = await studio.apiClient.request({
42140
+ method: "POST",
42141
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}/enums`,
42142
+ body: payload
42143
+ });
42144
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
42145
+ }
42146
+ });
42147
+ studioCli.command("update-enums", {
42148
+ description: "Update enums for a field",
42149
+ options: external_exports.object({
42150
+ appId: external_exports.string().optional().describe("App ID"),
42151
+ appSlug: external_exports.string().optional().describe("App slug"),
42152
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
42153
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
42154
+ fieldId: external_exports.string().optional().describe("Field ID"),
42155
+ fieldSlug: external_exports.string().optional().describe("Field slug"),
42156
+ data: external_exports.string().optional().describe("JSON payload"),
42157
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file")
42158
+ }),
42159
+ run: async (context) => {
42160
+ const studio = await getStudioRunContext(dependencies);
42161
+ const appId = await studio.resolver.resolveAppId({
42162
+ appId: context.options.appId,
42163
+ appSlug: context.options.appSlug
42164
+ });
42165
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
42166
+ appId,
42167
+ dataSourceId: context.options.dataSourceId,
42168
+ dataSourceSlug: context.options.dataSourceSlug
42169
+ });
42170
+ const fieldId = await studio.resolver.resolveFieldId({
42171
+ appId,
42172
+ dataSourceId,
42173
+ fieldId: context.options.fieldId,
42174
+ fieldSlug: context.options.fieldSlug
42175
+ });
42176
+ const payload = normalizeBatchPayload(
42177
+ await readStudioWriteInput({
42178
+ data: context.options.data,
42179
+ fromFile: context.options.fromFile
42180
+ }),
42181
+ "enums"
42182
+ );
42183
+ const response = await studio.apiClient.request({
42184
+ method: "PATCH",
42185
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}/enums`,
42186
+ body: payload
42187
+ });
42188
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
42189
+ }
42190
+ });
42191
+ studioCli.command("delete-enums", {
42192
+ description: "Delete enums for a field",
42193
+ options: external_exports.object({
42194
+ appId: external_exports.string().optional().describe("App ID"),
42195
+ appSlug: external_exports.string().optional().describe("App slug"),
42196
+ dataSourceId: external_exports.string().optional().describe("Data source ID"),
42197
+ dataSourceSlug: external_exports.string().optional().describe("Data source slug"),
42198
+ fieldId: external_exports.string().optional().describe("Field ID"),
42199
+ fieldSlug: external_exports.string().optional().describe("Field slug"),
42200
+ data: external_exports.string().optional().describe("JSON payload"),
42201
+ fromFile: external_exports.string().optional().describe("Path to JSON payload file")
42202
+ }),
42203
+ run: async (context) => {
42204
+ const studio = await getStudioRunContext(dependencies);
42205
+ const appId = await studio.resolver.resolveAppId({
42206
+ appId: context.options.appId,
42207
+ appSlug: context.options.appSlug
42208
+ });
42209
+ const dataSourceId = await studio.resolver.resolveDataSourceId({
42210
+ appId,
42211
+ dataSourceId: context.options.dataSourceId,
42212
+ dataSourceSlug: context.options.dataSourceSlug
42213
+ });
42214
+ const fieldId = await studio.resolver.resolveFieldId({
42215
+ appId,
42216
+ dataSourceId,
42217
+ fieldId: context.options.fieldId,
42218
+ fieldSlug: context.options.fieldSlug
42219
+ });
42220
+ const payload = normalizeBatchPayload(
42221
+ await readStudioWriteInput({
42222
+ data: context.options.data,
42223
+ fromFile: context.options.fromFile
42224
+ }),
42225
+ "enumIds"
42226
+ );
42227
+ const response = await studio.apiClient.request({
42228
+ method: "DELETE",
42229
+ path: `/dev/apps/${appId}/data-sources/${dataSourceId}/fields/${fieldId}/enums`,
42230
+ body: payload
42231
+ });
42232
+ return await wrapStudioPayload(studio.apiBaseUrl, dependencies, response.data);
42233
+ }
42234
+ });
42235
+ return studioCli;
42236
+ }
42237
+
41149
42238
  // src/services/apiClient.ts
41150
42239
  function normalizeResponseHeaders(headers) {
41151
42240
  const normalized = {};
@@ -41299,7 +42388,7 @@ var ApiClient = class {
41299
42388
  };
41300
42389
 
41301
42390
  // src/services/authSession.ts
41302
- function isRecord2(value) {
42391
+ function isRecord5(value) {
41303
42392
  return typeof value === "object" && value !== null;
41304
42393
  }
41305
42394
  function extractRecordValue(record2, keys) {
@@ -41310,7 +42399,7 @@ function extractRecordValue(record2, keys) {
41310
42399
  }
41311
42400
  return void 0;
41312
42401
  }
41313
- function extractString(record2, keys) {
42402
+ function extractString2(record2, keys) {
41314
42403
  const value = extractRecordValue(record2, keys);
41315
42404
  return typeof value === "string" && value.length > 0 ? value : void 0;
41316
42405
  }
@@ -41598,15 +42687,15 @@ var AuthSessionService = class {
41598
42687
  fetchFn: this.params.fetchFn
41599
42688
  });
41600
42689
  const payload = response.data;
41601
- const dataCandidate = isRecord2(payload) && isRecord2(payload.data) ? payload.data : payload;
41602
- if (!isRecord2(dataCandidate)) {
42690
+ const dataCandidate = isRecord5(payload) && isRecord5(payload.data) ? payload.data : payload;
42691
+ if (!isRecord5(dataCandidate)) {
41603
42692
  throw new AuthSessionError("Unable to parse /users/me response.");
41604
42693
  }
41605
- const tenantCandidate = isRecord2(dataCandidate.tenant) ? dataCandidate.tenant : void 0;
41606
- const userId = extractString(dataCandidate, ["id", "user_id"]);
41607
- const email3 = extractString(dataCandidate, ["email"]);
41608
- const tenantId = tenantCandidate ? extractString(tenantCandidate, ["id"]) : extractString(dataCandidate, ["tenant_id", "tenantId"]);
41609
- const tenantName = tenantCandidate ? extractString(tenantCandidate, ["name"]) : extractString(dataCandidate, ["tenant_name", "tenantName"]);
42694
+ const tenantCandidate = isRecord5(dataCandidate.tenant) ? dataCandidate.tenant : void 0;
42695
+ const userId = extractString2(dataCandidate, ["id", "user_id"]);
42696
+ const email3 = extractString2(dataCandidate, ["email"]);
42697
+ const tenantId = tenantCandidate ? extractString2(tenantCandidate, ["id"]) : extractString2(dataCandidate, ["tenant_id", "tenantId"]);
42698
+ const tenantName = tenantCandidate ? extractString2(tenantCandidate, ["name"]) : extractString2(dataCandidate, ["tenant_name", "tenantName"]);
41610
42699
  const tenantNo = tenantCandidate ? extractNumber(tenantCandidate, ["no", "tenant_no"]) : extractNumber(dataCandidate, ["tenant_no", "tenantNo"]);
41611
42700
  if (!userId || !email3 || !tenantId || !tenantName || !tenantNo) {
41612
42701
  throw new AuthSessionError("Incomplete identity data returned from /users/me.");
@@ -41628,17 +42717,17 @@ var AuthSessionService = class {
41628
42717
  fetchFn: this.params.fetchFn
41629
42718
  });
41630
42719
  const payload = response.data;
41631
- const listCandidate = Array.isArray(payload) ? payload : isRecord2(payload) && Array.isArray(payload.data) ? payload.data : null;
42720
+ const listCandidate = Array.isArray(payload) ? payload : isRecord5(payload) && Array.isArray(payload.data) ? payload.data : null;
41632
42721
  if (!listCandidate) {
41633
42722
  throw new AuthSessionError("Unable to parse tenant catalog response.");
41634
42723
  }
41635
42724
  const mapped = [];
41636
42725
  for (const item of listCandidate) {
41637
- if (!isRecord2(item)) {
42726
+ if (!isRecord5(item)) {
41638
42727
  continue;
41639
42728
  }
41640
- const tenantId = extractString(item, ["id", "tenant_id"]);
41641
- const tenantName = extractString(item, ["name"]);
42729
+ const tenantId = extractString2(item, ["id", "tenant_id"]);
42730
+ const tenantName = extractString2(item, ["name"]);
41642
42731
  const tenantNo = extractNumber(item, ["tenant_no", "tenantNo", "no"]);
41643
42732
  const logoValue = extractRecordValue(item, ["logo"]);
41644
42733
  const logo = typeof logoValue === "string" || logoValue === null ? logoValue : void 0;
@@ -41793,8 +42882,8 @@ var AuthSessionService = class {
41793
42882
  };
41794
42883
 
41795
42884
  // src/services/authStore.ts
41796
- var import_promises3 = require("node:fs/promises");
41797
- var import_node_path4 = require("node:path");
42885
+ var import_promises5 = require("node:fs/promises");
42886
+ var import_node_path6 = require("node:path");
41798
42887
  function createEmptyState() {
41799
42888
  return {
41800
42889
  version: 2,
@@ -41833,7 +42922,7 @@ var AuthStore = class {
41833
42922
  }
41834
42923
  async readState() {
41835
42924
  try {
41836
- const raw = await (0, import_promises3.readFile)(this.authFilePath, "utf8");
42925
+ const raw = await (0, import_promises5.readFile)(this.authFilePath, "utf8");
41837
42926
  const parsed = JSON.parse(raw);
41838
42927
  const legacy = LegacyAuthSessionSchema.safeParse(parsed);
41839
42928
  if (legacy.success) {
@@ -41869,17 +42958,17 @@ var AuthStore = class {
41869
42958
  });
41870
42959
  }
41871
42960
  const normalized = normalizeState(validated.data);
41872
- const directory = (0, import_node_path4.dirname)(this.authFilePath);
41873
- await (0, import_promises3.mkdir)(directory, {
42961
+ const directory = (0, import_node_path6.dirname)(this.authFilePath);
42962
+ await (0, import_promises5.mkdir)(directory, {
41874
42963
  recursive: true,
41875
42964
  mode: 448
41876
42965
  });
41877
- await (0, import_promises3.writeFile)(this.authFilePath, `${JSON.stringify(normalized, null, 2)}
42966
+ await (0, import_promises5.writeFile)(this.authFilePath, `${JSON.stringify(normalized, null, 2)}
41878
42967
  `, {
41879
42968
  encoding: "utf8",
41880
42969
  mode: 384
41881
42970
  });
41882
- await (0, import_promises3.chmod)(this.authFilePath, 384);
42971
+ await (0, import_promises5.chmod)(this.authFilePath, 384);
41883
42972
  }
41884
42973
  async getActiveProfile(apiBaseUrl) {
41885
42974
  const normalizedApiBaseUrl = normalizeApiBaseUrl(apiBaseUrl);
@@ -42076,15 +43165,15 @@ var AuthStore = class {
42076
43165
  await this.writeState(state);
42077
43166
  }
42078
43167
  async clear() {
42079
- await (0, import_promises3.rm)(this.authFilePath, {
43168
+ await (0, import_promises5.rm)(this.authFilePath, {
42080
43169
  force: true
42081
43170
  });
42082
43171
  }
42083
43172
  };
42084
43173
 
42085
43174
  // src/services/environmentConfig.ts
42086
- var import_promises4 = require("node:fs/promises");
42087
- var import_node_path5 = require("node:path");
43175
+ var import_promises6 = require("node:fs/promises");
43176
+ var import_node_path7 = require("node:path");
42088
43177
  var ENVIRONMENT_ID_ALIASES = {
42089
43178
  "local-development": "dev",
42090
43179
  prod: "live"
@@ -42170,7 +43259,7 @@ var EnvironmentConfigService = class {
42170
43259
  }
42171
43260
  async readState() {
42172
43261
  try {
42173
- const raw = await (0, import_promises4.readFile)(this.configFilePath, "utf8");
43262
+ const raw = await (0, import_promises6.readFile)(this.configFilePath, "utf8");
42174
43263
  const parsed = JSON.parse(raw);
42175
43264
  const validated = EnvironmentConfigStateSchema.safeParse(parsed);
42176
43265
  if (!validated.success) {
@@ -42204,17 +43293,17 @@ var EnvironmentConfigService = class {
42204
43293
  });
42205
43294
  }
42206
43295
  const normalized = normalizeState2(validated.data);
42207
- const directory = (0, import_node_path5.dirname)(this.configFilePath);
42208
- await (0, import_promises4.mkdir)(directory, {
43296
+ const directory = (0, import_node_path7.dirname)(this.configFilePath);
43297
+ await (0, import_promises6.mkdir)(directory, {
42209
43298
  recursive: true,
42210
43299
  mode: 448
42211
43300
  });
42212
- await (0, import_promises4.writeFile)(this.configFilePath, `${JSON.stringify(normalized, null, 2)}
43301
+ await (0, import_promises6.writeFile)(this.configFilePath, `${JSON.stringify(normalized, null, 2)}
42213
43302
  `, {
42214
43303
  encoding: "utf8",
42215
43304
  mode: 384
42216
43305
  });
42217
- await (0, import_promises4.chmod)(this.configFilePath, 384);
43306
+ await (0, import_promises6.chmod)(this.configFilePath, 384);
42218
43307
  }
42219
43308
  async getActiveEnvironment() {
42220
43309
  const state = await this.#readStateWithDefaults();
@@ -42280,8 +43369,8 @@ var EnvironmentConfigService = class {
42280
43369
  };
42281
43370
 
42282
43371
  // src/services/tenantOpenApi.ts
42283
- var import_promises5 = require("node:fs/promises");
42284
- var import_node_path6 = require("node:path");
43372
+ var import_promises7 = require("node:fs/promises");
43373
+ var import_node_path8 = require("node:path");
42285
43374
  function resolveSourceUrl(tenantId, template) {
42286
43375
  return template.replace("{tenantId}", encodeURIComponent(tenantId));
42287
43376
  }
@@ -42295,7 +43384,7 @@ var TenantOpenApiService = class {
42295
43384
  throw new AuthSessionError("Tenant ID is required to resolve OpenAPI spec path.");
42296
43385
  }
42297
43386
  const rootPath = this.params?.rootPath || TENANT_OPENAPI_ROOT_PATH;
42298
- return (0, import_node_path6.join)(rootPath, normalizedTenantId, "openapi.json");
43387
+ return (0, import_node_path8.join)(rootPath, normalizedTenantId, "openapi.json");
42299
43388
  }
42300
43389
  async downloadTenantOpenApi(tenantId) {
42301
43390
  const normalizedTenantId = tenantId.trim();
@@ -42329,16 +43418,16 @@ var TenantOpenApiService = class {
42329
43418
  });
42330
43419
  }
42331
43420
  const filePath = this.getTenantOpenApiFilePath(normalizedTenantId);
42332
- await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(filePath), {
43421
+ await (0, import_promises7.mkdir)((0, import_node_path8.dirname)(filePath), {
42333
43422
  recursive: true,
42334
43423
  mode: 448
42335
43424
  });
42336
- await (0, import_promises5.writeFile)(filePath, `${JSON.stringify(parsedContent, null, 2)}
43425
+ await (0, import_promises7.writeFile)(filePath, `${JSON.stringify(parsedContent, null, 2)}
42337
43426
  `, {
42338
43427
  encoding: "utf8",
42339
43428
  mode: 384
42340
43429
  });
42341
- await (0, import_promises5.chmod)(filePath, 384);
43430
+ await (0, import_promises7.chmod)(filePath, 384);
42342
43431
  return {
42343
43432
  tenantId: normalizedTenantId,
42344
43433
  sourceUrl,
@@ -42362,6 +43451,7 @@ var ROOT_HELP_COMMANDS = [
42362
43451
  { command: "discover search <terms>", description: "Search in endpoint paths and entity names" },
42363
43452
  { command: "ds list <appSlug> <dataSourceSlug>", description: "List data source items" },
42364
43453
  { command: "apps list", description: "List apps" },
43454
+ { command: "studio list-data-sources --appSlug <slug>", description: "List studio data sources" },
42365
43455
  { command: "curl <path>", description: "Send arbitrary API requests" }
42366
43456
  ];
42367
43457
  function createDocyrusCli(params) {
@@ -42429,6 +43519,11 @@ function createDocyrusCli(params) {
42429
43519
  environmentConfigService,
42430
43520
  authStore
42431
43521
  }));
43522
+ cli2.command(createStudioCli({
43523
+ createApiClient,
43524
+ environmentConfigService,
43525
+ authStore
43526
+ }));
42432
43527
  cli2.command(createCurlCli({
42433
43528
  createApiClient,
42434
43529
  environmentConfigService,