使用 semantic-release 自動發版

前言


最近新項目準備做自動發版,就去研究了一下 semantic-release

什么是 semantic-release


Fully automated version management and package publishing

semantic-release automates the whole package release workflow including: determining the next version number, generating the release notes and publishing the package.

This removes the immediate connection between human emotions and version numbers, strictly following the Semantic Versioning specification.

用人話說就是一個完全自動化的工具,可以幫助你做版本號管理、生成 changelog ,并且發布到包管理器 ( 比如 npm ) 。

如何使用 ( 官方文檔 )


  1. Install semantic-release in your project

  2. Configure your Continuous Integration service to run semantic-release

  3. Configure your Git repository and package manager repository authentication in your Continuous Integration service

  4. Configure semantic-release options and plugins

cd your-module
npx semantic-release-cli setup

# For Node modules projects
npm install --save-dev semantic-release

# Then in the CI environment
npx semantic-release

以上是官方文檔的內容,操作真的是很簡單,簡單到不知道它是干嘛的,簡單到后面步入一個個坑不知道怎么解決。我照著官方文檔折騰了幾個小時愣是沒跑通,一直在 error 。要不是看在很多項目 ( 比如 octokit ) 都在用它,我都想直接放棄了。

官方文檔只是告訴你需要安裝這么一個包,有哪些參數,剩下的都沒細說,就算有也是藏在某個角落里生怕別人找到:

  1. 是否需要新建 GitHub 倉庫?

  2. 如何配置 GitHubnpmtoken ?

  3. 何時觸發 發布 操作的?手動執行命令?push

  4. 如何測試?一定要在 CI 環境中?

正確打開方式


