@rushstack/rush-redis-cobuild-plugin 5.97.1-pr3949.2 → 5.101.0-pr3949.3

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.
@@ -1,12 +1,12 @@
1
1
  /// <reference types="node" />
2
2
 
3
- import type { ICobuildCompletedState } from '@rushstack/rush-sdk';
4
- import type { ICobuildContext } from '@rushstack/rush-sdk';
5
- import type { ICobuildLockProvider } from '@rushstack/rush-sdk';
3
+ import { ICobuildCompletedState } from '@rushstack/rush-sdk';
4
+ import { ICobuildContext } from '@rushstack/rush-sdk';
5
+ import { ICobuildLockProvider } from '@rushstack/rush-sdk';
6
6
  import type { IRushPlugin } from '@rushstack/rush-sdk';
7
7
  import type { RedisClientOptions } from '@redis/client';
8
8
  import type { RushConfiguration } from '@rushstack/rush-sdk';
9
- import type { RushSession } from '@rushstack/rush-sdk';
9
+ import { RushSession } from '@rushstack/rush-sdk';
10
10
 
11
11
  /**
12
12
  * The redis client options
@@ -30,29 +30,26 @@ declare type IRushRedisCobuildPluginOptions = IRedisCobuildLockProviderOptions;
30
30
  export declare class RedisCobuildLockProvider implements ICobuildLockProvider {
31
31
  private readonly _options;
32
32
  private readonly _terminal;
33
+ private readonly _lockKeyIdentifierMap;
34
+ private readonly _completedStateKeyIdentifierMap;
33
35
  private readonly _redisClient;
34
- private readonly _lockKeyMap;
35
- private readonly _completedKeyMap;
36
36
  constructor(options: IRedisCobuildLockProviderOptions, rushSession: RushSession);
37
37
  static expandOptionsWithEnvironmentVariables(options: IRedisCobuildLockProviderOptions, environment?: NodeJS.ProcessEnv): IRedisCobuildLockProviderOptions;
38
38
  connectAsync(): Promise<void>;
39
39
  disconnectAsync(): Promise<void>;
40
+ /**
41
+ * Acquiring the lock based on the specific context.
42
+ *
43
+ * NOTE: this is a reentrant lock implementation
44
+ */
40
45
  acquireLockAsync(context: ICobuildContext): Promise<boolean>;
41
46
  renewLockAsync(context: ICobuildContext): Promise<void>;
42
47
  setCompletedStateAsync(context: ICobuildContext, state: ICobuildCompletedState): Promise<void>;
43
48
  getCompletedStateAsync(context: ICobuildContext): Promise<ICobuildCompletedState | undefined>;
44
- /**
45
- * Returns the lock key for the given context
46
- * Example: cobuild:v1:<contextId>:<cacheId>:lock
47
- */
48
- getLockKey(context: ICobuildContext): string;
49
- /**
50
- * Returns the completed key for the given context
51
- * Example: cobuild:v1:<contextId>:<cacheId>:completed
52
- */
53
- getCompletedStateKey(context: ICobuildContext): string;
54
49
  private _serializeCompletedState;
55
50
  private _deserializeCompletedState;
51
+ private _getLockKeyIdentifier;
52
+ private _getCompletedStateKeyIdentifier;
56
53
  }
57
54
 
58
55
  /**
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import type { ICobuildLockProvider, ICobuildContext, ICobuildCompletedState, RushSession } from '@rushstack/rush-sdk';
2
+ import { ICobuildLockProvider, ICobuildContext, ICobuildCompletedState, RushSession } from '@rushstack/rush-sdk';
3
3
  import type { RedisClientOptions } from '@redis/client';
4
4
  /**
5
5
  * The redis client options
@@ -17,28 +17,25 @@ export interface IRedisCobuildLockProviderOptions extends RedisClientOptions {
17
17
  export declare class RedisCobuildLockProvider implements ICobuildLockProvider {
18
18
  private readonly _options;
19
19
  private readonly _terminal;
20
+ private readonly _lockKeyIdentifierMap;
21
+ private readonly _completedStateKeyIdentifierMap;
20
22
  private readonly _redisClient;
21
- private readonly _lockKeyMap;
22
- private readonly _completedKeyMap;
23
23
  constructor(options: IRedisCobuildLockProviderOptions, rushSession: RushSession);
24
24
  static expandOptionsWithEnvironmentVariables(options: IRedisCobuildLockProviderOptions, environment?: NodeJS.ProcessEnv): IRedisCobuildLockProviderOptions;
25
25
  connectAsync(): Promise<void>;
26
26
  disconnectAsync(): Promise<void>;
27
+ /**
28
+ * Acquiring the lock based on the specific context.
29
+ *
30
+ * NOTE: this is a reentrant lock implementation
31
+ */
27
32
  acquireLockAsync(context: ICobuildContext): Promise<boolean>;
