@rushstack/rush-redis-cobuild-plugin 5.167.0 → 5.169.0

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.
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.56.2"
8
+ "packageVersion": "7.56.3"
9
9
  }
10
10
  ]
11
11
  }
@@ -0,0 +1,182 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import { createClient } from '@redis/client';
4
+ const COMPLETED_STATE_SEPARATOR = ';';
5
+ /**
6
+ * @beta
7
+ */
8
+ export class RedisCobuildLockProvider {
9
+ constructor(options, rushSession) {
10
+ this._lockKeyIdentifierMap = new WeakMap();
11
+ this._completedStateKeyIdentifierMap = new WeakMap();
12
+ this._options = RedisCobuildLockProvider.expandOptionsWithEnvironmentVariables(options);
13
+ // Provide a default reconnect strategy that prevents more than 5 reconnect attempts.
14
+ this._options.socket = {
15
+ reconnectStrategy: (count) => {
16
+ this._terminal.writeErrorLine(`Redis client reconnecting attempt #${count}`);
17
+ return count < 5 ? count * 1000 : false;
18
+ },
19
+ ...this._options.socket
20
+ };
21
+ this._terminal = rushSession.getLogger('RedisCobuildLockProvider').terminal;
22
+ try {
23
+ this._redisClient = createClient(this._options);
24
+ }
25
+ catch (e) {
26
+ throw new Error(`Failed to create redis client: ${e.message}`);
27
+ }
28
+ }
29
+ static expandOptionsWithEnvironmentVariables(options, environment = process.env) {
30
+ const finalOptions = { ...options };
31
+ const missingEnvironmentVariables = new Set();
32
+ if (finalOptions.passwordEnvironmentVariable) {
33
+ const password = environment[finalOptions.passwordEnvironmentVariable];
34
+ if (password !== undefined) {
35
+ finalOptions.password = password;
36
+ }
37
+ else {
38
+ missingEnvironmentVariables.add(finalOptions.passwordEnvironmentVariable);
39
+ }
40
+ finalOptions.passwordEnvironmentVariable = undefined;
41
+ }
42
+ if (missingEnvironmentVariables.size) {
43
+ throw new Error(`The "RedisCobuildLockProvider" tries to access missing environment variable${missingEnvironmentVariables.size > 1 ? 's' : ''}: ${Array.from(missingEnvironmentVariables).join(', ')}\nPlease check the configuration in rush-redis-cobuild-plugin.json file`);
44
+ }
45
+ return finalOptions;
46
+ }
47
+ async connectAsync() {
48
+ try {
49
+ await this._redisClient.connect();
50
+ // Check the connection works at early stage
51
+ await this._redisClient.ping();
52
+ }
53
+ catch (e) {
54
+ throw new Error(`Failed to connect to redis server: ${e.message}`);
55
+ }
56
+ // Register error event handler to avoid process exit when redis client error occurs.
57
+ this._redisClient.on('error', (e) => {
58
+ if (e.message) {
59
+ this._terminal.writeErrorLine(`Redis client error: ${e.message}`);
60
+ }
61
+ else {
62
+ this._terminal.writeErrorLine(`Redis client error: ${e}`);
63
+ }
64
+ });
65
+ }
66
+ async disconnectAsync() {
67
+ try {
68
+ await this._redisClient.destroy();
69
+ }
70
+ catch (e) {
71
+ throw new Error(`Failed to disconnect to redis server: ${e.message}`);
72
+ }
73
+ }
74
+ /**
75
+ * Acquiring the lock based on the specific context.
76
+ *
77
+ * NOTE: this is a reentrant lock implementation
78
+ */
79
+ async acquireLockAsync(context) {
80
+ const { _terminal: terminal } = this;
81
+ const { lockKey, lockExpireTimeInSeconds, runnerId } = context;
82
+ let result = false;
83
+ const lockKeyIdentifier = this._getLockKeyIdentifier(context);
84
+ try {
85
+ // According to the doc, the reply of set command is either "OK" or nil. The reply doesn't matter
86
+ await this._redisClient.set(lockKey, runnerId, {
87
+ NX: true,
88
+ // call EXPIRE in an atomic command
89
+ EX: lockExpireTimeInSeconds
90
+ // Do not specify GET here since using NX ane GET together requires Redis@7.
91
+ });
92
+ // Just read the value by lock key to see wether it equals current runner id
93
+ const value = await this._redisClient.get(lockKey);
94
+ if (value === null) {
95
+ // This should not happen.
96
+ throw new Error(`Get redis key failed: ${lockKey}`);
97
+ }
98
+ result = value === runnerId;
99
+ if (result) {
100
+ terminal.writeDebugLine(`Successfully acquired ${lockKeyIdentifier} to runner(${runnerId}) and it expires in ${lockExpireTimeInSeconds}s`);
101
+ }
102
+ else {
103
+ terminal.writeDebugLine(`Failed to acquire ${lockKeyIdentifier}, locked by runner ${value}`);
104
+ }
105
+ }
106
+ catch (e) {
107
+ throw new Error(`Error occurs when acquiring ${lockKeyIdentifier}: ${e.message}`);
108
+ }
109
+ return result;
110
+ }
111
+ async renewLockAsync(context) {
112
+ const { _terminal: terminal } = this;
113
+ const { lockKey, lockExpireTimeInSeconds } = context;
114
+ const lockKeyIdentifier = this._getLockKeyIdentifier(context);
115
+ try {
116
+ await this._redisClient.expire(lockKey, lockExpireTimeInSeconds);
117
+ }
118
+ catch (e) {
119
+ throw new Error(`Failed to renew ${lockKeyIdentifier}: ${e.message}`);
120
+ }
121
+ terminal.writeDebugLine(`Renewed ${lockKeyIdentifier} expires in ${lockExpireTimeInSeconds} seconds`);
122
+ }
123
+ async setCompletedStateAsync(context, state) {
124
+ const { _terminal: terminal } = this;
125
+ const { completedStateKey: key } = context;
126
+ const value = this._serializeCompletedState(state);
127
+ const completedStateKeyIdentifier = this._getCompletedStateKeyIdentifier(context);
128
+ try {
129
+ await this._redisClient.set(key, value);
130
+ }
131
+ catch (e) {
132
+ throw new Error(`Failed to set ${completedStateKeyIdentifier}: ${e.message}`);
133
+ }
134
+ terminal.writeDebugLine(`Set ${completedStateKeyIdentifier}: ${value}`);
135
+ }
136
+ async getCompletedStateAsync(context) {
137
+ const { _terminal: terminal } = this;
138
+ const { completedStateKey: key } = context;
139
+ const completedStateKeyIdentifier = this._getCompletedStateKeyIdentifier(context);
140
+ let state;
141
+ try {
142
+ const value = await this._redisClient.get(key);
143
+ if (value) {
144
+ state = this._deserializeCompletedState(value);
145
+ }
146
+ terminal.writeDebugLine(`Get ${completedStateKeyIdentifier}: ${value}`);
147
+ }
148
+ catch (e) {
149
+ throw new Error(`Failed to get ${completedStateKeyIdentifier}: ${e.message}`);
150
+ }
151
+ return state;
152
+ }
153
+ _serializeCompletedState(state) {
154
+ // Example: SUCCESS;1234567890
155
+ // Example: FAILURE;1234567890
156
+ const { status, cacheId } = state;
157
+ return [status, cacheId].join(COMPLETED_STATE_SEPARATOR);
158
+ }
159
+ _deserializeCompletedState(state) {
160
+ const [status, cacheId] = state.split(COMPLETED_STATE_SEPARATOR);
161
+ return { status: status, cacheId };
162
+ }
163
+ _getLockKeyIdentifier(context) {
164
+ let lockKeyIdentifier = this._lockKeyIdentifierMap.get(context);
165
+ if (lockKeyIdentifier === undefined) {
166
+ const { lockKey, packageName, phaseName } = context;
167
+ lockKeyIdentifier = `lock(${lockKey})_package(${packageName})_phase(${phaseName})`;
168
+ this._lockKeyIdentifierMap.set(context, lockKeyIdentifier);
169
+ }
170
+ return lockKeyIdentifier;
171
+ }
172
+ _getCompletedStateKeyIdentifier(context) {
173
+ let completedStateKeyIdentifier = this._completedStateKeyIdentifierMap.get(context);
174
+ if (completedStateKeyIdentifier === undefined) {
175
+ const { completedStateKey, packageName, phaseName } = context;
176
+ completedStateKeyIdentifier = `completed_state(${completedStateKey})_package(${packageName})_phase(${phaseName})`;
177
+ this._completedStateKeyIdentifierMap.set(context, completedStateKeyIdentifier);
178
+ }
179
+ return completedStateKeyIdentifier;
180
+ }
181
+ }
182
+ //# sourceMappingURL=RedisCobuildLockProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisCobuildLockProvider.js","sourceRoot":"","sources":["../src/RedisCobuildLockProvider.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { createClient } from '@redis/client';\nimport type {\n RedisClientOptions,\n RedisClientType,\n RedisFunctions,\n RedisModules,\n RedisScripts\n} from '@redis/client';\n\nimport type {\n ICobuildLockProvider,\n ICobuildContext,\n ICobuildCompletedState,\n RushSession\n} from '@rushstack/rush-sdk';\nimport type { ITerminal } from '@rushstack/terminal';\n\n/**\n * The redis client options\n * @beta\n */\nexport interface IRedisCobuildLockProviderOptions extends RedisClientOptions {\n /**\n * The environment variable name for the redis password\n */\n passwordEnvironmentVariable?: string;\n}\n\nconst COMPLETED_STATE_SEPARATOR: ';' = ';';\n\n/**\n * @beta\n */\nexport class RedisCobuildLockProvider implements ICobuildLockProvider {\n private readonly _options: IRedisCobuildLockProviderOptions;\n private readonly _terminal: ITerminal;\n private readonly _lockKeyIdentifierMap: WeakMap<ICobuildContext, string> = new WeakMap<\n ICobuildContext,\n string\n >();\n private readonly _completedStateKeyIdentifierMap: WeakMap<ICobuildContext, string> = new WeakMap<\n ICobuildContext,\n string\n >();\n\n private readonly _redisClient: RedisClientType<RedisModules, RedisFunctions, RedisScripts, 2 | 3>;\n\n public constructor(options: IRedisCobuildLockProviderOptions, rushSession: RushSession) {\n this._options = RedisCobuildLockProvider.expandOptionsWithEnvironmentVariables(options);\n // Provide a default reconnect strategy that prevents more than 5 reconnect attempts.\n this._options.socket = {\n reconnectStrategy: (count: number) => {\n this._terminal.writeErrorLine(`Redis client reconnecting attempt #${count}`);\n return count < 5 ? count * 1000 : false;\n },\n ...this._options.socket\n };\n this._terminal = rushSession.getLogger('RedisCobuildLockProvider').terminal;\n try {\n this._redisClient = createClient(this._options);\n } catch (e) {\n throw new Error(`Failed to create redis client: ${e.message}`);\n }\n }\n\n public static expandOptionsWithEnvironmentVariables(\n options: IRedisCobuildLockProviderOptions,\n environment: NodeJS.ProcessEnv = process.env\n ): IRedisCobuildLockProviderOptions {\n const finalOptions: IRedisCobuildLockProviderOptions = { ...options };\n const missingEnvironmentVariables: Set<string> = new Set<string>();\n\n if (finalOptions.passwordEnvironmentVariable) {\n const password: string | undefined = environment[finalOptions.passwordEnvironmentVariable];\n if (password !== undefined) {\n finalOptions.password = password;\n } else {\n missingEnvironmentVariables.add(finalOptions.passwordEnvironmentVariable);\n }\n finalOptions.passwordEnvironmentVariable = undefined;\n }\n\n if (missingEnvironmentVariables.size) {\n throw new Error(\n `The \"RedisCobuildLockProvider\" tries to access missing environment variable${\n missingEnvironmentVariables.size > 1 ? 's' : ''\n }: ${Array.from(missingEnvironmentVariables).join(\n ', '\n )}\\nPlease check the configuration in rush-redis-cobuild-plugin.json file`\n );\n }\n return finalOptions;\n }\n\n public async connectAsync(): Promise<void> {\n try {\n await this._redisClient.connect();\n // Check the connection works at early stage\n await this._redisClient.ping();\n } catch (e) {\n throw new Error(`Failed to connect to redis server: ${e.message}`);\n }\n\n // Register error event handler to avoid process exit when redis client error occurs.\n this._redisClient.on('error', (e: Error) => {\n if (e.message) {\n this._terminal.writeErrorLine(`Redis client error: ${e.message}`);\n } else {\n this._terminal.writeErrorLine(`Redis client error: ${e}`);\n }\n });\n }\n\n public async disconnectAsync(): Promise<void> {\n try {\n await this._redisClient.destroy();\n } catch (e) {\n throw new Error(`Failed to disconnect to redis server: ${e.message}`);\n }\n }\n\n /**\n * Acquiring the lock based on the specific context.\n *\n * NOTE: this is a reentrant lock implementation\n */\n public async acquireLockAsync(context: ICobuildContext): Promise<boolean> {\n const { _terminal: terminal } = this;\n const { lockKey, lockExpireTimeInSeconds, runnerId } = context;\n let result: boolean = false;\n const lockKeyIdentifier: string = this._getLockKeyIdentifier(context);\n try {\n // According to the doc, the reply of set command is either \"OK\" or nil. The reply doesn't matter\n await this._redisClient.set(lockKey, runnerId, {\n NX: true,\n // call EXPIRE in an atomic command\n EX: lockExpireTimeInSeconds\n // Do not specify GET here since using NX ane GET together requires Redis@7.\n });\n // Just read the value by lock key to see wether it equals current runner id\n const value: string | null = await this._redisClient.get(lockKey);\n if (value === null) {\n // This should not happen.\n throw new Error(`Get redis key failed: ${lockKey}`);\n }\n result = value === runnerId;\n if (result) {\n terminal.writeDebugLine(\n `Successfully acquired ${lockKeyIdentifier} to runner(${runnerId}) and it expires in ${lockExpireTimeInSeconds}s`\n );\n } else {\n terminal.writeDebugLine(`Failed to acquire ${lockKeyIdentifier}, locked by runner ${value}`);\n }\n } catch (e) {\n throw new Error(`Error occurs when acquiring ${lockKeyIdentifier}: ${e.message}`);\n }\n return result;\n }\n\n public async renewLockAsync(context: ICobuildContext): Promise<void> {\n const { _terminal: terminal } = this;\n const { lockKey, lockExpireTimeInSeconds } = context;\n const lockKeyIdentifier: string = this._getLockKeyIdentifier(context);\n try {\n await this._redisClient.expire(lockKey, lockExpireTimeInSeconds);\n } catch (e) {\n throw new Error(`Failed to renew ${lockKeyIdentifier}: ${e.message}`);\n }\n terminal.writeDebugLine(`Renewed ${lockKeyIdentifier} expires in ${lockExpireTimeInSeconds} seconds`);\n }\n\n public async setCompletedStateAsync(\n context: ICobuildContext,\n state: ICobuildCompletedState\n ): Promise<void> {\n const { _terminal: terminal } = this;\n const { completedStateKey: key } = context;\n const value: string = this._serializeCompletedState(state);\n const completedStateKeyIdentifier: string = this._getCompletedStateKeyIdentifier(context);\n try {\n await this._redisClient.set(key, value);\n } catch (e) {\n throw new Error(`Failed to set ${completedStateKeyIdentifier}: ${e.message}`);\n }\n terminal.writeDebugLine(`Set ${completedStateKeyIdentifier}: ${value}`);\n }\n\n public async getCompletedStateAsync(context: ICobuildContext): Promise<ICobuildCompletedState | undefined> {\n const { _terminal: terminal } = this;\n const { completedStateKey: key } = context;\n const completedStateKeyIdentifier: string = this._getCompletedStateKeyIdentifier(context);\n let state: ICobuildCompletedState | undefined;\n try {\n const value: string | null = await this._redisClient.get(key);\n if (value) {\n state = this._deserializeCompletedState(value);\n }\n terminal.writeDebugLine(`Get ${completedStateKeyIdentifier}: ${value}`);\n } catch (e) {\n throw new Error(`Failed to get ${completedStateKeyIdentifier}: ${e.message}`);\n }\n return state;\n }\n\n private _serializeCompletedState(state: ICobuildCompletedState): string {\n // Example: SUCCESS;1234567890\n // Example: FAILURE;1234567890\n const { status, cacheId } = state;\n return [status, cacheId].join(COMPLETED_STATE_SEPARATOR);\n }\n\n private _deserializeCompletedState(state: string): ICobuildCompletedState | undefined {\n const [status, cacheId] = state.split(COMPLETED_STATE_SEPARATOR);\n return { status: status as ICobuildCompletedState['status'], cacheId };\n }\n\n private _getLockKeyIdentifier(context: ICobuildContext): string {\n let lockKeyIdentifier: string | undefined = this._lockKeyIdentifierMap.get(context);\n if (lockKeyIdentifier === undefined) {\n const { lockKey, packageName, phaseName } = context;\n lockKeyIdentifier = `lock(${lockKey})_package(${packageName})_phase(${phaseName})`;\n this._lockKeyIdentifierMap.set(context, lockKeyIdentifier);\n }\n return lockKeyIdentifier;\n }\n\n private _getCompletedStateKeyIdentifier(context: ICobuildContext): string {\n let completedStateKeyIdentifier: string | undefined = this._completedStateKeyIdentifierMap.get(context);\n if (completedStateKeyIdentifier === undefined) {\n const { completedStateKey, packageName, phaseName } = context;\n completedStateKeyIdentifier = `completed_state(${completedStateKey})_package(${packageName})_phase(${phaseName})`;\n this._completedStateKeyIdentifierMap.set(context, completedStateKeyIdentifier);\n }\n return completedStateKeyIdentifier;\n }\n}\n"]}
@@ -0,0 +1,23 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import { Import } from '@rushstack/node-core-library';
4
+ const RedisCobuildLockProviderModule = Import.lazy('./RedisCobuildLockProvider', require);
5
+ const PLUGIN_NAME = 'RedisCobuildPlugin';
6
+ /**
7
+ * @public
8
+ */
9
+ export class RushRedisCobuildPlugin {
10
+ constructor(options) {
11
+ this.pluginName = PLUGIN_NAME;
12
+ this._options = options;
13
+ }
14
+ apply(rushSession, rushConfiguration) {
15
+ rushSession.hooks.initialize.tap(PLUGIN_NAME, () => {
16
+ rushSession.registerCobuildLockProviderFactory('redis', () => {
17
+ const options = this._options;
18
+ return new RedisCobuildLockProviderModule.RedisCobuildLockProvider(options, rushSession);
19
+ });
20
+ });
21
+ }
22
+ }
23
+ //# sourceMappingURL=RushRedisCobuildPlugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RushRedisCobuildPlugin.js","sourceRoot":"","sources":["../src/RushRedisCobuildPlugin.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAKtD,MAAM,8BAA8B,GAAgD,MAAM,CAAC,IAAI,CAC7F,4BAA4B,EAC5B,OAAO,CACR,CAAC;AAEF,MAAM,WAAW,GAAW,oBAAoB,CAAC;AAOjD;;GAEG;AACH,MAAM,OAAO,sBAAsB;IAKjC,YAAmB,OAAuC;QAJnD,eAAU,GAAW,WAAW,CAAC;QAKtC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAEM,KAAK,CAAC,WAAwB,EAAE,iBAAoC;QACzE,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,EAAE;YACjD,WAAW,CAAC,kCAAkC,CAAC,OAAO,EAAE,GAA6B,EAAE;gBACrF,MAAM,OAAO,GAAmC,IAAI,CAAC,QAAQ,CAAC;gBAC9D,OAAO,IAAI,8BAA8B,CAAC,wBAAwB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YAC3F,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { Import } from '@rushstack/node-core-library';\nimport type { IRushPlugin, RushSession, RushConfiguration } from '@rushstack/rush-sdk';\n\nimport type { IRedisCobuildLockProviderOptions, RedisCobuildLockProvider } from './RedisCobuildLockProvider';\n\nconst RedisCobuildLockProviderModule: typeof import('./RedisCobuildLockProvider') = Import.lazy(\n './RedisCobuildLockProvider',\n require\n);\n\nconst PLUGIN_NAME: string = 'RedisCobuildPlugin';\n\n/**\n * @public\n */\nexport type IRushRedisCobuildPluginOptions = IRedisCobuildLockProviderOptions;\n\n/**\n * @public\n */\nexport class RushRedisCobuildPlugin implements IRushPlugin {\n public pluginName: string = PLUGIN_NAME;\n\n private _options: IRushRedisCobuildPluginOptions;\n\n public constructor(options: IRushRedisCobuildPluginOptions) {\n this._options = options;\n }\n\n public apply(rushSession: RushSession, rushConfiguration: RushConfiguration): void {\n rushSession.hooks.initialize.tap(PLUGIN_NAME, () => {\n rushSession.registerCobuildLockProviderFactory('redis', (): RedisCobuildLockProvider => {\n const options: IRushRedisCobuildPluginOptions = this._options;\n return new RedisCobuildLockProviderModule.RedisCobuildLockProvider(options, rushSession);\n });\n });\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ /// <reference types="node" preserve="true" />
4
+ export { RushRedisCobuildPlugin as default } from './RushRedisCobuildPlugin';
5
+ export { RedisCobuildLockProvider } from './RedisCobuildLockProvider';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,8CAA8C;AAE9C,OAAO,EACL,sBAAsB,IAAI,OAAO,EAElC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\n/// <reference types=\"node\" preserve=\"true\" />\n\nexport {\n RushRedisCobuildPlugin as default,\n type IRushRedisCobuildPluginOptions\n} from './RushRedisCobuildPlugin';\nexport { RedisCobuildLockProvider } from './RedisCobuildLockProvider';\nexport type { IRedisCobuildLockProviderOptions } from './RedisCobuildLockProvider';\n"]}
@@ -0,0 +1,70 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "Configuration for cobuild lock with Redis configuration\n\nhttps://github.com/redis/node-redis/blob/master/docs/client-configuration.md",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "properties": {
7
+ "url": {
8
+ "type": "string",
9
+ "description": "redis[s]://[[username][:password]@][host][:port][/db-number]\n\n See the following links for more information:\n\nredis: https://www.iana.org/assignments/uri-schemes/prov/redis\n\nrediss: https://www.iana.org/assignments/uri-schemes/prov/rediss"
10
+ },
11
+ "socket": {
12
+ "type": "object",
13
+ "description": "Socket connection properties. Unlisted net.connect properties (and tls.connect) are also supported",
14
+ "properties": {
15
+ "port": {
16
+ "description": "Redis server port. Default value is 6379",
17
+ "type": "number"
18
+ },
19
+ "host": {
20
+ "description": "Redis server host. Default value is localhost",
21
+ "type": "string"
22
+ },
23
+ "family": {
24
+ "description": "IP Stack version (one of 4 | 6 | 0). Default value is 0",
25
+ "type": "number"
26
+ },
27
+ "path": {
28
+ "description": "path to the UNIX Socket",
29
+ "type": "string"
30
+ },
31
+ "connectTimeout": {
32
+ "description": "Connection timeout in milliseconds. Default value is 5000",
33
+ "type": "number"
34
+ },
35
+ "noDelay": {
36
+ "description": "Toggle Nagle's algorithm. Default value is true",
37
+ "type": "boolean"
38
+ },
39
+ "keepAlive": {
40
+ "description": "Toggle keep alive on the socket",
41
+ "type": "boolean"
42
+ }
43
+ }
44
+ },
45
+ "username": {
46
+ "description": "ACL username",
47
+ "type": "string"
48
+ },
49
+ "passwordEnvironmentVariable": {
50
+ "description": "The environment variable used to get the ACL password",
51
+ "type": "string"
52
+ },
53
+ "name": {
54
+ "description": "Redis client name",
55
+ "type": "string"
56
+ },
57
+ "database": {
58
+ "description": "Redis database number",
59
+ "type": "number"
60
+ },
61
+ "legacyMode": {
62
+ "description": "Maintain some backwards compatibility",
63
+ "type": "boolean"
64
+ },
65
+ "pingInterval": {
66
+ "description": "Send PING command at interval (in ms). Useful with \"Azure Cache for Redis\".",
67
+ "type": "number"
68
+ }
69
+ }
70
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rushstack/rush-redis-cobuild-plugin",
3
- "version": "5.167.0",
3
+ "version": "5.169.0",
4
4
  "description": "Rush plugin for Redis cobuild lock",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,21 +8,45 @@
