diff --git a/.changeset/dry-files-float.md b/.changeset/dry-files-float.md new file mode 100644 index 0000000..bb02a85 --- /dev/null +++ b/.changeset/dry-files-float.md @@ -0,0 +1,6 @@ +--- +"@calycode/core": minor +"@calycode/cli": minor +--- + +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 diff --git a/packages/cli/src/commands/generate/implementation/internal-docs.ts b/packages/cli/src/commands/generate/implementation/internal-docs.ts index ddcfc4e..bb530e4 100644 --- a/packages/cli/src/commands/generate/implementation/internal-docs.ts +++ b/packages/cli/src/commands/generate/implementation/internal-docs.ts @@ -90,12 +90,29 @@ async function generateInternalDocs({ intro('Building directory structure...'); - if (!inputFile) throw new Error('Input YAML file is required'); + if (!inputFile) throw new Error('Input schema file (.json or .yaml) is required'); if (!outputDir) throw new Error('Output directory is required'); - log.step(`Reading and parsing YAML file -> ${inputFile}`); + log.step(`Reading and parsing schema file -> ${inputFile}`); const fileContents = await core.storage.readFile(inputFile, 'utf8'); - const jsonData = load(fileContents); + + let jsonData: any; + try { + if (inputFile.endsWith('.json')) { + jsonData = JSON.parse(fileContents); + } else if (inputFile.endsWith('.yaml') || inputFile.endsWith('.yml')) { + jsonData = load(fileContents); + } else { + // Fallback: Try JSON, then YAML if extension is missing or weird + try { + jsonData = JSON.parse(fileContents); + } catch { + jsonData = load(fileContents); + } + } + } catch (err) { + throw new Error(`Failed to parse schema file: ${err.message}`); + } const plannedWrites: { path: string; content: string }[] = await core.generateInternalDocs({ jsonData, diff --git a/packages/cli/src/commands/generate/implementation/repo.ts b/packages/cli/src/commands/generate/implementation/repo.ts index b03e5e4..9f29675 100644 --- a/packages/cli/src/commands/generate/implementation/repo.ts +++ b/packages/cli/src/commands/generate/implementation/repo.ts @@ -101,12 +101,31 @@ async function generateRepo({ intro('Building directory structure...'); - if (!inputFile) throw new Error('Input YAML file is required'); + if (!inputFile) throw new Error('Input schema file (.json or .yaml) is required'); if (!outputDir) throw new Error('Output directory is required'); - log.step(`Reading and parsing YAML file -> ${inputFile}`); + log.step(`Reading and parsing schema file -> ${inputFile}`); const fileContents = await core.storage.readFile(inputFile, 'utf8'); - const jsonData = load(fileContents); + + let jsonData: any; + try { + if (inputFile.endsWith('.json')) { + jsonData = JSON.parse(fileContents); + } else if (inputFile.endsWith('.yaml') || inputFile.endsWith('.yml')) { + jsonData = load(fileContents); + } else { + // Fallback: Try JSON, then YAML if extension is missing or weird + try { + jsonData = JSON.parse(fileContents); + } catch { + jsonData = load(fileContents); + } + } + } catch (err) { + throw new Error(`Failed to parse schema file: ${err.message}`); + } + + // 3. Proceed with generation const plannedWrites: { path: string; content: string }[] = await core.generateRepo({ jsonData, instance: instanceConfig.name, diff --git a/packages/cli/src/commands/generate/index.ts b/packages/cli/src/commands/generate/index.ts index 4f153c8..2589ce9 100644 --- a/packages/cli/src/commands/generate/index.ts +++ b/packages/cli/src/commands/generate/index.ts @@ -68,7 +68,7 @@ function registerGenerateCommands(program, core) { .description( '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.' ) - .option('-I, --input ', 'Workspace yaml file from a local source, if present.') + .option('-I, --input ', 'Workspace schema file (.yaml [legacy] or .json) from a local source, if present.') .option( '-O, --output ', '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) { .description( '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.' ) - .option('-I, --input ', 'Workspace yaml file from a local source, if present.') + .option( + '-I, --input ', + 'Workspace schema file (.yaml [legacy] or .json) from a local source, if present.' + ) .option( '-O, --output ', 'Output directory (overrides default config), useful when ran from a CI/CD pipeline and want to ensure consistent output location.' diff --git a/packages/cli/src/node-config-storage.ts b/packages/cli/src/node-config-storage.ts index bfbec4c..beb9890 100644 --- a/packages/cli/src/node-config-storage.ts +++ b/packages/cli/src/node-config-storage.ts @@ -291,16 +291,21 @@ export const nodeConfigStorage: ConfigStorage = { // Extract with tar await x({ file: tarGzPath, cwd: tempDir }); - // Read all files in tempDir - const files = await fs.promises.readdir(tempDir); - for (const file of files) { - if (file.endsWith('.yaml')) { - result[file] = await fs.promises.readFile(join(tempDir, file)); + const entries = await fs.promises.readdir(tempDir, { recursive: true }); + + for (const file of entries) { + // Check for both extensions + if (file.endsWith('.yaml') || file.endsWith('.json')) { + const fullPath = join(tempDir, file); + // Ensure we are reading a file, not a directory that happens to end in .json + const stat = await fs.promises.stat(fullPath); + if (stat.isFile()) { + result[file] = await fs.promises.readFile(fullPath); + } } } } finally { - // Clean up tempDir here - await fs.promises.rm(tempDir, { recursive: true }); + await fs.promises.rm(tempDir, { recursive: true, force: true }); } return result; diff --git a/packages/utils/src/methods/fetch-and-extract-yaml.ts b/packages/utils/src/methods/fetch-and-extract-yaml.ts index d2a2b9e..7dcf26c 100644 --- a/packages/utils/src/methods/fetch-and-extract-yaml.ts +++ b/packages/utils/src/methods/fetch-and-extract-yaml.ts @@ -47,9 +47,14 @@ export async function fetchAndExtractYaml({ // Find the .yaml file inside outDir const files: string[] = await core.storage.readdir(outDir); - const yamlFile = files.find((f) => f.endsWith('.yaml')); - if (!yamlFile) throw new Error('No .yaml found in the exported archive!'); - const yamlFilePath = joinPath(outDir, yamlFile); + const schemaFile = files.find((f) => f.endsWith('.json') || f.endsWith('.yaml')); + + if (!schemaFile) { + throw new Error( + `No schema file (.json or .yaml) found in the exported archive! Found: ${files.join(', ')}` + ); + } + const schemaFilePath = joinPath(outDir, schemaFile); core.emit('progress', { name: 'fetch-extract-yaml', @@ -57,5 +62,5 @@ export async function fetchAndExtractYaml({ percent: 100, }); - return yamlFilePath; + return schemaFilePath; }