() => {
            let dataBuffer = new Buffer('/**QUERY File**/ query things');
            let filename = 'queries.qry';
            mockClientService.createQueryFile.returns(mockQueryFile);

            // Run method
            component.createQuery(dataBuffer);

            // Assertions
            component.fileType.should.equal('qry');
            mockClientService.createQueryFile.should.have.been.calledWith(filename, dataBuffer.toString());
            component.currentFile.should.deep.equal(mockQueryFile);
            component.currentFileName.should.equal(filename);
        }


() => {
        it('should establish a readme file', async(() => {
            let dataBuffer = new Buffer('/**README File**/ read all the things');

            // Run method
            component.createReadme(dataBuffer);

            // Assertions
            component.fileType.should.equal('md');
            component.currentFileName.should.equal('README.md');
            component.currentFile.should.equal(dataBuffer.toString());
        }));

    }


() => {
            let dataBuffer = new Buffer('/**README File**/ read all the things');

            // Run method
            component.createReadme(dataBuffer);

            // Assertions
            component.fileType.should.equal('md');
            component.currentFileName.should.equal('README.md');
            component.currentFile.should.equal(dataBuffer.toString());
        }


() => {
        it('should set current file to a script file, created by calling createScript with correct parameters', async(() => {
            let mockScript = sinon.createStubInstance(Script);
            mockScript.getIdentifier.returns('lib/script.js');
            mockClientService.getScripts.returns([]);
            mockClientService.createScriptFile.returns(mockScript);
            component.fileType = 'js';

            // Run method
            component.changeCurrentFileType();

            // Assertions
            mockClientService.createScriptFile.getCall(0).args[0].should.equal('lib/script.js');
        }));

        it('should increment a script file name if one already exists', async(() => {
            let mockScript = sinon.createStubInstance(Script);
            let mockScript0 = sinon.createStubInstance(Script);
            let mockScript1 = sinon.createStubInstance(Script);
            mockScript.getIdentifier.returns('lib/script.js');
            mockScript0.getIdentifier.returns('lib/script0.js');
            mockScript1.getIdentifier.returns('lib/script1.js');
            mockClientService.getScripts.returns([mockScript, mockScript0, mockScript1]);
            mockClientService.createScriptFile.returns(mockScript);

            component.fileType = 'js';

            // Run method
            component.changeCurrentFileType();

            // Assertions
            mockClientService.createScriptFile.getCall(0).args[0].should.equal('lib/script2.js');
        }));

        it('should change this.currentFileType to a cto file', async(() => {
            mockClientService.getModelFiles.returns([]);
            let b = new Blob(
                [`/**
 * New model file
 */

namespace org.acme.model`],
                {type: 'text/plain'}
            );
            let file = new File([b], 'models/org.acme.model.cto');
            let dataBuffer = new Buffer(`/**
 * New model file
 */

namespace org.acme.model`);
            let mockModel = new ModelFile(mockModelManager, dataBuffer.toString(), file.name);
            mockClientService.createModelFile.returns(mockModel);
            component.fileType = 'cto';

            // Run method
            component.changeCurrentFileType();

            // Assertions
            component.currentFileName.should.equal('models/org.acme.model.cto');
            component.currentFile.should.deep.equal(mockModel);
        }));

        it('should append the file number to the cto file name', () => {

            let b = new Blob(
                [`/**
 * New model file
 */

namespace org.acme.model`],
                {type: 'text/plain'}
            );
            let file = new File([b], 'org.acme.model.cto');
            let dataBuffer = new Buffer(`/**
 * New model file
 */

namespace org.acme.model`);
            let mockModel = new ModelFile(mockModelManager, dataBuffer.toString(), file.name);

            // One element, so the number 0 should be appended
            mockClientService.getModelFiles.returns([mockModel]);

            component.fileType = 'cto';

            // Run method
            component.changeCurrentFileType();

            // Assertions
            mockClientService.createModelFile.getCall(0).args[1].should.be.equal('models/org.acme.model0.cto');
            component.currentFileName.should.equal('models/org.acme.model0.cto');
        });

        it('should fill in template model name indices for a cto file name', async(() => {
            let mockFile = sinon.createStubInstance(ModelFile);
            mockFile.getNamespace.returns('org.acme.model');
            let mockFile0 = sinon.createStubInstance(ModelFile);
            mockFile0.getNamespace.returns('org.acme.model0');
            let mockFile1 = sinon.createStubInstance(ModelFile);
            mockFile1.getNamespace.returns('org.acme.model1');
            let mockFile3 = sinon.createStubInstance(ModelFile);
            mockFile3.getNamespace.returns('org.acme.model3');
            let mockFile4 = sinon.createStubInstance(ModelFile);
            mockFile4.getNamespace.returns('org.acme.model4');
            mockClientService.getModelFiles.returns([mockFile, mockFile0, mockFile1, mockFile3, mockFile4]);

            let b = new Blob(
                [`/**
 * New model file
 */

namespace org.acme.model`],
                {type: 'text/plain'}
            );
            let file = new File([b], 'org.acme.model.cto');
            let dataBuffer = new Buffer(`/**
 * New model file
 */

namespace org.acme.model`);

            let mockModel = new ModelFile(mockModelManager, dataBuffer.toString(), file.name);
            mockClientService.createModelFile.returns(mockModel);
            component.fileType = 'cto';

            // Run method
            component.changeCurrentFileType();

            // Assertions
            component.currentFileName.should.equal('models/org.acme.model2.cto');
        }));

        it('should change current file to a query file upon calling createQueryFile', () => {

            let dataBuffer = new Buffer(`/**
 * New query file
 */`);

            let mockQuery = new QueryFile('queries.qry', mockQueryManager, dataBuffer.toString());
            mockClientService.createAclFile.returns(mockQuery);
            component.fileType = 'qry';

            component.changeCurrentFileType();

            component.currentFileName.should.equal('queries.qry');

        });

        it('should change current file to an acl file upon calling createAclFile', () => {

            let dataBuffer = new Buffer(`/**
 * New access control file
 */
 rule AllAccess {
     description: "AllAccess - grant everything to everybody."
     participant: "org.hyperledger.composer.system.Participant"
     operation: ALL
     resource: "org.hyperledger.composer.system.**"
     action: ALLOW
 }`);

            let mockAcl = new AclFile('permissions.acl', mockAclManager, dataBuffer.toString());
            mockClientService.createAclFile.returns(mockAcl);
            component.fileType = 'acl';

            component.changeCurrentFileType();

            component.currentFileName.should.equal('permissions.acl');
        });
    }


() => {
            let mockScript = sinon.createStubInstance(Script);
            mockScript.getIdentifier.returns('lib/script.js');
            mockClientService.getScripts.returns([]);
            mockClientService.createScriptFile.returns(mockScript);
            component.fileType = 'js';

            // Run method
            component.changeCurrentFileType();

            // Assertions
            mockClientService.createScriptFile.getCall(0).args[0].should.equal('lib/script.js');
        }


() => {
            let mockScript = sinon.createStubInstance(Script);
            let mockScript0 = sinon.createStubInstance(Script);
            let mockScript1 = sinon.createStubInstance(Script);
            mockScript.getIdentifier.returns('lib/script.js');
            mockScript0.getIdentifier.returns('lib/script0.js');
            mockScript1.getIdentifier.returns('lib/script1.js');
            mockClientService.getScripts.returns([mockScript, mockScript0, mockScript1]);
            mockClientService.createScriptFile.returns(mockScript);

            component.fileType = 'js';

            // Run method
            component.changeCurrentFileType();

            // Assertions
            mockClientService.createScriptFile.getCall(0).args[0].should.equal('lib/script2.js');
        }


() => {
            mockClientService.getModelFiles.returns([]);
            let b = new Blob(
                [`/**
 * New model file
 */

namespace org.acme.model`],
                {type: 'text/plain'}
            );
            let file = new File([b], 'models/org.acme.model.cto');
            let dataBuffer = new Buffer(`/**
 * New model file
 */

namespace org.acme.model`);
            let mockModel = new ModelFile(mockModelManager, dataBuffer.toString(), file.name);
            mockClientService.createModelFile.returns(mockModel);
            component.fileType = 'cto';

            // Run method
            component.changeCurrentFileType();

            // Assertions
            component.currentFileName.should.equal('models/org.acme.model.cto');
            component.currentFile.should.deep.equal(mockModel);
        }


() => {

            let b = new Blob(
                [`/**
 * New model file
 */

namespace org.acme.model`],
                {type: 'text/plain'}
            );
            let file = new File([b], 'org.acme.model.cto');
            let dataBuffer = new Buffer(`/**
 * New model file
 */

namespace org.acme.model`);
            let mockModel = new ModelFile(mockModelManager, dataBuffer.toString(), file.name);

            // One element, so the number 0 should be appended
            mockClientService.getModelFiles.returns([mockModel]);

            component.fileType = 'cto';

            // Run method
            component.changeCurrentFileType();

            // Assertions
            mockClientService.createModelFile.getCall(0).args[1].should.be.equal('models/org.acme.model0.cto');
            component.currentFileName.should.equal('models/org.acme.model0.cto');
        }


() => {
            let mockFile = sinon.createStubInstance(ModelFile);
            mockFile.getNamespace.returns('org.acme.model');
            let mockFile0 = sinon.createStubInstance(ModelFile);
            mockFile0.getNamespace.returns('org.acme.model0');
            let mockFile1 = sinon.createStubInstance(ModelFile);
            mockFile1.getNamespace.returns('org.acme.model1');
            let mockFile3 = sinon.createStubInstance(ModelFile);
            mockFile3.getNamespace.returns('org.acme.model3');
            let mockFile4 = sinon.createStubInstance(ModelFile);
            mockFile4.getNamespace.returns('org.acme.model4');
            mockClientService.getModelFiles.returns([mockFile, mockFile0, mockFile1, mockFile3, mockFile4]);

            let b = new Blob(
                [`/**
 * New model file
 */

namespace org.acme.model`],
                {type: 'text/plain'}
            );
            let file = new File([b], 'org.acme.model.cto');
            let dataBuffer = new Buffer(`/**
 * New model file
 */

namespace org.acme.model`);

            let mockModel = new ModelFile(mockModelManager, dataBuffer.toString(), file.name);
            mockClientService.createModelFile.returns(mockModel);
            component.fileType = 'cto';

            // Run method
            component.changeCurrentFileType();

            // Assertions
            component.currentFileName.should.equal('models/org.acme.model2.cto');
        }


() => {

            let dataBuffer = new Buffer(`/**
 * New query file
 */`);

            let mockQuery = new QueryFile('queries.qry', mockQueryManager, dataBuffer.toString());
            mockClientService.createAclFile.returns(mockQuery);
            component.fileType = 'qry';

            component.changeCurrentFileType();

            component.currentFileName.should.equal('queries.qry');

        }


