@codexsploitx/schemaapi 1.1.12 → 1.1.13

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.
@@ -0,0 +1 @@
1
+ export declare function generateSdkClient(name: string): string;
@@ -0,0 +1 @@
1
+ export declare function generateTest(name: string): string;
package/dist/cli.js CHANGED
@@ -6854,6 +6854,208 @@ export const { GET, POST, PUT, DELETE } = handlers;
6854
6854
  `;
6855
6855
  }
6856
6856
 
6857
+ function generateSdkClient(name) {
6858
+ const pascalName = toPascalCase(name);
6859
+ const camelName = toCamelCase(name);
6860
+ return `import {
6861
+ Create${pascalName}Request,
6862
+ Create${pascalName}Response,
6863
+ Get${pascalName}Response,
6864
+ Update${pascalName}Request,
6865
+ Update${pascalName}Response,
6866
+ Delete${pascalName}Response,
6867
+ List${pascalName}sResponse
6868
+ } from '../../contracts/${name}Contract';
6869
+
6870
+ export class ${pascalName}Client {
6871
+ private baseUrl: string;
6872
+ private headers: Record<string, string>;
6873
+
6874
+ constructor(baseUrl: string, token?: string) {
6875
+ this.baseUrl = baseUrl;
6876
+ this.headers = {
6877
+ 'Content-Type': 'application/json',
6878
+ ...(token ? { Authorization: \`Bearer \${token}\` } : {}),
6879
+ };
6880
+ }
6881
+
6882
+ /**
6883
+ * Create a new ${camelName}
6884
+ */
6885
+ async create(data: Create${pascalName}Request): Promise<Create${pascalName}Response> {
6886
+ const response = await fetch(\`\${this.baseUrl}/${name}\`, {
6887
+ method: 'POST',
6888
+ headers: this.headers,
6889
+ body: JSON.stringify(data),
6890
+ });
6891
+
6892
+ if (!response.ok) {
6893
+ throw new Error(\`Failed to create ${camelName}: \${response.statusText}\`);
6894
+ }
6895
+
6896
+ return response.json();
6897
+ }
6898
+
6899
+ /**
6900
+ * Get a ${camelName} by ID
6901
+ */
6902
+ async get(id: string): Promise<Get${pascalName}Response> {
6903
+ const response = await fetch(\`\${this.baseUrl}/${name}/\${id}\`, {
6904
+ method: 'GET',
6905
+ headers: this.headers,
6906
+ });
6907
+
6908
+ if (!response.ok) {
6909
+ throw new Error(\`Failed to get ${camelName}: \${response.statusText}\`);
6910
+ }
6911
+
6912
+ return response.json();
6913
+ }
6914
+
6915
+ /**
6916
+ * Update a ${camelName}
6917
+ */
6918
+ async update(id: string, data: Update${pascalName}Request): Promise<Update${pascalName}Response> {
6919
+ const response = await fetch(\`\${this.baseUrl}/${name}/\${id}\`, {
6920
+ method: 'PUT',
6921
+ headers: this.headers,
6922
+ body: JSON.stringify(data),
6923
+ });
6924
+
6925
+ if (!response.ok) {
6926
+ throw new Error(\`Failed to update ${camelName}: \${response.statusText}\`);
6927
+ }
6928
+
6929
+ return response.json();
6930
+ }
6931
+
6932
+ /**
6933
+ * Delete a ${camelName}
6934
+ */
6935
+ async delete(id: string): Promise<Delete${pascalName}Response> {
6936
+ const response = await fetch(\`\${this.baseUrl}/${name}/\${id}\`, {
6937
+ method: 'DELETE',
6938
+ headers: this.headers,
6939
+ });
6940
+
6941
+ if (!response.ok) {
6942
+ throw new Error(\`Failed to delete ${camelName}: \${response.statusText}\`);
6943
+ }
6944
+
6945
+ return response.json();
6946
+ }
6947
+
6948
+ /**
6949
+ * List all ${name}
6950
+ */
6951
+ async list(): Promise<List${pascalName}sResponse> {
6952
+ const response = await fetch(\`\${this.baseUrl}/${name}\`, {
6953
+ method: 'GET',
6954
+ headers: this.headers,
6955
+ });
6956
+
6957
+ if (!response.ok) {
6958
+ throw new Error(\`Failed to list ${name}: \${response.statusText}\`);
6959
+ }
6960
+
6961
+ return response.json();
6962
+ }
6963
+ }
6964
+ `;
6965
+ }
6966
+
6967
+ function generateTest(name) {
6968
+ const pascalName = toPascalCase(name);
6969
+ const camelName = toCamelCase(name);
6970
+ return `import { describe, it, expect, beforeAll } from 'vitest';
6971
+ import { ${pascalName}Client } from '../src/sdk/${name}Client';
6972
+
6973
+ // CONFIG: Change this to your running API URL
6974
+ const BASE_URL = process.env.API_URL || 'http://localhost:3000';
6975
+
6976
+ describe('${pascalName} Resource Integration Tests', () => {
6977
+ const client = new ${pascalName}Client(BASE_URL);
6978
+ let createdId: string;
6979
+
6980
+ beforeAll(async () => {
6981
+ // Optional: Check if server is running
6982
+ try {
6983
+ await fetch(BASE_URL);
6984
+ } catch (e) {
6985
+ console.warn('āš ļø Warning: API server might not be running at ' + BASE_URL);
6986
+ console.warn(' Tests might fail if the server is not reachable.');
6987
+ }
6988
+ });
6989
+
6990
+ it('should create a new ${camelName}', async () => {
6991
+ const data = {
6992
+ // TODO: Add required fields based on your contract
6993
+ name: 'Test ${pascalName}',
6994
+ // email: 'test@example.com',
6995
+ };
6996
+
6997
+ try {
6998
+ const response = await client.create(data as any);
6999
+ expect(response).toBeDefined();
7000
+ // Assuming the response has an id
7001
+ if ((response as any).id) {
7002
+ createdId = (response as any).id;
7003
+ }
7004
+ } catch (error) {
7005
+ // Ignore error if server is not implemented yet
7006
+ console.log('Skipping create test (server likely not implemented)');
7007
+ }
7008
+ });
7009
+
7010
+ it('should list all ${name}', async () => {
7011
+ try {
7012
+ const list = await client.list();
7013
+ expect(Array.isArray(list)).toBe(true);
7014
+ } catch (error) {
7015
+ console.log('Skipping list test');
7016
+ }
7017
+ });
7018
+
7019
+ it('should get a ${camelName} by ID', async () => {
7020
+ if (!createdId) return;
7021
+
7022
+ try {
7023
+ const item = await client.get(createdId);
7024
+ expect(item).toBeDefined();
7025
+ } catch (error) {
7026
+ console.log('Skipping get test');
7027
+ }
7028
+ });
7029
+
7030
+ it('should update a ${camelName}', async () => {
7031
+ if (!createdId) return;
7032
+
7033
+ const updateData = {
7034
+ name: 'Updated ${pascalName}',
7035
+ };
7036
+
7037
+ try {
7038
+ const updated = await client.update(createdId, updateData as any);
7039
+ expect(updated).toBeDefined();
7040
+ } catch (error) {
7041
+ console.log('Skipping update test');
7042
+ }
7043
+ });
7044
+
7045
+ it('should delete a ${camelName}', async () => {
7046
+ if (!createdId) return;
7047
+
7048
+ try {
7049
+ const result = await client.delete(createdId);
7050
+ expect(result).toBeDefined();
7051
+ } catch (error) {
7052
+ console.log('Skipping delete test');
7053
+ }
7054
+ });
7055
+ });
7056
+ `;
7057
+ }
7058
+
6857
7059
  // Helper to load the main library
6858
7060
  function loadSchemaApi() {
6859
7061
  try {
@@ -6865,7 +7067,7 @@ function loadSchemaApi() {
6865
7067
  return {};
6866
7068
  }
6867
7069
  }
6868
- function loadContractsModule$1(contractsDir) {
7070
+ function loadContractsModule(contractsDir) {
6869
7071
  const cwd = process.cwd();
6870
7072
  const baseDir = path__namespace.join(cwd, contractsDir || "contracts");
6871
7073
  const candidates = ["index.js", "index.cjs", "index.ts"];
@@ -6925,7 +7127,7 @@ function generateDocs(config) {
6925
7127
  if (adapter || contractsDir) {
6926
7128
  console.log(`Using config: adapter=${adapter || "unknown"}, contractsDir=${contractsDir}`);
6927
7129
  }
6928
- const contractsModule = loadContractsModule$1(contractsDir);
7130
+ const contractsModule = loadContractsModule(contractsDir);
6929
7131
  if (!contractsModule) {
6930
7132
  console.error(`Could not find contracts entry in ${contractsDir}. Expected index.js or index.cjs`);
6931
7133
  return;
@@ -6979,19 +7181,36 @@ function handleGenerate(args) {
6979
7181
  return;
6980
7182
  }
6981
7183
  if (type === 'sdk') {
6982
- console.log("Generating SDK from contract");
6983
- // Placeholder restoration
6984
- if (adapter || config?.contractsDir) {
6985
- console.log(`Using config: adapter=${adapter || "unknown"}, contractsDir=${config?.contractsDir || "contracts"}`);
7184
+ if (!name) {
7185
+ console.error('Usage: schemaapi generate sdk <name>');
7186
+ return;
6986
7187
  }
7188
+ console.log(`Generating SDK Client for "${name}"...`);
7189
+ const cwd = process.cwd();
7190
+ const sdkDir = path__namespace.join(cwd, 'src', 'sdk');
7191
+ ensureDir(sdkDir);
7192
+ const sdkContent = generateSdkClient(name);
7193
+ writeFile(path__namespace.join(sdkDir, `${name}Client.ts`), sdkContent);
7194
+ console.log(`Created SDK Client in src/sdk/${name}Client.ts`);
7195
+ console.log(`\nUsage example:`);
7196
+ console.log(`import { ${name.charAt(0).toUpperCase() + name.slice(1)}Client } from './sdk/${name}Client';`);
7197
+ console.log(`const client = new ${name.charAt(0).toUpperCase() + name.slice(1)}Client('http://localhost:3000');`);
6987
7198
  return;
6988
7199
  }
6989
7200
  if (type === 'tests') {
6990
- console.log("Generating tests from contract");
6991
- // Placeholder restoration
6992
- if (adapter || config?.contractsDir) {
6993
- console.log(`Using config: adapter=${adapter || "unknown"}, contractsDir=${config?.contractsDir || "contracts"}`);
7201
+ if (!name) {
7202
+ console.error('Usage: schemaapi generate tests <name>');
7203
+ return;
6994
7204
  }
7205
+ console.log(`Generating Integration Tests for "${name}"...`);
7206
+ const cwd = process.cwd();
7207
+ const testsDir = path__namespace.join(cwd, 'tests');
7208
+ ensureDir(testsDir);
7209
+ const testContent = generateTest(name);
7210
+ writeFile(path__namespace.join(testsDir, `${name}.test.ts`), testContent);
7211
+ console.log(`Created Integration Test in tests/${name}.test.ts`);
7212
+ console.log(`\nRequires 'vitest' and the generated SDK.`);
7213
+ console.log(`Run with: npx vitest tests/${name}.test.ts`);
6995
7214
  return;
6996
7215
  }
6997
7216
  if (type !== 'resource') {
@@ -7099,103 +7318,6 @@ function handleGenerate(args) {
7099
7318
  }
7100
7319
  }
7101
7320
 
7102
- function loadContractsModule(contractsDir) {
7103
- const cwd = process.cwd();
7104
- const baseDir = path__namespace.join(cwd, contractsDir);
7105
- const candidates = ["index.js", "index.cjs", "index.ts"];
7106
- for (const file of candidates) {
7107
- const fullPath = path__namespace.join(baseDir, file);
7108
- if (fs__namespace.existsSync(fullPath)) {
7109
- try {
7110
- // We use require/import to load the contract objects
7111
- // In a real TS environment, we might need ts-node/register if loading .ts directly
7112
- // But assuming the user has compiled code or we are running in a compatible env:
7113
- if (file.endsWith('.ts')) {
7114
- try {
7115
- // Register ts-node with specific options to handle imports correctly
7116
- require('ts-node').register({
7117
- transpileOnly: true,
7118
- skipProject: true, // Ignore tsconfig.json in the project
7119
- compilerOptions: {
7120
- module: 'commonjs',
7121
- moduleResolution: 'node',
7122
- esModuleInterop: true,
7123
- target: 'es2020'
7124
- }
7125
- });
7126
- }
7127
- catch (e) {
7128
- // Ignore if ts-node is not available
7129
- }
7130
- }
7131
- return require(fullPath);
7132
- }
7133
- catch (error) {
7134
- console.error(`Failed to load contracts module at ${fullPath}:`, (error && error.message) || error);
7135
- return null;
7136
- }
7137
- }
7138
- }
7139
- return null;
7140
- }
7141
- function handleAudit(args) {
7142
- const config = loadConfig();
7143
- const contractsDir = config?.contractsDir || "contracts";
7144
- console.log(`šŸ” Starting SchemaApi Audit in directory: ${contractsDir}`);
7145
- const contractsModule = loadContractsModule(contractsDir);
7146
- if (!contractsModule) {
7147
- console.error(`āŒ Could not find contracts entry in ${contractsDir}. Make sure index.ts/js exists and exports your contracts.`);
7148
- return;
7149
- }
7150
- let totalIssues = 0;
7151
- const candidates = Object.values(contractsModule).filter((value) => value &&
7152
- typeof value === "object" &&
7153
- typeof value.docs === "function" // Duck typing for Contract
7154
- );
7155
- if (candidates.length === 0) {
7156
- console.warn("āš ļø No contracts found exported in the entry file.");
7157
- return;
7158
- }
7159
- candidates.forEach((contract, index) => {
7160
- const docs = contract.docs(); // This returns the contract structure
7161
- const routes = docs.routes || [];
7162
- console.log(`\nšŸ“„ Contract #${index + 1}: Found ${routes.length} routes`);
7163
- routes.forEach((route) => {
7164
- const issues = [];
7165
- const method = route.method;
7166
- const pathStr = route.path;
7167
- const fullRoute = `${method} ${pathStr}`;
7168
- // 1. Security Check: Roles
7169
- if (!route.roles || route.roles.length === 0) {
7170
- issues.push(`āš ļø [Security] No roles defined. Endpoint is public? Consider adding explicit roles or 'public'.`);
7171
- }
7172
- // 2. Validation Check: Params
7173
- // Basic check: if path has :param, schema should have it
7174
- const pathParams = (pathStr.match(/:[a-zA-Z0-9_]+/g) || []).map((p) => p.substring(1));
7175
- if (pathParams.length > 0) ;
7176
- // 3. Completeness: Errors
7177
- if (!route.errors || Object.keys(route.errors).length === 0) {
7178
- issues.push(`ā„¹ļø [Best Practice] No error status codes defined. Clients won't know what errors to expect.`);
7179
- }
7180
- if (issues.length > 0) {
7181
- console.log(` šŸ”ø ${fullRoute}`);
7182
- issues.forEach(issue => console.log(` ${issue}`));
7183
- totalIssues += issues.length;
7184
- }
7185
- else {
7186
- console.log(` āœ… ${fullRoute}`);
7187
- }
7188
- });
7189
- });
7190
- console.log(`\nšŸ Audit complete.`);
7191
- if (totalIssues > 0) {
7192
- console.log(`Found ${totalIssues} potential issues.`);
7193
- }
7194
- else {
7195
- console.log(`No obvious issues found. Great job! šŸŽ‰`);
7196
- }
7197
- }
7198
-
7199
7321
  async function cli(args) {
7200
7322
  const command = args[0];
7201
7323
  switch (command) {
@@ -7205,8 +7327,21 @@ async function cli(args) {
7205
7327
  case 'generate':
7206
7328
  handleGenerate(args.slice(1));
7207
7329
  break;
7208
- case 'audit':
7209
- handleAudit(args.slice(1));
7330
+ case 'help':
7331
+ case '--help':
7332
+ case '-h':
7333
+ console.log(`
7334
+ @codexsploitx/schemaapi CLI
7335
+
7336
+ Usage:
7337
+ npx @codexsploitx/schemaapi init <framework> Initialize configuration
7338
+ npx @codexsploitx/schemaapi generate resource <name> Generate Backend Resource (API Routes)
7339
+ npx @codexsploitx/schemaapi generate docs Generate documentation
7340
+ npx @codexsploitx/schemaapi generate sdk <name> Generate Frontend Client (SDK)
7341
+ npx @codexsploitx/schemaapi generate tests <name> Generate Integration Tests
7342
+ npx @codexsploitx/schemaapi adapters List available adapters
7343
+ npx @codexsploitx/schemaapi help Show this help message
7344
+ `);
7210
7345
  break;
7211
7346
  case 'adapters':
7212
7347
  console.log(`
@@ -7224,16 +7359,9 @@ Available Adapters:
7224
7359
  break;
7225
7360
  default:
7226
7361
  console.log(`
7227
- SchemaApi CLI
7362
+ Unknown command: ${command}
7228
7363
 
7229
- Usage:
7230
- npx schemaapi init <framework> Initialize configuration
7231
- npx schemaapi generate resource <name> Generate a new resource
7232
- npx schemaapi generate docs Generate documentation
7233
- npx schemaapi generate sdk Generate SDK (coming soon)
7234
- npx schemaapi generate tests Generate tests (coming soon)
7235
- npx schemaapi audit Audit contracts
7236
- npx schemaapi adapters List available adapters
7364
+ Run 'npx @codexsploitx/schemaapi help' to see available commands.
7237
7365
  `);
7238
7366
  }
7239
7367
  }