├── .eslintignore ├── packages ├── cli │ ├── .npmrc │ ├── h5bundle │ │ ├── .npmrc │ │ ├── src │ │ │ ├── miniprogram │ │ │ ├── react-app-env.d.ts │ │ │ ├── setupTests.ts │ │ │ ├── index.css │ │ │ ├── types.ts │ │ │ ├── reportWebVitals.ts │ │ │ ├── index.tsx │ │ │ ├── App.css │ │ │ ├── components │ │ │ │ ├── component-item │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.tsx │ │ │ │ ├── view-container │ │ │ │ │ └── index.css │ │ │ │ └── component-title-bar │ │ │ │ │ └── index.tsx │ │ │ ├── h5pro-component-react │ │ │ │ └── index.d.ts │ │ │ └── logo.svg │ │ ├── .eslintignore │ │ ├── public │ │ │ ├── robots.txt │ │ │ ├── favicon.ico │ │ │ ├── logo192.png │ │ │ ├── logo512.png │ │ │ ├── manifest.json │ │ │ └── index.html │ │ ├── .gitignore │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── README.md │ ├── src │ │ ├── lib │ │ │ ├── util │ │ │ │ ├── sleep.ts │ │ │ │ ├── replaceUuid.ts │ │ │ │ ├── whoami.ts │ │ │ │ ├── tarExtract.ts │ │ │ │ ├── writePortFile.ts │ │ │ │ ├── getRc.ts │ │ │ │ ├── isMacM1.ts │ │ │ │ ├── getGulpLocation.ts │ │ │ │ ├── performance.ts │ │ │ │ ├── checkNodeVersion.ts │ │ │ │ ├── clean.ts │ │ │ │ ├── getCurrentPkgInfo.ts │ │ │ │ ├── getCliArgsAndOptions.ts │ │ │ │ ├── getMiniProjectJson.ts │ │ │ │ ├── getMt2Config.ts │ │ │ │ ├── getJson.ts │ │ │ │ ├── getGlobalIntallCommand.ts │ │ │ │ ├── setJson.ts │ │ │ │ ├── suggestCommands.ts │ │ │ │ ├── globalRc.ts │ │ │ │ ├── connectToIDE.ts │ │ │ │ ├── gulpInspector.ts │ │ │ │ ├── getSimulatorFrameworkStoreDir.ts │ │ │ │ ├── getSimulatorAssetsDir.ts │ │ │ │ ├── getValidateRc.ts │ │ │ │ ├── getH5ProBinPath.ts │ │ │ │ └── getNgrokBinPath.ts │ │ │ ├── cli-shared-utils │ │ │ │ ├── index.ts │ │ │ │ └── lib │ │ │ │ │ ├── logger │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── base.ts │ │ │ │ │ └── index.ts │ │ │ │ │ ├── network │ │ │ │ │ └── index.ts │ │ │ │ │ ├── spinner.ts │ │ │ │ │ ├── watcher.ts │ │ │ │ │ └── process │ │ │ │ │ └── getProcessForPort.ts │ │ │ └── sevice │ │ │ │ └── index.ts │ │ ├── scheduler │ │ │ └── command │ │ │ │ ├── commandWrapper.ts │ │ │ │ ├── commandLoader.ts │ │ │ │ └── index.ts │ │ ├── actions │ │ │ ├── updateConfig.ts │ │ │ ├── init.ts │ │ │ ├── proxy │ │ │ │ ├── proxy.ts │ │ │ │ └── index.ts │ │ │ ├── simulatorProxyServer.ts │ │ │ └── lint.ts │ │ ├── commands │ │ │ ├── init │ │ │ │ └── index.ts │ │ │ ├── login │ │ │ │ └── index.ts │ │ │ ├── lint │ │ │ │ └── index.ts │ │ │ ├── upload │ │ │ │ └── index.ts │ │ │ ├── preview │ │ │ │ └── index.ts │ │ │ ├── preview-inside │ │ │ │ └── index.ts │ │ │ ├── remote-debug │ │ │ │ └── index.ts │ │ │ └── ngrok │ │ │ │ └── index.ts │ │ └── bin │ │ │ └── dd.ts │ ├── __tests__ │ │ ├── mock │ │ │ ├── mp1 │ │ │ │ └── ding.config.json │ │ │ ├── mp2 │ │ │ │ ├── ding.config.json │ │ │ │ └── mini.project.json │ │ │ └── fakeCommand │ │ │ │ └── index.ts │ │ └── utils.spec.ts │ ├── assets │ │ └── ding.cfg │ ├── .babelrc │ ├── .npmignore │ ├── tsconfig.json │ ├── README.md │ ├── tpl │ │ ├── webSimulator.html │ │ └── miniAppWebSimulator.html │ └── LICENSE ├── generator │ ├── src │ │ ├── router │ │ │ └── index.ts │ │ ├── common │ │ │ ├── constants.ts │ │ │ ├── errors.ts │ │ │ └── config.ts │ │ └── utils │ │ │ ├── copy.ts │ │ │ ├── request.ts │ │ │ ├── isValidRepoDirectory.ts │ │ │ ├── getJson.ts │ │ │ ├── clean.ts │ │ │ ├── createManifest.ts │ │ │ ├── getGlobalInstallCommand.ts │ │ │ ├── timeInspector.ts │ │ │ ├── logger.ts │ │ │ ├── getVersions.ts │ │ │ ├── cli-shared-utils.ts │ │ │ ├── generateTitle.ts │ │ │ └── store.ts │ ├── generators │ │ ├── router │ │ │ └── index.js │ │ ├── utils │ │ │ ├── request.js │ │ │ ├── isValidRepoDirectory.js │ │ │ ├── timeInspector.js │ │ │ ├── copy.js │ │ │ ├── logger.js │ │ │ ├── getJson.js │ │ │ ├── getGlobalInstallCommand.js │ │ │ ├── store.js │ │ │ └── clean.js │ │ └── common │ │ │ ├── constants.js │ │ │ ├── errors.js │ │ │ └── config.js │ ├── README.md │ ├── .npmignore │ ├── tsconfig.json │ ├── .babelrc │ ├── LICENSE │ ├── __tests__ │ │ └── generator.spec.ts │ └── package.json ├── opensdk │ ├── .npmignore │ ├── __tests__ │ │ ├── examples │ │ │ ├── miniapp-use-plugin │ │ │ │ ├── app.acss │ │ │ │ ├── pages │ │ │ │ │ └── index │ │ │ │ │ │ ├── index.acss │ │ │ │ │ │ ├── index.axml │ │ │ │ │ │ ├── index.json │ │ │ │ │ │ └── index.js │ │ │ │ ├── snapshot.png │ │ │ │ ├── app.js │ │ │ │ └── app.json │ │ │ ├── miniapp-use-subpackages │ │ │ │ ├── app.acss │ │ │ │ ├── packageA │ │ │ │ │ └── pages │ │ │ │ │ │ └── b │ │ │ │ │ │ ├── b.acss │ │ │ │ │ │ ├── b.json │ │ │ │ │ │ ├── b.axml │ │ │ │ │ │ └── b.js │ │ │ │ ├── pages │ │ │ │ │ └── index │ │ │ │ │ │ ├── index.acss │ │ │ │ │ │ ├── index.json │ │ │ │ │ │ ├── index.axml │ │ │ │ │ │ └── index.js │ │ │ │ ├── snapshot.png │ │ │ │ ├── app.js │ │ │ │ └── app.json │ │ │ ├── plugin │ │ │ │ ├── plugin │ │ │ │ │ ├── pages │ │ │ │ │ │ └── index │ │ │ │ │ │ │ ├── index.acss │ │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ │ ├── index.json │ │ │ │ │ │ │ └── index.axml │ │ │ │ │ ├── components │ │ │ │ │ │ └── version │ │ │ │ │ │ │ ├── version.acss │ │ │ │ │ │ │ ├── version.json │ │ │ │ │ │ │ ├── version.axml │ │ │ │ │ │ │ └── version.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── api │ │ │ │ │ │ └── data.js │ │ │ │ │ └── plugin.json │ │ │ │ ├── miniprogram │ │ │ │ │ ├── app.acss │ │ │ │ │ ├── pages │ │ │ │ │ │ └── index │ │ │ │ │ │ │ ├── index.json │ │ │ │ │ │ │ ├── index.acss │ │ │ │ │ │ │ ├── index.axml │ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── plugin.png │ │ │ │ │ ├── app.js │ │ │ │ │ └── app.json │ │ │ │ ├── mini.project.json │ │ │ │ └── snapshot.png │ │ │ └── miniapp-default │ │ │ │ ├── pages │ │ │ │ ├── index │ │ │ │ │ ├── index.json │ │ │ │ │ ├── index.axml │ │ │ │ │ └── index.js │ │ │ │ └── web-view │ │ │ │ │ ├── web-view.acss │ │ │ │ │ ├── web-view.json │ │ │ │ │ ├── web-view.js │ │ │ │ │ └── web-view.axml │ │ │ │ ├── app.acss │ │ │ │ ├── snapshot.png │ │ │ │ ├── app.json │ │ │ │ └── app.js │ │ ├── sdk-test-config.ts │ │ ├── utils.test.ts │ │ ├── config │ │ │ └── projectConfig.test.ts │ │ ├── openapi.test.ts │ │ ├── openapiV2.spec.ts │ │ └── task │ │ │ ├── buildTask.test.ts │ │ │ └── previewBuildTask.test.ts │ ├── src │ │ ├── log.ts │ │ ├── config │ │ │ ├── projectConfig.ts │ │ │ └── appConfig.ts │ │ ├── utils.ts │ │ ├── utils │ │ │ ├── download.ts │ │ │ ├── getCompiler.ts │ │ │ └── downloadTinyCli.ts │ │ ├── task │ │ │ └── TaskBase.ts │ │ └── index.ts │ ├── tsconfig.json │ └── package.json └── dingtalk-h5package-opensdk │ ├── tsconfig.json │ ├── __tests__ │ ├── source │ │ └── index.html │ └── index.spec.ts │ ├── README.md │ ├── package.json │ └── src │ ├── cli │ └── h5package.ts │ └── OpenGateway.ts ├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ └── proposal.md ├── jest.config.js ├── .editorconfig ├── lerna.json ├── appveyor.yml ├── scripts ├── build.js ├── test.js └── genChangelog.js ├── LICENSE ├── .eslintrc ├── .circleci └── config.yml ├── package.json ├── CHANGELOG.md ├── .gitignore └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /node_modules/ -------------------------------------------------------------------------------- /packages/cli/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /packages/generator/src/router/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/opensdk/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ -------------------------------------------------------------------------------- /packages/cli/h5bundle/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /packages/generator/generators/router/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-plugin/app.acss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-subpackages/app.acss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | packages/cli @lou1swu 2 | packages/generator @lou1swu -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/plugin/pages/index/index.acss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-default/pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-plugin/pages/index/index.acss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/plugin/pages/index/index.js: -------------------------------------------------------------------------------- 1 | Page({}) -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/plugin/pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/miniprogram: -------------------------------------------------------------------------------- 1 | /Users/wuzequan/Desktop/pluginTest/miniprogram -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-default/pages/web-view/web-view.acss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-default/pages/web-view/web-view.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-subpackages/packageA/pages/b/b.acss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-subpackages/pages/index/index.acss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/plugin/components/version/version.acss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/.eslintignore: -------------------------------------------------------------------------------- 1 | src/bundle 2 | src/h5pro-component-react 3 | src/sdk -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-subpackages/packageA/pages/b/b.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-subpackages/pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/miniprogram/app.acss: -------------------------------------------------------------------------------- 1 | page { 2 | background: #f7f7f7; 3 | } 4 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/plugin/pages/index/index.axml: -------------------------------------------------------------------------------- 1 | Hello from plugin page -------------------------------------------------------------------------------- /packages/generator/README.md: -------------------------------------------------------------------------------- 1 | # generator-dd-worktab-plugin 2 | This is a generator for dingding application. 3 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/plugin/components/version/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true 3 | } -------------------------------------------------------------------------------- /packages/cli/h5bundle/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-plugin/pages/index/index.axml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/cli/src/lib/util/sleep.ts: -------------------------------------------------------------------------------- 1 | export default (ms: number): Promise => new Promise(resolve => setTimeout(resolve, ms)); -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/plugin/components/version/version.axml: -------------------------------------------------------------------------------- 1 | 2 | Pluign Version: 0.0.5 3 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-default/pages/web-view/web-view.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | data: {}, 3 | onLoad() {}, 4 | }); 5 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-subpackages/packageA/pages/b/b.axml: -------------------------------------------------------------------------------- 1 | 2 | Page B in packageA/pages/b/b 3 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-subpackages/packageA/pages/b/b.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | data: {}, 3 | onLoad() {}, 4 | }); 5 | -------------------------------------------------------------------------------- /packages/cli/__tests__/mock/mp1/ding.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "mp", 3 | "typescript": false, 4 | "base": "./", 5 | "outDir": "./" 6 | } -------------------------------------------------------------------------------- /packages/cli/__tests__/mock/mp2/ding.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "mp", 3 | "typescript": false, 4 | "base": "./", 5 | "outDir": "./" 6 | } -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-default/pages/web-view/web-view.axml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/dingtalk-design-cli/HEAD/packages/cli/h5bundle/public/favicon.ico -------------------------------------------------------------------------------- /packages/cli/h5bundle/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/dingtalk-design-cli/HEAD/packages/cli/h5bundle/public/logo192.png -------------------------------------------------------------------------------- /packages/cli/h5bundle/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/dingtalk-design-cli/HEAD/packages/cli/h5bundle/public/logo512.png -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-default/app.acss: -------------------------------------------------------------------------------- 1 | page { 2 | box-sizing: border-box; 3 | background: #f7f7f7; 4 | padding: 10px; 5 | } -------------------------------------------------------------------------------- /packages/cli/src/lib/util/replaceUuid.ts: -------------------------------------------------------------------------------- 1 | export default function (url: string, uuid: string): string { 2 | return url.replace('___uuid___', uuid); 3 | } 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: [ 5 | '**/__tests__/**/*.spec.ts' 6 | ], 7 | }; -------------------------------------------------------------------------------- /packages/cli/__tests__/mock/mp2/mini.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "enableParallelLoader": false, 3 | "enableDistFileMinify": true, 4 | "enableEnhancedBuild": true 5 | } -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/miniprogram/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "version": "plugin://myPlugin/version" 4 | } 5 | } -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-plugin/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "plugin-version": "plugin://myPlugin/version" 4 | } 5 | } -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/mini.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "miniprogram", 3 | "pluginRoot": "plugin", 4 | "compileType": "plugin" 5 | } 6 | -------------------------------------------------------------------------------- /packages/cli/assets/ding.cfg: -------------------------------------------------------------------------------- 1 | server_addr: "vaiwan.cn:443" 2 | trust_host_root_certs: false 3 | tunnels: 4 | ssh: 5 | proto: 6 | tcp: "3306" 7 | remote_port: 1234 -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/dingtalk-design-cli/HEAD/packages/opensdk/__tests__/examples/plugin/snapshot.png -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/plugin/index.js: -------------------------------------------------------------------------------- 1 | var data = require('./api/data') 2 | 3 | module.exports = { 4 | getData: data.getData, 5 | setData: data.setData 6 | } 7 | -------------------------------------------------------------------------------- /packages/cli/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-proposal-class-properties" 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-default/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/dingtalk-design-cli/HEAD/packages/opensdk/__tests__/examples/miniapp-default/snapshot.png -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-plugin/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/dingtalk-design-cli/HEAD/packages/opensdk/__tests__/examples/miniapp-use-plugin/snapshot.png -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/miniprogram/plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/dingtalk-design-cli/HEAD/packages/opensdk/__tests__/examples/plugin/miniprogram/plugin.png -------------------------------------------------------------------------------- /packages/cli/src/lib/util/whoami.ts: -------------------------------------------------------------------------------- 1 | import * as username from 'username'; 2 | 3 | let me = ''; 4 | 5 | export default function whoami(): string { 6 | me = me || username.sync(); 7 | return me || 'N/A'; 8 | } -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-subpackages/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/dingtalk-design-cli/HEAD/packages/opensdk/__tests__/examples/miniapp-use-subpackages/snapshot.png -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-default/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/web-view/web-view" 5 | ], 6 | "window": { 7 | "defaultTitle": "My App" 8 | } 9 | } -------------------------------------------------------------------------------- /packages/generator/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export enum APP_TYPE_ENUM { 2 | PLUGIN = 'plugin', 3 | MP = 'mp', 4 | H5 = 'h5', 5 | DOCSCOOLAPP = 'docscoolapp', 6 | } 7 | 8 | export const DEFAULT_APP_TYPE = APP_TYPE_ENUM.PLUGIN; -------------------------------------------------------------------------------- /packages/cli/src/lib/cli-shared-utils/index.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import * as execa from 'execa'; 3 | import semver from 'semver'; 4 | 5 | export * from './lib/env'; 6 | export { 7 | chalk, 8 | execa, 9 | semver, 10 | }; -------------------------------------------------------------------------------- /packages/generator/.npmignore: -------------------------------------------------------------------------------- 1 | /.vscode/ 2 | /__tests__/ 3 | /src/ 4 | /node_modules/ 5 | .babelrc 6 | .editorconfig 7 | .eslintignore 8 | .gitignore 9 | .eslintrc 10 | yarn.lock 11 | jest.config.js 12 | package-lock.json 13 | tsconfig.json -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/plugin/components/version/version.js: -------------------------------------------------------------------------------- 1 | Component({ 2 | mixins: [], 3 | data: {}, 4 | props: {}, 5 | didMount() {}, 6 | didUpdate() {}, 7 | didUnmount() {}, 8 | methods: {}, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/opensdk/src/log.ts: -------------------------------------------------------------------------------- 1 | 2 | // // 统计预览、上次成功失败的case 3 | // // 统计预览、上次的使用量 4 | // // 统计应用维度分布 5 | 6 | // wpo.setConfig({ 7 | // sample: 1, 8 | // spmId: 'dd.mini.opensdk', 9 | // tag: 'dd.mini', 10 | // }); 11 | 12 | // export const tracker = wpo; -------------------------------------------------------------------------------- /packages/cli/.npmignore: -------------------------------------------------------------------------------- 1 | /.vscode/ 2 | /__tests__/ 3 | /node_modules/ 4 | /src/ 5 | .babelrc 6 | .editorconfig 7 | .eslintignore 8 | .gitignore 9 | .eslintrc 10 | yarn.lock 11 | jest.config.js 12 | package-lock.json 13 | /tsconfig.json 14 | 15 | !/h5bundle/**/*.* -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-subpackages/pages/index/index.axml: -------------------------------------------------------------------------------- 1 | 2 | Main Page 3 | 4 | go to page/b/b 5 | 6 | -------------------------------------------------------------------------------- /packages/cli/src/lib/util/tarExtract.ts: -------------------------------------------------------------------------------- 1 | import tar from 'tar'; 2 | import mkdirp from 'mkdirp'; 3 | 4 | export default async (source: string, dest: string): Promise => { 5 | await mkdirp(dest); 6 | await tar.x({ 7 | file: source, 8 | C: dest, 9 | }); 10 | }; -------------------------------------------------------------------------------- /packages/cli/src/lib/util/writePortFile.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | export default function writePortFile(port: number): void { 5 | fs.writeFileSync(path.join(__dirname, '../../actions/port.js'), `window.miniapp_proxy_server_port = ${port};`); 6 | } 7 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/plugin/api/data.js: -------------------------------------------------------------------------------- 1 | var data = 'init data' 2 | 3 | function getData() { 4 | return data 5 | } 6 | 7 | function setData(value) { 8 | data = value 9 | } 10 | 11 | module.exports = { 12 | getData: getData, 13 | setData: setData 14 | } 15 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/plugin/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicComponents": { 3 | "version": "components/version/version" 4 | }, 5 | "publicPages": { 6 | "hello-page": "pages/index/index" 7 | }, 8 | "pages": [ 9 | "pages/index/index" 10 | ], 11 | "main": "index" 12 | } -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-plugin/app.js: -------------------------------------------------------------------------------- 1 | App({ 2 | onLaunch(options) { 3 | // 第一次打开 4 | // options.query == {number:1} 5 | console.info('App onLaunch'); 6 | }, 7 | onShow(options) { 8 | // 从后台被 scheme 重新打开 9 | // options.query == {number:1} 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/miniprogram/app.js: -------------------------------------------------------------------------------- 1 | App({ 2 | onLaunch(options) { 3 | // 第一次打开 4 | // options.query == {number:1} 5 | console.info('App onLaunch'); 6 | }, 7 | onShow(options) { 8 | // 从后台被 scheme 重新打开 9 | // options.query == {number:1} 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-plugin/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index" 4 | ], 5 | "window": { 6 | "defaultTitle": "My App" 7 | }, 8 | "plugins": { 9 | "myPlugin": { 10 | "provider": "5000000000100101", 11 | "version": "*" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-subpackages/app.js: -------------------------------------------------------------------------------- 1 | App({ 2 | onLaunch(options) { 3 | // 第一次打开 4 | // options.query == {number:1} 5 | console.info('App onLaunch'); 6 | }, 7 | onShow(options) { 8 | // 从后台被 scheme 重新打开 9 | // options.query == {number:1} 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /packages/cli/src/lib/cli-shared-utils/lib/logger/types.ts: -------------------------------------------------------------------------------- 1 | export enum ELoggerLevel { 2 | silent = 0, 3 | error = 1, 4 | warn = 2, 5 | info = 3, 6 | debug = 4, 7 | } 8 | 9 | export type ILoggerElement = any; 10 | 11 | export interface ILoggerOptions { 12 | logLevel?: ELoggerLevel; 13 | debug?: boolean; 14 | } -------------------------------------------------------------------------------- /packages/generator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "generators", 5 | "rootDir": "src", 6 | "baseUrl": "./src", 7 | "paths": { 8 | "@common/*": ["./common/*"] 9 | } 10 | }, 11 | "include": [ 12 | "src/**/*" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/miniprogram/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index" 4 | ], 5 | "plugins": { 6 | "myPlugin": { 7 | "version": "dev", 8 | "provider": "{{currentPluginId}}" 9 | } 10 | }, 11 | "window": { 12 | "defaultTitle": "My App" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/dingtalk-h5package-opensdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ], 11 | "exclude": [ 12 | "node_modules", 13 | "dist" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-subpackages/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "packageA/pages/b/b" 5 | ], 6 | "window": { 7 | "defaultTitle": "My App" 8 | }, 9 | "subPackages": [ 10 | { 11 | "root": "packageA", 12 | "pages": [ 13 | "pages/b/b" 14 | ] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/miniprogram/pages/index/index.acss: -------------------------------------------------------------------------------- 1 | .demo-page { 2 | padding: 116rpx; 3 | } 4 | 5 | .logo { 6 | width: 194rpx; 7 | height: 206rpx; 8 | } 9 | 10 | .title { 11 | margin: 40rpx 0; 12 | font-size: 28rpx; 13 | font-weight: bold; 14 | } 15 | 16 | .nav { 17 | margin-bottom: 10rpx; 18 | color: #1890FF; 19 | } 20 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/sdk-test-config.ts: -------------------------------------------------------------------------------- 1 | 2 | export const pre = { 3 | appKey: '', 4 | appSecret: '', 5 | host: '', 6 | miniAppId: '', 7 | agentId: '', 8 | }; 9 | 10 | 11 | export const daily = { 12 | appKey: '', 13 | appSecret: '', 14 | accessToken: '', 15 | host: 'https://pre-api.dingtalk.com', 16 | miniAppId: '', 17 | agentId: '', 18 | }; -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getRc.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { logger, } from '../cli-shared-utils/lib/logger'; 3 | import getJson from './getJson'; 4 | 5 | export default (path: string) => { 6 | try { 7 | const rc = getJson(path, false); 8 | return rc; 9 | } catch(e) { 10 | logger.error('get rc fail', e.message); 11 | return null; 12 | } 13 | }; -------------------------------------------------------------------------------- /packages/cli/src/lib/util/isMacM1.ts: -------------------------------------------------------------------------------- 1 | import * as os from 'os'; 2 | import { logger, } from '../cli-shared-utils/lib/logger'; 3 | 4 | export default (): boolean => { 5 | try { 6 | const cpus = os.cpus(); 7 | const m1Cpu = cpus.find(cpu => cpu.model === 'Apple M1'); 8 | return !!m1Cpu; 9 | } catch(e) { 10 | logger.error(e); 11 | return false; 12 | } 13 | }; -------------------------------------------------------------------------------- /packages/generator/src/utils/copy.ts: -------------------------------------------------------------------------------- 1 | import { execSync, } from 'child_process'; 2 | import * as path from 'path'; 3 | 4 | /** 5 | * 会强制覆盖目标文件且静默 6 | */ 7 | export default (source: string, dest: string) => { 8 | try { 9 | execSync(`cp -af ${path.join(source, '/')} ${dest}`); 10 | } catch(e) { 11 | console.error('exec cp error', e); 12 | throw e; 13 | } 14 | }; -------------------------------------------------------------------------------- /packages/dingtalk-h5package-opensdk/__tests__/source/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | hellp h5 package 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/generator/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "compact": false, 3 | "retainLines": true, 4 | "minified": false, 5 | "inputSourceMap": false, 6 | "sourceMaps": false, 7 | "plugins": [ 8 | [ 9 | "module-resolver", 10 | { 11 | "root": ["./generators"], 12 | "alias": { 13 | "@common": "./generators/common" 14 | } 15 | } 16 | ] 17 | ] 18 | } -------------------------------------------------------------------------------- /packages/cli/src/scheduler/command/commandWrapper.ts: -------------------------------------------------------------------------------- 1 | import { ICommandConfig, PlainRecord, } from '../../lib/common/types'; 2 | 3 | 4 | /** 5 | * A function used to add type to plugin constructor. 6 | * 7 | * - CO: CommandOptions 8 | */ 9 | export default function CommandWrapper< 10 | CO extends PlainRecord = PlainRecord 11 | >(config: ICommandConfig): ICommandConfig { 12 | return config; 13 | } -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "lib": [ 5 | "DOM" 6 | ], 7 | "outDir": "dist", 8 | "rootDir": "src", 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "allowJs": true, 12 | "strict": false 13 | }, 14 | "include": [ 15 | "src/**/*" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-default/app.js: -------------------------------------------------------------------------------- 1 | App({ 2 | onLaunch(options) { 3 | // 第一次打开 4 | // options.query == {number:1} 5 | console.info('App onLaunch'); 6 | 7 | console.log('language', dd.getLanguageSync()); 8 | console.log('language 2', dd.getSystemInfoSync().language); 9 | }, 10 | onShow(options) { 11 | // 从后台被 scheme 重新打开 12 | // options.query == {number:1} 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /packages/generator/src/common/errors.ts: -------------------------------------------------------------------------------- 1 | export const ERROR_REPO_NOT_FOUND = '找不到该模板,请检测输入的模板名和开发语言是否正常'; 2 | 3 | export const ERROR_PM_NOT_FOUND = '没有找到包管理器,请检查当前命令行的上下文中是否包含yarn、npm或pnpm'; 4 | 5 | export const ERROR_DEMO_COPY = '模板复制失败,请检查是否有足够的权限操作当前目录'; 6 | 7 | export const ERROR_APP_TYPE_NOT_FOUND = '在模板仓库中未能找到匹配当前应用类型的模板'; 8 | 9 | export const ERROR_REPO_NOT_VALID = '模板仓库非法'; 10 | 11 | export const ERROR_REPO_IS_EMPTY = '模板仓库是空的'; 12 | -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getGulpLocation.ts: -------------------------------------------------------------------------------- 1 | import { getGlobalGulp, getWorkspaceGulp, } from '../cli-shared-utils/lib/env'; 2 | 3 | /** 4 | * 检测tsc位置 5 | * 6 | * 优先级: workspace -> global 7 | */ 8 | 9 | export default (cwd: string): string => { 10 | const workspace = getWorkspaceGulp(cwd); 11 | if (workspace) return workspace; 12 | 13 | const global = getGlobalGulp(); 14 | if (global) return global; 15 | 16 | return ''; 17 | }; -------------------------------------------------------------------------------- /packages/opensdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "lib": [ 5 | "DOM" 6 | ], 7 | "outDir": "dist", 8 | "rootDir": "src", 9 | "esModuleInterop":true, 10 | "strict": false, 11 | "skipLibCheck": true, 12 | "allowJs": true 13 | }, 14 | "include": [ 15 | "src/**/*" 16 | ], 17 | "exclude": [ 18 | "node_modules", 19 | "dist" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface IGlobalConfig { 2 | corpId: string; 3 | pageAppType: string; 4 | from: string; 5 | locale: string; 6 | mode: string; 7 | } 8 | 9 | export interface ITitleBarModel { 10 | icon?: string; 11 | title: string; 12 | link?: string; 13 | titleColor: string; 14 | subLabel?: string; 15 | showArrow?: boolean; 16 | manage?: { 17 | link?: string; 18 | leftIcon?: string; 19 | } 20 | } -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-default/pages/index/index.axml: -------------------------------------------------------------------------------- 1 | 2 | 3 | httpRequest 4 | 5 | 6 | dtHttpRequest 7 | 8 | 9 | web-view 10 | 11 | -------------------------------------------------------------------------------- /packages/generator/generators/utils/request.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.default = { 4 | get(uri, opts) { 5 | // lazy require 6 | const util = require('util'); 7 | const request = util.promisify(require('request')); 8 | const reqOpts = Object.assign({ method: 'GET', timeout: 30000, resolveWithFullResponse: true, json: true, uri }, opts); 9 | return request(reqOpts); 10 | } 11 | }; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | tab_width = 1 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | insertSpaces = false 13 | detectIndentation = false 14 | prettier.eslintIntegration = true 15 | eslint.autoFixOnSave = true 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/utils.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import os from 'os'; 3 | import path from 'path'; 4 | 5 | import { friendlyPackageSize, parsePath, } from '../src/utils'; 6 | 7 | 8 | it('friendlyPackageSize', () => { 9 | expect(friendlyPackageSize(5 * 1024 * 1024 )).toBe('5.0MB'); 10 | expect(friendlyPackageSize(0.55 * 1024 * 1024 )).toBe('0.55MB'); 11 | }); 12 | 13 | it('parsePath', () => { 14 | expect(parsePath('~/home')).toBe(path.join(os.homedir(), 'home')); 15 | }); -------------------------------------------------------------------------------- /packages/cli/src/lib/util/performance.ts: -------------------------------------------------------------------------------- 1 | type TickKey = string | number; 2 | 3 | export class Performance { 4 | private tickMap: Record; 5 | 6 | constructor() { 7 | this.tickMap = {}; 8 | } 9 | 10 | tick(key: TickKey): void { 11 | this.tickMap[key] = Date.now(); 12 | } 13 | 14 | interval(start: TickKey, end: TickKey): number { 15 | return this.tickMap[end] - this.tickMap[start]; 16 | } 17 | } 18 | 19 | export default new Performance(); -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/miniprogram/pages/index/index.axml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Plugin Component: 4 | 5 | Plugin Page: 6 | To Plugin Page (using navigator) 7 | To Plugin Page (using dd.navigate) 8 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "changelog": { 6 | "repo": "open-dingtalk/dingtalk-design-cli", 7 | "labels": { 8 | "feat": ":rocket: New Feature", 9 | "bug": ":bug: Bug Fix", 10 | "doc": ":memo: Documentation", 11 | "internal": ":house: Internal", 12 | "breaking": ":boom: Breaking Change" 13 | }, 14 | "cacheDir": ".changelog" 15 | }, 16 | "npmClient": "yarn", 17 | "version": "1.0.8", 18 | "useWorkspaces": true 19 | } 20 | -------------------------------------------------------------------------------- /packages/generator/src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import { Options, } from 'request'; 2 | export default { 3 | get (uri: string, opts: Partial) { 4 | // lazy require 5 | const util = require('util'); 6 | const request = util.promisify(require('request')); 7 | const reqOpts = { 8 | method: 'GET', 9 | timeout: 30000, 10 | resolveWithFullResponse: true, 11 | json: true, 12 | uri, 13 | ...opts, 14 | }; 15 | 16 | return request(reqOpts); 17 | }, 18 | }; -------------------------------------------------------------------------------- /packages/cli/src/lib/util/checkNodeVersion.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import * as semver from 'semver'; 3 | 4 | export default function checkNodeVersion (wanted: string, id: string) { 5 | if (!semver.satisfies(process.version, wanted, { includePrerelease: true, })) { 6 | console.log(chalk.red( 7 | 'You are using Node ' + process.version + ', but this version of ' + id + 8 | ' requires Node ' + wanted + '.\nPlease upgrade your Node version.', 9 | )); 10 | process.exit(1); 11 | } 12 | } -------------------------------------------------------------------------------- /packages/cli/src/lib/util/clean.ts: -------------------------------------------------------------------------------- 1 | import { logger, } from '../cli-shared-utils/lib/logger'; 2 | import rimraf from 'rimraf'; 3 | 4 | export default async (path: string) => { 5 | try { 6 | await new Promise((res, rej) => { 7 | rimraf(path, (err) => { 8 | if (err) { 9 | rej(err); 10 | } else { 11 | res(null); 12 | } 13 | }); 14 | }); 15 | logger.debug(`${path} cleaned.`); 16 | } catch(e) { 17 | logger.debug(`${path} clean fail`, e); 18 | } 19 | }; -------------------------------------------------------------------------------- /packages/generator/src/utils/isValidRepoDirectory.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_DIRECTORY_SEPERATOR, } from '../common/config'; 2 | 3 | /** 4 | * is valid repo directory 5 | * @param directoryName 6 | * @param appType 7 | * @param seperator 8 | */ 9 | const isValidRepoDirectory = (directoryName: string, appType: string, seperator = DEFAULT_DIRECTORY_SEPERATOR): boolean => { 10 | return !directoryName.startsWith('.') && directoryName.startsWith(`${appType}${seperator}`); 11 | }; 12 | 13 | export default isValidRepoDirectory; -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /packages/generator/generators/common/constants.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.DEFAULT_APP_TYPE = exports.APP_TYPE_ENUM = void 0; 4 | var APP_TYPE_ENUM; 5 | (function (APP_TYPE_ENUM) { 6 | APP_TYPE_ENUM["PLUGIN"] = "plugin"; 7 | APP_TYPE_ENUM["MP"] = "mp"; 8 | APP_TYPE_ENUM["H5"] = "h5"; 9 | APP_TYPE_ENUM["DOCSCOOLAPP"] = "docscoolapp"; 10 | })(APP_TYPE_ENUM = exports.APP_TYPE_ENUM || (exports.APP_TYPE_ENUM = {})); 11 | exports.DEFAULT_APP_TYPE = APP_TYPE_ENUM.PLUGIN; -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | nodejs_version: "12" 3 | 4 | install: 5 | - ps: Install-Product node $env:nodejs_version 6 | - yarn --network-timeout 600000 7 | 8 | test_script: 9 | - git --version 10 | - node --version 11 | - yarn --version 12 | - yarn run build 13 | - set NODE_ENV=__DEBUG__ 14 | - yarn test 15 | 16 | cache: 17 | - node_modules -> appveyor.yml, **\package.json, yarn.lock 18 | - '%LOCALAPPDATA%\Yarn -> appveyor.yml, **\package.json, yarn.lock' 19 | 20 | build: off 21 | 22 | branches: 23 | except: 24 | - docs -------------------------------------------------------------------------------- /packages/generator/generators/utils/isValidRepoDirectory.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const config_1 = require("../common/config"); 4 | /** 5 | * is valid repo directory 6 | * @param directoryName 7 | * @param appType 8 | * @param seperator 9 | */ 10 | const isValidRepoDirectory = (directoryName, appType, seperator = config_1.DEFAULT_DIRECTORY_SEPERATOR) => { 11 | return !directoryName.startsWith('.') && directoryName.startsWith(`${appType}${seperator}`); 12 | }; 13 | exports.default = isValidRepoDirectory; -------------------------------------------------------------------------------- /packages/opensdk/__tests__/config/projectConfig.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { ECompileType, } from '../../src/types'; 3 | 4 | import { findProjectConfig, } from '../../src/config/projectConfig'; 5 | 6 | test('findProjectConfig', () => { 7 | const entry = path.join(__dirname, '../examples/plugin'); 8 | const projectConfig = findProjectConfig(entry); 9 | 10 | expect(projectConfig.miniprogramRoot).toBe('miniprogram'); 11 | expect(projectConfig.pluginRoot).toBe('plugin'); 12 | expect(projectConfig.compileType).toBe(ECompileType.Plugin); 13 | }); -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getCurrentPkgInfo.ts: -------------------------------------------------------------------------------- 1 | let pkgInfo: { 2 | version: string; 3 | name: string; 4 | engines: { 5 | node: string; 6 | } 7 | }; 8 | 9 | export default (): typeof pkgInfo => { 10 | if (pkgInfo) return pkgInfo; 11 | 12 | const pkgJson = require('../../../package.json'); 13 | const pkgVersion = pkgJson.version; 14 | const pkgName = pkgJson.name; 15 | const engines = pkgJson.engines; 16 | 17 | const info = { 18 | version: pkgVersion, 19 | name: pkgName, 20 | engines, 21 | }; 22 | pkgInfo = info; 23 | return pkgInfo; 24 | }; -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getCliArgsAndOptions.ts: -------------------------------------------------------------------------------- 1 | import * as cac from 'cac'; 2 | import { logger, } from '../cli-shared-utils/lib/logger'; 3 | 4 | let cliArgsAndOptions: { 5 | options: { 6 | [k: string]: any 7 | } 8 | args: readonly string[] 9 | }; 10 | 11 | export default function getCliArgsAndOptions(): typeof cliArgsAndOptions { 12 | if (cliArgsAndOptions) return cliArgsAndOptions; 13 | 14 | const cli = cac.default(); 15 | const parsed = cli.parse(); 16 | 17 | cliArgsAndOptions = { 18 | options: parsed.options, 19 | args: parsed.args, 20 | }; 21 | return cliArgsAndOptions; 22 | } -------------------------------------------------------------------------------- /packages/opensdk/src/config/projectConfig.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { ECompileType, } from '../types'; 4 | 5 | export interface IProjectConfig { 6 | miniprogramRoot?: string; 7 | pluginRoot?: string; 8 | compileType?: ECompileType; 9 | } 10 | 11 | export function findProjectConfig(entry: string) { 12 | const file = path.join(entry, 'mini.project.json'); 13 | 14 | if (!fs.existsSync(file)) { 15 | return { 16 | miniprogramRoot: './', 17 | compileType: ECompileType.MiniProgram, 18 | }; 19 | } 20 | 21 | return fs.readJSONSync(file) as IProjectConfig; 22 | } -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getMiniProjectJson.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import getJson from './getJson'; 3 | import { IMiniProjectJson, } from '../common/types'; 4 | import { isEmpty, } from 'lodash'; 5 | 6 | export default (cwd: string): { 7 | content: IMiniProjectJson, 8 | path: string 9 | } => { 10 | const miniProjectJsonPath = path.join(cwd, 'mini.project.json'); 11 | const content = getJson(miniProjectJsonPath, true); 12 | if (!isEmpty(content)) { 13 | return { 14 | content, 15 | path: miniProjectJsonPath, 16 | }; 17 | } 18 | return { 19 | content: {}, 20 | path: '', 21 | }; 22 | }; -------------------------------------------------------------------------------- /packages/cli/h5bundle/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/generator/generators/common/errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ERROR_REPO_IS_EMPTY = exports.ERROR_REPO_NOT_VALID = exports.ERROR_APP_TYPE_NOT_FOUND = exports.ERROR_DEMO_COPY = exports.ERROR_PM_NOT_FOUND = exports.ERROR_REPO_NOT_FOUND = void 0; 4 | exports.ERROR_REPO_NOT_FOUND = '找不到该模板,请检测输入的模板名和开发语言是否正常'; 5 | exports.ERROR_PM_NOT_FOUND = '没有找到包管理器,请检查当前命令行的上下文中是否包含yarn、npm或pnpm'; 6 | exports.ERROR_DEMO_COPY = '模板复制失败,请检查是否有足够的权限操作当前目录'; 7 | exports.ERROR_APP_TYPE_NOT_FOUND = '在模板仓库中未能找到匹配当前应用类型的模板'; 8 | exports.ERROR_REPO_NOT_VALID = '模板仓库非法'; 9 | exports.ERROR_REPO_IS_EMPTY = '模板仓库是空的'; -------------------------------------------------------------------------------- /packages/cli/src/actions/updateConfig.ts: -------------------------------------------------------------------------------- 1 | import { logger, } from '../lib/cli-shared-utils/lib/logger'; 2 | import config from '../lib/common/config'; 3 | import { setJsonItem, } from '../lib/util/setJson'; 4 | import * as path from 'path'; 5 | 6 | interface IOpts { 7 | cwd: string; 8 | configKey: string; 9 | configValue: any; 10 | } 11 | 12 | export default async (opts: IOpts) => { 13 | const { 14 | configKey, 15 | cwd, 16 | configValue, 17 | } = opts; 18 | 19 | if(setJsonItem(path.join(cwd, config.workspaceRcName), configKey, configValue)) { 20 | logger.success('更新配置文件成功', `配置文件中 ${configKey} 字段被设置为 ${configValue}`); 21 | } 22 | 23 | }; -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const cp = require('child_process'); 4 | 5 | const packagesPath = path.join(__dirname, '../packages'); 6 | const subDirNames = fs.readdirSync(packagesPath); 7 | console.log('packages start building...'); 8 | 9 | subDirNames.filter(v=>!v.startsWith('.')).forEach((subDirName)=>{ 10 | const subDirPath = path.join(packagesPath, subDirName); 11 | try { 12 | cp.execSync(`cd ${subDirPath} && npm run build`); 13 | console.log(`${subDirName} build success.`); 14 | } catch(e) { 15 | console.log(`${subDirName} build fail.`, e); 16 | } 17 | }); 18 | 19 | console.log('packages end building.'); -------------------------------------------------------------------------------- /packages/cli/README.md: -------------------------------------------------------------------------------- 1 | # DingTalk Design CLI [](https://circleci.com/gh/open-dingtalk/dingtalk-design-cli/tree/develop) [](https://ci.appveyor.com/project/lou1swu/dingtalk-design-cli/branch/develop) [](https://lerna.js.org/) 2 | 3 | ```bash 4 | npm install dingtalk-design-cli -g 5 | 6 | # OR 7 | 8 | yarn global add dingtalk-design-cli 9 | ``` 10 | 11 | [Docs](https://github.com/open-dingtalk/dingtalk-design-cli#dingtalk-design-cli----) 12 | -------------------------------------------------------------------------------- /packages/dingtalk-h5package-opensdk/__tests__/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { packTarGz, } from '../src/index'; 2 | import path from 'path'; 3 | import decompress from 'decompress'; 4 | import fs from 'fs-extra'; 5 | 6 | describe('dingtalk-opensdk-h5package', () => { 7 | afterEach(async () => { 8 | fs.remove(path.join(__dirname, 'output')); 9 | }); 10 | 11 | it('zip', async () => { 12 | const result = await packTarGz( 13 | path.join(__dirname, 'source'), 14 | path.join(__dirname, 'output/zip.tar.gz') 15 | ); 16 | 17 | const files = await decompress( 18 | result.output, 19 | path.join(__dirname, 'output') 20 | ); 21 | 22 | console.log(files); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | **What kind of change does this PR introduce?** (check at least one) 7 | 8 | - [ ] Bugfix 9 | - [ ] Feature 10 | - [ ] Code style update 11 | - [ ] Refactor 12 | - [ ] Docs 13 | - [ ] Underlying tools 14 | - [ ] Other, please describe: 15 | 16 | 21 | 22 | **Does this PR introduce a breaking change?** (check one) 23 | 24 | - [ ] Yes 25 | - [ ] No 26 | 27 | **Other information:** 28 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": false, 10 | "checkJs": false, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": false, 15 | "forceConsistentCasingInFileNames": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "noEmit": true, 22 | "jsx": "react-jsx", 23 | "noEmitOnError": true 24 | }, 25 | "include": [ 26 | "src" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-plugin/pages/index/index.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | onLoad(query) { 3 | // 页面加载 4 | console.info(`Page onLoad with query: ${JSON.stringify(query)}`); 5 | }, 6 | onReady() { 7 | // 页面加载完成 8 | }, 9 | onShow() { 10 | // 页面显示 11 | }, 12 | onHide() { 13 | // 页面隐藏 14 | }, 15 | onUnload() { 16 | // 页面被关闭 17 | }, 18 | onTitleClick() { 19 | // 标题被点击 20 | }, 21 | onPullDownRefresh() { 22 | // 页面被下拉 23 | }, 24 | onReachBottom() { 25 | // 页面被拉到底部 26 | }, 27 | onShareAppMessage() { 28 | // 返回自定义分享信息 29 | return { 30 | title: 'My App', 31 | desc: 'My App description', 32 | path: 'pages/index/index', 33 | }; 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-use-subpackages/pages/index/index.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | onLoad(query) { 3 | // 页面加载 4 | console.info(`Page onLoad with query: ${JSON.stringify(query)}`); 5 | }, 6 | onReady() { 7 | // 页面加载完成 8 | }, 9 | onShow() { 10 | // 页面显示 11 | }, 12 | onHide() { 13 | // 页面隐藏 14 | }, 15 | onUnload() { 16 | // 页面被关闭 17 | }, 18 | onTitleClick() { 19 | // 标题被点击 20 | }, 21 | onPullDownRefresh() { 22 | // 页面被下拉 23 | }, 24 | onReachBottom() { 25 | // 页面被拉到底部 26 | }, 27 | onShareAppMessage() { 28 | // 返回自定义分享信息 29 | return { 30 | title: 'My App', 31 | desc: 'My App description', 32 | path: 'pages/index/index', 33 | }; 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/miniapp-default/pages/index/index.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | doHttp() { 3 | dd.httpRequest({ 4 | url: 'http://httpbin.org', 5 | dataType: 'text', 6 | success: () => { 7 | dd.alert({ content: 'success' }); 8 | }, 9 | fail: (r) => { 10 | dd.alert({ title: 'fail', content: JSON.stringify(r) }); 11 | }, 12 | }); 13 | }, 14 | doDtHttp() { 15 | dd.httpRequest({ 16 | url: 'http://httpbin.org', 17 | impl: 'dt', 18 | dataType: 'text', 19 | success: () => { 20 | dd.alert({ content: 'success' }); 21 | }, 22 | fail: (r) => { 23 | dd.alert({ title: 'fail', content: JSON.stringify(r) }); 24 | }, 25 | }); 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /packages/cli/src/commands/init/index.ts: -------------------------------------------------------------------------------- 1 | import CommandWrapper from '../../scheduler/command/commandWrapper'; 2 | import { ECommandName, } from '../../lib/common/types'; 3 | import commandsConfig from '../commandsConfig'; 4 | import init from '../../actions/init'; 5 | 6 | interface ICommandOptions { 7 | appType?: string; 8 | template?: string; 9 | language?: string; 10 | 'skip-install'?: boolean; 11 | outDir?: string; 12 | } 13 | 14 | export default CommandWrapper({ 15 | name: ECommandName.init, 16 | registerCommand(ctx) { 17 | return { 18 | ...commandsConfig.init, 19 | action: async (options) => { 20 | ctx.logger.debug('cli options', options); 21 | await init(options); 22 | }, 23 | }; 24 | }, 25 | }); -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | const minimist = require('minimist'); 2 | const path = require('path'); 3 | const rawArgs = process.argv.slice(2); 4 | const args = minimist(rawArgs); 5 | const configPath = path.join(__dirname, '../jest.config.js'); 6 | 7 | let regex; 8 | if (args.p) { 9 | const packages = (args.p || args.package).split(',').join('|'); 10 | regex = `.*(${packages})/.*\\.spec\\.ts$`; 11 | const i = rawArgs.indexOf('-p'); 12 | rawArgs.splice(i, 2); 13 | } 14 | 15 | const jestArgs = [ 16 | '--env', 'node', 17 | '--runInBand', 18 | '--config', configPath, 19 | ...rawArgs, 20 | ...(regex ? [regex] : []), 21 | ]; 22 | 23 | console.log(`running jest with args: ${jestArgs.join(' ')}`); 24 | 25 | process.env.NO_REPORT = true; // 不进行监控上报 26 | require('jest').run(jestArgs); -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getMt2Config.ts: -------------------------------------------------------------------------------- 1 | import urllib from 'urllib'; 2 | import { logger, } from '../cli-shared-utils/lib/logger'; 3 | import getMonitor from '../cli-shared-utils/lib/monitor/framework-monitor'; 4 | import config from '../common/config'; 5 | 6 | const monitor = getMonitor(config.yuyanId); 7 | 8 | interface IMt2Config { 9 | frameworkConfig: { 10 | miniAppHtml: string; 11 | h5Html: string; 12 | extend: string; 13 | } 14 | } 15 | 16 | export default async (): Promise => { 17 | try { 18 | const res = await urllib.request(config.mt2Config, { 19 | method: 'GET', 20 | dataType: 'json', 21 | }); 22 | return res.data; 23 | } catch(e) { 24 | logger.debug('请求mt2Config失败', e); 25 | monitor.logJSError(e); 26 | return null; 27 | } 28 | }; -------------------------------------------------------------------------------- /packages/opensdk/__tests__/openapi.test.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPI, } from '../src/openapi'; 2 | import * as sdkConfig from './sdk-test-config'; 3 | 4 | const miniAppId = ''; 5 | const cfg = sdkConfig.daily; 6 | 7 | describe('openapi', () => { 8 | let openApi: OpenAPI; 9 | 10 | beforeAll(() => { 11 | openApi = new OpenAPI(cfg); 12 | }); 13 | 14 | beforeEach(async () => { 15 | await openApi.init(); 16 | }); 17 | 18 | test('getAccessToken', async () => { 19 | const rt = await openApi.getUploadAccessKey({ miniapp_id: cfg.miniAppId, }); 20 | 21 | expect(rt.accessid).not.toBeNull(); 22 | expect(rt.access_key_secret).not.toBeNull(); 23 | expect(rt.expiration).not.toBeNull(); 24 | expect(rt.name).not.toBeNull(); 25 | expect(rt.security_token).not.toBeNull(); 26 | }); 27 | 28 | 29 | }); -------------------------------------------------------------------------------- /packages/opensdk/src/utils.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | export function friendlyPackageSize(size: number) { 4 | const de = Math.pow(1024, 2); 5 | const t = size / de; 6 | 7 | if (t < 1) { 8 | return t.toFixed(2) + 'MB'; 9 | } else { 10 | return t.toFixed(1) + 'MB'; 11 | } 12 | } 13 | 14 | export function checkSizeIfThrow(size: number, limit: number) { 15 | if (size > limit) { 16 | throw new Error(`当前包体积为${friendlyPackageSize(size)},超出${friendlyPackageSize(limit)}体积限制,请优化包体积`); 17 | } 18 | } 19 | 20 | export function parsePath(src: string) { 21 | if (src[0] == '~') { 22 | return path.join(process.env.HOME as string, src.slice(1)); 23 | } 24 | 25 | if (path.isAbsolute(src)) { 26 | return src; 27 | } else { 28 | return path.resolve(src); 29 | } 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getJson.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | /** 4 | * Load a json object 5 | * @param filePath 6 | */ 7 | export default function loadJSON>( 8 | filePath: string, 9 | loose = true, 10 | fallbackValue = {}, 11 | ): T { 12 | const isExist = fs.existsSync(filePath); 13 | if (isExist) { 14 | try { 15 | const content = fs.readFileSync(filePath, { encoding: 'utf-8', }); 16 | return JSON.parse(content) as T; 17 | } catch (e) { 18 | if (loose) { 19 | console.error(e); 20 | return fallbackValue as T; 21 | } 22 | throw new Error(`Failed to load: ${filePath}`); 23 | } 24 | } else { 25 | if (loose) { 26 | return fallbackValue as T; 27 | } 28 | throw new Error(`Non-existed path: ${filePath}`); 29 | } 30 | } -------------------------------------------------------------------------------- /packages/generator/src/utils/getJson.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | /** 4 | * Load a json object 5 | * @param filePath 6 | */ 7 | export default function loadJSON>( 8 | filePath: string, 9 | loose = true, 10 | fallbackValue = {}, 11 | ): T { 12 | const isExist = fs.existsSync(filePath); 13 | if (isExist) { 14 | try { 15 | const content = fs.readFileSync(filePath, { encoding: 'utf-8', }); 16 | return JSON.parse(content) as T; 17 | } catch (e) { 18 | if (loose) { 19 | console.error(e); 20 | return fallbackValue as T; 21 | } 22 | throw new Error(`Failed to load: ${filePath}`); 23 | } 24 | } else { 25 | if (loose) { 26 | return fallbackValue as T; 27 | } 28 | throw new Error(`Non-existed path: ${filePath}`); 29 | } 30 | } -------------------------------------------------------------------------------- /packages/cli/__tests__/mock/fakeCommand/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fakeCommand 3 | * 用来测试 4 | */ 5 | import CommandWrapper from '../../../src/scheduler/command/commandWrapper'; 6 | 7 | export interface ICommandOptions { 8 | test: boolean; 9 | } 10 | 11 | export default CommandWrapper({ 12 | // @ts-ignore 13 | name: 'fakeCommand', 14 | // @ts-ignore 15 | registerCommand(ctx) { 16 | return { 17 | command: { 18 | name: 'fakeCommand', 19 | description: '测试', 20 | }, 21 | options: { 22 | test: { 23 | description: '选项', 24 | default: '', 25 | type: 'string', 26 | }, 27 | }, 28 | action: async (options) => { 29 | return { 30 | ctx, 31 | options, 32 | }; 33 | }, 34 | }; 35 | }, 36 | }); -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getGlobalIntallCommand.ts: -------------------------------------------------------------------------------- 1 | import { execa, hasYarn, hasPnpm3OrLater, } from '../cli-shared-utils'; 2 | 3 | export default function getGlobalInstallCommand () { 4 | if (hasYarn()) { 5 | const { stdout: yarnGlobalDir, } = execa.sync('yarn', ['global', 'dir']); 6 | if (__dirname.includes(yarnGlobalDir)) { 7 | return 'yarn global add'; 8 | } 9 | } 10 | 11 | if (hasPnpm3OrLater()) { 12 | const { stdout: pnpmGlobalPrefix, } = execa.sync('pnpm', ['config', 'get', 'prefix']); 13 | if (__dirname.includes(pnpmGlobalPrefix) && __dirname.includes('pnpm-global')) { 14 | return 'pnpm i -g'; 15 | } 16 | } 17 | 18 | const { stdout: npmGlobalPrefix, } = execa.sync('npm', ['config', 'get', 'prefix']); 19 | if (__dirname.includes(npmGlobalPrefix)) { 20 | return 'npm i -g'; 21 | } 22 | } -------------------------------------------------------------------------------- /packages/cli/src/commands/login/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 目前仅服务于钉钉inside小程序场景 3 | * 4 | * 参考:https://yuque.antfin.com/docs/share/f5fdcc3d-e6ec-4f00-b598-0bda87a55aa0?# 5 | */ 6 | 7 | import CommandWrapper from '../../scheduler/command/commandWrapper'; 8 | import { ECommandName, } from '../../lib/common/types'; 9 | import commandsConfig from '../commandsConfig'; 10 | import { minidev, } from 'minidev'; 11 | 12 | interface ICommandOptions { 13 | } 14 | 15 | export default CommandWrapper({ 16 | name: ECommandName.login, 17 | registerCommand(ctx) { 18 | return { 19 | ...commandsConfig.login, 20 | action: async (options) => { 21 | ctx.logger.debug('cli options', options); 22 | 23 | await minidev.login({ 24 | clientType: 'alipay', 25 | }); 26 | }, 27 | }; 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /packages/cli/src/lib/util/setJson.ts: -------------------------------------------------------------------------------- 1 | import getJson from './getJson'; 2 | import * as fs from 'fs'; 3 | import { logger, } from '../cli-shared-utils/lib/logger'; 4 | 5 | export function setJsonItem (path: string, key: string, value: any): boolean { 6 | try { 7 | const content = getJson(path, false); 8 | content[key] = value; 9 | fs.writeFileSync(path, JSON.stringify(content, null, '\t'), { 10 | encoding: 'utf-8', 11 | }); 12 | return true; 13 | } catch(e) { 14 | logger.debug('setJsonItem fail', e); 15 | return false; 16 | } 17 | } 18 | 19 | export default (path: string, value: any) => { 20 | try { 21 | fs.writeFileSync(path, JSON.stringify(value, null, '\t'), { 22 | encoding: 'utf-8', 23 | }); 24 | return true; 25 | } catch(e) { 26 | logger.debug('setJson fail', e); 27 | return false; 28 | } 29 | }; -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/App.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | background-color: #F5F7FA; 3 | } 4 | 5 | .App { 6 | width: 7.26rem; 7 | text-align: center; 8 | margin: 0 auto; 9 | } 10 | 11 | .App-logo { 12 | height: 40vmin; 13 | pointer-events: none; 14 | } 15 | 16 | @media (prefers-reduced-motion: no-preference) { 17 | .App-logo { 18 | animation: App-logo-spin infinite 20s linear; 19 | } 20 | } 21 | 22 | .App-header { 23 | background-color: #282c34; 24 | min-height: 100vh; 25 | display: flex; 26 | flex-direction: column; 27 | align-items: center; 28 | justify-content: center; 29 | font-size: calc(0.2rem + 2vmin); 30 | color: white; 31 | } 32 | 33 | .App-link { 34 | color: #61dafb; 35 | } 36 | 37 | @keyframes App-logo-spin { 38 | from { 39 | transform: rotate(0deg); 40 | } 41 | to { 42 | transform: rotate(360deg); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/generator/src/utils/clean.ts: -------------------------------------------------------------------------------- 1 | import { execSync, } from 'child_process'; 2 | import * as fs from 'fs'; 3 | 4 | export default async (filePath: string) => { 5 | if (!filePath) return; 6 | 7 | if (!fs.existsSync(filePath)) { 8 | console.warn(`ENOENT ${filePath} is not exist`); 9 | return; 10 | } 11 | 12 | const statInfo = fs.statSync(filePath); 13 | const isDirectory = statInfo.isDirectory(); 14 | if (isDirectory) { 15 | console.warn(`Clean a directory: ${filePath}`); 16 | } 17 | 18 | try { 19 | execSync(`ls ${filePath}`); 20 | } catch (e) { 21 | console.warn(`ll exec failed. filePath: ${filePath}`); 22 | } 23 | 24 | try { 25 | execSync(`rm -r ${filePath}`); 26 | console.log(`Clean success. filePath: ${filePath}`); 27 | } catch (e) { 28 | console.warn(`Clean failed. filePath: ${filePath}. ${e}`); 29 | } 30 | }; -------------------------------------------------------------------------------- /packages/opensdk/src/utils/download.ts: -------------------------------------------------------------------------------- 1 | import tar from 'tar'; 2 | import ProgressBar from 'progress'; 3 | import { promisify, } from 'util'; 4 | import stream from 'stream'; 5 | import got from 'got'; 6 | 7 | async function download(url: string, target: string) { 8 | let bar: ProgressBar; 9 | let transferred = 0; 10 | await promisify(stream.pipeline)( 11 | got.stream(url).on('downloadProgress', (progress) => { 12 | if (!bar) { 13 | bar = new ProgressBar('download compiler [:bar] :percent :etas', { 14 | complete: '=', 15 | incomplete: ' ', 16 | width: 20, 17 | total: progress.total, 18 | }); 19 | } 20 | bar.tick(progress.transferred - transferred); 21 | transferred = progress.transferred; 22 | }), 23 | tar.x({ cwd: target, }) 24 | ); 25 | } 26 | 27 | export default download; 28 | -------------------------------------------------------------------------------- /packages/cli/src/lib/util/suggestCommands.ts: -------------------------------------------------------------------------------- 1 | import leven from 'leven'; 2 | import chalk from 'chalk'; 3 | import { logger, } from '../cli-shared-utils/lib/logger'; 4 | import { ECommandName, } from '../common/types'; 5 | 6 | /** 7 | * 当开发者输入一个错误命令时,比对目前已经注册的命令,提取最接近输入的命令进行校正提示 8 | */ 9 | export default (unknownCommand: string, commandNameList: (ECommandName | '')[]): void => { 10 | let suggestion: string | undefined; 11 | commandNameList.forEach(name => { 12 | const isBestMatch = leven(name, unknownCommand) < leven(suggestion || '', unknownCommand); 13 | if (leven(name, unknownCommand) < 3 && isBestMatch) { 14 | suggestion = name; 15 | } 16 | }); 17 | 18 | if (suggestion) { 19 | console.log(' ' + chalk.red(`未能找到命令${unknownCommand},相似的命令有 ${chalk.yellow(suggestion)}`)); 20 | } else { 21 | logger.debug(`未找到与 ${unknownCommand} 类似的命令`, commandNameList); 22 | } 23 | }; -------------------------------------------------------------------------------- /packages/opensdk/__tests__/openapiV2.spec.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIV2, } from '../src/openapiV2'; 2 | import * as sdkConfig from './sdk-test-config'; 3 | 4 | const miniAppId = ''; 5 | const cfg = sdkConfig.daily; 6 | 7 | describe('openapi', () => { 8 | let openApi: OpenAPIV2; 9 | 10 | beforeAll(() => { 11 | openApi = new OpenAPIV2({ 12 | accessToken: cfg.accessToken, 13 | host: cfg.host, 14 | }); 15 | }); 16 | 17 | test('getAccessToken', async () => { 18 | const rt = await openApi.getH5packageUploadtoken({ 19 | agentId: +cfg.agentId, 20 | }).catch(e => { 21 | console.error(e); 22 | }); 23 | console.log(rt); 24 | // expect(rt.accessid).not.toBeNull(); 25 | // expect(rt.access_key_secret).not.toBeNull(); 26 | // expect(rt.expiration).not.toBeNull(); 27 | // expect(rt.name).not.toBeNull(); 28 | // expect(rt.security_token).not.toBeNull(); 29 | }); 30 | 31 | 32 | }); -------------------------------------------------------------------------------- /packages/opensdk/__tests__/examples/plugin/miniprogram/pages/index/index.js: -------------------------------------------------------------------------------- 1 | var plugin = requirePlugin("myPlugin"); 2 | 3 | Page({ 4 | onLoad(query) { 5 | plugin.getData(); 6 | // 页面加载 7 | console.info(`Page onLoad with query: ${JSON.stringify(query)}`); 8 | }, 9 | onReady() { 10 | // 页面加载完成 11 | }, 12 | onShow() { 13 | // 页面显示 14 | }, 15 | onHide() { 16 | // 页面隐藏 17 | }, 18 | onUnload() { 19 | // 页面被关闭 20 | }, 21 | onTitleClick() { 22 | // 标题被点击 23 | }, 24 | onPullDownRefresh() { 25 | // 页面被下拉 26 | }, 27 | onReachBottom() { 28 | // 页面被拉到底部 29 | }, 30 | onShareAppMessage() { 31 | // 返回自定义分享信息 32 | return { 33 | title: 'My App', 34 | desc: 'My App description', 35 | path: 'pages/index/index', 36 | }; 37 | }, 38 | navigateToPlugin() { 39 | dd.navigateTo({ 40 | url: 'plugin://myPlugin/hello-page', 41 | }); 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/components/component-item/index.css: -------------------------------------------------------------------------------- 1 | .ddworkspace-view-container { 2 | position: relative; 3 | background-color: transparent; 4 | background-repeat: no-repeat; 5 | background-size: 100% auto; 6 | background-position: 0 0; 7 | overflow: hidden; 8 | } 9 | 10 | .ddworkspace-view-container.card-style { 11 | /*关键的卡片样式,模拟官方的情况-仅模拟,不代表完全的真实情况*/ 12 | margin-bottom: 8px; 13 | margin-left: 8px; 14 | margin-right: 8px; 15 | border-top-left-radius: 8px; 16 | border-top-right-radius: 8px; 17 | border-bottom-left-radius: 8px; 18 | border-bottom-right-radius: 8px; 19 | background-color: #fff; 20 | } 21 | 22 | .ddworkspace-view-container.card-style.dark { 23 | background-color: #1E1E1E; 24 | } 25 | 26 | .ddworkspace-view-container.transform-layer { 27 | /* 28 | * 子元素里有用到transform,渲染时,如果父元素不transform,会导致子元素的图片超出父元素的圆角范围 29 | * 与渲染图层有关,实际上父元素的z-index改变也能解 30 | */ 31 | transform: rotate(0); 32 | } -------------------------------------------------------------------------------- /scripts/genChangelog.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const execa = require('execa'); 4 | 5 | async function genNewRelease () { 6 | const nextVersion = require('../lerna.json').version; 7 | const { stdout, } = await execa(require.resolve('lerna-changelog/bin/cli'), [ 8 | '--next-version', 9 | nextVersion, 10 | ]); 11 | return stdout; 12 | } 13 | 14 | const gen = (module.exports = async () => { 15 | let newRelease = await genNewRelease(); 16 | const changelogPath = path.resolve(__dirname, '../CHANGELOG.md'); 17 | 18 | if (newRelease) { 19 | newRelease = newRelease + '\n\n\n'; 20 | } 21 | 22 | const newChangelog = 23 | newRelease + fs.readFileSync(changelogPath, { encoding: 'utf8', }); 24 | fs.writeFileSync(changelogPath, newChangelog); 25 | 26 | delete process.env.PREFIX; 27 | }); 28 | 29 | if (require.main === module) { 30 | gen().catch(err => { 31 | console.error(err); 32 | process.exit(1); 33 | }); 34 | } -------------------------------------------------------------------------------- /packages/generator/src/utils/createManifest.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { v4 as uuid } from 'uuid'; 4 | import chalk from 'chalk'; 5 | 6 | export default async function createManifest(output: string, logger?: Console['log']) { 7 | return new Promise((resolve) => { 8 | const jsonPath = path.resolve(output, 'devManifest.json'); 9 | fs.readFile(jsonPath, 'utf8', (err, jsonString) => { 10 | if (err) { 11 | logger(chalk.red.bold(`Cannot find devManifest.json in ${jsonPath}`)); 12 | process.exit(1); 13 | } 14 | const jsonData = JSON.parse(jsonString) as any; 15 | jsonData.dev.id = 'dingdocs-' + uuid().substring(0, 18); 16 | fs.writeFile(jsonPath, JSON.stringify(jsonData, null, 2), err => { 17 | if (err) { 18 | logger(chalk.red.bold(`Cannot find devManifest.json in ${jsonPath}`)); 19 | process.exit(1); 20 | } 21 | resolve(true); 22 | }) 23 | }); 24 | }); 25 | } -------------------------------------------------------------------------------- /packages/generator/src/utils/getGlobalInstallCommand.ts: -------------------------------------------------------------------------------- 1 | import * as execa from 'execa'; 2 | import { hasYarn, hasPnpm3OrLater, } from './cli-shared-utils'; 3 | 4 | /** 5 | * when use npm link to debug, would get nothing because 6 | * `__dirname` is wrong 7 | */ 8 | export default function getGlobalInstallCommand (): string | undefined { 9 | if (hasYarn()) { 10 | const { stdout: yarnGlobalDir, } = execa.sync('yarn', ['global', 'dir']); 11 | if (__dirname.includes(yarnGlobalDir)) { 12 | return 'yarn global add'; 13 | } 14 | } 15 | 16 | if (hasPnpm3OrLater()) { 17 | const { stdout: pnpmGlobalPrefix, } = execa.sync('pnpm', ['config', 'get', 'prefix']); 18 | if (__dirname.includes(pnpmGlobalPrefix) && __dirname.includes('pnpm-global')) { 19 | return 'pnpm i -g'; 20 | } 21 | } 22 | 23 | const { stdout: npmGlobalPrefix, } = execa.sync('npm', ['config', 'get', 'prefix']); 24 | if (__dirname.includes(npmGlobalPrefix)) { 25 | return 'npm i -g'; 26 | } 27 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/proposal.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Proposal 3 | about: Suggest an idea for improving DingTalk Tools 4 | labels: 'issue: proposal, needs triage' 5 | --- 6 | 7 | ### Is your proposal related to a problem? 8 | 9 | 13 | 14 | (Write your answer here.) 15 | 16 | ### Describe the solution you'd like 17 | 18 | 21 | 22 | (Describe your proposed solution here.) 23 | 24 | ### Describe alternatives you've considered 25 | 26 | 29 | 30 | (Write your answer here.) 31 | 32 | ### Additional context 33 | 34 | 38 | 39 | (Write your answer here.) 40 | -------------------------------------------------------------------------------- /packages/cli/tpl/webSimulator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | Getting Started 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/generator/src/utils/timeInspector.ts: -------------------------------------------------------------------------------- 1 | import { time, } from 'console'; 2 | 3 | /** 4 | * inspect fn which run out of time 5 | * @param fn inspected callback, must be a promise 6 | * @param interval time interval 7 | */ 8 | export default async (fn: () => Promise, opts?: Partial<{ 9 | logger: Console['log']; 10 | interval: number; 11 | fetchingTips: string; 12 | timeout: number; 13 | timeoutTips: string; 14 | }>): Promise => { 15 | const { 16 | logger = console.log, 17 | interval = 3 * 1000, 18 | fetchingTips = 'fetching...', 19 | timeout = 30 * 1000, 20 | timeoutTips = 'task timeout', 21 | } = opts || {}; 22 | 23 | const timer = setInterval(()=>{ 24 | logger(fetchingTips); 25 | }, interval); 26 | 27 | const killerTimer = setTimeout(()=>{ 28 | clearInterval(timer); 29 | clearTimeout(killerTimer); 30 | throw timeoutTips; 31 | }, timeout); 32 | 33 | try { 34 | await fn(); 35 | } finally { 36 | clearInterval(timer); 37 | clearTimeout(killerTimer); 38 | } 39 | }; -------------------------------------------------------------------------------- /packages/cli/src/lib/cli-shared-utils/lib/network/index.ts: -------------------------------------------------------------------------------- 1 | import detect from 'detect-port-alt'; 2 | import chalk from 'chalk'; 3 | 4 | const isRoot = process.getuid && process.getuid() === 0; 5 | 6 | export function choosePort(host, defaultPort): Promise { 7 | return detect(defaultPort, host).then( 8 | port => 9 | new Promise(resolve => { 10 | if (port === defaultPort) { 11 | return resolve(port); 12 | } 13 | const message = 14 | process.platform !== 'win32' && defaultPort < 1024 && !isRoot 15 | ? 'Admin permissions are required to run a server on a port below 1024.' 16 | : `${defaultPort} 端口被占用。已经自动切换为 ${port}`; 17 | 18 | console.log(chalk.yellow(message)); 19 | resolve(port); 20 | }), 21 | err => { 22 | throw new Error( 23 | chalk.red(`Could not find an open port at ${chalk.bold(host)}.`) + 24 | '\n' + 25 | ('Network error message: ' + err.message || err) + 26 | '\n' 27 | ); 28 | } 29 | ); 30 | } -------------------------------------------------------------------------------- /packages/cli/tpl/miniAppWebSimulator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | Getting Started 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 louiswu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/dingtalk-h5package-opensdk/README.md: -------------------------------------------------------------------------------- 1 | # `dingtalk-h5package-opensdk` 2 | 3 | > H5离线包OpenSDK 4 | 5 | 6 | ## 安装 7 | 8 | ``` 9 | 10 | npm install dingtalk-h5package-opensdk --save-dev 11 | 12 | ``` 13 | 14 | ## 用法 15 | 16 | 17 | ### 命令行用法 18 | 19 | ```javascript 20 | 21 | // 企业自建应用 22 | npx h5package inner --id <企业自建应用agentId> --accesstoken <企业apiToken> --dir ./dist 23 | 24 | // 第三方企业应用 25 | npx h5package provider --id <第三方企业应用appId> --accesstoken <企业apiToken> --dir ./dist 26 | 27 | 28 | ``` 29 | 30 | 31 | ### 脚本用法 32 | 33 | ```javascript 34 | import { sdk } from 'dingtalk-h5package-opensdk'; 35 | 36 | // 初始化参数 37 | sdk.setConfig({ 38 | accessToken: '从开放平台获取的apiToken', 39 | }); 40 | 41 | // 创建H5离线包 42 | const { version } = await sdk.createPackage({ 43 | // 应用的ID,二选一即可。 44 | appId: '第三方企业应用appId', 45 | agentId: '企业内部agentId', 46 | input: 'H5离线包资源所在的目录地址', // 在此目录下的所有文件会作为H5离线包的静态资源,压缩上传并创建H5离线包 47 | }); 48 | 49 | // H5离线包发布上线 50 | await sdk.publishPackage({ 51 | // 设置应用的ID,二选一即可。 52 | appId: '第三方企业应用appId', 53 | agentId: '企业内部agentId', 54 | version: version, // 需要发布的离线包的版本号,如0.0.1。 55 | }); 56 | 57 | ``` 58 | -------------------------------------------------------------------------------- /packages/cli/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 louiswu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/generator/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/components/component-item/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {PropsWithChildren} from 'react'; 2 | import cx from 'classnames'; 3 | import { IGlobalConfig } from '../../types'; 4 | import ViewContainer from '../view-container'; 5 | import './index.css'; 6 | import ComponentTitleBar from '../component-title-bar' 7 | 8 | interface IProps { 9 | mode: string; 10 | componentProps: any; 11 | config: IGlobalConfig 12 | } 13 | 14 | export default function ComponentItem(props: PropsWithChildren) { 15 | const { 16 | mode, 17 | componentProps = {}, 18 | config, 19 | children, 20 | } = props 21 | 22 | 23 | return ( 24 | 25 | 30 | {componentProps.__useStandardHead__ && ( 31 | 36 | )} 37 | {children} 38 | 39 | 40 | ) 41 | } -------------------------------------------------------------------------------- /packages/cli/src/lib/util/globalRc.ts: -------------------------------------------------------------------------------- 1 | import config from '../common/config'; 2 | import * as fs from 'fs'; 3 | import { logger, } from '../cli-shared-utils/lib/logger'; 4 | import { IGlobalRc, } from '../common/types'; 5 | import getJson from './getJson'; 6 | import { isEmpty, } from 'lodash'; 7 | 8 | export const getGlobalRc = (): IGlobalRc => { 9 | return getJson(config.globalRc, true, {}) as IGlobalRc; 10 | }; 11 | 12 | export const setGlobalRcItem = (k: keyof IGlobalRc, v: any): void => { 13 | let rc = getGlobalRc(); 14 | if (isEmpty(rc)) { 15 | rc = { 16 | h5ProExecPath: '', 17 | }; 18 | } 19 | rc[k] = v; 20 | 21 | const rcStr = JSON.stringify(rc); 22 | try { 23 | fs.writeFileSync(config.globalRc, rcStr, { 24 | encoding: 'utf-8', 25 | }); 26 | } catch(e) { 27 | logger.error('set global rc fail', e.message); 28 | } 29 | }; 30 | 31 | export const setGlobalRc = (opts: IGlobalRc): void => { 32 | const rcStr = JSON.stringify({ 33 | ...getGlobalRc(), 34 | ...opts, 35 | }); 36 | try { 37 | fs.writeFileSync(config.globalRc, rcStr, { 38 | encoding: 'utf-8', 39 | }); 40 | } catch(e) { 41 | logger.error('set global rc fail', e.message); 42 | } 43 | }; -------------------------------------------------------------------------------- /packages/cli/h5bundle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "h5bundle", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "classnames": "^2.3.1", 14 | "query-string": "^7.0.0", 15 | "react": "^17.0.2", 16 | "react-dom": "^17.0.2", 17 | "react-scripts": "4.0.3", 18 | "typescript": "^4.1.2", 19 | "web-vitals": "^1.0.1" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/generator/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import stripAnsi from 'strip-ansi'; 3 | 4 | const format = (label: string, msg: string) => { 5 | return msg.split('\n').map((line, i) => { 6 | return i === 0 7 | ? `${label} ${line}` 8 | : line.padStart(stripAnsi(label).length); 9 | }).join('\n'); 10 | }; 11 | 12 | export const debug = (msg: string) => { 13 | const txt = format(chalk.bgBlue.black(' DEBUG '), msg); 14 | return process.env.DEBUG && console.log(txt); 15 | }; 16 | 17 | export const info = (msg: string, pure?: boolean): string | void => { 18 | const txt = format(chalk.bgBlue.black(' INFO '), msg); 19 | return pure ? txt : console.log(pure); 20 | }; 21 | 22 | export const done = (msg: string, pure?: boolean): string | void => { 23 | const txt = format(chalk.bgGreen.black(' DONE '), msg); 24 | return pure ? txt : console.log(pure); 25 | }; 26 | 27 | export const warn = (msg: string, pure?: boolean): string | void => { 28 | const txt = format(chalk.bgYellow.black(' WARN '), msg); 29 | return pure ? txt : console.warn(pure); 30 | }; 31 | 32 | export const error = (msg: string, pure?: boolean): string | void => { 33 | const txt = format(chalk.bgRed(' ERROR '), msg); 34 | return pure ? txt : console.error(pure); 35 | }; -------------------------------------------------------------------------------- /packages/cli/src/commands/lint/index.ts: -------------------------------------------------------------------------------- 1 | import CommandWrapper from '../../scheduler/command/commandWrapper'; 2 | import { ECommandName, } from '../../lib/common/types'; 3 | import lint from '../../actions/lint'; 4 | import commmandsConfig from '../commandsConfig'; 5 | import config from '../../lib/common/config'; 6 | 7 | interface ICommandOptions { 8 | } 9 | 10 | /** 11 | * 不直接支持存量项目,但会引导支持,比如如何创建ding.config.json 12 | * 13 | * 校验逻辑: 14 | * 工作台组件 15 | * - 有miniAppId和token,拉取rc和权限点来校验 16 | * - 无miniAppId或token,用通用rc来校验,参考dingtalk-worktab-plugin-script中的defaultRc 17 | * 18 | * 小程序/h5 19 | * - 存量项目,跑eslint 20 | * - 增量项目,执行 `npm run lint` / `npm run test` 21 | */ 22 | export default CommandWrapper({ 23 | name: ECommandName.lint, 24 | registerCommand(ctx) { 25 | return { 26 | ...commmandsConfig.lint, 27 | action: async (options) => { 28 | const { 29 | cwd, 30 | dtdConfig, 31 | } = ctx; 32 | 33 | if (!dtdConfig.type) { 34 | ctx.logger.error(`当前目录 ${cwd} 下没有找到 ${config.workspaceRcName} 文件,请先使用init初始化项目;或者也可以选择手动新增 ${config.workspaceRcName} 配置文件,参考文档 https://developers.dingtalk.com/document/resourcedownload/configuration-description?pnamespace=app`); 35 | return; 36 | } 37 | await lint(ctx); 38 | }, 39 | }; 40 | }, 41 | }); -------------------------------------------------------------------------------- /packages/cli/src/lib/cli-shared-utils/lib/logger/base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | import chalk from 'chalk'; 5 | import { ILoggerElement, ELoggerLevel, ILoggerOptions, } from './types'; 6 | 7 | /** 8 | * Expose LoggerBase 9 | */ 10 | export class LoggerBase { 11 | prefix: string; 12 | 13 | options: ILoggerOptions; 14 | 15 | getLogArgsWithPrefix: (args: ILoggerElement[]) => ILoggerElement[]; 16 | 17 | constructor( 18 | prefix?: string, 19 | options: ILoggerOptions = {} as ILoggerOptions, 20 | ) { 21 | this.options = { 22 | logLevel: options.debug || process.argv.includes('--verbose') 23 | ? ELoggerLevel.debug 24 | : ELoggerLevel.info, 25 | ...options, 26 | }; 27 | this.prefix = prefix || ''; 28 | const prefixLog = chalk.dim(`[${this.prefix}]`); 29 | this.getLogArgsWithPrefix = this.prefix 30 | ? ((args: ILoggerElement[]): ILoggerElement[] => [prefixLog, ...args]) 31 | : ((args: ILoggerElement[]): ILoggerElement[] => args); 32 | } 33 | 34 | public setOptions(options: ILoggerOptions) { 35 | Object.assign(this.options, options); 36 | } 37 | 38 | public useLogLevel(logLevel: ELoggerLevel, fn: () => void) { 39 | if (this.options.logLevel && this.options.logLevel >= logLevel) { 40 | fn(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/generator/src/utils/getVersions.ts: -------------------------------------------------------------------------------- 1 | import Store from './store'; 2 | import * as semver from 'semver'; 3 | import getRemoteVersion from './getRemoteVersion'; 4 | 5 | const localStore = new Store(); 6 | 7 | type VersionOutput = { 8 | current: string; 9 | latest: string; 10 | error: string | null; 11 | } 12 | 13 | async function getVersions (): Promise { 14 | const localConfig = localStore.getAll(); 15 | const pkg = require('../../package.json'); 16 | const pkgVersion = pkg.version; 17 | const pkgName = pkg.name; 18 | const daysPassed = (Date.now() - localConfig.lastUpdateCheck || 0) / (60 * 60 * 1000 * 24); 19 | const res: VersionOutput = { 20 | current: pkgVersion, 21 | latest: localConfig.latestVersion || pkgVersion, 22 | error: null, 23 | }; 24 | 25 | try { 26 | // won't repeatly check in a day 27 | if (daysPassed > 1) { 28 | res.latest = semver.gt(localConfig.latestVersion, pkgVersion) ? localConfig.latestVersion : pkgVersion; 29 | } else { 30 | const latestVersion = await getRemoteVersion(pkgName) as string; 31 | localStore.setAll({ 32 | lastUpdateCheck: Date.now(), 33 | latestVersion, 34 | }); 35 | res.latest = latestVersion; 36 | } 37 | } catch(e) { 38 | res.error = e.message; 39 | } 40 | return res; 41 | } 42 | 43 | export default getVersions; -------------------------------------------------------------------------------- /packages/cli/src/lib/util/connectToIDE.ts: -------------------------------------------------------------------------------- 1 | import { getOrDownloadIDE, createIdeShellSimpleLauncher, ProjectType, } from './ideLocator'; 2 | import { logWithSpinner, successSpinner, } from '../cli-shared-utils/lib/spinner'; 3 | import sleep from './sleep'; 4 | import { locateMiniStudioBinPath, } from './ideLocator'; 5 | import { URI, } from 'vscode-uri'; 6 | 7 | export const path2Uri = (p: string): string => URI.file(p).toString(); 8 | 9 | /** 10 | * 单纯启动IDE,不进行编译托管 11 | */ 12 | export const launchIDEOnly = async ( 13 | projectPath: string, 14 | useLite: boolean, 15 | projectType: ProjectType, 16 | binPath?: string, // 如果传入binPath则使用binPath指定的ide来启动 17 | ): Promise => { 18 | let miniStudioBinPath = ''; 19 | if (binPath) { 20 | miniStudioBinPath = binPath; 21 | } else { 22 | const ideInstallRoot = await getOrDownloadIDE(); 23 | miniStudioBinPath = locateMiniStudioBinPath(ideInstallRoot); 24 | } 25 | 26 | logWithSpinner(useLite ? 'IDE Lite模式启动中' : 'IDE启动中'); 27 | 28 | createIdeShellSimpleLauncher(miniStudioBinPath, { 29 | isDebug: !!process.env.DEBUG, 30 | })({ 31 | windowMode: useLite ? 'lite' : 'default', 32 | projectUri: path2Uri(projectPath), 33 | projectType, 34 | volDriverChannelName: 'mini', 35 | }); 36 | // 调用启动命令后无法准确获知IDE何时启动成功,因此这里sleep 1s后打印启动成功 37 | await sleep(1000); 38 | successSpinner('启动成功!'); 39 | }; -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", // Specifies the ESLint parser 3 | "parserOptions": { 4 | "ecmaVersion": 2020, // Allows for the parsing of modern ECMAScript features 5 | "sourceType": "module" // Allows for the use of imports 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended" // Uses the recommended rules from the @typescript-eslint/eslint-plugin 10 | ], 11 | "env": { 12 | "node": true 13 | }, 14 | "rules": { 15 | "no-console": [1, { "allow": ["warn", "error"] }], 16 | "valid-jsdoc": [0], 17 | "indent": ["error", 2], 18 | "semi": "error", 19 | "@typescript-eslint/type-annotation-spacing": "error", 20 | "@typescript-eslint/no-unsafe-assignment": 0, 21 | "@typescript-eslint/no-var-requires": 0, 22 | "quotes": ["error", "single"], 23 | "@typescript-eslint/ban-ts-comment": 0, 24 | "comma-dangle": [ 25 | 1, 26 | { 27 | "arrays": "ignore", 28 | "objects": "always", 29 | "imports": "always", 30 | "exports": "ignore", 31 | "functions": "ignore" 32 | } 33 | ], 34 | "comma-spacing": 1, 35 | "object-curly-spacing": ["error", "always"], 36 | "space-infix-ops": ["error", {"int32Hint": false}], 37 | "@typescript-eslint/no-empty-interface": 0, 38 | "no-empty": 0 39 | } 40 | } -------------------------------------------------------------------------------- /packages/cli/src/commands/upload/index.ts: -------------------------------------------------------------------------------- 1 | import CommandWrapper from '../../scheduler/command/commandWrapper'; 2 | import { EAppType, ECommandName, } from '../../lib/common/types'; 3 | import upload from '../../actions/upload'; 4 | import commmandsConfig from '../commandsConfig'; 5 | import config from '../../lib/common/config'; 6 | 7 | export interface ICommandOptions { 8 | miniAppId?: string; 9 | version?: string; 10 | token?: string; 11 | override?: boolean; 12 | } 13 | 14 | export default CommandWrapper({ 15 | name: ECommandName.upload, 16 | registerCommand(ctx) { 17 | return { 18 | ...commmandsConfig[ECommandName.upload], 19 | action: async (options) => { 20 | const { 21 | dtdConfig, 22 | cwd, 23 | } = ctx; 24 | 25 | if (!dtdConfig.type) { 26 | ctx.logger.error(`当前目录 ${cwd} 下没有找到 ${config.workspaceRcName} 文件,请先使用init初始化项目;或者也可以选择手动新增 ${config.workspaceRcName} 配置文件,参考文档 https://developers.dingtalk.com/document/resourcedownload/configuration-description?pnamespace=app`); 27 | return; 28 | } 29 | 30 | if ([EAppType.MP, EAppType.PLUGIN].indexOf(dtdConfig.type) === -1) { 31 | ctx.logger.error(`${ECommandName.upload} 命令只支持小程序和工作台组件`); 32 | return; 33 | } 34 | 35 | await upload(ctx, options); 36 | }, 37 | }; 38 | }, 39 | }); -------------------------------------------------------------------------------- /packages/cli/src/commands/preview/index.ts: -------------------------------------------------------------------------------- 1 | import CommandWrapper from '../../scheduler/command/commandWrapper'; 2 | import qrcode from '../../actions/qrcode'; 3 | import { EAppType, ECommandName, } from '../../lib/common/types'; 4 | import commmandsConfig from '../commandsConfig'; 5 | import config from '../../lib/common/config'; 6 | 7 | export interface ICommandOptions { 8 | miniAppId?: string; 9 | token?: string; 10 | debug?: boolean; 11 | page?: string; 12 | query?: string; 13 | } 14 | 15 | export default CommandWrapper({ 16 | name: ECommandName.preview, 17 | registerCommand(ctx) { 18 | return { 19 | ...commmandsConfig.preview, 20 | action: async (options) => { 21 | const { 22 | dtdConfig, 23 | cwd, 24 | } = ctx; 25 | 26 | if (!dtdConfig.type) { 27 | ctx.logger.error(`当前目录 ${cwd} 下没有找到 ${config.workspaceRcName} 文件,请先使用init初始化项目;或者也可以选择手动新增 ${config.workspaceRcName} 配置文件,参考文档 https://developers.dingtalk.com/document/resourcedownload/configuration-description?pnamespace=app`); 28 | return; 29 | } 30 | 31 | if ([EAppType.MP, EAppType.PLUGIN].indexOf(dtdConfig.type as EAppType) === -1) { 32 | ctx.logger.error(`${ECommandName.preview} 命令只支持小程序或工作台组件`); 33 | return; 34 | } 35 | 36 | qrcode(ctx, options); 37 | }, 38 | }; 39 | }, 40 | }); -------------------------------------------------------------------------------- /packages/cli/src/lib/util/gulpInspector.ts: -------------------------------------------------------------------------------- 1 | import { spawn, } from 'child_process'; 2 | import { getLogger, } from '../cli-shared-utils/lib/logger'; 3 | import stripAnsi from 'strip-ansi'; 4 | import { isWindows, } from '../cli-shared-utils'; 5 | 6 | const logger = getLogger('gulpInspector'); 7 | 8 | export default (gulpBin: string, opts: { 9 | gulpfile: string; 10 | src: string; 11 | dist: string; 12 | cwd: string; 13 | }, hooks?: { 14 | onError?: (err: Error) => void, 15 | onDone?: () => void, 16 | }): void => { 17 | const { 18 | gulpfile, 19 | src, 20 | dist, 21 | cwd, 22 | } = opts; 23 | 24 | const cp = spawn( 25 | require.resolve(gulpBin), 26 | [ 27 | '--gulpfile', gulpfile, 28 | '--src', src, 29 | '--dist', dist, 30 | '--color' // preserve the terminal color 31 | ], 32 | { 33 | stdio: 'pipe', 34 | cwd, 35 | env: process.env, 36 | shell: true, 37 | } 38 | ); 39 | 40 | cp.stdout.pipe(process.stdout); 41 | cp.stderr.pipe(process.stderr); 42 | cp.stdout.on('data', (chunk)=>{ 43 | const data = stripAnsi(chunk.toString()); 44 | if (data.indexOf('Starting \'auto\'...') !== -1) { 45 | hooks.onDone && hooks.onDone(); 46 | } 47 | }); 48 | cp.on('error', (err) => { 49 | logger.debug('gulpInspector exit witch err', err); 50 | hooks.onError && hooks.onError(err); 51 | }); 52 | }; -------------------------------------------------------------------------------- /packages/cli/src/scheduler/command/commandLoader.ts: -------------------------------------------------------------------------------- 1 | import { ICommandConfig, PlainRecord, } from '../../lib/common/types'; 2 | import { logger, } from '../../lib/cli-shared-utils/lib/logger'; 3 | import { isAbsolute, } from 'path'; 4 | import { existsSync, } from 'fs'; 5 | import * as path from 'path'; 6 | 7 | /** 8 | * Expose a utility to resolve path of a command. 9 | * @param name command name 10 | */ 11 | export function resolveCommandPath( 12 | name: string, 13 | root: string, 14 | ): string { 15 | if (isAbsolute(name)) { 16 | if (!existsSync(name)) { 17 | throw new Error(`command: ${name} does not exist!`); 18 | } 19 | } 20 | const modulePath = require.resolve(path.resolve(root, name)); 21 | return modulePath; 22 | } 23 | 24 | /** 25 | * Expose a utility to resolve a command. 26 | * @param name command name 27 | */ 28 | export function resolveCommand( 29 | name: string, 30 | root: string, 31 | ): ICommandConfig { 32 | try { 33 | const modulePath = resolveCommandPath( 34 | name, 35 | root, 36 | ); 37 | const required = require(modulePath); 38 | return required.default || required; 39 | } catch (e) { 40 | if (e instanceof Error) { 41 | logger.error(`Failed to load command: ${name}\n${e.message}`); 42 | } else { 43 | logger.error(`Failed to load command: ${name}\n${JSON.stringify(e)}`); 44 | } 45 | throw e; 46 | } 47 | } -------------------------------------------------------------------------------- /packages/generator/src/utils/cli-shared-utils.ts: -------------------------------------------------------------------------------- 1 | import { execSync, } from 'child_process'; 2 | import * as semver from 'semver'; 3 | 4 | export function hasYarn (): boolean { 5 | try { 6 | execSync('yarn --version', { stdio: 'ignore', }); 7 | return true; 8 | } catch (e) { 9 | return false; 10 | } 11 | } 12 | 13 | export function hasNpm (): boolean { 14 | try { 15 | execSync('npm --version', { stdio: 'ignore', }); 16 | return true; 17 | } catch (e) { 18 | return false; 19 | } 20 | } 21 | 22 | export function hasPnpm (): boolean { 23 | try { 24 | execSync('npm --version', { stdio: 'ignore', }); 25 | return true; 26 | } catch (e) { 27 | return false; 28 | } 29 | } 30 | 31 | export function getPnpmVersion (): string { 32 | let pnpmversion; 33 | try { 34 | pnpmversion = execSync('pnpm --version', { 35 | stdio: ['pipe', 'pipe', 'ignore'], 36 | }).toString(); 37 | // there's a critical bug in pnpm 2 38 | // https://github.com/pnpm/pnpm/issues/1678#issuecomment-469981972 39 | // so we only support pnpm >= 3.0.0 40 | } finally { 41 | pnpmversion = pnpmversion || '0.0.0'; 42 | } 43 | return pnpmversion; 44 | } 45 | 46 | export function hasPnpmVersionOrLater (version: string): boolean { 47 | return semver.gte(getPnpmVersion(), version); 48 | } 49 | 50 | export function hasPnpm3OrLater (): boolean { 51 | return hasPnpmVersionOrLater('3.0.0'); 52 | } 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /packages/dingtalk-h5package-opensdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dingtalk-h5package-opensdk", 3 | "version": "0.0.3", 4 | "description": "H5离线包OpenSDK", 5 | "author": "vularr ", 6 | "homepage": "https://github.com/open-dingtalk/dingtalk-design-cli#readme", 7 | "license": "MIT", 8 | "main": "dist/index.js", 9 | "directories": { 10 | "dist": "dist", 11 | "test": "__tests__" 12 | }, 13 | "bin": { 14 | "h5package": "./dist/cli/h5package.js" 15 | }, 16 | "files": [ 17 | "dist" 18 | ], 19 | "publishConfig": { 20 | "registry": "https://registry.npmjs.org/" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/open-dingtalk/dingtalk-design-cli.git" 25 | }, 26 | "scripts": { 27 | "build": "tsc ", 28 | "lint": "eslint src", 29 | "test": "jest -i", 30 | "prepublishOnly": "npm run lint && npm run build" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/open-dingtalk/dingtalk-design-cli/issues" 34 | }, 35 | "devDependencies": { 36 | "@tsconfig/node12": "^1.0.11", 37 | "@types/archiver": "^5.3.1", 38 | "@types/fs-extra": "^9.0.13", 39 | "@types/node": "^18.7.18", 40 | "@types/uuid": "^8.3.4", 41 | "decompress": "^4.2.1" 42 | }, 43 | "dependencies": { 44 | "ali-oss": "^6.17.1", 45 | "archiver": "^5.3.1", 46 | "axios": "^0.27.2", 47 | "commander": "^9.4.1", 48 | "fs-extra": "^10.1.0", 49 | "uuid": "^9.0.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/h5pro-component-react/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface IRegisterModuleOption4RegisterGlobalSymbol { 4 | my: unknown; 5 | getApp(): unknown; 6 | } 7 | 8 | interface IAppxH5ComponentPagePublicInstance { 9 | readonly data: Record; 10 | readonly $viewId: string; 11 | readonly $id: number; 12 | $batchedUpdates(fn: () => void): void; 13 | $spliceData(data: Record, callback?: () => void): void; 14 | setData(data: Record, callback?: () => void): void; 15 | [P: string]: any; 16 | } 17 | 18 | interface IRegisterModuleOption { 19 | onRegisterGlobalSymbol(symbolMap: IRegisterModuleOption4RegisterGlobalSymbol): void | Partial; 20 | onPageLoad(payload: { 21 | page: IAppxH5ComponentPagePublicInstance; 22 | componentIs: string; 23 | readonly componentProps: Record; 24 | }): void; 25 | } 26 | 27 | export declare function Slot(props: { children?: T }): T | null; 28 | export declare function createComponent(componentModule: any): React.ComponentClass<{ is: string; [P: string]: any }>; 29 | export declare function registerModule( 30 | componentModule: any, 31 | option?: void | Partial | IRegisterModuleOption['onRegisterGlobalSymbol'] 32 | ): { 33 | main: T; 34 | component2: boolean; 35 | Component: React.ComponentClass<{ is: string; [P: string]: any }>; 36 | pluginId: string | void; 37 | }; 38 | -------------------------------------------------------------------------------- /packages/generator/src/utils/generateTitle.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import * as semver from 'semver'; 3 | import boxen from 'boxen'; 4 | import getVersions from './getVersions'; 5 | import getGlobalInstallCommand from './getGlobalInstallCommand'; 6 | import { debug, } from './logger'; 7 | 8 | export default async function generateTitle(checkUpdate: boolean): Promise { 9 | const { current, latest, error, } = await getVersions(); 10 | 11 | let title = chalk.bold.blue(`Dingding Worktab-Plugin v${current}`); 12 | if (error) { 13 | debug(error); 14 | title += '\n' + chalk.red('Failed to check for updates'); 15 | } 16 | 17 | if(checkUpdate && semver.gt(latest, current)) { 18 | let upgradeMessage = `New version available ${chalk.magenta(current)} → ${chalk.green(latest)}`; 19 | 20 | try { 21 | const command = getGlobalInstallCommand(); 22 | let name = require('../../package.json').name; 23 | if (semver.prerelease(latest)) { 24 | name += '@next'; 25 | } 26 | 27 | if (command) { 28 | upgradeMessage += 29 | `\nRun ${chalk.yellow(`${command} ${name}`)} to update!`; 30 | } 31 | // eslint-disable-next-line no-empty 32 | } catch(e) { 33 | } finally { 34 | const upgradeBox = boxen(upgradeMessage, { 35 | align: 'center', 36 | borderColor: 'green', 37 | dimBorder: true, 38 | padding: 1, 39 | }); 40 | title += `\n${upgradeBox}\n`; 41 | } 42 | } 43 | 44 | return title; 45 | } -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | defaults: &defaults 4 | working_directory: ~/project/dd 5 | docker: 6 | - image: circleci/node:lts-browsers 7 | 8 | aliases: 9 | - &restore-yarn-cache 10 | key: v2-dd-cli-{{ checksum "yarn.lock" }} 11 | 12 | - &save-yarn-cache 13 | key: v2-dd-cli-{{ checksum "yarn.lock" }} 14 | paths: 15 | - node_modules/ 16 | - ~/.cache 17 | 18 | workflow_filters: &filters 19 | filters: 20 | branches: 21 | ignore: 22 | - docs 23 | 24 | jobs: 25 | install: 26 | <<: *defaults 27 | steps: 28 | - checkout 29 | - restore_cache: *restore-yarn-cache 30 | - run: yarn --network-timeout 600000 31 | - save_cache: *save-yarn-cache 32 | - persist_to_workspace: 33 | root: ~/ 34 | paths: 35 | - project/dd 36 | - .cache/Cypress 37 | 38 | dd-cli: 39 | <<: *defaults 40 | steps: 41 | - attach_workspace: 42 | at: ~/ 43 | - run: yarn run build 44 | - run: yarn test -p cli 45 | 46 | dd-generator: 47 | <<: *defaults 48 | steps: 49 | - attach_workspace: 50 | at: ~/ 51 | - run: yarn test -p generator 52 | 53 | workflows: 54 | version: 2 55 | test: 56 | jobs: 57 | - install: 58 | <<: *filters 59 | - dd-cli: 60 | <<: *filters 61 | requires: 62 | - install 63 | - dd-generator: 64 | <<: *filters 65 | requires: 66 | - install -------------------------------------------------------------------------------- /packages/generator/generators/utils/timeInspector.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = this && this.__awaiter || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) {return value instanceof P ? value : new P(function (resolve) {resolve(value);});} 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) {try {step(generator.next(value));} catch (e) {reject(e);}} 6 | function rejected(value) {try {step(generator["throw"](value));} catch (e) {reject(e);}} 7 | function step(result) {result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);} 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | /** 13 | * inspect fn which run out of time 14 | * @param fn inspected callback, must be a promise 15 | * @param interval time interval 16 | */ 17 | exports.default = (fn, opts) => __awaiter(void 0, void 0, void 0, function* () { 18 | const { logger = console.log, interval = 3 * 1000, fetchingTips = 'fetching...', timeout = 30 * 1000, timeoutTips = 'task timeout' } = opts || {}; 19 | const timer = setInterval(() => { 20 | logger(fetchingTips); 21 | }, interval); 22 | const killerTimer = setTimeout(() => { 23 | clearInterval(timer); 24 | clearTimeout(killerTimer); 25 | throw timeoutTips; 26 | }, timeout); 27 | try { 28 | yield fn(); 29 | } finally 30 | { 31 | clearInterval(timer); 32 | clearTimeout(killerTimer); 33 | } 34 | }); -------------------------------------------------------------------------------- /packages/generator/generators/utils/copy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = this && this.__createBinding || (Object.create ? function (o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function () {return m[k];} }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | } : function (o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | }); 13 | var __setModuleDefault = this && this.__setModuleDefault || (Object.create ? function (o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | } : function (o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = this && this.__importStar || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | const child_process_1 = require("child_process"); 27 | const path = __importStar(require("path")); 28 | /** 29 | * 会强制覆盖目标文件且静默 30 | */ 31 | exports.default = (source, dest) => { 32 | try { 33 | (0, child_process_1.execSync)(`cp -af ${path.join(source, '/')} ${dest}`); 34 | } 35 | catch (e) { 36 | console.error('exec cp error', e); 37 | throw e; 38 | } 39 | }; -------------------------------------------------------------------------------- /packages/cli/src/lib/cli-shared-utils/lib/spinner.ts: -------------------------------------------------------------------------------- 1 | import ora from 'ora'; 2 | import chalk from 'chalk'; 3 | 4 | const spinner = ora({ 5 | discardStdin: false, 6 | }); 7 | let lastMsg: { symbol: string; text: string } | null = null; 8 | let isPaused = false; 9 | 10 | export function logWithSpinner(symbol: string, msg?: string) { 11 | if (!msg) { 12 | msg = symbol; 13 | symbol = chalk.green('✔'); 14 | } 15 | if (lastMsg) { 16 | spinner.stopAndPersist({ 17 | symbol: lastMsg.symbol, 18 | text: lastMsg.text, 19 | }); 20 | } 21 | spinner.text = ` ${msg}`; 22 | lastMsg = { 23 | symbol: `${symbol} `, 24 | text: msg, 25 | }; 26 | spinner.start(); 27 | } 28 | 29 | export function stopSpinner(text?: string, persist?: boolean) { 30 | if (lastMsg && persist !== false) { 31 | spinner.stopAndPersist({ 32 | symbol: lastMsg.symbol, 33 | text: text || lastMsg.text, 34 | }); 35 | } else { 36 | spinner.stop(); 37 | } 38 | lastMsg = null; 39 | } 40 | 41 | export function pauseSpinner() { 42 | if (spinner.isSpinning) { 43 | spinner.stop(); 44 | isPaused = true; 45 | } 46 | } 47 | 48 | export function resumeSpinner() { 49 | if (isPaused) { 50 | spinner.start(); 51 | isPaused = false; 52 | } 53 | } 54 | 55 | export function successSpinner(text: string) { 56 | spinner.stopAndPersist({ 57 | symbol: lastMsg ? lastMsg.symbol : '', 58 | text: text || (lastMsg ? lastMsg.text : ''), 59 | }); 60 | } 61 | 62 | export function failSpinner(text: string) { 63 | spinner.fail(text); 64 | } 65 | -------------------------------------------------------------------------------- /packages/generator/src/utils/store.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { RC_PATH, } from '@common/config'; 3 | 4 | export interface Options { 5 | // 存储地址 6 | storePath?: string; 7 | } 8 | 9 | export interface Config { 10 | // 上一次检查的时间,保证一天最多检查一次,13位时间戳 11 | lastUpdateCheck: number; 12 | // 上一次检查拉回来的最新latest版本 13 | latestVersion: string; 14 | } 15 | 16 | const DEFAULT_STORE_PATH = RC_PATH; 17 | 18 | export default class Store { 19 | storePath: string; 20 | config: Config; 21 | 22 | constructor(opts?: Options) { 23 | this.storePath = opts && opts.storePath || DEFAULT_STORE_PATH; 24 | 25 | try { 26 | const config = JSON.parse(fs.readFileSync(this.storePath, { 27 | encoding: 'utf-8', 28 | })) as Config; 29 | this.config = config; 30 | } catch(e) { 31 | this.config = {} as Config; 32 | } 33 | } 34 | 35 | set(key: keyof Config, val: Config[keyof Config]): void { 36 | // @ts-expect-error 37 | this.config[key] = val; 38 | fs.writeFile(this.storePath, JSON.stringify(this.config), { encoding: 'utf-8', }, (err)=>{ 39 | if(err) { 40 | console.error(err); 41 | } 42 | }); 43 | } 44 | 45 | setAll(cf: Config): void { 46 | this.config = cf; 47 | fs.writeFile(this.storePath, JSON.stringify(this.config), { encoding: 'utf-8', }, (err)=>{ 48 | if(err) { 49 | console.error(err); 50 | } 51 | }); 52 | } 53 | 54 | get(key: keyof Config): Config[keyof Config] { 55 | return this.config[key]; 56 | } 57 | 58 | getAll(): Config { 59 | return this.config; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getSimulatorFrameworkStoreDir.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 云端获取模拟器框架页面,并存储到本地 3 | * 如果获取失败,则默认取 tpl/webSimulator.html作为模拟器框架页面 4 | */ 5 | 6 | import config from '../common/config'; 7 | import { failSpinner, logWithSpinner, successSpinner, } from '../cli-shared-utils/lib/spinner'; 8 | import download from 'download'; 9 | import { logger, } from '../cli-shared-utils/lib/logger'; 10 | import path from 'path'; 11 | 12 | import getMt2Config from './getMt2Config'; 13 | import getMonitor from '../cli-shared-utils/lib/monitor/framework-monitor'; 14 | 15 | const monitor = getMonitor(config.yuyanId); 16 | 17 | export default async (isMiniApp?: boolean): Promise => { 18 | const DEFAULT_SIMULATOR_FRAMEWORK_DIR = path.join(__dirname, '../../../tpl'); 19 | 20 | const configType = isMiniApp ? 'miniAppWebSimulator' : 'webSimulator'; 21 | /** get mt2Config */ 22 | const mt2Config = await getMt2Config(); 23 | 24 | if (!mt2Config) return DEFAULT_SIMULATOR_FRAMEWORK_DIR; 25 | 26 | /** simulator framework download */ 27 | const htmlPath = isMiniApp ? mt2Config.frameworkConfig.miniAppHtml : mt2Config.frameworkConfig.h5Html; 28 | const frameworkDir = config[configType].webSimulatorFrameworkStoreDir; 29 | 30 | try { 31 | logWithSpinner('检查模拟器框架版本...'); 32 | await download(htmlPath, frameworkDir, { 33 | filename: config[configType].webSimulatorFrameworkHtmlName, 34 | }); 35 | successSpinner('模拟器框架更新成功'); 36 | return frameworkDir; 37 | } catch(e) { 38 | failSpinner('模拟器框架更新失败'); 39 | logger.error(e.message); 40 | monitor.logJSError(e); 41 | return ''; 42 | } 43 | }; -------------------------------------------------------------------------------- /packages/generator/generators/utils/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = this && this.__importDefault || function (mod) { 3 | return mod && mod.__esModule ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.error = exports.warn = exports.done = exports.info = exports.debug = void 0; 7 | const chalk_1 = __importDefault(require("chalk")); 8 | const strip_ansi_1 = __importDefault(require("strip-ansi")); 9 | const format = (label, msg) => { 10 | return msg.split('\n').map((line, i) => { 11 | return i === 0 ? 12 | `${label} ${line}` : 13 | line.padStart((0, strip_ansi_1.default)(label).length); 14 | }).join('\n'); 15 | }; 16 | const debug = (msg) => { 17 | const txt = format(chalk_1.default.bgBlue.black(' DEBUG '), msg); 18 | return process.env.DEBUG && console.log(txt); 19 | }; 20 | exports.debug = debug; 21 | const info = (msg, pure) => { 22 | const txt = format(chalk_1.default.bgBlue.black(' INFO '), msg); 23 | return pure ? txt : console.log(pure); 24 | }; 25 | exports.info = info; 26 | const done = (msg, pure) => { 27 | const txt = format(chalk_1.default.bgGreen.black(' DONE '), msg); 28 | return pure ? txt : console.log(pure); 29 | }; 30 | exports.done = done; 31 | const warn = (msg, pure) => { 32 | const txt = format(chalk_1.default.bgYellow.black(' WARN '), msg); 33 | return pure ? txt : console.warn(pure); 34 | }; 35 | exports.warn = warn; 36 | const error = (msg, pure) => { 37 | const txt = format(chalk_1.default.bgRed(' ERROR '), msg); 38 | return pure ? txt : console.error(pure); 39 | }; 40 | exports.error = error; -------------------------------------------------------------------------------- /packages/cli/src/actions/init.ts: -------------------------------------------------------------------------------- 1 | import yeomanRuntime from 'yeoman-environment'; 2 | import chalk from 'chalk'; 3 | import * as path from 'path'; 4 | import getMonitor from '../lib/cli-shared-utils/lib/monitor/framework-monitor'; 5 | import config from '../lib/common/config'; 6 | import { logger, } from '../lib/cli-shared-utils/lib/logger'; 7 | 8 | interface IOpts { 9 | cwd?: string; 10 | outDir?: string; 11 | appType?: string; 12 | template?: string; 13 | 'skip-install'?: boolean; 14 | language?: string; 15 | } 16 | interface IResponse {} 17 | 18 | const monitor = getMonitor(config.yuyanId); 19 | 20 | export default async (options: IOpts): Promise => { 21 | return new Promise((res, rej) => { 22 | const env = yeomanRuntime.createEnv(); 23 | const { 24 | cwd = '', 25 | outDir = '', 26 | } = options; 27 | const done = (err)=>{ 28 | if (err) { 29 | monitor.logJSError(err); 30 | rej(err); 31 | } else { 32 | logger.success(`初始化完成,请 ${chalk.yellow(`cd ${path.resolve(cwd, outDir)}`)} 进入工作目录,接下来可以使用 ${chalk.yellow('ding dev')} 进行开发调试`); 33 | res(null); 34 | } 35 | }; 36 | // @ts-ignore 37 | env.lookup(function () { 38 | logger.debug('GeneratorsMeta', env.getGeneratorsMeta()); 39 | env.run(`${config.generatorNamespace} ${outDir || ''}`, { 40 | appType: options['appType'], 41 | template: options['template'], 42 | language: options['language'], 43 | outDir: outDir || './', 44 | 'skip-install': options['skipInstall'], 45 | // @ts-ignore 46 | }, done); 47 | }); 48 | }); 49 | }; -------------------------------------------------------------------------------- /packages/cli/src/bin/dd.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-console */ 3 | import checkNodeVersion from '../lib/util/checkNodeVersion'; 4 | import * as path from 'path'; 5 | import Scheduler from '../scheduler'; 6 | import { getSchedulerOptionsFromProcessArgv, } from '../scheduler/utils'; 7 | import config from '../lib/common/config'; 8 | import getCurrentPkgInfo from '../lib/util/getCurrentPkgInfo'; 9 | import getMonitor from '../lib/cli-shared-utils/lib/monitor/framework-monitor'; 10 | import { logger, } from '../lib/cli-shared-utils/lib/logger'; 11 | 12 | const pkgInfo = getCurrentPkgInfo(); 13 | const requiredVersion = pkgInfo.engines.node; 14 | const pkgName = pkgInfo.name; 15 | 16 | checkNodeVersion(requiredVersion, pkgName); 17 | 18 | const opts = getSchedulerOptionsFromProcessArgv(); 19 | const scheduler = new Scheduler({ 20 | ...opts, 21 | yuyanId: config.yuyanId, 22 | commandRoot: path.join(__dirname, '../commands'), 23 | }); 24 | 25 | async function main() { 26 | const monitor = getMonitor(config.yuyanId); 27 | 28 | process.on('uncaughtException', (err) => { 29 | logger.debug('uncaughtException', err); 30 | monitor.logJSError(err); 31 | }); 32 | 33 | process.on('unhandledRejection', (reason, promise) => { 34 | logger.debug('unhandledRejection', reason); 35 | monitor.logJSError(new Error(JSON.stringify(reason))); 36 | }); 37 | 38 | process.on('exit', () => { 39 | monitor.flush(); 40 | }); 41 | 42 | try { 43 | await scheduler.bootstrap(); 44 | } catch (e) { 45 | monitor.logJSError(e); 46 | monitor.flush(); 47 | throw e; 48 | } 49 | } 50 | 51 | main(); 52 | 53 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/task/buildTask.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { sdk, } from '../../src/index'; 3 | import * as sdkConfig from '../sdk-test-config'; 4 | import path from 'path'; 5 | 6 | const cfg = sdkConfig.prod; 7 | 8 | jest.setTimeout(60000); 9 | 10 | describe('BuildTask', () => { 11 | 12 | beforeAll(() => { 13 | sdk.setConfig(cfg); 14 | }); 15 | 16 | test('miniapp', async () => { 17 | const resultUrl = await sdk.miniUpload({ 18 | project: path.resolve(__dirname, '../examples/miniapp-default'), 19 | miniAppId: cfg.miniAppId, 20 | // packageVersion: '0.0.11', 21 | onProgressUpdate: (info) => { 22 | console.log('xxx onProgressUpdate', info); 23 | }, 24 | }); 25 | 26 | console.log('xxx resultUrl', resultUrl); 27 | }); 28 | 29 | test('miniapp-use-plugin', async () => { 30 | const resultUrl = await sdk.miniUpload({ 31 | project: path.resolve(__dirname, '../examples/miniapp-use-plugin'), 32 | miniAppId: cfg.miniAppId, 33 | // packageVersion: '0.0.11', 34 | onProgressUpdate: (info) => { 35 | console.log('xxx onProgressUpdate', info); 36 | }, 37 | }); 38 | 39 | console.log('xxx resultUrl', resultUrl); 40 | }); 41 | 42 | test('miniapp-use-subpackages', async () => { 43 | const resultUrl = await sdk.miniUpload({ 44 | project: path.resolve(__dirname, '../examples/miniapp-use-subpackages'), 45 | miniAppId: cfg.miniAppId, 46 | // packageVersion: '0.0.11', 47 | onProgressUpdate: (info) => { 48 | console.log('xxx onProgressUpdate', info); 49 | }, 50 | }); 51 | 52 | console.log('xxx resultUrl', resultUrl); 53 | }); 54 | }); -------------------------------------------------------------------------------- /packages/generator/src/common/config.ts: -------------------------------------------------------------------------------- 1 | import * as os from 'os'; 2 | import * as path from 'path'; 3 | import { APP_TYPE_ENUM, } from './constants'; 4 | 5 | // 配置文件 6 | export const RC_PATH = path.join(os.homedir(), '.dd-cli-rc.json'); 7 | // 本地缓存 8 | export const REPO_LOCAL_ROOT_PATH = path.join(os.homedir(), '.dd-demo-repo'); 9 | // 代码仓库目录名分隔符,合格的目录名格式: ${appType}${seperator}${desc} 10 | export const DEFAULT_DIRECTORY_SEPERATOR = '_'; 11 | 12 | interface HUB { 13 | /** 14 | * 套件名称 15 | * 用于问询环节以及模板配置文件中 16 | */ 17 | name?: string; 18 | 19 | /** 20 | * 套件标识 21 | */ 22 | key: string; 23 | 24 | /** 25 | * 套件的代码仓库地址 26 | * git ssh 27 | * 仓库的层级为template->language,必须要严格按照此标准 28 | */ 29 | repoRemotePath: string; 30 | 31 | /** 32 | * 套件描述 33 | */ 34 | desc?: string; 35 | 36 | /** 37 | * @todo 38 | * 扩展命令 39 | * 由dde承载 40 | */ 41 | commands?: []; 42 | 43 | /** 44 | * @todo 45 | * init结束后的句柄 46 | */ 47 | postInit?: Promise; 48 | } 49 | 50 | const DEFAULT_REPO_REMOTE_PATH = process.env.REPO_REMOTE_PATH || 'https://gitee.com/open-dingtalk/dd-application-template.git'; 51 | // 套件配置 52 | export const HUBS_CONFIG: HUB[] = [ 53 | { 54 | key: APP_TYPE_ENUM.PLUGIN, 55 | name: '插件', 56 | repoRemotePath: DEFAULT_REPO_REMOTE_PATH, 57 | }, 58 | { 59 | key: APP_TYPE_ENUM.MP, 60 | name: '小程序', 61 | repoRemotePath: DEFAULT_REPO_REMOTE_PATH, 62 | }, 63 | { 64 | key: APP_TYPE_ENUM.H5, 65 | name: 'H5微应用', 66 | repoRemotePath: DEFAULT_REPO_REMOTE_PATH, 67 | }, 68 | { 69 | key: APP_TYPE_ENUM.DOCSCOOLAPP, 70 | name: '文档酷应用', 71 | repoRemotePath: DEFAULT_REPO_REMOTE_PATH, 72 | } 73 | ]; 74 | -------------------------------------------------------------------------------- /packages/cli/src/scheduler/command/index.ts: -------------------------------------------------------------------------------- 1 | import { getLogger, } from '../../lib/cli-shared-utils/lib/logger'; 2 | import { ICommandConfig, ECommandConfigProperty, PlainRecord, ICommandContext, ECommandName, } from '../../lib/common/types'; 3 | import { resolveCommand, } from './commandLoader'; 4 | import chalk from 'chalk'; 5 | 6 | export default class Command { 7 | public commandConfig: ICommandConfig; 8 | public commandContext: ICommandContext; 9 | 10 | constructor(opts: { 11 | name: ECommandName; 12 | ctx: ICommandContext; 13 | root: string; 14 | }) { 15 | const timeStart = Date.now(); 16 | 17 | this.commandConfig = resolveCommand(opts.name, opts.root); 18 | this.commandContext = Object.create(opts.ctx); 19 | this.commandContext.setCommandName(opts.name); 20 | this.commandContext.setLogger(getLogger(`bin/dd ${opts.name}`)); 21 | 22 | const timeEnd = Date.now(); 23 | this.commandContext.logger.debug(`load command ${opts.root}/${opts.name} (${chalk.yellowBright('' + (timeEnd - timeStart))}ms)`); 24 | } 25 | 26 | /** 27 | * 获取钩子 28 | * 29 | * @param name 30 | */ 31 | public getHook(name: K): (ICommandConfig[K]) { 32 | return this.commandConfig[name]; 33 | } 34 | 35 | /** 36 | * 运行钩子 37 | * 38 | * @param name 39 | * @param args 40 | */ 41 | public applyHook(name: ECommandConfigProperty, ...args: unknown[]): T { 42 | const hook = this.commandConfig[name]; 43 | if (typeof hook === 'function') { 44 | // @ts-ignore TODO 45 | return hook(this.commandContext, ...args); 46 | } 47 | throw new Error(`Invalid call: ${name}`); 48 | } 49 | } -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getSimulatorAssetsDir.ts: -------------------------------------------------------------------------------- 1 | import ProgressBar from 'progress'; 2 | import EasyDl from 'easydl'; 3 | import path from 'path'; 4 | import mkdirp from 'mkdirp'; 5 | import config from '../common/config'; 6 | import os from 'os'; 7 | import * as fs from 'fs'; 8 | import { setGlobalRc, getGlobalRc, } from './globalRc'; 9 | import { logger, } from '../cli-shared-utils/lib/logger'; 10 | import { failSpinner, logWithSpinner, successSpinner, } from '../cli-shared-utils/lib/spinner'; 11 | import download from 'download'; 12 | import clean from './clean'; 13 | import tarExtract from './tarExtract'; 14 | 15 | /** 16 | * 下载模拟器资源 17 | */ 18 | export default async (): Promise => { 19 | const globalRc = getGlobalRc(); 20 | if (globalRc && globalRc.localWebSimulatorAssetsDir) { 21 | return globalRc.localWebSimulatorAssetsDir; 22 | } 23 | 24 | /** tar download */ 25 | try { 26 | logWithSpinner('本地没找到模拟器资源,正在下载'); 27 | await download(config.webSimulator.tarUrl, config.webSimulator.storeDir); 28 | successSpinner('模拟器资源下载成功'); 29 | } catch(e) { 30 | failSpinner('模拟器资源下载失败'); 31 | logger.error(e.message); 32 | return ''; 33 | } 34 | 35 | /** tar extract */ 36 | const basename = path.basename(config.webSimulator.tarUrl); 37 | const tar = path.join(config.webSimulator.storeDir, basename); 38 | try { 39 | logger.debug('正在解压模拟器资源'); 40 | await tarExtract(tar, config.webSimulator.storeDir); 41 | logger.debug('模拟器资源解压成功'); 42 | } catch(e) { 43 | logger.debug('模拟器资源解压失败', e); 44 | return ''; 45 | } finally { 46 | await clean(tar); 47 | } 48 | 49 | /** set global path */ 50 | setGlobalRc({ 51 | localWebSimulatorAssetsDir: config.webSimulator.storeDir, 52 | }); 53 | return config.webSimulator.storeDir; 54 | }; -------------------------------------------------------------------------------- /packages/cli/src/actions/proxy/proxy.ts: -------------------------------------------------------------------------------- 1 | // 本地代理 2 | // anyproxy --intercept --rule proxy.js 3 | import AnyProxy, { ProxyOptions, } from 'anyproxy'; 4 | import { IProxyParams, } from '../../lib/common/types'; 5 | import rule from './rule'; 6 | function closeProxy() { 7 | AnyProxy.utils.systemProxyMgr.disableGlobalProxy('https'); 8 | } 9 | export default function({ miniAppId, cwd, }: IProxyParams): void { 10 | // 系统全局代理 11 | AnyProxy.utils.systemProxyMgr.enableGlobalProxy('127.0.0.1', '8001', 'https'); 12 | 13 | const options: ProxyOptions = { 14 | port: 8001, 15 | rule: rule({ miniAppId, cwd, }), 16 | webInterface: { 17 | enable: true, 18 | webPort: 8002, 19 | }, 20 | throttle: 10000, 21 | forceProxyHttps: false, 22 | wsIntercept: false, // 不开启websocket代理 23 | silent: true, 24 | dangerouslyIgnoreUnauthorized: true, 25 | }; 26 | const proxyServer = new AnyProxy.ProxyServer(options); 27 | 28 | proxyServer.on('ready', () => { /* */ }); 29 | proxyServer.on('error', () => { 30 | closeProxy(); 31 | }); 32 | proxyServer.start(); 33 | 34 | process.on('exit', () => { 35 | try { 36 | closeProxy(); 37 | proxyServer && proxyServer.close(); 38 | } catch (e) { 39 | console.error(e); 40 | } 41 | process.exit(); 42 | }); 43 | 44 | //exit cause ctrl+c 45 | process.on('SIGINT', () => { 46 | try { 47 | closeProxy(); 48 | proxyServer && proxyServer.close(); 49 | } catch (e) { 50 | console.error(e); 51 | } 52 | process.exit(); 53 | }); 54 | 55 | process.on('uncaughtException', () => { 56 | try { 57 | closeProxy(); 58 | proxyServer && proxyServer.close(); 59 | } catch (e) { 60 | console.error(e); 61 | } 62 | process.exit(); 63 | }); 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /packages/opensdk/src/utils/getCompiler.ts: -------------------------------------------------------------------------------- 1 | import { MiniProgramCompiler, ConsoleLogger, } from 'minicode-compile-bundled'; 2 | import { spawn, } from 'child_process'; 3 | import { join, } from 'path'; 4 | import { existsSync, } from 'fs'; 5 | import download, { downloadPath, name, } from './downloadTinyCli'; 6 | 7 | async function getCompiler() { 8 | const compilerPath = join(downloadPath, name); 9 | if (!existsSync(compilerPath)) { 10 | // console.log('没有找到本地编译工具,请先运行 "miniu compiler download"'); 11 | console.log('没有找到本地编译工具,开始下载'); 12 | await download(); 13 | 14 | // if (!existsSync(compilerPath)) { 15 | // console.log('下载失败,请稍后重试'); 16 | // return; 17 | // } 18 | } 19 | 20 | return new MiniProgramCompiler({ 21 | logger: new ConsoleLogger(), 22 | addMinifishProxy: false, 23 | debugUtilsPath: require.resolve('builder-debug-utils'), 24 | delegateOptions: { 25 | tiny: { 26 | createProcess: (type: string, argv: string[], options: any) => { 27 | if (!options.env) { 28 | options.env = {}; 29 | } 30 | options.env.BUILD_PKG_ENGINE = 'tiny'; 31 | options.env.NODE_OPTIONS = undefined; 32 | options.stdio = ['pipe', 'pipe', 'pipe', 'ipc']; 33 | return spawn(compilerPath, [type, ...argv], options); 34 | }, 35 | }, 36 | ng: { 37 | createProcess: (argv: string[], options: any) => { 38 | if (!options.env) { 39 | options.env = {}; 40 | } 41 | options.env.BUILD_PKG_ENGINE = 'cube'; 42 | options.env.NODE_OPTIONS = undefined; 43 | options.stdio = ['pipe', 'pipe', 'pipe', 'ipc']; 44 | return spawn(compilerPath, argv, options); 45 | }, 46 | }, 47 | }, 48 | }); 49 | } 50 | 51 | export { getCompiler, MiniProgramCompiler }; -------------------------------------------------------------------------------- /packages/dingtalk-h5package-opensdk/src/cli/h5package.ts: -------------------------------------------------------------------------------- 1 | import { program, createCommand } from 'commander'; 2 | import { MiniAppOpenSDK } from '../index'; 3 | 4 | async function run(opts: { 5 | token: string; 6 | appId?: string; 7 | agentId?: string; 8 | input?: string; 9 | }) { 10 | const sdk = new MiniAppOpenSDK(); 11 | 12 | sdk.setConfig({ accessToken: opts.token }); 13 | 14 | const createResult = await sdk.createPackage({ 15 | appId: opts.appId, 16 | agentId: opts.agentId, 17 | input: opts.input, // 在此目录下的所有文件会作为H5离线包的静态资源,压缩上传并创建H5离线包 18 | }); 19 | 20 | await sdk.publishPackage({ 21 | appId: opts.appId, 22 | agentId: opts.agentId, 23 | version: createResult.version, 24 | }); 25 | } 26 | 27 | const inner = createCommand('inner'); 28 | const provider = createCommand('provider'); 29 | 30 | inner 31 | .summary('上传企业自建应用离线包资源') 32 | .requiredOption('-t, --accesstoken ', '开发者后台apiToken') 33 | .requiredOption('-a, --id ', '企业自建应用的agentId') 34 | .option('-d, --dir ', '要打包上传的离线包资源目录', './') 35 | .action(async (options) => { 36 | return run({ 37 | token: options.accesstoken, 38 | agentId: options.id, 39 | input: options.dir, 40 | }); 41 | }); 42 | 43 | provider 44 | .summary('上传第三方企业应用离线包资源') 45 | .requiredOption('-t, --accesstoken ', '开发者后台apiToken') 46 | .requiredOption('-a, --id ', '企业自建应用的agentId') 47 | .option('-d, --dir ', '要打包上传的离线包资源目录', './') 48 | .action(async (options) => { 49 | return run({ 50 | token: options.accesstoken, 51 | appId: options.id, 52 | input: options.dir, 53 | }); 54 | }); 55 | 56 | program 57 | .name('h5package') 58 | .version('0.0.1', '-v, --version', 'output the current version') 59 | .addCommand(inner) 60 | .addCommand(provider); 61 | 62 | program.parseAsync(); 63 | -------------------------------------------------------------------------------- /packages/opensdk/src/task/TaskBase.ts: -------------------------------------------------------------------------------- 1 | import { IOpenSDKConfig, OpenAPI, } from '../openapi'; 2 | import path from 'path'; 3 | import os from 'os'; 4 | import { IProjectConfig, } from '../config/projectConfig'; 5 | import { IAppConfig, } from '../config/appConfig'; 6 | import { getCompiler, MiniProgramCompiler, } from '../utils/getCompiler'; 7 | import { EBuildStatusText, } from '../types'; 8 | 9 | export interface ITaskContext { 10 | projectConfig: IProjectConfig; 11 | appConfig: IAppConfig; 12 | sdkConfig: IOpenSDKConfig; 13 | options: T; 14 | } 15 | 16 | export interface ITaskProgressMessage { 17 | status: EBuildStatusText; 18 | data: T; 19 | } 20 | 21 | export interface ITaskOptionBase { 22 | project: string; 23 | miniAppId: string; 24 | onProgressUpdate: (message: ITaskProgressMessage) => void; 25 | } 26 | 27 | export class TaskBase { 28 | protected projectDir: string; 29 | protected outputDir: string; 30 | protected openApi: OpenAPI; 31 | protected getCompiler: typeof getCompiler; 32 | readonly PREVIEW_PACKAGE_SIZE_LIMIT = 50 * 1024 * 1024; 33 | readonly BUILD_PACKAGE_SIZE_LIMIT = 20 * 1024 * 1024; 34 | readonly SUBPACKAGE_SINGLE_SIZE_LIMIT = 2 * 1024 * 1024; 35 | readonly SUBPACKAGE_WHOLE_SIZE_LIMIT = 10 * 1024 * 1024; 36 | 37 | constructor(readonly context: ITaskContext) { 38 | const { options, sdkConfig, } = context; 39 | 40 | this.projectDir = path.isAbsolute(options.project) ? options.project : path.join(process.cwd(), options.project); 41 | this.outputDir = path.join(os.tmpdir(), options.miniAppId, 'build'); 42 | this.openApi = new OpenAPI(sdkConfig); 43 | this.getCompiler = getCompiler; 44 | } 45 | 46 | print(message: ITaskProgressMessage) { 47 | if (this.context.options.onProgressUpdate) { 48 | this.context.options.onProgressUpdate(message); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /packages/cli/src/actions/proxy/index.ts: -------------------------------------------------------------------------------- 1 | import AnyProxy from 'anyproxy'; 2 | import { execSync, } from 'child_process'; 3 | import chalk from 'chalk'; 4 | import ora from 'ora'; 5 | import proxyHttp from './proxy'; 6 | const { green, } = chalk; 7 | import { IProxyParams, } from '../../lib/common/types'; 8 | 9 | /** 10 | * 代理本地代码 11 | * @param argv 12 | */ 13 | async function proxy({ miniAppId, cwd, }: IProxyParams): Promise { 14 | const spinner = ora('本地代理中...'); 15 | spinner.start(); 16 | 17 | // 检测证书是否存在 18 | if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) { 19 | AnyProxy.utils.certMgr.generateRootCA((error, keyPath) => { 20 | // let users to trust this CA before using proxy 21 | if (!error) { 22 | const certDir = require('path').dirname(keyPath); 23 | // eslint-disable-next-line no-console 24 | console.log('The cert is generated at', certDir); 25 | const isWin = /^win/.test(process.platform); 26 | if (isWin) { 27 | execSync('start .', { cwd: certDir, }); 28 | } else { 29 | execSync('open .', { cwd: certDir, }); 30 | } 31 | } else { 32 | console.error('error when generating rootCA', error); 33 | } 34 | }); 35 | spinner.stop(); 36 | return; 37 | } 38 | proxyHttp({ miniAppId, cwd, }); 39 | spinner.succeed(`${green('本地代理成功')}\n${green('手机端代理地址:' + getIPAdress() + ':8001')}`); 40 | } 41 | 42 | // 获取本机电脑IP 43 | function getIPAdress() { 44 | const interfaces = require('os').networkInterfaces(); 45 | for (const devName in interfaces) { 46 | const iface = interfaces[devName]; 47 | for (let i = 0; i < iface.length; i++) { 48 | const alias = iface[i]; 49 | if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { 50 | // console.log(alias.address); 51 | return alias.address; 52 | } 53 | } 54 | } 55 | } 56 | export default proxy; 57 | -------------------------------------------------------------------------------- /packages/generator/__tests__/generator.spec.ts: -------------------------------------------------------------------------------- 1 | import * as helpers from 'yeoman-test'; 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | 5 | describe('Init', ()=>{ 6 | const testDir = 'testResult'; 7 | const absoluteTestDir = path.join(__dirname, testDir); 8 | const outDir = 'custom-worktab-plugin'; 9 | 10 | beforeEach(()=>{ 11 | if(!fs.existsSync(absoluteTestDir)) { 12 | fs.mkdirSync(absoluteTestDir); 13 | } 14 | console.log(__dirname); 15 | process.chdir(__dirname); 16 | }); 17 | 18 | afterEach(()=>{ 19 | try { 20 | fs.rmdirSync(path.join(absoluteTestDir, outDir), { 21 | recursive: true, 22 | }); 23 | } catch(e) { 24 | console.error(e); 25 | } 26 | }); 27 | 28 | afterAll(()=>{ 29 | try { 30 | fs.rmdirSync(absoluteTestDir, { 31 | recursive: true, 32 | }); 33 | } catch(e) { 34 | console.error(e); 35 | } 36 | }); 37 | 38 | test('plugin-default-ts', ()=>{ 39 | return helpers 40 | .run(path.join(__dirname, '../generators/app')) 41 | .cd(absoluteTestDir) 42 | .withArguments([outDir]) 43 | .withPrompts({ appType: 'plugin', template: 'plugin_default', language: 'typescript', 'skip-install': true, }) 44 | .withLocalConfig({ lang: 'en', }) 45 | .then(function() { 46 | expect(fs.existsSync(path.join(absoluteTestDir, outDir))).toBe(true); 47 | }); 48 | }, 50 * 1000); 49 | 50 | test('plugin-default-js', ()=>{ 51 | return helpers 52 | .run(path.join(__dirname, '../generators/app')) 53 | .cd(absoluteTestDir) 54 | .withArguments([outDir]) 55 | .withPrompts({ appType: 'plugin', template: 'plugin_default', language: 'javascript', 'skip-install': true, }) 56 | .withLocalConfig({ lang: 'en', }) 57 | .then(function() { 58 | expect(fs.existsSync(path.join(absoluteTestDir, outDir))).toBe(true); 59 | }); 60 | }, 50 * 1000); 61 | }); -------------------------------------------------------------------------------- /packages/generator/generators/utils/getJson.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = this && this.__createBinding || (Object.create ? function (o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function () {return m[k];} }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | } : function (o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | }); 13 | var __setModuleDefault = this && this.__setModuleDefault || (Object.create ? function (o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | } : function (o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = this && this.__importStar || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | const fs = __importStar(require("fs")); 27 | /** 28 | * Load a json object 29 | * @param filePath 30 | */ 31 | function loadJSON(filePath, loose = true, fallbackValue = {}) { 32 | const isExist = fs.existsSync(filePath); 33 | if (isExist) { 34 | try { 35 | const content = fs.readFileSync(filePath, { encoding: 'utf-8' }); 36 | return JSON.parse(content); 37 | } 38 | catch (e) { 39 | if (loose) { 40 | console.error(e); 41 | return fallbackValue; 42 | } 43 | throw new Error(`Failed to load: ${filePath}`); 44 | } 45 | } else 46 | { 47 | if (loose) { 48 | return fallbackValue; 49 | } 50 | throw new Error(`Non-existed path: ${filePath}`); 51 | } 52 | } 53 | exports.default = loadJSON; -------------------------------------------------------------------------------- /packages/cli/src/commands/preview-inside/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 目前仅服务于钉钉inside小程序场景 3 | * 参考:https://yuque.antfin.com/docs/share/f5fdcc3d-e6ec-4f00-b598-0bda87a55aa0?# 4 | */ 5 | 6 | import CommandWrapper from '../../scheduler/command/commandWrapper'; 7 | import { ECommandName, } from '../../lib/common/types'; 8 | import commmandsConfig from '../commandsConfig'; 9 | import { IPreviewArgs, minidev, } from 'minidev'; 10 | 11 | export interface ICommandOptions { 12 | 'appId'?: string; 13 | 'autoPush'?: boolean; 14 | 'ignoreHttpDomainCheck'?: boolean; 15 | 'ignoreWebviewDomainCheck'?: boolean; 16 | 'clientType'?: string; 17 | page?: string; 18 | 'pageQuery'?: string; 19 | query?: string; 20 | scene?: string; 21 | 'bundleId'?: string; 22 | } 23 | 24 | export default CommandWrapper({ 25 | name: ECommandName.previewInside, 26 | registerCommand(ctx) { 27 | return { 28 | ...commmandsConfig['preview-inside'], 29 | action: async (options) => { 30 | const previewParams: IPreviewArgs = { 31 | appId: options.appId, 32 | autoPush: options.autoPush || false, 33 | clientType: options.clientType || 'alipay', 34 | ignoreHttpDomainCheck: options.ignoreHttpDomainCheck || true, 35 | ignoreWebViewDomainCheck: options.ignoreWebviewDomainCheck || true, 36 | page: options['page'], 37 | pageQuery: options.pageQuery, 38 | query: options['query'], 39 | scene: options['scene'], 40 | bundleId: options.bundleId || 'com.alibaba.dingtalk', 41 | project: options.cwd, 42 | }; 43 | 44 | try { 45 | const { qrcodeUrl, version, } = await minidev.preview(previewParams); 46 | 47 | // 生成的临时版本号,对于插件项目可以用于宿主进行插件联调 48 | ctx.logger.info('二维码地址', qrcodeUrl); 49 | ctx.logger.info('预览临时版本号', version); 50 | } catch(e) { 51 | ctx.logger.error(e.message); 52 | } 53 | }, 54 | }; 55 | }, 56 | }); -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getValidateRc.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getRuleCheckInfos, 3 | getPermissionPoint, 4 | RuleCheckInfos, 5 | PermissionPoint, 6 | } from '../sevice/index'; 7 | import { IRcJson, } from 'dingtalk-worktab-plugin-script/dist/src/types/index'; 8 | import { logger, } from '../cli-shared-utils/lib/logger'; 9 | import getMonitor from '../cli-shared-utils/lib/monitor/framework-monitor'; 10 | import config from '../common/config'; 11 | 12 | const monitor = getMonitor(config.yuyanId); 13 | 14 | export default (miniAppId: string, token: string, isPcPlugin: boolean): Promise => { 15 | const ruleCheckInfosPromise = getRuleCheckInfos(miniAppId, token); 16 | const getPermissionPointPromise = getPermissionPoint(miniAppId, token); 17 | 18 | return Promise.all([ruleCheckInfosPromise, getPermissionPointPromise]) 19 | .then((res) => { 20 | const { 21 | packCode, 22 | pluginRuleCheckDetail, 23 | } = res[0].data as RuleCheckInfos; 24 | const { 25 | permissionPointList, 26 | } = res[1].data as PermissionPoint; 27 | 28 | // 拉取到rc后,需要inject supportEnvironments以及apiList 29 | // supportEnvironments代表工作台组件支持的运行环境,如["mobile", "pc"] 30 | // apiList代表工作台组件具有的权限点 31 | // 这部分逻辑和plugin-valid项目中的处理保持一致 32 | const supportEnvironment = ['mobile']; 33 | if (isPcPlugin) supportEnvironment.push('pc'); 34 | 35 | const parsedConfig = JSON.parse(pluginRuleCheckDetail); 36 | const targetConfig = parsedConfig[packCode] || {}; 37 | const reg = new RegExp(`"\\$\{${packCode}_apiList}"`); 38 | 39 | targetConfig.supportEnvironment = supportEnvironment; 40 | const rcJson = JSON.stringify(targetConfig).replace(reg, JSON.stringify(permissionPointList)); 41 | 42 | logger.debug('validate rc', rcJson); 43 | return JSON.parse(rcJson); 44 | }).catch(e => { 45 | logger.debug('getValidateRc fail', e); 46 | monitor.logJSError(new Error(e)); 47 | return null; 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /packages/dingtalk-h5package-opensdk/src/OpenGateway.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance, } from 'axios'; 2 | import assert from 'assert'; 3 | 4 | export interface IGatewayOptions { 5 | host?: string; 6 | accessToken: string; 7 | } 8 | 9 | export type IGatewaySuccessResult = T; 10 | 11 | export interface IGatewayErrorResult { 12 | RequestId: string; 13 | HostId: string; 14 | Code: string; 15 | Message: string; 16 | Recommend: string; 17 | } 18 | 19 | export class OpenGateWay { 20 | private client: AxiosInstance; 21 | private accessToken?: string; 22 | 23 | constructor(private options: IGatewayOptions) { 24 | if (!options.host) { 25 | options.host = 'https://api.dingtalk.com'; 26 | } 27 | 28 | assert(options.accessToken, 'accessToken is required'); 29 | 30 | this.client = axios.create({ 31 | baseURL: options.host, 32 | timeout: 10000, 33 | responseType: 'json', 34 | headers: { 35 | 'x-acs-dingtalk-access-token': options.accessToken, 36 | }, 37 | }); 38 | } 39 | 40 | public request( 41 | method: 'GET' | 'POST', 42 | pathname: string, 43 | query = {}, 44 | data = {} 45 | ): Promise { 46 | return this.client 47 | .request | IGatewayErrorResult>({ 48 | method, 49 | url: pathname, 50 | params: query, 51 | data, 52 | }) 53 | .then((response) => { 54 | if (response.status !== 200) { 55 | throw new Error(`[${response.status}]${response.statusText}`); 56 | } 57 | const responseData = response.data; 58 | if ((responseData as IGatewayErrorResult).Code) { 59 | throw new Error( 60 | `[${(responseData as IGatewayErrorResult).Code}]${ 61 | (responseData as IGatewayErrorResult).Message 62 | }` 63 | ); 64 | } else { 65 | return responseData as R; 66 | } 67 | }) 68 | .catch((e) => { 69 | return Promise.reject(e); 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/opensdk/src/utils/downloadTinyCli.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | import download from './download'; 4 | import os from 'os'; 5 | 6 | const config = { 7 | version: 'tiny6.3.0_cube0.49.18', 8 | darwin: 'https://gw.alipayobjects.com/os/bmw-prod/1588a3b3-5a5f-48a9-a11c-88ada68f3107.tgz', 9 | win32: 'https://gw.alipayobjects.com/os/bmw-prod/da38f87f-4e51-4eb5-a6e2-ae3223ec89fe.tgz', 10 | win7: 'https://gw.alipayobjects.com/os/bmw-prod/503e2143-c58b-4ac9-aae6-fb97637488ec.tgz', 11 | linux: 'https://gw.alipayobjects.com/os/bmw-prod/6026c82a-bd10-4eaa-9e06-3c5fd22c4c15.tgz', 12 | }; 13 | 14 | const platform = os.platform(); 15 | 16 | const downloadPath = path.join(os.homedir(), '.dd', 'compiler', config.version); 17 | let name = 'mini-pkg-builder'; 18 | 19 | let downloadURL: string; 20 | 21 | if (platform === 'win32') { 22 | if (!os.release().startsWith('10.0')) { 23 | downloadURL = config.win7; 24 | name = 'mini-pkg-builder-win7.exe'; 25 | } else { 26 | downloadURL = config.win32; 27 | name = 'mini-pkg-builder.exe'; 28 | } 29 | } else { 30 | downloadURL = config[platform]; 31 | } 32 | 33 | export { config, downloadPath, name, downloadURL }; 34 | 35 | 36 | export default async function () { 37 | const successFilePath = path.join(downloadPath, 'dd_download_success'); 38 | if (downloadURL) { 39 | if (!process.env.FORCE_DOWNLOAD && fs.existsSync(successFilePath)) { 40 | console.log(`compiler exists: ${config.version} `); 41 | } else { 42 | await fs.remove(downloadPath); 43 | await fs.ensureDir(downloadPath); 44 | try { 45 | await download(downloadURL, downloadPath); 46 | await fs.writeFile(successFilePath, ''); 47 | console.log('下载完成'); 48 | } catch (err) { 49 | console.log('下载失败'); 50 | await fs.remove(downloadPath); 51 | throw err; 52 | } 53 | } 54 | } else { 55 | const list = Object.keys(config); 56 | console.log(`当前系统为 ${process.platform} dd 支持 ${list.join()},部分功能可能会无法使用`); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/opensdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dingtalk-miniapp-opensdk", 3 | "version": "1.0.8", 4 | "description": "dingtalk miniprogram open sdk", 5 | "main": "./dist/index.js", 6 | "scripts": { 7 | "build": "tsc ", 8 | "lint": "eslint src", 9 | "test": "jest -i", 10 | "prepublishOnly": "npm run lint && npm run build" 11 | }, 12 | "publishConfig": { 13 | "registry": "https://registry.npmjs.org/" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "ali-oss": "^6.10.0", 19 | "axios": "^0.20.0", 20 | "builder-debug-utils": "3.0.2", 21 | "chalk": "^4.1.1", 22 | "console-png": "^1.2.1", 23 | "fs-extra": "^9.0.1", 24 | "got": "^11.8.2", 25 | "md5-file": "^5.0.0", 26 | "minicode-compile-bundled": "2.11.26", 27 | "open": "^8.2.1", 28 | "progress": "^2.0.3", 29 | "qr-image": "^3.2.0", 30 | "qrcode": "^1.4.4", 31 | "qs": "^6.9.4", 32 | "semver": "^7.3.2", 33 | "superagent": "^6.1.0", 34 | "tar": "^6.1.0" 35 | }, 36 | "devDependencies": { 37 | "@babel/core": "^7.11.6", 38 | "@babel/plugin-external-helpers": "^7.10.4", 39 | "@babel/plugin-proposal-class-properties": "^7.10.4", 40 | "@babel/plugin-proposal-object-rest-spread": "^7.11.0", 41 | "@babel/plugin-proposal-optional-chaining": "^7.11.0", 42 | "@babel/plugin-transform-runtime": "^7.11.5", 43 | "@babel/preset-env": "^7.11.5", 44 | "@babel/preset-typescript": "^7.10.4", 45 | "@types/ali-oss": "^6.0.5", 46 | "@types/fs-extra": "^9.0.1", 47 | "@types/got": "^9.6.11", 48 | "@types/jest": "^26.0.14", 49 | "@types/node": "^14.11.2", 50 | "@types/qr-image": "^3.2.3", 51 | "@types/qs": "^6.9.5", 52 | "@types/semver": "^7.3.4", 53 | "@types/superagent": "^4.1.11", 54 | "@typescript-eslint/eslint-plugin": "^4.3.0", 55 | "@typescript-eslint/parser": "^4.3.0", 56 | "babel-jest": "^26.3.0", 57 | "eslint": "^7.10.0", 58 | "jest": "^26.4.2", 59 | "typescript": "^4.3.2" 60 | }, 61 | "files": [ 62 | "dist" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /packages/opensdk/src/index.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { findAppConfig, } from './config/appConfig'; 3 | import { findProjectConfig, } from './config/projectConfig'; 4 | import { IOpenSDKConfig, } from './openapi'; 5 | import { BuildTask, IBuildTaskParams, } from './task/BuildTask'; 6 | import { IPreviewBuildOptions, PreviewBuildTask, } from './task/PreviewBuildTasK'; 7 | import { parsePath, } from './utils'; 8 | 9 | class MiniAppOpenSDK { 10 | private sdkConfig?: IOpenSDKConfig; 11 | 12 | setConfig(sdkConfig: IOpenSDKConfig) { 13 | this.sdkConfig = sdkConfig; 14 | } 15 | 16 | previewBuild(options: IPreviewBuildOptions) { 17 | if (!this.sdkConfig) { 18 | throw new Error('sdk must be config'); 19 | } 20 | 21 | assert(options.project, 'project must be config'); 22 | assert(options.miniAppId, 'miniAppId must be config'); 23 | 24 | const project = parsePath(options.project); 25 | const projectConfig = findProjectConfig(project); 26 | const appConfig = findAppConfig(project, projectConfig); 27 | 28 | const task = new PreviewBuildTask({ 29 | projectConfig, 30 | appConfig, 31 | sdkConfig: this.sdkConfig, 32 | options: { 33 | ...options, 34 | project, 35 | }, 36 | }); 37 | 38 | return task.start(); 39 | } 40 | 41 | miniUpload(options: IBuildTaskParams) { 42 | if (!this.sdkConfig) { 43 | throw new Error('sdk must be config'); 44 | } 45 | 46 | assert(options.project, 'project must be config'); 47 | assert(options.miniAppId, 'miniAppId must be config'); 48 | 49 | const project = parsePath(options.project); 50 | const projectConfig = findProjectConfig(project); 51 | const appConfig = findAppConfig(project, projectConfig); 52 | 53 | const task = new BuildTask({ 54 | sdkConfig: this.sdkConfig, 55 | projectConfig, 56 | appConfig, 57 | options: { 58 | ...options, 59 | project, 60 | }, 61 | }); 62 | 63 | return task.start(); 64 | } 65 | } 66 | 67 | export const sdk = new MiniAppOpenSDK(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DingTalk-Design-CLI", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/cli", 6 | "packages/generator", 7 | "packages/opensdk", 8 | "packages/dingtalk-h5package-opensdk" 9 | ], 10 | "version": "0.0.0", 11 | "scripts": { 12 | "changelog": "lerna-changelog", 13 | "commit": "git cz", 14 | "test": "node scripts/test.js", 15 | "build": "node scripts/build.js", 16 | "release": "npm run build && node scripts/release.js", 17 | "version": "node scripts/genChangelog.js && git add CHANGELOG.md", 18 | "bootstrap": "lerna bootstrap", 19 | "lint": "eslint packages --ext .ts", 20 | "lint:fix": "eslint packages --fix --ext .ts" 21 | }, 22 | "devDependencies": { 23 | "@types/jest": "^26.0.20", 24 | "@types/node": "^14.14.22", 25 | "@typescript-eslint/eslint-plugin": "^4.4.1", 26 | "@typescript-eslint/parser": "^4.4.1", 27 | "commitizen": "^4.2.3", 28 | "cz-conventional-changelog": "^3.3.0", 29 | "eslint": "^7.11.0", 30 | "execa": "^5.0.0", 31 | "husky": "^4.3.8", 32 | "jest": "^26.6.3", 33 | "lerna": "^3.22.1", 34 | "lerna-changelog": "^1.0.1", 35 | "minimist": "^1.2.5", 36 | "ts-jest": "^26.4.4", 37 | "typescript": "^4.1.3" 38 | }, 39 | "engines": { 40 | "node": ">=12.15.x" 41 | }, 42 | "husky": { 43 | "hooks": { 44 | "pre-commit": "npm run lint", 45 | "pre-push": "npm run test", 46 | "commit-msg": "npx validate-commit-msg" 47 | } 48 | }, 49 | "config": { 50 | "validate-commit-msg": { 51 | "helpMessage": "\nPlease fix your commit message (and consider using http://npm.im/commitizen)\n", 52 | "types": [ 53 | "feat", 54 | "fix", 55 | "docs", 56 | "style", 57 | "refactor", 58 | "perf", 59 | "test", 60 | "chore", 61 | "revert", 62 | "build", 63 | "release", 64 | "custom", 65 | "ci" 66 | ], 67 | "warnOnFail": false, 68 | "autoFix": true 69 | }, 70 | "commitizen": { 71 | "path": "./node_modules/cz-conventional-changelog" 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/generator/generators/utils/getGlobalInstallCommand.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = this && this.__createBinding || (Object.create ? function (o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function () {return m[k];} }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | } : function (o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | }); 13 | var __setModuleDefault = this && this.__setModuleDefault || (Object.create ? function (o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | } : function (o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = this && this.__importStar || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | const execa = __importStar(require("execa")); 27 | const cli_shared_utils_1 = require("./cli-shared-utils"); 28 | /** 29 | * when use npm link to debug, would get nothing because 30 | * `__dirname` is wrong 31 | */ 32 | function getGlobalInstallCommand() { 33 | if ((0, cli_shared_utils_1.hasYarn)()) { 34 | const { stdout: yarnGlobalDir } = execa.sync('yarn', ['global', 'dir']); 35 | if (__dirname.includes(yarnGlobalDir)) { 36 | return 'yarn global add'; 37 | } 38 | } 39 | if ((0, cli_shared_utils_1.hasPnpm3OrLater)()) { 40 | const { stdout: pnpmGlobalPrefix } = execa.sync('pnpm', ['config', 'get', 'prefix']); 41 | if (__dirname.includes(pnpmGlobalPrefix) && __dirname.includes('pnpm-global')) { 42 | return 'pnpm i -g'; 43 | } 44 | } 45 | const { stdout: npmGlobalPrefix } = execa.sync('npm', ['config', 'get', 'prefix']); 46 | if (__dirname.includes(npmGlobalPrefix)) { 47 | return 'npm i -g'; 48 | } 49 | } 50 | exports.default = getGlobalInstallCommand; -------------------------------------------------------------------------------- /packages/cli/src/actions/simulatorProxyServer.ts: -------------------------------------------------------------------------------- 1 | import { spawn, } from 'child_process'; 2 | 3 | import config from '../lib/common/config'; 4 | import { choosePort, } from '../lib/cli-shared-utils/lib/network'; 5 | import { logger, } from '../lib/cli-shared-utils/lib/logger'; 6 | import getMonitor from '../lib/cli-shared-utils/lib/monitor/framework-monitor'; 7 | import { isWindows, } from '../lib/cli-shared-utils'; 8 | 9 | 10 | const monitor = getMonitor(config.yuyanId); 11 | 12 | interface IOpts { 13 | /** 鉴权代理服务地址 */ 14 | proxyServerUrl?: string; 15 | /** 鉴权代理服务脚本地址 */ 16 | proxyServerScript?: string; 17 | } 18 | 19 | interface IResponse { 20 | proxyServerUrl: string; 21 | proxyServerPort: number; 22 | } 23 | 24 | const DEFAULT_HOST = '0.0.0.0'; 25 | 26 | export default async (opts: IOpts): Promise => { 27 | 28 | let proxyServerUrl = ''; 29 | let proxyServerPort = config.miniAppWebSimulator.proxyServerPort; 30 | 31 | if (opts.proxyServerUrl) { 32 | proxyServerUrl = opts.proxyServerUrl; 33 | } else { 34 | 35 | /** 36 | * 模拟器本地代理服务挂载 37 | */ 38 | try { 39 | proxyServerPort = await choosePort(DEFAULT_HOST, proxyServerPort); 40 | } catch(e) { 41 | logger.error('模拟器本地服务器代理启动失败', e); 42 | monitor.logJSError(e); 43 | return; 44 | } 45 | 46 | // 启动本地代理服务器 47 | if (!opts.proxyServerScript) { 48 | logger.error('模拟器本地服务器代理启动失败,请配置模拟器本地服务器代理服务脚本地址'); 49 | return; 50 | } 51 | const proxyServerScript = opts.proxyServerScript; 52 | const appType = 'miniApp'; 53 | const proxyServerCp = spawn( 54 | 'node', 55 | [ 56 | proxyServerScript, 57 | appType, 58 | ], 59 | { 60 | stdio: 'ignore', 61 | env: { 62 | ...process.env, 63 | PORT: String(proxyServerPort), 64 | }, 65 | shell: isWindows, 66 | } 67 | ); 68 | proxyServerCp.on('error', (err) => { 69 | logger.error('本地代理服务器启动失败', err); 70 | monitor.logJSError(err); 71 | }); 72 | proxyServerUrl = `http://127.0.0.1:${proxyServerPort}`; 73 | } 74 | 75 | return { 76 | proxyServerUrl, 77 | proxyServerPort, 78 | }; 79 | }; 80 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/components/view-container/index.css: -------------------------------------------------------------------------------- 1 | .card-default-height { 2 | height: 4rem; 3 | } 4 | 5 | .transform-layer { 6 | transform: rotate(0); 7 | } 8 | 9 | .columns-container-view-container.image-mode-scaleToFill{ 10 | background-size: 100% 100%; 11 | } 12 | .columns-container-view-container.image-mode-aspectFill{ 13 | background-size: cover; 14 | } 15 | .columns-container-view-container.image-mode-aspectFit{ 16 | background-size: contain; 17 | } 18 | .columns-container-view-container.image-mode-widthAutoFit{ 19 | background-size: auto 100%; 20 | } 21 | .columns-container-view-container.image-mode-heightAutoFit{ 22 | background-size: 100%; 23 | } 24 | 25 | .system-groups-view { 26 | background-position: center center; 27 | } 28 | 29 | .system-groups-view.image-mode-scaleToFill { 30 | background-size: 100% 100%; 31 | } 32 | .system-groups-view.image-mode-aspectFill { 33 | background-size: cover; 34 | } 35 | .system-groups-view.image-mode-aspectFit { 36 | background-size: contain; 37 | } 38 | .system-groups-view.image-mode-widthAutoFit { 39 | background-size: auto 100%; 40 | } 41 | .system-groups-view.image-mode-heightAutoFit { 42 | background-size: 100%; 43 | } 44 | 45 | .box-shadow { 46 | box-shadow: 0 0.16rem 0.48rem rgba(10, 30, 65, 0.12); 47 | } 48 | 49 | .view-container { 50 | position: relative; 51 | margin-bottom: 0; 52 | background-color: transparent; 53 | background-repeat: no-repeat; 54 | background-size: 100% auto; 55 | background-position: 0 0; 56 | overflow: hidden; 57 | } 58 | 59 | .no-full-width { 60 | margin: 0 0.32rem; 61 | border-style: solid; 62 | border-color: rgba(25, 31, 37, 0.08); 63 | border-width: 0; 64 | } 65 | 66 | .no-full-width.border-radius { 67 | border-radius: 0.16rem; 68 | } 69 | 70 | .custom-widget-container.dark .loading { 71 | background-image: url(https://gw.alicdn.com/tfs/TB1shvXReL2gK0jSZFmXXc7iXXa-654-344.png); 72 | background-size: 100% 100%; 73 | } 74 | 75 | .custom-widget-container .loading { 76 | background-image: url(https://gw.alicdn.com/tfs/TB1s9o8Q7L0gK0jSZFtXXXQCXXa-654-344.png); 77 | background-size: 100% 100%; 78 | } 79 | .custom-widget-container .default-margin { 80 | margin: 0 0.32rem; 81 | } -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getH5ProBinPath.ts: -------------------------------------------------------------------------------- 1 | import config from '../common/config'; 2 | import * as os from 'os'; 3 | import download from 'download'; 4 | import { logger, } from '../cli-shared-utils/lib/logger'; 5 | import * as path from 'path'; 6 | import { getGlobalRc, setGlobalRcItem, } from './globalRc'; 7 | import clean from './clean'; 8 | import { failSpinner, logWithSpinner, successSpinner, } from '../cli-shared-utils/lib/spinner'; 9 | import tarExtract from './tarExtract'; 10 | 11 | export default async (): Promise => { 12 | const platform = os.platform(); 13 | const h5ProConfig = config.h5pro; 14 | const platformConfigs = h5ProConfig.platforms; 15 | const h5ProCurPlatformConfig = platformConfigs[platform as keyof typeof platformConfigs]; 16 | const supportPlatform = Object.keys(h5ProConfig.platforms); 17 | if (supportPlatform.indexOf(platform) === -1) { 18 | logger.error('当前平台不支持编译pc工作台组件,支持的平台', supportPlatform); 19 | return ''; 20 | } 21 | 22 | const globalRc = getGlobalRc(); 23 | if (globalRc && globalRc.h5ProExecPath) { 24 | return globalRc.h5ProExecPath; 25 | } 26 | 27 | const binDownloadPath = h5ProCurPlatformConfig.binDownloadUrl; 28 | const tarStorePath = os.tmpdir(); 29 | /** builder extract */ 30 | const basename = path.basename(binDownloadPath); 31 | const tar = path.join(tarStorePath, basename); 32 | 33 | /** builder download */ 34 | 35 | try { 36 | logWithSpinner('本地没找到pc工作台组件构建器,正在下载'); 37 | await download(binDownloadPath, tar); 38 | successSpinner('pc工作台组件构建器下载成功'); 39 | } catch(e) { 40 | failSpinner('pc工作台组件构建器下载失败'); 41 | logger.error(e.message); 42 | return ''; 43 | } 44 | 45 | try { 46 | logger.debug('正在提取h5pro bin文件'); 47 | await tarExtract(path.join(tar, h5ProCurPlatformConfig.binName), h5ProConfig.binStoreDir); 48 | logger.debug('h5pro bin 提取成功'); 49 | } catch(e) { 50 | logger.debug('h5pro bin 提取失败', e); 51 | return ''; 52 | } finally { 53 | await clean(tar); 54 | } 55 | 56 | /** set global builder bin path */ 57 | const binExecPath = path.join(h5ProConfig.binStoreDir, h5ProCurPlatformConfig.binExtractPath); 58 | setGlobalRcItem('h5ProExecPath', binExecPath); 59 | return binExecPath; 60 | }; -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.11.0-beta.1 (2021-04-23) 2 | 3 | #### :rocket: New Feature 4 | * `cli`, `generator` 5 | * [#14](https://github.com/open-dingtalk/dingtalk-design-cli/pull/14) Feature/plugin preview pc ([@lou1swu](https://github.com/lou1swu)) 6 | 7 | #### Committers: 1 8 | - louiswu ([@lou1swu](https://github.com/lou1swu)) 9 | 10 | 11 | 12 | ## 0.10.0 (2021-04-22) 13 | 14 | #### :rocket: New Feature 15 | * `cli` 16 | * [#13](https://github.com/open-dingtalk/dingtalk-design-cli/pull/13) feat: change packageName "DingTalk Fe CLI" to "DingTalk Design CLI" ([@lou1swu](https://github.com/lou1swu)) 17 | 18 | #### Committers: 1 19 | - louiswu ([@lou1swu](https://github.com/lou1swu)) 20 | 21 | 22 | 23 | 24 | 25 | ## 0.7.0 (2021-03-24) 26 | 27 | #### :rocket: New Feature 28 | * `cli`, `generator` 29 | * [#10](https://github.com/open-dingtalk/dingtalk-design-cli/pull/10) Feature/multi app type ([@lou1swu](https://github.com/lou1swu)) 30 | 31 | #### :bug: Bug Fix 32 | * `cli`, `generator` 33 | * [#10](https://github.com/open-dingtalk/dingtalk-design-cli/pull/10) Feature/multi app type ([@lou1swu](https://github.com/lou1swu)) 34 | 35 | #### Committers: 1 36 | - louiswu ([@lou1swu](https://github.com/lou1swu)) 37 | 38 | 39 | 40 | ## 0.6.0 (2021-03-23) 41 | 42 | #### :bug: Bug Fix 43 | * `cli`, `generator` 44 | * [#9](https://github.com/open-dingtalk/dingtalk-design-cli/pull/9) fix(cli): add extra command preventing to be overrided by the system … ([@lou1swu](https://github.com/lou1swu)) 45 | 46 | #### Committers: 1 47 | - louiswu ([@lou1swu](https://github.com/lou1swu)) 48 | 49 | 50 | 51 | ## 0.5.0 (2021-03-03) 52 | 53 | #### :bug: Bug Fix 54 | * `generator` 55 | * [#8](https://github.com/open-dingtalk/dingtalk-design-cli/pull/8) fix(generators): fix copy when exec in windows ([@lou1swu](https://github.com/lou1swu)) 56 | 57 | #### Committers: 1 58 | - louiswu ([@lou1swu](https://github.com/lou1swu)) 59 | 60 | 61 | 62 | ## 0.4.0 (2021-03-03) 63 | 64 | #### :bug: Bug Fix 65 | * `cli` 66 | * [#7](https://github.com/open-dingtalk/dingtalk-design-cli/pull/7) fix(cli): fix generatorNamespace when env.run ([@lou1swu](https://github.com/lou1swu)) 67 | 68 | #### Committers: 1 69 | - louiswu ([@lou1swu](https://github.com/lou1swu)) 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /packages/cli/src/lib/cli-shared-utils/lib/watcher.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | import chokidar, { FSWatcher, } from 'chokidar'; 5 | import { debounce, } from 'lodash'; 6 | import { logger, } from './logger'; 7 | 8 | export interface IWatcherOptions { 9 | cwd: string; 10 | files: string[]; 11 | } 12 | 13 | export interface IWatcherWatchOptions { 14 | debounceDuration?: number; 15 | } 16 | 17 | export class Watcher { 18 | inited: boolean; 19 | 20 | watcher: FSWatcher; 21 | 22 | watchedFiles: Array<[string[], (filePath: string) => void]>; 23 | 24 | constructor( 25 | readonly opts: IWatcherOptions, 26 | ) { 27 | this.inited = false; 28 | this.watcher = null as unknown as FSWatcher; 29 | this.watchedFiles = []; 30 | } 31 | 32 | /** 33 | * 初始化 34 | * 35 | * @description 需要由调用方自行初始化 36 | */ 37 | init(): void { 38 | if (this.inited) { 39 | return; 40 | } 41 | this.inited = true; 42 | this.watcher = chokidar.watch([this.opts.cwd, ...(this.opts.files || [])], { 43 | ignored: /(\/node_modules\/|\/.git\/)/, 44 | ignoreInitial: true, 45 | }); 46 | this.watchedFiles = []; 47 | /** 48 | * TODO consider rest events. 49 | */ 50 | this.watcher.on('change', (filePath: string) => { 51 | logger.debug('[watcher]', filePath, 'changed'); 52 | for (const [files, callback] of this.watchedFiles) { 53 | if (files.includes(filePath)) { 54 | callback(filePath); 55 | } 56 | } 57 | }); 58 | } 59 | 60 | /** 61 | * 监听 62 | * 63 | * @param {string[]} files 64 | * @param {(filePath: string) => void} callback 65 | */ 66 | watch(files: string[], callback: (filePath: string) => void, options: IWatcherWatchOptions = {}): void { 67 | let cb = callback; 68 | if (options?.debounceDuration) { 69 | cb = debounce(cb, options.debounceDuration); 70 | } 71 | if (Array.isArray(files)) { 72 | this.watchedFiles.push([files, cb]); 73 | } else { 74 | throw new Error('`files` should be string array'); 75 | } 76 | } 77 | 78 | /** 79 | * 关闭监听 80 | */ 81 | close(): void { 82 | this.watcher.close(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .changelog 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | 107 | # lock 108 | package-lock.json 109 | 110 | # ide 111 | .vscode 112 | .DS_Store 113 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /packages/cli/src/commands/remote-debug/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 目前仅服务于钉钉inside小程序场景 3 | * 4 | * 参考:https://yuque.antfin.com/docs/share/f5fdcc3d-e6ec-4f00-b598-0bda87a55aa0?# 5 | */ 6 | 7 | import CommandWrapper from '../../scheduler/command/commandWrapper'; 8 | import { ECommandName, } from '../../lib/common/types'; 9 | import commandsConfig from '../commandsConfig'; 10 | import { IRemoteDebugArgs, minidev, } from 'minidev'; 11 | 12 | interface ICommandOptions { 13 | 'appId': string; 14 | 'autoPush'?: boolean; 15 | 'ignoreHttpDomainCheck'?: boolean; 16 | 'ignoreWebviewDomainCheck'?: boolean; 17 | 'clientType'?: string; 18 | page?: string; 19 | 'pageQuery'?: string; 20 | query?: string; 21 | scene?: string; 22 | 'bundleId'?: string; 23 | } 24 | 25 | export default CommandWrapper({ 26 | name: ECommandName.remoteDebug, 27 | registerCommand(ctx) { 28 | return { 29 | ...commandsConfig['remote-debug'], 30 | action: async (options) => { 31 | ctx.logger.debug('cli options', options); 32 | 33 | const debugParams: IRemoteDebugArgs = { 34 | appId: options.appId, 35 | project: options.cwd, 36 | autoPush: options.autoPush || false, 37 | clientType: options.clientType || 'alipay', 38 | ignoreHttpDomainCheck: options.ignoreHttpDomainCheck || true, 39 | ignoreWebViewDomainCheck: options.ignoreWebviewDomainCheck || true, 40 | page: options['page'], 41 | pageQuery: options.pageQuery, 42 | query: options['query'], 43 | scene: options['scene'], 44 | bundleId: options.bundleId || 'com.alibaba.dingtalk', 45 | autoOpenDevtool: true, 46 | }; 47 | 48 | ctx.logger.debug('remoteDebugParams', debugParams); 49 | 50 | try { 51 | const { 52 | qrcodeUrl, 53 | debugUrl, 54 | } = await minidev.remoteDebug(debugParams); 55 | 56 | ctx.logger.debug('qrcodeUrl', qrcodeUrl); 57 | 58 | ctx.logger.debug('debugUrl', debugUrl); 59 | } catch(e) { 60 | ctx.logger.error(e.message); 61 | 62 | /** 63 | * remote-debug进程没有自动退出 64 | */ 65 | process.exit(); 66 | } 67 | }, 68 | }; 69 | }, 70 | }); -------------------------------------------------------------------------------- /packages/cli/src/commands/ngrok/index.ts: -------------------------------------------------------------------------------- 1 | import CommandWrapper from '../../scheduler/command/commandWrapper'; 2 | import { ECommandName, } from '../../lib/common/types'; 3 | import lint from '../../actions/lint'; 4 | import commmandsConfig from '../commandsConfig'; 5 | import config from '../../lib/common/config'; 6 | import getNgrokBinPath from '../../lib/util/getNgrokBinPath'; 7 | import { isWindows, } from '../../lib/cli-shared-utils'; 8 | import { spawn, } from 'child_process'; 9 | import path from 'path'; 10 | import { logger, } from '../../lib/cli-shared-utils/lib/logger'; 11 | import getMonitor from '../../lib/cli-shared-utils/lib/monitor/framework-monitor'; 12 | 13 | interface ICommandOptions { 14 | subdomain: string; 15 | config: string; 16 | port: string; 17 | } 18 | 19 | const monitor = getMonitor(config.yuyanId); 20 | 21 | export default CommandWrapper({ 22 | name: ECommandName.ngrok, 23 | registerCommand(ctx) { 24 | return { 25 | ...commmandsConfig.ngrok, 26 | action: async (options) => { 27 | const { 28 | cwd, 29 | dtdConfig, 30 | } = ctx; 31 | 32 | const { 33 | subdomain, 34 | port, 35 | config, 36 | } = options; 37 | 38 | const ngrokBinPath = await getNgrokBinPath(); 39 | ctx.logger.debug('ngrokBinPath', ngrokBinPath); 40 | 41 | const ngrokCp = spawn( 42 | ngrokBinPath, 43 | [ 44 | `-config=${config || path.join(__dirname, '../../../assets/ding.cfg')}`, 45 | `-subdomain=${subdomain}`, 46 | port, 47 | ], 48 | { 49 | stdio: 'pipe', 50 | cwd, 51 | env: process.env, 52 | shell: isWindows, 53 | } 54 | ); 55 | ngrokCp.stdout.pipe(process.stdout); 56 | ngrokCp.stderr.pipe(process.stderr); 57 | // ngrokCp.stdout.on('data', (chunk)=>{}); 58 | 59 | ngrokCp.on('error', (err) => { 60 | logger.error('钉钉内网穿透工具启动失败', err.message); 61 | ngrokCp.kill(); 62 | monitor.logJSError(err); 63 | }); 64 | 65 | if (isWindows) { 66 | ctx.logger.success('Forwarding', `http://${subdomain}.vaiwan.cn -> http://127.0.0.1:${port}`); 67 | } 68 | }, 69 | }; 70 | }, 71 | }); -------------------------------------------------------------------------------- /packages/generator/generators/utils/store.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = this && this.__createBinding || (Object.create ? function (o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function () {return m[k];} }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | } : function (o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | }); 13 | var __setModuleDefault = this && this.__setModuleDefault || (Object.create ? function (o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | } : function (o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = this && this.__importStar || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | const fs = __importStar(require("fs")); 27 | const config_1 = require("../common/config"); 28 | const DEFAULT_STORE_PATH = config_1.RC_PATH; 29 | class Store { 30 | constructor(opts) { 31 | this.storePath = opts && opts.storePath || DEFAULT_STORE_PATH; 32 | try { 33 | const config = JSON.parse(fs.readFileSync(this.storePath, { 34 | encoding: 'utf-8' 35 | })); 36 | this.config = config; 37 | } 38 | catch (e) { 39 | this.config = {}; 40 | } 41 | } 42 | set(key, val) { 43 | // @ts-expect-error 44 | this.config[key] = val; 45 | fs.writeFile(this.storePath, JSON.stringify(this.config), { encoding: 'utf-8' }, (err) => { 46 | if (err) { 47 | console.error(err); 48 | } 49 | }); 50 | } 51 | setAll(cf) { 52 | this.config = cf; 53 | fs.writeFile(this.storePath, JSON.stringify(this.config), { encoding: 'utf-8' }, (err) => { 54 | if (err) { 55 | console.error(err); 56 | } 57 | }); 58 | } 59 | get(key) { 60 | return this.config[key]; 61 | } 62 | getAll() { 63 | return this.config; 64 | } 65 | } 66 | exports.default = Store; -------------------------------------------------------------------------------- /packages/cli/src/lib/util/getNgrokBinPath.ts: -------------------------------------------------------------------------------- 1 | import config from '../common/config'; 2 | import * as os from 'os'; 3 | import download from 'download'; 4 | import { logger, } from '../cli-shared-utils/lib/logger'; 5 | import * as path from 'path'; 6 | import { getGlobalRc, setGlobalRcItem, } from './globalRc'; 7 | import clean from './clean'; 8 | import { failSpinner, logWithSpinner, successSpinner, } from '../cli-shared-utils/lib/spinner'; 9 | import tarExtract from './tarExtract'; 10 | import isMacM1 from './isMacM1'; 11 | 12 | export default async (): Promise => { 13 | let platform: any = os.platform(); 14 | 15 | /** 16 | * ngrok m1机型需要用特定的执行文件 17 | */ 18 | if (isMacM1()) { 19 | platform = 'm1'; 20 | } 21 | 22 | const ngrokConfig = config.ngrok; 23 | const platformConfigs = ngrokConfig.platforms; 24 | const ngrokCurPlatformConfig = platformConfigs[platform as keyof typeof platformConfigs]; 25 | const supportPlatform = Object.keys(ngrokConfig.platforms); 26 | if (supportPlatform.indexOf(platform) === -1) { 27 | logger.error('当前平台不支持运行钉钉内网穿透工具,支持的平台', supportPlatform); 28 | return ''; 29 | } 30 | 31 | const globalRc = getGlobalRc(); 32 | if (globalRc && globalRc.ngrokExecPath) { 33 | return globalRc.ngrokExecPath; 34 | } 35 | 36 | const binDownloadPath = ngrokCurPlatformConfig.binDownloadUrl; 37 | const tarStorePath = os.tmpdir(); 38 | 39 | /** builder download */ 40 | try { 41 | logWithSpinner('本地没找到钉钉内网穿透工具,正在下载'); 42 | await download(binDownloadPath, tarStorePath); 43 | successSpinner('钉钉内网穿透工具下载成功'); 44 | } catch(e) { 45 | failSpinner('钉钉内网穿透工具下载失败'); 46 | logger.error(e.message); 47 | return ''; 48 | } 49 | 50 | /** builder extract */ 51 | const basename = ngrokCurPlatformConfig.binBaseName || path.basename(binDownloadPath); 52 | const tar = path.join(tarStorePath, basename); 53 | try { 54 | logger.debug('正在提取钉钉内网穿透工具', tar); 55 | await tarExtract(tar, ngrokConfig.binStoreDir); 56 | logger.debug('钉钉内网穿透工具提取成功'); 57 | } catch(e) { 58 | logger.debug('钉钉内网穿透工具提取失败', e); 59 | return ''; 60 | } finally { 61 | await clean(tar); 62 | } 63 | 64 | /** set global builder bin path */ 65 | const binExecPath = path.join(ngrokConfig.binStoreDir, ngrokCurPlatformConfig.binExtractPath); 66 | setGlobalRcItem('ngrokExecPath', binExecPath); 67 | return binExecPath; 68 | }; -------------------------------------------------------------------------------- /packages/generator/generators/common/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = this && this.__createBinding || (Object.create ? function (o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function () {return m[k];} }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | } : function (o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | }); 13 | var __setModuleDefault = this && this.__setModuleDefault || (Object.create ? function (o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | } : function (o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = this && this.__importStar || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | exports.HUBS_CONFIG = exports.DEFAULT_DIRECTORY_SEPERATOR = exports.REPO_LOCAL_ROOT_PATH = exports.RC_PATH = void 0; 27 | const os = __importStar(require("os")); 28 | const path = __importStar(require("path")); 29 | const constants_1 = require("./constants"); 30 | // 配置文件 31 | exports.RC_PATH = path.join(os.homedir(), '.dd-cli-rc.json'); 32 | // 本地缓存 33 | exports.REPO_LOCAL_ROOT_PATH = path.join(os.homedir(), '.dd-demo-repo'); 34 | // 代码仓库目录名分隔符,合格的目录名格式: ${appType}${seperator}${desc} 35 | exports.DEFAULT_DIRECTORY_SEPERATOR = '_'; 36 | const DEFAULT_REPO_REMOTE_PATH = process.env.REPO_REMOTE_PATH || 'https://gitee.com/open-dingtalk/dd-application-template.git'; 37 | // 套件配置 38 | exports.HUBS_CONFIG = [ 39 | { 40 | key: constants_1.APP_TYPE_ENUM.PLUGIN, 41 | name: '插件', 42 | repoRemotePath: DEFAULT_REPO_REMOTE_PATH 43 | }, 44 | { 45 | key: constants_1.APP_TYPE_ENUM.MP, 46 | name: '小程序', 47 | repoRemotePath: DEFAULT_REPO_REMOTE_PATH 48 | }, 49 | { 50 | key: constants_1.APP_TYPE_ENUM.H5, 51 | name: 'H5微应用', 52 | repoRemotePath: DEFAULT_REPO_REMOTE_PATH 53 | }, 54 | { 55 | key: constants_1.APP_TYPE_ENUM.DOCSCOOLAPP, 56 | name: '文档酷应用', 57 | repoRemotePath: DEFAULT_REPO_REMOTE_PATH 58 | }]; -------------------------------------------------------------------------------- /packages/cli/src/lib/sevice/index.ts: -------------------------------------------------------------------------------- 1 | import urllib, { RequestOptions, } from 'urllib'; 2 | import { logger, } from '../cli-shared-utils/lib/logger'; 3 | import getMonitor from '../cli-shared-utils/lib/monitor/framework-monitor'; 4 | import config from '../common/config'; 5 | 6 | enum EResponseCode { 7 | SUCCESS = 0, 8 | INTERNAL_ERROR = -1, 9 | REQUEST_ERROR = -2, 10 | } 11 | 12 | interface IUnifiedResponse { 13 | code: EResponseCode; 14 | data: T; 15 | msg?: string; 16 | } 17 | 18 | const HOST = 'https://api.dingtalk.com'; 19 | const monitor = getMonitor(config.yuyanId); 20 | 21 | async function request(url: string, opts: RequestOptions): Promise> { 22 | try { 23 | monitor.logApiInvoke(url as any); 24 | const res = await urllib.request(url, opts); 25 | const data = JSON.parse(res.data.toString()); 26 | logger.debug('request', url, 'response', data); 27 | 28 | // 发生错误时,会返回阿里云oapi的特定格式... 29 | if (data.Code && data.RequestId) { 30 | monitor.logApiError(url as any, data.Message, opts.data, data); 31 | return { 32 | code: EResponseCode.REQUEST_ERROR, 33 | data: null, 34 | msg: data.Message, 35 | }; 36 | } 37 | 38 | return { 39 | code: EResponseCode.SUCCESS, 40 | data, 41 | }; 42 | } catch(e) { 43 | logger.debug(`request url ${url} fail.`, e); 44 | monitor.logApiError(url as any, e.message, opts.data, e.stack); 45 | throw { 46 | code: EResponseCode.INTERNAL_ERROR, 47 | data: null, 48 | msg: e.message, 49 | }; 50 | } 51 | } 52 | 53 | export interface RuleCheckInfos { 54 | packCode: string; 55 | pluginRuleCheckDetail: string; 56 | } 57 | 58 | export interface PermissionPoint { 59 | permissionPointList: string[], 60 | } 61 | 62 | export async function getRuleCheckInfos(miniAppId: string, token: string): Promise> { 63 | return request(`${HOST}/v1.0/workbench/plugins/validationRules?miniAppId=${miniAppId}`, { 64 | headers: { 65 | 'x-acs-dingtalk-access-token': token, 66 | }, 67 | }); 68 | } 69 | 70 | export function getPermissionPoint(miniAppId: string, token: string): Promise> { 71 | return request(`${HOST}/v1.0/workbench/plugins/permissions?miniAppId=${miniAppId}`, { 72 | headers: { 73 | 'x-acs-dingtalk-access-token': token, 74 | }, 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /packages/generator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-dd-application", 3 | "version": "1.0.8", 4 | "description": "This is a generator for DingTalk application.", 5 | "scripts": { 6 | "build": "yarn clean && tsc -p tsconfig.json && babel generators -d generators", 7 | "test": "jest", 8 | "clean": "rimraf ./generators" 9 | }, 10 | "files": [ 11 | "generators" 12 | ], 13 | "keywords": [ 14 | "DingTalk", 15 | "worktab", 16 | "application" 17 | ], 18 | "author": "lou1swu", 19 | "license": "MIT", 20 | "dependencies": { 21 | "boxen": "^4.2.0", 22 | "chalk": "^4.1.0", 23 | "execa": "^4.0.3", 24 | "fs-extra": "^10.0.0", 25 | "ini": "^1.3.5", 26 | "request": "^2.88.2", 27 | "semver": "^7.3.2", 28 | "simple-git": "^2.21.0", 29 | "strip-ansi": "^6.0.0", 30 | "yeoman-generator": "^4.12.0", 31 | "uuid": "^3.4.0" 32 | }, 33 | "devDependencies": { 34 | "@types/copyfiles": "^2.4.0", 35 | "@types/fs-extra": "^9.0.11", 36 | "@types/ini": "^1.3.30", 37 | "@types/jest": "^26.0.14", 38 | "@types/request": "^2.48.5", 39 | "@types/semver": "^7.3.4", 40 | "@types/yeoman-generator": "^4.11.3", 41 | "@types/yeoman-test": "^2.0.5", 42 | "@typescript-eslint/eslint-plugin": "^4.4.1", 43 | "@typescript-eslint/parser": "^4.4.1", 44 | "babel-cli": "^6.26.0", 45 | "babel-plugin-module-resolver": "^4.0.0", 46 | "cz-conventional-changelog": "^3.3.0", 47 | "eslint": "^7.11.0", 48 | "husky": "^4.3.0", 49 | "jest": "^26.5.3", 50 | "rimraf": "^3.0.2", 51 | "ts-jest": "^26.4.1", 52 | "typescript": "^4.0.3", 53 | "yeoman-test": "^3.0.0" 54 | }, 55 | "pre-commit": [ 56 | "lint", 57 | "test" 58 | ], 59 | "pre-push": [ 60 | "lint" 61 | ], 62 | "publishConfig": { 63 | "registry": "https://registry.npmjs.org/" 64 | }, 65 | "config": { 66 | "commitizen": { 67 | "path": "./node_modules/cz-conventional-changelog", 68 | "disableScopeLowerCase": false, 69 | "disableSubjectLowerCase": false, 70 | "maxHeaderWidth": 100, 71 | "maxLineWidth": 100, 72 | "defaultType": "", 73 | "defaultScope": "", 74 | "defaultSubject": "", 75 | "defaultBody": "", 76 | "defaultIssues": "", 77 | "types": { 78 | "feat": { 79 | "description": "A new feature", 80 | "title": "Features" 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DingTalk Design CLI [](https://circleci.com/gh/open-dingtalk/dingtalk-design-cli/tree/develop) [](https://ci.appveyor.com/project/lou1swu/dingtalk-design-cli/branch/develop) [](https://lerna.js.org/) 2 | 3 | > DingTalk Design CLI 是面向钉钉开放平台小程序、h5微应用、工作台组件的研发命令行工具。提供对小程序、h5微应用、工作台组件的初始化、开发调试、本地校验、构建预览、上传等功能。 4 | 5 | 6 | 详情请查看:[DingTalk Design CLI操作指南](https://open.dingtalk.com/document/resourcedownload/introduction) 7 | 8 | ## 安装 9 | 10 | 11 | > DingTalk Design CLI需要Nodejs版本不小于12.15.x. 你可以通过 [n](https://github.com/tj/n), [nvm](https://github.com/creationix/nvm) or [nvm-windows](https://github.com/coreybutler/nvm-windows)来管理本地多个Nodejs版本. 12 | 13 | 可以通过以下命令来安装 DingTalk Design CLI 14 | 15 | ```bash 16 | npm install dingtalk-design-cli -g 17 | 18 | # OR 19 | 20 | yarn global add dingtalk-design-cli 21 | ``` 22 | 安装后,你可以通过`ding`命令来使用 DingTalk Design CLI 的功能。 23 | 通过以下命令检测 DingTalk Design CLI 已经成功安装: 24 | ```bash 25 | ding -v 26 | ``` 27 | 28 | 通过以下命令查阅`ding`命令支持的功能: 29 | ```bash 30 | ding -h 31 | ``` 32 | 33 | ### 更新 34 | 如果你之前已经安装过 DingTalk Design CLI,可以通过以下命令进行升级: 35 | ```bash 36 | npm update -g dingtalk-design-cli 37 | 38 | # OR 39 | yarn global upgrade --latest dingtalk-design-cli 40 | ``` 41 | ## 快速入门 42 | 43 | 44 | 首先,通过以下命令初始化一个本地项目: 45 | ```bash 46 | ding init -o mpTest 47 | ``` 48 | 49 | 1. 以小程序为例,选择应用类型为`小程序`: 50 |  51 | 52 | 53 | 2. 然后选择一个小程序模板`default`: 54 |  55 | 56 | 57 | 3. 最后选择开发语言为`javascript`: 58 |  59 | 60 | 61 | 4. 到这里,你已经完成了所有初始化步骤,已经初始化了一个项目到`mpTest`目录下了 62 |  63 | 64 | 65 | 5. 最后,进入`mpTest`项目目录,并执行`ding dev`就可以开启调试了 66 |  67 | 68 | 69 | ## License 70 | 71 | [MIT](https://github.com/open-dingtalk/dingtalk-design-cli/blob/develop/LICENSE) 72 | -------------------------------------------------------------------------------- /packages/cli/src/actions/lint.ts: -------------------------------------------------------------------------------- 1 | import { EAppType, ICommandContext, } from '../lib/common/types'; 2 | import eslint from 'eslint'; 3 | import pluginEl from 'dingtalk-worktab-plugin-script'; 4 | import { logWithSpinner, stopSpinner, } from '../lib/cli-shared-utils/lib/spinner'; 5 | import getValidateRc from '../lib/util/getValidateRc'; 6 | import { spawnSync, } from 'child_process'; 7 | 8 | 9 | export default async (commandContext: ICommandContext): Promise => { 10 | // 对于小程序和h5,区分是否有eslintrc的场景 11 | // 对于工作台组件,区分是否有miniAppId/token的场景 12 | const { dtdConfig, hasOriginDtdConfig, miniProgramConfigContent, } = commandContext; 13 | if (!miniProgramConfigContent) { 14 | commandContext.logger.error('当前目录下找不到mini.project.json,请在小程序或工作台组件工作目录下运行'); 15 | return; 16 | } 17 | 18 | const { 19 | type: appType, 20 | miniAppId, 21 | token, 22 | } = dtdConfig; 23 | const cwd = commandContext.cwd; 24 | 25 | if ([EAppType.H5, EAppType.MP].indexOf(appType as EAppType) !== -1) { 26 | if (!hasOriginDtdConfig) { 27 | const eslinter = new eslint.ESLint({ 28 | cwd, 29 | }); 30 | const res = await eslinter.lintFiles(['**']); 31 | commandContext.logger.info(res); 32 | } else { 33 | try { 34 | spawnSync( 35 | 'npm', 36 | [ 37 | 'run', 38 | 'lint', 39 | '--color', 40 | ], 41 | { 42 | stdio: 'inherit', 43 | cwd, 44 | env: process.env, 45 | }, 46 | ); 47 | commandContext.logger.info('校验结束(没有输出代表校验通过)'); 48 | } catch(e) { 49 | commandContext.logger.error('校验出错:', e); 50 | } 51 | } 52 | } else if (appType === EAppType.PLUGIN) { 53 | const pluginRoot = miniProgramConfigContent.pluginRoot; 54 | if (!pluginRoot) { 55 | commandContext.logger.error('mini.project.json中没有声明pluginRoot,无法找到工作台组件根目录'); 56 | return; 57 | } 58 | 59 | if (!hasOriginDtdConfig || !miniAppId || !token) { 60 | const res = await pluginEl(pluginRoot); 61 | commandContext.logger.warn('当前项目目录中读取不到miniAppId和token,校验时将使用标准规则'); 62 | commandContext.logger.info(res.data); 63 | return; 64 | } 65 | 66 | if (miniAppId && token) { 67 | logWithSpinner('正在拉取当前工作台组件的权限包'); 68 | const rcJson = await getValidateRc(miniAppId, token, dtdConfig.isPcPlugin); 69 | logWithSpinner('正在校验'); 70 | const res = await pluginEl(pluginRoot, rcJson); 71 | stopSpinner('校验完成'); 72 | commandContext.logger.info(res.data); 73 | } 74 | } 75 | }; -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/opensdk/__tests__/task/previewBuildTask.test.ts: -------------------------------------------------------------------------------- 1 | import { sdk, } from '../../src/index'; 2 | import * as sdkConfig from '../sdk-test-config'; 3 | import path from 'path'; 4 | import qs from 'qs'; 5 | import { EBuildTarget } from '../../src/types'; 6 | 7 | const cfg = sdkConfig.daily; 8 | 9 | jest.setTimeout(60000); 10 | 11 | describe('PreviewBuildTask', () => { 12 | 13 | beforeAll(() => { 14 | sdk.setConfig(cfg); 15 | }); 16 | 17 | test('miniapp', async () => { 18 | const { previewUrl } = await sdk.previewBuild({ 19 | project: path.resolve(__dirname, '../examples/miniapp-default'), 20 | miniAppId: cfg.miniAppId, 21 | page: 'pages/web-view/webview', 22 | query: 'a=2&b=2', 23 | corpId: 'testcorpid', 24 | buildTarget: EBuildTarget.Preview, 25 | onProgressUpdate: (info) => { 26 | console.log('xxx onProgressUpdate', info); 27 | }, 28 | }); 29 | 30 | console.log('xxx resultUrl', previewUrl); 31 | }); 32 | 33 | test('miniapp-use-plugin', async () => { 34 | const { previewUrl } = await sdk.previewBuild({ 35 | project: path.resolve(__dirname, '../examples/miniapp-use-plugin'), 36 | miniAppId: cfg.miniAppId, 37 | page: 'pages/index/index', 38 | query: 'a=2&b=2', 39 | // corpId: 'testcorpid', 40 | buildTarget: EBuildTarget.Preview, 41 | onProgressUpdate: (info) => { 42 | console.log('xxx onProgressUpdate', info); 43 | }, 44 | }); 45 | 46 | console.log('xxx resultUrl', previewUrl); 47 | }); 48 | 49 | test('miniapp-use-subpackages', async () => { 50 | const { previewUrl }= await sdk.previewBuild({ 51 | project: path.resolve(__dirname, '../examples/miniapp-use-subpackages'), 52 | miniAppId: cfg.miniAppId, 53 | page: 'pages/index/index', 54 | query: 'a=2&b=2', 55 | // corpId: 'testcorpid', 56 | buildTarget: EBuildTarget.Preview, 57 | onProgressUpdate: (info) => { 58 | console.log('xxx onProgressUpdate', info); 59 | }, 60 | }); 61 | 62 | console.log('xxx resultUrl', previewUrl); 63 | }); 64 | 65 | // test.skip('plugin', async () => { 66 | // const resultUrl = await sdk.previewBuild({ 67 | // project: path.resolve(__dirname, '../examples/plugin'), 68 | // // TODO: 需要一个插件ID 69 | // miniAppId: cfg.miniAppId, 70 | // page: 'pages/index/index', 71 | // query: "a=2&b=2", 72 | // onProgressUpdate: (info) => { 73 | // console.log('xxx onProgressUpdate', info); 74 | // } 75 | // }); 76 | 77 | // console.log('xxx resultUrl', resultUrl); 78 | // }); 79 | }); -------------------------------------------------------------------------------- /packages/cli/src/lib/cli-shared-utils/lib/process/getProcessForPort.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | 'use strict'; 9 | import chalk from 'chalk'; 10 | import { execSync, execFileSync, } from 'child_process'; 11 | import { ExecFileSyncOptionsWithStringEncoding, ExecSyncOptionsWithStringEncoding, } from 'node:child_process'; 12 | import * as path from 'path'; 13 | 14 | const execOptions = { 15 | encoding: 'utf8', 16 | stdio: [ 17 | 'pipe', // stdin (default) 18 | 'pipe', // stdout (default) 19 | 'ignore', //stderr 20 | ], 21 | }; 22 | 23 | function isProcessAReactApp(processCommand) { 24 | return /^node .*react-scripts\/scripts\/start\.js\s?$/.test(processCommand); 25 | } 26 | 27 | function getProcessIdOnPort(port) { 28 | return execFileSync('lsof', ['-i:' + port, '-P', '-t', '-sTCP:LISTEN'], execOptions as ExecFileSyncOptionsWithStringEncoding) 29 | .split('\n')[0] 30 | .trim(); 31 | } 32 | 33 | function getPackageNameInDirectory(directory) { 34 | const packagePath = path.join(directory.trim(), 'package.json'); 35 | 36 | try { 37 | return require(packagePath).name; 38 | } catch (e) { 39 | return null; 40 | } 41 | } 42 | 43 | function getProcessCommand(processId, processDirectory) { 44 | let command: string = execSync( 45 | 'ps -o command -p ' + processId + ' | sed -n 2p', 46 | execOptions as ExecSyncOptionsWithStringEncoding 47 | ); 48 | 49 | command = command.replace(/\n$/, ''); 50 | 51 | if (isProcessAReactApp(command)) { 52 | const packageName = getPackageNameInDirectory(processDirectory); 53 | return packageName ? packageName : command; 54 | } else { 55 | return command; 56 | } 57 | } 58 | 59 | function getDirectoryOfProcessById(processId) { 60 | return execSync( 61 | 'lsof -p ' + 62 | processId + 63 | ' | awk \'$4=="cwd" {for (i=9; i<=NF; i++) printf "%s ", $i}\'', 64 | execOptions as ExecSyncOptionsWithStringEncoding 65 | ).trim(); 66 | } 67 | 68 | export default function getProcessForPort(port: number | string): string | null { 69 | try { 70 | const processId = getProcessIdOnPort(port); 71 | const directory = getDirectoryOfProcessById(processId); 72 | const command = getProcessCommand(processId, directory); 73 | return ( 74 | chalk.cyan(command) + 75 | chalk.grey(' (pid ' + processId + ')\n') + 76 | chalk.blue(' in ') + 77 | chalk.cyan(directory) 78 | ); 79 | } catch (e) { 80 | return null; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/cli/h5bundle/src/components/component-title-bar/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | import { ITitleBarModel, } from '../../types'; 4 | import './index.css'; 5 | 6 | interface IProps { 7 | mode: string; 8 | titleBarModel: ITitleBarModel; 9 | componentProps: any 10 | } 11 | 12 | const PROMOTION_STATE_TRYOUT = 'STANDARD_WORKTAB'; 13 | 14 | export default function ComponentTitleBar(props: IProps) { 15 | const { 16 | mode, 17 | titleBarModel, 18 | componentProps, 19 | } = props; 20 | 21 | const onTitleClick = () => { 22 | const { 23 | titleBarModel, 24 | componentProps = {}, 25 | } = props; 26 | const { 27 | link = '', 28 | } = titleBarModel || {}; 29 | const { 30 | // 营销态标记 31 | promotionState, 32 | // 营销态地址 33 | tryoutAddress = '', 34 | } = componentProps; 35 | 36 | if (promotionState === PROMOTION_STATE_TRYOUT) { 37 | if (tryoutAddress) { 38 | alert({ 39 | content: `打开营销态地址:${tryoutAddress}`, 40 | }); 41 | } 42 | return; 43 | } 44 | alert({ 45 | content: `打开标题地址:${link}`, 46 | }); 47 | console.log('点击标题:', link); 48 | } 49 | 50 | const onManageClick = () => { 51 | const { 52 | titleBarModel = {} as ITitleBarModel, 53 | } = props; 54 | const { 55 | manage = {}, 56 | } = titleBarModel; 57 | const { 58 | link = '', 59 | } = manage; 60 | 61 | alert({ 62 | content: `打开管理地址:${link}`, 63 | }); 64 | } 65 | 66 | return ( 67 | 68 | 69 | {titleBarModel.icon && } 70 | {titleBarModel.title} 73 | {titleBarModel.subLabel && {titleBarModel.subLabel}} 74 | {titleBarModel.showArrow && } 75 | {componentProps.promotionState === PROMOTION_STATE_TRYOUT && 示例} 76 | 77 | 78 | {titleBarModel.manage && ( 79 | 80 | 83 | 84 | )} 85 | 86 | 87 | ) 88 | } -------------------------------------------------------------------------------- /packages/cli/src/lib/cli-shared-utils/lib/logger/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | import chalk, { Color, Modifiers, } from 'chalk'; 5 | import { LoggerBase, } from './base'; 6 | import { ELoggerLevel, ILoggerElement, } from './types'; 7 | 8 | /** 9 | * Expose Logger 10 | */ 11 | export class Logger extends LoggerBase { 12 | // level: 1 13 | error(...args: ILoggerElement[]) { 14 | this.useLogLevel(ELoggerLevel.error, () => { 15 | process.exitCode = process.exitCode || 1; 16 | console.error(chalk.red('error'), ...args); 17 | }); 18 | } 19 | 20 | // level: 2 21 | warn(...args: ILoggerElement[]) { 22 | this.useLogLevel(ELoggerLevel.warn, () => { 23 | console.warn(chalk.yellow('warning'), ...args); 24 | }); 25 | } 26 | 27 | // level: 3 28 | success(...args: ILoggerElement[]) { 29 | this.useLogLevel(ELoggerLevel.info, () => { 30 | this.status('green', 'success', ...args); 31 | }); 32 | } 33 | 34 | // level: 3 35 | tip(...args: ILoggerElement[]) { 36 | this.useLogLevel(ELoggerLevel.info, () => { 37 | this.status('blue', 'tip', ...args); 38 | }); 39 | } 40 | 41 | // level: 3 42 | info(...args: ILoggerElement[]) { 43 | this.useLogLevel(ELoggerLevel.info, () => { 44 | this.status('gray', '', ...args); 45 | }); 46 | } 47 | 48 | // level: 3 49 | log(...args: ILoggerElement[]) { 50 | this.useLogLevel(ELoggerLevel.info, () => { 51 | this.status('gray', '', ...args); 52 | }); 53 | } 54 | 55 | status( 56 | color: typeof Color | typeof Modifiers, 57 | label: string, ...args: ILoggerElement[] 58 | ) { 59 | if (this.options.logLevel && this.options.logLevel >= ELoggerLevel.debug) { 60 | this._statuslog(color, label, ...(this.getLogArgsWithPrefix(args))); 61 | } else { 62 | this._statuslog(color, label, ...args); 63 | } 64 | } 65 | 66 | _statuslog( 67 | color: typeof Color | typeof Modifiers, 68 | label: string, ...args: ILoggerElement[] 69 | ) { 70 | if (label) { 71 | console.log(chalk[color](label), ...args); 72 | } else { 73 | console.log(...args); 74 | } 75 | } 76 | 77 | // level: 4 78 | public debug(...args: ILoggerElement[]) { 79 | this.useLogLevel(ELoggerLevel.debug, () => { 80 | this.status('magenta', 'debug', ...args); 81 | }); 82 | } 83 | } 84 | 85 | const cachedLogger = new Map(); 86 | 87 | export const getLogger = (prefix: string): Logger => { 88 | const cached = cachedLogger.get(prefix); 89 | if (cached) { 90 | return cached; 91 | } 92 | const _logger = new Logger(prefix); 93 | cachedLogger.set(prefix, _logger); 94 | return _logger; 95 | }; 96 | 97 | export const logger = new Logger(); -------------------------------------------------------------------------------- /packages/cli/__tests__/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import getH5ProBinPath from '../src/lib/util/getH5ProBinPath'; 3 | import config from '../src/lib/common/config'; 4 | import * as os from 'os'; 5 | import { setGlobalRcItem, getGlobalRc, setGlobalRc, } from '../src/lib/util/globalRc'; 6 | import getJson from '../src/lib/util/getJson'; 7 | import { IGlobalRc, } from '../src/lib/common/types'; 8 | 9 | describe('Utils', ()=>{ 10 | const cwd = path.join(__dirname); 11 | console.log('cwd', cwd); 12 | 13 | test('getH5ProBinPath', async () => { 14 | const platform = os.platform(); 15 | const h5ProConfig = config.h5pro; 16 | const platformConfigs = h5ProConfig.platforms; 17 | const h5ProCurPlatformConfig = platformConfigs[platform as keyof typeof platformConfigs]; 18 | const supportPlatform = Object.keys(h5ProConfig.platforms); 19 | 20 | console.log(platform, supportPlatform); 21 | const binExecPath = await getH5ProBinPath(); 22 | 23 | if (binExecPath) { 24 | expect(binExecPath).toBe(path.join(h5ProConfig.binStoreDir, h5ProCurPlatformConfig.binExtractPath)); 25 | } 26 | }, 60 * 1000); 27 | 28 | test('globalRc', () => { 29 | const key = 'testKey' as keyof IGlobalRc; 30 | const value = 'testValue'; 31 | setGlobalRcItem(key, value); 32 | 33 | // check setGlobalRcItem 34 | const globalRc1 = getGlobalRc(); 35 | expect(globalRc1).not.toBe(null); 36 | if (globalRc1) { 37 | expect(globalRc1[key]).toBe(value); 38 | } 39 | 40 | // reset 41 | setGlobalRcItem(key, ''); 42 | 43 | // check setGlobalRc 44 | setGlobalRc({ 45 | ...globalRc1, 46 | [key]: value, 47 | }); 48 | const globalRc2 = getGlobalRc(); 49 | expect(globalRc2).not.toBe(null); 50 | if (globalRc2) { 51 | expect(globalRc2[key]).toBe(value); 52 | } 53 | }); 54 | 55 | test('getJson', () => { 56 | { 57 | // 文件不存在,容错 58 | const content = getJson('', true); 59 | expect(content).toEqual({}); 60 | } 61 | 62 | { 63 | // 文件不存在,容错且包含默认值 64 | const defaultJson = { 65 | test: '', 66 | }; 67 | const content = getJson('', true, defaultJson); 68 | expect(content).toEqual(defaultJson); 69 | } 70 | 71 | { 72 | // 文件不存在,不容错 73 | let content; 74 | try { 75 | content = getJson('', false); 76 | } catch(e) { 77 | } finally { 78 | expect(content).toEqual(undefined); 79 | } 80 | } 81 | 82 | { 83 | // 文件存在 84 | const filepath = path.join(cwd, `./mock/mp1/${config.workspaceRcName}`); 85 | const content = getJson(filepath); 86 | console.log(content); 87 | expect(content).not.toEqual({}); 88 | } 89 | 90 | }); 91 | }); -------------------------------------------------------------------------------- /packages/cli/h5bundle/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 本地预览 22 | 23 | 24 | You need to enable JavaScript to run this app. 25 | 26 | 27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /packages/generator/generators/utils/clean.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = this && this.__createBinding || (Object.create ? function (o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function () {return m[k];} }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | } : function (o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | }); 13 | var __setModuleDefault = this && this.__setModuleDefault || (Object.create ? function (o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | } : function (o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = this && this.__importStar || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | var __awaiter = this && this.__awaiter || function (thisArg, _arguments, P, generator) { 26 | function adopt(value) {return value instanceof P ? value : new P(function (resolve) {resolve(value);});} 27 | return new (P || (P = Promise))(function (resolve, reject) { 28 | function fulfilled(value) {try {step(generator.next(value));} catch (e) {reject(e);}} 29 | function rejected(value) {try {step(generator["throw"](value));} catch (e) {reject(e);}} 30 | function step(result) {result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);} 31 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 32 | }); 33 | }; 34 | Object.defineProperty(exports, "__esModule", { value: true }); 35 | const child_process_1 = require("child_process"); 36 | const fs = __importStar(require("fs")); 37 | exports.default = (filePath) => __awaiter(void 0, void 0, void 0, function* () { 38 | if (!filePath) 39 | return; 40 | if (!fs.existsSync(filePath)) { 41 | console.warn(`ENOENT ${filePath} is not exist`); 42 | return; 43 | } 44 | const statInfo = fs.statSync(filePath); 45 | const isDirectory = statInfo.isDirectory(); 46 | if (isDirectory) { 47 | console.warn(`Clean a directory: ${filePath}`); 48 | } 49 | try { 50 | (0, child_process_1.execSync)(`ls ${filePath}`); 51 | } 52 | catch (e) { 53 | console.warn(`ll exec failed. filePath: ${filePath}`); 54 | } 55 | try { 56 | (0, child_process_1.execSync)(`rm -r ${filePath}`); 57 | console.log(`Clean success. filePath: ${filePath}`); 58 | } 59 | catch (e) { 60 | console.warn(`Clean failed. filePath: ${filePath}. ${e}`); 61 | } 62 | }); -------------------------------------------------------------------------------- /packages/opensdk/src/config/appConfig.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | import { IPlugin, } from '../openapi'; 5 | import { IProjectConfig, } from './projectConfig'; 6 | 7 | export interface ITabBarItem { 8 | pagePath: string; 9 | name: string; 10 | icon?: string; 11 | activeIcon?: string; 12 | } 13 | 14 | export interface ITabBarConfig { 15 | textColor?: string; 16 | selectedColor?: string; 17 | backgroundColor?: string; 18 | items: Array; 19 | } 20 | 21 | export interface IWindowConfig { 22 | defaultTitle?: string; 23 | backgroundColor?: string; 24 | titleBarColor?: string; 25 | pullPrefresh?: boolean; 26 | allowsBounceVertical?: 'YES' | 'NO', 27 | supportColorScheme?: Array<'light' | 'dark'>; 28 | } 29 | 30 | export interface IAppConfig { 31 | pages: string[]; 32 | window?: IWindowConfig; 33 | tabBar?: ITabBarConfig; 34 | plugins?: { 35 | [key: string]: { 36 | provider: string; 37 | version: string; 38 | } 39 | } 40 | } 41 | 42 | export function findAppConfig(entry: string, projectConfig: IProjectConfig) { 43 | const miniprogramRoot = projectConfig.miniprogramRoot || '.'; 44 | const file = path.join(entry, miniprogramRoot, 'app.json'); 45 | 46 | fs.accessSync(file); 47 | 48 | return fs.readJSONSync(file) as IAppConfig; 49 | } 50 | 51 | export function getEnableTabBar(appConfig: IAppConfig) { 52 | const mainPage = getMainPage(appConfig); 53 | const tabBarItems = appConfig.tabBar?.items; 54 | 55 | if (!tabBarItems || tabBarItems.length === 0) { 56 | return 'NO'; 57 | } 58 | 59 | return mainPage === tabBarItems[0].pagePath ? 'YES' : 'NO'; 60 | } 61 | 62 | export function getMainPage(appConfig: IAppConfig) { 63 | return appConfig.pages[0]; 64 | } 65 | 66 | export function getPluginRefs(appConfig: IAppConfig) { 67 | if (!appConfig.plugins) { 68 | return []; 69 | } 70 | 71 | const pluginNames = Object.keys(appConfig.plugins); 72 | 73 | if (pluginNames.length === 0) { 74 | return []; 75 | } 76 | 77 | const pluginRefs = new Map(); 78 | 79 | for(const n of pluginNames) { 80 | const v = appConfig.plugins[n]; 81 | const id = v.provider; 82 | const version = v.version; 83 | 84 | if (!id || !version) { 85 | throw new Error(`非法的静态插件配置plugins.${n}:${JSON.stringify(v)}`); 86 | } 87 | 88 | // 小程序插件的开发配置,忽略掉 89 | if (version === 'dev') { 90 | continue; 91 | } 92 | 93 | if (version !== '*') { 94 | throw new Error(`非法的插件配置plugins.${n}:静态插件版本进支持通配符*`); 95 | } 96 | 97 | pluginRefs.set(id, { plugin_id: id, plugin_version: version, pluign_version: version, }); 98 | } 99 | 100 | return Array.from(pluginRefs.values()); 101 | } --------------------------------------------------------------------------------