8
8
  "directory": "rush-plugins/rush-redis-cobuild-plugin"
9
9
  },
10
10
  "homepage": "https://rushjs.io",
11
- "main": "lib/index.js",
12
- "types": "lib/index.d.ts",
11
+ "main": "./lib-commonjs/index.js",
12
+ "module": "./lib-esm/index.js",
13
+ "types": "./lib-dts/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./lib-dts/index.d.ts",
17
+ "import": "./lib-esm/index.js",
18
+ "require": "./lib-commonjs/index.js"
19
+ },
20
+ "./lib/*.schema.json": "./lib-commonjs/*.schema.json",
21
+ "./lib/*": {
22
+ "types": "./lib-dts/*.d.ts",
23
+ "import": "./lib-esm/*.js",
24
+ "require": "./lib-commonjs/*.js"
25
+ },
26
+ "./rush-plugin-manifest.json": "./rush-plugin-manifest.json",
27
+ "./package.json": "./package.json"
28
+ },
29
+ "typesVersions": {
30
+ "*": {
31
+ "lib/*": [
32
+ "lib-dts/*"
33
+ ]
34
+ }
35
+ },
13
36
  "license": "MIT",
14
37
  "dependencies": {
15
38
  "@redis/client": "~5.8.2",
16
- "@rushstack/node-core-library": "5.19.1",
17
- "@rushstack/rush-sdk": "5.167.0"
39
+ "@rushstack/node-core-library": "5.20.0",
40
+ "@rushstack/rush-sdk": "5.169.0"
18
41
  },
19
42
  "devDependencies": {
20
43
  "eslint": "~9.37.0",
21
- "@rushstack/heft": "1.1.13",
22
- "@rushstack/terminal": "0.21.0",
23
- "@microsoft/rush-lib": "5.167.0",
44
+ "@microsoft/rush-lib": "5.169.0",
45
+ "@rushstack/heft": "1.2.0",
46
+ "@rushstack/terminal": "0.22.0",
24
47
  "local-node-rig": "1.0.0"
25
48
  },
49
+ "sideEffects": false,
26
50
  "scripts": {
27
51
  "build": "heft build --clean",
28
52
  "start": "heft test-watch",
@@ -4,8 +4,8 @@
4
4
  {
5
5
  "pluginName": "rush-redis-cobuild-plugin",
6
6
  "description": "Rush plugin for Redis cobuild lock",
7
- "entryPoint": "lib/index.js",
8
- "optionsSchema": "lib/schemas/redis-config.schema.json"
7
+ "entryPoint": "./lib-commonjs/index.js",
8
+ "optionsSchema": "./lib-commonjs/schemas/redis-config.schema.json"
9
9
  }
10
10
  ]
11
11
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes