@scaleway/generate-react-sdk 0.2.3 → 0.2.4

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.
package/dist/buildAPI.js CHANGED
@@ -12,7 +12,7 @@ const dirGenName = cliArgs['dir-gen-name'] || 'src/__generated__';
12
12
  const sdkFactoryPath = cliArgs['sdk-factory-path'] ||
13
13
  '../contexts/SDKCacheProvider/sdkFactory';
14
14
  const packageNameFilter = cliArgs['package-name-filter'] || '@scaleway/sdk-';
15
- generateAPI({
15
+ await generateAPI({
16
16
  dirGenName,
17
17
  sdkFactoryPath,
18
18
  packageNameFilter,
@@ -1,6 +1,6 @@
1
- import type { ProcessDeclaration } from './types.ts';
1
+ import type { ProcessedMetadata } from '../metadata-types.ts';
2
2
  export declare const emitFiles: ({ res, sourceFolderGen, sdkFactoryPath, }: {
3
- res: ProcessDeclaration;
3
+ res: ProcessedMetadata;
4
4
  sourceFolderGen: string;
5
5
  sdkFactoryPath: string;
6
6
  }) => void;
@@ -1,9 +1,5 @@
1
- export declare const directoryOfSrcFolder: string;
2
1
  export declare const generateAPI: ({ dirGenName, sdkFactoryPath, packageNameFilter, }: {
3
2
  dirGenName: string;
4
3
  sdkFactoryPath: string;
5
- /**
6
- * Name of the package starting with
7
- */
8
4
  packageNameFilter: string;
9
- }) => void;
5
+ }) => Promise<void>;
@@ -0,0 +1,122 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { exec } from 'node:child_process';
10
+ import { existsSync, mkdirSync, readFileSync } from 'node:fs';
11
+ import { createRequire } from 'node:module';
12
+ import { dirname, join, resolve } from 'node:path';
13
+ import { exit, stdout } from 'node:process';
14
+ import { emitFiles } from './emitFiles.js';
15
+ import { generateType } from './generateType.js';
16
+ const directoryOfSrcFolder = resolve('./');
17
+ const require = createRequire(resolve('./package.json'));
18
+ function discoverSdkPackages(packageNameFilter) {
19
+ const pkgJsonPath = resolve('package.json');
20
+ if (!existsSync(pkgJsonPath)) {
21
+ stdout.write('⚠️ No package.json found in current directory\n');
22
+ return new Map();
23
+ }
24
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
25
+ const allDeps = {
26
+ ...pkgJson.dependencies,
27
+ ...pkgJson.devDependencies,
28
+ ...pkgJson.peerDependencies,
29
+ };
30
+ const packages = new Map();
31
+ for (const [name] of Object.entries(allDeps)) {
32
+ if (name.startsWith(packageNameFilter)) {
33
+ packages.set(name, name);
34
+ }
35
+ }
36
+ return packages;
37
+ }
38
+ async function loadVersions(packageName) {
39
+ try {
40
+ const resolvedPath = require.resolve(`${packageName}/metadata`);
41
+ const metadataModule = await import(__rewriteRelativeImportExtension(resolvedPath));
42
+ const versions = metadataModule?.pkgMetadata?.versions ||
43
+ metadataModule?.default?.versions ||
44
+ [];
45
+ return versions;
46
+ }
47
+ catch (error) {
48
+ stdout.write(`⚠️ Could not load metadata from ${packageName}: ${error}\n`);
49
+ return [];
50
+ }
51
+ }
52
+ async function loadMetadata(packageName, version) {
53
+ try {
54
+ // Try the exported path first
55
+ try {
56
+ const resolvedPath = require.resolve(`${packageName}/${version}/metadata`);
57
+ const metadataModule = await import(__rewriteRelativeImportExtension(resolvedPath));
58
+ return metadataModule.queriesMetadata;
59
+ }
60
+ catch {
61
+ stdout.write(`⚠️ Error loading metadata from ${packageName}/${version}/metadata \n Using dist fallback \n`);
62
+ // Fallback: construct path from node_modules
63
+ const pkgDir = join(dirname(resolve('package.json')), 'node_modules', packageName);
64
+ const distMetadataPath = join(pkgDir, 'dist', version, 'metadata.gen.js');
65
+ const metadataModule = await import(__rewriteRelativeImportExtension(distMetadataPath));
66
+ return metadataModule.queriesMetadata;
67
+ }
68
+ }
69
+ catch (error) {
70
+ stdout.write(`⚠️ Error loading metadata from ${packageName}/${version}/metadata: ${error}\n`);
71
+ return null;
72
+ }
73
+ }
74
+ export const generateAPI = async ({ dirGenName, sdkFactoryPath, packageNameFilter, }) => {
75
+ let result = {};
76
+ const dir = join(directoryOfSrcFolder, dirGenName);
77
+ // Create directory if it doesn't exist (don't delete existing files)
78
+ mkdirSync(dir, { recursive: true });
79
+ const sdkPackages = discoverSdkPackages(packageNameFilter);
80
+ if (sdkPackages.size === 0) {
81
+ stdout.write('⚠️ No SDK packages found in dependencies\n');
82
+ }
83
+ const skipPackages = new Set(['@scaleway/sdk-test', '@scaleway/sdk-std']);
84
+ for (const [packageName] of sdkPackages) {
85
+ if (skipPackages.has(packageName)) {
86
+ stdout.write(`⚠️ Skipping ${packageName}: excluded package\n`);
87
+ continue;
88
+ }
89
+ const versions = await loadVersions(packageName);
90
+ if (versions.length === 0) {
91
+ stdout.write(`⚠️ Skipping ${packageName}: no versions with metadata found\n`);
92
+ continue;
93
+ }
94
+ for (const version of versions) {
95
+ const metadata = await loadMetadata(packageName, version);
96
+ if (!metadata) {
97
+ stdout.write(`⚠️ Skipping ${packageName}/${version}: no queriesMetadata found\n`);
98
+ continue;
99
+ }
100
+ const namespace = metadata.folderName || metadata.namespace;
101
+ const apis = metadata.services
102
+ .map((service) => service.apiClass)
103
+ .filter((apiClass) => apiClass && apiClass.length > 0);
104
+ if (apis.length > 0) {
105
+ result = {
106
+ ...result,
107
+ [namespace]: {
108
+ packageName,
109
+ apis,
110
+ },
111
+ };
112
+ }
113
+ }
114
+ }
115
+ emitFiles({ res: result, sourceFolderGen: dir, sdkFactoryPath });
116
+ generateType(result);
117
+ exec('cd ../.. && pnpm run format').on('error', () => {
118
+ stdout.write('❌ Error during format !\n');
119
+ exit(1);
120
+ });
121
+ stdout.write('✅ files formatted !\n');
122
+ };
@@ -1,2 +1,2 @@
1
- import type { ProcessDeclaration } from './types.ts';
2
- export declare const generateType: (res: ProcessDeclaration) => void;
1
+ import type { ProcessedMetadata } from '../metadata-types.ts';
2
+ export declare const generateType: (res: ProcessedMetadata) => void;
@@ -6,15 +6,17 @@ export const generateType = (res) => {
6
6
  const template = ['//this file is generated \n\n'];
7
7
  // import types
8
8
  for (const [name, { packageName }] of Object.entries(res)) {
9
- const imp = `import type { ${name} } from "${packageName}"\n`;
9
+ const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
10
+ const imp = `import type { ${capitalizedName} } from "${packageName}"\n`;
10
11
  template.push(imp);
11
12
  }
12
13
  // export
13
14
  template.push('\n export type APISdk = {\n');
14
15
  for (const [name, { apis }] of Object.entries(res)) {
16
+ const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
15
17
  for (const api of apis) {
16
18
  const key = `${lowerCaseFirstLetter(name + api.replace('API', ''))}`;
17
- const type = `${name}.${api}`;
19
+ const type = `${capitalizedName}.${api}`;
18
20
  template.push(`${key}:${type},\n`);
19
21
  }
20
22
  }
@@ -1,14 +1,15 @@
1
1
  import { lowerCaseFirstLetter } from './helpers.js';
2
2
  export const getFileContent = ({ packageName, name, api, sdkFactoryPath, }) => {
3
- const apiName = name + api;
3
+ const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
4
+ const apiName = capitalizedName + api;
4
5
  const keyValue = name + api.replace('API', '');
5
6
  const template = [
6
- `import { ${name} } from "${packageName}"`,
7
+ `import { ${capitalizedName} } from "${packageName}"`,
7
8
  `import { createSDKFactory } from "${sdkFactoryPath}"`,
8
9
  '',
9
10
  '// This file is generated by scripts placed in `@scaleway/sdk:tools/generate-react-sdk` all modifications will be erased',
10
11
  `export const use${apiName} = createSDKFactory(`,
11
- `${name}.${api},`,
12
+ `${capitalizedName}.${api},`,
12
13
  `'${lowerCaseFirstLetter(keyValue)}'`,
13
14
  ')',
14
15
  ].join('\n');
@@ -1 +1 @@
1
- export { generateAPI } from './generate.ts';
1
+ export { generateAPI } from './generate-metadata.ts';
@@ -1 +1 @@
1
- export { generateAPI } from './generate.js';
1
+ export { generateAPI } from './generate-metadata.js';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Types for metadata-based generation.
3
+ * These types mirror the structure of metadata.gen.ts files in each SDK package.
4
+ */
5
+ /** A single API method from metadata */
6
+ export interface MetadataMethod {
7
+ methodName: string;
8
+ protoName: string;
9
+ paramsType: string;
10
+ returnType: string;
11
+ isList: boolean;
12
+ paginationType?: 'offset' | 'cursor' | 'none';
13
+ pageParamKey?: string;
14
+ listItemType?: string;
15
+ isPrivate: boolean;
16
+ description?: string;
17
+ hasWaiter?: boolean;
18
+ }
19
+ /** A service class (e.g. "API", "ZonedAPI") from metadata */
20
+ export interface MetadataService {
21
+ apiClass: string;
22
+ methods: MetadataMethod[];
23
+ }
24
+ /** Top-level metadata from metadata.gen.ts */
25
+ export interface Metadata {
26
+ namespace: string;
27
+ version: string;
28
+ folderName: string;
29
+ services: MetadataService[];
30
+ }
31
+ /** Processed result for code generation */
32
+ export type ProcessedMetadata = {
33
+ [namespace: string]: {
34
+ packageName: string;
35
+ apis: string[];
36
+ };
37
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Types for metadata-based generation.
3
+ * These types mirror the structure of metadata.gen.ts files in each SDK package.
4
+ */
5
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scaleway/generate-react-sdk",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "generate react sdk",
5
5
  "type": "module",
6
6
  "private": false,
@@ -24,8 +24,7 @@
24
24
  "directory": "tools/generate-react-sdk"
25
25
  },
26
26
  "devDependencies": {
27
- "@types/node": "20.19.35",
28
- "ts-morph": "27.0.2"
27
+ "@types/node": "20.19.35"
29
28
  },
30
29
  "scripts": {
31
30
  "build": "tsgo -p tsconfig.build.json",
@@ -1,3 +0,0 @@
1
- import { Project } from 'ts-morph';
2
- export declare const project: Project;
3
- export declare const rawStringTypes: string[];
@@ -1,5 +0,0 @@
1
- import { Project } from 'ts-morph';
2
- // Load the entry file
3
- export const project = new Project();
4
- // We dont want these types to be suffixed by namespaces
5
- export const rawStringTypes = ['string', 'Blob', 'string[]'];
@@ -1,57 +0,0 @@
1
- import { exec } from 'node:child_process';
2
- import { existsSync, readFileSync, rmSync } from 'node:fs';
3
- import { join, resolve } from 'node:path';
4
- import { exit, stdout } from 'node:process';
5
- import { project } from './config.js';
6
- import { emitFiles } from './emitFiles.js';
7
- import { generateType } from './generateType.js';
8
- import { processDeclarationFile } from './processDeclarationFile.js';
9
- export const directoryOfSrcFolder = join(resolve('./'));
10
- // Read package.json
11
- const packageJson = JSON.parse(readFileSync(resolve(resolve('./'), 'package.json'), 'utf8'));
12
- export const generateAPI = ({ dirGenName, sdkFactoryPath, packageNameFilter, }) => {
13
- let result = {};
14
- const dir = join(directoryOfSrcFolder, dirGenName);
15
- if (existsSync(dir)) {
16
- rmSync(dir, {
17
- recursive: true,
18
- force: true,
19
- });
20
- }
21
- // Generate SDK paths from dependencies
22
- const sdkPaths = [
23
- ...Object.keys(packageJson.dependencies ?? {}),
24
- ...Object.keys(packageJson.devDependencies ?? {}),
25
- ...Object.keys(packageJson.peerDependencies ?? {}),
26
- ]
27
- .filter(dep => dep.startsWith(packageNameFilter))
28
- .filter(dep => !['@scaleway/sdk-client'].includes(dep))
29
- .map(dep => {
30
- const declarationFile = 'dist/index.gen.d.ts';
31
- return [
32
- dep,
33
- resolve(resolve('./'), `./node_modules/${dep}/${declarationFile}`),
34
- ];
35
- });
36
- for (const [packageName, sdkPath] of sdkPaths) {
37
- if (!(packageName && sdkPath)) {
38
- throw new Error('packageName or sdkPath is undefined');
39
- }
40
- const path = project.addSourceFileAtPath(sdkPath);
41
- const res = processDeclarationFile({
42
- namespace: '',
43
- packageName,
44
- result,
45
- sourceFile: path,
46
- });
47
- result = { ...result, ...res };
48
- }
49
- emitFiles({ res: result, sourceFolderGen: dir, sdkFactoryPath });
50
- generateType(result);
51
- // // Finally we format all files to prevent updated files just because format is different
52
- exec('cd ../.. && pnpm run format').on('error', () => {
53
- stdout.write('❌ Error during format !\n');
54
- exit(1);
55
- });
56
- stdout.write('✅ files formatted !\n');
57
- };
@@ -1,2 +0,0 @@
1
- import type { ProcessDeclaration, ProcessDeclarationFileProps } from './types.ts';
2
- export declare function processDeclarationFile({ sourceFile, namespace, result, packageName }: ProcessDeclarationFileProps): ProcessDeclaration;
@@ -1,73 +0,0 @@
1
- import { resolveModule } from './resolveModule.js';
2
- // Recursive function to dive in all files
3
- export function processDeclarationFile({ sourceFile, namespace, result, packageName, }) {
4
- let copyOfResult = { ...result };
5
- // Export processing
6
- for (const exportDecl of sourceFile.getExportDeclarations()) {
7
- const moduleSpecifier = exportDecl.getModuleSpecifierValue();
8
- if (!moduleSpecifier) {
9
- break;
10
- }
11
- const resolvedFile = resolveModule(sourceFile, moduleSpecifier);
12
- if (!resolvedFile) {
13
- break;
14
- }
15
- copyOfResult = {
16
- ...copyOfResult,
17
- ...processDeclarationFile({
18
- namespace: `${namespace}_${exportDecl.getNamespaceExport()?.getName() ?? ''}`,
19
- packageName,
20
- result: copyOfResult,
21
- sourceFile: resolvedFile,
22
- }),
23
- };
24
- }
25
- // Import processing
26
- for (const importDecl of sourceFile.getImportDeclarations()) {
27
- const moduleSpecifier = importDecl.getModuleSpecifierValue();
28
- if (!moduleSpecifier) {
29
- break;
30
- }
31
- const resolvedFile = resolveModule(sourceFile, moduleSpecifier);
32
- if (!resolvedFile) {
33
- break;
34
- }
35
- // const newNS = `${namespace}_${importDecl.getNamespaceImport()?.getText() ?? ''}`
36
- copyOfResult = {
37
- ...copyOfResult,
38
- ...processDeclarationFile({
39
- namespace,
40
- packageName,
41
- result: copyOfResult,
42
- sourceFile: resolvedFile,
43
- }),
44
- };
45
- }
46
- for (const classDecl of sourceFile.getClasses()) {
47
- let className = classDecl.getName();
48
- if (className === 'InstanceV1UtilsAPI') {
49
- // instanceAPI is not well exported
50
- className = 'API';
51
- }
52
- if (className &&
53
- // theses API are manually set inside the SDK himself ...
54
- !['K8SUtilsAPI', 'InstanceV1UtilsAPI'].includes(className)) {
55
- const prettyNamespace = namespace.split('_').filter(aNamespace => aNamespace.length > 0)[0] ??
56
- '';
57
- // Take what ends with `API` and is not part of the ignored APIs array.
58
- const namespaceApi = {
59
- [prettyNamespace]: {
60
- apis: [
61
- ...new Set([
62
- ...(copyOfResult[prettyNamespace]?.apis ?? []),
63
- className,
64
- ]),
65
- ],
66
- packageName,
67
- },
68
- };
69
- Object.assign(copyOfResult, namespaceApi);
70
- }
71
- }
72
- return copyOfResult;
73
- }
@@ -1,2 +0,0 @@
1
- import type { SourceFile } from 'ts-morph';
2
- export declare function resolveModule(sourceFile: SourceFile, moduleSpecifier: string): SourceFile | null;
@@ -1,25 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { dirname, resolve } from 'node:path/posix';
3
- // Resolve the referenced file in import/export
4
- export function resolveModule(sourceFile, moduleSpecifier) {
5
- const currentDir = dirname(sourceFile.getFilePath());
6
- let resolvedPath = resolve(currentDir, moduleSpecifier.replace('.js', ''));
7
- // Ajouter les extensions .ts ou .d.ts si pas presente dans le path
8
- if (!(resolvedPath.endsWith('.ts') || resolvedPath.endsWith('.d.ts'))) {
9
- if (existsSync(`${resolvedPath}.ts`)) {
10
- resolvedPath = `${resolvedPath}.ts`;
11
- }
12
- else if (existsSync(`${resolvedPath}.d.ts`)) {
13
- resolvedPath = `${resolvedPath}.d.ts`;
14
- }
15
- else {
16
- return null;
17
- }
18
- }
19
- try {
20
- return (sourceFile.getProject().addSourceFileAtPathIfExists(resolvedPath) ?? null);
21
- }
22
- catch {
23
- return null;
24
- }
25
- }
@@ -1,13 +0,0 @@
1
- import type { SourceFile } from 'ts-morph';
2
- export type ProcessDeclarationFileProps = {
3
- sourceFile: SourceFile;
4
- namespace: string;
5
- result: ProcessDeclaration;
6
- packageName: string;
7
- };
8
- export type ProcessDeclaration = {
9
- [x: string]: {
10
- packageName: string;
11
- apis: string[];
12
- };
13
- };
@@ -1 +0,0 @@
1
- export {};