您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Auto Deploy for Van!
当前为
// ==UserScript== // @name Van Deploy // @namespace http://tampermonkey.net/ // @version 0.5.3 // @description Auto Deploy for Van! // @author Alexander // @match https://van.huolala.work/projects/835/* // @match https://van.huolala.work/projects/833/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant none // @license MIT // ==/UserScript== const getStorage = (name) => { // const cookieObj = document.cookie.split(';').reduce((acc, cur) => { // const [key, value] = cur.split('='); // acc[key.trim()] = value; // return acc; // } , {}) // return cookieObj[name || ''] || ''; return localStorage.getItem(name || '') || ''; } const setStorage = (name, value) => { // const cookieObj = document.cookie.split(';').reduce((acc, cur) => { // const [key, value] = cur.split('='); // acc[key.trim()] = value; // return acc; // } , {}) // cookieObj[name] = value; // const newCookie = Object.keys(cookieObj).map(i => `${i}=${cookieObj[i]}`).join(';'); // document.cookie = newCookie; localStorage.setItem(name, value); } (function() { 'use strict'; // console.log(' van deploy is running ') let monitor const startTime = new Date().getTime() const sleep = (time = 1000) => new Promise(resolve => setTimeout(resolve, time)); const committerDeployMap = { 'pk.peng': 'stable-1', 'alexander.xie': 'stable-2', 'james.fu': 'stable-3', 'edison.ma': 'stable-4', } class DeployTask { constructor(config) { this.id = config.id // building id this.branch = config.branch // branch this.committer = config.user // committer this.startTime = new Date().getTime() // this.config = config this.status = 'pending' // pending, success, failed, deploying , deployed, undeployed this.queryId = undefined this.deployInterval = undefined this.start() } donnotDeployToMyEnv() { return document.getElementById('deployEnv')?.checked } getDom() { const taskDom = Array.from(document.getElementsByTagName('strong')).find(dom => dom.innerText === this.id) return taskDom ? taskDom.parentNode.parentNode : null } async start () { try { console.log('task ', this.id, ' start') this.status = 'pending' await this.query() await this.queueForDeply() console.log('task is deploying ', this.id) this.status = 'deploying' await this.deploy() this.stop() } catch (error) { console.log('deploy error ', error) this.stop() } } async query() { return new Promise((resolve, reject) => { this.queryId = setInterval(() => { console.log(this.id, ' building status query ') const dom = this.getDom() if (!dom) { return } console.log('task is pending', this.id) const status = dom.getElementsByClassName('anticon')[0].className.split(' ') if (status.some(classItem => ['anticon-check-circle'].includes(classItem))) { clearInterval(this.queryId) this.status = 'success' resolve() } if (status.some(classItem => ['anticon-close-circle'].includes(classItem))) { clearInterval(this.queryId) this.status = 'failed' reject() } }, 1000) }) } async queueForDeply () { return new Promise((resolve, reject) => { console.log('queue for deploy ', this.id) this.deployInterval = setInterval(() => { if (monitor.canDeploy()) { clearInterval(this.deployInterval) resolve() } }, 500) }) } async deploy() { const dom = this.getDom() let deployed = false if (!dom) { return } dom.click() await sleep(3000) if (!document.getElementsByClassName('ant-dropdown')[0] || getComputedStyle( document.getElementsByClassName('ant-dropdown')[0]).display === 'none') { document.querySelector('.publish-btn-check button').click() } await sleep(1000) Array.from(document.getElementsByClassName('fast-publish-btn-menu')[0].getElementsByClassName('ant-menu-item-group')).forEach(envDom => { const envName = envDom.getElementsByClassName('ant-menu-item-group-title')[0].innerText.toLocaleLowerCase().trim() if (this.branch.includes(envName) ) { // stg、pre分支发对应的环境 envDom.getElementsByClassName('ant-space-item')[0].click() } else if (['stg', 'pre'].includes(envName.toLocaleLowerCase()) && !this.donnotDeployToMyEnv()) { // 特性分支 发stg和pre对应的个人环境 const deployName = committerDeployMap[this.committer] // envDom.getElementsByClassName('ant-space-item')[0].click() Array.from(envDom.getElementsByClassName('ant-space-item')).forEach(dployDom => { if (dployDom.innerText === deployName) { dployDom.click() } }) } deployed = true // env.getElementsByClassName('ant-space-item') }) await sleep(300) document.querySelector('.publish-btn-check button').click() if (deployed) { this.status = 'deployed' return Promise.resolve() } this.status = 'undeployed' return Promise.reject() } stop() { // deployed, failed console.log('monitor ', this.queryId, ' stop') clearInterval(this.queryId) clearInterval(this.deployInterval) } } class DeloyMonitor { constructor() { this.tasksQueue = [] this.monitorId = undefined this.hibernateId = undefined } getTaskNodes () { let count = 0 let queryIntervalId const getNodes = () => { try { const parentNode = document.getElementsByClassName('task-list-sider__list')[0] const nodes = parentNode.getElementsByClassName('task-card') return nodes } catch (error) { return null } } return new Promise((resolve, reject) => { queryIntervalId = setInterval(() => { count++; const nodesout = getNodes() if (nodesout) { clearInterval(this.hibernateId) return resolve(nodesout) } if (count > 60) { clearInterval(queryIntervalId) return reject() } }, 1000); }) } parseNode(node) { const id = node.getElementsByClassName('first-line')[0].getElementsByTagName('strong')[0].innerText const branch = node.getElementsByClassName('branch')[0].innerText.trim() const user = node.getElementsByClassName('second-line')[0].getElementsByTagName('strong')[0].innerText.split(' ')[1]; // const status = node.className.split(' ')[1]; // pending : anticon-sync anticon-spin // success : anticon-check-circle // failed : anticon-close-circle // deployed : anticon-flag const status = node.getElementsByClassName('anticon')[0].className.split(' ') // console.log('id', id, 'status', status) return { id, branch, user, status } } getCurrentUser() { try { const myCookie = document.cookie.split(';').reduce((acc, cur) => { const [key, value] = cur.trim().split('='); acc[key] = value return acc }, {}) const jsonStr = decodeURIComponent(myCookie?.sensorsdata2015jssdkcross || '{}') const user = JSON.parse(jsonStr)?.distinct_id return user } catch (error) { console.log('parse cookie error', error) } } addToggleCheckbox() { // getStorage setStorage if(document.getElementById('deployEnv')) { return } const container = document.createElement('div') const notDeployToMyEnv = getStorage('not_deploy_to_my_env') === 'no' //default yes const domStr = ` <div style="position: fixed; top: 150px; right: 40px;"> <input type="checkbox" id="deployEnv" name="deployEnv" value="Bike" ${notDeployToMyEnv ? 'checked' : ''}> <label for="deployEnv">不发布特性分支到个人环境</label> </div> ` container.innerHTML = domStr container.addEventListener('click', (e) => { // console.log(e); const { checked } = e?.target || {} setStorage('not_deploy_to_my_env', checked ? 'no' : 'yes') }) document.getElementById('root').appendChild(container) } donnotDeployToMyEnv() { return document.getElementById('deployEnv').checked } start () { if (this.monitorId) { return console.error('DeloyMonitor has already runned') } this.monitorId = setInterval(async () => { console.log('deloyMonitor is running') if (!navigator.onLine) { this.hibernate() } try { this.addToggleCheckbox() const taskNodes = await this.getTaskNodes(); console.log('taskNodes 999', taskNodes) if (!taskNodes) { setTimeout(this.reload, 1000) } Array.from(taskNodes).forEach(node => { const { id, branch, user, status } = this.parseNode(node) console.log('task user', user, this.getCurrentUser()) // add new task const task = this.tasksQueue.find(item => item.id === id) const isStgPre = branch.includes('stg') || branch.includes('pre') const waitingDeploy = status.some(statusClass => ['anticon-sync', 'anticon-spin'].includes(statusClass)) const isMyPush = user === this.getCurrentUser() // if (isStgPre && !task && waitingDeploy && isMyPush) { if (isMyPush && !task && waitingDeploy) { const task = new DeployTask({ id, branch, user, status }) console.log('add new task ', id , task) this.tasksQueue.push(task) } // remove when failed , deployed if (status.some(statusClass => ['anticon-close-circle', 'anticon-flag'].includes(statusClass))) { const taskIndex = this.tasksQueue.findIndex(item => item.id === id) taskIndex !== -1 && console.log('will remove task ', id , task, 'taskIndex', taskIndex) if (taskIndex !== -1) { task.stop() this.tasksQueue.splice(taskIndex, 1) } } // remove when success but undeployed if (status.some(statusClass => ['anticon-check-circle'].includes(statusClass))) { const taskIndex = this.tasksQueue.findIndex(item => item.id === id) const successTask = this.tasksQueue[taskIndex] if (['undeployed', 'pending'].includes(successTask?.status)) { successTask.stop() this.tasksQueue.splice(taskIndex, 1) } } }) } catch (err) { this.reload() } finally { console.clear() const timespan = parseInt((new Date().getTime() - startTime) / 1000) console.log('at time', timespan , 'tasks are ',[...this.tasksQueue]) timespan > 3600 && this.reload() } }, 5000) } stop () { console.log('monitor stop') this.tasksQueue.forEach(task => task.stop()) clearInterval(this.monitorId) this.monitorId = undefined } canDeploy (){ return !this.tasksQueue.some(task => task.status === 'deploying') } reload() { window.location.reload() } hibernate () { console.log('hibernate') this.stop() this.tasksQueue = [] this.hibernateId = setInterval(() => { if (navigator.onLine) { this.start() clearInterval(this.hibernateId) this.hibernateId = undefined } }, 60 * 1000) } } monitor = new DeloyMonitor() monitor.start(); window.onbeforeunload = function(e) { monitor.stop(); }; window.addEventListener('online', () => monitor.start()); window.addEventListener('offline', () => monitor.hibernate()); })();