28
33
  renewLockAsync(context: ICobuildContext): Promise<void>;
29
34
  setCompletedStateAsync(context: ICobuildContext, state: ICobuildCompletedState): Promise<void>;
30
35
  getCompletedStateAsync(context: ICobuildContext): Promise<ICobuildCompletedState | undefined>;
31
- /**
32
- * Returns the lock key for the given context
33
- * Example: cobuild:v1:<contextId>:<cacheId>:lock
34
- */
35
- getLockKey(context: ICobuildContext): string;
36
- /**
37
- * Returns the completed key for the given context
38
- * Example: cobuild:v1:<contextId>:<cacheId>:completed
39
- */
40
- getCompletedStateKey(context: ICobuildContext): string;
41
36
  private _serializeCompletedState;
42
37
  private _deserializeCompletedState;
38
+ private _getLockKeyIdentifier;
39
+ private _getCompletedStateKeyIdentifier;
43
40
  }
44
41
  //# sourceMappingURL=RedisCobuildLockProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RedisCobuildLockProvider.d.ts","sourceRoot":"","sources":["../src/RedisCobuildLockProvider.ts"],"names":[],"mappings":";AAKA,OAAO,KAAK,EACV,oBAAoB,EACpB,eAAe,EACf,sBAAsB,EACtB,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EACV,kBAAkB,EAKnB,MAAM,eAAe,CAAC;AAGvB;;;GAGG;AACH,MAAM,WAAW,gCAAiC,SAAQ,kBAAkB;IAC1E;;OAEG;IACH,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC;AAKD;;GAEG;AACH,qBAAa,wBAAyB,YAAW,oBAAoB;IACnE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IAEtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA8D;IAC3F,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4E;IACxG,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAG7B;gBAEe,OAAO,EAAE,gCAAgC,EAAE,WAAW,EAAE,WAAW;WAUxE,qCAAqC,CACjD,OAAO,EAAE,gCAAgC,EACzC,WAAW,GAAE,MAAM,CAAC,UAAwB,GAC3C,gCAAgC;IA0BtB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAU7B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAQhC,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC;IAiB5D,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD,sBAAsB,CACjC,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,IAAI,CAAC;IAYH,sBAAsB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC;IAgB1G;;;OAGG;IACI,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM;IAUnD;;;OAGG;IACI,oBAAoB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM;IAU7D,OAAO,CAAC,wBAAwB;IAMhC,OAAO,CAAC,0BAA0B;CAInC"}
1
+ {"version":3,"file":"RedisCobuildLockProvider.d.ts","sourceRoot":"","sources":["../src/RedisCobuildLockProvider.ts"],"names":[],"mappings":";AAKA,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,sBAAsB,EACtB,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EACV,kBAAkB,EAKnB,MAAM,eAAe,CAAC;AAGvB;;;GAGG;AACH,MAAM,WAAW,gCAAiC,SAAQ,kBAAkB;IAC1E;;OAEG;IACH,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC;AAID;;GAEG;AACH,qBAAa,wBAAyB,YAAW,oBAAoB;IACnE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAGlC;IACJ,OAAO,CAAC,QAAQ,CAAC,+BAA+B,CAG5C;IAEJ,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA8D;gBAExE,OAAO,EAAE,gCAAgC,EAAE,WAAW,EAAE,WAAW;WAUxE,qCAAqC,CACjD,OAAO,EAAE,gCAAgC,EACzC,WAAW,GAAE,MAAM,CAAC,UAAwB,GAC3C,gCAAgC;IA0BtB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAU7B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7C;;;;OAIG;IACU,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC;IAiC5D,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvD,sBAAsB,CACjC,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,IAAI,CAAC;IAaH,sBAAsB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC;IAiB1G,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,0BAA0B;IAKlC,OAAO,CAAC,qBAAqB;IAU7B,OAAO,CAAC,+BAA+B;CASxC"}
@@ -4,15 +4,14 @@
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.RedisCobuildLockProvider = void 0;
6
6
  const client_1 = require("@redis/client");
