@payfit/audit-log 2.0.0-rc.4 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.prettierrc +5 -0
  2. package/CHANGELOG.md +13 -0
  3. package/dist/index.d.ts +7 -7
  4. package/dist/index.js +44 -1
  5. package/dist/index.js.map +1 -0
  6. package/dist/transport/generated/api/default-api.d.ts +7 -7
  7. package/dist/transport/generated/api/default-api.js +156 -0
  8. package/dist/transport/generated/api/default-api.js.map +1 -0
  9. package/dist/transport/generated/api.js +26 -0
  10. package/dist/transport/generated/api.js.map +1 -0
  11. package/dist/transport/generated/base.d.ts +2 -2
  12. package/dist/transport/generated/base.js +64 -0
  13. package/dist/transport/generated/base.js.map +1 -0
  14. package/dist/transport/generated/configuration.d.ts +2 -22
  15. package/dist/transport/generated/configuration.js +27 -0
  16. package/dist/transport/generated/configuration.js.map +1 -0
  17. package/dist/transport/generated/index.d.ts +0 -1
  18. package/dist/transport/generated/index.js +27 -0
  19. package/dist/transport/generated/index.js.map +1 -0
  20. package/dist/transport/generated/model/index.js +14 -0
  21. package/dist/transport/generated/model/index.js.map +1 -0
  22. package/dist/transport/generated/model/inline-object.d.ts +20 -20
  23. package/dist/transport/generated/model/inline-object.js +15 -0
  24. package/dist/transport/generated/model/inline-object.js.map +1 -0
  25. package/dist/transport/implementation.d.ts +2 -1
  26. package/dist/transport/implementation.js +38 -0
  27. package/dist/transport/implementation.js.map +1 -0
  28. package/dist/transport/interface.d.ts +4 -4
  29. package/dist/transport/interface.js +3 -0
  30. package/dist/transport/interface.js.map +1 -0
  31. package/openapi-codegen-config.yml +6 -0
  32. package/openapitools.json +6 -0
  33. package/package.json +21 -45
  34. package/playground/index.ts +37 -0
  35. package/src/index.ts +199 -0
  36. package/src/transport/generated/.openapi-generator/VERSION +1 -0
  37. package/src/transport/generated/.openapi-generator-ignore +23 -0
  38. package/src/transport/generated/api/default-api.ts +132 -0
  39. package/src/transport/generated/api.ts +17 -0
  40. package/src/transport/generated/base.ts +70 -0
  41. package/src/transport/generated/configuration.ts +75 -0
  42. package/src/transport/generated/git_push.sh +58 -0
  43. package/src/transport/generated/index.ts +16 -0
  44. package/src/transport/generated/model/index.ts +1 -0
  45. package/src/transport/generated/model/inline-object.ts +144 -0
  46. package/src/transport/implementation.ts +27 -0
  47. package/src/transport/interface.ts +28 -0
  48. package/tsconfig.json +25 -0
  49. package/tslint.json +12 -0
  50. package/dist/index.mjs +0 -149
  51. package/dist/playground.d.ts +0 -1
  52. package/dist/transport/generated/common.d.ts +0 -65
