├── .editorconfig ├── .gitignore ├── .jshintrc ├── .logorc ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── logo.svg ├── mock ├── coffee.coffee ├── cookie.js ├── data │ ├── demo.json │ ├── demo.yaml │ └── huge.json ├── debug.js ├── file.js ├── huge.js ├── json.json ├── jsonp.js ├── manual.js ├── new-feature.js ├── placeholder.js ├── random.js ├── restful.js ├── special.js └── yaml.yaml ├── package.json └── tasks └── mock.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | 14 | # Set default charset 15 | [*.js] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | node_modules 4 | npm-debug.log 5 | tmp 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "newcap": true, 6 | "noarg": true, 7 | "sub": true, 8 | "undef": true, 9 | "boss": true, 10 | "eqnull": true, 11 | "node": true, 12 | "latedef": "nofunc" 13 | } 14 | -------------------------------------------------------------------------------- /.logorc: -------------------------------------------------------------------------------- 1 | logo: 'MOCK ' 2 | path: 3 | fill: '#6ccb99' 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .logorc 4 | .editorconfig 5 | .travis.yml 6 | .gitignore 7 | .npmignore 8 | .jshintrc 9 | .jshintignore 10 | Gruntfile.js 11 | 12 | logo.svg 13 | npm-debug.log 14 | 15 | node_modules 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 0.12 5 | 6 | script: 7 | - npm test 8 | 9 | matrix: 10 | fast_finish: true 11 | 12 | before_install: npm install -g grunt-cli 13 | install: npm install 14 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-restful-mock 3 | * 4 | * 5 | * Copyright (c) 2014 bubkoo 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function (grunt) { 12 | 13 | // Project configuration. 14 | grunt.initConfig({ 15 | jshint: { 16 | all: [ 17 | 'Gruntfile.js', 18 | 'tasks/**/*.js' 19 | ], 20 | options: { 21 | jshintrc: '.jshintrc' 22 | } 23 | }, 24 | // Configuration to be run (and then tested). 25 | mock: { 26 | options: { 27 | debug: true, 28 | watch: 'grunt/mock.js' 29 | }, 30 | 31 | demo: { 32 | options: { 33 | port: '6001', 34 | debug: true, 35 | placeholders: { 36 | hello: function (name) { 37 | return 'hello ' + name; 38 | } 39 | }, 40 | rules: { 41 | '/demo/for/inline1': { 42 | 'get': { 43 | data: { 44 | 'code': '0', 45 | 'data': { 46 | 'name': 'bubkoo', 47 | 'email': 'bubkoo@163.com' 48 | } 49 | } 50 | } 51 | }, 52 | '/demo/for/inline2': { 53 | post: { 54 | delay: 3000, 55 | data: { 56 | code: '0', 57 | msg: 'delay 3s' 58 | } 59 | } 60 | } 61 | } 62 | }, 63 | cwd: 'mock', 64 | src: ['*.js', '*.yaml', '*.coffee', '*.json'] 65 | } 66 | } 67 | }); 68 | 69 | // Actually load this plugin's task(s). 70 | grunt.loadTasks('tasks'); 71 | 72 | // These plugins provide necessary tasks. 73 | grunt.loadNpmTasks('grunt-contrib-jshint'); 74 | 75 | // By default, lint and run all tests. 76 | grunt.registerTask('default', ['jshint']); 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 bubkoo 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo.svg](https://cdn.rawgit.com/bubkoo/grunt-restful-mock/master/logo.svg) 2 | 3 | 4 | > 模拟 AJAX 请求返回的 JSON 数据,减少前端工程师对后端接口的依赖,在接口规范的基础之上并行开发。 5 | 6 | 7 | [![MIT License](https://img.shields.io/badge/license-MIT_License-green.svg?style=flat-square)](https://github.com/bubkoo/grunt-restful-mock/blob/master/LICENSE) 8 | [![npm:](https://img.shields.io/npm/v/grunt-restful-mock.svg?style=flat-square)](https://www.npmjs.com/packages/grunt-restful-mock) 9 | 10 | 11 | ## 特性 12 | 13 | - 基于数据模板生成随机数据 14 | - 自定义数据模板占位符 15 | - 支持 restful 接口 16 | - 支持 JSONP 请求 17 | - 模拟 HTTPOnly 的 Cookie 18 | - 模拟 HTTP 响应状态码,模拟请求超时,模拟网络延时 19 | - 热重启,修改 mock 配置后自动重启服务 20 | 21 | 22 | ## 使用 23 | 24 | - [开始使用](https://github.com/bubkoo/grunt-restful-mock/wiki/开始使用) 25 | - [使用示例](https://github.com/bubkoo/grunt-restful-mock/wiki/使用示例) 26 | - [数据模板语法规则](https://github.com/bubkoo/grunt-restful-mock/wiki/数据模板语法规则) 27 | - [内置占位符](https://github.com/bubkoo/grunt-restful-mock/wiki/内置占位符) 28 | 29 | 30 | ## 相关模块 31 | 32 | - [restful-mock-server](https://github.com/bubkoo/restful-mock-server) 33 | - [generate-random-data](https://github.com/bubkoo/generate-random-data) 34 | - [json-from-template](https://github.com/bubkoo/json-from-template) 35 | 36 | ## 历史 37 | 38 | ### v0.2.1 39 | 40 | - 添加 `logo.svg` 41 | - 更新文档 42 | 43 | ### v0.2.0 44 | - 重构,提取核心代码为独立的模块 45 | 46 | ### v0.1.21 47 | - 增加自定义占位符接口 48 | - 组合谓词(`get|post`),参数修饰谓词(`get[param1=value1]`) 49 | 50 | 51 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | MOCK -------------------------------------------------------------------------------- /mock/coffee.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | '/demo/for/coffee': 3 | 'get': 4 | data: 5 | 'code': '0' 6 | 'data': 7 | 'bind_email': '' 8 | 'bind_phone': '' 9 | 'set_wallet_pwd': true -------------------------------------------------------------------------------- /mock/cookie.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '/demo/for/cookie': { 3 | 'get': { 4 | delay: 500, 5 | cookies: [ 6 | { 7 | 'id': 123, 8 | 'options': { 9 | maxAge: 1000 * 60 * 60, // cookie 的存活期 10 | domain: 'some.com', 11 | path: '/cookie/path' 12 | } 13 | } 14 | ], 15 | data: { 16 | 'code': '0', 17 | 'data': { 18 | 'pageSize': 10, // 每页条数 19 | 'pageIndex': 2, // 当前页码 20 | 'pageCount|0-10': 0, // 总页数 21 | 'records|10-10': [ 22 | { 23 | 'bankCard': '**JxRX', 24 | 'bankName': '招商银行', 25 | 'bankNo': '007', 26 | 'certId': '', // 证件号码 27 | 'certType': '', // 证件类型 28 | 'tranAmount|0-10000000': 200, // 交易金额 29 | 'tranStatus': '1', // 交易状态 30 | 'tranTime': '@DATE("YYYYMMDDhhmmss")', // 交易时间 31 | 'tranType': '1', // 交易类型 32 | 'userId': '111', 33 | 'userName': 'chen' 34 | } 35 | ], 36 | 'recordsCount|0-100': 20 37 | }, 38 | 'message': '' 39 | } 40 | } 41 | } 42 | }; -------------------------------------------------------------------------------- /mock/data/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "message": "success", 4 | "code": 0, 5 | "data": { 6 | "from": "json", 7 | "name": "bubkoo", 8 | "age": 29, 9 | "blog": "http://bubkoo.com" 10 | } 11 | } -------------------------------------------------------------------------------- /mock/data/demo.yaml: -------------------------------------------------------------------------------- 1 | success: true 2 | message: success 3 | code: 0 4 | data: 5 | from: yaml 6 | name: bubkoo 7 | age: 29 8 | blog: http://bubkoo.com -------------------------------------------------------------------------------- /mock/debug.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '/demo/for/debug': { 3 | 'get': { 4 | data: { 5 | // 'code': '0', 6 | // 'message': '', 7 | 'data': { 8 | // 'name': 'xxx', 9 | // 'range2': '@range(@int(1,5), @int(6,10), 2)' 10 | // 'date': '@date("@randomDate", "YYYY-MM-DD")' 11 | 'num': '@d5', 12 | 'mobile': '@mobile, @zipcode', 13 | 'email': 'yyy\\@zzz.com', // 转义的 14 | 'native1': '\\@native', 15 | 'native2': '\\@native, \\@d5' 16 | } 17 | } 18 | } 19 | } 20 | }; -------------------------------------------------------------------------------- /mock/file.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '/demo/for/fromJson': { 3 | 'get': { 4 | //data: '@fromFile("./mock/data/demo.json")' 5 | //data: '@fromFile("./mock/data/demo.yaml")' 6 | //data: '@fromFile("./mock/data/huge.json")' 7 | data: { 8 | 'code': '0', 9 | 'message': '', 10 | 'data': '@fromFile("./mock/data/huge.json")' 11 | } 12 | } 13 | } 14 | }; -------------------------------------------------------------------------------- /mock/json.json: -------------------------------------------------------------------------------- 1 | { 2 | "/demo/for/json": { 3 | "get": { 4 | "delay": 3000, 5 | "data": { 6 | "code": "0", 7 | "msg": "delay 3s" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /mock/jsonp.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '/demo/for/jsonp': { 3 | 'get': { 4 | jsonp: 'callback', 5 | data: { 6 | 'code': '0', 7 | 'message': '', 8 | 'data': { 9 | // 累积收益,最小单位为分,100表示1元 10 | 'earnings|0-10000000': 100, 11 | 'money|0-10000000': 10000, 12 | 'history|0-2': [ 13 | { 14 | // 收益记录时间 15 | 'date': '@DATE("YYYY-MM-DD hh:mm:ss")', 16 | // 收益记录收益,最小单位为分,100表示1元 17 | 'earnings|0-10000000': 10 18 | } 19 | ] 20 | } 21 | } 22 | } 23 | } 24 | }; -------------------------------------------------------------------------------- /mock/manual.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '/demo/for/manual1': { 3 | 'get': { 4 | // 路由内部定义的 placeholder 不共享 5 | 'placeholders': { 6 | 'foo': function () { 7 | return 'foo: ' + this.now(); 8 | } 9 | }, 10 | 'data': { 11 | // 使用全局 placeholder 12 | 'hello': '@hello("bubkoo")', 13 | // 使用局部 placeholder 14 | 'foo': '@foo', 15 | 'bar': '@bar' 16 | } 17 | } 18 | }, 19 | '/demo/for/manual2': { 20 | 'get': { 21 | // 路由内部定义的 placeholder 不共享 22 | 'placeholders': { 23 | 'bar': function () { 24 | return 'bar: ' + this.now(); 25 | } 26 | }, 27 | 'data': { 28 | // 使用全局 placeholder 29 | 'hello': '@hello("bubkoo")', 30 | // 使用局部 placeholder 31 | 'foo': '@foo', 32 | 'bar': '@bar' 33 | } 34 | } 35 | } 36 | }; -------------------------------------------------------------------------------- /mock/new-feature.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '/multiple/methods': { 3 | // 用竖线分隔的谓词 4 | 'get|post': { 5 | data: { 6 | name: 'bubkoo', 7 | email: '@email', 8 | 'count|1-100': 0 9 | } 10 | } 11 | }, 12 | 13 | '/method/with/params': { 14 | 'get[type=role]': { 15 | data: { 16 | 'success': true, 17 | 'data': { 18 | 'pageSize': 10, // 每页条数 19 | 'pageIndex': 2, // 当前页码 20 | 'pageCount|0-10': 0, // 总页数 21 | 'records|10-10': [ 22 | { 23 | 'id|0-100': 0, 24 | 'name': '@name' 25 | } 26 | ], 27 | 'recordCount|0-100': 20 28 | }, 29 | 'message': '' 30 | } 31 | }, 32 | 'get[type=assigned, pageIndex=1]|post': { 33 | data: { 34 | 'success': true, 35 | 'data': { 36 | 'pageSize': 10, // 每页条数 37 | 'pageIndex': 1, // 当前页码 38 | 'pageCount|0-10': 0, // 总页数 39 | 'records|10-10': [ 40 | { 41 | 'id|0-100': 0, 42 | 'name': '@name' 43 | } 44 | ], 45 | 'recordCount|0-100': 20 46 | }, 47 | 'message': '' 48 | } 49 | } 50 | } 51 | }; -------------------------------------------------------------------------------- /mock/placeholder.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '/demo/for/placeholder': { 3 | 'get': { 4 | 'data': { 5 | 'inc': { 6 | 'inc1|10': [ 7 | { 8 | message:'start 1, step 1', 9 | result:'@inc(1, 1)' 10 | } 11 | ] 12 | } 13 | } 14 | }, 15 | 'post': { 16 | 'data': { 17 | 'formItem': '@formItem("message")' 18 | } 19 | } 20 | } 21 | }; -------------------------------------------------------------------------------- /mock/random.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '/demo/for/random': { 3 | 'get': { 4 | 'data': { 5 | 'rules': { 6 | 'number1|10': 1, // 返回 10 7 | 'number2|1-10': 1, // 返回 1-10 之间的数 8 | 'number3|1-10.1-4': 1, // 返回整数部分 1-10 之间,小数位数为 1-4 位 9 | 'number4|1-10.1-4': 1.123456, // 返回整数部分 1-10 之间,小数位数为 1-4 位,小数部分来源于原数字 10 | 'number5|1-10.2': 1, // 整数部分 1-10,小数固定两位 11 | 'number6|1-10.2': 1.123456, // 整数部分 1-10,小数固定两位,小数部分为 12 12 | 'number7|10.1-4': 1, // 整数固定为 10,小数位数为 1-4 位 13 | 'number8|10.2': 1, // 整数固定为 10,小数位数为 2 位 14 | 'number9|+1': 1, 15 | 16 | 'string1|1-4': 'Mock', 17 | 'string2|2': 'Mock', 18 | 19 | 'boolean1|1': true, 20 | 'boolean2|9-10': true, 21 | 22 | 'array1|2': [1, 2, 3], // 重复数组 2 次 23 | 'array2|2-5': [1, 2, 3], // 重复数组 2-5 次 24 | 25 | 'object1|2': {key1: 'value1', key2: 'value2', key3: 'value3'}, 26 | 'object2|1-3': {key1: 'value1', key2: 'value2', key3: 'value3', key4: 'value4'}, 27 | 28 | 'function1|2': function () { 29 | return 'Mock'; 30 | } 31 | }, 32 | 33 | 'base': { 34 | 'int1': '@int', 35 | 'int2': '@int(10)', 36 | 'int3': '@int(10,100)', 37 | 38 | 'natural1': '@natural', 39 | 'natural2': '@natural(10)', 40 | 'natural3': '@natural(10, 100)', 41 | 42 | 'boolean1': '@bool', 43 | 'boolean2': '@bool(1, 5, true)', 44 | 45 | 'float1': '@float', 46 | 'float2': '@float(1, 10)', 47 | 'float3': '@float(1, 10, 2)', 48 | 'float4': '@float(1, 10, 2, 4)', 49 | 50 | 'char1': '@char', 51 | 'char2': '@char("lower")', 52 | 'char3': '@char("upper")', 53 | 'char4': '@char("number")', 54 | 'char5': '@char("symbol")', 55 | 56 | 'string1': '@string("lower", 2, 10)', 57 | 'string2': '@string("lower", 2)', 58 | 'string3': '@string(2, 10)', 59 | 'string4': '@string("lower")', 60 | 'string5': '@string(2)', 61 | 'string6': '@string', 62 | 63 | 'capitalize': '@capitalize("mock")', 64 | 'upper': '@upper("mock")', 65 | 'lower': '@lower("MOCK")' 66 | }, 67 | 68 | 'dx': { 69 | 'd5': '@d5', 70 | 'd10': '@d10', 71 | 'd20': '@d20', 72 | 'd50': '@d50', 73 | 'd100': '@d100', 74 | 'd200': '@d200', 75 | 'd500': '@d500', 76 | 'd1000': '@d1000' 77 | }, 78 | 79 | 'array': { 80 | 'pickOne1': '@pickOne([1, 2, 3, 4, 5, 6, 7, 8, 9])', 81 | 'pickOne2': '@pickOne("abcdefgh")', 82 | 'pickSome1': '@pickSome([1, 2, 3, 4, 5, 6, 7, 8, 9])', 83 | 'pickSome2': '@pickSome([1, 2, 3, 4, 5, 6, 7, 8, 9], 4)', 84 | 'pickSome3': '@pickSome([1, 2, 3, 4, 5, 6, 7, 8, 9], 4, true)', 85 | 'range1': '@range(1, 10, 2)', 86 | 'range2': '@range(@int(1,5), @int(6,20), 2)', 87 | 'shuffle': '@shuffle([1,2,3,4,5,6,7,8,9])' 88 | }, 89 | 90 | 'address': { 91 | // 'countryList': '@countryList', 92 | // 'provinceList': '@provinceList', 93 | 'randomArea': '@randomArea' 94 | }, 95 | 96 | 'datetime': { 97 | 'now1': '@now', 98 | 'now2': '@now("YYYY年MM月DD日 HH时mm分ss秒")', 99 | 'now3': '@now("month", "YYYY-MM-DD HH:mm:ss")', 100 | 'randomDate': '@randomDate', 101 | 'date': '@date("@randomDate", "YYYY-MM-DD")', 102 | 'time': '@time("@randomDate", "HH:mm:ss")', 103 | 'datetime': '@datetime("@randomDate", "YYYY-MM-DD HH:mm:ss")', 104 | 'formatDate': '@formatDate("@randomDate", "YYYY-MM-DD HH:mm:ss")', 105 | 'parseDate': '@parseDate("2014-12-24 10:56:02")' 106 | }, 107 | 108 | 'form': { 109 | 'guid': '@guid', 110 | 'id': '@id', 111 | 'language': '@language', 112 | 'lang': '@language', 113 | 'zipcode1': '@zipcode', 114 | 'zipcode2': '@zipcode(8)', 115 | 'zip1': '@zipcode', 116 | 'zip2': '@zipcode(5)', 117 | 'mobile': '@mobile' 118 | }, 119 | 120 | 'names': { 121 | 'maleFirstName': '@maleFirstName', 122 | 'femaleFirstName': '@femaleFirstName', 123 | 'lastName': '@lastName', 124 | 'name1': '@name', 125 | 'name2': '@name("Danny")' 126 | }, 127 | 128 | 'article': { 129 | 'word1': '@word', 130 | 'word2': '@word(5)', 131 | 'word3': '@word(2, 10)', 132 | 133 | 'sentence1': '@sentence', 134 | 'sentence2': '@sentence(5)', 135 | 'sentence3': '@sentence(2, 10)', 136 | 137 | 'title1': '@title', 138 | 'title2': '@title(5)', 139 | 'title3': '@title(2, 10)', 140 | 141 | 'paragraph1': '@paragraph', 142 | 'paragraph2': '@paragraph(5)', 143 | 'paragraph3': '@paragraph(2, 10)', 144 | 145 | 'lorem': '@lorem', 146 | 'lorems': '@lorems' 147 | }, 148 | 149 | 'network': { 150 | 'tld': '@tld', 151 | 'domain1': '@domain', 152 | 'domain2': '@domain("us")', 153 | 'email1': '@email', 154 | 'email2': '@email("163.com")', 155 | 'url': '@url', 156 | 'ip': '@ip' 157 | }, 158 | 159 | 'color': { 160 | 'color': '@color' 161 | } 162 | } 163 | } 164 | } 165 | }; -------------------------------------------------------------------------------- /mock/restful.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 路由规则参考 https://github.com/pillarjs/path-to-regexp 3 | '/demo/for/restful/:id': { 4 | 'get': { 5 | data: { 6 | 'code': '0', 7 | 'message': '', 8 | 'data': { 9 | id: '@formItem("id")' 10 | } 11 | } 12 | } 13 | } 14 | }; -------------------------------------------------------------------------------- /mock/special.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '/demo/for/sp1': { 3 | 'get': { 4 | // 处理返回数据是数组的情况 5 | 'data|0-10': [ 6 | { 7 | 'date': '@DATE("YYYY-MM-DD hh:mm:ss")', 8 | 'count|0-100': 10 9 | } 10 | ] 11 | } 12 | } 13 | }; -------------------------------------------------------------------------------- /mock/yaml.yaml: -------------------------------------------------------------------------------- 1 | /demo/for/yaml: 2 | get: 3 | data: 4 | code: 0 5 | data: 6 | count: 30 7 | page: 1 8 | page_num: 1 9 | results|30: 10 | - date|+1: 20140514 11 | earnings|1-10000: 10 12 | 13 | total: 30 14 | msg: '' 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-restful-mock", 3 | "description": "A mock server returning random JSON from schema.", 4 | "version": "0.2.1", 5 | "homepage": "https://github.com/bubkoo/grunt-restful-mock", 6 | "author": { 7 | "name": "bubkoo", 8 | "email": "bubkoo@163.com", 9 | "url": "http://bubkoo.com" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/bubkoo/grunt-restful-mock.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/bubkoo/grunt-restful-mock/issues" 17 | }, 18 | "license": "MIT", 19 | "engines": { 20 | "node": ">= 0.8.0" 21 | }, 22 | "scripts": { 23 | "test": "grunt jshint" 24 | }, 25 | "dependencies": { 26 | "js-yaml": "^3.2.3", 27 | "restful-mock-server": "0.0.2" 28 | }, 29 | "devDependencies": { 30 | "grunt": "^0.4.5", 31 | "grunt-contrib-jshint": "^0.11.2" 32 | }, 33 | "peerDependencies": { 34 | "grunt": ">=0.4.0" 35 | }, 36 | "keywords": [ 37 | "gruntplugin", 38 | "mock", 39 | "restful", 40 | "connect", 41 | "API", 42 | "test", 43 | "rest" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /tasks/mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * __ __ ___ ____ _ __ 3 | * | \/ |/ _ \ / ___| |/ / 4 | * | |\/| | | | | | | ' / 5 | * | | | | |_| | |___| . \ 6 | * |_| |_|\___/ \____|_|\_\ 7 | * 8 | * Copyright (c) 2014-2016 bubkoo 9 | * Licensed under the MIT license. 10 | */ 11 | 12 | 'use strict'; 13 | 14 | var fs = require('fs'); 15 | var path = require('path'); 16 | var yaml = require('js-yaml'); 17 | var mockServer = require('restful-mock-server'); 18 | 19 | 20 | function getFilepath(filepath, cwd) { 21 | filepath = cwd ? path.join(cwd, filepath) : filepath; 22 | return path.join(process.cwd(), filepath); 23 | } 24 | 25 | function readfile(file) { 26 | // check for existence first 27 | if (!fs.existsSync(file)) { 28 | throw new Error('File: "' + file + '" doesn\'t exist'); 29 | } 30 | 31 | var ext = path.extname(file); 32 | 33 | // YAML file 34 | if (ext.match(/ya?ml/)) { 35 | var res = fs.readFileSync(file, 'utf8'); 36 | return yaml.safeLoad(res); 37 | } 38 | 39 | // JS / JSON / CoffeeScript 40 | if (ext.match(/json|js|coffee|ls/)) { 41 | if (require.cache[file]) { 42 | delete require.cache[file]; 43 | } 44 | return require(file); 45 | } 46 | 47 | // unknown 48 | throw new Error('File: "' + file + '" is an unsupported filetype'); 49 | } 50 | 51 | module.exports = function (grunt) { 52 | 53 | grunt.registerMultiTask('mock', 'Start a mock server.', function () { 54 | 55 | var self = this; 56 | var rules = {}; 57 | var _ = grunt.util._; 58 | var done = self.async(); 59 | var options = self.options({}); 60 | 61 | options.debug = grunt.option('debug') || options.debug === true; 62 | 63 | self.files.forEach(function (f) { 64 | 65 | f.src.filter(function (filepath) { 66 | 67 | filepath = getFilepath(filepath, f.cwd); 68 | 69 | if (!grunt.file.exists(filepath)) { 70 | grunt.log.warn('Source file "' + filepath + '" not found.'); 71 | return false; 72 | } else if (grunt.file.isFile(filepath)) { 73 | try { 74 | var result = readfile(filepath); 75 | if (_.isFunction(result)) { 76 | result = result(grunt); 77 | } 78 | _.merge(rules, result || {}); 79 | return true; 80 | } catch (err) { 81 | grunt.log.warn(err); 82 | return false; 83 | } 84 | } 85 | }); 86 | }); 87 | 88 | if (options.rules) { 89 | _.merge(options.rules, rules); 90 | } else { 91 | options.rules = rules; 92 | } 93 | 94 | 95 | // watch 96 | // ----- 97 | 98 | if (options.watch) { 99 | 100 | var files = ['Gruntfile.js', 'Gruntfile.coffee']; 101 | if (_.isArray(options.watch)) { 102 | files = files.concat(options.watch); 103 | } else if (_.isString(options.watch)) { 104 | files.push(options.watch); 105 | } 106 | 107 | if (self.data.src) { 108 | var cwd = self.data.cwd; 109 | var src = self.data.src; 110 | 111 | if (_.isArray(src)) { 112 | src.forEach(function (item) { 113 | files.push(path.join(cwd || '', item)); 114 | }); 115 | } else { 116 | files.push(path.join(cwd || '', src)); 117 | } 118 | } 119 | 120 | options.watch = files; 121 | } 122 | 123 | mockServer(options); 124 | }); 125 | }; 126 | --------------------------------------------------------------------------------