我特地建了一個新倉庫 semantic-release-test 來演示效果。感興趣的朋友可以照著我的提交記錄走一遍流程。

  • 本地新建一個項目,比如 semantic-release-test

    目錄結構

    .
    ├─.github
    │  └─workflows
    │    └─release.yml
    ├─src
    │  └─index.js
    ├─.env
    ├─.releaserc.js
    ├─package.json
    

    package.json

    替換為自己的包名和倉庫地址

    {
      "name": "@anyesu/semantic-release-test",
      "version": "0.0.0-development",
      "description": "semantic-release-test",
      "main": "src/index.js",
      "scripts": {
        "build": "echo build success.",
        "semantic-release": "dotenv -c -- semantic-release",
        "semantic-release:local": "dotenv -c -- semantic-release --no-ci"
      },
      "publishConfig": {
        "registry": "https://registry.npmjs.org/",
        "access": "public"
      },
      "repository": {
        "type": "git",
        "url": "https://github.com/iewgggg/semantic-release-test.git"
      },
      "author": "anyesu",
      "license": "MIT",
      "bugs": {
        "url": "https://github.com/iewgggg/semantic-release-test/issues"
      },
      "homepage": "https://github.com/iewgggg/semantic-release-test#readme",
      "files": [
        "src"
      ],
      "devDependencies": {
        "@semantic-release/git": "^9.0.0",
        "conventional-changelog-cmyr-config": "^1.2.3",
        "dotenv-cli": "^4.0.0",
        "semantic-release": "^17.2.1"
      }
    }
    

    .releaserc.js

    semantic-release 的配置文件,具體配置項見 文檔

    module.exports = {
      plugins: [
        "@semantic-release/commit-analyzer",
        [
          "@semantic-release/release-notes-generator",
          {
            config: "conventional-changelog-cmyr-config",
          },
        ],
        "@semantic-release/npm",
        [
          "@semantic-release/git",
          {
            assets: ["package.json"],
          },
        ],
        "@semantic-release/github",
      ],
    };
    

    .env

    環境變量配置文件,通過 dotenv-cli 在運行時讀取

    GITHUB_TOKEN

    NPM_TOKEN 如果是私有倉庫或者不想發布到 npm 可以不填

    GITHUB_TOKEN=******
    NPM_TOKEN=******
    

    .github/workflows/release.yml

    CI - GitHub Actions 的配置文件,可選。

    GitHub Actions 會自動創建名為 GITHUB_TOKENsecret ,不需要手動添加

    只需要 添加 額外的 NPM_TOKEN

    name: Release
    on:
      repository_dispatch:
        types: [ semantic-release ]
      push:
        branches:
          - master
          - next
          - beta
          - "*.x" # maintenance releases such as 15.x
    
    jobs:
      release:
        name: release
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
          - uses: actions/setup-node@v1
            with:
              node-version: 12
          - run: npm i
          - run: npm run build
          - run: npx semantic-release
            env:
              GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
    
  • 初始化為 Git 項目,并提交初始化版本

    git init
    git add package.json .releaserc.js .github/workflows/release.yml
    git commit -m "feat: :tada: Initial commit."
    
  • 新建遠程倉庫 ( 比如 semantic-release-test )

    很重要,一定要有遠程倉庫

  • 推送到 GitHub
    git remote add origin https://{GITHUB_TOKEN}@github.com/{owner}/{repo}
    git push -u origin master
    

    如果按照上面步驟配置了 GitHub Actions ,那么打開 GitHub 中的項目可以看到已經成功生成了 release

    同時還可以看到有一條新的提交記錄

    chore(release): 1.0.0 [skip ci]
    
  • 本地測試

    安裝項目

    git pull
    npm i
    

    隨便修改一個文件提交一條新記錄

    echo test > test.txt
    
    git add test.txt
    git commit -m "fix: :apple: Fixing something on macOS."
    

    測試效果

    npm run semantic-release
    

    <details>
    <summary>本地日志</summary>

    [semantic-release] ? i  Running semantic-release version 17.2.1
    [semantic-release] ? √  Loaded plugin "verifyConditions" from "@semantic-release/npm"
    [semantic-release] ? √  Loaded plugin "verifyConditions" from "@semantic-release/git"
    [semantic-release] ? √  Loaded plugin "verifyConditions" from "@semantic-release/github"
    [semantic-release] ? √  Loaded plugin "analyzeCommits" from "@semantic-release/commit-analyzer"
    [semantic-release] ? √  Loaded plugin "generateNotes" from "@semantic-release/release-notes-generator"
    [semantic-release] ? √  Loaded plugin "prepare" from "@semantic-release/npm"
    [semantic-release] ? √  Loaded plugin "prepare" from "@semantic-release/git"
    [semantic-release] ? √  Loaded plugin "publish" from "@semantic-release/npm"
    [semantic-release] ? √  Loaded plugin "publish" from "@semantic-release/github"
    [semantic-release] ? √  Loaded plugin "addChannel" from "@semantic-release/npm"
    [semantic-release] ? √  Loaded plugin "addChannel" from "@semantic-release/github"
    [semantic-release] ? √  Loaded plugin "success" from "@semantic-release/github"
    [semantic-release] ? √  Loaded plugin "fail" from "@semantic-release/github"
    [semantic-release] ? ?  This run was not triggered in a known CI environment, running in dry-run mode.
    [semantic-release] ? ?  Run automated release from branch master on repository https://[secure]@github.com/iewgggg/semantic-release-test.git in dry-run mode
    [semantic-release] ? √  Allowed to push to the Git repository
    [semantic-release] ? i  Start step "verifyConditions" of plugin "@semantic-release/npm"
    [semantic-release] ? √  Completed step "verifyConditions" of plugin "@semantic-release/npm"
    [semantic-release] ? i  Start step "verifyConditions" of plugin "@semantic-release/git"
    [semantic-release] ? √  Completed step "verifyConditions" of plugin "@semantic-release/git"
    [semantic-release] ? i  Start step "verifyConditions" of plugin "@semantic-release/github"
    [semantic-release] [@semantic-release/github] ? i  Verify GitHub authentication
    [semantic-release] ? √  Completed step "verifyConditions" of plugin "@semantic-release/github"
    [semantic-release] ? i  Found git tag v1.0.0 associated with version 1.0.0 on branch master
    [semantic-release] ? i  Found 1 commits since last release
    [semantic-release] ? i  Start step "analyzeCommits" of plugin "@semantic-release/commit-analyzer"
    [semantic-release] [@semantic-release/commit-analyzer] ? i  Analyzing commit: fix: :apple: Fixing something on macOS.
    [semantic-release] [@semantic-release/commit-analyzer] ? i  The release type for the commit is patch
    [semantic-release] [@semantic-release/commit-analyzer] ? i  Analysis of 1 commits complete: patch release
    [semantic-release] ? √  Completed step "analyzeCommits" of plugin "@semantic-release/commit-analyzer"
    [semantic-release] ? i  The next release version is 1.0.1
    [semantic-release] ? i  Start step "generateNotes" of plugin "@semantic-release/release-notes-generator"
    [semantic-release] ? √  Completed step "generateNotes" of plugin "@semantic-release/release-notes-generator"
    [semantic-release] ? ?  Skip step "prepare" of plugin "@semantic-release/npm" in dry-run mode
    [semantic-release] ? ?  Skip step "prepare" of plugin "@semantic-release/git" in dry-run mode
    [semantic-release] ? ?  Skip v1.0.1 tag creation in dry-run mode
    [semantic-release] ? ?  Skip step "publish" of plugin "@semantic-release/npm" in dry-run mode
    [semantic-release] ? ?  Skip step "publish" of plugin "@semantic-release/github" in dry-run mode
    [semantic-release] ? ?  Skip step "success" of plugin "@semantic-release/github" in dry-run mode
    [semantic-release] ? √  Published release 1.0.1 on default channel
    [semantic-release] ? i  Release note for version 1.0.1:
    ## 1.0.1 (https://github.com/iewgggg/semantic-release-test/compare/v1.0.0...v1.0.1) (2020-10-22)
    
    ### ? Bug Fixes
    
        * ?  Fixing something on macOS. (c83864b (https://github.com/iewgggg/semantic-release-test/commit/c83864b))
    

    </details>

    已經可以看到根據 提交記錄 生成的 changelog 的內容,由于不是在 CI 環境中運行的,所以實際并沒有發布到 GitHubnpm

    通過預覽知道已經 OK 了,下面可以在本地進行發布

    npm run semantic-release:local
    

    <details>
    <summary>本地日志</summary>

    [semantic-release] ? i  Running semantic-release version 17.2.1
    [semantic-release] ? √  Loaded plugin "verifyConditions" from "@semantic-release/npm"
    [semantic-release] ? √  Loaded plugin "verifyConditions" from "@semantic-release/git"
    [semantic-release] ? √  Loaded plugin "verifyConditions" from "@semantic-release/github"
    [semantic-release] ? √  Loaded plugin "analyzeCommits" from "@semantic-release/commit-analyzer"
    [semantic-release] ? √  Loaded plugin "generateNotes" from "@semantic-release/release-notes-generator"
    [semantic-release] ? √  Loaded plugin "prepare" from "@semantic-release/npm"
    [semantic-release] ? √  Loaded plugin "prepare" from "@semantic-release/git"
    [semantic-release] ? √  Loaded plugin "publish" from "@semantic-release/npm"
    [semantic-release] ? √  Loaded plugin "publish" from "@semantic-release/github"
    [semantic-release] ? √  Loaded plugin "addChannel" from "@semantic-release/npm"
    [semantic-release] ? √  Loaded plugin "addChannel" from "@semantic-release/github"
    [semantic-release] ? √  Loaded plugin "success" from "@semantic-release/github"
    [semantic-release] ? √  Loaded plugin "fail" from "@semantic-release/github"
    [semantic-release] ? √  Run automated release from branch master on repository https://[secure]@github.com/iewgggg/semantic-release-test.git
    [semantic-release] ? √  Allowed to push to the Git repository
    [semantic-release] ? i  Start step "verifyConditions" of plugin "@semantic-release/npm"
    [semantic-release] ? √  Completed step "verifyConditions" of plugin "@semantic-release/npm"
    [semantic-release] ? i  Start step "verifyConditions" of plugin "@semantic-release/git"
    [semantic-release] ? √  Completed step "verifyConditions" of plugin "@semantic-release/git"
    [semantic-release] ? i  Start step "verifyConditions" of plugin "@semantic-release/github"
    [semantic-release] [@semantic-release/github] ? i  Verify GitHub authentication
    [semantic-release] ? √  Completed step "verifyConditions" of plugin "@semantic-release/github"
    [semantic-release] ? i  Found git tag v1.0.0 associated with version 1.0.0 on branch master
    [semantic-release] ? i  Found 1 commits since last release
    [semantic-release] ? i  Start step "analyzeCommits" of plugin "@semantic-release/commit-analyzer"
    [semantic-release] [@semantic-release/commit-analyzer] ? i  Analyzing commit: fix: :apple: Fixing something on macOS.
    [semantic-release] [@semantic-release/commit-analyzer] ? i  The release type for the commit is patch
    [semantic-release] [@semantic-release/commit-analyzer] ? i  Analysis of 1 commits complete: patch release
    [semantic-release] ? √  Completed step "analyzeCommits" of plugin "@semantic-release/commit-analyzer"
    [semantic-release] ? i  The next release version is 1.0.1
    [semantic-release] ? i  Start step "generateNotes" of plugin "@semantic-release/release-notes-generator"
    [semantic-release] ? √  Completed step "generateNotes" of plugin "@semantic-release/release-notes-generator"
    [semantic-release] ? i  Start step "prepare" of plugin "@semantic-release/npm"
    [semantic-release] [@semantic-release/npm] ? i  Write version 1.0.1 to package.json in pathto\semantic-release-test
    v1.0.1
    [semantic-release] ? √  Completed step "prepare" of plugin "@semantic-release/npm"
    [semantic-release] ? i  Start step "prepare" of plugin "@semantic-release/git"
    [semantic-release] [@semantic-release/git] ? i  Found 1 file(s) to commit
    [semantic-release] [@semantic-release/git] ? i  Prepared Git release: v1.0.1
    [semantic-release] ? √  Completed step "prepare" of plugin "@semantic-release/git"
    [semantic-release] ? i  Start step "generateNotes" of plugin "@semantic-release/release-notes-generator"
    [semantic-release] ? √  Completed step "generateNotes" of plugin "@semantic-release/release-notes-generator"
    [semantic-release] ? √  Created tag v1.0.1
    [semantic-release] ? i  Start step "publish" of plugin "@semantic-release/npm"
    [semantic-release] [@semantic-release/npm] ? i  Skip publishing to npm registry as package.json's private property is true
    [semantic-release] ? √  Completed step "publish" of plugin "@semantic-release/npm"
    [semantic-release] ? i  Start step "publish" of plugin "@semantic-release/github"
    [semantic-release] [@semantic-release/github] ? i  Published GitHub release: https://github.com/iewgggg/semantic-release-test/releases/tag/v1.0.1
    [semantic-release] ? √  Completed step "publish" of plugin "@semantic-release/github"
    [semantic-release] ? i  Start step "success" of plugin "@semantic-release/github"
    [semantic-release] ? √  Completed step "success" of plugin "@semantic-release/github"
    [semantic-release] ? √  Published release 1.0.1 on default channel
    

    </details>

    再打開 GitHub 中的項目可以看到 release v1.0.1 也生成了。

    Tip: 可以注意到兩次 release 的作者是不一樣的,因為使用了不同的 GITHUB_TOKEN

    此時雖然 GitHub Actions 中多了一個新的執行記錄:

    chore(release): 1.0.1 [skip ci]
    

    但不需要擔心會重復發布,在日志中可以看到下面的內容:

    There are no relevant changes, so no new version is released.
    

原理分析


首先,我們結合 semantic-releasesemantic-release:local 兩個命令的日志看下它究竟做了什么

  1. 加載插件。

  2. 判斷是否在 CI 環境中,不是則跳過執行插件的一些生命周期。

  3. 檢查遠程倉庫是否存在,以及合法的分支。

  4. 檢查是否有倉庫的寫入權限。

    這一步比較坑爹,如果沒有設置 GITHUB_TOKEN 報錯竟然是版本落后???

    Run automated release from branch master on repository https://github.com/iewgggg/semantic-release-test.git in dry-run mode
    The local branch master is behind the remote one, therefore a new version won't be published.
    

    設置 GITHUB_TOKEN 后再看日志感受下區別

    Run automated release from branch master on repository https://[secure]@github.com/iewgggg/semantic-release-test.git in dry-run mode
    √  Allowed to push to the Git repository
    

    這個問題也是我 debug 源碼后才發現的。

  5. 檢查 NPM_TOKEN 是否存在,并寫入到 temp 目錄的 .npmrc 文件中。

  6. 檢查當前分支是否存在符合 語義化版本tag,存在則為當前版本號。

  7. 對比 遠程倉庫 是否有新的提交記錄,無則終止。

  8. 插件 @semantic-release/commit-analyzer 開始對 新的提交記錄 逐條分析,篩選符合 提交規范 的記錄, 篩選結果 為空則終止。

  9. 如果存在 當前版本號 ,則根據 篩選結果 確定升級類型 ( MAJOR / MINOR / PATCH ) 計算 新的版本號 ,否則 新版本號1.0.0

  10. 插件 @semantic-release/release-notes-generator 再對 新的提交記錄 逐條分析生成 changelog

  11. 插件 @semantic-release/npm新的版本號 重新寫入到 package.json 文件中。

  12. 插件 @semantic-release/git 對修改后的 package.json 進行 提交推送

    插件默認配置還會修改 CHANGELOG.md 文件,本文中配置為只修改 package.json

  13. 添加新版本號 tag

  14. npm 打包發布 ,如果 package.json 設置了 "private": true 則跳過。

  15. 發布到 GitHub ,包括壓縮包和 changelog

整個流程比較清晰了,主要操作也是通過幾個插件來完成,所以通過對插件的組合、配置來實現一些個性化的需求。

缺點


  1. semantic-release 強依賴于遠程倉庫的分支狀態,所以在正式使用前測試會非常麻煩,反復測試需要反復重置遠程倉庫的狀態,而且網絡不好就更惡心了。

  2. release 中的 changelog 是發布一個版本生成一次的,這就意味著倉庫遷移的話,歷史記錄就沒有了,只剩下 tag

    如果沒有使用插件 @semantic-release/git 的話可以不斷 reset HEAD 來恢復一個個版本,當然這也很麻煩。

FAQ


  • 官方文檔太爛怎么辦?

    直接閱讀源碼 + debug ,以力破法。

  • 為何本地執行命令會卡住?

    因為分別需要連接到 GitHubnpm ,網絡環境差就會卡住。

  • 如何不發布到 npm?

    1. 移除插件 @semantic-release/npm 即可,但這樣 package.json 中的版本號得不到修改。

    2. package.json 中設置 "private": true

    3. 插件 @semantic-release/npm 配置為 npmPublish: false

  • registry 設置為 https://registry.npm.taobao.org 但需要發布到 npm?

    package.json 中設置

    "publishConfig": {
      "registry": "https://registry.npmjs.org/"
    }
    
  • npm 發布報錯?

    npm ERR! 402 Payment Required - PUT https://registry.npmjs.org/ - You must sign up for private packages

    第一次發布時會出現這個錯誤,因為 scoped packages 默認會被發布為 私有包 ,需要將其發布為 公有包 。見 官方文檔

    下面有三種方案

    1. 修改 命令行 參數

      npm publish --access public
      
    2. .npmrc 中設置

      access=public
      
    3. package.json 中設置

      "publishConfig": {
        "access": "public"
      }
      
  • npm 開啟雙因素認證 ( Two Factor Authentication ) 后發布報錯?

    The npm token (https://github.com/semantic-release/npm/blob/master/README.md#npm-registry-authentication) configured in the NPM_TOKEN environment variable must be a valid token (https://docs.npmjs.com/getting-started/working_with_tokens) allowing to publish to
    the registry https://registry.npmjs.org/.

    If you are using Two-Factor Authentication, make configure the auth-only level (https://docs.npmjs.com/getting-started/using-two-factor-authentication#levels-of-authentication) is supported. semantic-release cannot publish with the default auth-and-writes level.

    1. 雙因素認證 需要設置為 auth-only 級別 ( 文檔 ) 。

    2. Access Tokens 類型選擇 Publish 而不是 Automation

  • 如何同步更新 package.json 中的版本號?

    默認不更新版本號,可以添加插件 @semantic-release/git 來額外提交。

  • 如何在 1.0.0 之前發布 prerelease 版本,如:1.0.0-beta.10 ?

    1. 項目不包含 release.yml 時初始化后推送到 master 分支,這樣確保 遠程倉庫 有一個 release 分支 并且不會自動發布。

    2. 切到 beta 分支提交 release.yml 后推送,自動發布為 1.0.0-beta.1

    這樣就生成 beta 版本了,后續迭代繼續在 beta 分支上進行,發布正式版只需將 beta 分支合并到 master 分支,然后所有 beta 版的 changelog 都會合并到 1.0.0 中。( 參考 )

  • 如何手動管理版本?

    可以使用 standard-version ,二者區別:

    standard-version takes a different approach by handling versioning, changelog generation, and git tagging for you without automatic pushing (to GitHub) or publishing (to an npm registry). Use of standard-version only affects your local git repo - it doesn't affect remote resources at all. After you run standard-version, you can review your release state, correct mistakes and follow the release strategy that makes the most sense for your codebase.

    "scripts": {
      "release:beta": "standard-version --release-as major --prerelease beta",
      "release:major": "standard-version --release-as major",
      "release:minor": "standard-version --release-as minor",
      "release:patch": "standard-version --release-as patch"
    },
    "devDependencies": {
      "standard-version": "^9.0.0"
    }
    
  • 如何手動發布?

    1. 本地執行 npm run semantic-release:local

    2. 通過 GitHub Actions Hook 調用,詳見 文檔

      也可以添加按鈕 - https://github-action-button.web.app

  • 如何修改 changelog 的格式?

    這部分功能底層依賴于 conventional-changelog ,已經內置幾種預設 ( angular , atom , codemirror, ember , eslint , express , jquery , jshint , conventionalcommits ) 可供選擇,我這里使用的是第三方配置 conventional-changelog-cmyr-config ,需要更加個性化詳見 conventional-changelog-writer 包配置項。

    本來我想配置成 antd 那樣的雙語 changelog ( 再復制一份用 翻譯 API 翻譯一下,再手動微調 ),但還要 fork 發包,那就先算了吧。

IDEA 插件推薦


  1. Git Commit Template

    通過表單的形式結構化輸入 commit message

    符合 AngularJSGit Commit Guidelines 規范。

  2. Gitmoji

    根據輸入的內容來選擇 emoji 表情 的表達式。

    Reference commit rules / 中文提交規則

  3. Gitmoji-Unicode

    Gitmoji 插件的 fork 版本,可以直接顯示 emoji 表情 而非表達式。

兩個插件都挺不錯的,缺點就是寫死了不可配置,再加上幾年沒維護了,用起來有點尷尬。

最后


按照文中的配置,基本上滿足我的需求了:

  1. 自動化版本管理,推送代碼或者合并 PR 即可發版。

  2. 自動生成 changelog ,不再需要單獨去寫。

  3. 自動發布到 npm ( ?? 直接省去了學習 npm 發包的步驟 ) 。

  4. 發布后對應的 PRissue 下會添加 發布通知 的評論,就不需要自己再手動通知了。


轉載請注明出處:https://github.com/anyesu/blog/issues/37

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容