() => {

            let dataBuffer = new Buffer(`/**
 * New access control file
 */
 rule AllAccess {
     description: "AllAccess - grant everything to everybody."
     participant: "org.hyperledger.composer.system.Participant"
     operation: ALL
     resource: "org.hyperledger.composer.system.**"
     action: ALLOW
 }`);

            let mockAcl = new AclFile('permissions.acl', mockAclManager, dataBuffer.toString());
            mockClientService.createAclFile.returns(mockAcl);
            component.fileType = 'acl';

            component.changeCurrentFileType();

            component.currentFileName.should.equal('permissions.acl');
        }


() => {
        it('should reset back to default values', async(() => {
            component.expandInput = true;
            component.currentFile = true;
            component.currentFileName = true;
            component.fileType = 'js';

            // Run method
            component.removeFile();

            // Assertions
            component.expandInput.should.not.be.true;
            expect(component.currentFile).to.be.null;
            expect(component.currentFileName).to.be.null;
            component.fileType.should.equal('');
        }));
    }


() => {
            component.expandInput = true;
            component.currentFile = true;
            component.currentFileName = true;
            component.fileType = 'js';

            // Run method
            component.removeFile();

            // Assertions
            component.expandInput.should.not.be.true;
            expect(component.currentFile).to.be.null;
            expect(component.currentFileName).to.be.null;
            component.fileType.should.equal('');
        }


() => {
        let file;
        let mockFileReadObj;
        let mockBuffer;
        let mockFileRead;
        let content;

        beforeEach(() => {
            content = 'hello world';
            let data = new Blob([content], {type: 'text/plain'});
            file = new File([data], 'mock.bna');

            mockFileReadObj = {
                readAsArrayBuffer: sandbox.stub(),
                result: content,
                onload: sinon.stub(),
                onerror: sinon.stub()
            };

            mockFileRead = sinon.stub(window, 'FileReader');
            mockFileRead.returns(mockFileReadObj);
        });

        afterEach(() => {
            mockFileRead.restore();
        });

        it('should return data from a file', () => {
            let promise = component.getDataBuffer(file);
            mockFileReadObj.onload();
            return promise
            .then((data) => {
                // Assertions
                data.toString().should.equal(content);
            });
        });

        it('should give error in promise chain', () => {
            let promise = component.getDataBuffer(file);
            mockFileReadObj.onerror('error');
            return promise
            .then((data) => {
                // Assertions
                data.should.be.null;
            })
            .catch((err) => {
                // Assertions
                err.should.equal('error');
            });
        });
    }


() => {
            content = 'hello world';
            let data = new Blob([content], {type: 'text/plain'});
            file = new File([data], 'mock.bna');

            mockFileReadObj = {
                readAsArrayBuffer: sandbox.stub(),
                result: content,
                onload: sinon.stub(),
                onerror: sinon.stub()
            };

            mockFileRead = sinon.stub(window, 'FileReader');
            mockFileRead.returns(mockFileReadObj);
        }


() => {
            mockFileRead.restore();
        }


() => {
            let promise = component.getDataBuffer(file);
            mockFileReadObj.onload();
            return promise
            .then((data) => {
                // Assertions
                data.toString().should.equal(content);
            });
        }


(data) => {
                // Assertions
                data.toString().should.equal(content);
            }


() => {
            let promise = component.getDataBuffer(file);
            mockFileReadObj.onerror('error');
            return promise
            .then((data) => {
                // Assertions
                data.should.be.null;
            })
            .catch((err) => {
                // Assertions
                err.should.equal('error');
            });
        }


(data) => {
                // Assertions
                data.should.be.null;
            }


(err) => {
                // Assertions
                err.should.equal('error');
            }


() => {

        it('should return true if an acl file is present', () => {
            let fileArray = [];
            fileArray.push({acl: true, id: 'acl file', displayID: 'acl0'});
            fileArray.push({script: true, id: 'script 0', displayID: 'script0'});
            component['files'] = fileArray;

            let result = component['aclExists']();
            result.should.equal(true);

        });

        it('should return false if an acl file is not present', () => {
            let fileArray = [];
            fileArray.push({script: true, id: 'script 0', displayID: 'script0'});
            fileArray.push({script: true, id: 'script 0', displayID: 'script1'});
            component['files'] = fileArray;

            let result = component['aclExists']();
            result.should.equal(false);
        });

    }


() => {
            let fileArray = [];
            fileArray.push({acl: true, id: 'acl file', displayID: 'acl0'});
            fileArray.push({script: true, id: 'script 0', displayID: 'script0'});
            component['files'] = fileArray;

            let result = component['aclExists']();
            result.should.equal(true);

        }


() => {
            let fileArray = [];
            fileArray.push({script: true, id: 'script 0', displayID: 'script0'});
            fileArray.push({script: true, id: 'script 0', displayID: 'script1'});
            component['files'] = fileArray;

            let result = component['aclExists']();
            result.should.equal(false);
        }


() => {

        it('should return true if a query file is present', () => {
            let fileArray = [];
            fileArray.push({query: true, id: 'query file', displayID: 'query0'});
            fileArray.push({script: true, id: 'script 0', displayID: 'script0'});
            component['files'] = fileArray;

            let result = component['queryExists']();
            result.should.equal(true);
        });

        it('should return true if a query file is not present', () => {
            let fileArray = [];
            fileArray.push({script: true, id: 'script 0', displayID: 'script0'});
            fileArray.push({script: true, id: 'script 0', displayID: 'script1'});
            component['files'] = fileArray;

            let result = component['queryExists']();
            result.should.equal(false);
        });

    }


() => {
            let fileArray = [];
            fileArray.push({query: true, id: 'query file', displayID: 'query0'});
            fileArray.push({script: true, id: 'script 0', displayID: 'script0'});
            component['files'] = fileArray;

            let result = component['queryExists']();
            result.should.equal(true);
        }


() => {
            let fileArray = [];
            fileArray.push({script: true, id: 'script 0', displayID: 'script0'});
            fileArray.push({script: true, id: 'script 0', displayID: 'script1'});
            component['files'] = fileArray;

            let result = component['queryExists']();
            result.should.equal(false);
        }


class MockAdminService {
    getAdminConnection(): AdminConnection {
        return new AdminConnection();
    }

    ensureConnection(): Promise<any> {
        return new Promise((resolve, reject) => {
            resolve(true);
        });
    }

    deploy(): Promise<any> {
        return new Promise((resolve, reject) => {
            resolve(new BusinessNetworkDefinition('org-acme-biznet@0.0.1', 'Acme Business Network'));
        });
    }

    update(): Promise<any> {
        return new Promise((resolve, reject) => {
            resolve(new BusinessNetworkDefinition('org-acme-biznet@0.0.1', 'Acme Business Network'));
        });
    }

    generateDefaultBusinessNetwork(): BusinessNetworkDefinition {
        return new BusinessNetworkDefinition('org-acme-biznet@0.0.1', 'Acme Business Network');
    }

    isInitialDeploy(): boolean {
        return true;
    }
}


class MockAlertService {
    public errorStatus$: Subject<string> = new BehaviorSubject<string>(null);
    public busyStatus$: Subject<string> = new BehaviorSubject<string>(null);
}


getAdminConnection(): AdminConnection {
        return new AdminConnection();
    }


ensureConnection(): Promise<any> {
        return new Promise((resolve, reject) => {
            resolve(true);
        });
    }


deploy(): Promise<any> {
        return new Promise((resolve, reject) => {
            resolve(new BusinessNetworkDefinition('org-acme-biznet@0.0.1', 'Acme Business Network'));
        });
    }


update(): Promise<any> {
        return new Promise((resolve, reject) => {
            resolve(new BusinessNetworkDefinition('org-acme-biznet@0.0.1', 'Acme Business Network'));
        });
    }


generateDefaultBusinessNetwork(): BusinessNetworkDefinition {
        return new BusinessNetworkDefinition('org-acme-biznet@0.0.1', 'Acme Business Network');
    }


isInitialDeploy(): boolean {
        return true;
    }


export function queryEncode(data: Record<string, any>) {
  const queries: string[] = [];
  Object.entries(data).forEach(([key, value]) => {
    if (value !== undefined) {
      if (Array.isArray(value)) {
        value.forEach((val) => {
          queries.push(`${encodeURIComponent(key)}=${encodeURIComponent(val)}`);
        });
      } else {
        queries.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
      }
    }
  });
  const result = queries.join('&');
  return result ? '?' + result : '';
}


export function queryDecode(url: string, forceArray = false) {
  const query = (url || '').substr(1);
  const result: Record<string, string | string[]> = {};
  if (query) {
    query.split('&').forEach(function (part) {
      const item = part.split('=');
      const key = decodeURIComponent(item[0]);
      const value = decodeURIComponent(item[1]);

      if (result[key]) {
        if (Array.isArray(result[key])) {
          (result[key] as string[]).push(value);
        } else {
          result[key] = [result[key] as string, value];
        }
      } else {
        if (forceArray) {
          result[key] = [value];
        } else {
          result[key] = value;
        }
      }
    });
  }
  return result;
}


([key, value]) => {
    if (value !== undefined) {
      if (Array.isArray(value)) {
        value.forEach((val) => {
          queries.push(`${encodeURIComponent(key)}=${encodeURIComponent(val)}`);
        });
      } else {
        queries.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
      }
    }
  }


(val) => {
          queries.push(`${encodeURIComponent(key)}=${encodeURIComponent(val)}`);
        }


(
  key: string,
  options?: Options
): [
  string | string[] | undefined,
  (value: string | string[] | undefined) => void
] => {
  const location = useLocation();
  const value = queryDecode(location.search)[key];
  const { replace, push } = useHistory();

  const getNewSearch = useCallback(
    (value: any) => {
      const data = queryDecode(window.location.search);
      const newValue = value === options?.defaultVal ? undefined : value;
      const newSearch = queryEncode({
        ...data,
        [key]: newValue,
      });

      return newSearch;
    },
    [options?.defaultVal]
  );

  const setState = useCallback(
    (value: any) => {
      const newSearch = getNewSearch(value);
      if (options?.history) {
        push(window.location.pathname + newSearch);
      } else {
        replace(window.location.pathname + newSearch);
      }
    },
    [getNewSearch]
  );

  useEffect(() => {
    return () => {
      if (options?.cleanup) {
        setState(undefined);
      }
    };
  }, [setState]);

  if (!options?.array) {
    return [value === undefined ? options?.defaultVal : value, setState];
  } else {
    return [
      Array.isArray(value) ? value : value !== undefined ? [value] : [],
      setState,
    ];
  }
}


(value: any) => {
      const data = queryDecode(window.location.search);
      const newValue = value === options?.defaultVal ? undefined : value;
      const newSearch = queryEncode({
        ...data,
        [key]: newValue,
      });

      return newSearch;
    }


(value: any) => {
      const newSearch = getNewSearch(value);
      if (options?.history) {
        push(window.location.pathname + newSearch);
      } else {
        replace(window.location.pathname + newSearch);
      }
    }


() => {
    return () => {
      if (options?.cleanup) {
        setState(undefined);
      }
    };
  }


() => {
      if (options?.cleanup) {
        setState(undefined);
      }
    }


type Options = {
  defaultVal?: string | string[];
  history?: boolean;
  array?: boolean;
  // clear parameter from url, after unmount
  cleanup?: boolean;
};


