/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v6.2.8
protocol/bin/flow.js
707 строк
22 KB
chrisnojima
new jsx and remove stories (#25359)
20 дек 2022, 17:38
Не верифицирован
20 дек 2022, 17:38
bba5de9
Код
Авторство
О чём код?
'use strict' const prettier = require('prettier') const util = require('util') const fs = require('fs') const path = require('path') const camelcase = require('camelcase') const json5 = require('json5') const enabledCalls = json5.parse(fs.readFileSync(path.join(__dirname, 'enabled-calls.json'))) // Sanity check this json file Object.keys(enabledCalls).forEach(rpc => Object.keys(enabledCalls[rpc]).forEach(type => { if (!['promise', 'incoming', 'engineListener'].includes(type)) { console.log(colors.red('ERROR! Invalid enabled call?\n\n '), rpc, type) process.exit(1) } }) ) var projects = { chat1: { customResponseIncomingMaps: {}, enums: {}, import: ['Gregor1', 'Keybase1', 'Stellar1'], incomingMaps: {}, notEnabled: [], out: 'rpc-chat-gen', root: './json/chat1', seenTypes: {}, }, keybase1: { customResponseIncomingMaps: {}, enums: {}, import: ['Gregor1'], incomingMaps: {}, notEnabled: [], out: 'rpc-gen', root: 'json/keybase1', seenTypes: {}, }, gregor1: { customResponseIncomingMaps: {}, enums: {}, import: [], incomingMaps: {}, notEnabled: [], out: 'rpc-gregor-gen', root: './json/gregor1', seenTypes: {}, }, stellar1: { customResponseIncomingMaps: {}, enums: {}, import: ['Keybase1'], incomingMaps: {}, notEnabled: [], out: 'rpc-stellar-gen', root: './json/stellar1', seenTypes: {}, }, } function jsonOnly(file) { return !!file.match(/.*\.json$/) } function load(file, project) { return JSON.parse(fs.readFileSync(path.join(project.root, file))) } function analyze(json, project) { lintJSON(json) return { consts: analyzeEnums(json, project), types: analyzeTypes(json, project), messages: analyzeMessages(json, project), } } function fixCase(s) { return s.toLowerCase().replace(/(_\w)/g, s => capitalize(s[1])) } function analyzeEnums(json, project) { return json.types .filter(t => t.type === 'enum') .map(t => { var en = {} t.symbols.forEach(s => { const parts = s.split('_') const val = parseInt(parts.pop(), 10) const name = fixCase(parts.join('_')) en[name] = val }) project.enums[t.name] = en return { name: t.name, map: en, } }) .reduce((map, t) => { map[decapitalize(t.name)] = `\nexport enum ${t.name} { ${Object.keys(t.map) .map(k => `${k} = ${t.map[k]}`) .join(',\n ')}, }` return map }, {}) } function analyzeTypes(json, project) { return json.types.reduce((map, t) => { if (project.seenTypes[t.name]) { return map } project.seenTypes[t.name] = true switch (t.type) { case 'record': map[t.name] = `export type ${t.name} = ${parseRecord(t)}` break case 'enum': break case 'variant': { const parsed = parseVariant(t, project) if (parsed) { map[t.name] = `export type ${t.name} =${parsed}` } } break case 'fixed': map[t.name] = `export type ${t.name} = string | null` break } return map }, {}) } function figureType(type, prefix = '') { if (!type) { return 'null' // keep backwards compat with old script } if (type instanceof Array) { if (type.length === 2) { if (type[0] === null) { return `${prefix}${capitalize(type[1])} | null` } if (type[1] === null) { return `${prefix}${capitalize(type[0])} | null` } } return `(${type.map(t => t || 'null').join(' | ')})` } else if (typeof type === 'object') { switch (type.type) { case 'array': return `Array<${prefix}${capitalize(type.items)}> | null` case 'map': return `{[key: string]: ${figureType(type.values)}}` default: console.log(`Unknown type: ${type}`) return 'unknown' } } return prefix + capitalize(type) } function capitalize(s) { return s.charAt(0).toUpperCase() + s.slice(1) } function analyzeMessages(json, project) { // ui means an incoming rpc. simple regexp to filter this but it might break in the future if // the core side doesn't have a consistent naming convention. (must be case insensitive to pass correctly) const isUIProtocol = ['notifyCtl'].indexOf(json.protocol) === -1 && !!json.protocol.match(/^(notify.*|.*ui|logsend)$/i) && !json.protocol.match(/NotifyFSRequest/) return Object.keys(json.messages).reduce((map, m) => { const message = json.messages[m] lintMessage(m, message) const arr = message.request .filter(r => r.name !== 'sessionID') // We have the engine handle this under the hood .map(r => { const rtype = figureType(r.type) return `readonly ${r.name}${ r.hasOwnProperty('default') || rtype.endsWith('| null') ? '?' : '' }: ${rtype}` }) const noParams = !arr.length const inParam = noParams ? 'void' : `{${arr.join(',')}}` const name = `${json.protocol}${capitalize(m)}` const outParam = figureType(message.response) const methodName = `'${json.namespace}.${json.protocol}.${m}'` const isUIMethod = isUIProtocol || enabledCall(methodName, 'incoming') if (isUIMethod) { project.incomingMaps[ methodName ] = `(params: MessageTypes[${methodName}]['inParam'] & {sessionID: number}) => IncomingReturn` if (!message.hasOwnProperty('notify')) { project.customResponseIncomingMaps[ methodName ] = `(params: MessageTypes[${methodName}]['inParam'] & {sessionID: number}, response: {error: IncomingErrorCallback, result: (res: MessageTypes[${methodName}]['outParam']) => void}) => IncomingReturn` } } const rpcPromise = isUIMethod ? '' : rpcPromiseGen(methodName, name, false) const rpcPromiseType = isUIMethod ? '' : rpcPromiseGen(methodName, name, true) const engineListener = isUIMethod ? '' : engineListenerGen(methodName, name, false) const engineListenerType = isUIMethod ? '' : engineListenerGen(methodName, name, true) if (rpcPromise.length) { project.hasEngine = true } if (engineListener) { project.hasEngineListener = true } const cleanName = methodName.substring(1, methodName.length - 1) if (!enabledCalls[cleanName]) { project.notEnabled.push(methodName) } // Must be an rpc we use if (rpcPromiseType || engineListenerType || isUIMethod) { map[methodName] = { inParam, outParam: outParam === 'null' ? 'void' : outParam, rpcPromise, rpcPromiseType, engineListener, engineListenerType, } } return map }, {}) } function enabledCall(methodName, type) { const cleanName = methodName.substring(1, methodName.length - 1) return enabledCalls[cleanName] && enabledCalls[cleanName][type] } function engineListenerGen(methodName, name, justType) { if (!enabledCall(methodName, 'engineListener')) { return '' } return justType ? `declare export function ${name}RpcListener (p: {params: MessageTypes[${methodName}]['inParam'], incomingCallMap: IncomingCallMapType, customResponseIncomingCallMap?: CustomResponseIncomingCallMap, waitingKey?: WaitingKey}): CallEffect<void, () => void, Array<void>>` : `export const ${name}RpcListener = (p: {params: MessageTypes[${methodName}]['inParam'], incomingCallMap: IncomingCallMapType, customResponseIncomingCallMap?: CustomResponseIncomingCallMap, waitingKey?: WaitingKey}, listenerApi: ListenerApi) => getEngineListener()({method: ${methodName}, params: p.params, incomingCallMap: p.incomingCallMap, customResponseIncomingCallMap: p.customResponseIncomingCallMap, waitingKey: p.waitingKey}, listenerApi)` } function rpcPromiseGen(methodName, name, justType) { if (!enabledCall(methodName, 'promise')) { return '' } return justType ? `declare export function ${name}RpcPromise (params: MessageTypes[${methodName}]['inParam'], waitingKey?: WaitingKey): Promise<MessageTypes[${methodName}]['outParam']>` : `export const ${name}RpcPromise = (params: MessageTypes[${methodName}]['inParam'], waitingKey?: WaitingKey) => new Promise<MessageTypes[${methodName}]['outParam']>((resolve, reject) => engine()._rpcOutgoing({method: ${methodName}, params, callback: (error: SimpleError, result: MessageTypes[${methodName}]['outParam']) => error ? reject(error) : resolve(result), waitingKey}))` } function maybeIfNot(s) { if (s.endsWith('| null')) return s return `${s} | null` } // Type parsing function parseInnerType(t) { if (!t) { return 'void' // keep backwards compat with old script } if (t.constructor === Array) { if (t.length === 2 && t[0] === null) { return maybeIfNot(figureType(t[1])) } else { return parseUnion(t) } } switch (t.type) { case 'record': return parseRecord(t) default: return figureType(t) } } function parseEnumSymbol(s) { var parts = s.split('_') return parseInt(parts.pop(), 10) } function parseEnum(t) { return parseUnion(t.symbols.map(s => `${parseEnumSymbol(s)} // ${s}\n`)) } function parseUnion(unionTypes) { return unionTypes.map(parseInnerType).join(' | ') } function parseRecord(t) { lintRecord(t) if (t.typedef) { return capitalize(t.typedef) } const fields = t.fields .map(f => { const innerType = parseInnerType(f.type) const innerOptional = innerType.endsWith('| null') const capsInnerType = capitalize(innerType) const name = f.mpackkey || f.name const comment = f.mpackkey ? ` /* ${f.name} */ ` : '' // If we have a maybe type, let's also make the key optional return `readonly ${name}${comment}${innerOptional ? '?' : ''}: ${capsInnerType},` }) .join('') return `{${fields}}` } function parseVariant(t, project) { var parts = t.switch.type.split('.') if (parts.length > 1) { project = projects[parts.shift()] } const rootType = t.switch.type const rootEnum = project.enums[rootType] let unhandled = new Set(Object.keys(rootEnum)) var type = parts.shift() const cases = t.cases .map(c => { if (c.label.def) { return null } else { var label = fixCase(c.label.name) unhandled.delete(label) let bodyType = '' if (c.body === null) { bodyType = 'null' } else if (typeof c.body === 'string') { bodyType = capitalize(c.body) } else if (c.body.type === 'array') { bodyType = `Array<${capitalize(c.body.items)}>` } const bodyStr = c.body ? `, ${label}: ${bodyType}` : '' return `{ ${t.switch.name}: ${type}.${label}${bodyStr} }` } }) .filter(Boolean) const otherCases = [...unhandled].map(label => `{ ${t.switch.name}: ${type}.${label}}`) const s = [...cases, ...otherCases].join(' | ') return s || 'void' } function writeActions() { const staticActions = { disconnected: {}, connected: {}, } const seenProjects = {} const data = { actions: Object.keys(projects).reduce((map, p) => { const callMap = projects[p].incomingMaps callMap && Object.keys(callMap).reduce((map, method) => { const name = method .replace(/'/g, '') .split('.') .map((p, idx) => (idx ? capitalize(p) : p)) .join('') seenProjects[p] = true let response = '' if (projects[p].customResponseIncomingMaps[method]) { response = `, response: {error: ${p}Types.IncomingErrorCallback, result: (param: ${p}Types.MessageTypes[${method}]['outParam']) => void}` } map[name] = { params: `${p}Types.MessageTypes[${method}]['inParam'] & {sessionID: number}${response}`, } return map }, map) return map }, staticActions), } const toWrite = JSON.stringify( { prelude: Object.keys(seenProjects).map( p => `import type * as ${p}Types from '../constants/types/${projects[p].out}'` ), ...data, }, null, 4 ) fs.writeFileSync(`js/engine-gen.json`, toWrite) } function writeAll() { const imports = Object.keys(projects) .map( p => `import type { CustomResponseIncomingCallMap as ${p}CustomResponseIncomingCallMap, IncomingCallMapType as ${p}IncomingCallMap, } from './${projects[p].out}' ` ) .join('\n') const exports = ` export type IncomingCallMapType = ${Object.keys(projects) .map(p => `${p}IncomingCallMap`) .join(' & ')} export type CustomResponseIncomingCallMapType = ${Object.keys(projects) .map(p => `${p}CustomResponseIncomingCallMap`) .join(' & ')} ` const toWrite = [imports, exports].join('\n') const destinationFile = `types/rpc-all-gen.tsx` // Only used by prettier so we can set an override in .prettierrc const formatted = prettier.format(toWrite, { ...prettier.resolveConfig.sync(destinationFile), parser: 'typescript', }) fs.writeFileSync(`js/rpc-all-gen.tsx`, formatted) } function writeFlow(typeDefs, project) { const importMap = { Gregor1: "import * as Gregor1 from './rpc-gregor-gen'", Keybase1: "import * as Keybase1 from './rpc-gen'", Stellar1: "import * as Stellar1 from './rpc-stellar-gen'", } const engineImports = [ project.hasEngine ? 'getEngine as engine' : '', project.hasEngineListener ? 'getEngineListener, type ListenerApi' : '', ] .filter(f => f.length) .join(', ') const engineImport = engineImports.length ? `import {${engineImports}} from '../../engine/require'` : '' const typePrelude = `/* eslint-disable */ // This file is auto-generated by client/protocol/Makefile. ${engineImport} ${project.import.map(n => importMap[n] || '').join('\n')} ${project.import.map(n => `export {${n}}`).join('\n')} export type Bool = boolean export type Boolean = boolean export type Bytes = Buffer export type Double = number export type Int = number export type Int64 = number export type Long = number export type String = string export type Uint = number export type Uint64 = number ${project.hasEngine ? 'type WaitingKey = string | Array<string>' : ''} type SimpleError = {code?: number, desc?: string} export type IncomingErrorCallback = (err?: SimpleError | null) => void ${ project.hasEngine ? ` import type {TypedActions} from '../../actions/typed-actions-gen' type IncomingReturn = Promise<Array<TypedActions> | TypedActions | false | undefined | void> | Array<TypedActions> | TypedActions | false | undefined | void ` : '' } ` const consts = Object.keys(typeDefs.consts).map(k => typeDefs.consts[k]) const types = Object.keys(typeDefs.types).map(k => typeDefs.types[k]) const messagePromise = Object.keys(typeDefs.messages).map(k => typeDefs.messages[k].rpcPromise) const messageEngineListener = Object.keys(typeDefs.messages).map(k => typeDefs.messages[k].engineListener) const callMapType = Object.keys(project.incomingMaps).length ? 'IncomingCallMapType' : 'void' const incomingMap = `\nexport type IncomingCallMapType = { ${Object.keys(project.incomingMaps) .map(im => ` ${im}?: ${project.incomingMaps[im]}`) .join(',')} }` const customResponseCallMapType = Object.keys(project.customResponseIncomingMaps).length ? 'CustomResponseIncomingCallMap' : 'void' const customResponseIncomingMap = `\nexport type CustomResponseIncomingCallMap = { ${Object.keys(project.customResponseIncomingMaps) .map(im => ` ${im}?: ${project.customResponseIncomingMaps[im]}`) .join(',')} }` const messageTypesData = Object.keys(typeDefs.messages) .map(k => { const data = typeDefs.messages[k] const types = {} return ` ${k}: { inParam: ${data.inParam}, outParam: ${data.outParam || 'void'}, },` }) .sort() .join('\n') const messageTypes = `\nexport type MessageTypes = { ${messageTypesData} }` const data = [ messageTypes, ...[...consts, ...types].sort(), incomingMap, customResponseIncomingMap, ...[...messagePromise, ...messageEngineListener].sort(), ] .filter(Boolean) .join('\n') const notEnabled = `// Not enabled calls. To enable add to enabled-calls.json:\n// ${project.notEnabled.join( '\n// ' )}` const toWrite = [typePrelude, data, notEnabled].join('\n') const destinationFile = `types/${project.out}` // Only used by prettier so we can set an override in .prettierrc const formatted = prettier.format(toWrite, { ...prettier.resolveConfig.sync(destinationFile), parser: 'typescript', }) fs.writeFileSync(`js/${project.out}.tsx`, formatted) } function write(typeDefs, project) { // Need any for weird flow issue where it gets confused by multiple // incoming call map types const callMapType = Object.keys(project.incomingMaps).length ? 'IncomingCallMapType' : 'void' const typePrelude = `/* eslint-disable */ // This file is auto-generated by client/protocol/Makefile. // Not enabled: calls need to be turned on in enabled-calls.json import {getEngine as engine} from '../../engine/require' ` const consts = Object.keys(typeDefs.consts).map(k => typeDefs.consts[k]) const messagePromise = Object.keys(typeDefs.messages).map(k => typeDefs.messages[k].rpcPromise) const messageEngineListener = Object.keys(typeDefs.messages).map(k => typeDefs.messages[k].engineListener) const data = [...consts, ...messagePromise, ...messageEngineListener].filter(Boolean).sort().join('\n') const toWrite = [typePrelude, data].join('\n') const destinationFile = `types/${project.out}` // Only used by prettier so we can set an override in .prettierrc const formatted = prettier.format(toWrite, { ...prettier.resolveConfig.sync(destinationFile), parser: 'typescript', }) fs.writeFileSync(`js/${project.out}.tsx`, formatted) } function decapitalize(s) { return s.charAt(0).toLowerCase() + s.slice(1) } const shorthands = [ {re: /Tty([A-Zs]|$)/g, into: 'TTY$1', re2: /^TTY/, into2: 'tty'}, {re: /Tlf([A-Zs]|$)/g, into: 'TLF$1', re2: /^TLF/, into2: 'tlf'}, {re: /Uid([A-Zs]|$)/g, into: 'UID$1', re2: /^UID/, into2: 'uid'}, {re: /Kid([A-Zs]|$)/g, into: 'KID$1', re2: /^KID/, into2: 'kid'}, {re: /Cli([A-Z]|$)/g, into: 'CLI$1', re2: /^CLI/, into2: 'cli'}, {re: /Api([A-Zs]|$)/g, into: 'API$1', re2: /^API/, into2: 'api'}, {re: /Btc([A-Z]|$)/g, into: 'BTC$1', re2: /^BTC/, into2: 'btc'}, {re: /Pgp([A-Z]|$)/g, into: 'PGP$1', re2: /^PGP/, into2: 'pgp'}, {re: /Gpg([A-Z]|$)/g, into: 'GPG$1', re2: /^GPG/, into2: 'gpg'}, {re: /Uri([A-Zs]|$)/g, into: 'URI$1', re2: /^URI/, into2: 'uri'}, {re: /Gui([A-Z]|$)/g, into: 'GUI$1', re2: /^GUI/, into2: 'gui'}, {re: /Kbfs([A-Z]|$)/g, into: 'KBFS$1', re2: /^KBFS/, into2: 'kbfs'}, {re: /Json([A-Z]|$)/g, into: 'JSON$1', re2: /^JSON/, into2: 'json'}, {re: /Ed25519([A-Z]|$)/g, into: 'ED25519$1', re2: /^ED25519/, into2: 'ed25519'}, {re: /Id([A-Zs]|$)/g, into: 'ID$1', re2: /^ID/, into2: 'id'}, {re: /Kv([A-Zs]|$)/g, into: 'KV$1', re2: /^KV/, into2: 'kv'}, {re: /Ui([A-Z]|$)/g, into: 'UI$1', re2: /^UI/, into2: 'ui'}, // this has to be placed after the one for UID {re: /Fs([A-Z]|$)/g, into: 'FS$1', re2: /^FS/, into2: 'fs'}, {re: /Md([A-Z]|$)/g, into: 'MD$1', re2: /^MD/, into2: 'md'}, {re: /Ok([A-Z]|$)/g, into: 'OK$1', re2: /^OK/, into2: 'ok'}, ] function camelcaseWithSpecialHandlings(s, shouldCapitalize) { const capitalized = capitalize(camelcase(s)) let specialized = capitalized for (const shorthand of shorthands) { specialized = specialized.replace(shorthand.re, shorthand.into) } specialized = specialized.replace(/[Tt][Ll][Ff][Ii][Dd]([A-Zs]|$)/g, 'TLFID$1') // since the handling FS would replace TLFs with TLFS specialized = specialized.replace(/T[Ll]FS/g, 'TLFs') if (shouldCapitalize) { return specialized } for (let shorthand of shorthands) { specialized = specialized.replace(shorthand.re2, shorthand.into2) } return decapitalize(specialized) } function lintTypedef(record, typedef) { switch (typedef) { case 'int64': case 'uint': case 'uint64': lintError( `${record.name}: ${typedef} cannot be fully represented as a Javascript number (double)`, record.lint ) break } } function lintRecord(record) { lintTypedef(record, record.typedef) const rName = camelcaseWithSpecialHandlings(record.name, true) if (rName !== record.name) { lintError(`Record name ${record.name} should be ${rName}`, record.lint) } record.fields.forEach(f => { const fName = camelcaseWithSpecialHandlings(f.name, false) if (fName !== f.name) { lintError(`Record variable name ${record.name}.${f.name} should be ${rName}.${fName}`, f.lint) } if (f.type === 'bool') { lintError(`Use boolean instead of bool: ${f.name}`) } }) } function lintMessage(name, message) { const mName = camelcaseWithSpecialHandlings(name, false) if (mName !== name) { lintError(`Method name ${name} should be ${mName}`, message.lint) } message.request.forEach(f => { const fName = camelcaseWithSpecialHandlings(f.name, false) if (fName !== f.name) { lintError(`Method arg name ${f.name} should be ${fName}`, message.lint) } if (f.type === 'bool') { lintError(`Use boolean instead of bool: ${f.name}`) } }) } function lintJSON(json) { const pName = camelcaseWithSpecialHandlings(json.protocol, true) if (pName !== json.protocol) { // Ignore protocol name lint errors by default // lintError(`Protocol names should be capitalized: ${json.protocol}`, 'ignore') } } function lintError(s, lint) { if (lint === 'ignore') { // console.log('Ignoring lint error:', colors.yellow(s)) } else { console.log(colors.red(s)) process.exit(1) } } const keys = Object.keys(projects) keys.forEach(key => { const project = projects[key] const typeDefs = fs .readdirSync(project.root) .filter(jsonOnly) .map(file => load(file, project)) .map(json => analyze(json, project)) .reduce((map, next) => { map.consts = {...map.consts, ...next.consts} map.types = {...map.types, ...next.types} map.messages = {...map.messages, ...next.messages} return map }, {}) writeFlow(typeDefs, project) }) writeAll() writeActions()