diff --git a/js/.prettierrc b/js/.prettierrc new file mode 100644 index 00000000..3ad86440 --- /dev/null +++ b/js/.prettierrc @@ -0,0 +1,11 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "endOfLine": "lf", + "printWidth": 120, + "semi": true, + "bracketSpacing": true, + "bracketSameLine": true, + "arrowParens": "always", + "tabWidth": 2 +} diff --git a/js/cli/rollup.config.js b/js/cli/rollup.config.js index 284b5c2e..2ed379a3 100644 --- a/js/cli/rollup.config.js +++ b/js/cli/rollup.config.js @@ -8,7 +8,7 @@ function cleanOldBuild() { return { name: 'clean-old-build', buildStart() { - rmSync('./lib', { recursive: true, force: true }); + rmSync('./build', { recursive: true, force: true }); }, }; } diff --git a/js/cli/src/app.ts b/js/cli/src/app.ts index b4611bf0..777cd7c9 100644 --- a/js/cli/src/app.ts +++ b/js/cli/src/app.ts @@ -1,124 +1,25 @@ #!/usr/bin/env node -import { readFileSync, mkdirSync, existsSync, writeFileSync } from 'fs'; import { SailsIdlParser } from 'sails-js-parser'; import { Command } from 'commander'; import { Sails } from 'sails-js'; -import * as _path from 'path'; -import { confirm } from '@inquirer/prompts'; -import { generateLib } from './generate/index.js'; -import * as config from './config.json'; +import { ProjectBuilder } from './generate/index.js'; const program = new Command(); -const handler = async (path: string, out: string, name: string, project: boolean) => { +const handler = async (path: string, out: string, name: string, project: boolean, typesOnly: boolean) => { const parser = new SailsIdlParser(); await parser.init(); const sails = new Sails(parser); - const idl = readFileSync(path, 'utf-8'); + const projectBuilder = new ProjectBuilder(sails, name) + .setRootPath(out) + .setIdlPath(path) + .setIsProject(project) + .setTypesOnly(typesOnly); - out = out || '.'; - const dir = out; - const libFile = project ? _path.join(dir, 'src', 'lib.ts') : _path.join(dir, 'lib.ts'); - - if (!existsSync(dir)) { - mkdirSync(dir, { recursive: true }); - } - - if (!project) { - if (existsSync(libFile)) { - const answer = await confirm({ - message: `File ${libFile} exists. Do you want to overwrite?`, - default: false, - }); - - if (!answer) { - process.exit(0); - } - } - } - - let libCode: string; - - try { - libCode = generateLib(sails.parseIdl(idl), name); - } catch (e) { - console.log(e.message, e.stack); - process.exit(1); - } - - if (!project) { - writeFileSync(libFile, libCode); - console.log(`Lib generated at ${libFile}`); - } else { - const srcDir = _path.join(dir, 'src'); - const tsconfigPath = _path.join(dir, 'tsconfig.json'); - const pkgJsonPath = _path.join(dir, 'package.json'); - - let writeTsconfig = true; - let writePkgJson = true; - - if (existsSync(tsconfigPath)) { - const answer = await confirm({ - message: `File ${tsconfigPath} exists. Do you want to overwrite?`, - default: false, - }); - - if (!answer) { - writeTsconfig = false; - } - } - - if (existsSync(pkgJsonPath)) { - const answer = await confirm({ - message: `File ${pkgJsonPath} exists. Do you want to overwrite?`, - default: false, - }); - - if (!answer) { - writePkgJson = false; - } - } - - if (!existsSync(srcDir)) { - mkdirSync(srcDir, { recursive: true }); - } - - writeFileSync(_path.join(srcDir, 'lib.ts'), libCode); - - if (writeTsconfig) { - writeFileSync(_path.join(dir, 'tsconfig.json'), JSON.stringify(config.tsconfig, null, 2)); - } - - if (writePkgJson) { - writeFileSync( - _path.join(dir, 'package.json'), - JSON.stringify( - { - name, - type: 'module', - dependencies: { - '@gear-js/api': config.versions['gear-js'], - '@polkadot/api': config.versions['polkadot-api'], - 'sails-js': config.versions['sails-js'], - }, - devDependencies: { - typescript: config.versions['typescript'], - }, - scripts: { - build: 'tsc', - }, - }, - null, - 2, - ), - ); - } - - console.log(`Lib generated at ${dir}`); - } + await projectBuilder.build(); }; program @@ -126,15 +27,26 @@ program .option('--no-project', 'Generate single file without project structure') .option('-n --name ', 'Name of the library', 'program') .option('-o --out ', 'Output directory') + .option('-t --types-only', 'Generate only types defined', false) .description('Generate typescript library based on .sails.idl file') - .action(async (path, options: { out: string; name: string; project: boolean }) => { - try { - await handler(path, options.out, options.name, options.project); - } catch (error) { - console.error(error.message); - process.exit(1); - } - process.exit(0); - }); + .action( + async ( + path, + options: { + out: string; + name: string; + project: boolean; + typesOnly: boolean; + }, + ) => { + try { + await handler(path, options.out, options.name, options.project, options.typesOnly); + } catch (error) { + console.error(error.message); + process.exit(1); + } + process.exit(0); + }, + ); program.parse(); diff --git a/js/cli/src/generate/index.ts b/js/cli/src/generate/index.ts index 163fe1a0..f2997a35 100644 --- a/js/cli/src/generate/index.ts +++ b/js/cli/src/generate/index.ts @@ -3,16 +3,143 @@ import { Sails } from 'sails-js'; import { ServiceGenerator } from './service-gen.js'; import { TypesGenerator } from './types-gen.js'; import { Output } from './output.js'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; +import path from 'path'; +import { confirm } from '@inquirer/prompts'; +import * as config from '../config.json'; -export function generateLib(sails: Sails, className = 'Program'): string { - const out = new Output(); +export class ProjectBuilder { + private projectPath = ['.', 'src']; + private isProject: boolean = true; + private typesOnly: boolean = false; - const typesGen = new TypesGenerator(out, sails.program); - typesGen.generate(); + constructor(private sails: Sails, private name: string = 'Program') {} - const serviceGen = new ServiceGenerator(out, sails.program, sails.scaleCodecTypes); + private async canCreateFile(filePath: string): Promise { + if (!existsSync(filePath)) { + return true; + } - serviceGen.generate(className); + const answer = await confirm({ + message: `File ${filePath} exists. Do you want to overwrite?`, + default: false, + }); - return out.finalize(); + return answer; + } + + private generateLib(): string { + const out = new Output(); + + const serviceGen = new ServiceGenerator(out, this.sails.program, this.sails.scaleCodecTypes, { + noImplementation: this.typesOnly, + }); + serviceGen.generate(this.name); + + return out.finalize(); + } + + private generateTypes(): string { + const out = new Output(); + + const typesGen = new TypesGenerator(out, this.sails.program); + typesGen.generate(); + + return out.finalize(); + } + + setIdlPath(path: string) { + const idl = readFileSync(path, 'utf-8'); + this.sails.parseIdl(idl); + + return this; + } + + setRootPath(path: string) { + this.projectPath[0] = path ? path : '.'; + + return this; + } + + setIsProject(isProject: boolean) { + this.isProject = isProject; + + return this; + } + + setTypesOnly(typesOnly: boolean) { + this.typesOnly = typesOnly; + + return this; + } + + async build() { + const rootPath = this.projectPath[0]; + const srcPath = path.join(...this.projectPath); + + const libFilePath = this.isProject ? path.join(...this.projectPath) : this.projectPath[0]; + + if (!existsSync(libFilePath)) { + mkdirSync(libFilePath, { recursive: true }); + } + + const libCode = this.generateLib(); + const libFile = path.join(libFilePath, 'lib.ts'); + if (await this.canCreateFile(libFile)) { + writeFileSync(libFile, libCode); + } else { + process.exit(0); + } + + const typesCode = this.generateTypes(); + const typesFile = path.join(libFilePath, 'global.d.ts'); + if (await this.canCreateFile(typesFile)) { + writeFileSync(typesFile, typesCode); + } else { + process.exit(0); + } + + if (!this.isProject) { + console.log(`Lib generated at ${libFilePath}`); + return; + } + + if (!existsSync(srcPath)) { + mkdirSync(srcPath, { recursive: true }); + } + + const tsconfigPath = path.join(rootPath, 'tsconfig.json'); + const pkgJsonPath = path.join(rootPath, 'package.json'); + + if (await this.canCreateFile(tsconfigPath)) { + writeFileSync(tsconfigPath, JSON.stringify(config.tsconfig, null, 2)); + } + + if (await this.canCreateFile(pkgJsonPath)) { + writeFileSync( + pkgJsonPath, + JSON.stringify( + { + name: this.name, + type: 'module', + dependencies: { + '@gear-js/api': config.versions['gear-js'], + '@polkadot/api': config.versions['polkadot-api'], + 'sails-js': config.versions['sails-js'], + }, + devDependencies: { + typescript: config.versions['typescript'], + }, + scripts: { + build: 'tsc', + }, + }, + null, + 2, + ), + ); + } + + console.log(`Lib generated at ${srcPath}`); + } } diff --git a/js/cli/src/generate/service-gen.ts b/js/cli/src/generate/service-gen.ts index dd93cd2b..85a913e3 100644 --- a/js/cli/src/generate/service-gen.ts +++ b/js/cli/src/generate/service-gen.ts @@ -27,6 +27,7 @@ export class ServiceGenerator extends BaseGenerator { out: Output, private _program: ISailsProgram, private scaleTypes: Record, + private options?: { noImplementation?: boolean }, ) { super(out); } @@ -85,6 +86,10 @@ export class ServiceGenerator extends BaseGenerator { args !== null ? ', ' + args : '' }): TransactionBuilder`, () => { + if (this.options?.noImplementation) { + this._out.line(`throw new Error('Not implemented')`); + return; + } this._out .line(`const builder = new TransactionBuilder(`, false) .increaseIndent() @@ -114,6 +119,10 @@ export class ServiceGenerator extends BaseGenerator { .block( `${getFuncName(name)}CtorFromCodeId(codeId: ${HEX_STRING_TYPE}${args !== null ? ', ' + args : ''})`, () => { + if (this.options?.noImplementation) { + this._out.line(`throw new Error('Not implemented')`); + return; + } this._out .line(`const builder = new TransactionBuilder(`, false) .increaseIndent() @@ -161,6 +170,10 @@ export class ServiceGenerator extends BaseGenerator { const returnType = this.getType(def, decodeMethod); this._out.line().block(this.getFuncSignature(name, params, returnType, isQuery), () => { + if (this.options?.noImplementation) { + this._out.line(`throw new Error('Not implemented')`); + return; + } if (isQuery) { this._out .line(createPayload(service.name, name, params)) @@ -228,6 +241,10 @@ export class ServiceGenerator extends BaseGenerator { .block( `public subscribeTo${event.name}Event(callback: (data: ${jsType}) => void | Promise): Promise<() => void>`, () => { + if (this.options?.noImplementation) { + this._out.line(`throw new Error('Not implemented')`); + return; + } this._out .line( `return this._program.api.gearEvents.subscribeToGearEvent('UserMessageSent', ({ data: { message } }) => {`, diff --git a/js/cli/src/generate/types-gen.ts b/js/cli/src/generate/types-gen.ts index e6de08a4..fcb6d8b1 100644 --- a/js/cli/src/generate/types-gen.ts +++ b/js/cli/src/generate/types-gen.ts @@ -5,14 +5,12 @@ import { Output } from './output.js'; import { BaseGenerator } from './base.js'; export class TypesGenerator extends BaseGenerator { - constructor( - out: Output, - private _program: ISailsProgram, - ) { + constructor(out: Output, private _program: ISailsProgram) { super(out); } public generate() { + this._out.line('declare global {').increaseIndent(); for (const { name, def } of this._program.types) { if (def.isStruct) { this.generateStruct(name, def); @@ -24,6 +22,7 @@ export class TypesGenerator extends BaseGenerator { throw new Error(`Unknown type: ${JSON.stringify(def)}`); } } + this._out.reduceIndent().line('}'); } private generateStruct(name: string, def: ISailsTypeDef) {