@@ -20,119 +20,119 @@ export interface InlineObject {
20
20
  * @type {string}
21
21
  * @memberof InlineObject
22
22
  */
23
- 'id': string;
23
+ id: string;
24
24
  /**
25
25
  * Audit log event country code
26
26
  * @type {string}
27
27
  * @memberof InlineObject
28
28
  */
29
- 'countryCode'?: string;
29
+ countryCode?: string;
30
30
  /**
31
31
  * Audit log event payload
32
32
  * @type {object}
33
33
  * @memberof InlineObject
34
34
  */
35
- 'payload'?: object;
35
+ payload?: object;
36
36
  /**
37
37
  * id of the payfiter who performed the audit log action, if signed as.
38
38
  * @type {string}
39
39
  * @memberof InlineObject
40
40
  */
41
- 'internalId'?: string;
41
+ internalId?: string;
42
42
  /**
43
43
  * Tells whether the logged action has been made by the system
44
44
  * @type {boolean}
45
45
  * @memberof InlineObject
46
46
  */
47
- 'isSystem'?: boolean;
47
+ isSystem?: boolean;
48
48
  /**
49
49
  * Audit log event namespace (business related)
50
50
  * @type {string}
51
51
  * @memberof InlineObject
52
52
  */
53
- 'namespace': string;
53
+ namespace: string;
54
54
  /**
55
55
  * Audit log event namespace (technical related)
56
56
  * @type {string}
57
57
  * @memberof InlineObject
58
58
  */
59
- 'service': string;
59
+ service: string;
60
60
  /**
61
61
  * Audit log event type
62
62
  * @type {string}
63
63
  * @memberof InlineObject
64
64
  */
65
- 'event': string;
65
+ event: string;
66
66
  /**
67
67
  * Audit log target
68
68
  * @type {string}
69
69
  * @memberof InlineObject
70
70
  */
71
- 'target': string;
71
+ target: string;
72
72
  /**
73
73
  * Audit log event environment, only required when in todd context
74
74
  * @type {string}
75
75
  * @memberof InlineObject
76
76
  */
77
- 'environment'?: string;
77
+ environment?: string;
78
78
  /**
79
79
  * holdingId of the account performing the event
80
80
  * @type {string}
81
81
  * @memberof InlineObject
82
82
  */
83
- 'holdingId'?: string;
83
+ holdingId?: string;
84
84
  /**
85
85
  * companyId of the account performing the event
86
86
  * @type {string}
87
87
  * @memberof InlineObject
88
88
  */
89
- 'companyId'?: string;
89
+ companyId?: string;
90
90
  /**
91
91
  * employeeId of the account performing the event
92
92
  * @type {string}
93
93
  * @memberof InlineObject
94
94
  */
95
- 'employeeId'?: string;
95
+ employeeId?: string;
96
96
  /**
97
97
  * individualId of the account performing the event
98
98
  * @type {string}
99
99
  * @memberof InlineObject
100
100
  */
101
- 'individualId'?: string;
101
+ individualId?: string;
102
102
  /**
103
103
  * roleId of the account performing the event
104
104
  * @type {string}
105
105
  * @memberof InlineObject
106
106
  */
107
- 'roleId'?: string;
107
+ roleId?: string;
108
108
  /**
109
109
  * userIp of the account performing the event
110
110
  * @type {string}
111
111
  * @memberof InlineObject
112
112
  */
113
- 'userIp'?: string;
113
+ userIp?: string;
114
114
  /**
115
115
  * specialRole of the account performing the event
116
116
  * @type {string}
117
117
  * @memberof InlineObject
118
118
  */
119
- 'specialRole'?: string;
119
+ specialRole?: string;
120
120
  /**
121
121
  * email of the account performing the event
122
122
  * @type {string}
123
123
  * @memberof InlineObject
124
124
  */
125
- 'email'?: string;
125
+ email?: string;
126
126
  /**
127
127
  * Audit log event scope
128
128
  * @type {object}
129
129
  * @memberof InlineObject
130
130
  */
131
- 'scope'?: object;
131
+ scope?: object;
132
132
  /**
133
133
  * Audit log event timestamp unix format
134
134
  * @type {number}
135
135
  * @memberof InlineObject
136
136
  */
137
- 'timestamp': number;
137
+ timestamp: number;
138
138
  }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ // tslint:disable
