diff --git a/.coding_release.yml.sample b/.coding_release.yml.sample new file mode 100644 index 0000000..e2bd646 --- /dev/null +++ b/.coding_release.yml.sample @@ -0,0 +1,100 @@ +service: +- name: e-coding + migrate: enterprise/app/e-coding/doc/mysql/migrate_script + source: + - enterprise/app/e-coding + - enterprise/controller/core + - enterprise/controller/admin + - enterprise/controller/git + - enterprise/lib/core + - enterprise/lib/statistic + - enterprise/lib/search + - enterprise/lib/importer + - enterprise/lib/admin + - enterprise/lib/build-artifact +- name: e-scheduler + source: + - enterprise/app/e-coding + - enterprise/controller/core + - enterprise/controller/admin + - enterprise/controller/git + - enterprise/lib/core + - enterprise/lib/statistic + - enterprise/lib/search + - enterprise/lib/importer + - enterprise/lib/admin + - enterprise/lib/build-artifact +- name: e-front + migrate: + source: + - frontend/coding-front-v2 +- name: e-admin + migrate: + source: + - frontend/coding-front/e-admin +- name: e-repo-importer + migrate: + source: + - enterprise/lib/importer/src/main/java +- name: e-nexus-server + migrate: + source: + - app/nexus-server +- name: e-git-backup + migrate: + source: + - go-git-server/cmd/git-backup + - go-git-server/pkg/backup +- name: e-git-http-server + migrate: + source: + - go-git-server/cmd/git-server/app/git.go + - go-git-server/pkg/server/http +- name: e-git-lfs-server + migrate: + source: + - go-git-server/cmd/git-server/app/lfs.go + - go-git-server/pkg/server/lfs +- name: e-git-rpc-server + migrate: + source: + - go-git-server/cmd/git-server/app/rpc.go + - go-git-server/pkg/server/rpc +- name: e-git-ssh-server + migrate: + source: + - go-git-server/cmd/git-server/app/ssh.go + - go-git-server/pkg/server/ssh +- name: e-git-svn-ssh-server + migrate: + source: + - go-git-server/cmd/git-server/app/svn.go + - go-git-server/pkg/server/svn +- name: e-repo-auth-server + migrate: + source: + - app/repo-auth-server +- name: e-git-svn-server + migrate: + source: + - app/git-svn-server +- name: e-git-svn-server + migrate: + source: + - app/repo-manager +- name: e-webhook-listener + migrate: + source: + - app/webhook-listener +- name: e-message + migrate: + source: + - app/message +- name: e-md2html + migrate: + source: + - app/md2html +- name: e-cci + migrate: + source: + - app/cci diff --git a/.git-pre-commit b/.git-pre-commit deleted file mode 100755 index c456026..0000000 --- a/.git-pre-commit +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -set -e - -FILES=$(git diff --diff-filter=d --name-only HEAD | { grep '.php$' || true; }) -for file in $FILES; do - ./vendor/bin/phpcs --extensions=php --standard=PSR12 "$file" - ./vendor/bin/phpmd "$file" text phpmd.xml --exclude vendor -done -XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-text --coverage-filter=app/ tests/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index bd90605..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: CI -on: - push: - branches: - - php - tags: - - 2.*.* - pull_request: - branches: - - php - -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: prepare - uses: docker://ecoding/php:8.0 - with: - args: composer install - - - name: Lint - uses: docker://ecoding/php:8.0 - with: - args: ./vendor/bin/phpcs --extensions=php --standard=PSR12 app/ tests/ - - - name: PHPMD - uses: docker://ecoding/php:8.0 - with: - args: ./vendor/bin/phpmd . text phpmd.xml --exclude vendor - - - name: test - uses: docker://ecoding/php:8.0 - env: - XDEBUG_MODE: coverage - with: - args: ./vendor/bin/phpunit --coverage-clover coverage.xml --coverage-filter app/ tests/ - - - name: codecov - uses: codecov/codecov-action@v2 - - - name: GitHub Environment Variables Action - uses: FranzDiebold/github-env-vars-action@v2 - - - name: Set env - run: | - echo "APP_VERSION=$CI_SHA_SHORT" >> $GITHUB_ENV - - - name: Set env when tag - if: startsWith(github.ref, 'refs/tags/') - run: | - echo "APP_VERSION=$CI_ACTION_REF_NAME" >> $GITHUB_ENV - - - name: build - uses: docker://ecoding/php:8.0 - env: - APP_VERSION: ${{ env.APP_VERSION }} - with: - args: php coding app:build --build-version=${{ env.APP_VERSION }} - - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - name: coding - path: builds/coding - - - name: Set up Docker Buildx - if: startsWith(github.ref, 'refs/tags/') - uses: docker/setup-buildx-action@v1 - - - name: Login to DockerHub - if: startsWith(github.ref, 'refs/tags/') - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_TOKEN }} - - - name: Build and push - id: docker_build - if: startsWith(github.ref, 'refs/tags/') - uses: docker/build-push-action@v2 - env: - APP_VERSION: ${{ env.APP_VERSION }} - with: - push: true - context: . - tags: ecoding/coding-cli:latest,ecoding/coding-cli:${{ env.APP_VERSION }} - - - name: Image digest - if: steps.docker_build.conclusion == 'success' - run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.gitignore b/.gitignore index 58f5e38..130412a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ -/builds -/coverage.xml -/vendor -/.idea -/.vscode -/.vagrant -.phpunit.result.cache -/database/database.sqlite +.cookie +vendor +.idea +dist +.history +.vscode +.coding_release.yml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index d19bf1b..0000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM php:8.0-cli -WORKDIR /root - -RUN apt-get update \ - && apt-get install -y libzip-dev -RUN docker-php-ext-install zip - -COPY builds/coding /usr/local/bin/ - -ENTRYPOINT ["coding"] -CMD ["list"] diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index ba8ead9..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,46 +0,0 @@ -pipeline { - agent { - docker { - reuseNode 'true' - registryUrl 'https://coding-public-docker.pkg.coding.net' - image 'public/docker/php:8.0' - } - } - stages { - stage('检出') { - steps { - checkout([ - $class: 'GitSCM', - branches: [[name: GIT_BUILD_REF]], - userRemoteConfigs: [[ - url: GIT_REPO_URL, - credentialsId: CREDENTIALS_ID - ]]]) - } - } - stage('打包') { - steps { - script { - if (env.TAG_NAME ==~ /.*/ ) { - BUILD_VERSION = "${env.TAG_NAME}" - } else if (env.MR_SOURCE_BRANCH ==~ /.*/ ) { - BUILD_VERSION = "dev-${env.MR_RESOURCE_ID}-${env.GIT_COMMIT_SHORT}" - } else { - BUILD_VERSION = "dev-${env.BRANCH_NAME.replace('/', '-')}-${env.GIT_COMMIT_SHORT}" - } - } - - sh 'composer install' - sh "php coding app:build --build-version=${BUILD_VERSION}" - } - } - stage('上传到制品库') { - steps { - sh 'mv builds/coding builds/coding.phar' - dir ('builds') { - codingArtifactsGeneric(files: 'coding.phar', repoName: 'downloads', version: "${BUILD_VERSION}") - } - } - } - } -} diff --git a/LICENSE b/LICENSE index 3330735..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,202 @@ -MIT License - -Copyright (c) 2021 CODING Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index dab9570..c9cb5da 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,69 @@ -# CODING cli +# Coding Release 发布工具 -[](https://github.com/Coding/coding-cli/actions/workflows/ci.yml) -[](https://codecov.io/gh/Coding/coding-cli) -[](https://hub.docker.com/r/ecoding/coding-cli) -CODING cli 基于 [Laravel Zero](https://laravel-zero.com/)。 +## 简介 -## run in Docker +用于创建 Coding 发布使用的 Release Checklist 文档 -```shell -docker pull ecoding/coding-cli -docker run -it ecoding/coding-cli -docker run -it ecoding/coding-cli wiki:import --help -docker run -it -v $(pwd):/root --env CODING_TOKEN=foo --env CONFLUENCE_USERNAME=admin ecoding/coding-cli wiki:import -docker run -it -v $(pwd):/root --env-file .env ecoding/coding-cli wiki:import +## 安装 + +windows 用户请先设置环境变量 $GOBIN + +```bash +curl https://raw.githubusercontent.com/coding/coding-cli/master/install.sh | sh ``` - +或者下载源码编译安装 -## run without Docker +## 命令 -要求:PHP 8.0 或更高版本 +### 登录用户 -访问「[CODING 公共制品库](https://coding-public.coding.net/public-artifacts/public/downloads/coding.phar/version/6352163/list)」,下载后在命令行中执行。 +示例命令:`coding-cli login -u username` -在 Linux/macOS 中,建议重命名,并放到系统目录: +在用户目录下创建一个 ~/.coding_release_rc 文件保存 session -```shell -chmod +x coding.phar -sudo mv coding.phar /usr/local/bin/coding -coding list -``` +### 生成 Release 文件 -## Confluence to CODING Wiki +在当前目录生成 Markdown 格式的 Release 文件 -1. 浏览器访问 Confluence 空间,导出 HTML,获得一个 zip 压缩包。 +示例命令:`coding-cli release release-20181122 general-products -p coding-frontend -o release-20181122-general-products.md` - +示例命令:`coding-cli release master enterprise-saas -o release-20181030.1-enterprise.md -l enterprise-saas -t normal -n 1 -c ~/.coding_release.yml` -2. 浏览器访问 CODING,创建个人令牌 +查看帮助:`coding-cli release -h` - + -3. 打开命令行,进入 zip 文件所在的目录,执行命令导入: +### 创建环境变量文件 + +示例命令:`coding-cli env add -c "redis.host=17.0.0.1" -f add_redis_host` + + + +### 创建 pt-online-schema-change 数据库表结构更新文件 + +示例命令:`coding-cli pt -t sample -a "add column nickname varchar(32) default null comment '昵称' after id" -f sample_table_add_nickname_col` + + + +### 创建数据库数据更新 SQL 文件 + +示例命令:`coding-cli sql -c " UPDATE sample SET nickname='tom' WHERE id = 1 " -f update_sample_nickname` + + -```shell -cd ~/Downloads/ -docker run -it -v $(pwd):/root --env CODING_IMPORT_PROVIDER=Confluence \ - --env CODING_IMPORT_DATA_TYPE=HTML \ - --env CODING_IMPORT_DATA_PATH=./Confluence-space-export-231543-81.html.zip \ - --env CODING_TOKEN=foo \ - ecoding/coding-cli wiki:import -``` - +### .coding_release.yml 文件示例 +```yml +service: +- name: e-coding + migrate: enterprise/app/e-coding/doc/mysql/migrate_script + source: + - enterprise/app/e-coding +- name: e-front + migrate: + source: + - frontend/coding-front-v2 +``` \ No newline at end of file diff --git a/app/Coding/Base.php b/app/Coding/Base.php deleted file mode 100644 index 95b516a..0000000 --- a/app/Coding/Base.php +++ /dev/null @@ -1,59 +0,0 @@ -client = $client ?? new Client(); - $this->zipArchive = $zipArchive ?? new ZipArchive(); - } - - public function createUploadToken($token, $projectName, $fileName) - { - $response = $this->client->request('POST', 'https://e.coding.net/open-api', [ - 'headers' => [ - 'Accept' => 'application/json', - 'Authorization' => "token ${token}", - 'Content-Type' => 'application/json' - ], - 'json' => [ - 'Action' => 'CreateUploadToken', - 'ProjectName' => $projectName, - 'FileName' => $fileName, - ], - ]); - $uploadToken = json_decode($response->getBody(), true)['Response']['Token']; - preg_match_all( - '|https://([a-z0-9\-]+)-(\d+)\.cos\.([a-z0-9\-]+)\.myqcloud\.com|', - $uploadToken['UploadLink'], - $matches - ); - $uploadToken['Bucket'] = $matches[1][0] . '-' . $matches[2][0]; - $uploadToken['AppId'] = $matches[2][0]; - $uploadToken['Region'] = $matches[3][0]; - return $uploadToken; - } - - public function upload(array $uploadToken, string $fileFullPath): bool - { - config(['filesystems.disks.cos.credentials.appId' => $uploadToken['AppId']]); - config(['filesystems.disks.cos.credentials.secretId' => $uploadToken['SecretId']]); - config(['filesystems.disks.cos.credentials.secretKey' => $uploadToken['SecretKey']]); - config(['filesystems.disks.cos.credentials.token' => $uploadToken['UpToken']]); - config(['filesystems.disks.cos.region' => $uploadToken['Region']]); - config(['filesystems.disks.cos.bucket' => $uploadToken['Bucket']]); - - $disk = Storage::build(config('filesystems.disks.cos')); - return $disk->put($uploadToken['StorageKey'], File::get($fileFullPath)); - } -} diff --git a/app/Coding/Disk.php b/app/Coding/Disk.php deleted file mode 100644 index 06db24a..0000000 --- a/app/Coding/Disk.php +++ /dev/null @@ -1,96 +0,0 @@ -client->request('POST', 'https://e.coding.net/open-api', [ - 'headers' => [ - 'Accept' => 'application/json', - 'Authorization' => "token ${token}", - 'Content-Type' => 'application/json' - ], - 'json' => [ - 'Action' => 'CreateFolder', - 'ProjectName' => $projectName, - 'FolderName' => $folderName, - 'ParentId' => $parentId, - ], - ]); - $result = json_decode($response->getBody(), true); - return $result['Response']['Data']['Id']; - } - - /** - * @param string $token - * @param string $projectName - * @param array $data - * @return int - * @throws \GuzzleHttp\Exception\GuzzleException - * @todo data 数组无法强类型校验内部字段,考虑用对象 - */ - public function createFile(string $token, string $projectName, array $data): array - { - $response = $this->client->request('POST', 'https://e.coding.net/open-api', [ - 'headers' => [ - 'Accept' => 'application/json', - 'Authorization' => "token ${token}", - 'Content-Type' => 'application/json' - ], - 'json' => array_merge([ - 'Action' => 'CreateFile', - 'ProjectName' => $projectName, - ], $data), - ]); - $result = json_decode($response->getBody(), true); - return $result['Response']['Data']; - } - - public function uploadAttachments(string $token, string $projectName, string $dataDir, array $attachments): array - { - if (empty($attachments)) { - return []; - } - $data = []; - // TODO hard code folder name - $folderId = $this->createFolder($token, $projectName, 'wiki-attachments'); - foreach ($attachments as $path => $filename) { - $uploadToken = $this->createUploadToken( - $token, - $projectName, - $filename - ); - $filePath = $dataDir . DIRECTORY_SEPARATOR . $path; - $result = []; - try { - $this->upload($uploadToken, $filePath); - $result = $this->createFile($token, $projectName, [ - "OriginalFileName" => $filename, - "MimeType" => mime_content_type($filePath), - "FileSize" => filesize($filePath), - "StorageKey" => $uploadToken['StorageKey'], - "Time" => $uploadToken['Time'], - "AuthToken" => $uploadToken['AuthToken'], - "FolderId" => $folderId, - ]); - } catch (\Exception $e) { - // TODO laravel log - error_log('ERROR: ' . $e->getMessage()); - } - $data[$path] = $result; - } - return $data; - } -} diff --git a/app/Coding/Iteration.php b/app/Coding/Iteration.php deleted file mode 100644 index 8ec9031..0000000 --- a/app/Coding/Iteration.php +++ /dev/null @@ -1,14 +0,0 @@ -year == $endAt->year ? 'm/d' : 'Y/m/d'; - return $startAt->format('Y/m/d') . '-' . $endAt->format($endFormat) . ' 迭代'; - } -} diff --git a/app/Coding/Wiki.php b/app/Coding/Wiki.php deleted file mode 100644 index eac9c32..0000000 --- a/app/Coding/Wiki.php +++ /dev/null @@ -1,204 +0,0 @@ -client->request('POST', 'https://e.coding.net/open-api', [ - 'headers' => [ - 'Accept' => 'application/json', - 'Authorization' => "token ${token}", - 'Content-Type' => 'application/json' - ], - 'json' => array_merge([ - 'Action' => 'CreateWiki', - 'ProjectName' => $projectName, - ], $data), - ]); - return json_decode($response->getBody(), true)['Response']['Data']; - } - - public function createMarkdownZip($markdown, $path, $markdownFilename, $title): bool|string - { - $zipFileFullPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $markdownFilename . '-' . Str::uuid() . '.zip'; - if ($this->zipArchive->open($zipFileFullPath, ZipArchive::CREATE) !== true) { - Log::error("cannot open <$zipFileFullPath>"); - return false; - } - $this->zipArchive->addFromString($markdownFilename, $markdown); - preg_match_all('/!\[\]\(([a-z0-9\/\._\-]+)\)/', $markdown, $matches); - if (!empty($matches)) { - foreach ($matches[1] as $attachment) { - // markdown image title:  - $tmp = explode(' ', $attachment); - $filename = $tmp[0]; - $filepath = $path . DIRECTORY_SEPARATOR . $filename; - if (!file_exists($filepath)) { - Log::error("文件不存在", ['filename' => $filename, 'title' => $title]); - continue; - } - $this->zipArchive->addFile($filepath, $filename); - } - } - $this->zipArchive->close(); - return $zipFileFullPath; - } - - public function createWikiByZip(string $token, string $projectName, array $uploadToken, array $data) - { - $response = $this->client->request('POST', 'https://e.coding.net/open-api', [ - 'headers' => [ - 'Accept' => 'application/json', - 'Authorization' => "token ${token}", - 'Content-Type' => 'application/json' - ], - 'json' => [ - 'Action' => 'CreateWikiByZip', - 'ProjectName' => $projectName, - 'ParentIid' => $data['ParentIid'], - 'FileName' => $data['FileName'], - 'Key' => $uploadToken['StorageKey'], - 'Time' => $uploadToken['Time'], - 'AuthToken' => $uploadToken['AuthToken'], - ], - ]); - $result = json_decode($response->getBody(), true); - if (!isset($result['Response']['JobId'])) { - return new Exception('failed'); - } - return $result['Response']; - } - - /** - * 获取 Wiki 导入任务的进度(API 文档未展示,其实此接口已上线) - * - * @param string $token - * @param string $projectName - * @param string $jobId - * @return mixed - * @throws \GuzzleHttp\Exception\GuzzleException - * @throws Exception - */ - public function getImportJobStatus(string $token, string $projectName, string $jobId) - { - $response = $this->client->request('POST', 'https://e.coding.net/open-api', [ - 'headers' => [ - 'Accept' => 'application/json', - 'Authorization' => "token ${token}", - 'Content-Type' => 'application/json' - ], - 'json' => [ - 'Action' => 'DescribeImportJobStatus', - 'ProjectName' => $projectName, - 'JobId' => $jobId, - ], - ]); - $result = json_decode($response->getBody(), true); - if (isset($result['Response']['Error']['Message'])) { - throw new Exception($result['Response']['Error']['Message']); - } - return $result['Response']['Data']; - } - - public function getImportJobStatusWithRetry(string $token, string $projectName, string $jobId, int $retry = 10) - { - $waitingTimes = 0; - while (true) { - // HACK 如果上传成功立即查询,会报错:invoke function - sleep(1); - try { - $jobStatus = $this->getImportJobStatus($token, $projectName, $jobId); - if (in_array($jobStatus['Status'], ['wait_process', 'processing']) && $waitingTimes < $retry) { - $waitingTimes++; - continue; - } - return $jobStatus; - } catch (Exception $e) { - if ($waitingTimes < 10) { - $waitingTimes++; - continue; - } - throw $e; - } - break; - } - } - public function createWikiByUploadZip(string $token, string $projectName, string $zipFileFullPath, int $parentId) - { - $zipFilename = basename($zipFileFullPath); - $uploadToken = $this->createUploadToken( - $token, - $projectName, - $zipFilename - ); - $this->upload($uploadToken, $zipFileFullPath); - return $this->createWikiByZip($token, $projectName, $uploadToken, [ - 'ParentIid' => $parentId, - 'FileName' => $zipFilename, - ]); - } - - public function getWiki(string $token, string $projectName, int $id, int $version = 1) - { - $response = $this->client->request('POST', 'https://e.coding.net/open-api', [ - 'headers' => [ - 'Accept' => 'application/json', - 'Authorization' => "token ${token}", - 'Content-Type' => 'application/json' - ], - 'json' => [ - 'Action' => 'DescribeWiki', - 'ProjectName' => $projectName, - 'Iid' => $id, - 'VersionId' => $version, - ], - ]); - $result = json_decode($response->getBody(), true); - return $result['Response']['Data']; - } - - public function updateTitle(string $token, string $projectName, int $id, string $title): bool - { - $response = $this->client->request('POST', 'https://e.coding.net/open-api', [ - 'headers' => [ - 'Accept' => 'application/json', - 'Authorization' => "token ${token}", - 'Content-Type' => 'application/json' - ], - 'json' => [ - 'Action' => 'ModifyWikiTitle', - 'ProjectName' => $projectName, - 'Iid' => $id, - 'Title' => $title, - ], - ]); - $result = json_decode($response->getBody(), true); - return $result['Response']['Data']['Title'] == $title; - } - - public function replaceAttachments(string $markdown, array $codingAttachments): string - { - if (empty($codingAttachments)) { - return $markdown; - } - $markdown .= "\n\nAttachments\n---\n\n"; - foreach ($codingAttachments as $attachmentPath => $codingAttachment) { - $resourceCode = $codingAttachment['ResourceCode'] ?? 0; - $filename = $codingAttachment['FileName'] ?? '此文件迁移失败'; - $markdown .= "- #${resourceCode} ${filename}\n"; - $markdown = preg_replace( - "|\[.*\]\(${attachmentPath}\)|", - " #${resourceCode} `${filename}`", - $markdown - ); - } - return $markdown; - } -} diff --git a/app/Commands/ConfluenceHtml2MarkdownCommand.php b/app/Commands/ConfluenceHtml2MarkdownCommand.php deleted file mode 100644 index 948ac29..0000000 --- a/app/Commands/ConfluenceHtml2MarkdownCommand.php +++ /dev/null @@ -1,42 +0,0 @@ -argument('html_path'); - $dataDir = dirname($htmlPath); - $page = basename($htmlPath); - $markdown = $confluence->htmlFile2Markdown($htmlPath); - $mdFilename = substr($page, 0, -5) . '.md'; - $mdPath = $dataDir . DIRECTORY_SEPARATOR . $mdFilename; - file_put_contents($mdPath, $markdown . "\n"); - $this->info($mdPath); - return 0; - } -} diff --git a/app/Commands/IssueCreateCommand.php b/app/Commands/IssueCreateCommand.php deleted file mode 100644 index c883013..0000000 --- a/app/Commands/IssueCreateCommand.php +++ /dev/null @@ -1,70 +0,0 @@ -setCodingApi(); - $codingIssue->setToken($this->codingToken); - - $data = [ - 'ProjectName' => $this->codingProjectUri, - ]; - $data['Type'] = $this->option('type') ?? $this->choice( - '类型:', - ['DEFECT', 'REQUIREMENT', 'MISSION', 'EPIC', 'SUB_TASK'], - 0 - ); - $data['Name'] = $this->option('name') ?? $this->ask('标题:'); - $data['Priority'] = $this->option('priority') ?? $this->choice( - '优先级:', - ['0', '1', '2', '3'], - 0 - ); - - try { - $result = $codingIssue->create($data); - } catch (\Exception $e) { - $this->error('Error: ' . $e->getMessage()); - return 1; - } - - $this->info('创建成功'); - $this->info("https://{$this->codingTeamDomain}.coding.net/p/{$this->codingProjectUri}" . - "/all/issues/${result['Code']}"); - - return 0; - } -} diff --git a/app/Commands/IssueImportCommand.php b/app/Commands/IssueImportCommand.php deleted file mode 100644 index 6b433ab..0000000 --- a/app/Commands/IssueImportCommand.php +++ /dev/null @@ -1,161 +0,0 @@ -setCodingApi(); - $codingIssue->setToken($this->codingToken); - $iteration->setToken($this->codingToken); - $projectSetting->setToken($this->codingToken); - - $filePath = $this->argument('file'); - if (!file_exists($filePath)) { - $this->error("文件不存在:$filePath"); - return 1; - } - - $rows = FastExcel::import($filePath); - if (!empty($rows) && isset($rows[0]['ID'])) { - $rows = $rows->sortBy('ID'); - } - foreach ($rows as $row) { - try { - $issueResult = $this->createIssueByRow($projectSetting, $codingIssue, $iteration, $row); - } catch (Exception $e) { - $this->error('Error: ' . $e->getMessage()); - return 1; - } - $this->info('标题:' . $row['标题']); - $this->info("https://{$this->codingTeamDomain}.coding.net/p/{$this->codingProjectUri}" . - "/all/issues/${issueResult['Code']}"); - } - - return 0; - } - - private function getIssueTypes(ProjectSetting $projectSetting, array $row): void - { - if (empty($this->issueTypes)) { - $result = $projectSetting->getIssueTypes(['ProjectName' => $this->codingProjectUri]); - foreach ($result as $item) { - $this->issueTypes[$item['Name']] = $item; - } - } - if (!isset($this->issueTypes[$row['事项类型']])) { - throw new Exception('「' . $row['事项类型'] . '」类型不存在,请在项目设置中添加'); - } - } - - private function getStatusId(ProjectSetting $projectSetting, string $issueTypeName, string $statusName): int - { - if (!isset($this->issueTypeStatus[$issueTypeName])) { - $type = $this->issueTypes[$issueTypeName]['IssueType']; - $typeId = $this->issueTypes[$issueTypeName]['Id']; - $result = $projectSetting->getIssueStatus([ - 'ProjectName' => $this->codingProjectUri, - 'IssueType' => $type, - 'IssueTypeId' => $typeId - ]); - foreach ($result as $item) { - $tmp = $item['IssueStatus']; - $this->issueTypeStatus[$issueTypeName][$tmp['Name']] = $tmp['Id']; - } - } - if (!isset($this->issueTypeStatus[$issueTypeName][$statusName])) { - throw new Exception('「' . $statusName . '」不存在,请在设置中添加'); - } - return intval($this->issueTypeStatus[$issueTypeName][$statusName]); - } - - private function createIssueByRow(ProjectSetting $projectSetting, Issue $issue, Iteration $iteration, array $row) - { - $this->getIssueTypes($projectSetting, $row); - $data = [ - 'ProjectName' => $this->codingProjectUri, - 'Type' => $this->issueTypes[$row['事项类型']]['IssueType'], - 'IssueTypeId' => $this->issueTypes[$row['事项类型']]['Id'], - 'Name' => $row['标题'], - ]; - if (!empty($row['优先级'])) { - $data['Priority'] = \App\Models\Issue::PRIORITY_MAP[$row['优先级']]; - } - if (!empty($row['所属迭代'])) { - $data['IterationCode'] = $this->getIterationCode($iteration, $row['所属迭代']); - } - if (!empty($row['ParentCode'])) { - $data['ParentCode'] = $this->issueCodeMap[$row['ParentCode']]; - } - foreach ( - [ - 'Description' => '描述', - 'DueDate' => '截止日期', - 'StartDate' => '开始日期', - 'StoryPoint' => '故事点', - ] as $english => $chinese - ) { - if (!empty($row[$chinese])) { - $data[$english] = $row[$chinese]; - } - } - if (!empty($row['状态'])) { - $data['StatusId'] = $this->getStatusId($projectSetting, $row['事项类型'], $row['状态']); - } - $result = $issue->create($data); - if (isset($row['ID'])) { - $this->issueCodeMap[$row['ID']] = intval($result['Code']); - } - return $result; - } - - private function getIterationCode(Iteration $iteration, string $name) - { - if (!isset($this->iterationMap[$name])) { - $result = $iteration->create([ - 'ProjectName' => $this->codingProjectUri, - 'Name' => $name, - ]); - $this->iterationMap[$name] = $result['Code']; - } - return $this->iterationMap[$name]; - } -} diff --git a/app/Commands/IterationCreateCommand.php b/app/Commands/IterationCreateCommand.php deleted file mode 100644 index bc1745a..0000000 --- a/app/Commands/IterationCreateCommand.php +++ /dev/null @@ -1,68 +0,0 @@ -setCodingApi(); - $iteration->setToken($this->codingToken); - - $data = [ - 'ProjectName' => $this->codingProjectUri, - ]; - $startAt = Carbon::parse($this->option('start_at') ?? $this->ask('开始时间:', Carbon::today()->toDateString())); - $data['StartAt'] = $startAt->toDateString(); - $endAt = Carbon::parse($this->option('end_at') ?? $this->ask( - '结束时间:', - Carbon::today()->addDays(14)->toDateString() - )); - $data['EndAt'] = $endAt->toDateString(); - $data['Name'] = $this->option('name') ?? $this->ask('标题:', LocalIteration::generateName($startAt, $endAt)); - $data['Goal'] = $this->option('goal'); - $data['Assignee'] = $this->option('assignee'); - - $result = $iteration->create($data); - - $this->info('创建成功'); - $this->info("https://{$this->codingTeamDomain}.coding.net/p/{$this->codingProjectUri}" . - "/iterations/${result['Code']}/issues"); - - return 0; - } -} diff --git a/app/Commands/ProjectGetIssueTypesCommand.php b/app/Commands/ProjectGetIssueTypesCommand.php deleted file mode 100644 index ff7b081..0000000 --- a/app/Commands/ProjectGetIssueTypesCommand.php +++ /dev/null @@ -1,47 +0,0 @@ -setCodingApi(); - $projectSetting->setToken($this->codingToken); - - $result = $projectSetting->getIssueTypes(['ProjectName' => $this->codingProjectUri]); - - foreach ($result as $item) { - $this->info($item['Id'] . ' ' . $item['Name']); - } - - return 0; - } -} diff --git a/app/Commands/WikiImportCommand.php b/app/Commands/WikiImportCommand.php deleted file mode 100644 index b49b3ea..0000000 --- a/app/Commands/WikiImportCommand.php +++ /dev/null @@ -1,324 +0,0 @@ -codingDisk = $codingDisk; - $this->codingWiki = $codingWiki; - $this->confluence = $confluence; - $this->document = $document; - $this->setCodingApi(); - - $provider = $this->option('coding_import_provider'); - if (is_null($provider)) { - $provider = config('coding.import.provider') ?? $this->choice( - '数据来源?', - ['Confluence', 'MediaWiki'], - 0 - ); - } - if ($provider != 'Confluence') { - $this->error('TODO'); - return 1; - } - - $dataType = $this->option('coding_import_data_type'); - if (is_null($dataType)) { - $dataType = config('coding.import.data_type') ?? $this->choice( - '数据类型?', - ['HTML', 'API'], - 0 - ); - } - switch ($dataType) { - case 'HTML': - $this->handleConfluenceHtml(); - break; - case 'API': - $this->handleConfluenceApi(); - break; - default: - break; - } - if (!empty($this->errors)) { - $this->info('报错信息汇总:'); - } - foreach ($this->errors as $error) { - $this->error($error); - } - return count($this->errors); - } - - private function createWiki($data) - { - $result = $this->codingWiki->createWiki($this->codingToken, $this->codingProjectUri, $data); - $path = $result['Path']; - $this->info("https://{$this->codingTeamDomain}.coding.net/p/{$this->codingProjectUri}/wiki/${path}"); - } - - private function handleConfluenceApi(): int - { - $baseUri = $this->option('confluence_base_uri'); - if (is_null($baseUri)) { - $baseUri = config('confluence.base_uri') ?? $this->ask( - 'Confluence API 链接:', - 'http://localhost:8090/rest/api/' - ); - } - config(['confluence.base_uri' => $baseUri]); - - $username = $this->option('confluence_username'); - if (is_null($username)) { - $username = config('confluence.username') ?? $this->ask('Confluence 账号:', 'admin'); - } - $password = $this->option('confluence_password'); - if (is_null($password)) { - $password = config('confluence.password') ?? $this->ask('Confluence 密码:', '123456'); - } - config(['confluence.auth' => [$username, $password]]); - - $data = Confluence::resource(Content::class)->index(); - $this->info("已获得 ${data['size']} 条数据"); - if ($data['size'] == 0) { - return 0; - } - $this->info("开始导入 CODING:"); - foreach ($data['results'] as $result) { - $content = Confluence::resource(Content::class)->show($result['id'], ['expand' => 'body.storage']); - $this->createWiki([ - 'Title' => $content['title'], - 'Content' => $content['body']['storage']['value'], - 'ParentIid' => 0, - ]); - } - return 0; - } - - private function handleConfluenceHtml(): int - { - $path = $this->unzipConfluenceHtml(); - if (str_ends_with($path, '.html')) { - return $this->uploadConfluencePage($path); - } - $htmlDir = $path; - $filePath = $htmlDir . DIRECTORY_SEPARATOR . 'index.html'; - if (!file_exists($filePath)) { - $message = "文件不存在:$filePath"; - $this->error($message); - $this->errors[] = $message; - return 1; - } - try { - libxml_use_internal_errors(true); - $this->document->loadHTMLFile($filePath); - $mainContent = $this->document->getElementById('main-content'); - $trList = $mainContent->getElementsByTagName('tr'); - $space = []; - foreach ($trList as $tr) { - if ($tr->getElementsByTagName('th')[0]->nodeValue == 'Key') { - $space['key'] = $tr->getElementsByTagName('td')[0]->nodeValue; - } elseif ($tr->getElementsByTagName('th')[0]->nodeValue == 'Name') { - $space['name'] = $tr->getElementsByTagName('td')[0]->nodeValue; - } - } - $this->info('空间名称:' . $space['name']); - $this->info('空间标识:' . $space['key']); - - $pages = $this->confluence->parseAvailablePages($filePath); - if (empty($pages['tree'])) { - $this->info("未发现有效数据"); - return 0; - } - $this->info('发现 ' . count($pages['tree']) . ' 个一级页面'); - $this->info("开始导入 CODING:"); - $this->clean($htmlDir); - $this->uploadConfluencePages($htmlDir, $pages['tree'], $pages['titles']); - } catch (\ErrorException $e) { - $this->error($e->getMessage()); - return 1; - } - - return 0; - } - - private function clean(string $htmlDir): void - { - if ($this->option('clean')) { - File::delete($htmlDir . DIRECTORY_SEPARATOR . 'success.log'); - } - if (file_exists($htmlDir . DIRECTORY_SEPARATOR . 'success.log')) { - $this->importedPages = parse_ini_file($htmlDir . DIRECTORY_SEPARATOR . 'success.log'); - } - } - - private function uploadConfluencePages(string $htmlDir, array $tree, array $titles, int $parentId = 0): void - { - foreach ($tree as $page => $subPages) { - $title = $titles[$page]; - $wikiId = $this->uploadConfluencePage($htmlDir . DIRECTORY_SEPARATOR . $page, $title, $parentId); - if ($wikiId && !empty($subPages)) { - $this->info('发现 ' . count($subPages) . ' 个子页面'); - // TODO tests - $this->uploadConfluencePages($htmlDir, $subPages, $titles, $wikiId); - } - } - } - - private function uploadConfluencePage(string $filePath, string $title = '', int $parentId = 0): int - { - $page = basename($filePath); - if (!$this->option('clean') && isset($this->importedPages[$page])) { - $this->warn('断点续传,跳过页面:' . $page); - return $this->importedPages[$page]; - } - try { - $markdown = $this->confluence->htmlFile2Markdown($filePath); - } catch (FileNotFoundException $e) { - $message = '页面不存在:' . $filePath; - $this->error($message); - $this->errors[] = $message; - return false; - } - libxml_use_internal_errors(true); - $this->document->loadHTMLFile($filePath); - if (empty($title)) { - $title = $this->document->getElementsByTagName('title')[0]->nodeValue; - } - $this->info('标题:' . $title); - - $htmlDir = dirname($filePath); - $markdown = $this->dealAttachments($filePath, $markdown); - $mdFilename = substr($page, 0, -5) . '.md'; - if ($this->option('save-markdown')) { - file_put_contents($htmlDir . DIRECTORY_SEPARATOR . $mdFilename, $markdown . "\n"); - } - $zipFilePath = $this->codingWiki->createMarkdownZip($markdown, $htmlDir, $mdFilename, $title); - $result = $this->codingWiki->createWikiByUploadZip( - $this->codingToken, - $this->codingProjectUri, - $zipFilePath, - $parentId, - ); - $this->info('上传成功,正在处理,任务 ID:' . $result['JobId']); - try { - $jobStatus = $this->codingWiki->getImportJobStatusWithRetry( - $this->codingToken, - $this->codingProjectUri, - $result['JobId'] - ); - if ($jobStatus['Status'] != 'success') { - throw new Exception('job status ' . $jobStatus['Status']); - } - $wikiId = intval($jobStatus['Iids'][0]); - } catch (Exception $e) { - $message = '错误:导入失败,跳过 ' . $title . ' ' . $page; - $this->error($message); - $this->errors[] = $message; - return false; - } - $this->codingWiki->updateTitle($this->codingToken, $this->codingProjectUri, $wikiId, $title); - file_put_contents($htmlDir . DIRECTORY_SEPARATOR . 'success.log', "$page = $wikiId\n", FILE_APPEND); - return $wikiId; - } - - private function unzipConfluenceHtml(): string - { - $dataPath = $this->option('coding_import_data_path'); - if (is_null($dataPath)) { - $dataPath = config('coding.import.data_path') ?? trim($this->ask( - '空间导出的 HTML zip 文件路径', - './confluence/space1.zip' - )); - } - - if (str_ends_with($dataPath, '.zip')) { - $zip = new ZipArchive(); - $zip->open($dataPath); - $tmpDir = sys_get_temp_dir() . '/confluence-' . Str::uuid(); - mkdir($tmpDir); - for ($i = 0; $i < $zip->numFiles; $i++) { - // HACK crash when zip include root path / - if ($zip->getNameIndex($i) != '/' && $zip->getNameIndex($i) != '__MACOSX/_') { - $zip->extractTo($tmpDir, [$zip->getNameIndex($i)]); - } - } - $zip->close(); - return $tmpDir . '/' . scandir($tmpDir, 1)[0] . '/'; - } - return rtrim($dataPath, '/'); - } - - private function dealAttachments(string $filePath, string $markdown): string - { - $attachments = $this->confluence->parseAttachments($filePath, $markdown); - $codingAttachments = $this->codingDisk->uploadAttachments( - $this->codingToken, - $this->codingProjectUri, - dirname($filePath), - $attachments - ); - foreach ($codingAttachments as $attachmentPath => $codingAttachment) { - if (empty($codingAttachment)) { - $message = '错误:文件上传失败 ' . $attachmentPath; - $this->error($message); - $this->errors[] = $message; - } - } - return $this->codingWiki->replaceAttachments($markdown, $codingAttachments); - } -} diff --git a/app/Commands/WikiUploadCommand.php b/app/Commands/WikiUploadCommand.php deleted file mode 100644 index bf6342c..0000000 --- a/app/Commands/WikiUploadCommand.php +++ /dev/null @@ -1,61 +0,0 @@ -codingDisk = $codingDisk; - $this->codingWiki = $codingWiki; - $this->setCodingApi(); - - $filePath = $this->argument('file'); - if (!file_exists($filePath)) { - $this->error("文件不存在:$filePath"); - return 1; - } - $parentId = intval($this->option('parent_id')); - $result = $this->codingWiki->createWikiByUploadZip( - $this->codingToken, - $this->codingProjectUri, - $filePath, - $parentId - ); - $this->info('上传成功,正在处理,任务 ID:' . $result['JobId']); - - return 0; - } -} diff --git a/app/Commands/WithCoding.php b/app/Commands/WithCoding.php deleted file mode 100644 index baaaa1c..0000000 --- a/app/Commands/WithCoding.php +++ /dev/null @@ -1,31 +0,0 @@ -option('coding_team_domain'); - if (is_null($codingTeamDomain)) { - $codingTeamDomain = config('coding.team_domain') ?? $this->ask('CODING 团队域名:'); - } - $this->codingTeamDomain = str_replace('.coding.net', '', "$codingTeamDomain"); - - $codingProjectUri = $this->option('coding_project_uri'); - if (is_null($codingProjectUri)) { - $codingProjectUri = config('coding.project_uri') ?? $this->ask('CODING 项目标识:'); - } - $this->codingProjectUri = "$codingProjectUri"; - - $codingToken = $this->option('coding_token'); - if (is_null($codingToken)) { - $codingToken = config('coding.token') ?? $this->ask('CODING Token:'); - } - $this->codingToken = "$codingToken"; - } -} diff --git a/app/Confluence.php b/app/Confluence.php deleted file mode 100644 index f13c092..0000000 --- a/app/Confluence.php +++ /dev/null @@ -1,158 +0,0 @@ -document = $document ?? new DOMDocument(); - $this->htmlConverter = $htmlConverter ?? new HtmlConverter(); - $this->htmlConverter->getConfig()->setOption('strip_tags', true); - $this->htmlConverter->getEnvironment()->addConverter(new TableConverter()); - } - - public function parsePageHtml(string $filename, string $spaceName): array - { - libxml_use_internal_errors(true); - $this->document->loadHTMLFile($filename); - $title = trim($this->document->getElementById('title-text')->nodeValue); - $title = str_replace($spaceName . ' : ', '', $title); - - $content = trim($this->document->getElementById('main-content')->nodeValue); - return [ - 'title' => $title, - 'content' => $content, - ]; - } - - public function htmlFile2Markdown(string $filename): string - { - $html = preg_replace( - [ - '|
|', - '|Hello World!
foo foo
", - "Id" : 1325288, - "Iid" : 27, - "LastVersion" : 1, - "Msg" : "", - "Order" : 2, - "ParentIid" : 0, - "ParentShared" : false, - "ParentVisibleRange" : "PUBLIC", - "Path" : "27", - "Title" : "foo by curl", - "UpdatedAt" : 1625214079014, - "VisibleRange" : "INHERIT" - }, - "RequestId" : "a50c8805-8e1f-fc4d-f965-855e5a3cf709" - } -} \ No newline at end of file diff --git a/tests/data/coding/DescribeImportJobStatusResponse.json b/tests/data/coding/DescribeImportJobStatusResponse.json deleted file mode 100644 index 34c5d58..0000000 --- a/tests/data/coding/DescribeImportJobStatusResponse.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Response" : { - "Data": { - "JobId": "123456ad-f123-4ac2-9586-42ebe5d1234d", - "Status": "success", - "Iids": [27] - }, - "RequestId": "78d6ecf8-9123-574f-8da9-23bb44c467f1" - } -} \ No newline at end of file diff --git a/tests/data/coding/DescribeImportJobStatusResponseError.json b/tests/data/coding/DescribeImportJobStatusResponseError.json deleted file mode 100644 index 26ea759..0000000 --- a/tests/data/coding/DescribeImportJobStatusResponseError.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Response" : { - "Error" : { - "Code" : "InvalidParameterValue", - "Message" : "资源未找到" - }, - "RequestId" : "cca345b2-762e-c257-da19-b98d27a62ab2" - } -} \ No newline at end of file diff --git a/tests/data/coding/DescribeProjectIssueStatusListResponse.json b/tests/data/coding/DescribeProjectIssueStatusListResponse.json deleted file mode 100644 index 09c446a..0000000 --- a/tests/data/coding/DescribeProjectIssueStatusListResponse.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "Response" : { - "ProjectIssueStatusList" : [ - { - "CreatedAt" : 1634639726000, - "IsDefault" : true, - "IssueStatus" : { - "CreatedAt" : 1572178128000, - "Description" : "", - "Id" : 1227034, - "Index" : 3, - "IsSystem" : true, - "Name" : "未开始", - "Type" : "TODO", - "UpdatedAt" : 1572178128000 - }, - "IssueStatusId" : 1227034, - "IssueType" : "REQUIREMENT", - "Sort" : 0, - "UpdatedAt" : 1634639726000 - }, - { - "CreatedAt" : 1634639726000, - "IsDefault" : false, - "IssueStatus" : { - "CreatedAt" : 1572178128000, - "Description" : "", - "Id" : 1227037, - "Index" : 4, - "IsSystem" : true, - "Name" : "处理中", - "Type" : "PROCESSING", - "UpdatedAt" : 1572178128000 - }, - "IssueStatusId" : 1227037, - "IssueType" : "REQUIREMENT", - "Sort" : 0, - "UpdatedAt" : 1634639726000 - }, - { - "CreatedAt" : 1634639726000, - "IsDefault" : false, - "IssueStatus" : { - "CreatedAt" : 1572178128000, - "Description" : "", - "Id" : 1227040, - "Index" : 5, - "IsSystem" : true, - "Name" : "待验证", - "Type" : "PROCESSING", - "UpdatedAt" : 1572178128000 - }, - "IssueStatusId" : 1227040, - "IssueType" : "REQUIREMENT", - "Sort" : 0, - "UpdatedAt" : 1634639726000 - }, - { - "CreatedAt" : 1634639726000, - "IsDefault" : false, - "IssueStatus" : { - "CreatedAt" : 1572178128000, - "Description" : "", - "Id" : 1227058, - "Index" : 11, - "IsSystem" : true, - "Name" : "已完成", - "Type" : "COMPLETED", - "UpdatedAt" : 1572178128000 - }, - "IssueStatusId" : 1227058, - "IssueType" : "REQUIREMENT", - "Sort" : 0, - "UpdatedAt" : 1634639726000 - } - ], - "RequestId" : "3a8bb049-e28f-01c9-9990-0f17b92952c4" - } -} diff --git a/tests/data/coding/DescribeProjectIssueTypeListResponse.json b/tests/data/coding/DescribeProjectIssueTypeListResponse.json deleted file mode 100644 index d18c46c..0000000 --- a/tests/data/coding/DescribeProjectIssueTypeListResponse.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "Response" : { - "IssueTypes" : [ - { - "Description" : "史诗是一个较大的功能或特性,可以分解为多个较小的需求或任务。通常其需要分多次迭代才可完成。", - "Id" : 213217, - "IsSystem" : true, - "IssueType" : "EPIC", - "Name" : "史诗", - "SplitTargetIssueTypeId" : [], - "SplitType" : "UNSPLITTABLE" - }, - { - "Description" : "用户故事是敏捷框架中最小的工作单元,是从用户角度描述软件如何为其带来特定的价值。", - "Id" : 213218, - "IsSystem" : true, - "IssueType" : "REQUIREMENT", - "Name" : "用户故事", - "SplitTargetIssueTypeId" : [], - "SplitType" : "ALL_REQUIREMENT" - }, - { - "Description" : "任务是指为实现某个目标或需求所进行的具体活动。", - "Id" : 213220, - "IsSystem" : true, - "IssueType" : "MISSION", - "Name" : "任务", - "SplitTargetIssueTypeId" : [], - "SplitType" : "UNSPLITTABLE" - }, - { - "Description" : "缺陷是指软件不符合最初定义的业务需求的现象,缺陷管理用于跟踪这些问题和错误。", - "Id" : 213221, - "IsSystem" : true, - "IssueType" : "DEFECT", - "Name" : "缺陷", - "SplitTargetIssueTypeId" : [], - "SplitType" : "UNSPLITTABLE" - }, - { - "Description" : "在敏捷模式下,将一个事项拆分成更小的块。", - "Id" : 213222, - "IsSystem" : true, - "IssueType" : "SUB_TASK", - "Name" : "子工作项", - "SplitTargetIssueTypeId" : [], - "SplitType" : "UNSPLITTABLE" - } - ], - "RequestId" : "9f7e8405-943d-fb02-96bf-3ee3c63e0fe6" - } -} diff --git a/tests/data/coding/DescribeWikiResponse.json b/tests/data/coding/DescribeWikiResponse.json deleted file mode 100644 index c409b97..0000000 --- a/tests/data/coding/DescribeWikiResponse.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "Response" : { - "Data" : { - "CanMaintain" : true, - "CanRead" : true, - "Content" : "foo foo", - "CreatedAt" : 1626070084000, - "Creator" : { - "Avatar" : "https://coding-net-production-static-ci.codehub.cn/2cb665a3-bebc-4b09-aa00-2b6df3e33edc.jpg?imageMogr2/auto-orient/format/jpeg/cut/400x400x0x0", - "Email" : "", - "GlobalKey" : "KMRnIKgzbV", - "Id" : 183478, - "Name" : "sinkcup", - "Phone" : "", - "RequestId" : "", - "Status" : "ACTIVE", - "TeamId" : 0 - }, - "CreatorId" : 0, - "CurrentUserRoleId" : 0, - "CurrentVersion" : 1, - "Editor" : { - "Avatar" : "https://coding-net-production-static-ci.codehub.cn/2cb665a3-bebc-4b09-aa00-2b6df3e33edc.jpg?imageMogr2/auto-orient/format/jpeg/cut/400x400x0x0", - "Email" : "", - "GlobalKey" : "KMRnIKgzbV", - "Id" : 183478, - "Name" : "sinkcup", - "Phone" : "", - "RequestId" : "", - "Status" : "ACTIVE", - "TeamId" : 0 - }, - "EditorId" : 0, - "HistoriesCount" : 1, - "HistoryId" : 2755733, - "Html" : "foo foo
", - "Id" : 1362209, - "Iid" : 148, - "LastVersion" : 1, - "Msg" : "", - "Order" : 120, - "ParentIid" : 0, - "ParentShared" : false, - "ParentVisibleRange" : "PUBLIC", - "Path" : "148", - "Title" : "foo by curl", - "UpdatedAt" : 1626070084000, - "VisibleRange" : "INHERIT" - }, - "RequestId" : "555fd71a-8719-f74f-49b2-097726e5ebe9" - } -} diff --git a/tests/data/coding/ModifyWikiTitleResponse.json b/tests/data/coding/ModifyWikiTitleResponse.json deleted file mode 100644 index 5a19a88..0000000 --- a/tests/data/coding/ModifyWikiTitleResponse.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Response" : { - "Data" : { - "HistoriesCount" : 2, - "HistoryId" : 2756351, - "Id" : 1362209, - "Iid" : 148, - "LastVersion" : 2, - "Path" : "148", - "Title" : "new title", - "UpdatedAt" : 1626075508079, - "VisibleRange" : "INHERIT" - }, - "RequestId" : "0509b996-3b9f-8d7f-8c2f-03613327bb1c" - } -} diff --git a/tests/data/coding/scrum-issue-5.csv b/tests/data/coding/scrum-issue-5.csv deleted file mode 100644 index 26d5df2..0000000 --- a/tests/data/coding/scrum-issue-5.csv +++ /dev/null @@ -1,2 +0,0 @@ -ID,事项类型,标题,描述,状态,创建时间,创建人,更新时间,所属迭代,故事点,处理人,缺陷类型,优先级,截止日期,模块,标签,关注人,开始日期 -5,用户故事,用户可通过手机号注册账户,,处理中,2021-10-19 11:26:37,sinkcup,2021-10-19 11:26:37,第 1 次迭代,2,sinkcup,,中,2021-10-21,,,, diff --git a/tests/data/coding/scrum-issues-5-6-7.csv b/tests/data/coding/scrum-issues-5-6-7.csv deleted file mode 100644 index 65d7046..0000000 --- a/tests/data/coding/scrum-issues-5-6-7.csv +++ /dev/null @@ -1,4 +0,0 @@ -ID,ParentCode,事项类型,标题,描述,状态,创建时间,创建人,更新时间,所属迭代,故事点,处理人,缺陷类型,优先级,截止日期,模块,标签,关注人,开始日期 -7,5,子工作项,完成通过手机号注册用户的接口,,已完成,2021-10-19 11:26:38,sinkcup,2021-10-19 11:26:38,,,,,中,,,,, -6,5,子工作项,完成手机号注册的短信验证码发送接口,,已完成,2021-10-19 11:26:38,sinkcup,2021-10-19 11:26:38,,,,,低,,,,, -5,,用户故事,用户可通过手机号注册账户,,处理中,2021-10-19 11:26:37,sinkcup,2021-10-19 11:26:37,,2,sinkcup,,,2021-10-21,,,, diff --git a/tests/data/coding/scrum-issues.csv b/tests/data/coding/scrum-issues.csv deleted file mode 100644 index 17c86a4..0000000 --- a/tests/data/coding/scrum-issues.csv +++ /dev/null @@ -1,32 +0,0 @@ -ID,ParentCode,事项类型,标题,描述,状态,创建时间,创建人,更新时间,所属迭代,故事点,处理人,缺陷类型,优先级,截止日期,模块,标签,关注人,开始日期 -23,,缺陷,商品详情页中商品价格字体应当显示为红色并且加粗,"步骤: - -测试环境中,打开商品列表页; -点击任意商品进详情页。 -测试结果:商品的价格字体显示为正常大小,颜色为黑色。 -预期结果:商品价格字体为红色加粗。",待处理,2021-10-19 11:26:39,sinkcup,2021-10-19 11:26:39,,,,,中,,,,, -22,,缺陷,登录页输入正确的用户名和密码后提示“用户不存在”,"步骤: -测试环境中,输入URL https://mywebsite.com/login 进入登录页; -输入用户名 Admin 和密码 mypassword; -点击“登录”按钮。 -测试结果:页面提示“用户不存在”。 -预期结果:提示“登录成功”并且页面自动跳转到首页。",处理中,2021-10-19 11:26:39,sinkcup,2021-10-19 11:26:39,,,sinkcup,,中,,,,, -21,,任务,编制新功能的帮助文档并发布,,未开始,2021-10-19 11:26:39,sinkcup,2021-10-19 11:26:39,,,,,中,,,,, -20,,任务,编写脚本将 Excel 中的线下订单转换为商城后台订单,,未开始,2021-10-19 11:26:39,sinkcup,2021-10-19 11:26:39,第 2 次迭代,5,,,中,,,,, -19,,任务,注册腾讯云账户,搭建测试环境和生产环境服务器,,处理中,2021-10-19 11:26:39,sinkcup,2021-10-19 11:26:39,第 1 次迭代,5,sinkcup,,中,,,,, -18,,用户故事,用户可对未支付的订单执行取消订单操作,,未开始,2021-10-19 11:26:39,sinkcup,2021-10-19 11:26:39,,,,,中,,,,, -17,,用户故事,管理员可在商城后台对订单执行发货操作,,未开始,2021-10-19 11:26:39,sinkcup,2021-10-19 11:26:39,第 2 次迭代,3,,,中,,,,, -16,,用户故事,用户可在手机端搜索并查看指定的订单详情,,未开始,2021-10-19 11:26:39,sinkcup,2021-10-19 11:26:39,第 2 次迭代,1,,,中,,,,, -15,,用户故事,通过访问邀请链接可注册成为商城用户,,未开始,2021-10-19 11:26:39,sinkcup,2021-10-19 11:26:39,第 2 次迭代,2,,,中,,,,, -14,,用户故事,管理员可取消未发货且状态异常的订单,,未开始,2021-10-19 11:26:38,sinkcup,2021-10-19 11:26:38,第 2 次迭代,3,,,中,,,,, -13,,用户故事,用户可在“个人信息”中编辑个人基本信息,包括修改密码,,未开始,2021-10-19 11:26:38,sinkcup,2021-10-19 11:26:38,第 2 次迭代,2,sinkcup,,中,,,,, -12,,用户故事,管理员可在商城后台搜索订单,,未开始,2021-10-19 11:26:38,sinkcup,2021-10-19 11:26:38,第 1 次迭代,2,,,中,,,,, -11,,用户故事,管理员可在商城后台手工为用户下单,,未开始,2021-10-19 11:26:38,sinkcup,2021-10-19 11:26:38,第 1 次迭代,5,,,中,,,,, -10,,用户故事,用户可在个人中心的“个人信息”中查看个人信息,,已完成,2021-10-19 11:26:38,sinkcup,2021-10-19 11:26:38,第 1 次迭代,1,,,中,,,,, -9,,用户故事,用户可通过短信验证码登录商城,,处理中,2021-10-19 11:26:38,sinkcup,2021-10-19 11:26:38,第 1 次迭代,1,,,中,,,,, -8,5,子工作项,完成用户注册页面控件并集成后端接口,,处理中,2021-10-19 11:26:38,sinkcup,2021-10-19 11:26:38,第 1 次迭代,,,,中,,,,, -7,5,子工作项,完成通过手机号注册用户的接口,,已完成,2021-10-19 11:26:38,sinkcup,2021-10-19 11:26:38,第 1 次迭代,,,,中,,,,, -6,5,子工作项,完成手机号注册的短信验证码发送接口,,已完成,2021-10-19 11:26:38,sinkcup,2021-10-19 11:26:38,第 1 次迭代,,,,中,,,,, -5,,用户故事,用户可通过手机号注册账户,,处理中,2021-10-19 11:26:37,sinkcup,2021-10-19 11:26:37,第 1 次迭代,2,sinkcup,,中,,,,, -2,,史诗,订单管理,订单管理将实现用户的订单列表查询、订单详情、订单改价、订单地址修改、申请售后、订单取消等功能,未开始,2021-10-19 11:26:37,sinkcup,2021-10-19 11:26:37,,,,,中,,,,, -1,,史诗,用户管理,用户管理将实现用户的注册、邀请、用户查询、个人信息管理、删除用户、注销账户等功能。,未开始,2021-10-19 11:26:37,sinkcup,2021-10-19 11:26:37,,,,,中,,,,, diff --git a/tests/data/confluence/Confluence-space-export-231543-81.html.zip b/tests/data/confluence/Confluence-space-export-231543-81.html.zip deleted file mode 100644 index 5c0e439..0000000 Binary files a/tests/data/confluence/Confluence-space-export-231543-81.html.zip and /dev/null differ diff --git a/tests/data/confluence/image-not-exist-demo.md b/tests/data/confluence/image-not-exist-demo.md deleted file mode 100644 index 8223afb..0000000 --- a/tests/data/confluence/image-not-exist-demo.md +++ /dev/null @@ -1 +0,0 @@ - world diff --git a/tests/data/confluence/recent-space-activity-demo.html b/tests/data/confluence/recent-space-activity-demo.html deleted file mode 100644 index b97017b..0000000 --- a/tests/data/confluence/recent-space-activity-demo.html +++ /dev/null @@ -1,135 +0,0 @@ - - - -When you create new pages in this space, they'll appear here automatically.
--
-
This is a demo page with attachment. Lorem Ipsum 2021-06-08T10_55_27+0800.txt -
- -Hello World!
-hello
-Lorem Ipsum is simply dummy text of the - printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever - since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type - specimen book. It has survived not only five centuries, but also the leap into electronic - typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of - Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software - like Aldus PageMaker including versions of Lorem Ipsum.
-It is a long established fact that a reader will be distracted by the - readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has - a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', - making it look like readable English. Many desktop publishing packages and web page editors now use - Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites - still in their infancy. Various versions have evolved over the years, sometimes by accident, - sometimes on purpose (injected humour and the like).
--
-
world
-- -
-
Key | -space1 | -
---|---|
Name | -空间 1 | -
Description | -- |
Created by | -admin (六月 04, 2021) | -
hello
-请求参数:
-参数名称 | 是否必传 | 说明 |
---|---|---|
order_id | 是 | 申请单ID |
- -
返回参数说明:
参数名称 | 类型 | 说明 |
---|---|---|
code | string | 申请单号 |
created_at | datetime | 申请时间 |
images | list | 图片 |
-