@xano/cli 0.0.64 → 0.0.66

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 (32) hide show
  1. package/README.md +25 -0
  2. package/dist/base-command.d.ts +25 -0
  3. package/dist/base-command.js +53 -11
  4. package/dist/commands/auth/index.d.ts +2 -0
  5. package/dist/commands/auth/index.js +23 -16
  6. package/dist/commands/function/edit/index.js +17 -18
  7. package/dist/commands/function/get/index.js +11 -11
  8. package/dist/commands/profile/create/index.d.ts +1 -0
  9. package/dist/commands/profile/create/index.js +10 -0
  10. package/dist/commands/profile/edit/index.d.ts +2 -0
  11. package/dist/commands/profile/edit/index.js +23 -1
  12. package/dist/commands/profile/list/index.js +3 -0
  13. package/dist/commands/profile/wizard/index.d.ts +2 -0
  14. package/dist/commands/profile/wizard/index.js +23 -12
  15. package/dist/commands/release/export/index.js +14 -13
  16. package/dist/commands/release/pull/index.d.ts +0 -6
  17. package/dist/commands/release/pull/index.js +15 -62
  18. package/dist/commands/release/push/index.js +16 -6
  19. package/dist/commands/tenant/backup/export/index.js +4 -2
  20. package/dist/commands/tenant/create/index.js +3 -0
  21. package/dist/commands/tenant/deploy_platform/index.js +1 -0
  22. package/dist/commands/tenant/deploy_release/index.js +1 -0
  23. package/dist/commands/tenant/pull/index.d.ts +0 -6
  24. package/dist/commands/tenant/pull/index.js +9 -56
  25. package/dist/commands/tenant/push/index.js +16 -6
  26. package/dist/commands/workspace/git/pull/index.js +9 -8
  27. package/dist/commands/workspace/pull/index.js +9 -6
  28. package/dist/commands/workspace/push/index.js +10 -1
  29. package/dist/utils/document-parser.d.ts +22 -0
  30. package/dist/utils/document-parser.js +54 -1
  31. package/oclif.manifest.json +992 -952
  32. package/package.json +1 -1
@@ -5,7 +5,7 @@ import * as os from 'node:os';
5
5
  import * as path from 'node:path';
6
6
  import snakeCase from 'lodash.snakecase';
7
7
  import BaseCommand from '../../../base-command.js';