3
+ /**
4
+ * Audit Logs
5
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
6
+ *
7
+ * The version of the OpenAPI document: 1.0.0
8
+ * Contact: damian.bursztyn@payfit.com
9
+ *
10
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
11
+ * https://openapi-generator.tech
12
+ * Do not edit the class manually.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ //# sourceMappingURL=inline-object.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-object.js","sourceRoot":"","sources":["../../../../src/transport/generated/model/inline-object.ts"],"names":[],"mappings":";AAAA,iBAAiB;AACjB;;;;;;;;;;GAUG"}
@@ -1,8 +1,9 @@
1
+ import { DefaultApi } from './generated';
1
2
  import { InlineObject } from './generated/model';
2
3
  import { IAuditLogTransport } from './interface';
3
4
  export declare class Transport implements IAuditLogTransport {
4
5
  protected apiUrl: string;
5
- private api;
6
+ api: DefaultApi;
6
7
  constructor(apiUrl: string);
7
8
  create(auditLogData: InlineObject): Promise<void>;
8
9
  }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.Transport = void 0;
13
+ const generated_1 = require("./generated");
14
+ class Transport {
15
+ constructor(apiUrl) {
16
+ this.apiUrl = apiUrl;
17
+ this.api = new generated_1.DefaultApi({
18
+ basePath: this.apiUrl,
19
+ });
20
+ }
21
+ create(auditLogData) {
22
+ var _a;
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ try {
25
+ yield this.api.v1LogPost(auditLogData);
26
+ }
27
+ catch (err) {
28
+ if (!err.isAxiosError) {
29
+ throw err;
30
+ }
31
+ const message = ((_a = err.response) === null || _a === void 0 ? void 0 : _a.data) || err.message;
32
+ throw new Error(message);
33
+ }
34
+ });
35
+ }
36
+ }
37
+ exports.Transport = Transport;
38
+ //# sourceMappingURL=implementation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"implementation.js","sourceRoot":"","sources":["../../src/transport/implementation.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAwC;AAKxC,MAAa,SAAS;IAGpB,YAAsB,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;QAClC,IAAI,CAAC,GAAG,GAAG,IAAI,sBAAU,CAAC;YACxB,QAAQ,EAAE,IAAI,CAAC,MAAM;SACtB,CAAC,CAAA;IACJ,CAAC;IAEK,MAAM,CAAC,YAA0B;;;YACrC,IAAI;gBACF,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,YAAY,CAAC,CAAA;aACvC;YAAC,OAAO,GAAG,EAAE;gBACZ,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE;oBACrB,MAAM,GAAG,CAAA;iBACV;gBAED,MAAM,OAAO,GAAG,OAAA,GAAG,CAAC,QAAQ,0CAAE,IAAI,KAAI,GAAG,CAAC,OAAO,CAAA;gBACjD,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;aACzB;;KACF;CACF;AArBD,8BAqBC"}
@@ -1,11 +1,11 @@
1
- export interface IAuditLogCreationData {
1
+ export declare type IAuditLogCreationData = {
2
2
  id: string;
3
3
  timestamp: number;
4
4
  environment: string;
5
5
  service: string;
6
- scope?: Record<string, unknown>;
6
+ scope?: object;
7
7
  countryCode?: string;
8
- payload?: Record<string, unknown>;
8
+ payload?: object;
9
9
  internalId?: string;
10
10
  isSystem?: boolean;
11
11
  namespace: string;
@@ -19,7 +19,7 @@ export interface IAuditLogCreationData {
19
19
  userIp?: string;
20
20
  specialRole?: string;
21
21
  email?: string;
22
- }
22
+ };
23
23
  export interface IAuditLogTransport {
24
24
  create: (data: IAuditLogCreationData) => Promise<void>;
25
25
  }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/transport/interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ withSeparateModelsAndApi: true
2
+ apiPackage: 'api'
3
+ modelPackage: 'model'
4
+ enumPropertyNaming: UPPERCASE
5
+ modelPropertyNaming: camelCase
6
+ withoutPrefixEnums: true
@@ -0,0 +1,6 @@
1
+ {
2
+ "spaces": 2,
3
+ "generator-cli": {
4
+ "version": "4.3.1"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,58 +1,34 @@
1
1
  {
2
2
  "name": "@payfit/audit-log",
3
- "version": "2.0.0-rc.4",
4
- "repository": {
5
- "type": "git",
6
- "url": "https://github.com/PayFit/audit-logs.git",
7
- "directory": "sdk"
8
- },
3
+ "version": "2.0.1",
9
4
  "description": "Library to handle audit logs at Payfit",
10
5
  "main": "dist/index.js",
11
- "module": "dist/index.mjs",
12
- "types": "dist/index.d.ts",
13
6
  "scripts": {
14
- "generate": "openapi-generator-cli generate -i ../pkg/broker/v1/swagger/openapi.json -g typescript-axios -o src/transport/generated -c openapi-codegen-config.yml",
15
- "build": "rimraf dist && vite build",
16
- "prepack": "npm run build",
17
- "lint": "eslint .",
18
- "start:playground": "ts-node src/playground.ts",
19
- "format:check": "prettier --check .",
20
- "format:fix": "prettier --write .",
21
- "release": "semantic-release --noCi"
7
+ "test": "echo \"Error: no test specified\" && exit 1",
8
+ "type:check": "tsc --noEmit",
9
+ "codegen:sdk": "openapi-generator-cli generate -i ../pkg/broker/v1/swagger/openapi.json -g typescript-axios -o src/transport/generated -c openapi-codegen-config.yml",
10
+ "build": "npm run codegen:sdk && rimraf dist && tsc",
11
+ "prepublishOnly": "npm run build",
12
+ "start:playground": "ts-node playground/index.ts"
22
13
  },
23
14
  "author": "",
24
15
  "license": "ISC",
25
16
  "dependencies": {
26
- "axios": "^1.0.0",
17
+ "axios": "0.24.0",
27
18
  "lodash": "^4.17.20",
28
- "uuid": "^9.0.0"
19
+ "uuid": "^7.0.3"
29
20
  },
30
21
  "devDependencies": {
31
- "@openapitools/openapi-generator-cli": "2.7.0",
32
- "@payfit/browserslist-config": "1.1.1",
33
- "@payfit/eslint-config-node": "1.1.0",
34
- "@payfit/semantic-release-package-config": "6.13.0",
35
- "@rollup/plugin-typescript": "11.1.5",
36
- "@tsconfig/node18": "18.2.2",
37
- "@tsconfig/strictest": "2.0.2",
38
- "@types/lodash": "4.14.202",
39
- "@types/node": "20.10.6",
40
- "@types/uuid": "9.0.7",
41
- "@typescript-eslint/eslint-plugin": "6.17.0",
42
- "builtin-modules": "3.3.0",
43
- "eslint": "8.56.0",
44
- "prettier": "3.1.1",
45
- "rimraf": "5.0.5",
46
- "semantic-release": "22.0.12",
47
- "ts-node": "10.9.2",
48
- "typescript": "5.3.3",
49
- "vite": "4.5.1"
50
- },
51
- "peerDependencies": {},
52
- "publishConfig": {
53
- "registry": "https://registry.npmjs.org/"
54
- },
55
- "files": [
56
- "dist"
57
- ]
22
+ "@openapitools/openapi-generator-cli": "2.4.18",
23
+ "@types/lodash": "4.14.177",
24
+ "@types/node": "16.11.12",
25
+ "@types/uuid": "3.4.10",
26
+ "prettier": "2.5.1",
27
+ "rimraf": "3.0.2",
28
+ "ts-node": "10.4.0",
29
+ "ts-node-dev": "1.1.8",
30
+ "tslint": "5.20.1",
31
+ "tslint-config-prettier": "1.18.0",
32
+ "typescript": "3.9.10"
33
+ }
58
34
  }
@@ -0,0 +1,37 @@
1
+ import { AuditLogger } from '../src'
2
+
3
+ const logger = new AuditLogger({
4
+ apiUrl: 'http://localhost:8090',
5
+ environment: 't-hello-audit',
6
+ service: 'auth',
7
+ })
8
+
9
+ async function main() {
10
+ await logger.log({
11
+ event: 'update',
12
+ target: 'user_settings',
13
+ namespace: 'auth',
14
+ payload: {
15
+ isTest: true,
16
+ },
17
+ account: {
18
+ internalId: 'an internal id',
19
+ individualId: 'an individual id',
20
+ employeeId: 'an employee id',
21
+ holdingId: 'a holding id',
22
+ companyId: 'a company id',
23
+ roleId: 'a role id',
24
+ userIp: '127.0.0.1',
25
+ countryCode: 'FR',
26
+ email: 'truc@ok.fr',
27
+ },
28
+ scope: {
29
+ employeeId: 'an employee id',
30
+ companyId: 'a company id',
31
+ },
32
+ })
33
+ }
34
+
35
+ main().catch((err) => {
36
+ console.log('Error:', err)
37
+ })
package/src/index.ts ADDED
@@ -0,0 +1,199 @@
1
+ import { v4 as uuidV4 } from 'uuid'
2
+ import { IAuditLogTransport } from './transport/interface'
3
+ import { Transport } from './transport/implementation'
4
+
5
+ /**
6
+ * The allowed Namespaces for the AuditLog
7
+ */
8
+ export type AuditNamespaces =
9
+ | 'employee'
10
+ | 'company'
11
+ | 'permissions'
12
+ | 'absence'
13
+ | 'expense'
14
+ | 'advancePayment'
15
+ | 'auth'
16
+ | 'billing'
17
+ | 'modules'
18
+ | 'integrations'
19
+ | 'partnerApi'
20
+ | 'donation'
21
+ | 'notificationSettings'
22
+ | 'people'
23
+ | 'subscriptions'
24
+
25
+ /**
26
+ * The allowed targets for the AuditLog
27
+ */
28
+ export type AuditTargets =
29
+ | 'reporting'
30
+ | 'employee'
31
+ | 'employee.settings'
32
+ | 'employee-request'
33
+ | 'company.billings'
34
+ | 'company.variables'
35
+ | 'company.settings'
36
+ | 'company.emails'
37
+ | 'company.addons'
38
+ | 'company.plans'
39
+ | 'user.role'
40
+ | 'user.account'
41
+ | 'user.primaryEmail'
42
+ | 'user.secondaryEmail'
43
+ | 'user.locale'
44
+ | 'user.notificationSettings'
45
+ | 'role'
46
+ | 'team'
47
+ | 'department'
48
+ | 'workflow'
49
+ | 'integration'
50
+ | 'apiAccess'
51
+ | 'individual'
52
+ | 'association'
53
+ | 'association.archived'
54
+ | 'donation'
55
+ | 'donation.amount'
56
+ | 'donation.auth.partner.portal'
57
+ | 'donation.company.onboarding'
58
+ | 'donation.taxReceipt'
59
+ | 'donationPayment.status'
60
+ | 'people.document.comments'
61
+ | 'people.document.notifications'
62
+ | 'people.document.reactions'
63
+ | 'people.document.status'
64
+ | 'people.document.answers'
65
+ | 'people.settings'
66
+ | 'people.relation'
67
+
68
+ /**
69
+ * The allowed Events for the AuditLog
70
+ */
71
+ export type AuditEvents = 'create' | 'update' | 'delete' | 'view' | 'merge'
72
+
73
+ /**
74
+ * Data to audit log.
75
+ */
76
+ export interface IAuditLogData {
77
+ /**
78
+ * The data specific to the audit log.
79
+ * For example, if we add a bonus to an employee, it could contain the amount, the impacted employeeId, etc...
80
+ */
81
+ payload?: object
82
+
83
+ /**
84
+ * The buisness domain impacted.
85
+ * e.g documents, authentication, ...
86
+ */
87
+ namespace: AuditNamespaces
88
+
89
+ /**
90
+ * The name of the event.
91
+ * e.g. signIn, passwordUpdated, ...
92
+ */
93
+ event: AuditEvents
94
+
95
+ /**
96
+ * The target of event.
97
+ * e.g. user.settings, employee.bonuses, ...
98
+ */
99
+ target: AuditTargets
100
+
101
+ /**
102
+ * The data that describes who did perform the logged action.
103
+ */
104
+ account: IAuditLogAuthData
105
+
106
+ /**
107
+ * The scope of the audit log
108
+ */
109
+ scope: IAuditLogScope
110
+ }
111
+
112
+ /**
113
+ * Data used to indentify who performed an action
114
+ */
115
+ export type IAuditLogAuthData =
116
+ | {
117
+ /**
118
+ * The id of the payfiter that did the action if it has been made during a siginAs
119
+ */
120
+ internalId?: string
121
+
122
+ // the "account" infos
123
+ holdingId?: string
124
+ companyId?: string
125
+ employeeId?: string
126
+ individualId?: string
127
+ roleId?: string
128
+
129
+ /**
130
+ *
131
+ */
132
+ countryCode?: string
133
+ /**
134
+ * The IP address of the user
135
+ */
136
+ userIp?: string
137
+ /**
138
+ * The email address of the user
139
+ */
140
+ email?: string
141
+ }
142
+ | {
143
+ /* Indicates that the action has been performed by the system */
144
+ isSystem: true
145
+ }
146
+
147
+ /**
148
+ * Data used to "scope" the audit log.
149
+ * Might be useful to retrieve all actions performed in a holding, a company, or on an employee.
150
+ */
151
+ export type IAuditLogScope =
152
+ | {
153
+ holdingId: string
154
+ }
155
+ | {
156
+ companyId: string
157
+ employeeId?: string
158
+ }
159
+ | {
160
+ individualId: string
161
+ }
162
+ | null
163
+
164
+ // warning: modifying this interface means probably make a breaking change to the SDK
165
+ export interface IAuditLogger {
166
+ log: (data: IAuditLogData) => Promise<void>
167
+ }
168
+
169
+ export interface IAuditLoggerInitialization {
170
+ apiUrl: string
171
+ environment: string
172
+ service: string
173
+ }
174
+
175
+ export class AuditLogger implements IAuditLogger {
176
+ private transport: IAuditLogTransport
177
+
178
+ constructor(protected params: IAuditLoggerInitialization) {
179
+ this.transport = new Transport(params.apiUrl)
180
+ }
181
+
182
+ async log({ scope, account, ...data }: IAuditLogData) {
183
+ const uuid = uuidV4()
184
+ const { environment, service } = this.params
185
+ const timestamp = Math.floor(Date.now() / 1000)
186
+
187
+ const log = {
188
+ ...data,
189
+ ...account,
190
+ service,
191
+ id: uuid,
192
+ environment,
193
+ timestamp,
194
+ ...(scope && { scope }),
195
+ }
196
+
197
+ await this.transport.create(log)
198
+ }
199
+ }
@@ -0,0 +1,23 @@
1
+ # OpenAPI Generator Ignore
2
+ # Generated by openapi-generator https://github.com/openapitools/openapi-generator
3
+
4
+ # Use this file to prevent files from being overwritten by the generator.
5
+ # The patterns follow closely to .gitignore or .dockerignore.
6
+
7
+ # As an example, the C# client generator defines ApiClient.cs.
8
+ # You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
9
+ #ApiClient.cs
10
+
11
+ # You can match any string of characters against a directory, file or extension with a single asterisk (*):
12
+ #foo/*/qux
13
+ # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14
+
15
+ # You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16
+ #foo/**/qux
17
+ # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18
+
19
+ # You can also negate patterns with an exclamation (!).
20
+ # For example, you can ignore all files in a docs folder with the file extension .md:
21
+ #docs/*.md
22
+ # Then explicitly reverse the ignore rule for a single file:
23
+ #!docs/README.md