export interface ictx extends RouterContext {
    session: iSession;
}


interface iSession {
    user: IUser;
}


interface IUser {
    id: number;
    user_type: number;
    username: string;
    nickname: string;
    uuid: string;
}


function readFiles (sourcePath, originalFilePath) {
      readPromises.push(new Promise((resolve, reject) => {
        klaw(sourcePath)
          .on('data', file => {
            const REG_IGNORE = /(\\|\/)\.(svn|git)\1/i;
            const relativePath = path.relative(appPath, file.path)
            if (file.stats.isSymbolicLink()) {
              let linkFile = fs.readlinkSync(file.path)
              if (!path.isAbsolute(linkFile)) {
                linkFile = path.resolve(file.path, '..', linkFile)
              }
              readFiles.call(this, linkFile, file.path)
            } else if (!file.stats.isDirectory() && !REG_IGNORE.test(relativePath)) {
              printLog(processTypeEnum.CREATE, '发现文件', relativePath)
              this.processFiles(file.path, originalFilePath)
            }
          })
          .on('end', () => {
            resolve()
          })
        }))
    }


export async function build (appPath: string, buildConfig: IBuildOptions, buildHooks: IBuildHooks) {
  process.env.TARO_ENV = 'h5'
  await checkCliAndFrameworkVersion(appPath, 'h5')
  const compiler = new Compiler(appPath)
  await compiler.clean()
  await compiler.buildTemp()
  if (compiler.h5Config.transformOnly !== true) {
    await compiler.buildDist(buildConfig, buildHooks)
  }
  if (buildConfig.watch) {
    compiler.watchFiles()
  }
}


filePath => filePath.indexOf(path.dirname(path.join(appPath, PROJECT_CONFIG))) >= 0


([pageName, filePath]) => {
      const relPage = path.normalize(
        path.relative(appPath, pageName)
      )
      if (path.relative(relPage, relSrcPath) === '') return true
      return false
    }


(resolve, reject) => {
        klaw(sourcePath)
          .on('data', file => {
            const REG_IGNORE = /(\\|\/)\.(svn|git)\1/i;
            const relativePath = path.relative(appPath, file.path)
            if (file.stats.isSymbolicLink()) {
              let linkFile = fs.readlinkSync(file.path)
              if (!path.isAbsolute(linkFile)) {
                linkFile = path.resolve(file.path, '..', linkFile)
              }
              readFiles.call(this, linkFile, file.path)
            } else if (!file.stats.isDirectory() && !REG_IGNORE.test(relativePath)) {
              printLog(processTypeEnum.CREATE, '发现文件', relativePath)
              this.processFiles(file.path, originalFilePath)
            }
          })
          .on('end', () => {
            resolve()
          })
        }


file => {
            const REG_IGNORE = /(\\|\/)\.(svn|git)\1/i;
            const relativePath = path.relative(appPath, file.path)
            if (file.stats.isSymbolicLink()) {
              let linkFile = fs.readlinkSync(file.path)
              if (!path.isAbsolute(linkFile)) {
                linkFile = path.resolve(file.path, '..', linkFile)
              }
              readFiles.call(this, linkFile, file.path)
            } else if (!file.stats.isDirectory() && !REG_IGNORE.test(relativePath)) {
              printLog(processTypeEnum.CREATE, '发现文件', relativePath)
              this.processFiles(file.path, originalFilePath)
            }
          }


() => {
            resolve()
          }


(filename: string) => {
      return path.join(tempPath, filename)
    }


([pagename, filePath]) => {
        return [filePath, [getEntryFile(filePath) + '.js']]
      }


filePath => {
        const relativePath = path.relative(appPath, filePath)
        printLog(processTypeEnum.CREATE, '添加文件', relativePath)
        this.processFiles(filePath, filePath)
      }


filePath => {
        const relativePath = path.relative(appPath, filePath)
        printLog(processTypeEnum.MODIFY, '文件变动', relativePath)
        this.processFiles(filePath, filePath)
      }


filePath => {
        const relativePath = path.relative(appPath, filePath)
        const extname = path.extname(relativePath)
        const distDirname = this.getTempDir(filePath, filePath)
        const isScriptFile = REG_SCRIPTS.test(extname)
        const dist = this.getDist(distDirname, filePath, isScriptFile)
        printLog(processTypeEnum.UNLINK, '删除文件', relativePath)
        fs.unlinkSync(dist)
      }


(currentPagename: string, funcBody: string) => {
      const firstPage = first(pages)
      const homePage = firstPage ? firstPage[0] : ''

      const panel = `
        <${tabBarPanelComponentName}>
          ${funcBody}
        </${tabBarPanelComponentName}>`

      const comp = `
        <${tabBarComponentName}
          conf={this.state.${tabBarConfigName}}
          homePage="${homePage}"
          ${currentPagename ? `currentPagename={'${currentPagename}'}` : ''}
          ${tabbarPos === 'top' ? `tabbarPos={'top'}` : ''} />`

      return `
        <${tabBarContainerComponentName}>
          ${tabbarPos === 'top' ? `${comp}${panel}` : `${panel}${comp}`}
        </${tabBarContainerComponentName}>`
    }


(funcBody: string) => {
      return `
        <${providerImportName} store={${storeName}}>
          ${funcBody}
        </${providerImportName}>
      `
    }


(funcBody: string) => {
      return `{return (${funcBody});}`
    }


(pages: [PageName, FilePath][]) => {
              const routes = pages.map(([pageName, filePath], k) => {
                const shouldLazyloadPage = typeof routerLazyload === 'function'
                  ? routerLazyload(pageName)
                  : routerLazyload
                return createRoute({
                  pageName,
                  lazyload: shouldLazyloadPage,
                  isIndex: k === 0
                })
              })
              return `
                <Router
                  mode={${JSON.stringify(routerMode)}}
                  history={_taroHistory}
                  routes={[${routes.join(',')}]}
                  ${tabBar ? `tabBar={this.state.${tabBarConfigName}}` : ''}
                  customRoutes={${JSON.stringify(customRoutes)}} />
                `
            }


([pageName, filePath], k) => {
                const shouldLazyloadPage = typeof routerLazyload === 'function'
                  ? routerLazyload(pageName)
                  : routerLazyload
                return createRoute({
                  pageName,
                  lazyload: shouldLazyloadPage,
                  isIndex: k === 0
                })
              }


v => {
              if (t.isSpreadProperty(v)) return false
              return toVar(v.key) === 'root'
            }


