{"id":13763760,"url":"https://github.com/Meteor-Community-Packages/meteor-roles","last_synced_at":"2025-05-10T17:31:04.950Z","repository":{"id":6221488,"uuid":"7452866","full_name":"Meteor-Community-Packages/meteor-roles","owner":"Meteor-Community-Packages","description":"Authorization package for Meteor, compatible with built-in accounts packages","archived":false,"fork":false,"pushed_at":"2025-03-28T22:12:43.000Z","size":1576,"stargazers_count":922,"open_issues_count":32,"forks_count":170,"subscribers_count":34,"default_branch":"master","last_synced_at":"2025-05-07T17:05:32.567Z","etag":null,"topics":["hacktoberfest","meteor"],"latest_commit_sha":null,"homepage":"http://meteor-community-packages.github.io/meteor-roles/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Meteor-Community-Packages.png","metadata":{"files":{"readme":"README.md","changelog":"History.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["storytellercz","jankapunkt"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2013-01-05T07:09:20.000Z","updated_at":"2025-05-01T11:49:51.000Z","dependencies_parsed_at":"2024-03-07T20:23:09.094Z","dependency_job_id":"8424eb5e-a014-49c8-bbd4-d19690203918","html_url":"https://github.com/Meteor-Community-Packages/meteor-roles","commit_stats":{"total_commits":450,"total_committers":36,"mean_commits":12.5,"dds":0.6866666666666666,"last_synced_commit":"0a09132828e2affda7b12c651483d23a9a73e98e"},"previous_names":["alanning/meteor-roles"],"tags_count":55,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Meteor-Community-Packages%2Fmeteor-roles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Meteor-Community-Packages%2Fmeteor-roles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Meteor-Community-Packages%2Fmeteor-roles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Meteor-Community-Packages%2Fmeteor-roles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Meteor-Community-Packages","download_url":"https://codeload.github.com/Meteor-Community-Packages/meteor-roles/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253453212,"owners_count":21911061,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","meteor"],"created_at":"2024-08-03T15:00:57.914Z","updated_at":"2025-05-10T17:30:59.937Z","avatar_url":"https://github.com/Meteor-Community-Packages.png","language":"JavaScript","funding_links":["https://github.com/sponsors/storytellercz","https://github.com/sponsors/jankapunkt","https://github.com/sponsors/StorytellerCZ"],"categories":["Users and Authentication","Packages"],"sub_categories":[],"readme":"meteor-roles v3\n===============\n[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)\n![GitHub](https://img.shields.io/github/license/Meteor-Community-Packages/meteor-roles)\n[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)\n[![CodeQL](https://github.com/Meteor-Community-Packages/meteor-roles/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/Meteor-Community-Packages/meteor-roles/actions/workflows/github-code-scanning/codeql)\n![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/Meteor-Community-Packages/meteor-roles?label=latest\u0026sort=semver)\n[![](https://img.shields.io/badge/semver-2.0.0-success)](http://semver.org/spec/v2.0.0.html) \n\nAuthorization package for Meteor - compatible with built-in accounts package.\n\nThere are also older versions of this package:\n* [v1](https://github.com/Meteor-Community-Packages/meteor-roles/tree/v1)\n* [v2](https://github.com/Meteor-Community-Packages/meteor-roles/tree/v2)\n* [v3](https://github.com/Meteor-Community-Packages/meteor-roles/tree/v3)\n\n\u003cbr /\u003e\n\n\u003ca id=\"roles-toc\" name=\"roles-toc\"\u003e\u003c/a\u003e\n## Table of Contents\n- [meteor-roles v3](#meteor-roles-v3)\n  - [Table of Contents](#table-of-contents)\n  - [Contributors](#contributors)\n  - [Authorization](#authorization)\n  - [Permissions vs roles  (or What's in a name...)](#permissions-vs-roles--or-whats-in-a-name)\n  - [What are \"scopes\"?](#what-are-scopes)\n  - [Changes to default Meteor behavior](#changes-to-default-meteor-behavior)\n  - [Installing](#installing)\n  - [Migration to 3.0](#migration-to-30)\n    - [Changes between 2.x and 3.0](#changes-between-2x-and-30)\n  - [Usage Examples](#usage-examples)\n  - [API Docs](#api-docs)\n  - [Example Apps](#example-apps)\n  - [Contributions, development and tests](#contributions-development-and-tests)\n\n\u003cbr /\u003e\n\n\n\u003ca id=\"roles-contributors\" name=\"roles-contributors\"\u003e\u003c/a\u003e\n## Contributors\n\nThanks to:\n\n  * [@storytellerCZ](https://github.com/sponsors/StorytellerCZ)\n  * [@jankapunkt](https://github.com/sponsors/jankapunkt)\n  * [@alanning](https://github.com/alanning)\n  * [@mitar](https://github.com/mitar)\n  * [@challett](https://github.com/challett)\n  * [@ianserlin](https://github.com/ianserlin)\n  * [@leebenson](https://github.com/leebenson)\n  * [@pward123](https://github.com/pward123)\n  * [@dandv](https://github.com/dandv)\n  * [@aldeed](https://github.com/aldeed)\n  * [@kevb](https://github.com/kevb)\n  * [@zimme](https://github.com/zimme)\n  * [@danieljonce](https://github.com/danieljonce)\n  * [@pascoual](https://github.com/pascoual)\n  * [@nickmoylan](https://github.com/nickmoylan)\n  * [@mcrider](https://github.com/mcrider)\n  * [@simonsimcity](https://github.com/simonsimcity)\n\n\u003cbr /\u003e\n\n\n\u003ca id=\"roles-authorization\" name=\"roles-authorization\"\u003e\u003c/a\u003e\n## Authorization\n\nThis package lets you attach roles to a user, which you can then check against later when deciding whether to grant\naccess to Meteor methods or publish data. The core concept is very simple, essentially you are creating an assignment\nof roles to a user and then checking for the existence of those roles later. This package provides helper methods\nto make the process of adding, removing, and verifying those roles easier.\n\n\u003cbr /\u003e\n\n\u003ca id=\"roles-naming\" name=\"roles-naming\"\u003e\u003c/a\u003e\n## Permissions vs roles  (or What's in a name...)\n\nAlthough the name of this package is `roles`, you can define your **roles**, **scopes** or **permissions** however you like.\nThey are essentially just tags that you assign to a user and which you can check upon later.\n\nYou can have traditional roles like, `admin` or `webmaster`, or you can assign more granular permissions such\nas, `view-secrets`, `users.view`, or `users.manage`.  Often, more granular is actually better because you are\nable to handle all those pesky edge cases that come up in real-life usage without creating a ton of higher-level\n`roles`.  With the `roles` package, it's all just a role object.\n\nRoles can be put into a **hierarchy**.\nRoles can have multiple parents and can be children (subroles) of multiple roles.\nIf a parent role is set to the user, all its descendants are also applying.\nYou can use this to create \"super roles\" aggregating permissions all the way through the bottom of the tree.\nFor example, you could name two top-level roles `user` and `admin` and then you could use your second-level roles as permissions and name them `USERS_VIEW`, `POST_EDIT`, and similar.\nThen you could set `admin` role as parent role for `USERS_VIEW` and `POST_EDIT`, while `user` would be parent\nonly of the `POST_EDIT` role. You can then assign `user` and `admin` roles to your users. And if you need to\nchange permissions later for the whole role, just add or remove children roles. You can create roles from this example\nwith:\n\n```javascript\nimport { Roles } from 'meteor/alanning:roles';\n\nRoles.createRoleAsync('user');\nRoles.createRoleAsync('admin');\nRoles.createRoleAsync('USERS_VIEW');\nRoles.createRoleAsync('POST_EDIT');\nRoles.addRolesToParentAsync('USERS_VIEW', 'admin');\nRoles.addRolesToParentAsync('POST_EDIT', 'admin');\nRoles.addRolesToParentAsync('POST_EDIT', 'user');\n```\n\n\u003cbr /\u003e\n\n\u003ca id=\"roles-scopes\" name=\"roles-scopes\"\u003e\u003c/a\u003e\n## What are \"scopes\"?\n\nSometimes it is useful to let a user have independent sets of roles. The `roles` package calls these independent\nsets \"scopes\" for lack of a better term. You can use them to represent various communities inside your\napplication. Or maybe your application supports [multiple tenants](https://en.wikipedia.org/wiki/Multitenancy).\nYou can put each of those tenants into their own scope. Alternatively, you can use scopes to represent\nvarious resources you have.\n\nUsers can have both scope roles assigned, and global roles. Global roles are in effect for all scopes.\nBut scopes are independent of each other. Users can have one set of roles in scope A and another set\nof roles in scope B. Let's go through an example of this using soccer/football teams as scopes.\n\n```javascript\nRoles.addUsersToRolesAsync(joesUserId, ['manage-team','schedule-game'], 'manchester-united.com');\nRoles.addUsersToRolesAsync(joesUserId, ['player','goalie'], 'real-madrid.com');\n\nRoles.userIsInRoleAsync(joesUserId, 'manage-team', 'manchester-united.com'); // true\nRoles.userIsInRoleAsync(joesUserId, 'manage-team', 'real-madrid.com'); // false\n```\n\nIn this example, we can see that Joe manages Manchester United and plays for Real Madrid. By using scopes, we can\nassign roles independently and make sure that they don't get mixed up between scopes.\n\nNow, let's take a look at how to use the global roles. Say we want to give Joe permission to do something across\nall of our scopes. That is what the global roles are for:\n\n```javascript\nRoles.addUsersToRolesAsync(joesUserId, 'super-admin', null); // Or you could just omit the last argument.\n\nconst isInRole = await Roles.userIsInRoleAsync(joesUserId, ['manage-team', 'super-admin'], 'real-madrid.com')\nif (isInRole) {\n  // True! Even though Joe doesn't manage Real Madrid, he has\n  // a 'super-admin' global role so this check succeeds.\n}\n```\n\n\u003cbr /\u003e\n\n\u003ca id=\"roles-changes\" name=\"roles-changes\"\u003e\u003c/a\u003e\n## Changes to default Meteor behavior\n\n  1. A new collection `Meteor.roleAssignment` contains the information about which role has been assigned to which user.\n  1. A new collection `Meteor.roles` contains a global list of defined role names.\n  1. All existing roles are automatically published to the client.\n\n\u003cbr /\u003e\n\n\u003ca id=\"roles-installing\" name=\"roles-installing\"\u003e\u003c/a\u003e\n## Installing\n\n1. Add one of the built-in accounts packages so the `Meteor.users` collection exists. From a command prompt:\n    ```bash\n    meteor add accounts-password\n    ```\n\n1. Add this package to your project. From a command prompt:\n    ```bash\n    meteor add alanning:roles\n    ```\n\n1. Publish the role assignments you need to the client:\n    ```js\n    Meteor.publish(null, function () {\n      if (this.userId) {\n        return Meteor.roleAssignment.find({ 'user._id': this.userId });\n      } else {\n        this.ready()\n      }\n    })\n    ```\n\n1. Run your application:\n    ```bash\n    meteor\n    ```\n\n\u003cbr /\u003e\n\n\u003ca id=\"roles-migration\" name=\"roles-migration\"\u003e\u003c/a\u003e\n## Migration to 4.0\n\nIf you are currently using this package in a version older than 3.6, please upgrade to 3.6 and follow all the steps described for previous major version upgrades.\nMake sure to stay on 3.x until you have run the migration scripts as those are no longer available in version 4.\n\nBefore upgrading to version 4 make sure that all your server side roles call use the async versions of the functions.\n\nHere is a full list of new async functions:\n  * createRoleAsync\n  * deleteRoleAsync\n  * renameRoleAsync\n  * addRolesToParentAsync\n  * removeRolesFromParentAsync\n  * addUsersToRolesAsync\n  * setUserRolesAsync\n  * removeUsersFromRolesAsync\n  * userIsInRoleAsync\n  * getRolesForUserAsync\n  * getUsersInRoleAsync\n  * getGroupsForUserAsync\n  * getScopesForUserAsync\n  * renameScopeAsync\n  * removeScopeAsync\n  * isParentOfAsync\n\n\u003e NOTE: The sync version of these functions are still available on the client.\n\n## Migration to 3.0\n\nIf you are currently using this package in a version older than 2.x, please upgrade to 2.0 by running the migration script required there: https://github.com/Meteor-Community-Packages/meteor-roles/tree/v2#migration-to-20\n\nIn meteor-roles 3.0, functions are mostly backwards compatible with 2.x, but roles are stored differently in the database. Please take a backup of the `users` collection before migrating. To migrate the database to the new schema, run `Meteor._forwardMigrate2()` on the server:\n\n```bash\nmeteor shell\n\u003e Package['alanning:roles'].Roles._forwardMigrate2()\n```\n\nIn case something fails, there is also a script available for rolling back the changes. But be warned that a backward migration takes a magnitude longer than a forward migration. To migrate the database back to the old schema, run `Meteor._backwardMigrate2()` on the server:\n\n```bash\nmeteor shell\n\u003e Package['alanning:roles'].Roles._backwardMigrate2()\n```\n\n### Changes between 2.x and 3.0\n\nHere is the list of important changes between meteor-roles 2.x and 3.0 to consider when migrating to 3.0:\n\n* Role assignments have been moved from the `users` documents to a separate collection called `role-assignment`, available at `Meteor.roleAssignment`.\n* Role assignments are not published automatically. If you want all your role-assignments to be published automatically, please include the following code:\n```js\nMeteor.publish(null, function () {\n  if (this.userId) {\n    return Meteor.roleAssignment.find({ 'user._id': this.userId });\n  } else {\n    this.ready()\n  }\n})\n```\n* [BC] The behavior of `getRolesForUser()` used with the option `fullObjects` changed. [In case you need the old behavior ...](https://github.com/Meteor-Community-Packages/meteor-roles/pull/276/commits/41d2ed493852f21cf508b5b0b76e4f8a09ae8f5c#diff-b2ab7f7879884835e55802c6a35ee27e)\n* Added option `anyScope` to `removeUsersFromRoles()`\n* Add option `onlyScoped` to `getRolesForUser()` to allow limiting the result to only scoped permissions\n* All functions (excepted for those listed above) work with 2.x arguments, but in 3.x accept extra arguments and/or options.\n* Details and reasoning can be found in [#276](https://github.com/Meteor-Community-Packages/meteor-roles/pull/276)\n\n\u003cbr /\u003e\n\n\n\u003ca id=\"roles-usage\" name=\"roles-usage\"\u003e\u003c/a\u003e\n## Usage Examples\n\n\u003cbr /\u003e\n\nHere are some potential use cases:\n\n\u003cbr /\u003e\n\n-- **Server** --\n\n\nAdd users to roles:\n```js\nvar users = [\n      {name:\"Normal User\",email:\"normal@example.com\",roles:[]},\n      {name:\"View-Secrets User\",email:\"view@example.com\",roles:['view-secrets']},\n      {name:\"Manage-Users User\",email:\"manage@example.com\",roles:['manage-users']},\n      {name:\"Admin User\",email:\"admin@example.com\",roles:['admin']}\n    ];\n\nfor (const user of users) {\n  var id;\n\n  id = await Accounts.createUserAsync({\n    email: user.email,\n    password: \"apple1\",\n    profile: { name: user.name }\n  });\n\n  const count = await Meteor.roleAssignment.coundDocuments({ 'user._id': id })\n  if (count === 0) {\n    import { Roles } from 'meteor/alanning:roles';\n    \n    for (const role of user.roles)  {\n      Roles.createRoleAsync(role, {unlessExists: true});\n    }\n    // Need _id of existing user record so this call must come after `Accounts.createUser`.\n    Roles.addUsersToRolesAsync(id, user.roles);\n  }\n\n}\n```\n\n\u003cbr /\u003e\nNote that the `Roles.addUsersToRoles` call needs to come _after_ `Accounts.createUser` or else\nthe roles package won't be able to find the user record (since it hasn't been created yet).\nYou can use `postSignUpHook` to assign roles when using\n[user accounts package](https://github.com/meteor-useraccounts/core).\nThis SO answer gives more detail: http://stackoverflow.com/a/22650399/219238\n\n\u003cbr /\u003e\n\nCheck user roles before publishing sensitive data:\n```js\n// server/publish.js\nimport { Roles } from 'meteor/alanning:roles'\n\n// Give authorized users access to sensitive data by scope\nMeteor.publish('secrets', async function (scope) {\n  check(scope, String);\n\n  const isInRole = await Roles.userIsInRoleAsync(this.userId, ['view-secrets','admin'], scope)\n  if (isInRole) {\n    \n    return Meteor.secrets.find({scope: scope});\n    \n  } else {\n\n    // user not authorized. do not publish secrets\n    this.stop();\n    return;\n\n  }\n});\n```\n\n\u003cbr /\u003e\n\nPrevent non-authorized users from creating new users:\n```js\nAccounts.validateNewUser(async function (user) {\n  import { Roles } from 'meteor/alanning:roles'\n  \n  var loggedInUser = Meteor.user();\n\n  const isInRole = await Roles.userIsInRoleAsync(loggedInUser, ['admin','manage-users'])\n  if () {\n    return true;\n  }\n\n  throw new Meteor.Error('unauthorized', \"Not authorized to create new users\");\n});\n```\n\n\u003cbr /\u003e\n\nPrevent access to certain functionality, such as deleting a user:\n```js\n// server/userMethods.js\nimport { Roles } from 'meteor/alanning:roles'\n\nMeteor.methods({\n  /**\n   * Revokes roles for a user in a specific scope.\n   * \n   * @method revokeUser\n   * @param {String} targetUserId ID of user to revoke roles for.\n   * @param {String} scope Company to update roles for.\n   */\n  revokeUser: async function (targetUserId, scope) {\n    check(targetUserId, String);\n    check(scope, String);\n  \n    const loggedInUser = Meteor.user();\n\n    const isInRole = await Roles.userIsInRole(loggedInUser, ['manage-users', 'support-staff'], scope);\n    if (!loggedInUser || !isInRole) {\n      throw new Meteor.Error('access-denied', \"Access denied\")\n    }\n\n    // remove roles for target scope\n    Roles.setUserRolesAsync(targetUserId, [], scope)\n  }\n})\n```\n\n\u003cbr /\u003e\n\nManage a user's roles:\n```js\n// server/userMethods.js\nimport { Roles } from 'meteor/alanning:roles'\n\nMeteor.methods({\n  /**\n   * Update a user's roles.\n   *\n   * @param {Object} targetUserId Id of user to update.\n   * @param {Array} roles User's new roles.\n   * @param {String} scope Company to update roles for.\n   */\n  updateRoles: async function (targetUserId, roles, scope) {\n    check(targetUserId, String);\n    check(roles, [String]);\n    check(scope, String);\n\n    const loggedInUser = Meteor.user();\n\n    const isInRole = await Roles.userIsInRole(loggedInUser, ['manage-users', 'support-staff'], scope);\n    if (!loggedInUser || !isInRole) {\n      throw new Meteor.Error('access-denied', \"Access denied\");\n    }\n\n    Roles.setUserRolesAsync(targetUserId, roles, scope);\n  }\n})\n```\n\n\u003cbr /\u003e\n\n-- **Client** --\n\nClient JavaScript does not by default have access to all the same Roles functions as the server unless you publish\nthese role-assignments. In addition, Blaze will have the addition of a `isInRole` handlebars helper which is\nautomatically registered by the Roles package.\n\nAs with all Meteor applications, client-side checks are a convenience, rather than a true security implementation \nsince Meteor bundles the same client-side code to all users. Providing the Roles functions client-side also allows\nfor latency compensation during Meteor method calls. Roles functions which modify the database should not be\ncalled directly, but inside the Meteor methods.\n\nNOTE: Any sensitive data needs to be controlled server-side to prevent unwanted disclosure. To be clear, Meteor sends\nall templates, client-side JavaScript, and published data to the client's browser. This is by design and is a good thing.\nThe following example is just sugar to help improve the user experience for normal users. Those interested in seeing\nthe 'admin_nav' template in the example below will still be able to do so by manually reading the bundled `client.js`\nfile. It won't be pretty, but it is possible. But this is not a problem as long as the actual data is restricted server-side.\n\nTo check for global roles or when not using scopes:\n\n```handlebars\n\u003c!-- client/myApp.html --\u003e\n\n\u003ctemplate name=\"header\"\u003e\n  ... regular header stuff\n  {{#if isInRole 'admin'}}\n    {{\u003e admin_nav}}  \n  {{/if}}\n  {{#if isInRole 'admin,editor'}}\n    {{\u003e editor_stuff}}\n  {{/if}}\n\u003c/template\u003e\n```\n\nTo check for roles when using scopes:\n\n```handlebars\n\u003c!-- client/myApp.html --\u003e\n\n\u003ctemplate name=\"header\"\u003e\n  ... regular header stuff\n  {{#if isInRole 'admin,editor' 'group1'}}\n    {{\u003e editor_stuff}}  \n  {{/if}}\n\u003c/template\u003e\n```\n\n\u003cbr /\u003e\n\n\n\u003ca id=\"roles-docs\" name=\"roles-docs\"\u003e\u003c/a\u003e\n## API Docs\n\nOnline API docs found here: https://meteor-community-packages.github.io/meteor-roles/\n\nAPI docs generated using [YUIDoc](https://yui.github.io/yuidoc/)\n\nTo re-generate documentation:\n  1. install YUIDoc\n  2. `cd meteor-roles`\n  3. `yuidoc`\n\nTo serve documentation locally:\n  1. install YUIDoc\n  2. `cd meteor-roles`\n  3. `yuidoc --server 5000`\n  4. point browser at http://localhost:5000/\n\n\n\u003cbr /\u003e\n\n\n\u003ca id=\"roles-example-apps\" name=\"roles-example-apps\"\u003e\u003c/a\u003e\n## Example Apps\n\nThe `examples` directory contains Meteor apps which show off the following features:\n* Server-side publishing with authorization to secure sensitive data\n* Client-side navigation with link visibility based on user roles\n* 'Sign-in required' app with up-front login page using `accounts-ui`\n* Client-side routing\n\n  1. `git clone https://github.com/Meteor-Community-Packages/meteor-roles.git`\n  2. choose an example, eg.\n    * `cd meteor-roles/examples/iron-router` or\n    * `cd meteor-roles/examples/flow-router`\n  3. `meteor`\n  4. point browser to `http://localhost:3000`\n\n\u003cbr /\u003e\n\n\n\u003ca id=\"roles-testing\" name=\"roles-testing\"\u003e\u003c/a\u003e\n## Contributions, development and tests\n\nPlease read our [contribution guidelines](./CONTRIBUTING.md),\nwhich also describes how to set up and run the linter and tests.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMeteor-Community-Packages%2Fmeteor-roles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FMeteor-Community-Packages%2Fmeteor-roles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMeteor-Community-Packages%2Fmeteor-roles/lists"}