使用 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阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容