(v: t.StringLiteral) => {
              const pagePath = `${root}/${v.value}`.replace(/\/{2,}/g, '/')
              const pageName = removeLeadingSlash(pagePath)
              pages.push([pageName, renamePagename(pageName).replace(/\//g, '')])
              v.value = addLeadingSlash(v.value)
            }


(v: t.StringLiteral) => {
              const pagePath = v.value.replace(/\/{2,}/g, '/')
              const pageName = removeLeadingSlash(pagePath)
              pages.push([pageName, renamePagename(pageName).replace(/\//g, '')])
              v.value = addLeadingSlash(v.value)
            }


(node) => {
            if (t.isSpreadProperty(node)) return
            switch (toVar(node.key)) {
              case 'position':
                tabbarPos = toVar(node.value)
                break
              case 'list':
                t.isArrayExpression(node.value) && node.value.elements.forEach(v => {
                  if (!t.isObjectExpression(v)) return
                  v.properties.forEach(property => {
                    if (!t.isObjectProperty(property)) return
                    switch (toVar(property.key)) {
                      case 'iconPath':
                      case 'selectedIconPath':
                        if (t.isStringLiteral(property.value)) {
                          property.value = t.callExpression(
                            t.identifier('require'),
                            [t.stringLiteral(`./${property.value.value}`)]
                          )
                        }
                        break
                      case 'pagePath':
                        property.value = t.stringLiteral(addLeadingSlash(toVar(property.value)))
                        break
                    }
                  })
                })
            }
          }


v => {
                  if (!t.isObjectExpression(v)) return
                  v.properties.forEach(property => {
                    if (!t.isObjectProperty(property)) return
                    switch (toVar(property.key)) {
                      case 'iconPath':
                      case 'selectedIconPath':
                        if (t.isStringLiteral(property.value)) {
                          property.value = t.callExpression(
                            t.identifier('require'),
                            [t.stringLiteral(`./${property.value.value}`)]
                          )
                        }
                        break
                      case 'pagePath':
                        property.value = t.stringLiteral(addLeadingSlash(toVar(property.value)))
                        break
                    }
                  })
                }


property => {
                    if (!t.isObjectProperty(property)) return
                    switch (toVar(property.key)) {
                      case 'iconPath':
                      case 'selectedIconPath':
                        if (t.isStringLiteral(property.value)) {
                          property.value = t.callExpression(
                            t.identifier('require'),
                            [t.stringLiteral(`./${property.value.value}`)]
                          )
                        }
                        break
                      case 'pagePath':
                        property.value = t.stringLiteral(addLeadingSlash(toVar(property.value)))
                        break
                    }
                  }


(res, [pageName, filePath], key) => {
                res[addLeadingSlash(pageName)] = addLeadingSlash(filePath)
              }


(astPath: NodePath<t.ImportDeclaration>) => {
          const node = astPath.node
          const source = node.source
          const specifiers = node.specifiers

          if (source.value === '@tarojs/taro') {
            const specifier = specifiers.find(item => t.isImportDefaultSpecifier(item))
            if (specifier) {
              taroImportDefaultName = toVar(specifier.local)
            }
            source.value = '@tarojs/taro-h5'
          } else if (source.value === '@tarojs/redux') {
            const specifier = specifiers.find(item => {
              return t.isImportSpecifier(item) && item.imported.name === providerComponentName
            })
            if (specifier) {
              providerImportName = specifier.local.name
            } else {
              providerImportName = providerComponentName
              specifiers.push(t.importSpecifier(t.identifier(providerComponentName), t.identifier(providerComponentName)))
            }
            source.value = '@tarojs/redux-h5'
          } else if (source.value === '@tarojs/mobx') {
            const specifier = specifiers.find(item => {
              return t.isImportSpecifier(item) && item.imported.name === providerComponentName
            })
            if (specifier) {
              providerImportName = specifier.local.name
            } else {
              providerImportName = providerComponentName
              specifiers.push(t.importSpecifier(t.identifier(providerComponentName), t.identifier(providerComponentName)))
            }
            source.value = '@tarojs/mobx-h5'
          } else if (source.value === 'nervjs') {
            hasNerv = true
            const defaultSpecifier = specifiers.find(item => t.isImportDefaultSpecifier(item))
            if (!defaultSpecifier) {
              specifiers.unshift(
                t.importDefaultSpecifier(t.identifier(nervJsImportDefaultName))
              )
            }
          }

          if (isAliasPath(source.value, pathAlias)) {
            source.value = this.transformToTempDir(replaceAliasPath(filePath, source.value, pathAlias))
          }

          if (!isNpmPkg(source.value)) {
            if (source.value.indexOf('.') === 0) {
              const pathArr = source.value.split('/')

              /* FIXME: 会导致误删除 */
              if (pathArr.indexOf('pages') >= 0) {
                astPath.remove()
              } else if (REG_SCRIPTS.test(source.value) || path.extname(source.value) === '') {
                /* 移除后缀名 */
                const absolutePath = path.resolve(filePath, '..', source.value)
                const dirname = path.dirname(absolutePath)
                const extname = path.extname(absolutePath)
                const realFilePath = resolveScriptPath(path.join(dirname, path.basename(absolutePath, extname)))
                const removeExtPath = realFilePath.replace(path.extname(realFilePath), '')
                source.value = promoteRelativePath(path.relative(filePath, removeExtPath)).replace(/\\/g, '/')
              }
            }
          }
        }


item => t.isImportDefaultSpecifier(item)


item => {
              return t.isImportSpecifier(item) && item.imported.name === providerComponentName
            }


(ast) => {
      return generate(ast, {
        jsescOption: {
          minimal: true
        }
      }).code
    }


([pageName, filePath], k) => {
        const createFuncBody = () => {
          const shouldLazyloadPage = typeof routerLazyload === 'function'
            ? routerLazyload(pageName)
            : routerLazyload
          const route = createRoute({
            pageName,
            lazyload: shouldLazyloadPage,
            isIndex: k === 0
          })
          return `
            <Router
              mode={${JSON.stringify(routerMode)}}
              history={_taroHistory}
              routes={[${route}]}
              ${tabBar ? `tabBar={this.state.${tabBarConfigName}}` : ''}
              customRoutes={${JSON.stringify(customRoutes)}} />
            `
        }
        const replaceMultiRouterVisitor: TraverseOptions<t.Node> = {
          ClassMethod: {
            exit (astPath: NodePath<t.ClassMethod>) {
              const node = astPath.node
              const key = node.key
              const keyName = toVar(key)

              const isRender = keyName === 'render'
              const isComponentWillMount = keyName === 'componentWillMount'
              const isComponentDidMount = keyName === 'componentDidMount'
              const isComponentWillUnmount = keyName === 'componentWillUnmount'
              const isConstructor = keyName === 'constructor'

              if (isRender) {
                const buildFuncBody = pipe(
                  ...compact([
                    createFuncBody,
                    tabBar && partial(wrapWithTabbar, [addLeadingSlash(pageName)]),
                    providerComponentName && storeName && wrapWithProvider,
                    wrapWithFuncBody
                  ])
                )

                node.body = toAst(buildFuncBody(pages), { preserveComments: true })
              } else {
                node.body.body = compact([
                  hasComponentDidHide && isComponentWillUnmount && callComponentDidHideNode,
                  ...node.body.body,
                  tabBar && isComponentWillMount && initTabbarApiNode,
                  hasConstructor && isConstructor && additionalConstructorNode,
                  hasComponentDidShow && isComponentDidMount && callComponentDidShowNode
                ])
              }
            }
          },
          Program: {
            exit (astPath: NodePath<t.Program>) {
              const node = astPath.node
              node.body.forEach((bodyNode) => {
                if (t.isExpressionStatement(bodyNode)
                  && t.isCallExpression(bodyNode.expression)
                  && t.isIdentifier(bodyNode.expression.callee)
                  && bodyNode.expression.callee.name === 'mountApis') {
                  const mountApisOptNode = bodyNode.expression.arguments[0]
                  if (t.isObjectExpression(mountApisOptNode)) {
                    const valueNode = t.stringLiteral(addLeadingSlash(pageName))
                    let basenameNode = mountApisOptNode.properties.find((property: t.ObjectProperty) => {
                      return toVar<string>(property.key) === 'currentPagename'
                    }) as t.ObjectProperty | undefined
                    if (basenameNode) {
                      basenameNode.value = valueNode
                    } else {
                      basenameNode = t.objectProperty(t.stringLiteral('currentPagename'), valueNode)
                      mountApisOptNode.properties.push(basenameNode)
                    }
                  }
                }
              })
            }
          }
        }
        traverse(ast, replaceMultiRouterVisitor)
        return [filePath, generateCode(ast)]
      }


() => {
          const shouldLazyloadPage = typeof routerLazyload === 'function'
            ? routerLazyload(pageName)
            : routerLazyload
          const route = createRoute({
            pageName,
            lazyload: shouldLazyloadPage,
            isIndex: k === 0
          })
          return `
            <Router
              mode={${JSON.stringify(routerMode)}}
              history={_taroHistory}
              routes={[${route}]}
              ${tabBar ? `tabBar={this.state.${tabBarConfigName}}` : ''}
              customRoutes={${JSON.stringify(customRoutes)}} />
            `
        }


(bodyNode) => {
                if (t.isExpressionStatement(bodyNode)
                  && t.isCallExpression(bodyNode.expression)
                  && t.isIdentifier(bodyNode.expression.callee)
                  && bodyNode.expression.callee.name === 'mountApis') {
                  const mountApisOptNode = bodyNode.expression.arguments[0]
                  if (t.isObjectExpression(mountApisOptNode)) {
                    const valueNode = t.stringLiteral(addLeadingSlash(pageName))
                    let basenameNode = mountApisOptNode.properties.find((property: t.ObjectProperty) => {
                      return toVar<string>(property.key) === 'currentPagename'
                    }) as t.ObjectProperty | undefined
                    if (basenameNode) {
                      basenameNode.value = valueNode
                    } else {
                      basenameNode = t.objectProperty(t.stringLiteral('currentPagename'), valueNode)
                      mountApisOptNode.properties.push(basenameNode)
                    }
                  }
                }
              }


(property: t.ObjectProperty) => {
                      return toVar<string>(property.key) === 'currentPagename'
                    }


(componentName: string, node: t.JSXOpeningElement) => {
      const idAttrName = MAP_FROM_COMPONENTNAME_TO_ID.get(componentName)
      return node.attributes.reduce((prev, attribute) => {
        if (prev) return prev
        const attrName = toVar(attribute.name)
        if (attrName === idAttrName) return toVar(attribute.value)
        else return false
      }, false as string | false)
    }


(prev, attribute) => {
        if (prev) return prev
        const attrName = toVar(attribute.name)
        if (attrName === idAttrName) return toVar(attribute.value)
        else return false
      }


(node: t.JSXOpeningElement) => {
      return node.attributes.find(attribute => {
        return toVar(attribute.name) === 'ref'
      })
    }


attribute => {
        return toVar(attribute.name) === 'ref'
      }


(componentId: string) => {
      return t.arrowFunctionExpression(
        [t.identifier('ref')],
        t.blockStatement([
          toAst(`this['__taroref_${componentId}'] = ref`) as t.Statement
        ])
      )
    }


(astPath: NodePath<t.ExportNamedDeclaration>) => {
      if (!astPath) return

      const node = astPath.node
      if (t.isFunctionDeclaration(node.declaration)) {

        astPath.replaceWithMultiple([
          node.declaration,
          t.exportDefaultDeclaration(node.declaration.id)
        ])
      } else if (t.isClassDeclaration(node.declaration)) {
        astPath.replaceWithMultiple([
          node.declaration,
          t.exportDefaultDeclaration(node.declaration.id)
        ])
      } else if (t.isVariableDeclaration(node.declaration)) {
        const declarationId = node.declaration.declarations[0].id
        if (t.isIdentifier(declarationId)) {
          astPath.replaceWithMultiple([
            node.declaration,
            t.exportDefaultDeclaration(declarationId)
          ])
        }
      } else if (node.specifiers && node.specifiers.length) {
        astPath.replaceWithMultiple([
          t.exportDefaultDeclaration(node.specifiers[0].local)
        ])
      }
    }


(astPath: NodePath<t.ImportDeclaration>) => {
          const node = astPath.node
          const source = node.source
          const specifiers = node.specifiers

          if (source.value === '@tarojs/taro') {
            importTaroNode = node
            specifiers.forEach(specifier => {
              if (t.isImportDefaultSpecifier(specifier)) {
                taroImportDefaultName = toVar(specifier.local)
              } else if (t.isImportSpecifier(specifier)) {
                taroapiMap.set(toVar(specifier.local), toVar(specifier.imported))
              }
            })
            source.value = '@tarojs/taro-h5'
          } else if (source.value === '@tarojs/redux') {
            source.value = '@tarojs/redux-h5'
          } else if (source.value === '@tarojs/mobx') {
            source.value = '@tarojs/mobx-h5'
          } else if (source.value === '@tarojs/components') {
            importTaroComponentNode = node
            node.specifiers.forEach((specifier) => {
              if (!t.isImportSpecifier(specifier)) return
              componentnameMap.set(toVar(specifier.local), toVar(specifier.imported))
            })
          } else if (source.value === 'nervjs') {
            importNervNode = node
          }

          if (isAliasPath(source.value, pathAlias)) {
            source.value = this.transformToTempDir(replaceAliasPath(filePath, source.value, pathAlias))
          }

          if (!isNpmPkg(source.value)) {
            if (REG_SCRIPTS.test(source.value) || path.extname(source.value) === '') {
              const absolutePath = path.resolve(filePath, '..', source.value)
              const dirname = path.dirname(absolutePath)
              const extname = path.extname(absolutePath)
              const realFilePath = resolveScriptPath(path.join(dirname, path.basename(absolutePath, extname)))
              const removeExtPath = realFilePath.replace(path.extname(realFilePath), '')
              source.value = promoteRelativePath(path.relative(filePath, removeExtPath)).replace(/\\/g, '/')
            }
          }
        }


specifier => {
              if (t.isImportDefaultSpecifier(specifier)) {
                taroImportDefaultName = toVar(specifier.local)
              } else if (t.isImportSpecifier(specifier)) {
                taroapiMap.set(toVar(specifier.local), toVar(specifier.imported))
              }
            }


(specifier) => {
              if (!t.isImportSpecifier(specifier)) return
              componentnameMap.set(toVar(specifier.local), toVar(specifier.imported))
            }


(p: NodePath<t.Node>) => p.isClassExpression() || p.isClassDeclaration()


specifier => {
              if (!t.isImportSpecifier(specifier)) return false
              const importedComponent = toVar(specifier.imported)
              return importedComponent === 'PullDownRefresh'
            }


renderReturnStatementPath => {
              const funcParentPath: NodePath = renderReturnStatementPath.getFunctionParent()
              return funcParentPath.node === renderClassMethodNode
            }


returnAstPath => {
              const statement = returnAstPath.node
              const varName = returnAstPath.scope.generateUid()
              const returnValue = statement.argument
              const pullDownRefreshNode = t.variableDeclaration(
                'const',
                [t.variableDeclarator(
                  t.identifier(varName),
                  returnValue
                )]
              )
              returnAstPath.insertBefore(pullDownRefreshNode)
              statement.argument = (toAst(`
                <PullDownRefresh
                  onRefresh={this.onPullDownRefresh && this.onPullDownRefresh.bind(this)}
                  ref={ref => {
                    if (ref) this.pullDownRefreshRef = ref
                }}>{${varName}}</PullDownRefresh>`) as t.ExpressionStatement).expression
            }


([pageName, code]) => {
              fs.writeFileSync(
                path.join(distDirname, `${pageName}.js`),
                code
              )
            }


type PageName = string


type FilePath = string


async clean () {
    const tempPath = this.tempPath
    const outputPath = this.outputPath
    try {
      await pRimraf(tempPath)
      await pRimraf(outputPath)
    } catch (e) {
      console.log(e)
    }
  }


copyFiles () {}


classifyFiles (filename) {
    const pages = this.pages
    const appPath = this.appPath
    const entryFilePath = this.entryFilePath

    const relPath = path.normalize(
      path.relative(appPath, filename)
    )
    if (path.relative(filename, entryFilePath) === '') return FILE_TYPE.ENTRY

    let relSrcPath = path.relative(this.sourceRoot, relPath)
    relSrcPath = path.format({
      dir: path.dirname(relSrcPath),
      base: path.basename(relSrcPath, path.extname(relSrcPath))
    })

    const isPage = pages.some(([pageName, filePath]) => {
      const relPage = path.normalize(
        path.relative(appPath, pageName)
      )
      if (path.relative(relPage, relSrcPath) === '') return true
      return false
    })

    if (isPage) {
      return FILE_TYPE.PAGE
    } else {
      return FILE_TYPE.NORMAL
    }
  }


buildTemp () {
    const tempPath = this.tempPath
    const sourcePath = this.sourcePath
    const appPath = this.appPath

    fs.ensureDirSync(tempPath)
    const readPromises: any[] = []
    function readFiles (sourcePath, originalFilePath) {
      readPromises.push(new Promise((resolve, reject) => {
        klaw(sourcePath)
          .on('data', file => {
            const REG_IGNORE = /(\\|\/)\.(svn|git)\1/i;
            const relativePath = path.relative(appPath, file.path)
            if (file.stats.isSymbolicLink()) {
              let linkFile = fs.readlinkSync(file.path)
              if (!path.isAbsolute(linkFile)) {
                linkFile = path.resolve(file.path, '..', linkFile)
              }
              readFiles.call(this, linkFile, file.path)
            } else if (!file.stats.isDirectory() && !REG_IGNORE.test(relativePath)) {
              printLog(processTypeEnum.CREATE, '发现文件', relativePath)
              this.processFiles(file.path, originalFilePath)
            }
          })
          .on('end', () => {
            resolve()
          })
        }))
    }
    readFiles.call(this, sourcePath, sourcePath)
    return Promise.all(readPromises)
  }


async buildDist ({ watch, port }: IBuildOptions, { modifyWebpackChain, modifyBuildAssets, onBuildFinish }: IBuildHooks) {
    const isMultiRouterMode = get(this.h5Config, 'router.mode') === 'multi'
    const entryFileName = this.entryFileName
    const projectConfig = this.projectConfig
    /** 不是真正意义上的IH5Config对象 */
    const h5Config: IH5Config & {
      deviceRatio?: IDeviceRatio
      env?: IOption
    } = this.h5Config
    const outputDir = this.outputDir
    const sourceRoot = this.sourceRoot
    const tempPath = this.tempPath
    const pathAlias = this.pathAlias

    const getEntryFile = (filename: string) => {
      return path.join(tempPath, filename)
    }

    const entryFile = path.basename(entryFileName, path.extname(entryFileName)) + '.js'
    const defaultEntry = isMultiRouterMode
      ? fromPairs(this.pages.map(([pagename, filePath]) => {
        return [filePath, [getEntryFile(filePath) + '.js']]
      }))
      : {
        app: [getEntryFile(entryFile)]
      }
    if (projectConfig.deviceRatio) {
      h5Config.deviceRatio = projectConfig.deviceRatio
    }
    if (projectConfig.env) {
      h5Config.env = projectConfig.env
    }
    recursiveMerge(h5Config, {
      alias: pathAlias,
      copy: projectConfig.copy,
      homePage: first(this.pages),
      defineConstants: projectConfig.defineConstants,
      designWidth: projectConfig.designWidth,
      entry: merge(defaultEntry, h5Config.entry),
      env: {
        TARO_ENV: JSON.stringify('h5')
      },
      isWatch: !!watch,
      outputRoot: outputDir,
      babel: projectConfig.babel,
      csso: projectConfig.csso,
      uglify: projectConfig.uglify,
      sass: projectConfig.sass,
      plugins: projectConfig.plugins,
      port,
      sourceRoot,
      modifyWebpackChain,
      modifyBuildAssets,
      onBuildFinish
    })

    const webpackRunner = await npmProcess.getNpmPkg('@tarojs/webpack-runner', this.appPath)
    webpackRunner(this.appPath, h5Config)
  }


watchFiles () {
    const sourcePath = this.sourcePath
    const appPath = this.appPath
    const watcher = chokidar.watch(path.join(sourcePath), {
      ignored: /(^|[/\\])\../,
      persistent: true,
      ignoreInitial: true
    })
    watcher
      .on('add', filePath => {
        const relativePath = path.relative(appPath, filePath)
        printLog(processTypeEnum.CREATE, '添加文件', relativePath)
        this.processFiles(filePath, filePath)
      })
      .on('change', filePath => {
        const relativePath = path.relative(appPath, filePath)
        printLog(processTypeEnum.MODIFY, '文件变动', relativePath)
        this.processFiles(filePath, filePath)
      })
      .on('unlink', filePath => {
        const relativePath = path.relative(appPath, filePath)
        const extname = path.extname(relativePath)
        const distDirname = this.getTempDir(filePath, filePath)
        const isScriptFile = REG_SCRIPTS.test(extname)
        const dist = this.getDist(distDirname, filePath, isScriptFile)
        printLog(processTypeEnum.UNLINK, '删除文件', relativePath)
        fs.unlinkSync(dist)
      })
  }


enter (astPath: NodePath<t.ClassDeclaration> | NodePath<t.ClassExpression>) {
        const node = astPath.node
        if (!node.superClass) return
        if (isTaroClass(astPath)) {
          resetTSClassProperty(node.body.body)
        }
      }


exit (astPath: NodePath<t.ClassMethod>) {
          if (isMultiRouterMode) return

          const node = astPath.node
          const key = node.key
          const keyName = toVar(key)

          const isRender = keyName === 'render'
          const isComponentWillMount = keyName === 'componentWillMount'
          const isComponentDidMount = keyName === 'componentDidMount'
          const isComponentWillUnmount = keyName === 'componentWillUnmount'
          const isConstructor = keyName === 'constructor'

          if (isRender) {
            const createFuncBody = (pages: [PageName, FilePath][]) => {
              const routes = pages.map(([pageName, filePath], k) => {
                const shouldLazyloadPage = typeof routerLazyload === 'function'
                  ? routerLazyload(pageName)
                  : routerLazyload
                return createRoute({
                  pageName,
                  lazyload: shouldLazyloadPage,
                  isIndex: k === 0
                })
              })
              return `
                <Router
                  mode={${JSON.stringify(routerMode)}}
                  history={_taroHistory}
                  routes={[${routes.join(',')}]}
                  ${tabBar ? `tabBar={this.state.${tabBarConfigName}}` : ''}
                  customRoutes={${JSON.stringify(customRoutes)}} />
                `
            }

            const buildFuncBody = pipe(
              ...compact([
                createFuncBody,
                tabBar && partial(wrapWithTabbar, ['']),
                providerComponentName && storeName && wrapWithProvider,
                wrapWithFuncBody
              ])
            )

            node.body = toAst(buildFuncBody(pages), { preserveComments: true })
          } else {
            node.body.body = compact([
              hasComponentDidHide && isComponentWillUnmount && callComponentDidHideNode,
              ...node.body.body,
              tabBar && isComponentWillMount && initTabbarApiNode,
              hasConstructor && isConstructor && additionalConstructorNode,
              hasComponentDidShow && isComponentDidMount && callComponentDidShowNode
            ])
          }
        }


exit (astPath: NodePath<t.ClassBody>) {
          const node = astPath.node
          if (hasComponentDidShow && !hasComponentDidMount) {
            node.body.push(t.classMethod(
              'method', t.identifier('componentDidMount'), [],
              t.blockStatement([callComponentDidShowNode]), false, false))
          }
          if (hasComponentDidHide && !hasComponentWillUnmount) {
            node.body.push(t.classMethod(
              'method', t.identifier('componentWillUnmount'), [],
              t.blockStatement([callComponentDidHideNode]), false, false))
          }
          if (!hasConstructor) {
            node.body.push(t.classMethod(
              'method', t.identifier('constructor'), [t.identifier('props'), t.identifier('context')],
              t.blockStatement([toAst('super(props, context)'), additionalConstructorNode] as t.Statement[]), false, false))
          }
          if (tabBar) {
            if (!hasComponentWillMount) {
              node.body.push(t.classMethod(
                'method', t.identifier('componentWillMount'), [],
                t.blockStatement([initTabbarApiNode]), false, false))
            }
            if (!stateNode) {
              stateNode = t.classProperty(
                t.identifier('state'),
                t.objectExpression([])
              )
              node.body.unshift(stateNode)
            }
            if (t.isObjectExpression(stateNode.value)) {
              stateNode.value.properties.push(t.objectProperty(
                t.identifier(tabBarConfigName),
                tabBar
              ))
            }
          }
        }


ObjectProperty (astPath: NodePath<t.ObjectProperty>) {
        const node = astPath.node
        const key = node.key
        const value = node.value
        const keyName = toVar(key)
        if (keyName === 'pages' && t.isArrayExpression(value)) {
          const subPackageParent = astPath.findParent(isUnderSubPackages)
          if (subPackageParent) {
            /* 在subPackages属性下，说明是分包页面，需要处理root属性 */
            const parent = astPath.parent as t.ObjectExpression
            const rootNode = parent.properties.find(v => {
              if (t.isSpreadProperty(v)) return false
              return toVar(v.key) === 'root'
            }) as t.ObjectProperty
            const root = rootNode ? toVar(rootNode.value) : '';
            value.elements.forEach((v: t.StringLiteral) => {
              const pagePath = `${root}/${v.value}`.replace(/\/{2,}/g, '/')
              const pageName = removeLeadingSlash(pagePath)
              pages.push([pageName, renamePagename(pageName).replace(/\//g, '')])
              v.value = addLeadingSlash(v.value)
            })
          } else {
            value.elements.forEach((v: t.StringLiteral) => {
              const pagePath = v.value.replace(/\/{2,}/g, '/')
              const pageName = removeLeadingSlash(pagePath)
              pages.push([pageName, renamePagename(pageName).replace(/\//g, '')])
              v.value = addLeadingSlash(v.value)
            })
          }
        } else if (keyName === 'tabBar' && t.isObjectExpression(value)) {
          // tabBar相关处理
          tabBar = value
          value.properties.forEach((node) => {
            if (t.isSpreadProperty(node)) return
            switch (toVar(node.key)) {
              case 'position':
                tabbarPos = toVar(node.value)
                break
              case 'list':
                t.isArrayExpression(node.value) && node.value.elements.forEach(v => {
                  if (!t.isObjectExpression(v)) return
                  v.properties.forEach(property => {
                    if (!t.isObjectProperty(property)) return
                    switch (toVar(property.key)) {
                      case 'iconPath':
                      case 'selectedIconPath':
                        if (t.isStringLiteral(property.value)) {
                          property.value = t.callExpression(
                            t.identifier('require'),
                            [t.stringLiteral(`./${property.value.value}`)]
                          )
                        }
                        break
                      case 'pagePath':
                        property.value = t.stringLiteral(addLeadingSlash(toVar(property.value)))
                        break
                    }
                  })
                })
            }
          })
          value.properties.push(t.objectProperty(
            t.identifier('mode'),
            t.stringLiteral(routerMode)
          ))
          value.properties.push(t.objectProperty(
            t.identifier('basename'),
            t.stringLiteral(routerBasename)
          ))
          value.properties.push(t.objectProperty(
            t.identifier('customRoutes'),
            t.objectExpression(objToAst(customRoutes))
          ))
        }
      }


enter (astPath: NodePath<t.ClassProperty>) {
          const node = astPath.node
          const key = node.key
          const keyName = toVar(key)

          if (keyName === 'state') {
            stateNode = node
          } else if (keyName === 'config') {
            astPath.traverse(classPropertyVisitor)
            if (isMultiRouterMode) {
              merge(customRoutes, transform(pages, (res, [pageName, filePath], key) => {
                res[addLeadingSlash(pageName)] = addLeadingSlash(filePath)
              }, {}))
            }
          }
        }


enter (astPath: NodePath<t.CallExpression>) {
          const node = astPath.node
          const callee = node.callee
          const calleeName = toVar(callee)
          const parentPath = astPath.parentPath
          const arg0 = node.arguments[0]

          if (calleeName === 'require' && t.isStringLiteral(arg0)) {
            const required = arg0.value
            if (required === '@tarojs/taro-h5') {
              arg0.value = `@tarojs/taro-h5/dist/index.cjs.js`
            }
          } else if (t.isMemberExpression(callee)) {
            const object = callee.object as t.Identifier
            const property = callee.property as t.Identifier
            if (object.name === taroImportDefaultName && property.name === 'render') {
              object.name = nervJsImportDefaultName
              renderCallCode = generate(astPath.node).code
              astPath.remove()
            }
          } else {
            if (calleeName === setStoreFuncName) {
              if (parentPath.isAssignmentExpression() ||
                parentPath.isExpressionStatement() ||
                parentPath.isVariableDeclarator()) {
                parentPath.remove()
              }
            }
          }
        }


exit (astPath: NodePath<t.ClassMethod>) {
          const node = astPath.node
          const key = node.key
          const keyName = toVar(key)
          if (keyName === 'constructor') {
            hasConstructor = true
          } else if (keyName === 'componentWillMount') {
            hasComponentWillMount = true
          } else if (keyName === 'componentDidMount') {
            hasComponentDidMount = true
          } else if (keyName === 'componentDidShow') {
            hasComponentDidShow = true
          } else if (keyName === 'componentDidHide') {
            hasComponentDidHide = true
          } else if (keyName === 'componentWillUnmount') {
            hasComponentWillUnmount = true
          }
        }


enter (astPath: NodePath<t.JSXOpeningElement>) {
          const node = astPath.node
          if (toVar(node.name) === 'Provider') {
            for (const v of node.attributes) {
              if (v.name.name !== 'store') continue
              if (!t.isJSXExpressionContainer(v.value)) return
              storeName = toVar(v.value.expression)
              break
            }
          }
        }


exit (astPath: NodePath<t.Program>) {
          const node = astPath.node
          const lastImportIndex = findLastIndex(astPath.node.body, t.isImportDeclaration)
          const lastImportNode = astPath.get(`body.${lastImportIndex > -1 ? lastImportIndex : 0}`) as NodePath<t.ImportDeclaration>
          const firstPage = first(pages)
          const routerConfigs = JSON.stringify({
            basename: routerBasename,
            customRoutes
          })

          const extraNodes: (t.Node | false)[] = [
            !hasNerv && toAst(`import ${nervJsImportDefaultName} from 'nervjs'`),
            tabBar && toAst(`import { View, ${tabBarComponentName}, ${tabBarContainerComponentName}, ${tabBarPanelComponentName}} from '@tarojs/components'`),
            toAst(`import { Router, createHistory, mountApis } from '@tarojs/router'`),
            toAst(`Taro.initPxTransform(${JSON.stringify(pxTransformConfig)})`),
            toAst(`
              const _taroHistory = createHistory({
                mode: "${routerMode}",
                basename: "${routerBasename}",
                customRoutes: ${JSON.stringify(customRoutes)},
                firstPagePath: "${addLeadingSlash(firstPage ? firstPage[0] : '')}"
              });
            `),
            isMultiRouterMode ? toAst(`mountApis(${routerConfigs});`) : toAst(`mountApis(${routerConfigs}, _taroHistory);`)
          ]
          astPath.traverse(programExitVisitor)
          if (!isUi) {
            lastImportNode.insertAfter(compact(extraNodes))
          }
          if (renderCallCode) {
            const renderCallNode = toAst(renderCallCode)
            node.body.push(renderCallNode)
          }
        }


exit (astPath: NodePath<t.ClassMethod>) {
              const node = astPath.node
              const key = node.key
              const keyName = toVar(key)

              const isRender = keyName === 'render'
              const isComponentWillMount = keyName === 'componentWillMount'
              const isComponentDidMount = keyName === 'componentDidMount'
              const isComponentWillUnmount = keyName === 'componentWillUnmount'
              const isConstructor = keyName === 'constructor'

              if (isRender) {
                const buildFuncBody = pipe(
                  ...compact([
                    createFuncBody,
                    tabBar && partial(wrapWithTabbar, [addLeadingSlash(pageName)]),
                    providerComponentName && storeName && wrapWithProvider,
                    wrapWithFuncBody
                  ])
                )

                node.body = toAst(buildFuncBody(pages), { preserveComments: true })
              } else {
                node.body.body = compact([
                  hasComponentDidHide && isComponentWillUnmount && callComponentDidHideNode,
                  ...node.body.body,
                  tabBar && isComponentWillMount && initTabbarApiNode,
                  hasConstructor && isConstructor && additionalConstructorNode,
                  hasComponentDidShow && isComponentDidMount && callComponentDidShowNode
                ])
              }
            }


exit (astPath: NodePath<t.Program>) {
              const node = astPath.node
              node.body.forEach((bodyNode) => {
                if (t.isExpressionStatement(bodyNode)
                  && t.isCallExpression(bodyNode.expression)
                  && t.isIdentifier(bodyNode.expression.callee)
                  && bodyNode.expression.callee.name === 'mountApis') {
                  const mountApisOptNode = bodyNode.expression.arguments[0]
                  if (t.isObjectExpression(mountApisOptNode)) {
                    const valueNode = t.stringLiteral(addLeadingSlash(pageName))
                    let basenameNode = mountApisOptNode.properties.find((property: t.ObjectProperty) => {
                      return toVar<string>(property.key) === 'currentPagename'
                    }) as t.ObjectProperty | undefined
                    if (basenameNode) {
                      basenameNode.value = valueNode
                    } else {
                      basenameNode = t.objectProperty(t.stringLiteral('currentPagename'), valueNode)
                      mountApisOptNode.properties.push(basenameNode)
                    }
                  }
                }
              })
            }


enter (astPath: NodePath<t.ClassDeclaration> | NodePath<t.ClassExpression>) {
        const node = astPath.node
        if (!node.superClass) return
        if (isTaroClass(astPath)) {
          resetTSClassProperty(node.body.body)
          if (t.isClassDeclaration(astPath)) {
            if (node.id === null) {
              componentClassName = '_TaroComponentClass'
              astPath.replaceWith(
                t.classDeclaration(
                  t.identifier(componentClassName),
                  node.superClass as t.Expression,
                  node.body as t.ClassBody,
                  node.decorators as t.Decorator[] || []
                )
              )
            } else {
              componentClassName = node.id.name
            }
          } else {
            if (node.id === null) {
              const parentNode = astPath.parentPath.node as any
              if (t.isVariableDeclarator(astPath.parentPath)) {
                componentClassName = parentNode.id.name
              } else {
                componentClassName = '_TaroComponentClass'
              }
              astPath.replaceWith(
                t.classExpression(
                  t.identifier(componentClassName),
                  node.superClass as t.Expression,
                  node.body as t.ClassBody,
                  node.decorators as t.Decorator[] || []
                )
              )
            } else {
              componentClassName = node.id.name
            }
          }
        }
      }


exit (astPath: NodePath<t.JSXElement>) {
          hasJSX = true
        }


exit (astPath: NodePath<t.JSXOpeningElement>) {
          const node = astPath.node
          const tagName = toVar(node.name)
          const componentName = componentnameMap.get(tagName)
          const componentId = getComponentId(componentName, node)
          const componentRef = getComponentRef(node)

          if (tagName === BLOCK_TAG_NAME) {
            node.name = t.jSXMemberExpression(
              t.jSXIdentifier('Nerv'),
              t.jSXIdentifier('Fragment')
            )
          }

          if (!componentId) return
          const refFunc = createRefFunc(componentId)

          if (componentRef) {
            const expression = (componentRef.value as t.JSXExpressionContainer).expression;
            (refFunc.body as t.BlockStatement).body.unshift(
              t.expressionStatement(
                t.callExpression(expression, [t.identifier('ref')])
              )
            );
            (componentRef.value as t.JSXExpressionContainer).expression = refFunc
          } else {
            node.attributes.push(
              t.jSXAttribute(
                t.jSXIdentifier('ref'),
                t.jSXExpressionContainer(refFunc)
              )
            )
          }
        }


exit (astPath: NodePath<t.JSXClosingElement>) {
          const node = astPath.node
          const tagName = toVar(node.name)
          if (tagName === BLOCK_TAG_NAME) {
            node.name = t.jSXMemberExpression(
              t.jSXIdentifier('Nerv'),
              t.jSXIdentifier('Fragment')
            )
          }
        }


exit (astPath: NodePath<t.CallExpression>) {
          const node = astPath.node
          const callee = node.callee
          const calleeName = toVar(callee)
          let needToAppendThis = false
          let funcName = ''

          const arg0 = node.arguments[0]

          if (calleeName === 'require' && t.isStringLiteral(arg0)) {
            const required = arg0.value
            if (required === '@tarojs/taro-h5') {
              arg0.value = `@tarojs/taro-h5/dist/index.cjs.js`
            }
          } else if (t.isMemberExpression(callee)) {
            const objName = toVar(callee.object)
            const tmpFuncName = toVar(callee.property)
            if (objName === taroImportDefaultName && APIS_NEED_TO_APPEND_THIS.has(tmpFuncName)) {
              needToAppendThis = true
              funcName = tmpFuncName
            }
          } else if (t.isIdentifier(callee)) {
            const tmpFuncName = toVar(callee)
            const oriFuncName = taroapiMap.get(tmpFuncName)
            if (APIS_NEED_TO_APPEND_THIS.has(oriFuncName)) {
              needToAppendThis = true
              funcName = oriFuncName
            }
          }
          if (needToAppendThis) {
            const thisOrder = APIS_NEED_TO_APPEND_THIS.get(funcName)
            if (thisOrder && !node.arguments[thisOrder]) {
              node.arguments[thisOrder] = t.thisExpression()
            }
          }
        }


AssignmentExpression (astPath) {
        const node = astPath.node
        const left = node.left
        if (t.isMemberExpression(left) && t.isIdentifier(left.object)) {
          if (left.object.name === componentClassName
              && t.isIdentifier(left.property)
              && left.property.name === 'config') {
            needSetConfigFromHooks = true
            configFromHooks = node.right
            pageConfig = toVar(node.right)
          }
        }
      }


exit (astPath: NodePath<t.Program>) {
          const node = astPath.node
          if (hasJSX) {
            if (!importNervNode) {
              importNervNode = t.importDeclaration(
                [t.importDefaultSpecifier(t.identifier(nervJsImportDefaultName))],
                t.stringLiteral('nervjs')
              )
              const specifiers = importNervNode.specifiers
              const defaultSpecifier = specifiers.find(item => t.isImportDefaultSpecifier(item))
              if (!defaultSpecifier) {
                specifiers.unshift(
                  t.importDefaultSpecifier(t.identifier(nervJsImportDefaultName))
                )
              }
              node.body.unshift(importNervNode)
            }
            if (!importTaroNode) {
              importTaroNode = t.importDeclaration(
                [t.importDefaultSpecifier(t.identifier('Taro'))],
                t.stringLiteral('@tarojs/taro-h5')
              )
              node.body.unshift(importTaroNode)
            }
            astPath.traverse({
              ClassBody (astPath) {
                if (needSetConfigFromHooks) {
                  const classPath = astPath.findParent((p: NodePath<t.Node>) => p.isClassExpression() || p.isClassDeclaration()) as NodePath<t.ClassDeclaration>
                  classPath.node.body.body.unshift(
                    t.classProperty(
                      t.identifier('config'),
                      configFromHooks as t.ObjectExpression
                    )
                  )
                }
              }
            })
          }
        }


ClassBody (astPath) {
                if (needSetConfigFromHooks) {
                  const classPath = astPath.findParent((p: NodePath<t.Node>) => p.isClassExpression() || p.isClassDeclaration()) as NodePath<t.ClassDeclaration>
                  classPath.node.body.body.unshift(
                    t.classProperty(
                      t.identifier('config'),
                      configFromHooks as t.ObjectExpression
                    )
                  )
                }
              }


enter (astPath: NodePath<t.ClassProperty>) {
          const node = astPath.node
          const key = toVar(node.key)
          if (key === 'config') {
            pageConfig = toVar(node.value)
          }
        }


exit (astPath: NodePath<t.ClassMethod>) {
          const node = astPath.node
          const key = node.key
          const keyName = toVar(key)
          if (keyName === 'componentDidMount') {
            componentDidMountNode = node
          } else if (keyName === 'componentDidShow') {
            componentDidShowNode = node
          } else if (keyName === 'componentDidHide') {
            componentDidHideNode = node
          } else if (keyName === 'onPageScroll') {
            hasOnPageScroll = true
          } else if (keyName === 'onReachBottom') {
            hasOnReachBottom = true
          } else if (keyName === 'onPullDownRefresh') {
            hasOnPullDownRefresh = true
          } else if (keyName === 'render') {
            renderReturnStatementPaths.length = 0
            renderClassMethodNode = node
            astPath.traverse({
              ReturnStatement: {
                exit (returnAstPath: NodePath<t.ReturnStatement>) {
                  renderReturnStatementPaths.push(returnAstPath)
                }
              }
            })
          }
        }


exit (returnAstPath: NodePath<t.ReturnStatement>) {
                  renderReturnStatementPaths.push(returnAstPath)
                }


exit (astPath: NodePath<t.ClassBody>) {
          const node = astPath.node
          if (!componentDidMountNode) {
            componentDidMountNode = t.classMethod('method', t.identifier('componentDidMount'), [],
              t.blockStatement([
                toAst('super.componentDidMount && super.componentDidMount()') as t.Statement
              ]), false, false)
            node.body.push(componentDidMountNode)
          }
          if (!componentDidShowNode) {
            componentDidShowNode = t.classMethod('method', t.identifier('componentDidShow'), [],
              t.blockStatement([
                toAst('super.componentDidShow && super.componentDidShow()') as t.Statement
              ]), false, false)
            node.body.push(componentDidShowNode)
          }
          if (!componentDidHideNode) {
            componentDidHideNode = t.classMethod('method', t.identifier('componentDidHide'), [],
              t.blockStatement([
                toAst('super.componentDidHide && super.componentDidHide()') as t.Statement
              ]), false, false)
            node.body.push(componentDidHideNode)
          }
          if (hasOnReachBottom) {
            componentDidShowNode.body.body.push(
              toAst(`
                this._offReachBottom = Taro.onReachBottom({
                  callback: this.onReachBottom,
                  ctx: this,
                  onReachBottomDistance: ${JSON.stringify(pageConfig.onReachBottomDistance)}
                })
              `)
            )
            componentDidHideNode.body.body.push(
              toAst('this._offReachBottom && this._offReachBottom()')
            )
          }
          if (hasOnPageScroll) {
            componentDidShowNode.body.body.push(
              toAst('this._offPageScroll = Taro.onPageScroll({ callback: this.onPageScroll, ctx: this })')
            )
            componentDidHideNode.body.body.push(
              toAst('this._offPageScroll && this._offPageScroll()')
            )
          }
          if (hasOnPullDownRefresh) {
            componentDidShowNode.body.body.push(
              toAst(`
                this.pullDownRefreshRef && this.pullDownRefreshRef.bindEvent()
              `)
            )
            componentDidHideNode.body.body.push(
              toAst(`
                this.pullDownRefreshRef && this.pullDownRefreshRef.unbindEvent()
              `)
            )
          }
        }


exit (astPath: NodePath<t.ExportDefaultDeclaration>) {
          exportDefaultDeclarationNode = astPath.node
        }


exit (astPath: NodePath<t.ExportNamedDeclaration>) {
          exportNamedDeclarationPath = astPath
        }


exit (astPath: NodePath<t.Program>) {
          if (hasOnPullDownRefresh) {
            // 增加PullDownRefresh组件
            if (!importTaroComponentNode) {
              importTaroComponentNode = t.importDeclaration(
                [],
                t.stringLiteral('@tarojs/components')
              )
              astPath.node.body.unshift(importTaroComponentNode)
            }
            const specifiers = importTaroComponentNode.specifiers
            const pos = importTaroComponentNode.specifiers.findIndex(specifier => {
              if (!t.isImportSpecifier(specifier)) return false
              const importedComponent = toVar(specifier.imported)
              return importedComponent === 'PullDownRefresh'
            })
            if (pos === -1) {
              specifiers.push(
                t.importSpecifier(
                  t.identifier('PullDownRefresh'),
                  t.identifier('PullDownRefresh')
                )
              )
            }
            const returnStatement = renderReturnStatementPaths.filter(renderReturnStatementPath => {
              const funcParentPath: NodePath = renderReturnStatementPath.getFunctionParent()
              return funcParentPath.node === renderClassMethodNode
            })
            returnStatement.forEach(returnAstPath => {
              const statement = returnAstPath.node
              const varName = returnAstPath.scope.generateUid()
              const returnValue = statement.argument
              const pullDownRefreshNode = t.variableDeclaration(
                'const',
                [t.variableDeclarator(
                  t.identifier(varName),
                  returnValue
                )]
              )
              returnAstPath.insertBefore(pullDownRefreshNode)
              statement.argument = (toAst(`
                <PullDownRefresh
                  onRefresh={this.onPullDownRefresh && this.onPullDownRefresh.bind(this)}
                  ref={ref => {
                    if (ref) this.pullDownRefreshRef = ref
                }}>{${varName}}</PullDownRefresh>`) as t.ExpressionStatement).expression
            })
          }

          if (!exportDefaultDeclarationNode && exportNamedDeclarationPath) {
            replaceExportNamedToDefault(exportNamedDeclarationPath)
          }
        }


getTempDir (filePath, originalFilePath) {
    const appPath = this.appPath
    const sourcePath = this.sourcePath
    const tempDir = this.tempDir
    let dirname = path.dirname(filePath)

    if (filePath.indexOf(sourcePath) < 0) {
      dirname = path.extname(originalFilePath) ? path.dirname(originalFilePath) : originalFilePath
    }
    const relPath = path.relative(sourcePath, dirname)

    return path.resolve(appPath, tempDir, relPath)
  }


transformToTempDir (filePath: string) {
    const sourcePath = this.sourcePath
    const isAbsolute = path.isAbsolute(filePath)
    if (!isAbsolute) return filePath

    const relPath = path.relative(sourcePath, filePath)
    return relPath.startsWith('..')
      ? filePath
      : path.resolve(this.tempPath, relPath)
  }


processFiles (filePath, originalFilePath) {
    const original = fs.readFileSync(filePath, { encoding: 'utf8' })
    const extname = path.extname(filePath)
    const distDirname = this.getTempDir(filePath, originalFilePath)
    const isScriptFile = REG_SCRIPTS.test(extname)
    const distPath = this.getDist(distDirname, filePath, isScriptFile)
    fs.ensureDirSync(distDirname)

    try {
      if (isScriptFile) {
        // 脚本文件 处理一下
        const fileType = this.classifyFiles(filePath)
        if (fileType === FILE_TYPE.ENTRY) {
          this.pages = []
          const result = this.processEntry(original, filePath)
          if (Array.isArray(result)) {
            result.forEach(([pageName, code]) => {
              fs.writeFileSync(
                path.join(distDirname, `${pageName}.js`),
                code
              )
            })
          } else {
            fs.writeFileSync(distPath, result)
          }
        } else {
          const code = this.processOthers(original, filePath, fileType)
          fs.writeFileSync(distPath, code)
        }
      } else {
        // 其他 直接复制
        fs.copySync(filePath, distPath)
      }
    } catch (e) {
      console.log(e)
    }
  }


getDist (distDirname, filename, isScriptFile) {
    return isScriptFile
      ? path.format({
        dir: distDirname,
        ext: '.js',
        name: path.basename(filename, path.extname(filename))
      })
      : path.format({
        dir: distDirname,
        base: path.basename(filename)
      })
  }


responsePriceData => {
        this.estates = responsePriceData;
        console.log('success', this.estates);
      }


responsePriceError => {
        this.estates = null;
        console.log('fail')
      }


() => console.log('get scraps method executed')


@Component({
  selector: 'app-compare-rent',
  templateUrl: './compare-rent.component.html',
  styleUrls: ['./compare-rent.component.css']
})
export class CompareRentComponent implements OnInit {

  public barChartOptions: ChartOptions = {
    responsive: true,
    // We use these empty structures as placeholders for dynamic theming.
    scales: { xAxes: [{}], yAxes: [{}] },
    plugins: {
      datalabels: {
        anchor: 'end',
        align: 'end',
      }
    }
  };

  data = [
    {
      "_id": "5ca7e5c5fdbfa311d857c11c",
      "Locality": "Bannimantap",
      "Min": "4211",
      "max": "5614",
      "AvgPrice": "4912"
    },
    { "_id": "5ca7e639f271a611d88312ae", "Locality": "Bogadi", "Min": "2042", "max": "3500", "AvgPrice": "2771" },
    { "_id": "5ca7e664f271a611d88312af", "Locality": "Gokulam", "Min": "5093", "max": "5648", "AvgPrice": "5370" }];

  public barChartLabels: Label[] = ['Min', 'Max', 'AvgPrice'];
  public barChartType: ChartType = 'bar';
  public barChartLegend = true;
  public estates: any[];

  public barChartData: ChartDataSets[] = [
    { data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A' },
    { data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B' }
  ];

  constructor(private _scrapper: ScrapperService) {
    const temp = [];
    this.data.forEach(function (elem) {
      temp.push({
        data: [
          parseInt(elem['Min'], 10),
          parseInt(elem['max'], 10),
          parseInt(elem['AvgPrice'], 10)
        ],
        label: elem['Locality']
      });
      console.log(temp);
    });
    this.barChartData = temp;
  }

  getScrapper() {
    this._scrapper.getScraps('gokulam').subscribe(
      responsePriceData => {
        this.estates = responsePriceData;
        console.log('success', this.estates);
      },
      responsePriceError => {
        this.estates = null;
        console.log('fail')
      },
      () => console.log('get scraps method executed')
    );
  }

  ngOnInit() {
    this.getScrapper();
  }

}


getScrapper() {
    this._scrapper.getScraps('gokulam').subscribe(
      responsePriceData => {
        this.estates = responsePriceData;
        console.log('success', this.estates);
      },
      responsePriceError => {
        this.estates = null;
        console.log('fail')
      },
      () => console.log('get scraps method executed')
    );
  }


ngOnInit() {
    this.getScrapper();
  }


async () => {
  const input = parseInt("foo", 10);
  await expect(wait(input)).rejects.toThrow("milliseconds not a number");
}


async () => {
  const start = new Date();
  await wait(500);
  const end = new Date();
  var delta = Math.abs(end.getTime() - start.getTime());
  expect(delta).toBeGreaterThan(450);
}


() => {
  process.env["INPUT_MILLISECONDS"] = "500";
  const np = process.execPath;
  const ip = path.join(__dirname, "..", "dist", "index.js");
  const options: cp.ExecFileSyncOptions = {
    env: process.env
  };
  console.log(cp.execFileSync(np, [ip], options).toString());
}


params => {
        const artistId = params['id'];

        return this.artistService.getById(artistId);
      }


@Component({
  selector: 'app-artist',
  templateUrl: './artist.component.html',
  styleUrls: ['./artist.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ArtistComponent implements OnInit {
  artist$: Observable<Artist>;

  @ViewChild('albums')
  albumsElement: ElementRef;

  @ViewChild('artists')
  artistsElement: ElementRef;

  constructor(
    private route: ActivatedRoute,
    private renderer2: Renderer2,
    private artistService: ArtistService
  ) { }

  ngOnInit(): void {
    this.artist$ = this.route.params.pipe(
      concatMap(params => {
        const artistId = params['id'];

        return this.artistService.getById(artistId);
      })
    );
  }

  onShowButtonClick(): void {
    this.renderer2.addClass(this.albumsElement.nativeElement, ALBUMS_EXPANDED_CLASS);
    this.renderer2.addClass(this.artistsElement.nativeElement, SIMILAR_ARTISTS_SHRINKED_CLASS);
  }

  onHideButtonClick(): void {
    this.renderer2.removeClass(this.albumsElement.nativeElement, ALBUMS_EXPANDED_CLASS);
    this.renderer2.removeClass(this.artistsElement.nativeElement, SIMILAR_ARTISTS_SHRINKED_CLASS);
  }
}


ngOnInit(): void {
    this.artist$ = this.route.params.pipe(
      concatMap(params => {
        const artistId = params['id'];

        return this.artistService.getById(artistId);
      })
    );
  }


onShowButtonClick(): void {
    this.renderer2.addClass(this.albumsElement.nativeElement, ALBUMS_EXPANDED_CLASS);
    this.renderer2.addClass(this.artistsElement.nativeElement, SIMILAR_ARTISTS_SHRINKED_CLASS);
  }


onHideButtonClick(): void {
    this.renderer2.removeClass(this.albumsElement.nativeElement, ALBUMS_EXPANDED_CLASS);
    this.renderer2.removeClass(this.artistsElement.nativeElement, SIMILAR_ARTISTS_SHRINKED_CLASS);
  }


function AdapterGitHubButton(props: { repo: Repository; user: User }) {
	const { repo, user } = props;
	const [counts, setCounts] = useState(`${repo.open_issues} open issues/PRs`);
	const [color, setColor] = useState<"primary" | "error">("primary");

	useEffect(() => {
		const loadPullRequests = async () => {
			if (!repo.open_issues) {
				setCounts("no open PRs / no open issues");
				return;
			}

			const gitHub = GitHubComm.forToken(user.token);
			const pullRequests = await gitHub
				.getRepo(repo)
				.getPullRequests("open");
			const prCount = pullRequests.length;
			const issueCount = repo.open_issues - prCount;
			const getText = (value: number, type: string) => {
				return `${value || "no"} open ${type}${value === 1 ? "" : "s"}`;
			};

			setCounts(
				`${getText(prCount, "PR")} / ${getText(issueCount, "issue")}`,
			);
			setColor(prCount > 0 ? "error" : "primary");
		};
		loadPullRequests().catch(console.error);
	}, [repo, user]);

	return (
		<CardButton
			icon={
				<Tooltip title={`GitHub Repository (${counts})`}


function AdapterSentryButton(props: { projects: ProjectInfo[] }) {
	const { projects } = props;

	const [tooltip, setTooltip] = useState("Sentry available for this adapter");
	const [errorCount, setErrorCount] = useState<number[]>();
	const [errorCounts, setErrorCounts] = useState<number[][]>([]);
	const [open, setOpen] = useState(false);

	useEffect(() => {
		if (projects.length === 0) {
			return;
		}

		const loadProjectInfos = async () => {
			const stats24h = await getSentryStats(
				projects.map((p) => p.id),
				"24h",
			);
			const stats30d = await getSentryStats(
				projects.map((p) => p.id),
				"30d",
			);
			const errors = [
				stats24h.map(
					(stat) => stat.stats.reduce((old, num) => old + num[1], 0),
					0,
				),
				stats30d.map(
					(stat) => stat.stats.reduce((old, num) => old + num[1], 0),
					0,
				),
			];
			setErrorCounts(errors);

			const totalErrors = errors.map((e) =>
				e.reduce((old, err) => old + err, 0),
			);
			setErrorCount(totalErrors);

			if (totalErrors[0]) {
				setTooltip(
					`Sentry (${totalErrors[0]} errors in the last 24 hours)`,
				);
			} else {
				setTooltip(
					`Sentry (${totalErrors[1]} errors in the last 30 days)`,
				);
			}
		};
		loadProjectInfos().catch(console.error);
	}, [projects]);

	if (projects.length === 0) {
		return (
			<Tooltip title="Sentry not used by this adapter">
				<span>
					<CardButton disabled icon={<SentryIcon />} />
				</span>
			</Tooltip>
		);
	}


function AddWatchDialog(props: AddWatchDialogProps) {
	const { user, open, onClose } = props;

	const [repoNames, setRepoNames] = useState<string[]>([]);
	const [repoName, setRepoName] = useState("");
	const [error, setError] = useState("");
	const [validating, setValidating] = useState(false);

	useEffect(() => {
		if (!open) {
			return;
		}
		const loadData = async () => {
			const latest = await getLatest();
			const names = Object.keys(latest).map((adapterName) =>
				latest[adapterName].meta.replace(
					/^\w+:\/\/[^/]+\/([^/]+\/[^/]+)\/.+$/,
					"$1",
				),
			);
			setRepoNames(names);
		};
		loadData().catch(console.error);
	}, [open]);

	const validate = async () => {
		setValidating(true);
		try {
			const gitHub = GitHubComm.forToken(user.token);
			const [owner, repo] = repoName.split("/", 2);
			const latest = await getLatest();
			const infos = await getAdapterInfos(
				await gitHub.getRepo(owner, repo).getRepo(),
				latest,
			);
			if (!infos.info) {
				throw new Error("This is not an ioBroker adapter");
			}
			onClose(repoName);
		} catch (error: any) {
			setError(error.message || error);
		} finally {
			setValidating(false);
		}
	};

	return (
		<Dialog
			open={!!open}


async function getDiscoveryLink(adapterName: string) {
	return (await hasDiscoverySupport(adapterName))
		? `https://github.com/ioBroker/ioBroker.discovery/blob/master/lib/adapters/` +
				`${uc(adapterName)}.js`
		: "";
}


async function getWeblateLink(adapterName: string) {
	try {
		const components = await getWeblateAdapterComponents();
		const component = components.results.find(
			(c: any) => c.name === adapterName,
		);
		if (component) {
			return (
				`https://weblate.iobroker.net/projects/adapters/` +
				`${uc(component.slug)}/`
			);
		}
	} catch {
		// ignore and leave "weblateLink" empty
	}

	return "";
}


async function getSentryProjects(adapterName: string) {
	const allProjects = await getSentryProjectInfos();
	return allProjects.filter((p) => p.adapterName === adapterName);
}


async function getAdapterCard(
	infos: AdapterInfos,
	history: H.History<AdapterCheckLocationState>,
	user: User,
	onClose?: () => void,
): Promise<DashboardCardProps | undefined> {
	const { repo, info } = infos;
	if (!info) {
		return;
	}
	const [discoveryLink, weblateLink, sentryProjects, ratings] =
		await Promise.all([
			getDiscoveryLink(info.name),
			getWeblateLink(info.name),
			getSentryProjects(info.name),
			getAllRatings(),
		]);
	const openAdapterCheck = () => {
		history.push("/adapter-check", {
			repoFullName: repo.full_name,
		});
	};

	return {
		title: repo.name,
		onClose,
		img: info?.extIcon,
		badges: {
			"npm version": `https://img.shields.io/npm/v/iobroker.${info.name}.svg`,
			"Stable version": `https://iobroker.live/badges/${info.name}-stable.svg`,
		},
		rating: ratings[info.name]?.rating,
		text: info?.desc?.en || repo.description || "No description available",
		squareImg: true,
		to: `/adapter/${info.name}`,
		buttons: [
			<AdapterGitHubButton repo={repo} user={user} />,
			<CardButton
				icon={
					<Tooltip title="Start Adapter Check">
						<AdapterCheckIcon />
					</Tooltip>
				}
				onClick={openAdapterCheck}
			/>,
			<Tooltip
				title={`${
					discoveryLink ? "S" : "Not s"
				}upported by ioBroker.discovery`}
			>
				<span>
					<CardButton
						disabled={!discoveryLink}
						icon={<DiscoveryIcon />}