diff --git a/.gitignore b/.gitignore index 224dc8411..b3201b6b0 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ out/ .clj-kondo/ .lsp/ +.hugo_build.lock \ No newline at end of file diff --git a/api/index.ts b/api/index.ts index d7da7d20a..859d1f792 100644 --- a/api/index.ts +++ b/api/index.ts @@ -29,15 +29,51 @@ const viewsEndpoint = new awsx.apigateway.API("bmpi-dev-post-views", { const client = new aws.sdk.DynamoDB.DocumentClient(); - const result = await client.update({ - TableName: counterTable.name.get(), - Key: { id: route }, - UpdateExpression: "SET hit = if_not_exists(hit, :zero) + :incr", - ExpressionAttributeValues: { ":zero": 0, ":incr": incr }, - ReturnValues:"UPDATED_NEW", - }).promise(); + var count = 0; - let count = result.Attributes!.hit; + if (route == 'bmpi-dev-dev-page-views') { + const result = await client.scan({ + TableName: counterTable.name.get(), + FilterExpression: "begins_with(id, :q) AND hit > :hit_filter", + ExpressionAttributeValues: {":q": "bmpi.dev/dev/", ":hit_filter": 100}, + ProjectionExpression: "hit" + }).promise(); + let items = result.Items!; + for (let item of items) { + count = count + item.hit; + } + } else if (route == 'bmpi-dev-self-page-views') { + const result = await client.scan({ + TableName: counterTable.name.get(), + FilterExpression: "begins_with(id, :q) AND hit > :hit_filter", + ExpressionAttributeValues: {":q": "bmpi.dev/self/", ":hit_filter": 100}, + ProjectionExpression: "hit" + }).promise(); + let items = result.Items!; + for (let item of items) { + count = count + item.hit; + } + } else if (route == 'bmpi-dev-money-page-views') { + const result = await client.scan({ + TableName: counterTable.name.get(), + FilterExpression: "begins_with(id, :q) AND hit > :hit_filter", + ExpressionAttributeValues: {":q": "bmpi.dev/money/", ":hit_filter": 100}, + ProjectionExpression: "hit" + }).promise(); + let items = result.Items!; + for (let item of items) { + count = count + item.hit; + } + } else { + const result = await client.update({ + TableName: counterTable.name.get(), + Key: { id: route }, + UpdateExpression: "SET hit = if_not_exists(hit, :zero) + :incr", + ExpressionAttributeValues: { ":zero": 0, ":incr": incr }, + ReturnValues:"UPDATED_NEW", + }).promise(); + count = result.Attributes!.hit; + } return { statusCode: 200, diff --git a/api/package-lock.json b/api/package-lock.json index f603dda48..b3f3131e9 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -553,9 +553,9 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "protobufjs": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", - "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -568,14 +568,14 @@ "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/long": "^4.0.1", - "@types/node": "^13.7.0", + "@types/node": ">=13.7.0", "long": "^4.0.0" }, "dependencies": { "@types/node": { - "version": "13.13.40", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.40.tgz", - "integrity": "sha512-eKaRo87lu1yAXrzEJl0zcJxfUMDT5/mZalFyOkT44rnQps41eS2pfWzbaulSPpQLFNy29bFqn+Y5lOTL8ATlEQ==" + "version": "18.7.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==" } } }, diff --git a/api/yarn.lock b/api/yarn.lock new file mode 100644 index 000000000..500b52da3 --- /dev/null +++ b/api/yarn.lock @@ -0,0 +1,1176 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@grpc/grpc-js@^1.2.7": + version "1.6.12" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.6.12.tgz#20f710d8a8c5c396b2ae9530ba6c06b984614fdf" + integrity sha512-JmvQ03OTSpVd9JTlj/K3IWHSz4Gk/JMLUTtW7Zb0KvO1LcOYGATh5cNuRYzCAeDR3O8wq+q8FZe97eO9MBrkUw== + dependencies: + "@grpc/proto-loader" "^0.7.0" + "@types/node" ">=12.12.47" + +"@grpc/proto-loader@^0.7.0": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.2.tgz#fa63178853afe1473c50cff89fe572f7c8b20154" + integrity sha512-jCdyLIT/tdQ1zhrbTQnJNK5nbDf0GoBpy5jVNywBzzMDF+Vs6uEaHnfz46dMtDxkvwrF2hzk5Z67goliceH0sA== + dependencies: + "@types/long" "^4.0.1" + lodash.camelcase "^4.3.0" + long "^4.0.0" + protobufjs "^7.0.0" + yargs "^16.2.0" + +"@logdna/tail-file@^2.0.6": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@logdna/tail-file/-/tail-file-2.2.0.tgz#158a362d293f940dacfd07c835bf3ae2f9e0455a" + integrity sha512-XGSsWDweP80Fks16lwkAUIr54ICyBs6PsI4mpfTLQaWgEJRtY9xEV+PeyDpJ+sJEGZxqINlpmAwe/6tS1pP8Ng== + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + +"@pulumi/aws@^3.0.0": + version "3.38.1" + resolved "https://registry.yarnpkg.com/@pulumi/aws/-/aws-3.38.1.tgz#d7972f82bebc2205622d8283b0983e96a36f7c94" + integrity sha512-XnCa7AHq+/Py/eHItlus+YTjelKEn77u9ffO+owjrucb8RRhkvDzAa3prjQbhrRDxBvwZ7GcQqp/5SGTamkboQ== + dependencies: + "@pulumi/pulumi" "^2.17.0" + aws-sdk "^2.0.0" + builtin-modules "3.0.0" + mime "^2.0.0" + read-package-tree "^5.2.1" + resolve "^1.7.1" + +"@pulumi/awsx@^0.22.0": + version "0.22.0" + resolved "https://registry.yarnpkg.com/@pulumi/awsx/-/awsx-0.22.0.tgz#de7a635ad93cb4917a958fabb6f2cc11efb40784" + integrity sha512-A9GA9CelWRh8xtuINl6cpCMDnEVVtJ+RNvVArgXegYnNZltaYbHrQJKG6cAu/JOM+AD8WF5PVTFTbYNVFkPUsw== + dependencies: + "@pulumi/docker" "^1.0.0 || ^2.0.0" + "@types/aws-lambda" "^8.10.23" + mime "^2.0.0" + +"@pulumi/docker@^1.0.0 || ^2.0.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@pulumi/docker/-/docker-2.10.0.tgz#cac544739261be10268e3bb044fe7a6e100193c3" + integrity sha512-nZkZpAd1FuOWjms5FlrA5BK++Mwp3rQn+7U/oEpHcGCGzKBm8Xsi0gGzBzfHZXlpxRg3tLGQLcDdHM1seV8QVQ== + dependencies: + "@pulumi/pulumi" "^2.15.0" + semver "^5.4.0" + +"@pulumi/pulumi@^2.0.0", "@pulumi/pulumi@^2.15.0", "@pulumi/pulumi@^2.17.0": + version "2.25.2" + resolved "https://registry.yarnpkg.com/@pulumi/pulumi/-/pulumi-2.25.2.tgz#8ef5a921302d26ba1220169cefc92620c1192642" + integrity sha512-JVPkxr2zt44fsUbqrW0yW2jTcsCXXEwJfOged+2dgXheYBHNdaGtdZgaHXi4lr21tm5ddZEdtwY6i5rDAblyQw== + dependencies: + "@grpc/grpc-js" "^1.2.7" + "@logdna/tail-file" "^2.0.6" + "@pulumi/query" "^0.3.0" + google-protobuf "^3.5.0" + js-yaml "^3.14.0" + minimist "^1.2.0" + normalize-package-data "^2.4.0" + protobufjs "^6.8.6" + read-package-tree "^5.3.1" + require-from-string "^2.0.1" + semver "^6.1.0" + source-map-support "^0.4.16" + split2 "^3.2.2" + ts-node "^7.0.1" + typescript "~3.7.3" + upath "^1.1.0" + +"@pulumi/query@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@pulumi/query/-/query-0.3.0.tgz#f496608e86a18c3dd31b6c533408e2441c29071d" + integrity sha512-xfo+yLRM2zVjVEA4p23IjQWzyWl1ZhWOGobsBqRpIarzLvwNH/RAGaoehdxlhx4X92302DrpdIFgTICMN4P38w== + +"@types/aws-lambda@^8.10.23": + version "8.10.102" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.102.tgz#d2402224ec30cdddfb669005c25b6ee01fd6f5be" + integrity sha512-BT05v46n9KtSHa9SgGuOvm49eSruJ9utD8iNXpdpuUVYk8wOcqmm1LEzpNRkrXxD0CULc38sdLpk6q3Wa2WOwg== + +"@types/long@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + +"@types/node@>=12.12.47", "@types/node@>=13.7.0": + version "18.7.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.14.tgz#0fe081752a3333392d00586d815485a17c2cf3c9" + integrity sha512-6bbDaETVi8oyIARulOE9qF1/Qdi/23z6emrUh0fNJRUmjznqrixD4MpGDdgOFk5Xb0m2H6Xu42JGdvAxaJR/wA== + +"@types/node@^10.0.0": + version "10.17.60" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" + integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array.prototype.reduce@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz#8167e80089f78bff70a99e20bd4201d4663b0a6f" + integrity sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.7" + +arrify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== + +asap@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + +aws-sdk@^2.0.0: + version "2.1208.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1208.0.tgz#7dc8c877652d2b1ea126a3c256157c1cbd2e20e2" + integrity sha512-Wyq9TJyvRZMcHmcGwmOJag5/94m+Gq3BHcK2klwFvgUf1OWWJc4OYqmi90d7qJ09ydTeGGMeodNJildQdkOrYQ== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.16.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + util "^0.12.4" + uuid "8.0.0" + xml2js "0.4.19" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.0.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +buffer-from@^1.0.0, buffer-from@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@4.9.2: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-modules@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.0.0.tgz#1e587d44b006620d90286cc7a9238bbc6129cab1" + integrity sha512-hMIeU4K2ilbXV6Uv93ZZ0Avg/M91RaKXucQ+4me2Do1txxBDyDZWCBa5bJSLqoNTRpXTLwEzIk1KmloenDDjhg== + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +debuglog@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw== + +define-properties@^1.1.3, define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +dezalgo@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== + dependencies: + asap "^2.0.0" + wrappy "1" + +diff@^3.1.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +es-abstract@^1.19.0, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.0, es-abstract@^1.20.1: + version "1.20.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.2.tgz#8495a07bc56d342a3b8ea3ab01bd986700c2ccb3" + integrity sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + function.prototype.name "^1.1.5" + get-intrinsic "^1.1.2" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-symbols "^1.0.3" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-weakref "^1.0.2" + object-inspect "^1.12.2" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.4.3" + string.prototype.trimend "^1.0.5" + string.prototype.trimstart "^1.0.5" + unbox-primitive "^1.0.2" + +es-array-method-boxes-properly@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" + integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +events@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function.prototype.name@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" + integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + functions-have-names "^1.2.2" + +functions-have-names@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" + integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +glob@^7.1.1: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +google-protobuf@^3.5.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.0.tgz#8dfa3fca16218618d373d414d3c1139e28034d6e" + integrity sha512-byR7MBTK4tZ5PZEb+u5ZTzpt4SfrTxv5682MjPlHN16XeqgZE2/8HOIWeiXe8JKnT9OVbtBGhbq8mtvkK8cd5g== + +graceful-fs@^4.1.2: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +ieee754@1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-core-module@^2.9.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.3, is-typed-array@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.9.tgz#246d77d2871e7d9f5aeb1d54b9f52c71329ece67" + integrity sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-abstract "^1.20.0" + for-each "^0.3.3" + has-tostringtag "^1.0.0" + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +isarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +jmespath@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== + +js-yaml@^3.14.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +long@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.0.tgz#2696dadf4b4da2ce3f6f6b89186085d94d52fd61" + integrity sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w== + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +mime@^2.0.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +mkdirp@^0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +normalize-package-data@^2.0.0, normalize-package-data@^2.4.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +npm-normalize-package-bin@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +object-inspect@^1.12.2, object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.getownpropertydescriptors@^2.0.3: + version "2.1.4" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37" + integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== + dependencies: + array.prototype.reduce "^1.0.4" + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +protobufjs@^6.8.6: + version "6.11.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + +protobufjs@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.1.0.tgz#5174b5f96fad4f7dea7dd4abd594042ac360e665" + integrity sha512-rCuxKlh0UQKSMjrpIcTLbR5TtGQ52cgs1a5nUoPBAKOccdPblN67BJtjrbtudUJK6HmBvUdsmymyYOzO7lxZEA== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== + +read-package-json@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.2.tgz#6992b2b66c7177259feb8eaac73c3acd28b9222a" + integrity sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA== + dependencies: + glob "^7.1.1" + json-parse-even-better-errors "^2.3.0" + normalize-package-data "^2.0.0" + npm-normalize-package-bin "^1.0.0" + +read-package-tree@^5.2.1, read-package-tree@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.3.1.tgz#a32cb64c7f31eb8a6f31ef06f9cedf74068fe636" + integrity sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw== + dependencies: + read-package-json "^2.0.0" + readdir-scoped-modules "^1.0.0" + util-promisify "^2.1.0" + +readable-stream@^3.0.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdir-scoped-modules@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" + integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== + dependencies: + debuglog "^1.0.1" + dezalgo "^1.0.0" + graceful-fs "^4.1.2" + once "^1.3.0" + +regexp.prototype.flags@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" + integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + functions-have-names "^1.2.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve@^1.10.0, resolve@^1.7.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== + +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +"semver@2 || 3 || 4 || 5", semver@^5.4.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.1.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +source-map-support@^0.4.16: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== + dependencies: + source-map "^0.5.6" + +source-map-support@^0.5.6: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.12" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz#69077835abe2710b65f03969898b6637b505a779" + integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== + +split2@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" + integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== + dependencies: + readable-stream "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string.prototype.trimend@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" + integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + +string.prototype.trimstart@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" + integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +ts-node@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.1.tgz#9562dc2d1e6d248d24bc55f773e3f614337d9baf" + integrity sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw== + dependencies: + arrify "^1.0.0" + buffer-from "^1.1.0" + diff "^3.1.0" + make-error "^1.1.1" + minimist "^1.2.0" + mkdirp "^0.5.1" + source-map-support "^0.5.6" + yn "^2.0.0" + +typescript@~3.7.3: + version "3.7.7" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.7.tgz#c931733e2ec10dda56b855b379cc488a72a81199" + integrity sha512-MmQdgo/XenfZPvVLtKZOq9jQQvzaUAUpcKW8Z43x9B2fOm4S5g//tPtMweZUIP+SoBqrVPEIm+dJeQ9dfO0QdA== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +upath@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ== + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +util-promisify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/util-promisify/-/util-promisify-2.1.0.tgz#3c2236476c4d32c5ff3c47002add7c13b9a82a53" + integrity sha512-K+5eQPYs14b3+E+hmE2J6gCZ4JmMl9DbYS6BeP2CHq6WMuNxErxf5B/n0fz85L8zUuoO6rIzNNmIQDu/j+1OcA== + dependencies: + object.getownpropertydescriptors "^2.0.3" + +util@^0.12.4: + version "0.12.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253" + integrity sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + safe-buffer "^5.1.2" + which-typed-array "^1.1.2" + +uuid@8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" + integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-typed-array@^1.1.2: + version "1.1.8" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.8.tgz#0cfd53401a6f334d90ed1125754a42ed663eb01f" + integrity sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-abstract "^1.20.0" + for-each "^0.3.3" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.9" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +xml2js@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yn@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" + integrity sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ== diff --git a/config.toml b/config.toml index 95e111e83..f1d23951e 100644 --- a/config.toml +++ b/config.toml @@ -90,12 +90,6 @@ FootnoteReturnLinkContents = "↑" # OG og_image = "images/bg2.png" -# Comments on github issue -[params.utteranc] - repo = "bmpi-dev/bmpi.dev" - issue-term = "pathname" - theme = "github-light" - # Social links [[params.social]] name = "Github" diff --git a/content/about.zh-cn.md b/content/about.zh-cn.md index 937c5e88c..bbba0bc09 100644 --- a/content/about.zh-cn.md +++ b/content/about.zh-cn.md @@ -47,9 +47,9 @@ id = "about" - [Web3](https://twitter.com/madawei2699/status/1479829788139286533) - [内容营销](https://twitter.com/madawei2699/status/1481596368716451842) #### 开源项目 +- [Invest Alchemy](https://github.com/bmpi-dev/invest-alchemy) - [free4chat](https://github.com/madawei2699/free4chat) - [instaghub](https://github.com/bmpi-dev/instaghub) -- [Invest Alchemy](https://github.com/bmpi-dev/invest-alchemy) - [tg2web](https://github.com/bmpi-dev/tg2web) #### 个人产品 - bmpi.dev @@ -63,9 +63,8 @@ id = "about" - [word](https://word.bmpi.dev/) - [wechat](https://wechat.bmpi.dev/) - [free4.chat](https://free4.chat/) -- [双均线交易策略系统](https://money.i365.tech/) +- [投资炼金术](https://money.bmpi.dev/) - 自用托管网站 - - [logseq.xyz](https://logseq.xyz/) - [excalidraw](https://wb.bmpi.dev/) - 流量站 - [gitopx.com](https://www.gitopx.com/) @@ -117,6 +116,9 @@ id = "about" ### 本站变更日志 ```text +✔ Open Graph Image(og.bmpi.dev) @done(22-09-15 18:51) +✔ 网站分享功能移除sharethis @done(22-07-28 23:00) +✔ 网站评论集成giscus @done(22-07-27 23:20) ✔ 添加umami追踪站点流量 @done(22-06-18 18:00) ✔ 文章页支持expand shortcode @done(22-06-10 18:20) ✔ 添加Plotly绘图支持 @done(22-04-28 00:15) diff --git a/content/affiliate.zh-cn.md b/content/affiliate.zh-cn.md index 2cbbd5b3c..2b0464904 100644 --- a/content/affiliate.zh-cn.md +++ b/content/affiliate.zh-cn.md @@ -11,23 +11,7 @@ math = true 这种模式在国外称之为Affiliate Network(联盟营销)/ CPS,也就是我们常说的的推广链接。 ``` -## 我在用的服务器与域名 - -{{< figure src="https://img.bmpi.dev/f81c828c-acb1-224b-cda5-a42a65066af2.png" caption="Vultr" link="https://www.vultr.com/?ref=7252988">}} - -{{< figure src="https://img.bmpi.dev/2619ddcc-f159-0ca3-62b0-e5c9ecbde404.png" caption="Namecheap" link="https://www.tkqlhce.com/kp97mu2-u1HRKNKNLMHJLKOOOOQ">}} - -## 我在用的机场/梯子 - -> 如果你的网络上不了Google/YouTube/Twitter,那你就需要一个机场或梯子,它能去除那股神秘的力量,助你正常上网 - -- [Blinkload](https://blinkload.to/aff/LYWO) - -## 我在用的键盘 - -吃灰中的 [HHKB](https://union-click.jd.com/jdc?e=&p=AyIGZRprFQMTBlUaUhcLFwJUKx9KWkxYZUIeUENQDEsFA1BWThgJBABAHUBZCQUdRUFGGRJDD1MdQlUQQwVKDFRXFk8jQA4SBlQaWxQLEA5QHloleAxfAHw7XH5weTNyWmNwTVcGTBhgYh4LZRprFQMTBFYTWhcHFzdlG1wlVHwHVBpaFAoRAFYTaxQyEgNcG14XBhMGUxtZFzIVB1wrB0VXQkEBQ1gSCho3ZStYJTIiB2UYa1dsFwYBG1gcVhRQAEgJEFZGDl1OXkUAFwFVGVwQV0AEBxxrFwMTA1w%3D) - -## 我心目中的好书 +## 好书推荐 ### 投资类 diff --git a/content/dev/authentication-and-authorization-in-a-distributed-system/index.zh-cn.md b/content/dev/authentication-and-authorization-in-a-distributed-system/index.zh-cn.md index 1ecddbcdc..43bd2575f 100644 --- a/content/dev/authentication-and-authorization-in-a-distributed-system/index.zh-cn.md +++ b/content/dev/authentication-and-authorization-in-a-distributed-system/index.zh-cn.md @@ -8,8 +8,11 @@ description: "本文从高层次梳理在不同架构演进中认证、授权及 isCJKLanguage: true og_image: "https://img.bmpi.dev/0f3dfb95-7f80-2311-74f1-f70ea7fd9a69.png" categories: [ - "什么是X" + "分布式技术" ] +markmap: + enabled: true + id: "authentication-and-authorization-in-a-distributed-system" --- 在软件系统设计中,如何让应用能够在各种环境中安全高效的访问是个复杂的问题,这个问题的背后是一系列软件设计时需要考虑的架构安全问题:[架构安全性 | 凤凰架构](https://icyfenix.cn/architect-perspective/general-architecture/system-security/) @@ -22,7 +25,69 @@ categories: [ 在漫长的架构演进历史中,业界对这些问题已经有很成熟的解决方案。在架构安全这块,最好的是遵循技术标准与最佳实践,尽可能**不重复造轮子或“创新”**。下面这个思维导图就是针对这些问题的常见的技术标准及方案: -![](https://img.bmpi.dev/0f3dfb95-7f80-2311-74f1-f70ea7fd9a69.png) + + +```markmap +# 架构安全 +## 认证 +### 方式 +#### 通信信道上的认证 +##### SSL/TLS +- 加密算法 +- 生成密钥 +- 公钥分发 +- CA 认证 +- 核验公钥 +- 签名 +- 验证 +#### 通信协议上的认证 +##### HTTP +- RFC 7235 + - HTTP Basic +- RFC 7616 + - Digest +- RFC 6750 + - Bearer +- RFC 7486 + - HOBA +- RFC 6287 + - OCRA(质疑/应答算法) +- 扩展方案 + - AWS4-HMAC-SHA256 + - Windows Live ID + - Twitter Basic +#### 通信内容上的认证 +##### Web认证 +- 表单认证 +- RFC 6238 + - TOTP(基于时间的一次性密码算法) +- WebAuthn +### SSO +- Kerberos-based +- CAS +- SAML +- OIDC +## 授权 +### 访问控制 +- ACL +- RBAC +- ABAC +### OAuth2 +- 授权码模式 +- 隐式授权模式 +- 密码模式 +- 客户端模式 +## 凭证 +### 自包含令牌 +- JWT +### 引用令牌 +- Cookie-Session +## 实现 +### Java +- JAAS +- Shiro +- Spring Security +``` 在研究分布式系统的认证和授权问题前,让我们回到单体架构的时代,看看在单体架构上这些问题是如何被解决的。 diff --git a/content/dev/failure-driven-development/index.zh-cn.md b/content/dev/failure-driven-development/index.zh-cn.md index 6d2bd9d91..363dc43c4 100644 --- a/content/dev/failure-driven-development/index.zh-cn.md +++ b/content/dev/failure-driven-development/index.zh-cn.md @@ -8,6 +8,9 @@ description: "本文分享笔者对失败驱动开发(Failure Driven Developme isCJKLanguage: true series: ["技术洞见"] og_image: "https://img.bmpi.dev/27015307-1633-2ad8-7868-f1edb27bfeb8.png" +categories: [ + "个人成长" +] --- ![](https://img.bmpi.dev/27015307-1633-2ad8-7868-f1edb27bfeb8.png) diff --git "a/content/dev/g\035uide-to-setup-blog-site-with-zero-cost-1/index.zh-cn.md" "b/content/dev/g\035uide-to-setup-blog-site-with-zero-cost-1/index.zh-cn.md" index 14d582638..bf6f5e840 100644 --- "a/content/dev/g\035uide-to-setup-blog-site-with-zero-cost-1/index.zh-cn.md" +++ "b/content/dev/g\035uide-to-setup-blog-site-with-zero-cost-1/index.zh-cn.md" @@ -11,7 +11,6 @@ isCJKLanguage: true > 本文属于[零成本搭建现代博客指南](/series/零成本搭建现代博客指南/)系列第一篇【个人技术博客搭建篇】。 > ![](https://img.bmpi.dev/3972bce0-19ad-3c31-78da-99b35aab1f8a.png) -> 这个系列不少的东西需要你科学上网才能访问,也就是你需要一把[梯子](/affiliate)。 ## 为什么要搭建自己的个人博客 diff --git a/content/dev/guide-to-k8s-cloud-native/index.zh-cn.md b/content/dev/guide-to-k8s-cloud-native/index.zh-cn.md index 2f53f370d..5df303ced 100644 --- a/content/dev/guide-to-k8s-cloud-native/index.zh-cn.md +++ b/content/dev/guide-to-k8s-cloud-native/index.zh-cn.md @@ -7,6 +7,9 @@ keywords: "" description: "本文分享了笔者使用 K8S 云原生技术开发个人项目的踩坑经验。" isCJKLanguage: true og_image: "https://img.bmpi.dev/4c613ca1-0874-d09b-79ad-243cd926bfba.png" +categories: [ + "什么是X" +] --- > 注:本篇文章是作者与 [GitHub Copilot](https://copilot.github.com/) 结对创作完成。Copilot 大概完成了本文 5% 左右的部分。作者也在这个 [Tweet](https://twitter.com/madawei2699/status/1458313535792955393) 上部分记录了 Copilot 的创作部分。 diff --git a/content/dev/guide-to-mining/index.zh-cn.md b/content/dev/guide-to-mining/index.zh-cn.md index e048e0c0e..1fc13ca03 100644 --- a/content/dev/guide-to-mining/index.zh-cn.md +++ b/content/dev/guide-to-mining/index.zh-cn.md @@ -7,6 +7,9 @@ keywords: "" description: "本文分享笔者使用 AWS GPU 服务器挖 ETH 和 XMR 加密币的经验。" isCJKLanguage: true og_image: "https://img.bmpi.dev/d0286afd-989a-0500-0aca-c91bee9893d2.png" +categories: [ + "什么是X" +] --- {{% notice warning %}} diff --git a/content/dev/guide-to-serverless/index.en.md b/content/dev/guide-to-serverless/index.en.md index 3950bcdeb..be30ca843 100644 --- a/content/dev/guide-to-serverless/index.en.md +++ b/content/dev/guide-to-serverless/index.en.md @@ -32,7 +32,7 @@ This article covers the following: - Setting up S3 buckets. - Setting up CloudFormation. -The final result is available at: [Online version](https://money.i365.tech/). +The final result is available at: [Online version](https://money.bmpi.dev/). The source code is available at: [Code Repository](https://github.com/bmpi-dev/invest-alchemy). diff --git a/content/dev/guide-to-serverless/index.zh-cn.md b/content/dev/guide-to-serverless/index.zh-cn.md index a610a5e17..f9cd7cf99 100644 --- a/content/dev/guide-to-serverless/index.zh-cn.md +++ b/content/dev/guide-to-serverless/index.zh-cn.md @@ -7,6 +7,9 @@ keywords: "" description: "本文描述了笔者开发一个基于 AWS Serverless 技术的应用的全过程。" isCJKLanguage: true og_image: "https://img.bmpi.dev/48504c01-c2d0-e05a-1eda-d82b88f6496d.png" +categories: [ + "什么是X" +] --- ## 背景 @@ -33,7 +36,7 @@ og_image: "https://img.bmpi.dev/48504c01-c2d0-e05a-1eda-d82b88f6496d.png" - 设置 S3 桶。 - 设置 CloudFormation。 -最终效果见:[线上版本](https://money.i365.tech/)。 +最终效果见:[线上版本](https://money.bmpi.dev/)。 源码见:[代码仓库](https://github.com/bmpi-dev/invest-alchemy)。 diff --git a/content/dev/guide-to-setup-blog-site-with-zero-cost-2/index.zh-cn.md b/content/dev/guide-to-setup-blog-site-with-zero-cost-2/index.zh-cn.md index ead3d4bee..95ab1b141 100644 --- a/content/dev/guide-to-setup-blog-site-with-zero-cost-2/index.zh-cn.md +++ b/content/dev/guide-to-setup-blog-site-with-zero-cost-2/index.zh-cn.md @@ -11,7 +11,6 @@ isCJKLanguage: true > 本文属于[零成本搭建现代博客指南](/series/零成本搭建现代博客指南/)系列第二篇【SEO优化篇】。 > ![](https://img.bmpi.dev/adc30b81-2169-a595-ece1-174b05630393.png) -> 这个系列不少的东西需要你科学上网才能访问,也就是你需要一把[梯子](/affiliate)。 > 如果你对`SEO`并不了解,请先看这篇[《什么是SEO》](/money/what-is-seo/)。 diff --git a/content/dev/guide-to-setup-blog-site-with-zero-cost-3/index.zh-cn.md b/content/dev/guide-to-setup-blog-site-with-zero-cost-3/index.zh-cn.md index 284a511d4..2a5dc21d9 100644 --- a/content/dev/guide-to-setup-blog-site-with-zero-cost-3/index.zh-cn.md +++ b/content/dev/guide-to-setup-blog-site-with-zero-cost-3/index.zh-cn.md @@ -11,7 +11,6 @@ isCJKLanguage: true > 本文属于[零成本搭建现代博客指南](/series/零成本搭建现代博客指南/)系列第三篇【订阅推送篇】。 > ![](https://img.bmpi.dev/e456f0cb-bb75-3f7c-fd9e-dec5fbfd3761.png) -> 这个系列不少的东西需要你科学上网才能访问,也就是你需要一把[梯子](/affiliate)。 博客搭建好后,如何让你的读者第一时间收到文章更新? diff --git a/content/dev/guide-to-setup-blog-site-with-zero-cost-4/index.zh-cn.md b/content/dev/guide-to-setup-blog-site-with-zero-cost-4/index.zh-cn.md index a15054073..8b9f1f3b2 100644 --- a/content/dev/guide-to-setup-blog-site-with-zero-cost-4/index.zh-cn.md +++ b/content/dev/guide-to-setup-blog-site-with-zero-cost-4/index.zh-cn.md @@ -11,7 +11,6 @@ isCJKLanguage: true > 本文属于[零成本搭建现代博客指南](/series/零成本搭建现代博客指南/)系列第四篇【加载速度优化篇】。 > ![](https://img.bmpi.dev/e4e1ee7e-2f99-972a-b691-07edeb0b4935.png) -> 这个系列不少的东西需要你科学上网才能访问,也就是你需要一把[梯子](/affiliate)。 个人博客搭建好后,如何优化加载速度?我们都知道如果一个网页在2至3秒还没有加载出来的话,用户因为焦虑就会关闭这个网页了。 diff --git a/content/dev/how-to-learn-tech/index.zh-cn.md b/content/dev/how-to-learn-tech/index.zh-cn.md index d948eac65..54f125623 100644 --- a/content/dev/how-to-learn-tech/index.zh-cn.md +++ b/content/dev/how-to-learn-tech/index.zh-cn.md @@ -7,6 +7,9 @@ keywords: "" description: "本文分享笔者在学习一门新技术时的一些经验。" isCJKLanguage: true og_image: "https://img.bmpi.dev/feafeb84-b5f7-3b81-3b07-3bc04fa4375d.png" +categories: [ + "个人成长" +] --- - [要学否](#要学否) diff --git a/content/dev/learning-from-puzzles.zh-cn.md b/content/dev/learning-from-puzzles.zh-cn.md index d55795f12..03eb09b01 100644 --- a/content/dev/learning-from-puzzles.zh-cn.md +++ b/content/dev/learning-from-puzzles.zh-cn.md @@ -7,6 +7,9 @@ keywords: "" description: "本文通过一个难题的解决场景分享我解决技术难题的一些思路及从中不断学习提升的个人经验。" isCJKLanguage: true og_image: "https://img.bmpi.dev/dcdaa6da-7d19-8810-7029-b91bed5a12a2.png" +categories: [ + "个人成长" +] --- - [难题的成因](#难题的成因) diff --git a/content/dev/parser_black_magic/index.zh-cn.md b/content/dev/parser_black_magic/index.zh-cn.md index aab93f57f..82d1ab90c 100644 --- a/content/dev/parser_black_magic/index.zh-cn.md +++ b/content/dev/parser_black_magic/index.zh-cn.md @@ -9,6 +9,9 @@ aliases: [ keywords: "时间管理、工具、parser解析器、jison、bison、lex、flex、bnf、yacc、dsl、词法分析、语法分析、编译原理" description: "本文讲述了基于词法分析和语法分析等编译原理知识使用bison开发一款解析ToDo格式文本的解析Parser" isCJKLanguage: true +categories: [ + "什么是X" +] --- 我们在开发过程中对于文本处理一般会用Regex来搞定,比如识别一个字符串中的邮箱及手机号之类。但是对于复杂的文本格式,我们可能会通过写一个解析工具来解析,但是你可能很难想到用flex/bison来做。想象下这么一个场景,你需要将Nginx的conf文件转换成json格式。 diff --git a/content/dev/software-engineering-at-google/culture.zh-cn.md b/content/dev/software-engineering-at-google/culture.zh-cn.md new file mode 100644 index 000000000..141deb6ba --- /dev/null +++ b/content/dev/software-engineering-at-google/culture.zh-cn.md @@ -0,0 +1,393 @@ +--- +title: "Google软件工程之文化篇" +date: 2022-07-20 +draft: false +tags: ["软件工程", "Tech Lead"] +keywords: "" +description: "本文是《Software Engineering at Google》的读书笔记,同时会穿插分享我对软件工程的理解。本文主要介绍软件工程中人的因素,也就是文化部分。" +isCJKLanguage: true +og_image: "https://img.bmpi.dev/fb352645-8a44-1b52-f54c-50c0b6e651a0.png" +categories: [ + "软件工程" +] +markmap: + enabled: true + id: "software-engineering-at-google-culture" +--- + +- [编程与软件工程](#编程与软件工程) +- [软件工程发展史](#软件工程发展史) +- [Google软件工程文化](#google软件工程文化) + - [团队协作的基石](#团队协作的基石) + - [打造知识共享文化](#打造知识共享文化) + - [领导团队(Tech Lead)](#领导团队tech-lead) + - [工程效率测量](#工程效率测量) +- [总结](#总结) + +在软件工程的概念被提出之前,IT行业经历了[软件危机](https://zh.m.wikipedia.org/zh-hans/软件危机)。当时IT行业开发的软件正在经历从小规模到大规模的过程,而没有系统化的方法论指导大规模软件的开发过程,导致软件工程师之间的协作非常的低效且质量难以得到保证。 + +直到IT行业意识到软件需要一种工程化的方法论来指导开发过程。 + +> 软件工程是将系统化的、规范的、可度量的方法用于软件的开发、运行和维护的过程,即将工程化应用于软件开发中(IEEE,1993)。 + +## 编程与软件工程 + +编程是利用某个编程语言实现某个算法或技术方案从而来解决某类问题,但这类问题规模一般很小,也不太会随着时间产生变化,或者其生命周期也很短,不需要考虑长期维护的问题。但软件工程与编程的区别在于前者需考虑时间与规模带来变化的影响。 + +时间的因素使软件工程需要考虑质量的问题,糟糕的质量会产生很多认知负荷,最终难以长期维护。规模的因素使软件工程需要考虑协作的问题,低效的协作可能与沟通方式有关,最终拖垮交付进度。 + +## 软件工程发展史 + +早期的软件工程借鉴了项目管理的流程,而传统的项目管理关注生产过程大过于人。 + +```markmap +# 传统软件工程 +## 软件开发过程 +### 需求 +### 设计 +### 开发 +### 测试 +### 集成 +### 部署 +### 维护 +## 软件项目管理 +### 范围管理 +### 成本管理 +### 时间管理 +### 风险管理 +### 干系人管理 +### 沟通管理 +### 人力资源管理 +### 质量管理 +``` + +以过程为中心的软件工程过程方法论主要有瀑布式与统一软件开发过程。这种软件开发过程需要产生大量的正式文档,通过严格的流程管理控制软件的开发过程。但软件开发过程却是一个知识密集型的生产过程,过于关注流程而忽略人的因素导致这类方法论很难适应需求变化,花费很多时间开发出的产品很难满足变化迅速的市场,最终逐渐被淘汰。 + +以人为中心的软件开发过程是为适应需求变化的代码编写和团队组织方法论。这类逐渐产生以极限编程与敏捷过程两种方法论。 + +```markmap +# 软件开发过程方法论 +### 以过程为中心 +- 瀑布式(Waterfall) +- 统一软件开发过程(RUP) +### 以人为中心 +- 极限编程(XP) +- 敏捷(Agile) +``` + +这里以敏捷过程为例,敏捷开发至今已经成为很主流的软件开发过程。 + +```markmap +# 敏捷过程 +## 价值观 +- 个体和互动高于流程和工具 +- 工作的软件高于详尽的文档 +- 客户合作高于合同谈判 +- 响应变化高于遵循计划 +## 框架 +### Scrum +### Kanban +## 实践 +- 沟通 + - 每日站会 + - 迭代计划会议(IPM) + - 成果展示(Showcase) + - 回顾(Retrospective) + - 反馈(Feedback) +- 进度 + - Sprint + - Backlog + - Story + - User Story + - Story Point + - Story Kickoff + - Story Desk Check +- 质量 + - 测试驱动开发(TDD) + - 持续集成交付(CI/CD) + - 代码评审(Code Diff) + - 结对编程(Pair Programming) + - 知识分享(Session) + - 工作坊(Workshop) +``` + +敏捷开发倡导信任人而非死板的流程与计划管理,通过一系列如上所示的实践来营造一个信任为主的文化土壤,从而打造一个高效的研发团队,最终开发出高质量的软件成品。 + +当然本文不是介绍敏捷开发过程的,而是介绍Google公司的软件工程实践。 + +Google是一家伟大的互联网公司,同时研发出来了大量知名的软件产品。Google是怎么做到的?在2020年Google的三位工程师[@TitusWinters](https://twitter.com/TitusWinters) [@manshreck](https://twitter.com/manshreck) [@hyrumwright](https://twitter.com/hyrumwright)出版了《[Software Engineering at Google](https://abseil.io/resources/swe-book)》这本书来介绍Google的软件工程实践,这给了我们一个机会一窥究竟。 + +本文是Google软件工程系列的上篇之文化篇,来介绍软件工程最重要的一个基石:文化要素。 + +## Google软件工程文化 + +> 以下是《Software Engineering at Google》一书第二部分文化篇的思维导图,由于此部分占全书近20%,所以本文不会详细的介绍其中的概念,想详细了解的读者建议阅读原书。本文会结合此书这部分内容分享作者的个人理解及相关经验。 + +```markmap +# Google软件工程 +## 文化 +### 团队协作的基石 +- 谦虚(Humility) +- 尊重(Respect) +- 信任(Trust) +### 打造知识共享文化 +- 专业知识共享的挑战 + - 缺乏心理安全 + - 知识孤岛 + - 知识单点故障 + - 团队知识断层 + - 鹦鹉行为 + - 知识禁区 +- 塑造团队学习文化 + - 营造心理安全的环境 + - 导师制度 + - 群组交流模式 + - 个人学习成长 + - 从遗留知识中学习 + - 问好问题 + - 群组 + - 邮件列表 + - 内部社区 + - 以教促学 + - 技术演讲 + - 技术文档 + - 代码 + - 注释 + - 评审 + - 团队知识沉淀 + - 知识库(Wiki) + - 社区(Community) + - 代码库(Codebase) + - 简报(Newsletter) +### 领导团队(Tech Lead) +- 核心能力 + - 领导力 + - 职权(Authority) + - 非职权 + - 团队成员 + - 项目干系人 + - 开发 + - 架构 +- 反面模式 + - 招聘听话的团队成员 + - 忽视表现差的团队成员 + - 忽视人性问题 + - 做老好人 + - 打破招聘门槛 + - 把团队成员当孩子看待 +- 正面模式 + - 不自我 + - 谦虚不自大 + - 信任团队成员 + - 接受团队成员反馈 + - 当犯错时及时道歉 + - 做一个禅师 + - 始终保持冷静 + - 通过提问帮助成员解决问题 + - 做团队催化剂 + - 构建团队共识 + - 扫清团队绊脚石 + - 技术问题 + - 非技术问题 + - 委托对的人解决问题 + - 成为团队老师和导师 + - 给团队成员成长机会 + - 量体裁衣式指导团队成员 + - 给团队设定清晰目标 + - 真诚 + - 友善富有同理心的提出反馈 + - 正面提出负面反馈 + - 追踪团队成员幸福感 + - 心情曲线 + - 匿名收集反馈 + - 一对一与成员沟通 + - 与团队成员沟通职业规划 + - 其它建议 + - 委托任务但也与团队成员一块解决问题 + - 培养可替换自己的第二梯队成员 + - 在问题发酵膨胀前解决掉它 + - 保护团队成员免受无关因素干扰 + - 让团队成员知道当他们做的好时 + - 在可控范围内给团队成员提供试错的机会 +- 大规模团队领导力 + - 总是在做决策 + - 识别盲点 + - 权衡利弊 + - 决策,然后迭代 + - 总是不在场 + - 建立自驱团队 + - 划分问题空间 + - 总是在扩大规模 + - 通过成功的循环模式解决难题 + - 解决重要的委托,委托紧急的问题 + - 学会放弃 + - 保护好精力 +### 工程效率测量 +- 是否值得测量 + - 期望的结果是什么?为什么? + - 如果数据符合预期,会采取什么行动? + - 如果结果与预期相反,会采取什么行动? + - 谁在何时决定对结果采取行动? +- GSM框架 + - 目标(Goals) + - 代码质量(Quality of the code) + - 工程师注意力(Attention from engineers) + - 认知复杂度(Intellectual complexity) + - 节奏和速度(Tempo and velocity) + - 满意度(Satisfaction) + - 信号(Signals) + - 指标(Metrics) + - 定量指标(Quantitative) + - 定性指标(Qualitative) +## 过程 +## 工具 +``` + +文化为何是软件工程很重要的一个要素?我在刚入IT行业时对软件开发的理解只是学习某个编程语言来写代码。之后经过多年多个项目的历练,我逐渐对整个软件开发的全貌有了一定的理解。但还是停留在对软件开发过程的了解及相关工具的使用,直到读完这本书的文化篇后才意识到了文化的重要性。 + +文化虽然是和技术没有直接关系的一些软性的东西,但文化就像水一样,利用好它的力量,可以让软件开发变得更高效、高质。 + +### 团队协作的基石 + +软件开发早已不是单兵作战的时代了,虽然人们喜欢听黑客奇迹般的故事,但现在的中大型软件都是团队的集体智慧产出。 + +![](https://img.bmpi.dev/63d3f7fb-9e20-7d21-6ea1-d7601259b3f5.png) + +![](https://img.bmpi.dev/414a14e0-e5b5-84c2-5a15-b17526d84e00.png) + +如上是两大顶级开源基金会 [@TheASF](https://twitter.com/TheASF) 与 [@linuxfoundation](https://twitter.com/linuxfoundation) 的知名项目,成千上万的软件工程师的集体协作开发了达上亿的代码行数最终才诞生了这些伟大的项目。 + +而团队协作最重要的是成员间的谦虚、尊重与信任。如果没有这些最基本的条件,集体协作将会困难重重,团队成员无法形成稳固的合作关系,时间和精力很容易被内耗完。 + +### 打造知识共享文化 + +软件工程与传统工程的区别在于其是一项知识密集型的活动,其中涉及到了大量的知识管理工作。[知识管理](/tags/知识管理/)很容易做差,最终产生了以下的一些问题: + +- 缺乏心理安全:当一个新成员进入组织的时候,会因为缺乏心理安全感而不敢暴露自己不懂的知识,这会阻碍该成员的个人成长,最终也会传导到团队的开发效率上来。 +- 知识孤岛:知识没有分享的文化土壤,每个团队都闷头搞自己的项目,会逐渐产生很多重复造轮子的现象。 +- 知识单点故障:团队内的重要信息只有一个人知道,而这个人又没有分享给其他人,一旦这个人不在场,团队将无法正常运转。 +- 团队知识断层:团队内的技术专家并没有意愿将个人的经验传授给团队其它成员,导致团队的运转离不开一小部分技术专家,一旦这些人离开,整个团队将无法正常运转。 +- 鹦鹉行为:这种很容易出现在团队新成员,由于经验限制或背景上下文了解有限,而在不了解其原理的情况下复制一些代码。虽然系统可以运行,但潜在的埋下了一些问题。 +- 知识禁区:团队成员由于经验限制或不了解背景上下文,而不敢修改代码库的某些遗留代码。 + +这些都是组织学习文化中会遇到的一些挑战。Google在打造知识共享文化上做了很多尝试,比如营造安全的学习环境,鼓励新人通过提问与分享来提高个人的技术。这些实践在敏捷过程中也可以看到,比如: + +- 鼓励个人反馈的文化(Feedback)。对事不对人,根据事实对成员提供建设性的反馈,帮助成员变的更好。 +- 鼓励团队成员定期做分享(Session)。比如每周某个成员可以做某个主题的Session,这种实践能让分享者对分享的主题有更深的了解。 +- 每日站会同步工作进度。不仅可以让其它成员了解你所工作的范围,还能告知其它成员潜在的交付风险。 +- 定期回顾(Retro)。团队定期举行迭代回顾的会议,回顾最近工作中好的、不好的的方面及相关建议,最终制定出一些行动来提升团队的工作效率、质量与幸福感。 +- 团队Wiki工作区。团队知识沉淀的体现,新人可以通过查阅这些资料迅速了解项目背景上下文。 +- 代码评审(Code Diff)。代码评审在Google的软件工程中是一项全员必须执行的实践。代码评审可以提高团队成员的编码水平,发掘代码潜在Bug,识别代码坏味道,形成统一的编码风格,共享业务与技术知识,消减团队知识断层的影响,甚至可以解决鹦鹉行为与知识禁区的问题。比如我所在的项目上团队所有开发成员每天会拿出一小时做集体的Code Diff,虽然成本不低,但其收益也很高。 +- 结对编程(Pair Programming)。结对编程看起来是两个人在做一个人的活,但一般也有两种模式:乒乓模式(Ping-Pong)与领航员观察者模式(Navigator-Observer),前者适合以TDD的方式开发,后者适合老带新。结对编程是成本高但非常高效的知识共享的实践,需结合项目实际的带宽决定当前是否采用此模式开发。比如我们项目会在交付时间不紧张时采用此开发模式。 + +以上的一些实践需结合组织或项目的实际带宽来决定是否使用,比如Code Diff一般是建议或者强制的,而结对编程是可选的,分享(Session)和回顾可能是定期或不定期的。 + +### 领导团队(Tech Lead) + +当做了一段时间独立贡献者(Individual Contributor)后,可能有机会做Tech Lead了。Tech Lead是一个团队必备的角色,具有一定的职权影响力,当然更多的是采用非职权影响力去带领团队。 + +如何领导团队是个复杂的工作,对技术人员来说,从技术到管理是很难的事情。 + +```markmap +# 领导团队(Tech Lead) +- 核心能力 + - 领导力 + - 职权(Authority) + - 非职权 + - 团队成员 + - 项目干系人 + - 开发 + - 架构 +- 反面模式 + - 招聘听话的团队成员 + - 忽视表现差的团队成员 + - 忽视人性问题 + - 做老好人 + - 打破招聘门槛 + - 把团队成员当孩子看待 +- 正面模式 + - 不自我 + - 谦虚不自大 + - 信任团队成员 + - 接受团队成员反馈 + - 当犯错时及时道歉 + - 做一个禅师 + - 始终保持冷静 + - 通过提问帮助成员解决问题 + - 做团队催化剂 + - 构建团队共识 + - 扫清团队绊脚石 + - 技术问题 + - 非技术问题 + - 委托对的人解决问题 + - 成为团队老师和导师 + - 给团队成员成长机会 + - 量体裁衣式指导团队成员 + - 给团队设定清晰目标 + - 真诚 + - 友善富有同理心的提出反馈 + - 正面提出负面反馈 + - 追踪团队成员幸福感 + - 心情曲线 + - 匿名收集反馈 + - 一对一与成员沟通 + - 与团队成员沟通职业规划 + - 其它建议 + - 委托任务但也与团队成员一块解决问题 + - 培养可替换自己的第二梯队成员 + - 在问题发酵膨胀前解决掉它 + - 保护团队成员免受无关因素干扰 + - 让团队成员知道当他们做的好时 + - 在可控范围内给团队成员提供试错的机会 +- 大规模团队领导力 + - 总是在做决策 + - 识别盲点 + - 权衡利弊 + - 决策,然后迭代 + - 总是不在场 + - 建立自驱团队 + - 划分问题空间 + - 总是在扩大规模 + - 通过成功的循环模式解决难题 + - 解决重要的委托,委托紧急的问题 + - 学会放弃 + - 保护好精力 +``` + +对于此我的个人经验也不是很丰富,所以想了解此部分的推荐阅读此书原文。当然下面这些文章也不错: + +- [The Definition of a Tech Lead](https://www.patkua.com/blog/the-definition-of-a-tech-lead/) +- [What Does a Software Tech Lead Do?](http://allyouneedisbackend.com/blog/2018/08/03/what-does-a-tech-lead-do/) +- [Tech Lead](https://icodebook.com/tags/tl/) +- [如何应对团队协作的五大障碍](https://insights.thoughtworks.cn/how-to-solve-five-dysfunctions-of-team/) +- [技术决策与团队认知负载](https://insights.thoughtworks.cn/monolith-microservice-architectural-tradeoff-cognitive-load/) + +### 工程效率测量 + +没有测量就没有优化。只有测量到团队的工程效率后,我们才有可能制定提升效率的行动。Google设计出了GSM框架来测量工程效率。 + +```markmap +- GSM框架 + - 目标(Goals) + - 代码质量(Quality of the code) + - 工程师注意力(Attention from engineers) + - 认知复杂度(Intellectual complexity) + - 节奏和速度(Tempo and velocity) + - 满意度(Satisfaction) + - 信号(Signals) + - 指标(Metrics) + - 定量指标(Quantitative) + - 定性指标(Qualitative) +``` + +工程效率的测量一般发生在大规模团队或组织级别,我所经历的项目上并没有此实践。对小型团队来说,可以通过简单的一些问卷调查这类定性的方式来收集团队成员的反馈,当然也可以通过一些量化的指标如流水线构建速度、迭代开发速率、代码静态分析结果、测试覆盖率等指标测量团队的工程效率。 + +推荐进一步阅读的文章: + +- [敏捷交付的工程效能治理](https://insights.thoughtworks.cn/engineering-productivity-governance-in-agile-delivery/) + +## 总结 + +软件工程是一项复杂的知识工程,这让其区别于传统的项目管理。Google的软件工程文化与以人为中心的敏捷过程所倡导的理念有很多相似之处。 + +但反观国内很多软件公司虽然也用上了敏捷过程的方法论,但底层的文化土壤还是以过程为中心的方法论,对团队成员的不信任,没有分享文化,团队领导一言堂还是存在的。希望这种现象能随着国内IT行业的逐渐成熟越来越少吧。 diff --git a/content/dev/software-engineering-at-google/process.zh-cn.md b/content/dev/software-engineering-at-google/process.zh-cn.md new file mode 100644 index 000000000..d80009140 --- /dev/null +++ b/content/dev/software-engineering-at-google/process.zh-cn.md @@ -0,0 +1,592 @@ +--- +title: "Google软件工程之过程篇" +date: 2022-08-08 +draft: false +tags: ["软件工程", "Style Guide", "Code Review", "Technical Documentation", "Unit Test", "Test Pyramid", "Deprecation"] +keywords: "" +description: "本文是《Software Engineering at Google》的读书笔记,同时会穿插分享我对软件工程的理解。本文主要介绍软件工程的过程,主要包括Code Review、技术文档与自动化测试(单元、集成、E2E)。" +isCJKLanguage: true +og_image: "https://img.bmpi.dev/d70f36dc-76bb-fa3c-4cb9-43ce186b8203.png" +categories: [ + "软件工程" +] +markmap: + enabled: true + id: "software-engineering-at-google-process" +--- + +- [风格指南(Style Guide)](#风格指南style-guide) +- [代码评审(Code Review)](#代码评审code-review) + - [Code Review v.s. Code Diff](#code-review-vs-code-diff) +- [技术文档(Technical Documentation)](#技术文档technical-documentation) +- [测试(Testing)](#测试testing) + - [单元测试(Unit Testing)](#单元测试unit-testing) + - [测试替身(Test Doubles)](#测试替身test-doubles) + - [较大型的测试(Larger Testing)](#较大型的测试larger-testing) +- [弃用(Deprecation)](#弃用deprecation) +- [总结](#总结) + +[上篇](/dev/software-engineering-at-google/culture/)介绍了Google软件工程中的文化部分,本篇介绍软件工程中主要的过程部分,包括编码风格指南、代码评审、技术文档、自动化测试(单元测试、集成测试与较大型测试)与弃用。 + +> 以下是《Software Engineering at Google》一书第三部分过程篇的思维导图,由于此部分占全书近40%,所以本文不会详细的介绍其中的概念,想详细了解的读者建议阅读原书。本文会结合此书这部分内容分享作者的个人理解及相关经验。 + +```markmap +# Google软件工程 +## 文化(Culture) +## 过程(Process) +- Style Guide +- Code Review + - 好处 + - 代码正确性 + - 代码可读性 + - 代码一致性 + - 促进团队知识共享 + - 塑造团队工程文化 + - 最佳实践(Best Practices) + - 友善且专业 + - 小的变更 + - 清晰的变更描述 + - 小规模评审 + - 尽可能自动化 + - 金字塔模型 + - 评审类型 + - 特性代码 + - 优化代码 + - Bug Fixes + - 重构 +- Technical Documentation + - 一些实践 + - 文档即代码 + - 文档流程化 + - 了解受众 + - 文档评审 + - 文档类型 + - 引用类(Reference) + - 设计类(Design) + - 教程类(Tutorial) + - 概念类(Conceptual) + - 登陆页(Landing Page) + - 文档写作最佳实践 + - 5W+1H + - 三段式 + - 好文档的特征 + - 弃用文档 +- Testing + - 测试概述 + - 测试代码的好处 + - 更少的Debugging + - 提升对代码变更的信心 + - 测试代码是更好的文档 + - 让代码评审更简单 + - 好的测试反向提升代码设计 + - 自动化测试让持续交付变的更容易 + - 设计测试套件 + - 测试大小 + - 小 + - 中 + - 大 + - 实践 + - 测试范围 + - 窄范围测试 + - 中范围测试 + - 广范围测试 + - 实践 + - 金字塔模型 + - 反模式 + - 测试模型的选择 + - The Beyoncé Rule + - 测试原则:测试所有不想被破坏的东西 + - 测什么 + - 关于测试覆盖率 + - 仅测量单元测试覆盖率 + - 覆盖率目标是底线 + - 考虑被测试的行为 + - 测试套件的陷阱 + - 脆弱的测试 + - 缓慢的测试 + - Google测试文化的历史 + - 针对新员工的专项课程 + - 测试认证计划 + - 在厕所推广测试(TotT) + - 自动化测试的局限 + - 无法取代人工探索性测试 + - 单元测试(Unit Testing) + - 优势 + - 提高可维护性 + - 脆弱的测试 + - 不清晰的测试 + - 好的实践 + - 测试行为而非方法 + - 测试名称应提现测试行为 + - 测试不应包含逻辑 + - DAMP原则 + - 测试替身(Test Doubles) + - 基本概念 + - 缝(Seams) + - Mocking框架 + - 测试替身技术 + - 伪造(Faking) + API的轻量级实现 + - 打桩(Stubbing) + 赋予函数行为的过程 + - 交互测试(Interaction Testing) + 验证函数调用行为及参数 + - 何时使用 + - 真实实现 + - 伪造(Faking) + - 打桩(Stubbing) + - 交互测试(Interaction Testing) + - 较大型的测试 + - 为什么需要 + - 挑战 + - 结构 + - 类型 + - 工作流 +- Deprecation + - 为什么 + - 代码是负债而非资产 + 因为需要维护 + - 过时的系统需要弃用 + - 维护成本太高 + - 功能无用、重复或被替代 + - 旧不意味着过时 + - 代码越少,功能越多 + - 困难 + - 系统用户越多,越难弃用 + - 系统启用,代码保留 + - 被弃用的,和没有准备好的 + - 演进而非弃用 + - 在设计时考虑弃用 + - 谨慎启动新项目 + - 类型 + - 咨询 + - 强制 + - 警告 + - hope is not a strategy + - alert fatigue + - 流程 + - Owner + - 里程碑 + - 工具 + - How/By who + - Code Search + - Testing +## 工具(Tool) +``` + +## 风格指南(Style Guide) + +```text +We value “simple to read" over "simple to write." (Software Engineering at Google - Style Guides and Rules) +``` + +代码可能只会被写一次,但会被读很多次。如果团队成员的代码风格都不统一,可读性会很差,所以保持团队代码风格统一很重要。 + +历史证明,能写的很飘逸的编程语言使用人数一般都不会很多,典型的如古老的Perl语言,可以达到“一人千面”的代码风格。而写起来中规中矩甚至没有啥高级技巧的语言如Java、Go等在工业上反而用的很多。 + +Google的代码风格指南不太适合一般规模的公司,所以此部分不做过多介绍。从我的个人经验来说,一般项目上会配置一套自动化的代码风格检查工具(如[checkstyle](https://github.com/checkstyle/checkstyle)),甚至会集成到流水线(Pipeline)中强制团队保持一致的代码风格。某些编程语言如Go在构建工具中也提供了gofmt的代码格式化工具。 + +代码风格指南只能解决一些很基本的可读性问题,如代码缩进、函数命名风格、代码行数限制等。但代码的可读性可不只体现在这些表面,更深层次的可读性问题如API语义的可读性该怎么解决?一个可行的实践是代码评审。 + +## 代码评审(Code Review) + +代码评审是如此重要,以至于其在Google是必须做的一个实践过程。它能提供以下的好处: + +- 代码正确性:评审人员可能发现评审代码中的逻辑问题,从而提前消除一些潜在的Bug; +- 代码可读性:代码能否被其他人很容易的理解?API语义设计是否合理?是否包含测试?是否有必要的文档与注释? +- 代码一致性:代码风格是否与团队和组织保持一致? +- 促进团队知识共享:代码评审可以让团队其他成员了解你所做工作的上下文; +- 塑造团队工程文化:团队保持代码评审的实践,本身也是团队工程文化的一部分,能让新的成员迅速适应团队工程文化; + +代码评审的最佳实践有以下: + +- 友善且专业 +- 小的变更 +- 清晰的变更描述 +- 小规模评审 +- 尽可能自动化 +- 金字塔模型 + +代码评审金字塔模型如下图所示: + +{{< figure src="https://img.bmpi.dev/94f914a6-e7cd-1075-062b-dfbdf3880f15.png" caption="图片来源:《The Code Review Pyramid - Gunnar Morling》" link="https://www.morling.dev/blog/the-code-review-pyramid/">}} + +代码评审的反模式是倒金字塔模型,也就是很多时间花费在了可以自动化执行的部分比如代码风格的统一、自动化测试等,但在金字塔模型里,代码评审应该把主要的精力放在API语义、实现语义及文档等部分。 + +### Code Review v.s. Code Diff + +Diff 和 Review 的区别在于前者是一个团队集体行动,团队成员一块看某个开发者前一天写的代码,这样的好处在于每个人都能反馈,也能了解其他人做的工作,防止一些信息不同步的问题。代码评审一般是一两个人(可能甚至是团队外部的人)去审查对方要合入主干分支的代码,更适合外部人员提交代码到主干这种 GitHub PR 分支管理模式。 + +我所在项目的团队每天会做 Code Diff ,这是个必须的实践。团队规模在几人以内可以让每个人都有时间讲解自己的代码,如果代码太多,那可以给每个人一个时间限制。如果团队太大那可以拆分成多个 stream 来管理,总之 Diff 的人员不能太多,但每天都应该花时间做,因为收益要高于成本,可以统一代码风格,保证可读性,提高成员技术水平。 + +## 技术文档(Technical Documentation) + +```markmap +- Technical Documentation + - 一些实践 + - 文档即代码 + - 文档流程化 + - 了解受众 + - 文档评审 + - 文档类型 + - 引用类(Reference) + - 设计类(Design) + - 教程类(Tutorial) + - 概念类(Conceptual) + - 登陆页(Landing Page) + - 文档写作最佳实践 + - 5W+1H + - 三段式 + - 好文档的特征 + - 完整 + - 准确 + - 清晰 + - 弃用文档 +``` + +技术文档与代码一样应该得到开发者同等的重视,但有太多文档与代码不同步的场景出现,导致文档的可用性大大降低。为什么会出现这种问题?一方面是因为开发者重视度不够的问题,另外一方面是因为写一份好的技术文档并不是一件简单的事情。 + +如何写一份好的技术文档?推荐阅读如下的文章: + +- [Technical Writing for Developers](https://css-tricks.com/technical-writing-for-developers/) +- [Technical Writing | Google Developers](https://developers.google.com/tech-writing/overview) +- [SEO Copywriting Guide](https://www.semrush.com/blog/seo-copywriting/) + +开发人员不喜欢文档的另外一个原因在于,代码和文档的工作流程并不相同,一般文档都存放在与代码不同的位置,比如某个FTP目录以Word的格式存在。要是文档的编写可以和代码在同一套工作流里,就能极大的降低开发者的心智负担,这正是[Docs-as-code](https://www.writethedocs.org/guide/docs-as-code/)的设计理念,具体的流程实践可以看这篇文章: + +- [Working in public — our docs-as-code approach](https://blog.cloudflare.com/our-docs-as-code-approach/) + +## 测试(Testing) + +```markmap +- 测试概述 + - 测试代码的好处 + - 更少的Debugging + - 提升对代码变更的信心 + - 测试代码是更好的文档 + - 让代码评审更简单 + - 好的测试反向提升代码设计 + - 自动化测试让持续交付变的更容易 + - 设计测试套件 + - 测试大小 + - 小 + - 单线程 + - 无法执行阻塞线程的操作 + (测试替身) + - 类比单元测试 + - 中 + - 可跨进程但不能跨机器 + - 类比集成测试 + - 大 + - 可跨机器 + - 主要验证环境配置 + - 在构建和发布时执行此类测试 + - 类比E2E测试 + - 实践 + - 测试之间应隔离 + - 清晰简单的测试 + - 测试范围 + - 窄范围测试 + - 验证代码逻辑 + - 类比单元测试 + - 中范围测试 + - 验证系统组件交互 + - 类比单元测试 + - 广范围测试 + - 验证系统间交互 + - 类比E2E测试 + - 实践 + - 金字塔模型 + - 80%单元测试 + - 15%集成测试 + - 5%E2E测试 + - 反模式 + - 冰激凌甜筒模型 + - 沙漏模型 + - 测试模型的选择 + - 工程生产力 + - 产品信心 + - The Beyoncé Rule + - 测试原则:测试所有不想被破坏的东西 + - 测什么 + - 性能 + - 行为正确性 + - 可用性 + - 安全 + - 异常与错误 + - 关于测试覆盖率 + - 仅测量单元测试覆盖率 + - 覆盖率目标是底线 + - 考虑被测试的行为 + - 对系统的正常工作有信心 + - 对依赖的破坏性更新有信心 + - 测试稳定可靠吗 + - 测试套件的陷阱 + - 脆弱的测试 + - 糟糕的测试代码 + - 滥用Mock + - 缓慢的测试 + - 处理大数据集 + - 启动系统 + - 同步等待其他依赖 + - Google测试文化的历史 + - 针对新员工的专项课程 + - 测试认证计划 + - 在厕所推广测试(TotT) + - 自动化测试的局限 + - 无法取代人工探索性测试 +``` + +测试是软件工程过程中很重要的一个组成部分,而这里的测试主要指自动化测试过程,人工测试占比很少。测试也有一个金字塔模型,如下图所示: + +![](https://img.bmpi.dev/ca9e23e9-b0c0-3b22-bce3-abbdcabc684f.png) + +关于测试金字塔的细节,推荐阅读这篇文章: + +- [The Practical Test Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html) + +开发人员写自动化测试有如下好处: + +- 更少的Debugging:有了自动化测试后,系统的很多行为可以通过测试代码观察到。当然Bug一旦产生说明测试代码覆盖不全面,需要补上相关的测试,久而久之,测试代码就形成了非常全面的防护网。 +- 提升对代码变更的信心:当有了测试防护网后,对代码一旦产生破坏性的更新,测试代码会失败,这就给开发人员机会在部署前去修复此问题。 +- 测试代码是更好的文档:当面对一个完全陌生的代码库时,除了有限的文档,另外一个了解系统行为的方式就是看测试代码。测试代码相比文档,有着更全面清晰的业务细节,能给予开发人员更多的信息去了解此业务系统。 +- 让代码评审更简单:测试代码相比生产代码更接近业务视角,能让评审人员从业务系统对外行为的视角去了解生产代码的意图。这样也能让评审人员做出更有效的反馈意见。 +- 好的测试反向提升代码设计:要让系统模块具备一定的测试性才能写出测试代码,所以有测试的代码从设计的角度看,其可读性与解耦度相比没有测试代码的要更高。敏捷实践中推崇的TDD(Test Driven Development)就是一种通过测试驱动出好的实现代码的实践。 +- 自动化测试让持续交付变的更容易:如果没有自动化测试的帮助,代码部署上去后出Bug的概率要更高,这会提高系统交付的时间。 + +> 没有测试代码的系统是遗留系统。 + +### 单元测试(Unit Testing) + +```markmap +- 单元测试(Unit Testing) + - 优势 + - 小且快,能立即获得反馈 + - 容易编写 + - 能提高测试覆盖率 + - 失败时能快速定位问题 + - 能作为技术文档 + - 提高可维护性 + - 脆弱的测试 + - 不变的测试 + - 重构 + (不应修改测试) + - 新特性 + (不应修改测试) + - Bug Fixes + (不应修改测试) + - 业务行为改变 + (只有在此情况下才修改测试) + - 测试公开接口 + - 避免测试具体实现 + - 合适的测试范围 + - 局部支持类无需独立测试 + - 只测试公开的接口 + - 通用支持类需有独立测试 + - 测试状态而非交互 + - 不清晰的测试 + - 编写完整简洁的测试 + - 通过冗余提供完整的信息 + - 不包含无关的信息 + - 好的实践 + - 测试行为而非方法 + - given + - when + - then + - 测试名称应提现测试行为 + - 测试行动 + - 结果预期 + - 测试不应包含逻辑 + - 测试代码简单直接 + - 适当冗余而非精简 + - DAMP原则 + Descriptive And Meaningful Phrases + 当在测试代码共享代码和数据时采用此规则 +``` + +单元测试作为占比测试金字塔最大部分的底座,重要性不言而喻。它的优势很多,但Google在多年的实践中发现,提高单元测试的可维护性非常重要。而难以维护的测试代码主要有两方面造成: + +- 测试脆弱:当在代码重构、添加新特性及修复Bug时,会出现一些测试无法跑通,只能通过修改测试的方式来解决,这说明已有的测试很脆弱。好的测试应该只有在系统的业务行为发生改变时,才需要修改生产代码和测试代码。造成测试脆弱的原因有很多种,可能的原因包括测试隔离没做好,比如依赖了很多共享的全局性状态,或者测试了非公开的函数或方法,又或者测试的粒度过细,把很多实现细节给测试了。 +- 测试不清晰:不清晰的原因也有很多方面,比如测试的名称并没有体现其测试意图,在单个测试中测试了一些不必要的行为,又或包含了很多无关的信息。 + +要提高可维护性,一些好的实践包括以下方面: + +- 测试行为而非方法:很多测试框架如Junit都倡导Given/When/Then三段式测试编写方式,这样可以从验收标准(Acceptance Criteria)的业务视角去编写测试,而非针对单个函数或方法去编写测试(这很容易写出脆弱的测试)。 +- 测试名称应提现测试行为:当单元测试失败时,最先看到的就是测试失败单元的名称,好的测试名称能以最直接的方式体现该测试意图,所以测试名称长一些也可以。 +- 测试不应包含逻辑:因为测试单元本身并没有额外的测试,如果测试包含了比较复杂的逻辑,可能会导致测试代码的Bug。所以测试代码中尽可能不包含逻辑计算的过程。 +- DAMP(Descriptive And Meaningful Phrases)原则:在生产代码上业界倡导[DRY](https://zh.wikipedia.org/wiki/一次且仅一次)(Don't repeat yourself)的基本原则。而在测试代码中,正如上面几条实践表明,一定程度上的代码冗余是有必要的,这能帮助我们编写出简单而清晰的测试代码。 + +单元测试的代码执行速度一定要快,但在要测试的生产代码中,可能包含了执行速度很慢的代码,比如网络或文件等I/O操作,又或者对数据库的请求,甚至需要整个应用启动来获得完整的执行环境。如何将这类慢的代码与真正要测试业务逻辑的代码隔离开来?那就是接下来要介绍的测试替身技术。 + +### 测试替身(Test Doubles) + +```markmap +- 测试替身(Test Doubles) + - 基本概念 + - 缝(Seams) + 通过使用测试替身 + 实现可测试性的技术 + - 依赖注入(Dependency Injection) + - Mocking框架 + - 测试替身技术 + - 伪造(Faking) + API的轻量级实现 + - 打桩(Stubbing) + 赋予函数行为的过程 + - 交互测试(Interaction Testing) + 验证函数调用行为及参数 + - 何时使用 + - 真实实现 + - 执行时间快 + - 确定性的 + - 实例构造简单 + - 伪造(Faking) + - 权衡维护成本与收益 + - 当真实实现不可用时 + - 考虑伪造的保真度 + - 需要写测试 + - 打桩(Stubbing) + - 谨慎使用打桩 + - 可能会让测试意图不清晰 + - 可能让测试变得脆弱 + 测试代码包含了实现细节 + - 可能让测试变得低效 + 难以测试有副作用的状态 + - 在真实实现和伪造不可用时 + - 少量函数仅依赖返回值时 + - 交互测试(Interaction Testing) + - 容易让测试变得脆弱 + 测试代码包含了实现细节 + 尽可能测试状态而非交互 + - 需测试函数副作用时使用 + - 需测试函数调用次序时使用 + - 不要过度测试 + 仅测试必须测试的信息 +``` + +测试替身能通过一些模拟或伪造的技术来控制被测试代码的执行路径,比如在OOP中我们可以通过接口的多个实现,来完成生产代码与测试代码的不同实现。 + +由于测试替身技术本身非常成熟,所以本文不做基本的介绍,推荐阅读这篇文章进一步了解: + +- [TestDouble](https://martinfowler.com/bliki/TestDouble.html) + +在Google的多年实践中发现,测试替身很容易被滥用,造成很多脆弱的测试,而被滥用最多的就是打桩(Stubbing)技术。不同替身技术都有其适用场景,推荐的一个决策流程是: + +- 如果生产代码的执行时间足够快,那就不需要替身技术,直接测试生产代码; +- 如果伪造(Faking)的实现成本很低,且伪造的保真度够高(能尽可能模拟真实的使用场景),则推荐使用伪造替身技术; +- 如果在前两者都不可用的情况下,仅被测试代码只依赖少量函数或方法的返回值时,可以使用打桩(Stubbing)替身技术; +- 交互测试(Interaction Testing)替身技术谨慎使用,如果要用也仅在需测试函数副作用或调用次序时使用,并且不要过度测试不必要的数据; + +### 较大型的测试(Larger Testing) + +```markmap +- 较大型的测试 + - 为什么需要 + - 单元测试保真度的问题 + - 环境配置的问题 + - 负载下的问题 + - 预期外的行为与副作用 + - 紧急行为和真空效应 + - 意外的修改 + - 单元测试覆盖不到的位置 + - 挑战 + - 维护与归属的问题 + - 缺乏开发标准 + - 结构 + - SUT + - 契约测试(Contract test) + - 测试数据 + - 种子数据 + - 测试流量 + - 采样数据 + - 手工数据 + - 真实基线 + - 验证 + - 手工 + - 断言 + - 类型 + - 功能性测试 + - 浏览器和设备测试 + - 性能测试 + - 部署配置测试 + - 探索性测试 + - Bug Bash + - A/B测试 + - 用户验收测试(UAT) + - 混沌测试(Chaos) + - 工作流 + - 编写 + - 运行 + - 加速测试 + - 降低内部系统超时和延迟 + - 优化测试构建时 + - 驱除松散性 + - 密封的SUT环境 + - 使测试易于理解 + - 清晰的故障定位信息 + - 尽可能减少定位问题的成本 + 如日志打印分布式追踪ID而非堆栈信息 + - 提供支持和联系信息 + - 维护 +``` + +在测试金字塔的顶端是占比只有20%的集成测试与E2E测试,虽然占比少,但其却可解决单元测试的以下问题: + +- 保真度的问题:单元测试因使用测试替身来加速执行时间,但替身与实现本身就存在保真度的问题,一旦被替身的实现发生改变,单元测试因模拟行为未变,可能造成一些意想不到的Bugs。 +- 环境配置的问题:环境的问题只能在接近生产环境的测试环境(如UAT)环境中去测试与发现问题,这是单元测试无法覆盖的测试范围。如Google的一些重大全球性的Bug都和环境配置问题有关系。 +- 负载下的问题:在压力测试下,系统的行为表现如何?性能是否能达到业务要求?这类非功能性的需求测试只能在E2E测试中完成。 +- 预期外的行为与副作用:单元测试是在开发者预期的视角下完成的,所以存在一定的视角盲区。在一个接近生产环境的测试环境测试是发现这类问题最好的办法。 +- 紧急行为和真空效应:如果系统的运行时环境发生一些意外的修改,如集群网络配置或部署配置发生变更,这类问题也只能在集成环境中发现。 + +较大型测试的编写与维护都是成本高昂的,在我们项目实践中,一般和业务系统强相关的集成测试和部分E2E测试都是业务开发团队完成的。但一些公共的E2E测试,比如某个全局性的功能性测试,可能由一个独立的小组完成,也可能只完成一个MVP的版本,之后由业务系统维护团队开发完成。 + +推荐进一步阅读的文章: + +- [浅谈契约测试](https://insights.thoughtworks.cn/contract-testing/) +- [契约测试之核心解惑](https://ariman.cn/2019/05/19/契约测试之核心解惑/) + +## 弃用(Deprecation) + +```markmap +- Deprecation + - 为什么 + - 代码是负债而非资产 + 因为需要维护 + - 过时的系统需要弃用 + - 维护成本太高 + - 功能无用、重复或被替代 + - 旧不意味着过时 + - 代码越少,功能越多 + - 困难 + - 系统用户越多,越难弃用 + - 系统启用,代码保留 + - 被弃用的,和没有准备好的 + - 演进而非弃用 + - 在设计时考虑弃用 + - 谨慎启动新项目 + - 类型 + - 咨询 + - 强制 + - 警告 + - hope is not a strategy + - alert fatigue + - 流程 + - Owner + - 里程碑 + - 工具 + - How/By who + - Code Search + - Testing +``` + +代码是资产还是负债?Google的答案是负债,因为代码需要不断的维护才能正常工作。负债是有高昂的利息,降低负债最好的办法就是在不需要的时候砍掉它。而这就是弃用过程的价值。 + +对开发人员来说,弃用是个难以接受的过程,因为幸苦写的代码,很难下定决心去销毁它。所以一个中庸之道是在代码将要被弃用前,想办法通过演进的方式给予其二次生命。如果非要弃用,也只是停止维护和运行,旧的代码依旧会在代码仓库中可被搜索到,历史记录也会被保留。 + +我的个人项目实践是,下线一个系统是一件需要重视的过程。一个系统一旦被发布,它被使用的场景就很难以想象,API的用户可能会以意想不到的方式去使用它。所以尽可能通过代码搜索去找到其被使用的场景,之后再给充足的Deadline广而告之,甚至可以主动与用户沟通,确保不会让其出现大的损失。 + +## 总结 + +代码可能只会被写一次,但会被读很多次。所以软件工程中的过程部分主要致力于解决代码可读性的问题。无论是风格指南、代码评审、文档甚至自动化测试,很大程度上都在为提高代码可读性。 + +写代码很容易,能写出易懂的代码却有难度。所以从这个角度看,写代码是个入门简单精通却难的技能,需要我们不断的精进,通过多种实践去提高这个技能。希望这篇文章能让你对写代码这件事有更多的理解。 diff --git a/content/dev/software-engineering-at-google/the-test-pyramid.excalidraw b/content/dev/software-engineering-at-google/the-test-pyramid.excalidraw new file mode 100644 index 000000000..efa438d76 --- /dev/null +++ b/content/dev/software-engineering-at-google/the-test-pyramid.excalidraw @@ -0,0 +1,878 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", + "elements": [ + { + "id": "sSVbJW3C-g7Rq-3z71dQO", + "type": "line", + "x": 820.8984375, + "y": 284.4375, + "width": 272.328125, + "height": 472, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 1717100817, + "version": 54, + "versionNonce": 909781919, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -272.328125, + 472 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "pEGcDne346VA7xq4fgz4d", + "type": "line", + "x": 822.6328125, + "y": 286.375, + "width": 270.10546875, + "height": 468, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 19778481, + "version": 32, + "versionNonce": 42410449, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 270.10546875, + 468 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "FiD_PAR1_1clJDP4y5EoX", + "type": "line", + "x": 549.7578125, + "y": 756.9140625, + "width": 540.83984375, + "height": 0, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 1035445727, + "version": 59, + "versionNonce": 642867135, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 540.83984375, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "LZP_75Xrhm6XDqvibBdwM", + "type": "line", + "x": 781.2253643263132, + "y": 360.7265625, + "width": 83.09651950597754, + "height": 0, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 772868337, + "version": 149, + "versionNonce": 700759985, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 83.09651950597754, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "Z8Jm8Cqw2Bq7MiJ-Y3PQa", + "type": "line", + "x": 737.0859375, + "y": 432.1328125, + "width": 170.44140625, + "height": 0, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 685741439, + "version": 68, + "versionNonce": 1370296287, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 170.44140625, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "gXJRm9ompqmDdXDwLz5FI", + "type": "text", + "x": 728.78515625, + "y": 222.4921875, + "width": 190, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 2046218801, + "version": 168, + "versionNonce": 928227729, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "text": "Auto Test Pyramid", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "Auto Test Pyramid" + }, + { + "id": "x3ZhV0L6slDewPLOtoTf5", + "type": "text", + "x": 760.7453209100577, + "y": 585.26896402189, + "width": 149, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 1834072337, + "version": 147, + "versionNonce": 1381874687, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "text": "Unite Tests\n(single process)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 43, + "containerId": null, + "originalText": "Unite Tests\n(single process)" + }, + { + "type": "text", + "version": 163, + "versionNonce": 1483425649, + "isDeleted": false, + "id": "S_NjunrDM53uQQP4c-Ywm", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 763.79296875, + "y": 389.99609375, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 116.97265625, + "height": 16.067672561813193, + "seed": 1679470943, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999226, + "link": null, + "locked": false, + "fontSize": 12.854138049450555, + "fontFamily": 1, + "text": "Integration Tests", + "baseline": 11.067672561813193, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Integration Tests" + }, + { + "id": "vXAJBQQieLixAcKxLI6fG", + "type": "text", + "x": 804.2083726377354, + "y": 313.8413529778893, + "width": 33, + "height": 27, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 2026737215, + "version": 211, + "versionNonce": 744743967, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "text": "E2E\nTests", + "fontSize": 10.69737723214286, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 23, + "containerId": null, + "originalText": "E2E\nTests" + }, + { + "id": "PMYbOwOOl7taOwriwv5Rd", + "type": "arrow", + "x": 1126.0119994182967, + "y": 291.8982991456986, + "width": 0.09298971503062603, + "height": 427.5730787444114, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 1121410815, + "version": 168, + "versionNonce": 659763537, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -0.09298971503062603, + 427.5730787444114 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "VF866nBBSuVLXnBFOzLll", + "focus": 0.13764880952380953, + "gap": 12.691267895698616 + }, + "endBinding": { + "elementId": "_h6pn_xe-HGGbXtgTgXnY", + "focus": -0.13509114583333334, + "gap": 8.942684609889966 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "VF866nBBSuVLXnBFOzLll", + "type": "text", + "x": 1099.68359375, + "y": 254.20703125, + "width": 61, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 1582879071, + "version": 74, + "versionNonce": 263194687, + "isDeleted": false, + "boundElements": [ + { + "id": "PMYbOwOOl7taOwriwv5Rd", + "type": "arrow" + } + ], + "updated": 1660131999226, + "link": null, + "locked": false, + "text": "Slower", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "Slower" + }, + { + "id": "_h6pn_xe-HGGbXtgTgXnY", + "type": "text", + "x": 1096.5078125, + "y": 728.4140625, + "width": 68, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 1308401201, + "version": 57, + "versionNonce": 157474609, + "isDeleted": false, + "boundElements": [ + { + "id": "PMYbOwOOl7taOwriwv5Rd", + "type": "arrow" + } + ], + "updated": 1660131999226, + "link": null, + "locked": false, + "text": "Faster", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "Faster" + }, + { + "id": "6FezFWypVvibeROGDmltZ", + "type": "text", + "x": 979.66796875, + "y": 557.609375, + "width": 49, + "height": 25, + "angle": 1.0399241303226852, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 164796703, + "version": 256, + "versionNonce": 1512220767, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "text": "80%", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "80%" + }, + { + "type": "text", + "version": 341, + "versionNonce": 1291964689, + "isDeleted": false, + "id": "4A8bltC_tXntSI1jXj-LA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 1.0399241303226852, + "x": 879.9531249999999, + "y": 375.6406249999999, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 38, + "height": 25, + "seed": 954370609, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999226, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "15%", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "15%" + }, + { + "type": "text", + "version": 396, + "versionNonce": 1502364799, + "isDeleted": false, + "id": "u83N8atryAlw4FFowm3R2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 1.0399241303226852, + "x": 842.97265625, + "y": 304.34765625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32, + "height": 25, + "seed": 278136575, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999227, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "5%", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "5%" + }, + { + "type": "text", + "version": 446, + "versionNonce": 128419569, + "isDeleted": false, + "id": "7Ip6WVPKVJ8CkDpiiJ6f_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 772.1563414348082, + "y": 407.2565364453433, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 106, + "height": 18, + "seed": 823143551, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999227, + "link": null, + "locked": false, + "fontSize": 14.34767576863472, + "fontFamily": 1, + "text": "(single machine)", + "baseline": 13, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "(single machine)" + }, + { + "type": "text", + "version": 1095, + "versionNonce": 1303207071, + "isDeleted": false, + "id": "P1p4lAtuy4rw1G7l7B7tl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 767.7552061030044, + "y": 343.38573832560735, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 106.39826862948642, + "height": 14.846270041323669, + "seed": 252421521, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999227, + "link": null, + "locked": false, + "fontSize": 11.833859384805969, + "fontFamily": 1, + "text": "(multiple machines)", + "baseline": 10.846270041323669, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "(multiple machines)" + }, + { + "id": "J0S3QOpW7rimA4ATfEV7_", + "type": "text", + "x": 1146.0832823135563, + "y": 314.49829699598695, + "width": 263, + "height": 125, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 1209939935, + "version": 219, + "versionNonce": 870023377, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999227, + "link": null, + "locked": false, + "text": "- more complex\n- higher costs\n- slower execute time\n- more integration required\n- less reliable", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 118, + "containerId": null, + "originalText": "- more complex\n- higher costs\n- slower execute time\n- more integration required\n- less reliable" + }, + { + "type": "text", + "version": 343, + "versionNonce": 708210879, + "isDeleted": false, + "id": "b0TVDTcBtm-2c6ZZTLkuw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1146.628021387079, + "y": 585.6238509858254, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 258, + "height": 125, + "seed": 82247263, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999227, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "- simple tests\n- lower costs\n- quick execute time\n- less integration required\n- more reliable", + "baseline": 118, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- simple tests\n- lower costs\n- quick execute time\n- less integration required\n- more reliable" + }, + { + "id": "hvaSr2980aML8j5UTkbVE", + "type": "line", + "x": 741.3340444698583, + "y": 349.7225363641757, + "width": 56.12932697575434, + "height": 40.983719073141515, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 1718339647, + "version": 144, + "versionNonce": 255147697, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999227, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 56.12932697575434, + -40.983719073141515 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "L1LwU10D75ue7b_mX-U3G", + "type": "line", + "x": 738.1656280441985, + "y": 271.342120913913, + "width": 56.67446995932153, + "height": 36.335547990132625, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 1438208703, + "version": 296, + "versionNonce": 415760607, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999227, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 56.67446995932153, + 36.335547990132625 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "text", + "version": 653, + "versionNonce": 945569937, + "isDeleted": false, + "id": "vvq28BSyKSGTGm4AuWTZE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 616.7554826297848, + "y": 265.9205293474675, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 133, + "height": 78, + "seed": 1953577439, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999227, + "link": null, + "locked": false, + "fontSize": 12.69950664491254, + "fontFamily": 1, + "text": "- UI tests\n- functional tests\n- load tests\n- UAT\n- exploratory testing", + "baseline": 73, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- UI tests\n- functional tests\n- load tests\n- UAT\n- exploratory testing" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/content/dev/software-engineering-at-google/tool.zh-cn.md b/content/dev/software-engineering-at-google/tool.zh-cn.md new file mode 100644 index 000000000..b6d762a5a --- /dev/null +++ b/content/dev/software-engineering-at-google/tool.zh-cn.md @@ -0,0 +1,33 @@ +--- +title: "Google软件工程之工具篇" +date: 2022-08-19 +draft: true +tags: ["软件工程", "Branch Management", "Build System", "Code Search", "Static Analysis", "Dependency Management", "Continuous Integration", "Continuous Delivery"] +keywords: "" +description: "本文是《Software Engineering at Google》的读书笔记,同时会穿插分享我对软件工程的理解。本文主要介绍软件工程的相关工具,主要包括版本控制、分支管理、代码搜索、构建系统、代码静态化分析、依赖管理与CI/CD等。" +isCJKLanguage: true +og_image: "" +categories: [ + "软件工程" +] +markmap: + enabled: true + id: "software-engineering-at-google-tool" +--- + +> 以下是《Software Engineering at Google》一书第四部分工具篇的思维导图,由于此部分占全书近40%,所以本文不会详细的介绍其中的概念,想详细了解的读者建议阅读原书。本文会结合此书这部分内容分享作者的个人理解及相关经验。 + +```markmap +# Google软件工程 +## 文化(Culture) +## 过程(Process) +## 工具(Tool) +- Version control +- Branch management +- Code search +- Build system +- Static analysis +- Dependency management +- Continuous integration +- Continuous delivery +``` diff --git a/content/dev/tech-stack-of-side-project/index.zh-cn.md b/content/dev/tech-stack-of-side-project/index.zh-cn.md index 739d40e69..a9e9e8be2 100644 --- a/content/dev/tech-stack-of-side-project/index.zh-cn.md +++ b/content/dev/tech-stack-of-side-project/index.zh-cn.md @@ -6,6 +6,9 @@ tags: ["编程语言", "基础设施", "SaaS", "监控分析", "技术栈", "Inf keywords: "" description: "本文分享我的个人项目技术栈,包括编程语言、框架与库、数据库、基础设施、CI/CD、监控分析、设计及常用的SaaS服务。" isCJKLanguage: true +categories: [ + "个人成长" +] --- --- diff --git a/content/dev/time-in-distributed-system/index.zh-cn.md b/content/dev/time-in-distributed-system/index.zh-cn.md index a0ad9bb1c..27a1db503 100644 --- a/content/dev/time-in-distributed-system/index.zh-cn.md +++ b/content/dev/time-in-distributed-system/index.zh-cn.md @@ -8,7 +8,7 @@ description: "本文梳理在分布式系统中时间对事件序列的影响, isCJKLanguage: true og_image: "https://img.bmpi.dev/3d3413ee-7024-5ff7-5904-e9120dd5690f.png" categories: [ - "什么是X" + "分布式技术" ] markmap: enabled: true diff --git a/content/dev/vscode-on-cloud/index.zh-cn.md b/content/dev/vscode-on-cloud/index.zh-cn.md index 26a0c8d33..7ee3a3d46 100644 --- a/content/dev/vscode-on-cloud/index.zh-cn.md +++ b/content/dev/vscode-on-cloud/index.zh-cn.md @@ -7,6 +7,9 @@ keywords: "" description: "本文分享笔者基于AWS与Pulumi搭建类GitHub Codespaces的云端VSCode的经验。" isCJKLanguage: true og_image: "https://img.bmpi.dev/dafdc38a-8e97-7daa-d860-4ad78c4d182b.png" +categories: [ + "什么是X" +] --- - [云端 IDE](#云端-ide) diff --git a/content/dev/what-markdown-can-do/index.zh-cn.md b/content/dev/what-markdown-can-do/index.zh-cn.md index 37a71988c..0225691b1 100644 --- a/content/dev/what-markdown-can-do/index.zh-cn.md +++ b/content/dev/what-markdown-can-do/index.zh-cn.md @@ -6,6 +6,9 @@ tags: ["Markdown", "排版", "LaTex"] keywords: "Markdown" description: "本文介绍了零成本用Markdown搞定博客网站、笔记文档、演讲胶片与年终总结报告,彻底抛弃Word与PPT" isCJKLanguage: true +categories: [ + "技术写作" +] --- 你是否遇到这些问题:写报告需要打开Word/PPT,每次浪费不少时间在排版上?写博客需要在管理后台网页里排版?在这篇文章里我将会介绍如何使用一种纯文本标记语言Markdown去排版各类型文档。 diff --git a/content/invest.zh-cn.md b/content/invest.zh-cn.md index 0e3ac1be5..7c126d054 100644 --- a/content/invest.zh-cn.md +++ b/content/invest.zh-cn.md @@ -38,4 +38,4 @@ outputs = "html" ## 双均线交易信号邮件订阅 -- 你可以在这里邮件订阅 [双均线交易策略信号](https://money.i365.tech/) +- 你可以在这里邮件订阅 [双均线交易策略信号](https://money.bmpi.dev/) diff --git a/content/link.zh-cn.md b/content/link.zh-cn.md index 2c9f46481..9125a1d1a 100644 --- a/content/link.zh-cn.md +++ b/content/link.zh-cn.md @@ -21,13 +21,6 @@ outputs = "html" | [ZoomQuiet](https://blog.zoomquiet.io/) | 是也乎( ̄▽ ̄) | | [认知.xKnow](https://blog.xknow.net/) | 认知.xKnow | | [刘悦的技术博客](https://v3u.cn/) | 刘悦的技术博客 | - -## 我关注的博客 - -| | | -| -- | -- | -| [面向信仰编程](https://draveness.me/) | Go语言设计与实现 | -| [酷壳](https://coolshell.cn/) | 享受编程和技术所带来的快乐 | | [麻广广的博客](https://maguangguang.xyz/) | 关注领域:企业架构设计(分布式架构设计、微服务架构设计),企业数字化转型,自我提升及个人成长,读书/学习思考笔记等 | | [少个分号](https://shaogefenhao.com/) | 软件架构、高认知软件工程、DDD、领域驱动设计、敏捷、研发自测、敏捷团队 | | [爱码叔-iCodeBook](https://icodebook.com/) | 专注于软件设计、系统架构 | @@ -37,3 +30,11 @@ outputs = "html" | [于晓南](https://qualityfocus.club/yxn) | 关注软件质量 | | [聂子云](http://www.niezitalk.com/) | 聂子云 | | [张凯峰](https://www.kaifengzhang.com/) | 张凯峰 | + +## 我关注的博客 + +| | | +| -- | -- | +| [面向信仰编程](https://draveness.me/) | Go语言设计与实现 | +| [酷壳](https://coolshell.cn/) | 享受编程和技术所带来的快乐 | +| [Limboy](https://limboy.me/) | Limboy | \ No newline at end of file diff --git a/content/money/invest-alchemy.zh-cn.md b/content/money/invest-alchemy.zh-cn.md new file mode 100644 index 000000000..2bb92c97e --- /dev/null +++ b/content/money/invest-alchemy.zh-cn.md @@ -0,0 +1,81 @@ +--- +title: "投资炼金术" +date: 2022-09-12 +draft: false +tags: ["投资组合", "交易策略", "资金策略", "风险评估", "交易评测"] +keywords: "投资炼金术" +description: "投资炼金术系统支持多种投资交易策略,可评估用户的投资交易水平,自动计算用户投资组合的净值,从而提升用户的投资交易水平。" +isCJKLanguage: true +--- + +## 背后的故事 + +在2016年的时候,我曾开发过一款App名为[交易日记](/money/build-trade-system/),这个App是根据我自身对投资交易的需求而打造,之所以有这些需求是因为我经历了黑暗的A股2015年股灾。当时我的投资仓位出现了很大的亏损,甚至参与创业的公司也因此而倒闭,所以花费了累计半年的时间学习[投资交易原理相关的书籍](/money/passive-income-protfolio/202007/)。 + +之后有了交易日记App的前身,是一个Excel表格: + +![](https://img.bmpi.dev/815c5166-a61f-1625-401f-80c695979bb7.png) + +这个Excel是由VBA开发的,甚至对接了东方财富Choice软件的接口去获取股价等数据。当时我的注意力还放在股票投资交易上。但经过半年的实操后,发觉股票投资并不适合业余投资交易的人。就算能盈利也需要耗费非常多的心力,所以之后我逐渐将注意力放在了指数基金上。在18年开始,受到一本书的启发,我决定做一个长期的投资组合,名为[被动收入投资组合](/money/passive-income-protfolio/202006/)。 + +被动收入投资组合是一个完全由指数基金组成的投资组合,它类似FoF基金。我为此开发了另外一套更简单的Excel模版来监控整个投资组合的收益与风险指标。 + +![](https://img.bmpi.dev/7ea206be-409c-d2a2-bd39-7b618e3ba345.png) + +在长达几年的实操中,我逐渐青睐技术分析中的[双均线投资策略](/money/passive-income-protfolio/202008/),为此开发了一个策略提醒的小工具,这个工具每天会自动将投资交易的买卖信号通过邮件的方式去提醒交易者。 + +![](https://img.bmpi.dev/ed73c1ad-0a1c-cd37-7b10-250ae3cdd56b.png) + +在这个工具持续运行的时间里,我一直在思考它的演进方向。直到今年才有了一些想法: + +![](https://img.bmpi.dev/179d4fd0-018a-9fe0-a432-8dc8cf5d7fdd.png) + +最终这些想法变成具体的一些任务及代码: + +![](https://img.bmpi.dev/898d87a6-f801-e6e5-8510-c8791a3b9da5.png) + +在肝了近百小时后,这个系统上线了: + +[money.bmpi.dev](https://money.bmpi.dev/) + +我給它起了一个很俗的名字:投资炼金术。 + +## [投资炼金术](https://money.bmpi.dev/) + +从功能上来讲,投资炼金术可以做下面的事情: + +- 策略:策略提醒功能,能支持多种交易策略的扩展。 + - Double MA:双均线策略,已经上线。 + - Turtle:海龟交易策略。 + - Backtrace MA250:回踩年线策略。 + - Breakthrough Platform:平台突破策略。 + - 其他。 +- 市场 + - 市场热度监控:比如全市场历史PE/PB的区间,开户人数的变化等,这些数据的变化和趋势本身也可以作为交易策略的输入。 + - 全球指数的历史数据:这些数据主要给投资组合表现评测做对比,也可以生成投资组合与全球主流指数对比的走势图。 +- 投资组合:以基金计算净值的方式衡量投资组合的表现。 + - 通过历史交易、资金流水、历史持仓台账的数据计算出组合的净值。当前可支持A股ETF/LOF等场内基金、股票等投资标的,场外公募基金未来会支持的。 +- 交易 + - 用户:支持用户的投资组合净值的计算。 + - 机器人:机器人可以自动追踪某个交易策略,根据资金策略自动维护某个投资组合。这比简单的回测要更系统和真实,可以看到每天的交易记录和持仓记录,还有资金的变化记录等。 +- Web界面 + - 自动展示某个投资组合的收益走势图及全球主流指数的对比,还可以看到投资组合的风险状况,及交易、持仓和资金记录。 +- 通知 + - 邮件通知 + - Telegram Channel通知 + +一些系统界面截图: + +![](https://img.bmpi.dev/7360956a-1c30-77a2-e9a2-fd4db6f03683.png) + +目前投资炼金术只上线了三款投资组合,前两款都是机器人跟随双均线交易策略的投资组合,第三个是被动收入投资组合。 + +![](https://img.bmpi.dev/e7eda067-5c2a-4fa3-05aa-2df0ca94ad45.png) + +## 未来的规划 + +投资炼金术的主体功能大概完成了八成左右,剩下的就是围绕此做的一些修补,最多的还是如何开发不同的投资策略及机器人,用来方便的追踪不同交易策略对投资组合收益与风险的影响。 + +当然也可以把真实用户的投资交易记录用这个系统生成投资组合并自动维护,也可以方便的知道自己的交易水平与全球主流指数相比到底相差几何,我相信这种可视化的风险监控方式对我们长期投资来说是有益处的。 + +最后这套系统的代码是[开源](https://github.com/bmpi-dev/invest-alchemy)的,这意味着你可以按照自己的想法去开发,也可以免费的自用。当然如果你不想花时间折腾这些,但是又想用这种方式去量化理解自己的投资组合,那也可以[与我联系](/about/)。 diff --git a/content/money/passive-income-protfolio/202008.zh-cn.md b/content/money/passive-income-protfolio/202008.zh-cn.md index 6f88a4612..8321f0f54 100644 --- a/content/money/passive-income-protfolio/202008.zh-cn.md +++ b/content/money/passive-income-protfolio/202008.zh-cn.md @@ -50,7 +50,7 @@ isCJKLanguage: true 看完以上视频后如果觉得双均线交易策略还不错的话,那么你可能需要这个工具: -[Invest Alchemy](https://money.i365.tech/) +[Invest Alchemy](https://money.bmpi.dev/) ![](https://img.bmpi.dev/fb53992f-9bd7-d241-e0c3-f748c5837072.png) diff --git a/content/money/passive-income-protfolio/202009.zh-cn.md b/content/money/passive-income-protfolio/202009.zh-cn.md index 16ec55b38..9e716682b 100644 --- a/content/money/passive-income-protfolio/202009.zh-cn.md +++ b/content/money/passive-income-protfolio/202009.zh-cn.md @@ -42,7 +42,7 @@ isCJKLanguage: true 从双均线交易信号看,目前大多数指数基金都处于空仓仓位,如果你采用此交易策略,那可以躲过这次大的跌幅。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 大跌时我们能做什么 diff --git a/content/money/passive-income-protfolio/202010.zh-cn.md b/content/money/passive-income-protfolio/202010.zh-cn.md index 89a28c2e2..5c0e96d1e 100644 --- a/content/money/passive-income-protfolio/202010.zh-cn.md +++ b/content/money/passive-income-protfolio/202010.zh-cn.md @@ -38,7 +38,7 @@ isCJKLanguage: true 从双均线交易信号看,目前大多数指数基金都处于持仓仓位,但是由于市场处于震荡整理行情,所以双均线会出现短期买入并卖出的信号干扰。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 投资交易的心理建设 diff --git a/content/money/passive-income-protfolio/202011.zh-cn.md b/content/money/passive-income-protfolio/202011.zh-cn.md index ca6daaace..b20ac10c7 100644 --- a/content/money/passive-income-protfolio/202011.zh-cn.md +++ b/content/money/passive-income-protfolio/202011.zh-cn.md @@ -38,7 +38,7 @@ isCJKLanguage: true 从双均线交易信号看,目前持有的大部分是估值处于中部的标的。从趋势交易的角度看目前值得持有的都是一些中低估值的标的。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 关于银行分期贷款的坑 diff --git a/content/money/passive-income-protfolio/202012.zh-cn.md b/content/money/passive-income-protfolio/202012.zh-cn.md index 75b1e4898..acea1ccfd 100644 --- a/content/money/passive-income-protfolio/202012.zh-cn.md +++ b/content/money/passive-income-protfolio/202012.zh-cn.md @@ -82,7 +82,7 @@ isCJKLanguage: true 如果你分析下双均线策略信号最近两个月的表现,会发现双均线在趋势行情中表现还是很不错的。对于高估值的标的如创业板、消费与医药,双均线在上月底还是空仓状态,躲过上个月的大跌,又吃到了这个月的大涨。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202101.zh-cn.md b/content/money/passive-income-protfolio/202101.zh-cn.md index 652a3b8f5..f5de4614a 100644 --- a/content/money/passive-income-protfolio/202101.zh-cn.md +++ b/content/money/passive-income-protfolio/202101.zh-cn.md @@ -121,7 +121,7 @@ H股ETF(510900.SH)于20210104日买入, 持有25天, 盈利5.45% 从双均线持仓的品种中可以看出创业板50、医药、原油、深100、环保、50AH、中国互联与中概互联这些品种持仓一个多月收益都超过10%了,当然也有下跌的品种,平均跌幅范围在-5%至-1%之间。双均线策略非常适合牛市这种大趋势行情,而之前我们在 [双均线交易策略](/money/passive-income-protfolio/202008/) 这篇文章中回测发现创业板很适合采用双均线策略,因为其波动性比较大,也比较有趋势性。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202102.zh-cn.md b/content/money/passive-income-protfolio/202102.zh-cn.md index bb2631127..ce99b7547 100644 --- a/content/money/passive-income-protfolio/202102.zh-cn.md +++ b/content/money/passive-income-protfolio/202102.zh-cn.md @@ -17,7 +17,7 @@ og_image: "https://img.bmpi.dev/28a8c22b-f064-8846-2f58-17f3d9749be2.png" 在使用了一段时间估值策略后,我发现估值策略并不适合作为一个单独的交易策略:如果通过计算一个标的的历史估值区间,然后将这个区间划分为多个区间段,在低估值区间段分批买入,高估值区间段分批卖出。这种交易策略本身是可以赚钱的,它的买入策略可以让我们在下跌大趋势时做左侧交易,卖出策略可以让我们在上涨大趋势时做右侧交易。这种策略宏观上是没错的,难的在于微观上操作时尤其是卖出时很容易错失最具爆发力的后期上涨阶段。以创业板、消费和医药指数为例,被动收入投资组合在估值便宜的时候一直持有,大概在估值达95-98%的时候开始卖出,实际上涨幅最猛烈的就属于98-100%的这个阶段,而且可能会长时间(比一般人想象的要久一些)在这个估值区间运行。 -后期我开始调整策略,转而在高估值区间采用趋势跟随交易策略(双均线),最终开发了 [双均线策略信号邮件提醒程序](https://money.i365.tech/)。 +后期我开始调整策略,转而在高估值区间采用趋势跟随交易策略(双均线),最终开发了 [双均线策略信号邮件提醒程序](https://money.bmpi.dev/)。 那么估值究竟是什么?能不能用估值作为交易策略?在这篇 [燕翔:树能不能长到天上去,谈估值天花板(国信策略)](https://mp.weixin.qq.com/s/2dNo-QXZ-ipkAQW44B8-NA) 文章中: @@ -117,7 +117,7 @@ H股ETF(510900.SH)于20210224日买入, 持有2天, 盈利-1.46% 双均线策略天生很适合在大趋势行情中捕捉大段的盈利区间,以创业板50()为例,双均线交易策略于[2020年12月11日发出买入信号](https://www.i365.tech/invest-alchemy/data/strategy/double-ma/20201211.txt)(买入价1.116),于[2021年02月26日发出卖出信号](https://www.i365.tech/invest-alchemy/data/strategy/double-ma/20210226.txt)(卖出价1.269),持仓77天盈利13.7%。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202103.zh-cn.md b/content/money/passive-income-protfolio/202103.zh-cn.md index b6dd66d9c..6b6eac89f 100644 --- a/content/money/passive-income-protfolio/202103.zh-cn.md +++ b/content/money/passive-income-protfolio/202103.zh-cn.md @@ -127,7 +127,7 @@ H股ETF(510900.SH)于20210305日卖出, 空仓28天, 空仓期涨幅-0.57% 双均线策略里很多品种都进入空仓状态,已经持仓的品种都已盈利,目前的市场行情并不好,可以继续等待更多的买入时机。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202104.zh-cn.md b/content/money/passive-income-protfolio/202104.zh-cn.md index a00c68011..f4b08cb89 100644 --- a/content/money/passive-income-protfolio/202104.zh-cn.md +++ b/content/money/passive-income-protfolio/202104.zh-cn.md @@ -118,7 +118,7 @@ H股ETF(510900.SH)于20210305日卖出, 空仓55天, 空仓期涨幅-3.18% 双均线策略再次捕捉到了医药和创业板的暴涨,持仓盈利近 10%。我在思考要不要在高估值的品种只采取双均线这类趋势跟随的策略?因为一般投资品种在高估值大多经历了暴涨,在这个阶段波动区间很大,趋势跟随策略的表现应该很好。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202105.zh-cn.md b/content/money/passive-income-protfolio/202105.zh-cn.md index 765680d16..6a6a65fd6 100644 --- a/content/money/passive-income-protfolio/202105.zh-cn.md +++ b/content/money/passive-income-protfolio/202105.zh-cn.md @@ -102,7 +102,7 @@ H股ETF(510900.SH)于20210305日卖出, 空仓84天, 空仓期涨幅-6.28% 纳指ETF(159941.SZ)于20210507日卖出, 空仓21天, 空仓期涨幅-0.92% ``` -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202106.zh-cn.md b/content/money/passive-income-protfolio/202106.zh-cn.md index e36619a98..bcdf2b779 100644 --- a/content/money/passive-income-protfolio/202106.zh-cn.md +++ b/content/money/passive-income-protfolio/202106.zh-cn.md @@ -330,7 +330,7 @@ H股ETF(510900.SH)于20210616日卖出, 空仓13天, 空仓期涨幅2.39% 黄金基金(518800.SH)于20210610日卖出, 空仓19天, 空仓期涨幅-4.05% ``` -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202107.zh-cn.md b/content/money/passive-income-protfolio/202107.zh-cn.md index c5bd8ad39..d3cba51d1 100644 --- a/content/money/passive-income-protfolio/202107.zh-cn.md +++ b/content/money/passive-income-protfolio/202107.zh-cn.md @@ -152,7 +152,7 @@ H股ETF(510900.SH)于20210707日卖出, 空仓23天, 空仓期涨幅-9.61% 中国互联(164906.SZ)于20210513日卖出, 空仓78天, 空仓期涨幅-28.04% 中概互联(513050.SH)于20210708日卖出, 空仓22天, 空仓期涨幅-7.66% ``` -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202108.zh-cn.md b/content/money/passive-income-protfolio/202108.zh-cn.md index 79126b776..340512417 100644 --- a/content/money/passive-income-protfolio/202108.zh-cn.md +++ b/content/money/passive-income-protfolio/202108.zh-cn.md @@ -139,7 +139,7 @@ H股ETF(510900.SH)于20210707日卖出, 空仓54天, 空仓期涨幅-11.51% 中概互联(513050.SH)于20210708日卖出, 空仓53天, 空仓期涨幅-13.23% ``` -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202109.zh-cn.md b/content/money/passive-income-protfolio/202109.zh-cn.md index ceffd0089..eef4554f1 100644 --- a/content/money/passive-income-protfolio/202109.zh-cn.md +++ b/content/money/passive-income-protfolio/202109.zh-cn.md @@ -57,7 +57,7 @@ og_image: "https://img.bmpi.dev/e1ce3cfa-c724-42d6-8a28-d322bc70c86c.png" 军工ETF(512660.SH)于20210907日卖出, 空仓22天, 空仓期涨幅-7.53% ``` -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/2021Q4.zh-cn.md b/content/money/passive-income-protfolio/2021Q4.zh-cn.md index 76bdd044d..39a4fb3e4 100644 --- a/content/money/passive-income-protfolio/2021Q4.zh-cn.md +++ b/content/money/passive-income-protfolio/2021Q4.zh-cn.md @@ -79,7 +79,7 @@ og_image: "https://img.bmpi.dev/8f81177f-470f-5878-6381-e2993db90e0a.png" 经过上述交易分析,我并不适合估值交易策略。所以之后的交易策略统一为双均线交易策略。但我会对双均线策略做一定的优化,旨在降低双均线策略在震荡行情中的频繁交易。 -我也会对 [双均线策略信号提醒系统](https://money.i365.tech/) 做一定的优化,修复已知的 Bugs。 +我也会对 [双均线策略信号提醒系统](https://money.bmpi.dev/) 做一定的优化,修复已知的 Bugs。 ## 组合季报 @@ -93,7 +93,7 @@ og_image: "https://img.bmpi.dev/8f81177f-470f-5878-6381-e2993db90e0a.png" 投资市场:中国、香港及美国。 -交易策略:[双均线策略](/money/passive-income-protfolio/202008/)。想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/)。 +交易策略:[双均线策略](/money/passive-income-protfolio/202008/)。想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/)。 资金管理:分散投资,买入标的单个不超过总资金 20%,单类(同一行业)标的持仓总市值不超过总资金 30%。 diff --git a/content/money/passive-income-protfolio/2022Q1.zh-cn.md b/content/money/passive-income-protfolio/2022Q1.zh-cn.md index f3072a390..5a9cc23a5 100644 --- a/content/money/passive-income-protfolio/2022Q1.zh-cn.md +++ b/content/money/passive-income-protfolio/2022Q1.zh-cn.md @@ -59,7 +59,7 @@ og_image: "https://img.bmpi.dev/9e8d58a1-5ff1-c6ed-135d-a5f76e7366d0.png" 投资市场:中国、香港及美国。 -交易策略:[双均线策略](/money/passive-income-protfolio/202008/)。想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/)。 +交易策略:[双均线策略](/money/passive-income-protfolio/202008/)。想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/)。 资金管理:分散投资,买入标的单个不超过总资金 20%,单类(同一行业)标的持仓总市值不超过总资金 30%。 diff --git a/content/money/passive-income-protfolio/2022Q2.zh-cn.md b/content/money/passive-income-protfolio/2022Q2.zh-cn.md index 536e33fa3..89ddee4aa 100644 --- a/content/money/passive-income-protfolio/2022Q2.zh-cn.md +++ b/content/money/passive-income-protfolio/2022Q2.zh-cn.md @@ -17,7 +17,7 @@ og_image: "https://img.bmpi.dev/b2c19910-b83c-94a0-7153-9fc938fa74cf.png" 之前写过一篇[长期投资之难](/money/passive-income-protfolio/202103/)的文章,介绍了看似简单的投资行为要长期坚持,却是件很难的事情。本期就交易之难,分享下我的个人经验。[被动收入投资组合](/categories/被动收入投资组合/)的交易策略是[双均线系统](/money/passive-income-protfolio/202008/),这是一个简单到几分钟就可以解释清楚的交易策略,但要用好却并不简单。 -以本季度双均线交易策略发出的买入卖出信号来分析(数据来源于[交易信号邮件提醒](https://money.i365.tech/)系统): +以本季度双均线交易策略发出的买入卖出信号来分析(数据来源于[交易信号邮件提醒](https://money.bmpi.dev/)系统): ```text 4月1日: @@ -152,7 +152,7 @@ og_image: "https://img.bmpi.dev/b2c19910-b83c-94a0-7153-9fc938fa74cf.png" 投资市场:中国、香港及美国。 -交易策略:[双均线策略](/money/passive-income-protfolio/202008/)。想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/)。 +交易策略:[双均线策略](/money/passive-income-protfolio/202008/)。想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/)。 资金管理:分散投资,买入标的单个不超过总资金 20%,单类(同一行业)标的持仓总市值不超过总资金 30%。 diff --git a/content/project.zh-cn.md b/content/project.zh-cn.md index 6e0512973..5e5a9e111 100644 --- a/content/project.zh-cn.md +++ b/content/project.zh-cn.md @@ -19,27 +19,25 @@ outputs = "html" | | | | -- | -- | -| [Invest Alchemy](https://github.com/bmpi-dev/invest-alchemy) | 被动收入投资助手 | -| [scrape_google_keyword](https://github.com/bmpi-dev/scrape_google_keyword) | 刮取Google关键词Top10排名网站信息 | +| [投资炼金术](https://money.bmpi.dev/) | 投资交易助手 | +| [free4.chat](https://github.com/madawei2699/free4chat) | 匿名语音聊天室 | | [tg2web](https://github.com/bmpi-dev/tg2web) | 电报频道静态化工具 | -| [git2pdf](https://github.com/bmpi-dev/git2pdf) | 打印GitHub README文档 | | [awesome-seo](https://github.com/madawei2699/awesome-seo) | 我整理的关于Google SEO系统学习的收藏资源及通过流量变现的介绍 | | [notion-sites](https://github.com/madawei2699/notion-sites) | 我整理的Notion好站列表 | | [xian-IT](https://github.com/madawei2699/xian-IT) | 如果你对西安互联网公司熟悉,欢迎提交 Issue 或者PR,期待你的参与。 | -| [logseq.xyz](https://github.com/bmpi-dev/logseq.xyz) | Logseq 自托管版本(v0.5.5) | -| [free4.chat](https://github.com/madawei2699/free4chat) | 匿名语音聊天室 | -## 流量站 +## SEO流量站 | | | | -- | -- | | [gitopx.com](https://www.gitopx.com) | GitHub Top100 用户/仓库排名信息 | | [webtg.org](https://www.webtg.org) | Web Telegram | -| [instaghub](https://github.com/bmpi-dev/instaghub) | Instagram流量站 | ## 归档 | | | | -- | -- | -| [一款产品的从0到1之旅](/dev/zero-to-one/) | 交易日记iOS App[已下线] | -| [项目Google邮件列表](https://groups.google.com/forum/#!forum/qunpin) | 群品电子书项目[已下线] | +| [logseq.xyz](https://github.com/bmpi-dev/logseq.xyz) | Logseq 自托管版本(v0.5.5) | +| [instaghub](https://github.com/bmpi-dev/instaghub) | Ins Viewer | +| [一款产品的从0到1之旅](/dev/zero-to-one/) | 交易日记 iOS App | +| [项目Google Groups](https://groups.google.com/forum/#!forum/qunpin) | 群品电子书项目 | diff --git a/content/self/build-personal-knowledge-system/index.zh-cn.md b/content/self/build-personal-knowledge-system/index.zh-cn.md index 8493ee074..2fe997af2 100644 --- a/content/self/build-personal-knowledge-system/index.zh-cn.md +++ b/content/self/build-personal-knowledge-system/index.zh-cn.md @@ -8,6 +8,9 @@ description: "如何建立个人终身学习知识体系?如何自我管理? series: ["自我提升"] isCJKLanguage: true og_image: "https://img.bmpi.dev/bf1093ef-68c2-c92a-0de5-a08decb02b31.png" +categories: [ + "个人成长" +] --- - [为何要建立个人终身学习知识体系](#为何要建立个人终身学习知识体系) @@ -24,6 +27,7 @@ og_image: "https://img.bmpi.dev/bf1093ef-68c2-c92a-0de5-a08decb02b31.png" - [微信/电报群](#微信电报群) - [1 对 1 沟通](#1-对-1-沟通) - [信息输入渠道金字塔](#信息输入渠道金字塔) + - [我的信息输入渠道](#我的信息输入渠道) - [目标设定](#目标设定) - [个人愿景的设定](#个人愿景的设定) - [知识内化](#知识内化) @@ -168,6 +172,21 @@ Twitter 里我一般会关注一些 List,比如 [Elixir](https://twitter.com/i 我在学习 [投资](/series/投资实证/) 与 [SEO](/series/seo实践日志/) 领域知识时,基本是通过上述学习流程而执行的。 +#### 我的信息输入渠道 + +以下仅是我常用的信息输入App清单: + +![](https://img.bmpi.dev/b4ac0cc4-b3f5-38a9-f6a8-5cb0554f8fcf.png) + +- 读库:需要订阅,主要是非虚构类的文章。读库主编老六的品味不错,经常能看到一些有意思的人或事的介绍; +- 财新:订阅了财新通,有和读库的组合订阅。可以阅读财新周刊电子版(历年的都可以看),没订阅纸版是因为太贵了,数字版便宜还能在手机上看; +- 端传媒:简体中文新闻媒体能看的很少,繁体中文里算较为客观的,能看到一些不同的视角报道; +- 今日热榜:可以订阅一些论坛社区的热门帖子,虽然简体中文新闻媒体很多没法看,但一些好东西可以在老的论坛社区看到。类似的网站有[AnyKnew](https://www.anyknew.com/),还可以用[RSSHub](https://github.com/DIYgod/RSSHub)做自己的基于RSS的聚合阅读器; +- narwhal:是一个我觉得不错的[Reddit](https://reddit.com/)社区客户端,看帖子交互比较好; +- 图书:iOS自带的图书App,配合[Z-Library](https://z-lib.org/)能阅读海量的电子书。可以和macOS的图书自动同步,阅读英文电子书可以用自带的翻译工具大段落的翻译,阅读体验好; +- Octal:[Hacker News](https://news.ycombinator.com/)的第三方App,偶尔看看,平时还是通过邮件阅读每周热门的帖子; +- YouTube:订阅了会员,没有广告观看体验好很多; + ### 目标设定 给自己设定个目标很重要,甚至是个人知识体系最重要的一个环节。如何设定一个长期目标?可以从自身如需求、兴趣、性格、特长、时间等方面考虑,这是一个漫长的过程,我在确定 [我的中长期目标](/goal/) 时也花了很多时间,甚至在确定后不断的微调。 diff --git a/content/self/build-write-tool-v1.zh-cn.md b/content/self/build-write-tool-v1.zh-cn.md index c6d5e9e77..da075cc69 100644 --- a/content/self/build-write-tool-v1.zh-cn.md +++ b/content/self/build-write-tool-v1.zh-cn.md @@ -10,6 +10,9 @@ keywords: ["写作系统", "Hexo", "GitHub Pages", "图床"] description: "本文介绍了使用GitHub Pages以及Hexo工具打造个人优化高效的写作系统" series: ["零成本搭建现代博客指南", "自我提升"] isCJKLanguage: true +categories: [ + "技术写作" +] --- 写作是一件很有趣的事情,长期形成良好的写作习惯可以帮助我们有条理的处理解决问题,同时给人生留下点回忆也不错。在我们很小的时候也许就有记日记的习惯,大脑的记忆量毕竟很有限,所以我们用纸和笔来记东西,但是传统的纸和笔存在很大的问题,无法长久的保存东西。进入互联网时代后我们有了很多选择,各类网站博客、专栏、微博、公众号等可以让我们写作的地方,但是这些都无法解决以下几个问题。 diff --git a/content/self/life-in-plain-text/index.zh-cn.md b/content/self/life-in-plain-text/index.zh-cn.md index 005ca8b98..acaa5c1b5 100644 --- a/content/self/life-in-plain-text/index.zh-cn.md +++ b/content/self/life-in-plain-text/index.zh-cn.md @@ -8,6 +8,9 @@ description: "本文分享我使用纯文本管理我的目标(OKR)、时间(GTD isCJKLanguage: true series: ["自我提升", "人生管理系统"] og_image: "https://img.bmpi.dev/cf3a1023-57ae-b3ce-4110-9624c26f860b.png" +categories: [ + "个人成长" +] --- 我在 [我的时间管理工具](/self/gtd-tools-i-used/) 与 [我的笔记系统](/self/note-system/) 中分享过我不断演进的时间管理工具与笔记系统。在使用这些系统多年后,始终有个困惑萦绕在我耳边,那就是: diff --git a/content/self/my-drawing-toolbox/index.zh-cn.md b/content/self/my-drawing-toolbox/index.zh-cn.md index 1a1389549..d2719012f 100644 --- a/content/self/my-drawing-toolbox/index.zh-cn.md +++ b/content/self/my-drawing-toolbox/index.zh-cn.md @@ -11,6 +11,9 @@ markmap: enabled: true isMermaidEnabled: true plotly: true +categories: [ + "技术写作" +] --- 由于在写作中经常需要配图,本文分享下我常用的绘图工具以及如何存储并展示配图。 diff --git a/content/self/my-writing-story/250k.zh-cn.md b/content/self/my-writing-story/250k.zh-cn.md index ed00f1617..9b729fced 100644 --- a/content/self/my-writing-story/250k.zh-cn.md +++ b/content/self/my-writing-story/250k.zh-cn.md @@ -9,6 +9,9 @@ isCJKLanguage: true og_image: "https://img.bmpi.dev/9208f19f-d1b5-2bfd-a6a8-15d6fa9d9cec.png" markmap: enabled: true +categories: [ + "技术写作" +] --- 不知不觉竟已写了超过 25 万字的文章,实际可能更多。一般的长篇小说都是十几万字以上,所以我竟也在大学毕业后断断续续作文了一部长篇小说的规模。我是怎么做到的? @@ -228,8 +231,6 @@ markmap: 持续的写作最终会驱动作者走向打造个人品牌的路,关于此我之前有做过一个简单的讲座,想了解可看这个Slide:[从内容创作到个人品牌](https://talk.bmpi.dev/2022/content-marketing-to-personal-brand/)。 -以上就是我在写作字数达25万时的一些经验之谈。希望这些文字能帮助你开启自己的写作之路,或者说写作习惯吧。当然这篇文章我也希望在字数达50万甚至100万时能有更多的经验之谈。 - ### 流量分发对创作的影响 总的来说在平台上创作会受到平台流量分发机制的影响。比如在一个类似抖音的平台上创作,流量大多来自平台的自动推荐,这会引导创作者去追逐热点,制作出大量重复的内容,最终胜利者拿走了绝大部分流量。而在一个类似微信公众号订阅制的平台上,流量的分发大多来自创作者的主动推广或用户的口碑推广,少了对流量的争夺,也会让创作者更多元的去创作。 @@ -244,6 +245,8 @@ markmap: 作为一名[长期主义者](/goal/),把时间投入到元能力的提高上,是[自我提升](/series/自我提升/)的一个好的体现。 +以上就是我在写作字数达25万时的一些经验之谈。希望这些文字能帮助你开启自己的写作之路,或者说写作习惯吧。当然这篇文章我也希望在字数达50万甚至100万时能有更多的经验之谈。 + ## 推荐阅读 - [写作 12 年,我的经验和技巧](https://catcoding.me/p/writing-for-joy/) diff --git a/content/self/okr-gtd-note-logseq/index.zh-cn.md b/content/self/okr-gtd-note-logseq/index.zh-cn.md index e3460dfa5..a8a921b16 100644 --- a/content/self/okr-gtd-note-logseq/index.zh-cn.md +++ b/content/self/okr-gtd-note-logseq/index.zh-cn.md @@ -8,6 +8,9 @@ description: "本文分享我使用Logseq实现人生管理系统的OKR、GTD与 isCJKLanguage: true series: ["自我提升", "人生管理系统"] og_image: "https://img.bmpi.dev/d33c93b6-734c-cf47-a3cf-866ccfd29872.png" +categories: [ + "个人成长" +] --- {{< expand "历史更新" >}} diff --git a/content/self/road_to_life_games/index.zh-cn.md b/content/self/road_to_life_games/index.zh-cn.md index 9a864513e..1df625fa6 100644 --- a/content/self/road_to_life_games/index.zh-cn.md +++ b/content/self/road_to_life_games/index.zh-cn.md @@ -10,6 +10,9 @@ keywords: ["时间管理", "目标管理", "资产管理", "技能管理", "社 description: "本文介绍了我对自我提升的理解,分为时间管理、目标管理、资产管理、技能管理、社交管理以及个人品牌的建设" series: ["自我提升"] isCJKLanguage: true +categories: [ + "个人成长" +] --- 前段时间很火的一个美剧《西部世界》,剧中的机器人和正常的人类很相似,其中“老鸨”机器人甚至可以通过修改自己的参数使其变的更为强大。在我们的想象中机器人应该是有很多参数去控制他的行为和“意识”,这好比在游戏中的角色可以通过打怪升级提升自己技能点,而现实世界中一个人也可以通过不断学习与历练提高自己的思维能力和专业技能。 diff --git a/content/talk.zh-cn.md b/content/talk.zh-cn.md index dd3d70a54..b8150527a 100644 --- a/content/talk.zh-cn.md +++ b/content/talk.zh-cn.md @@ -7,10 +7,18 @@ outputs = "html" ## 2022 +### 软件工程师如何打造个人技术影响力 + +- [XConf China 2022](https://app.ma.scrmtech.com/meetings-api/sapIndex/SapSourceData?pf_uid=7019_1254&sid=63242&source=2&pf_type=3&code=061qOR1006W8lO1cFc200VuVl42qOR1q&state=&appid=wx4bd00f95dd7c7ca1) + ### 编程语言是如何实现并发的 - [Slide](https://talk.bmpi.dev/2022/how-to-implement-concurrency/) +### 技术写作的那点事 + +- [直播回放](https://app.ma.scrmtech.com/meetings-api/sapIndex/SapSourceData?pf_uid=7019_1254&sid=54902&source=2&pf_type=3&code=031Pre0006oOmO1qG6000qe8ei0Pre0p&state=&appid=wx4bd00f95dd7c7ca1) + ### 从内容创作到个人品牌 - [Slide](https://talk.bmpi.dev/2022/content-marketing-to-personal-brand/) diff --git a/content/tool.zh-cn.md b/content/tool.zh-cn.md index b483662b0..0313ae503 100644 --- a/content/tool.zh-cn.md +++ b/content/tool.zh-cn.md @@ -12,6 +12,26 @@ outputs = "html" - [Nextjournal - Data-driven Research Livebook](https://nextjournal.com/) - [Elixir Livebook](https://bmpi.fly.dev) - [Metabase - Database Visualization](https://github.com/metabase/metabase) +- [ServerCat - Server Status, Docker Management and SSH client for iOS/macOS](https://servercat.app/) + +## SaaS + +### 云平台 + +- [AWS](https://aws.amazon.com/) +- [Azure](https://azure.microsoft.com/) +- [Vercel](https://vercel.com/) +- [Cloudflare](https://cloudflare.net/) +- [Supabase](https://supabase.com/) +- [Fly.io](https://fly.io/) + +### 网络代理 + +- [Rotating Reverse Proxies](https://stormproxies.com/clients/aff/go/madawei2699) + +## Open Graph Image + +- [Open Graph Image for bmpi.dev](https://og.bmpi.dev/) ## 微信Markdown排版工具 diff --git a/resources/_gen/assets/scss/scss/coder.scss_fd4b5b3f9a48bc0c7f005d2f7a4cc30f.content b/resources/_gen/assets/scss/scss/coder.scss_fd4b5b3f9a48bc0c7f005d2f7a4cc30f.content index 078d3383d..46e8f238a 100644 --- a/resources/_gen/assets/scss/scss/coder.scss_fd4b5b3f9a48bc0c7f005d2f7a4cc30f.content +++ b/resources/_gen/assets/scss/scss/coder.scss_fd4b5b3f9a48bc0c7f005d2f7a4cc30f.content @@ -405,6 +405,31 @@ div.notices.tip p:first-child:after { @media only screen and (max-width: 768px) { *[id^='markmap-about-'] > svg { height: 350px; } } +*[id^='markmap-software-engineering-at-google-culture-3'] > svg { + height: 1000px; } + @media only screen and (max-width: 768px) { + *[id^='markmap-software-engineering-at-google-culture-3'] > svg { + height: 350px; } } +*[id^='markmap-software-engineering-at-google-process-0'] > svg { + height: 1000px; } + @media only screen and (max-width: 768px) { + *[id^='markmap-software-engineering-at-google-process-0'] > svg { + height: 350px; } } +*[id^='markmap-software-engineering-at-google-process-2'] > svg { + height: 1000px; } + @media only screen and (max-width: 768px) { + *[id^='markmap-software-engineering-at-google-process-2'] > svg { + height: 350px; } } +*[id^='markmap-software-engineering-at-google-tool-0'] > svg { + height: 1000px; } + @media only screen and (max-width: 768px) { + *[id^='markmap-software-engineering-at-google-tool-0'] > svg { + height: 350px; } } +*[id^='markmap-authentication-and-authorization-in-a-distributed-system-0'] > svg { + height: 600px; } + @media only screen and (max-width: 768px) { + *[id^='markmap-authentication-and-authorization-in-a-distributed-system-0'] > svg { + height: 350px; } } .mermaid { width: 100%; height: auto; @@ -427,6 +452,21 @@ a[href$="/dev/"] { a[href$="/money/"] { color: #cc5595 !important; } +.share-twitter-container { + text-align: center; + background: -webkit-linear-gradient(45deg, #e5b751, #1677b3, #cc5595 80%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; } + .share-twitter-container a { + display: inline; } + @media only screen and (max-width: 768px) { + .share-twitter-container a { + display: block; } } +.share-twitter-container-dashed { + display: inline; } + @media only screen and (max-width: 768px) { + .share-twitter-container-dashed { + display: none; } } .content { flex: 1; display: flex; diff --git a/static/css/main.css b/static/css/main.css index 10afed6f3..2a900089f 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -547,4 +547,70 @@ li .fa-jk { code.language-ascii { font-family: monospace !important; +} + +/* The Modal (background) */ +.modal { + display: none; /* Hidden by default */ + position: fixed; /* Stay in place */ + z-index: 1; /* Sit on top */ + padding-top: 100px; /* Location of the box */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + background-color: rgb(0,0,0); /* Fallback color */ + background-color: rgba(0,0,0,0.9); /* Black w/ opacity */ +} + +/* Modal Content (image) */ +.modal-content { + margin: auto; + display: block; + width: 80%; + max-width: 700px; +} + +/* Add Animation */ +.modal-content { + -webkit-animation-name: zoom; + -webkit-animation-duration: 0.6s; + animation-name: zoom; + animation-duration: 0.6s; +} + +@-webkit-keyframes zoom { + from {-webkit-transform:scale(0)} + to {-webkit-transform:scale(1)} +} + +@keyframes zoom { + from {transform:scale(0)} + to {transform:scale(1)} +} + +/* The Close Button */ +.the-modal-close { + position: absolute; + top: 15px; + right: 35px; + color: #f1f1f1; + font-size: 40px; + font-weight: bold; + transition: 0.3s; +} + +.the-modal-close:hover, +.the-modal-close:focus { + color: #bbb; + text-decoration: none; + cursor: pointer; +} + +/* 100% Image Width on Smaller Screens */ +@media only screen and (max-width: 700px){ + .modal-content { + width: 100%; + } } \ No newline at end of file diff --git a/static/js/custom.js b/static/js/custom.js index 2c8089eb8..bfffa7307 100644 --- a/static/js/custom.js +++ b/static/js/custom.js @@ -1,3 +1,10 @@ +// common functions +window.mobileCheck = function() { + let check = false; + (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); + return check; +}; + // load sw.js if("serviceWorker" in navigator) { window.addEventListener("load", () => { @@ -7,6 +14,16 @@ if("serviceWorker" in navigator) { }); } +function fileReader (blob) { + return new Promise((resolve, reject) => { + let reader = new FileReader() + reader.onload = (e) => { + resolve(e.target.result) + } + reader.readAsDataURL(blob) + }) +} + function httpGetAsync(theUrl, callback) { var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function() { @@ -29,6 +46,9 @@ function set_home_page_site_run_days() { } let allPageViewsAPI = "https://api.bmpi.dev/page-views/bmpi-dev-all-page-views/"; +let devPageViewsAPI = "https://api.bmpi.dev/page-views/bmpi-dev-dev-page-views/"; +let selfPageViewsAPI = "https://api.bmpi.dev/page-views/bmpi-dev-self-page-views/"; +let moneyPageViewsAPI = "https://api.bmpi.dev/page-views/bmpi-dev-money-page-views/"; let singlePageViewsAPI = "https://api.bmpi.dev/page-views/bmpi.dev" + window.location.pathname; function get_post_views(url, callback) { @@ -44,6 +64,24 @@ function set_stats_on_home() { get_post_views(singlePageViewsAPI, res => {}); } else { postViews = document.getElementById("post-views"); + devPostViews = document.getElementById("dev-page-views"); + selfPostViews = document.getElementById("self-page-views"); + moneyPostViews = document.getElementById("money-page-views"); + if (devPostViews != undefined) { + get_post_views(devPageViewsAPI, res => { + devPostViews.textContent = JSON.parse(res).count; + }); + } + if (selfPostViews != undefined) { + get_post_views(selfPageViewsAPI, res => { + selfPostViews.textContent = JSON.parse(res).count; + }); + } + if (moneyPostViews != undefined) { + get_post_views(moneyPageViewsAPI, res => { + moneyPostViews.textContent = JSON.parse(res).count; + }); + } if (postViews != undefined) { get_post_views(allPageViewsAPI, res => {}); get_post_views(singlePageViewsAPI, res => { @@ -129,7 +167,7 @@ function addAuthorPart(html) { function addQRPart(html) { let div = document.createElement('div'); let qr = document.createElement('img'); - qr.setAttribute('src', 'https://api.qrserver.com/v1/create-qr-code/?size=70x70&data=' + window.location.href); + qr.setAttribute('src', 'https://api.qrserver.com/v1/create-qr-code/?size=70x70&data=' + window.location.href + '?utm_source=bookmark'); qr.setAttribute('style', 'width: 70px; height: 70px; max-width: none !important;'); div.appendChild(qr); html.appendChild(div); @@ -138,28 +176,55 @@ function addQRPart(html) { async function html2Img(html) { let div = document.createElement('div'); div.id = 'capture'; - div.setAttribute('style', 'padding: 30px 20px;background: #000;color: #f7f4cb;font-family: "LXGW WenKai";'); + div.setAttribute('style', 'padding: 30px 20px;background: #000;color: #f7f4cb;font-family: "LXGW WenKai";max-width: 768px;margin: auto;'); div.innerHTML = html; + let containImages = div.getElementsByTagName('img'); + if (containImages.length > 0 && window.mobileCheck()) { + launch_toast("手机端不支持图片书签,请用电脑浏览器"); + return; + } + for (let i = 0; i < containImages.length; i++) { + let originImg = containImages[i]; + let originImgSrc = originImg.getAttribute('src'); + let imgDataURL = await fetch(originImgSrc) + .then(function (response) { + return response.blob(); + }).then(function (blob) { + return fileReader(blob); + }); + originImg.src = imgDataURL; + } addDatePart(div); let footer = document.createElement('div'); footer.setAttribute('style', 'display: flex;flex-direction: row;justify-content: space-between;align-items: center;margin-top: 20px;padding-top: -20px;padding-top: -20px;border-top-style: dashed;border-top-width: 1px;padding-top: 10px;'); addAuthorPart(footer); addQRPart(footer); div.appendChild(footer); + let bodyBackup = document.body; + let postionBackup = window.pageYOffset || document.documentElement.scrollTop; + if (window.mobileCheck()) { + document.body = document.createElement("body"); + } document.body.appendChild(div); let canvas = await html2canvas(div, {allowTaint: true, useCORS: true}); + if (window.mobileCheck()) { + document.body = bodyBackup; + window.scrollTo(0, postionBackup); + } else { + document.body.removeChild(div); + } let dataURL = canvas.toDataURL("image/png"); - const blob = dataURItoBlob(dataURL); - const data = { - files: [ - new File([blob], document.querySelector('.title').textContent + '.png', { - type: blob.type, - }), - ], - // title: document.title, - // text: text, - }; if (navigator.canShare && navigator.canShare(data)) { + const blob = dataURItoBlob(dataURL); + const data = { + files: [ + new File([blob], document.querySelector('.title').textContent + '.png', { + type: blob.type, + }), + ], + // title: document.title, + // text: text, + }; try { await navigator.share(data); ga('gtag_UA_154678195_1.send', { @@ -183,10 +248,17 @@ async function html2Img(html) { umami.trackEvent('bookmark_fail', 'bookmark'); } } else { - launch_toast("浏览器不支持此功能"); - console.log(`Your system doesn't support sharing files.`); + let modal = document.getElementById("the-modal"); + var modalImg = document.getElementById("the-modal-img01"); + modal.style.display = "block"; + modalImg.src = dataURL; + // Get the element that closes the modal + var span = document.getElementsByClassName("the-modal-close")[0]; + // When the user clicks on (x), close the modal + span.onclick = function() { + modal.style.display = "none"; + } } - document.body.removeChild(div); } async function oncontroldown(event) { @@ -216,16 +288,14 @@ function webShare() { elements.forEach(i => { i.onpointerup = ()=>{ - if (navigator.canShare) { - let selection = document.getSelection(), text = selection.toString(); - if (text !== "") { - let rect = selection.getRangeAt(0).getBoundingClientRect(); - let articleY = document.body.getBoundingClientRect().top; - control.style.top = `calc(${rect.bottom}px - ${articleY}px + 20px)`; - control.style.left = `calc(${rect.left}px + calc(${rect.width}px / 2) - 30px)`; - control['html']= getSelectionHtml(); - document.body.appendChild(control); - } + let selection = document.getSelection(), text = selection.toString(); + if (text !== "") { + let rect = selection.getRangeAt(0).getBoundingClientRect(); + let articleY = document.body.getBoundingClientRect().top; + control.style.top = `calc(${rect.bottom}px - ${articleY}px + 20px)`; + control.style.left = `calc(${rect.left}px + calc(${rect.width}px / 2) - 30px)`; + control['html']= getSelectionHtml(); + document.body.appendChild(control); } } }); @@ -252,12 +322,6 @@ if( document.readyState !== 'loading' ) { // remove dashboard iframe on mobile -// window.mobileCheck = function() { -// let check = false; -// (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); -// return check; -// }; - // var dashboard = document.getElementById("dashboard_iframe"); // if (window.mobileCheck()) { diff --git a/themes/hugo-coder/assets/scss/_base.scss b/themes/hugo-coder/assets/scss/_base.scss index af00bca4a..70c83cf45 100644 --- a/themes/hugo-coder/assets/scss/_base.scss +++ b/themes/hugo-coder/assets/scss/_base.scss @@ -483,6 +483,41 @@ div.notices.tip p:first-child:after { } } +*[id^='markmap-software-engineering-at-google-culture-3'] > svg { + height: 1000px; + @media only screen and (max-width : 768px) { + height: 350px; + } +} + +*[id^='markmap-software-engineering-at-google-process-0'] > svg { + height: 1000px; + @media only screen and (max-width : 768px) { + height: 350px; + } +} + +*[id^='markmap-software-engineering-at-google-process-2'] > svg { + height: 1000px; + @media only screen and (max-width : 768px) { + height: 350px; + } +} + +*[id^='markmap-software-engineering-at-google-tool-0'] > svg { + height: 1000px; + @media only screen and (max-width : 768px) { + height: 350px; + } +} + +*[id^='markmap-authentication-and-authorization-in-a-distributed-system-0'] > svg { + height: 600px; + @media only screen and (max-width : 768px) { + height: 350px; + } +} + .mermaid { width: 100%; height: auto; @@ -509,3 +544,23 @@ a[href$="/dev/"] { a[href$="/money/"] { color: #cc5595 !important; } + +.share-twitter-container { + text-align: center; + background: -webkit-linear-gradient(45deg, #e5b751,#1677b3, #cc5595 80%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + a { + display: inline; + @media only screen and (max-width : 768px) { + display: block; + } + } +} + +.share-twitter-container-dashed { + display: inline; + @media only screen and (max-width : 768px) { + display: none; + } +} diff --git a/themes/hugo-coder/layouts/_default/baseof.html b/themes/hugo-coder/layouts/_default/baseof.html index 6bf40b79c..d6b4a4e04 100644 --- a/themes/hugo-coder/layouts/_default/baseof.html +++ b/themes/hugo-coder/layouts/_default/baseof.html @@ -31,7 +31,7 @@ {{ if .Params.og_image }} {{ else }} - + {{ end }} @@ -39,7 +39,7 @@ {{ if .Params.og_image }} {{ else }} - + {{ end }} {{ partial "schema.html" . }} @@ -132,7 +132,6 @@ {{ if or (hasPrefix .RelPermalink "/dev/") (hasPrefix .RelPermalink "/en/dev/") (hasPrefix .RelPermalink "/self/") (hasPrefix .RelPermalink "/en/self/") (hasPrefix .RelPermalink "/money/") (hasPrefix .RelPermalink "/en/money/") }} - @@ -173,6 +172,11 @@ {{ partial "footer.html" . }} + + diff --git a/themes/hugo-coder/layouts/dev/list.html b/themes/hugo-coder/layouts/dev/list.html index 87e703d13..74c73eee2 100644 --- a/themes/hugo-coder/layouts/dev/list.html +++ b/themes/hugo-coder/layouts/dev/list.html @@ -3,8 +3,15 @@ {{ end }} {{ define "content" }}
-

{{ .Description }}

- + {{$scratch := newScratch}} + {{ range (where .Site.RegularPages "Section" "==" "dev") }} + {{$scratch.Add "total" (countwords .Content)}} + {{ end }} +

+ {{ .Description }} +
+
共{{ len (where .Site.RegularPages "Section" "==" "dev") }}篇文章/总计{{$scratch.Get "total" }}字/浏览量
+

    {{- range .Paginator.Pages -}} {{- .Render "li" -}} diff --git a/themes/hugo-coder/layouts/dev/single.html b/themes/hugo-coder/layouts/dev/single.html index 95a2d860e..3e1eb127b 100644 --- a/themes/hugo-coder/layouts/dev/single.html +++ b/themes/hugo-coder/layouts/dev/single.html @@ -94,7 +94,7 @@

    {{ .Title }}

    {{ partial "posts/series.html" . }} {{ partial "posts/disqus.html" . }} {{ partial "posts/commento.html" . }} - {{ partial "posts/utteranc.html" . }} + {{ partial "posts/giscus.html" . }} diff --git a/themes/hugo-coder/layouts/money/list.html b/themes/hugo-coder/layouts/money/list.html index 87e703d13..2070628e8 100644 --- a/themes/hugo-coder/layouts/money/list.html +++ b/themes/hugo-coder/layouts/money/list.html @@ -3,7 +3,15 @@ {{ end }} {{ define "content" }}
    -

    {{ .Description }}

    + {{$scratch := newScratch}} + {{ range (where .Site.RegularPages "Section" "==" "money") }} + {{$scratch.Add "total" (countwords .Content)}} + {{ end }} +

    + {{ .Description }} +
    +
    共{{ len (where .Site.RegularPages "Section" "==" "money") }}篇文章/总计{{$scratch.Get "total" }}字/浏览量
    +

      {{- range .Paginator.Pages -}} diff --git a/themes/hugo-coder/layouts/money/single.html b/themes/hugo-coder/layouts/money/single.html index 95a2d860e..3e1eb127b 100644 --- a/themes/hugo-coder/layouts/money/single.html +++ b/themes/hugo-coder/layouts/money/single.html @@ -94,7 +94,7 @@

      {{ .Title }}

      {{ partial "posts/series.html" . }} {{ partial "posts/disqus.html" . }} {{ partial "posts/commento.html" . }} - {{ partial "posts/utteranc.html" . }} + {{ partial "posts/giscus.html" . }} diff --git a/themes/hugo-coder/layouts/partials/footer.html b/themes/hugo-coder/layouts/partials/footer.html index c8ccd71d9..b5dfb3f47 100644 --- a/themes/hugo-coder/layouts/partials/footer.html +++ b/themes/hugo-coder/layouts/partials/footer.html @@ -5,7 +5,7 @@ ⚔️ Talk ⚔️ - Invest + Invest ⚔️ Think diff --git a/themes/hugo-coder/layouts/partials/posts/giscus.html b/themes/hugo-coder/layouts/partials/posts/giscus.html new file mode 100644 index 000000000..f4d8f082f --- /dev/null +++ b/themes/hugo-coder/layouts/partials/posts/giscus.html @@ -0,0 +1,16 @@ + diff --git a/themes/hugo-coder/layouts/partials/posts/share.html b/themes/hugo-coder/layouts/partials/posts/share.html index b50f788a4..58c1cd65e 100644 --- a/themes/hugo-coder/layouts/partials/posts/share.html +++ b/themes/hugo-coder/layouts/partials/posts/share.html @@ -1,2 +1,5 @@ -
      -
      + diff --git a/themes/hugo-coder/layouts/self/list.html b/themes/hugo-coder/layouts/self/list.html index 87e703d13..a97f0ad01 100644 --- a/themes/hugo-coder/layouts/self/list.html +++ b/themes/hugo-coder/layouts/self/list.html @@ -3,7 +3,15 @@ {{ end }} {{ define "content" }}
      -

      {{ .Description }}

      + {{$scratch := newScratch}} + {{ range (where .Site.RegularPages "Section" "==" "self") }} + {{$scratch.Add "total" (countwords .Content)}} + {{ end }} +

      + {{ .Description }} +
      +
      共{{ len (where .Site.RegularPages "Section" "==" "self") }}篇文章/总计{{$scratch.Get "total" }}字/浏览量
      +

        {{- range .Paginator.Pages -}} diff --git a/themes/hugo-coder/layouts/self/single.html b/themes/hugo-coder/layouts/self/single.html index 95a2d860e..3e1eb127b 100644 --- a/themes/hugo-coder/layouts/self/single.html +++ b/themes/hugo-coder/layouts/self/single.html @@ -94,7 +94,7 @@

        {{ .Title }}

        {{ partial "posts/series.html" . }} {{ partial "posts/disqus.html" . }} {{ partial "posts/commento.html" . }} - {{ partial "posts/utteranc.html" . }} + {{ partial "posts/giscus.html" . }}