Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ This project provides a suite of tools to improve developer experience with Xano

### Testing
- **Test framework**: Jest with ts-jest transformer
- **Test files**: `.test.ts` extension (currently no test files exist)
- **Test files**: `.test.ts` or `.spec.ts` extension
- **Test configuration**: JSON-based config files for API testing
- **Test environment**: Node.js environment
- **Coverage**: Use `jest-html-reporter` for HTML coverage reports
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import config from '../../jest.config.js';
export default {
...config,
testPathIgnorePatterns: ['src/commands/test/implementation/'],
testPathIgnorePatterns: [
...(config.testPathIgnorePatterns ?? []),
'src/commands/test/implementation/',
],
};
8 changes: 4 additions & 4 deletions packages/cli/src/commands/generate/implementation/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ async function generateCodeFromOas({
});
s.stop(`Code generated for group "${group.name}" → ${outputPath}/${generator}`);
printOutputDir(printOutput, outputPath);
} catch (err) {
s.stop();
log.error(err.message);
}
} catch (err: any) {
s.stop();
log.error(err?.message ?? String(err));
}
}

const endTime: Date = new Date();
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/generate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function registerGenerateCommands(program, core) {
'Additional arguments to pass to the generator. For options for each generator see https://openapi-generator.tech/docs/usage#generate this also accepts Orval additional arguments e.g. --mock etc. See Orval docs as well: https://orval.dev/reference/configuration/full-example'
)
.action(
withErrorHandler(async (opts, passthroughArgs) => {
withErrorHandler(async (passthroughArgs, opts) => {
const stack: { generator: string; args: string[] } = {
generator: opts.generator || 'typescript-fetch',
args: passthroughArgs || [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ async function addToXano({
for (const file of registryItem.files) {
if (file.type === 'registry:query') {
if (!file.apiGroupName) {
throw new Error(`Missing apiGroupName for file ${file.path || 'unnamed'} in registry item ${registryItem.name || registryItem.id}`);
throw new Error(
`Missing apiGroupName for file ${file.path || 'unnamed'} in registry item ${componentName}`,
);
}
const apiGroup = await getApiGroupByName(
file.apiGroupName,
Expand Down
76 changes: 43 additions & 33 deletions packages/cli/src/features/code-gen/open-api-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,42 +56,52 @@ async function runOpenApiGenerator({
const args = buildGeneratorArgs({ generator, inputPath, outputPath, additionalArgs });
const { logStream, logPath } = await setupLogStream(logger);

return new Promise((resolvePromise, reject) => {
const proc = spawn(cliBin, args, { shell: true, stdio: ['ignore', 'pipe', 'pipe'] });
return new Promise((resolvePromise, reject) => {
const proc = spawn(cliBin, args, { shell: true, stdio: ['ignore', 'pipe', 'pipe'] });

// Pipe or suppress output
if (logStream) {
proc.stdout.pipe(logStream);
proc.stderr.pipe(logStream);
} else {
proc.stdout.resume();
proc.stderr.resume();
}
// Always capture stderr for error reporting
const stderrChunks: Buffer[] = [];
const stdoutChunks: Buffer[] = [];

proc.on('close', (code) => {
if (logStream) logStream.end();
if (code === 0) {
resolvePromise({ logPath });
} else {
reject(
new Error(
`Generator failed with exit code ${code}.` +
(logPath ? ` See log: ${logPath}` : '')
)
);
}
});
if (logStream) {
proc.stdout.pipe(logStream);
proc.stderr.pipe(logStream);
} else {
proc.stdout.on('data', (chunk) => stdoutChunks.push(chunk));
}

proc.on('error', (err) => {
if (logStream) logStream.end();
reject(
new Error(
`Failed to start generator: ${err.message}.` +
(logPath ? ` See log: ${logPath}` : '')
)
);
});
});
// Always collect stderr regardless of logger setting
proc.stderr.on('data', (chunk) => stderrChunks.push(chunk));

proc.on('close', (code) => {
if (logStream) logStream.end();
const stderrOutput = Buffer.concat(stderrChunks).toString().trim();
const stdoutOutput = Buffer.concat(stdoutChunks).toString().trim();

if (code === 0) {
resolvePromise({ logPath });
} else {
const details = stderrOutput || stdoutOutput;
reject(
new Error(
`Generator failed with exit code ${code}.` +
(logPath ? ` See log: ${logPath}` : '') +
(details ? `\n\n--- Generator output ---\n${details}` : '')
)
);
}
});

proc.on('error', (err) => {
if (logStream) logStream.end();
reject(
new Error(
`Failed to start generator: ${err.message}.` +
(logPath ? ` See log: ${logPath}` : '')
)
);
});
});
}

export { runOpenApiGenerator };
11 changes: 9 additions & 2 deletions packages/cli/src/utils/feature-focused/registry/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,15 @@ async function promptForComponents(core, registryUrl) {
try {
const registry = await core.getRegistryIndex(registryUrl);

const options = registry.items.map((item) => ({
const items = registry?.items ?? [];
if (!items.length) {
console.error('No components available in registry index.');
return [];
}

const options = items.map((item) => ({
value: item.name,
label: `${item.name} - ${item.description}`,
label: item.description ? `${item.name} - ${item.description}` : item.name,
}));

const selected = await multiselect({
Expand Down Expand Up @@ -89,6 +95,7 @@ async function getApiGroupByName(
},
});
}
return selectedGroup;
}

export { sortFilesByType, promptForComponents, getApiGroupByName };
8 changes: 7 additions & 1 deletion packages/core/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
import config from '../../jest.config.js';
export default config;
export default {
...config,
testPathIgnorePatterns: [
...(config.testPathIgnorePatterns ?? []),
'/dist/',
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ describe('installRegistryItemToXano', () => {
);

expect(results.skipped).toHaveLength(1);
expect(results.skipped[0].reason).toContain('already exists');
expect(results.skipped[0].error).toContain('already exists');
expect(results.failed).toHaveLength(0);
});

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/features/registry/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async function getRegistryItem(name, registryUrl) {
* Get registry item content, prioritizing inline content over file paths.
*/
async function fetchRegistryFileContent(item, filePath, registryUrl) {
if (item.content) {
if (item.content != null) {
return item.content;
}
const normalized = validateRegistryPath(filePath);
Expand Down
Loading