8
- import { parseDocument } from '../../../utils/document-parser.js';
8
+ import { buildApiGroupFolderResolver, parseDocument } from '../../../utils/document-parser.js';
9
9
  export default class Pull extends BaseCommand {
10
10
  static args = {
11
11
  directory: Args.string({
@@ -149,6 +149,9 @@ Pulled 42 documents to ./my-workspace
149
149
  const outputDir = path.resolve(args.directory);
150
150
  // Create the output directory if it doesn't exist
151
151
  fs.mkdirSync(outputDir, { recursive: true });
152
+ // Resolve api_group names to unique folder names, disambiguating collisions
153
+ // where different names produce the same snakeCase (e.g., "Authentication" vs "authentication")
154
+ const getApiGroupFolder = buildApiGroupFolderResolver(documents, snakeCase);
152
155
  // Track filenames per type to handle duplicates
153
156
  const filenameCounters = new Map();
154
157
  let writtenCount = 0;
@@ -206,14 +209,14 @@ Pulled 42 documents to ./my-workspace
206
209
  baseName = this.sanitizeFilename(doc.name);
207
210
  }
208
211
  else if (doc.type === 'api_group') {
209
- // api_group "test" → api/test/api_group.xs
210
- const groupFolder = snakeCase(doc.name);
212
+ // api_group "test" → api/{resolved_folder}/{name}.xs
213
+ const groupFolder = getApiGroupFolder(doc.name);
211
214
  typeDir = path.join(outputDir, 'api', groupFolder);
212
- baseName = 'api_group';
215
+ baseName = this.sanitizeFilename(doc.name);
213
216
  }
214
217
  else if (doc.type === 'query' && doc.apiGroup) {
215
- // query in group "test" → api/test/{query_name}.xs
216
- const groupFolder = snakeCase(doc.apiGroup);
218
+ // query in group "test" → api/{resolved_folder}/{query_name}.xs
219
+ const groupFolder = getApiGroupFolder(doc.apiGroup);
217
220
  const nameParts = doc.name.split('/');
218
221
  const leafName = nameParts.pop();
219
222
  const folderParts = nameParts.map((part) => snakeCase(part));
@@ -4,7 +4,7 @@ import * as fs from 'node:fs';
4
4
  import * as os from 'node:os';
5
5
  import * as path from 'node:path';
6
6
  import BaseCommand from '../../../base-command.js';
7
- import { buildDocumentKey, parseDocument } from '../../../utils/document-parser.js';
7
+ import { buildDocumentKey, findFilesWithGuid, parseDocument } from '../../../utils/document-parser.js';
8
8
  export default class Push extends BaseCommand {
9
9
  static args = {
10
10
  directory: Args.string({
@@ -201,6 +201,15 @@ Push schema only, skip records and environment variables
201
201
  catch {
202
202
  errorMessage += `\n${errorText}`;
203
203
  }
204
+ // Surface local files involved in duplicate GUID errors
205
+ const guidMatch = errorMessage.match(/Duplicate \w+ guid: (\S+)/);
206
+ if (guidMatch) {
207
+ const dupeFiles = findFilesWithGuid(documentEntries, guidMatch[1]);
208
+ if (dupeFiles.length > 0) {
209
+ const relPaths = dupeFiles.map((f) => path.relative(inputDir, f));
210
+ errorMessage += `\n Local files with this GUID:\n${relPaths.map((f) => ` ${f}`).join('\n')}`;
211
+ }
212
+ }
204
213
  this.error(errorMessage);
205
214
  }
206
215
  // Parse the response for GUID map
@@ -1,6 +1,8 @@
1
1
  export interface ParsedDocument {
2
2
  apiGroup?: string;
3
+ canonical?: string;
3
4
  content: string;
5
+ guid?: string;
4
6
  name: string;
5
7
  type: string;
6
8
  verb?: string;
@@ -15,3 +17,23 @@ export declare function parseDocument(content: string): null | ParsedDocument;
15
17
  * Used to match server GUID map entries back to local files.
16
18
  */
17
19
  export declare function buildDocumentKey(type: string, name: string, verb?: string, apiGroup?: string): string;
20
+ /**
21
+ * Build a map of api_group name → unique folder name for a set of documents.
22
+ *
23
+ * When two api_groups produce the same snakeCase folder (e.g., "Authentication" and
24
+ * "authentication" both → "authentication"), the first group keeps the base name
25
+ * and subsequent groups get a numeric suffix (authentication_2, authentication_3, etc.).
26
+ *
27
+ * @param documents - Parsed documents (only api_group type docs are considered)
28
+ * @param snakeCaseFn - The snakeCase function to use for folder name generation
29
+ * @returns A function that resolves an api_group name to its unique folder name
30
+ */
31
+ export declare function buildApiGroupFolderResolver(documents: ParsedDocument[], snakeCaseFn: (s: string) => string): (groupName: string) => string;
32
+ /**
33
+ * Find local .xs files that contain a specific GUID.
34
+ * Used to surface which files are involved when the server reports a duplicate GUID error.
35
+ */
36
+ export declare function findFilesWithGuid(entries: Array<{
37
+ content: string;
38
+ filePath: string;
39
+ }>, guid: string): string[];
@@ -45,7 +45,19 @@ export function parseDocument(content) {
45
45
  if (apiGroupMatch) {
46
46
  apiGroup = apiGroupMatch[1];
47
47
  }
48
- return { apiGroup, content, name, type, verb };
48
+ // Extract canonical if present (e.g., canonical = "abc123")
49
+ let canonical;
50
+ const canonicalMatch = content.match(/canonical\s*=\s*"([^"]*)"/);
51
+ if (canonicalMatch) {
52
+ canonical = canonicalMatch[1];
53
+ }
54
+ // Extract guid if present (e.g., guid = "abc123")
55
+ let guid;
56
+ const guidMatch = content.match(/guid\s*=\s*"([^"]*)"/);
57
+ if (guidMatch) {
58
+ guid = guidMatch[1];
59
+ }
60
+ return { apiGroup, canonical, content, guid, name, type, verb };
49
61
  }
50
62
  /**
51
63
  * Build a unique key for a document based on its type, name, verb, and api_group.
@@ -59,3 +71,44 @@ export function buildDocumentKey(type, name, verb, apiGroup) {
59
71
  parts.push(apiGroup);
60
72
  return parts.join(':');
61
73
  }
74
+ /**
75
+ * Build a map of api_group name → unique folder name for a set of documents.
76
+ *
77
+ * When two api_groups produce the same snakeCase folder (e.g., "Authentication" and
78
+ * "authentication" both → "authentication"), the first group keeps the base name
79
+ * and subsequent groups get a numeric suffix (authentication_2, authentication_3, etc.).
80
+ *
81
+ * @param documents - Parsed documents (only api_group type docs are considered)
82
+ * @param snakeCaseFn - The snakeCase function to use for folder name generation
83
+ * @returns A function that resolves an api_group name to its unique folder name
84
+ */
85
+ export function buildApiGroupFolderResolver(documents, snakeCaseFn) {
86
+ const apiGroupFolderMap = new Map();
87
+ const folderClaims = new Map();
88
+ for (const doc of documents) {
89
+ if (doc.type !== 'api_group')
90
+ continue;
91
+ const folder = snakeCaseFn(doc.name);
92
+ const names = folderClaims.get(folder) ?? [];
93
+ if (!names.includes(doc.name)) {
94
+ names.push(doc.name);
95
+ }
96
+ folderClaims.set(folder, names);
97
+ }
98
+ for (const [folder, names] of folderClaims) {
99
+ apiGroupFolderMap.set(names[0], folder);
100
+ for (let i = 1; i < names.length; i++) {
101
+ apiGroupFolderMap.set(names[i], `${folder}_${i + 1}`);
102
+ }
103
+ }
104
+ return (groupName) => {
105
+ return apiGroupFolderMap.get(groupName) ?? snakeCaseFn(groupName);
106
+ };
107
+ }
108
+ /**
109
+ * Find local .xs files that contain a specific GUID.
110
+ * Used to surface which files are involved when the server reports a duplicate GUID error.
111
+ */
112
+ export function findFilesWithGuid(entries, guid) {
113
+ return entries.filter((e) => e.content.includes(guid)).map((e) => e.filePath);
114
+ }