#!/usr/bin/env node require('source-map-support').install() import { readFileSync } from "fs"; import * as fs from "fs"; import * as ts from "typescript"; import * as path from 'path' import { Platform, CheckPlatform, GetModuleMap, platforms } from './base' import { PlatformString, GetJsonValueString } from './cloudMetaData' import { CreatDartSdk } from './buildDartSdk' import { VerifyOptions, IClientCacheMode } from "./leancloud-cloud-decorator"; // import * as vm from 'vm' // require('./cloud/index') function printNode(sourceFile: ts.SourceFile) { let resultText = '' var deep = 0; function scanNode(node: ts.Node) { var space = '' for (var i = 0; i < deep; ++i) space += ' ' console.log(space + deep + '.' + ts.SyntaxKind[node.kind]) console.log(space + node.getText()) console.log() deep += 1 ts.forEachChild(node, scanNode); deep -= 1 } ts.forEachChild(sourceFile, scanNode); return resultText; } function getFunctionName(node: ts.MethodDeclaration) { let classNode = node.parent if (!classNode.name || !node.name) throw new Error('missing classNode.name or node.name') let functionName = classNode.name.getText() + '.' + node.name.getText() return functionName } function getReturnTypeDeclare(node: ts.MethodDeclaration) { return ((node.type as ts.NodeWithTypeArguments)?.typeArguments?.[0]?.getText()) || 'any' } function createCloudRunText(node: ts.MethodDeclaration, method = 'run', clientCache?: string, versionCb?: string) { let functionName = getFunctionName(node) if (clientCache) { let clientCacheConfig = JSON.parse(clientCache.replace('undefined', '""')) as { keyPath: string[][], mode?: IClientCacheMode, offlineDefaultCache?: any } let defaultCache = clientCacheConfig.offlineDefaultCache let defaultCacheText = defaultCache ? JSON.stringify(defaultCache, null, 2) : 'undefined' let versionString = 'let version = ' + (versionCb ? `options?.clientCacheVersionParams && (${versionCb})(options!.clientCacheVersionParams).toString() || ''` : '""') let keyPath = ` (${JSON.stringify(clientCacheConfig.keyPath)}.findIndex((e:string[]) => e.every(e => Object.keys(params||{}).includes(e)) && Object.keys(params||{}).every(e2 => e.includes(e2))) >= 0) ? "${functionName}"+(version&&("_"+version)||"")+"?"+` + "Object.keys(params||{}).map(e=>`${e}=${Array.isArray(params![e])?(params![e] as any[]).map(e=>encodeURIComponent(e)).join('|'):encodeURIComponent(params![e])}`).join('&')" + ` :undefined ` if (node.parameters.length > 0) { let parameterName = node.parameters[0].name.getText() return `{ ${versionString} return API.${method}('${functionName}',${parameterName},undefined,true,version||undefined,${keyPath},options?.mode||"${clientCacheConfig.mode || 'remote'}",${defaultCacheText},options?.onData,options?.onError,options?.onInvokeCallback) }` } return `{ ${versionString} return API.${method}('${functionName}',undefined,undefined,true,version||undefined,${keyPath},options?.mode||"${clientCacheConfig.mode || 'remote'}",${defaultCacheText},options?.onData,options?.onError,options?.onInvokeCallback) }` } if (node.parameters.length > 0) { let parameterName = node.parameters[0].name.getText() return `{return API.${method}('${functionName}',${parameterName}) }` } return `{return API.${method}('${functionName}') }` } // function ExpressionToJson(expression:ts.Node):any{ // switch(expression.kind){ // case ts.SyntaxKind.ObjectLiteralExpression:return ObjectLiteralExpressionToJson(expression) // case ts.SyntaxKind.ArrayLiteralExpression:return ArrayLiteralExpressionToJson(expression) // case ts.SyntaxKind.StringLiteral:return expression.getText() // case ts.SyntaxKind.NumericLiteral:return parseFloat( expression.getText() ) // case ts.SyntaxKind.TrueKeyword:return true // case ts.SyntaxKind.FalseKeyword:return false // case ts.SyntaxKind.NullKeyword:return null // case ts.SyntaxKind.PropertyAccessExpression: expression.getText() // default: // throw new Error('unknow expression '+ts.SyntaxKind[expression.kind]) // } // } // function ArrayLiteralExpressionToJson(expression:ts.Node){ // let array:any[] = [] // let arrayLiteralExpression = expression as ts.ArrayLiteralExpression; // for(var i=0;inodes.push(n)) // for(let i=0;inodes.push(n)) // // // console.log(nodes) // // let name = nodes[0].getText() // // for(let i=1;i m.kind === ts.SyntaxKind.ExportKeyword) } function getModuleExportNameText(name: ts.ModuleExportName): string { return ts.isIdentifier(name) ? name.escapedText.toString() : name.text } function GetImportName(importSpecifier: ts.ImportSpecifier) { return ((importSpecifier.propertyName && (getModuleExportNameText(importSpecifier.propertyName) + ' as ')) || '') + importSpecifier.name.escapedText.toString() } export function IsExportDisabled(node: ts.Node) { return node.getFullText().includes('@lcc-export-disabled') } //https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API //在线查看代码ast的工具 https://ts-ast-viewer.com/ function createSdkFile(sourceFile: ts.SourceFile) { let results: string[] = [] let lastPositions: number[] = []; let exportText = '' // for(let i=0;i" + text+'=>'+start) results[platform] += text lastPositions[platform] = end; // console.log('results:') // console.log(results[platform]) } function appendText(text: string, platform: number) { results[platform] += text } function scanNode(node: ts.Node) { switch (node.kind) { case ts.SyntaxKind.FunctionDeclaration: { if (IsExportDisabled(node)) { skipAllNode(node) break } let functionDeclaration = node if (!IsInternalName(functionDeclaration) && functionDeclaration.modifiers && functionDeclaration.modifiers.find(e => e.kind == ts.SyntaxKind.ExportKeyword)) { } else { skipAllNode(node) } } break; case ts.SyntaxKind.ExpressionStatement: case ts.SyntaxKind.IfStatement: case ts.SyntaxKind.ExportAssignment: skipAllNode(node) break case ts.SyntaxKind.TypeAliasDeclaration: case ts.SyntaxKind.VariableStatement: { if (IsExportDisabled(node)) { skipAllNode(node) break } const declaration = node as ts.TypeAliasDeclaration | ts.VariableStatement if (hasExportModifier(declaration)) { } else { skipAllNode(node) } } break case ts.SyntaxKind.ImportDeclaration: { if (IsExportDisabled(node)) { skipAllNode(node) break } const skipModuleNames = [ './cloud', './index', './base', 'bluebird', 'leancloud-cloud-decorator' ] let importDeclaration = node let moduleName = importDeclaration.moduleSpecifier.getText() // console.log(moduleName.substring(1, moduleName.length - 1)) moduleName = moduleName.substring(1, moduleName.length - 1) if (!moduleName.includes('..') && !moduleName.includes('.json') && !skipModuleNames.includes(moduleName)) { let importClause = importDeclaration.importClause if (importClause) { let text = '' if (!node.getText().includes('_')) { text = node.getText() if (moduleMap[moduleName]) { text = text.replace(moduleName, moduleMap[moduleName]) } } else { //去除_开头的引用项目 if (importClause.name && !IsInternalName(importClause)) { text += importClause.name.escapedText.toString() } let namedImports = importClause.namedBindings if (namedImports && namedImports.elements) { let names = namedImports.elements.filter(e => !IsInternalName(e)).map(e => GetImportName(e)) if (names.length > 0) { text += ((text && ', ') || '') + `{ ${names.join(', ')} }` } } if (text) { let moduleName2 = moduleMap[moduleName] || moduleName text = `import ${text} from '${moduleName2}'` } } // for (let i = 0; i < Object.keys(Platform).length; ++i) { let i = 0 appendText(text + '\n', i) } } } skipAllNode(node) } break case ts.SyntaxKind.ImportEqualsDeclaration: { if (IsExportDisabled(node)) { skipAllNode(node) break } let importEqualsDeclaration = node if (importEqualsDeclaration.moduleReference.kind == ts.SyntaxKind.QualifiedName) { } else { skipAllNode(node) } // let moduleName = (importEqualsDeclaration.moduleReference).expression.getText() // let importName = importEqualsDeclaration.name.getText() // // console.log(moduleName) // if (moduleName[1] != '.') { // let importText = `import * as ${importName} from ${moduleName}` // for (let i = 0; i < Platform.count; ++i) { // // appendText(node.getText() + '\n', i) // appendText(importText + '\n', i) // } // } } break case ts.SyntaxKind.InterfaceDeclaration: { if (IsExportDisabled(node)) { skipAllNode(node) break } let interfaceNode = node //是否需要增加 export let needExport = true if (interfaceNode.modifiers) { if (interfaceNode.modifiers.find(x => x.kind == ts.SyntaxKind.ExportKeyword)) { needExport = false } } if (needExport) { // for(let i=0;inode let needExport = true if (classNode.modifiers) { if (classNode.modifiers.find(x => x.kind == ts.SyntaxKind.ExportKeyword)) { needExport = false } } if (needExport) { // for(let i=0;inode let decorators = getDecorators(methodNode) if (decorators) { let needSkip = true; for (let i = 0; i < decorators.length; ++i) { // console.log(JSON.stringify(decorator)) let decorator = decorators[i].getText() if (decorator.substring(0, 6) == '@Cloud') { // let sandbox = { // result :{platforms:[],rpc:false}, // Platform:Platform // } // vm.createContext(sandbox); // Contextify the sandbox. // let cloudFunction = decorator.substring(1) // let genericIndex = cloudFunction.indexOf('Cloud<') // if (genericIndex >= 0) { // cloudFunction = 'Cloud' + cloudFunction.substring(cloudFunction.indexOf('>')+1) // } // let code = 'function Cloud(p){result = p} '+ cloudFunction // vm.runInContext(code, sandbox); let platformText = PlatformString(decorator) // console.log(paramsText) let platforms = platformText && JSON.parse(platformText) let rpcText = GetJsonValueString(decorator, 'rpc') let rpc = rpcText && JSON.parse(rpcText) let internalText = GetJsonValueString(decorator, 'internal') let internal = internalText && JSON.parse(internalText) let verifyText = GetJsonValueString(decorator, 'verify') let verify = verifyText && JSON.parse(verifyText) as VerifyOptions let clientCache = GetJsonValueString(decorator, 'clientCache') let versionCb: string | undefined = undefined if (clientCache) { [versionCb, clientCache] = GetJsonValueString(clientCache, 'versionCb', true) } let clientCacheVersionParams = versionCb && (versionCb.indexOf(":") >= 0 ? GetJsonValueString(versionCb, '', false, 0) : 'any') needSkip = false; // let parameters = sandbox.result || {} // let platforms:string[] = parameters.platforms // let keys = Object.keys(Platform) // for (let i = 0; i < keys.length; ++i) { // let s = keys[i].replace('_', '-') // let s = targetPlatform.replace('_', '-') if (internal || (platforms && !platforms.includes(targetPlatform))) { skipNode(node, node, i) } else if (methodNode.body) { skipText(decorators[0].getStart(), decorators[decorators.length - 1].getEnd(), i) skipNode(methodNode.body, methodNode.body, i) let text = results[i] let lastIndex = text.lastIndexOf(')') if (verify) { let VerifyParamsText = '' if (verify.type == 'geetest') { VerifyParamsText = `& { cloudVerify? :{sessionId:string,data:{ geetest_challenge:string geetest_seccode:string geetest_validate:string }} }` } else if (verify.type == 'sms') { VerifyParamsText = `& { cloudVerify? :{sessionId:string,data:{ mobilePhoneNumber:string smsCode:string }} }` } if (VerifyParamsText) { results[i] = ClipParamsText(text.substring(0, lastIndex)) + VerifyParamsText + text.substring(lastIndex) } } if (clientCache) { results[i] = ClipParamsText(text.substring(0, lastIndex)) + `,options?:{ /** * 覆盖默认的访问模式 */ mode?: 'remote' | 'local' | 'localFirst' | 'remoteFirst' /** * 云引擎返回数据调用此函数 */ onData?: (data : ${getReturnTypeDeclare(methodNode)}) => void /** * 使用缓存后,远端请求报错时,调用此回调 */ onError?: (data) => void /** * onData的同级回调,目前仅用于调试用 */ onInvokeCallback?: (params: { mode: 'remote' | 'local' | 'localFirst' | 'remoteFirst' function: string params: any }) => void ${clientCacheVersionParams ? "clientCacheVersionParams:" + clientCacheVersionParams : ""} }`+ text.substring(lastIndex) } appendText(createCloudRunText(methodNode, rpc ? 'rpc' : 'run', clientCache, versionCb), i) } } break } } if (needSkip) skipAllNode(node) } else skipAllNode(node) } break } } ts.forEachChild(sourceFile, scanNode); if (!exportText) return null // for(let i=0;i { if (diagnostic.file) { let { line, character } = diagnostic.file.getLineAndCharacterOfPosition( diagnostic.start! ); let message = ts.flattenDiagnosticMessageText( diagnostic.messageText, "\n" ); console.log( `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}` ); } else { console.log( `${ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}` ); } }); let exitCode = emitResult.emitSkipped ? 1 : 0; console.log(`Process exiting with code '${exitCode}'.`); } let targetPlatform = CheckPlatform(process.argv[2]) let moduleMap = GetModuleMap(targetPlatform) moduleMap['leanengine'] = moduleMap['leanengine'] || moduleMap['leancloud-storage'] || 'leancloud-storage' // console.log('clear last build....') // clearOldBuild() if (platforms[targetPlatform].type == 'dart') { CreatDartSdk({ platform: targetPlatform as string, dirroot: _dirroot }) } else { const exclude = ['cloud.ts', 'index.ts', 'base.ts'] let dir = fs.readdirSync(_dirroot + 'src/cloud/') console.log('build typescript sdk....') createSdk(dir, exclude) } // console.log('compile....') // compileAndPush() // let sourceFile = ts.createSourceFile( // 'story.ts', // readFileSync(__dirname + '/../src/cloud/story.ts').toString(), // ts.ScriptTarget.ES2015, // /*setParentNodes */ true // ); // console.log(printNode(sourceFile)) // let sourceFile = ts.createSourceFile( // 'story.ts', // readFileSync(__dirname + '/../src/cloud/story.ts').toString(), // ts.ScriptTarget.ES2015, // /*setParentNodes */ true // ); // // console.log(printNode(sourceFile)) // var sdks = createSdk(sourceFile) // for(let i=0;i