Skip to content

Commit 1e0cba9

Browse files
authored
fix: update workspace schema exporting endpoint (#182)
2 parents 31375e2 + 3db8259 commit 1e0cba9

6 files changed

Lines changed: 74 additions & 19 deletions

File tree

.changeset/dry-files-float.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@calycode/core": minor
3+
"@calycode/cli": minor
4+
---
5+
6+
fix: **BREAKING CHANGE** fixing a breaking change in the Xano Metadata API for the workspace/{workspace_id}/export-schema endpoint causing the repo and internal docs generator commands to fail

packages/cli/src/commands/generate/implementation/internal-docs.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,29 @@ async function generateInternalDocs({
9090

9191
intro('Building directory structure...');
9292

93-
if (!inputFile) throw new Error('Input YAML file is required');
93+
if (!inputFile) throw new Error('Input schema file (.json or .yaml) is required');
9494
if (!outputDir) throw new Error('Output directory is required');
9595

96-
log.step(`Reading and parsing YAML file -> ${inputFile}`);
96+
log.step(`Reading and parsing schema file -> ${inputFile}`);
9797
const fileContents = await core.storage.readFile(inputFile, 'utf8');
98-
const jsonData = load(fileContents);
98+
99+
let jsonData: any;
100+
try {
101+
if (inputFile.endsWith('.json')) {
102+
jsonData = JSON.parse(fileContents);
103+
} else if (inputFile.endsWith('.yaml') || inputFile.endsWith('.yml')) {
104+
jsonData = load(fileContents);
105+
} else {
106+
// Fallback: Try JSON, then YAML if extension is missing or weird
107+
try {
108+
jsonData = JSON.parse(fileContents);
109+
} catch {
110+
jsonData = load(fileContents);
111+
}
112+
}
113+
} catch (err) {
114+
throw new Error(`Failed to parse schema file: ${err.message}`);
115+
}
99116

100117
const plannedWrites: { path: string; content: string }[] = await core.generateInternalDocs({
101118
jsonData,

packages/cli/src/commands/generate/implementation/repo.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,31 @@ async function generateRepo({
101101

102102
intro('Building directory structure...');
103103

104-
if (!inputFile) throw new Error('Input YAML file is required');
104+
if (!inputFile) throw new Error('Input schema file (.json or .yaml) is required');
105105
if (!outputDir) throw new Error('Output directory is required');
106106

107-
log.step(`Reading and parsing YAML file -> ${inputFile}`);
107+
log.step(`Reading and parsing schema file -> ${inputFile}`);
108108
const fileContents = await core.storage.readFile(inputFile, 'utf8');
109-
const jsonData = load(fileContents);
109+
110+
let jsonData: any;
111+
try {
112+
if (inputFile.endsWith('.json')) {
113+
jsonData = JSON.parse(fileContents);
114+
} else if (inputFile.endsWith('.yaml') || inputFile.endsWith('.yml')) {
115+
jsonData = load(fileContents);
116+
} else {
117+
// Fallback: Try JSON, then YAML if extension is missing or weird
118+
try {
119+
jsonData = JSON.parse(fileContents);
120+
} catch {
121+
jsonData = load(fileContents);
122+
}
123+
}
124+
} catch (err) {
125+
throw new Error(`Failed to parse schema file: ${err.message}`);
126+
}
127+
128+
// 3. Proceed with generation
110129
const plannedWrites: { path: string; content: string }[] = await core.generateRepo({
111130
jsonData,
112131
instance: instanceConfig.name,

packages/cli/src/commands/generate/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ function registerGenerateCommands(program, core) {
6868
.description(
6969
'Collect all descriptions, and internal documentation from a Xano instance and combine it into a nice documentation suite that can be hosted on a static hosting.'
7070
)
71-
.option('-I, --input <file>', 'Workspace yaml file from a local source, if present.')
71+
.option('-I, --input <file>', 'Workspace schema file (.yaml [legacy] or .json) from a local source, if present.')
7272
.option(
7373
'-O, --output <dir>',
7474
'Output directory (overrides default config), useful when ran from a CI/CD pipeline and want to ensure consistent output location.'
@@ -134,7 +134,10 @@ function registerGenerateCommands(program, core) {
134134
.description(
135135
'Process Xano workspace into repo structure. We use the export-schema metadata API to offer the full details. However that is enriched with the Xanoscripts after Xano 2.0 release.'
136136
)
137-
.option('-I, --input <file>', 'Workspace yaml file from a local source, if present.')
137+
.option(
138+
'-I, --input <file>',
139+
'Workspace schema file (.yaml [legacy] or .json) from a local source, if present.'
140+
)
138141
.option(
139142
'-O, --output <dir>',
140143
'Output directory (overrides default config), useful when ran from a CI/CD pipeline and want to ensure consistent output location.'

packages/cli/src/node-config-storage.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -291,16 +291,21 @@ export const nodeConfigStorage: ConfigStorage = {
291291
// Extract with tar
292292
await x({ file: tarGzPath, cwd: tempDir });
293293

294-
// Read all files in tempDir
295-
const files = await fs.promises.readdir(tempDir);
296-
for (const file of files) {
297-
if (file.endsWith('.yaml')) {
298-
result[file] = await fs.promises.readFile(join(tempDir, file));
294+
const entries = await fs.promises.readdir(tempDir, { recursive: true });
295+
296+
for (const file of entries) {
297+
// Check for both extensions
298+
if (file.endsWith('.yaml') || file.endsWith('.json')) {
299+
const fullPath = join(tempDir, file);
300+
// Ensure we are reading a file, not a directory that happens to end in .json
301+
const stat = await fs.promises.stat(fullPath);
302+
if (stat.isFile()) {
303+
result[file] = await fs.promises.readFile(fullPath);
304+
}
299305
}
300306
}
301307
} finally {
302-
// Clean up tempDir here
303-
await fs.promises.rm(tempDir, { recursive: true });
308+
await fs.promises.rm(tempDir, { recursive: true, force: true });
304309
}
305310

306311
return result;

packages/utils/src/methods/fetch-and-extract-yaml.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,20 @@ export async function fetchAndExtractYaml({
4747

4848
// Find the .yaml file inside outDir
4949
const files: string[] = await core.storage.readdir(outDir);
50-
const yamlFile = files.find((f) => f.endsWith('.yaml'));
51-
if (!yamlFile) throw new Error('No .yaml found in the exported archive!');
52-
const yamlFilePath = joinPath(outDir, yamlFile);
50+
const schemaFile = files.find((f) => f.endsWith('.json') || f.endsWith('.yaml'));
51+
52+
if (!schemaFile) {
53+
throw new Error(
54+
`No schema file (.json or .yaml) found in the exported archive! Found: ${files.join(', ')}`
55+
);
56+
}
57+
const schemaFilePath = joinPath(outDir, schemaFile);
5358

5459
core.emit('progress', {
5560
name: 'fetch-extract-yaml',
5661
message: 'Extracted workspace schema!',
5762
percent: 100,
5863
});
5964

60-
return yamlFilePath;
65+
return schemaFilePath;
6166
}

0 commit comments

Comments
 (0)