7
- const KEY_SEPARATOR = ':';
8
7
  const COMPLETED_STATE_SEPARATOR = ';';
9
8
  /**
10
9
  * @beta
11
10
  */
12
11
  class RedisCobuildLockProvider {
13
12
  constructor(options, rushSession) {
14
- this._lockKeyMap = new WeakMap();
15
- this._completedKeyMap = new WeakMap();
13
+ this._lockKeyIdentifierMap = new WeakMap();
14
+ this._completedStateKeyIdentifierMap = new WeakMap();
16
15
  this._options = RedisCobuildLockProvider.expandOptionsWithEnvironmentVariables(options);
17
16
  this._terminal = rushSession.getLogger('RedisCobuildLockProvider').terminal;
18
17
  try {
@@ -58,97 +57,113 @@ class RedisCobuildLockProvider {
58
57
  throw new Error(`Failed to disconnect to redis server: ${e.message}`);
59
58
  }
60
59
  }
60
+ /**
61
+ * Acquiring the lock based on the specific context.
62
+ *
63
+ * NOTE: this is a reentrant lock implementation
64
+ */
61
65
  async acquireLockAsync(context) {
62
66
  const { _terminal: terminal } = this;
63
- const lockKey = this.getLockKey(context);
67
+ const { lockKey, lockExpireTimeInSeconds, runnerId } = context;
64
68
  let result = false;
69
+ const lockKeyIdentifier = this._getLockKeyIdentifier(context);
65
70
  try {
66
- const incrResult = await this._redisClient.incr(lockKey);
67
- result = incrResult === 1;
68
- terminal.writeDebugLine(`Acquired lock for ${lockKey}: ${incrResult}, 1 is success`);
71
+ // According to the doc, the reply of set command is either "OK" or nil. The reply doesn't matter
72
+ await this._redisClient.set(lockKey, runnerId, {
73
+ NX: true,
74
+ // call EXPIRE in an atomic command
75
+ EX: lockExpireTimeInSeconds
76
+ // Do not specify GET here since using NX ane GET together requires Redis@7.
77
+ });
78
+ // Just read the value by lock key to see wether it equals current runner id
79
+ const value = await this._redisClient.get(lockKey);
80
+ if (value === null) {
81
+ // This should not happen.
82
+ throw new Error(`Get redis key failed: ${lockKey}`);
83
+ }
84
+ result = value === runnerId;
69
85
  if (result) {
70
- await this.renewLockAsync(context);
86
+ terminal.writeDebugLine(`Successfully acquired ${lockKeyIdentifier} to runner(${runnerId}) and it expires in ${lockExpireTimeInSeconds}s`);
87
+ }
88
+ else {
89
+ terminal.writeDebugLine(`Failed to acquire ${lockKeyIdentifier}, locked by runner ${value}`);
71
90
  }
72
91
  }
73
92
  catch (e) {
74
- throw new Error(`Failed to acquire lock for ${lockKey}: ${e.message}`);
93
+ throw new Error(`Error occurs when acquiring ${lockKeyIdentifier}: ${e.message}`);
75
94
  }
76
95
  return result;
77
96
  }
78
97
  async renewLockAsync(context) {
79
98
  const { _terminal: terminal } = this;
80
- const lockKey = this.getLockKey(context);
99
+ const { lockKey, lockExpireTimeInSeconds } = context;
100
+ const lockKeyIdentifier = this._getLockKeyIdentifier(context);
81
101
  try {
82
- await this._redisClient.expire(lockKey, 30);
102
+ await this._redisClient.expire(lockKey, lockExpireTimeInSeconds);
83
103
  }
84
104
  catch (e) {
85
- throw new Error(`Failed to renew lock for ${lockKey}: ${e.message}`);
105
+ throw new Error(`Failed to renew ${lockKeyIdentifier}: ${e.message}`);
86
106
  }
87
- terminal.writeDebugLine(`Renewed lock for ${lockKey}`);
107
+ terminal.writeDebugLine(`Renewed ${lockKeyIdentifier} expires in ${lockExpireTimeInSeconds} seconds`);
88
108
  }
89
109
  async setCompletedStateAsync(context, state) {
90
110
  const { _terminal: terminal } = this;
91
- const key = this.getCompletedStateKey(context);
111
+ const { completedStateKey: key } = context;
92
112
  const value = this._serializeCompletedState(state);
113
+ const completedStateKeyIdentifier = this._getCompletedStateKeyIdentifier(context);
93
114
  try {
94
115
  await this._redisClient.set(key, value);
95
116
  }
96
117
  catch (e) {
97
- throw new Error(`Failed to set completed state for ${key}: ${e.message}`);
118
+ throw new Error(`Failed to set ${completedStateKeyIdentifier}: ${e.message}`);
98
119
  }
99
- terminal.writeDebugLine(`Set completed state for ${key}: ${value}`);
120
+ terminal.writeDebugLine(`Set ${completedStateKeyIdentifier}: ${value}`);
100
121
  }
101
122
  async getCompletedStateAsync(context) {
102
123
  const { _terminal: terminal } = this;
103
- const key = this.getCompletedStateKey(context);
124
+ const { completedStateKey: key } = context;
125
+ const completedStateKeyIdentifier = this._getCompletedStateKeyIdentifier(context);
104
126
  let state;
105
127
  try {
106
128
  const value = await this._redisClient.get(key);
107
129
  if (value) {
108
130
  state = this._deserializeCompletedState(value);
109
131
  }
110
- terminal.writeDebugLine(`Get completed state for ${key}: ${value}`);
132
+ terminal.writeDebugLine(`Get ${completedStateKeyIdentifier}: ${value}`);
111
133
  }
112
134
  catch (e) {
113
- throw new Error(`Failed to get completed state for ${key}: ${e.message}`);
135
+ throw new Error(`Failed to get ${completedStateKeyIdentifier}: ${e.message}`);
114
136
  }
115
137
  return state;
116
138
  }
117
- /**
118
- * Returns the lock key for the given context
119
- * Example: cobuild:v1:<contextId>:<cacheId>:lock
120
- */
121
- getLockKey(context) {
122
- const { version, contextId, cacheId } = context;
123
- let lockKey = this._lockKeyMap.get(context);
124
- if (!lockKey) {
125
- lockKey = ['cobuild', `v${version}`, contextId, cacheId, 'lock'].join(KEY_SEPARATOR);
126
- this._lockKeyMap.set(context, lockKey);
127
- }
128
- return lockKey;
129
- }
130
- /**
131
- * Returns the completed key for the given context
132
- * Example: cobuild:v1:<contextId>:<cacheId>:completed
133
- */
134
- getCompletedStateKey(context) {
135
- const { version, contextId, cacheId } = context;
136
- let completedKey = this._completedKeyMap.get(context);
137
- if (!completedKey) {
138
- completedKey = ['cobuild', `v${version}`, contextId, cacheId, 'completed'].join(KEY_SEPARATOR);
139
- this._completedKeyMap.set(context, completedKey);
140
- }
141
- return completedKey;
142
- }
143
139
  _serializeCompletedState(state) {
144
140
  // Example: SUCCESS;1234567890
145
141
  // Example: FAILURE;1234567890
146
- return `${state.status}${COMPLETED_STATE_SEPARATOR}${state.cacheId}`;
142
+ const { status, cacheId } = state;
143
+ return [status, cacheId].join(COMPLETED_STATE_SEPARATOR);
147
144
  }
148
145
  _deserializeCompletedState(state) {
149
146
  const [status, cacheId] = state.split(COMPLETED_STATE_SEPARATOR);
150
147
  return { status: status, cacheId };
151
148
  }
149
+ _getLockKeyIdentifier(context) {
150
+ let lockKeyIdentifier = this._lockKeyIdentifierMap.get(context);
151
+ if (lockKeyIdentifier === undefined) {
152
+ const { lockKey, packageName, phaseName } = context;
153
+ lockKeyIdentifier = `lock(${lockKey})_package(${packageName})_phase(${phaseName})`;
154
+ this._lockKeyIdentifierMap.set(context, lockKeyIdentifier);
155
+ }
156
+ return lockKeyIdentifier;
157
+ }
158
+ _getCompletedStateKeyIdentifier(context) {
159
+ let completedStateKeyIdentifier = this._completedStateKeyIdentifierMap.get(context);
160
+ if (completedStateKeyIdentifier === undefined) {
161
+ const { completedStateKey, packageName, phaseName } = context;
162
+ completedStateKeyIdentifier = `completed_state(${completedStateKey})_package(${packageName})_phase(${phaseName})`;
163
+ this._completedStateKeyIdentifierMap.set(context, completedStateKeyIdentifier);
164
+ }
165
+ return completedStateKeyIdentifier;
166
+ }
152
167
  }
153
168
  exports.RedisCobuildLockProvider = RedisCobuildLockProvider;
154
169
  //# sourceMappingURL=RedisCobuildLockProvider.js.map
@@ -1 +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';\n\nimport type {\n ICobuildLockProvider,\n ICobuildContext,\n ICobuildCompletedState,\n RushSession\n} from '@rushstack/rush-sdk';\nimport type {\n RedisClientOptions,\n RedisClientType,\n RedisFunctions,\n RedisModules,\n RedisScripts\n} from '@redis/client';\nimport type { ITerminal } from '@rushstack/node-core-library';\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 KEY_SEPARATOR: ':' = ':';\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\n private readonly _redisClient: RedisClientType<RedisModules, RedisFunctions, RedisScripts>;\n private readonly _lockKeyMap: WeakMap<ICobuildContext, string> = new WeakMap<ICobuildContext, string>();\n private readonly _completedKeyMap: WeakMap<ICobuildContext, string> = new WeakMap<\n ICobuildContext,\n string\n >();\n\n public constructor(options: IRedisCobuildLockProviderOptions, rushSession: RushSession) {\n this._options = RedisCobuildLockProvider.expandOptionsWithEnvironmentVariables(options);\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\n public async disconnectAsync(): Promise<void> {\n try {\n await this._redisClient.disconnect();\n } catch (e) {\n throw new Error(`Failed to disconnect to redis server: ${e.message}`);\n }\n }\n\n public async acquireLockAsync(context: ICobuildContext): Promise<boolean> {\n const { _terminal: terminal } = this;\n const lockKey: string = this.getLockKey(context);\n let result: boolean = false;\n try {\n const incrResult: number = await this._redisClient.incr(lockKey);\n result = incrResult === 1;\n terminal.writeDebugLine(`Acquired lock for ${lockKey}: ${incrResult}, 1 is success`);\n if (result) {\n await this.renewLockAsync(context);\n }\n } catch (e) {\n throw new Error(`Failed to acquire lock for ${lockKey}: ${e.message}`);\n }\n return result;\n }\n\n public async renewLockAsync(context: ICobuildContext): Promise<void> {\n const { _terminal: terminal } = this;\n const lockKey: string = this.getLockKey(context);\n try {\n await this._redisClient.expire(lockKey, 30);\n } catch (e) {\n throw new Error(`Failed to renew lock for ${lockKey}: ${e.message}`);\n }\n terminal.writeDebugLine(`Renewed lock for ${lockKey}`);\n }\n\n public async setCompletedStateAsync(\n context: ICobuildContext,\n state: ICobuildCompletedState\n ): Promise<void> {\n const { _terminal: terminal } = this;\n const key: string = this.getCompletedStateKey(context);\n const value: string = this._serializeCompletedState(state);\n try {\n await this._redisClient.set(key, value);\n } catch (e) {\n throw new Error(`Failed to set completed state for ${key}: ${e.message}`);\n }\n terminal.writeDebugLine(`Set completed state for ${key}: ${value}`);\n }\n\n public async getCompletedStateAsync(context: ICobuildContext): Promise<ICobuildCompletedState | undefined> {\n const { _terminal: terminal } = this;\n const key: string = this.getCompletedStateKey(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 completed state for ${key}: ${value}`);\n } catch (e) {\n throw new Error(`Failed to get completed state for ${key}: ${e.message}`);\n }\n return state;\n }\n\n /**\n * Returns the lock key for the given context\n * Example: cobuild:v1:<contextId>:<cacheId>:lock\n */\n public getLockKey(context: ICobuildContext): string {\n const { version, contextId, cacheId } = context;\n let lockKey: string | undefined = this._lockKeyMap.get(context);\n if (!lockKey) {\n lockKey = ['cobuild', `v${version}`, contextId, cacheId, 'lock'].join(KEY_SEPARATOR);\n this._lockKeyMap.set(context, lockKey);\n }\n return lockKey;\n }\n\n /**\n * Returns the completed key for the given context\n * Example: cobuild:v1:<contextId>:<cacheId>:completed\n */\n public getCompletedStateKey(context: ICobuildContext): string {\n const { version, contextId, cacheId } = context;\n let completedKey: string | undefined = this._completedKeyMap.get(context);\n if (!completedKey) {\n completedKey = ['cobuild', `v${version}`, contextId, cacheId, 'completed'].join(KEY_SEPARATOR);\n this._completedKeyMap.set(context, completedKey);\n }\n return completedKey;\n }\n\n private _serializeCompletedState(state: ICobuildCompletedState): string {\n // Example: SUCCESS;1234567890\n // Example: FAILURE;1234567890\n return `${state.status}${COMPLETED_STATE_SEPARATOR}${state.cacheId}`;\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"]}
1
+ {"version":3,"file":"RedisCobuildLockProvider.js","sourceRoot":"","sources":["../src/RedisCobuildLockProvider.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAE3D,0CAA6C;AA4B7C,MAAM,yBAAyB,GAAQ,GAAG,CAAC;AAE3C;;GAEG;AACH,MAAa,wBAAwB;IAcnC,YAAmB,OAAyC,EAAE,WAAwB;QAXrE,0BAAqB,GAAqC,IAAI,OAAO,EAGnF,CAAC;QACa,oCAA+B,GAAqC,IAAI,OAAO,EAG7F,CAAC;QAKF,IAAI,CAAC,QAAQ,GAAG,wBAAwB,CAAC,qCAAqC,CAAC,OAAO,CAAC,CAAC;QACxF,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC,QAAQ,CAAC;QAC5E,IAAI;YACF,IAAI,CAAC,YAAY,GAAG,IAAA,qBAAY,EAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;SACjD;QAAC,OAAO,CAAC,EAAE;YACV,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;SAChE;IACH,CAAC;IAEM,MAAM,CAAC,qCAAqC,CACjD,OAAyC,EACzC,cAAiC,OAAO,CAAC,GAAG;QAE5C,MAAM,YAAY,qBAA0C,OAAO,CAAE,CAAC;QACtE,MAAM,2BAA2B,GAAgB,IAAI,GAAG,EAAU,CAAC;QAEnE,IAAI,YAAY,CAAC,2BAA2B,EAAE;YAC5C,MAAM,QAAQ,GAAuB,WAAW,CAAC,YAAY,CAAC,2BAA2B,CAAC,CAAC;YAC3F,IAAI,QAAQ,KAAK,SAAS,EAAE;gBAC1B,YAAY,CAAC,QAAQ,GAAG,QAAQ,CAAC;aAClC;iBAAM;gBACL,2BAA2B,CAAC,GAAG,CAAC,YAAY,CAAC,2BAA2B,CAAC,CAAC;aAC3E;YACD,YAAY,CAAC,2BAA2B,GAAG,SAAS,CAAC;SACtD;QAED,IAAI,2BAA2B,CAAC,IAAI,EAAE;YACpC,MAAM,IAAI,KAAK,CACb,8EACE,2BAA2B,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAC/C,KAAK,KAAK,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,IAAI,CAC/C,IAAI,CACL,yEAAyE,CAC3E,CAAC;SACH;QACD,OAAO,YAAY,CAAC;IACtB,CAAC;IAEM,KAAK,CAAC,YAAY;QACvB,IAAI;YACF,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;YAClC,4CAA4C;YAC5C,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;SAChC;QAAC,OAAO,CAAC,EAAE;YACV,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;SACpE;IACH,CAAC;IAEM,KAAK,CAAC,eAAe;QAC1B,IAAI;YACF,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC;SACtC;QAAC,OAAO,CAAC,EAAE;YACV,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;SACvE;IACH,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,gBAAgB,CAAC,OAAwB;QACpD,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;QACrC,MAAM,EAAE,OAAO,EAAE,uBAAuB,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;QAC/D,IAAI,MAAM,GAAY,KAAK,CAAC;QAC5B,MAAM,iBAAiB,GAAW,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;QACtE,IAAI;YACF,iGAAiG;YACjG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE;gBAC7C,EAAE,EAAE,IAAI;gBACR,mCAAmC;gBACnC,EAAE,EAAE,uBAAuB;gBAC3B,4EAA4E;aAC7E,CAAC,CAAC;YACH,4EAA4E;YAC5E,MAAM,KAAK,GAAkB,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAClE,IAAI,KAAK,KAAK,IAAI,EAAE;gBAClB,0BAA0B;gBAC1B,MAAM,IAAI,KAAK,CAAC,yBAAyB,OAAO,EAAE,CAAC,CAAC;aACrD;YACD,MAAM,GAAG,KAAK,KAAK,QAAQ,CAAC;YAC5B,IAAI,MAAM,EAAE;gBACV,QAAQ,CAAC,cAAc,CACrB,yBAAyB,iBAAiB,cAAc,QAAQ,uBAAuB,uBAAuB,GAAG,CAClH,CAAC;aACH;iBAAM;gBACL,QAAQ,CAAC,cAAc,CAAC,qBAAqB,iBAAiB,sBAAsB,KAAK,EAAE,CAAC,CAAC;aAC9F;SACF;QAAC,OAAO,CAAC,EAAE;YACV,MAAM,IAAI,KAAK,CAAC,+BAA+B,iBAAiB,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;SACnF;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAEM,KAAK,CAAC,cAAc,CAAC,OAAwB;QAClD,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;QACrC,MAAM,EAAE,OAAO,EAAE,uBAAuB,EAAE,GAAG,OAAO,CAAC;QACrD,MAAM,iBAAiB,GAAW,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;QACtE,IAAI;YACF,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC;SAClE;QAAC,OAAO,CAAC,EAAE;YACV,MAAM,IAAI,KAAK,CAAC,mBAAmB,iBAAiB,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;SACvE;QACD,QAAQ,CAAC,cAAc,CAAC,WAAW,iBAAiB,eAAe,uBAAuB,UAAU,CAAC,CAAC;IACxG,CAAC;IAEM,KAAK,CAAC,sBAAsB,CACjC,OAAwB,EACxB,KAA6B;QAE7B,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;QACrC,MAAM,EAAE,iBAAiB,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;QAC3C,MAAM,KAAK,GAAW,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC,CAAC;QAC3D,MAAM,2BAA2B,GAAW,IAAI,CAAC,+BAA+B,CAAC,OAAO,CAAC,CAAC;QAC1F,IAAI;YACF,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;SACzC;QAAC,OAAO,CAAC,EAAE;YACV,MAAM,IAAI,KAAK,CAAC,iBAAiB,2BAA2B,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;SAC/E;QACD,QAAQ,CAAC,cAAc,CAAC,OAAO,2BAA2B,KAAK,KAAK,EAAE,CAAC,CAAC;IAC1E,CAAC;IAEM,KAAK,CAAC,sBAAsB,CAAC,OAAwB;QAC1D,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;QACrC,MAAM,EAAE,iBAAiB,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;QAC3C,MAAM,2BAA2B,GAAW,IAAI,CAAC,+BAA+B,CAAC,OAAO,CAAC,CAAC;QAC1F,IAAI,KAAyC,CAAC;QAC9C,IAAI;YACF,MAAM,KAAK,GAAkB,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9D,IAAI,KAAK,EAAE;gBACT,KAAK,GAAG,IAAI,CAAC,0BAA0B,CAAC,KAAK,CAAC,CAAC;aAChD;YACD,QAAQ,CAAC,cAAc,CAAC,OAAO,2BAA2B,KAAK,KAAK,EAAE,CAAC,CAAC;SACzE;QAAC,OAAO,CAAC,EAAE;YACV,MAAM,IAAI,KAAK,CAAC,iBAAiB,2BAA2B,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;SAC/E;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,wBAAwB,CAAC,KAA6B;QAC5D,8BAA8B;QAC9B,8BAA8B;QAC9B,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;QAClC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IAC3D,CAAC;IAEO,0BAA0B,CAAC,KAAa;QAC9C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACjE,OAAO,EAAE,MAAM,EAAE,MAA0C,EAAE,OAAO,EAAE,CAAC;IACzE,CAAC;IAEO,qBAAqB,CAAC,OAAwB;QACpD,IAAI,iBAAiB,GAAuB,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACpF,IAAI,iBAAiB,KAAK,SAAS,EAAE;YACnC,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;YACpD,iBAAiB,GAAG,QAAQ,OAAO,aAAa,WAAW,WAAW,SAAS,GAAG,CAAC;YACnF,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;SAC5D;QACD,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IAEO,+BAA+B,CAAC,OAAwB;QAC9D,IAAI,2BAA2B,GAAuB,IAAI,CAAC,+BAA+B,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACxG,IAAI,2BAA2B,KAAK,SAAS,EAAE;YAC7C,MAAM,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;YAC9D,2BAA2B,GAAG,mBAAmB,iBAAiB,aAAa,WAAW,WAAW,SAAS,GAAG,CAAC;YAClH,IAAI,CAAC,+BAA+B,CAAC,GAAG,CAAC,OAAO,EAAE,2BAA2B,CAAC,CAAC;SAChF;QACD,OAAO,2BAA2B,CAAC;IACrC,CAAC;CACF;AAzLD,4DAyLC","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';\n\nimport {\n ICobuildLockProvider,\n ICobuildContext,\n ICobuildCompletedState,\n RushSession\n} from '@rushstack/rush-sdk';\nimport type {\n RedisClientOptions,\n RedisClientType,\n RedisFunctions,\n RedisModules,\n RedisScripts\n} from '@redis/client';\nimport type { ITerminal } from '@rushstack/node-core-library';\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>;\n\n public constructor(options: IRedisCobuildLockProviderOptions, rushSession: RushSession) {\n this._options = RedisCobuildLockProvider.expandOptionsWithEnvironmentVariables(options);\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\n public async disconnectAsync(): Promise<void> {\n try {\n await this._redisClient.disconnect();\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"]}
@@ -3,7 +3,7 @@ import type { IRedisCobuildLockProviderOptions } from './RedisCobuildLockProvide
3
3
  /**
4
4
  * @public
5
5
  */
6
- export declare type IRushRedisCobuildPluginOptions = IRedisCobuildLockProviderOptions;
6
+ export type IRushRedisCobuildPluginOptions = IRedisCobuildLockProviderOptions;
7
7
  /**
8
8
  * @public
9
9
  */
@@ -1 +1 @@
1
- {"version":3,"file":"RushRedisCobuildPlugin.d.ts","sourceRoot":"","sources":["../src/RushRedisCobuildPlugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,KAAK,EAAE,gCAAgC,EAA4B,MAAM,4BAA4B,CAAC;AAS7G;;GAEG;AACH,oBAAY,8BAA8B,GAAG,gCAAgC,CAAC;AAE9E;;GAEG;AACH,qBAAa,sBAAuB,YAAW,WAAW;IACjD,UAAU,EAAE,MAAM,CAAe;IAExC,OAAO,CAAC,QAAQ,CAAiC;gBAE9B,OAAO,EAAE,8BAA8B;IAInD,KAAK,CAAC,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,GAAG,IAAI;CAQnF"}
1
+ {"version":3,"file":"RushRedisCobuildPlugin.d.ts","sourceRoot":"","sources":["../src/RushRedisCobuildPlugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,KAAK,EAAE,gCAAgC,EAA4B,MAAM,4BAA4B,CAAC;AAS7G;;GAEG;AACH,MAAM,MAAM,8BAA8B,GAAG,gCAAgC,CAAC;AAE9E;;GAEG;AACH,qBAAa,sBAAuB,YAAW,WAAW;IACjD,UAAU,EAAE,MAAM,CAAe;IAExC,OAAO,CAAC,QAAQ,CAAiC;gBAE9B,OAAO,EAAE,8BAA8B;IAInD,KAAK,CAAC,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,GAAG,IAAI;CAQnF"}
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.34.4"
8
+ "packageVersion": "7.36.4"
9
9
  }
10
10
  ]
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rushstack/rush-redis-cobuild-plugin",
3
- "version": "5.97.1-pr3949.2",
3
+ "version": "5.101.0-pr3949.3",
4
4
  "description": "Rush plugin for Redis cobuild lock",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,22 +13,22 @@
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
15
  "@redis/client": "~1.5.5",
16
- "@rushstack/node-core-library": "3.55.2",
17
- "@rushstack/rush-sdk": "5.97.1-pr3949.2"
16
+ "@rushstack/node-core-library": "3.59.7",
17
+ "@rushstack/rush-sdk": "5.101.0-pr3949.3"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@types/heft-jest": "1.0.1",
21
21
  "@types/node": "14.18.36",
22
- "@microsoft/rush-lib": "5.97.1-pr3949.2",
23
- "@rushstack/eslint-config": "3.2.0",
24
- "@rushstack/heft": "0.50.0",
25
- "@rushstack/heft-node-rig": "1.12.6"
22
+ "@microsoft/rush-lib": "5.101.0-pr3949.3",
23
+ "@rushstack/eslint-config": "3.3.3",
24
+ "@rushstack/heft": "0.58.2",
25
+ "@rushstack/heft-node-rig": "2.2.22"
26
26
  },
27
27
  "scripts": {
28
28
  "build": "heft build --clean",
29
- "start": "heft test --clean --watch",
30
- "test": "heft test",
31
- "_phase:build": "heft build --clean",
32
- "_phase:test": "heft test --no-build"
29
+ "start": "heft test-watch",
30
+ "test": "heft test --clean",
31
+ "_phase:build": "heft run --only build -- --clean",
32
+ "_phase:test": "heft run --only test -- --clean"
33
33
  }
34
34
  }