@lumpcode/cli-utils 0.1.0 → 0.2.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.
- package/dist/index.cjs +96 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.js +95 -23
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@ var core = require('@lumpcode/core');
|
|
|
5
5
|
var path = require('node:path');
|
|
6
6
|
var os = require('node:os');
|
|
7
7
|
var crypto = require('node:crypto');
|
|
8
|
+
var fsSync = require('node:fs');
|
|
8
9
|
var fs = require('node:fs/promises');
|
|
9
10
|
var jsYaml = require('js-yaml');
|
|
10
11
|
|
|
@@ -28,6 +29,7 @@ function _interopNamespaceDefault(e) {
|
|
|
28
29
|
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
29
30
|
var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os);
|
|
30
31
|
var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto);
|
|
32
|
+
var fsSync__namespace = /*#__PURE__*/_interopNamespaceDefault(fsSync);
|
|
31
33
|
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
32
34
|
|
|
33
35
|
const globalConfigFolderPath = path.join(os.homedir(), '.lumpcode');
|
|
@@ -99,6 +101,8 @@ function formatJsonFileContent(input) {
|
|
|
99
101
|
return trailingNewline ? `${json}\n` : json;
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
const WORKSPACE_LOCK_WAIT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
105
|
+
const WORKSPACE_LOCK_WAIT_LOG_INTERVAL_MS = 30000;
|
|
102
106
|
const WAIT_POLL_MS = 500;
|
|
103
107
|
function workspaceLocksDirPath(input) {
|
|
104
108
|
return path__namespace.join(input.globalConfigFolderPath, input.spec.locksSubdirName);
|
|
@@ -111,26 +115,53 @@ function workspaceLockFilePath(input) {
|
|
|
111
115
|
spec: input.spec,
|
|
112
116
|
}), `${hash}.lock.json`);
|
|
113
117
|
}
|
|
114
|
-
function
|
|
115
|
-
const { spec, workspacePath, holder } = input;
|
|
118
|
+
function formatHolderClause(holder, style) {
|
|
116
119
|
if (holder?.lumpName && holder.pid) {
|
|
117
|
-
return
|
|
118
|
-
`(pid ${holder.pid}, lump "${holder.lumpName}")
|
|
120
|
+
return style === 'busy'
|
|
121
|
+
? ` (pid ${holder.pid}, lump "${holder.lumpName}")`
|
|
122
|
+
: ` (held by lump "${holder.lumpName}" pid ${holder.pid})`;
|
|
119
123
|
}
|
|
120
124
|
if (holder?.pid) {
|
|
121
|
-
return (
|
|
122
|
-
|
|
125
|
+
return style === 'busy' ? ` (pid ${holder.pid})` : ` (held by pid ${holder.pid})`;
|
|
126
|
+
}
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
function formatBusyMessage(input) {
|
|
130
|
+
const { spec, workspacePath, holder } = input;
|
|
131
|
+
const clause = formatHolderClause(holder, 'busy');
|
|
132
|
+
if (clause) {
|
|
133
|
+
return (`${spec.workspaceLabel} "${workspacePath}" is in use by another lumpcode run` +
|
|
134
|
+
`${clause}. Wait for it to finish or stop the daemon before running again.`);
|
|
123
135
|
}
|
|
124
136
|
return (`${spec.workspaceLabel} "${workspacePath}" is in use by another lumpcode run. ` +
|
|
125
137
|
`Wait for it to finish or stop the daemon before running again.`);
|
|
126
138
|
}
|
|
127
139
|
function formatWorkspaceFileWaitMessage(input) {
|
|
128
140
|
const { spec, workspacePath, holder } = input;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
141
|
+
return (`${spec.waitLogNoun} busy at "${workspacePath}"` +
|
|
142
|
+
`${formatHolderClause(holder, 'held')}; waiting…`);
|
|
143
|
+
}
|
|
144
|
+
function formatWorkspaceFileStillWaitingMessage(input) {
|
|
145
|
+
const { spec, workspacePath, holder, elapsedMs } = input;
|
|
146
|
+
const elapsedSec = Math.max(1, Math.round(elapsedMs / 1000));
|
|
147
|
+
return (`${spec.waitLogNoun} busy at "${workspacePath}"` +
|
|
148
|
+
`${formatHolderClause(holder, 'held')}; still waiting (${elapsedSec}s)…`);
|
|
149
|
+
}
|
|
150
|
+
function formatWaitTimeoutMessage(input) {
|
|
151
|
+
const { spec, workspacePath, holder, waitTimeoutMs } = input;
|
|
152
|
+
return (`Waited ${waitTimeoutMs}ms for ${spec.waitLogNoun} "${workspacePath}"` +
|
|
153
|
+
`${formatHolderClause(holder, 'held')}; giving up.`);
|
|
154
|
+
}
|
|
155
|
+
function toBusyError(input) {
|
|
156
|
+
const { spec, workspacePath, holder, message, reason } = input;
|
|
157
|
+
return {
|
|
158
|
+
code: spec.busyCode,
|
|
159
|
+
message,
|
|
160
|
+
[spec.workspacePathField]: workspacePath,
|
|
161
|
+
...(holder?.pid !== undefined ? { holderPid: holder.pid } : {}),
|
|
162
|
+
...(holder?.lumpName !== undefined ? { holderLumpName: holder.lumpName } : {}),
|
|
163
|
+
...(reason !== undefined ? { reason } : {}),
|
|
164
|
+
};
|
|
134
165
|
}
|
|
135
166
|
async function readLockHolder(lockFilePath) {
|
|
136
167
|
const result = await readJsonFile({ filePath: lockFilePath, ifMissing: 'undefined' });
|
|
@@ -173,6 +204,8 @@ async function tryAcquireWorkspaceFileLockOnce(input) {
|
|
|
173
204
|
}
|
|
174
205
|
async function acquireWorkspaceFileLock(input) {
|
|
175
206
|
const { spec, globalConfigFolderPath, workspacePath, lumpName, mode, projectName, logger } = input;
|
|
207
|
+
const waitTimeoutMs = input.waitTimeoutMs ?? WORKSPACE_LOCK_WAIT_TIMEOUT_MS;
|
|
208
|
+
const waitLogIntervalMs = input.waitLogIntervalMs ?? WORKSPACE_LOCK_WAIT_LOG_INTERVAL_MS;
|
|
176
209
|
const normalizedWorkspacePath = path__namespace.resolve(workspacePath);
|
|
177
210
|
const locksDir = workspaceLocksDirPath({ globalConfigFolderPath, spec });
|
|
178
211
|
await fs__namespace.mkdir(locksDir, { recursive: true });
|
|
@@ -189,10 +222,12 @@ async function acquireWorkspaceFileLock(input) {
|
|
|
189
222
|
...(projectName !== undefined ? { projectName } : {}),
|
|
190
223
|
};
|
|
191
224
|
let loggedWait = false;
|
|
225
|
+
let lastWaitLogAt = 0;
|
|
226
|
+
const waitStartedAt = Date.now();
|
|
192
227
|
for (;;) {
|
|
193
228
|
const attempt = await tryAcquireWorkspaceFileLockOnce({ lockFilePath, payload, spec, logger });
|
|
194
229
|
if (attempt.status === 'acquired') {
|
|
195
|
-
const
|
|
230
|
+
const releaseAsync = async () => {
|
|
196
231
|
try {
|
|
197
232
|
const holder = await readLockHolder(lockFilePath);
|
|
198
233
|
if (holder?.pid === process.pid) {
|
|
@@ -203,6 +238,20 @@ async function acquireWorkspaceFileLock(input) {
|
|
|
203
238
|
// lock already gone
|
|
204
239
|
}
|
|
205
240
|
};
|
|
241
|
+
const release = Object.assign(releaseAsync, {
|
|
242
|
+
sync: () => {
|
|
243
|
+
try {
|
|
244
|
+
const raw = fsSync__namespace.readFileSync(lockFilePath, 'utf8');
|
|
245
|
+
const holder = JSON.parse(raw);
|
|
246
|
+
if (holder?.pid === process.pid) {
|
|
247
|
+
fsSync__namespace.unlinkSync(lockFilePath);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// lock already gone or unreadable
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
});
|
|
206
255
|
return core.success(release);
|
|
207
256
|
}
|
|
208
257
|
if (attempt.status === 'stale_removed') {
|
|
@@ -210,29 +259,51 @@ async function acquireWorkspaceFileLock(input) {
|
|
|
210
259
|
continue;
|
|
211
260
|
}
|
|
212
261
|
if (mode === 'fail') {
|
|
213
|
-
return core.failure({
|
|
214
|
-
|
|
262
|
+
return core.failure(toBusyError({
|
|
263
|
+
spec,
|
|
264
|
+
workspacePath: normalizedWorkspacePath,
|
|
265
|
+
holder: attempt.holder,
|
|
215
266
|
message: formatBusyMessage({
|
|
216
267
|
spec,
|
|
217
268
|
workspacePath: normalizedWorkspacePath,
|
|
218
269
|
holder: attempt.holder,
|
|
219
270
|
}),
|
|
220
|
-
|
|
221
|
-
...(attempt.holder?.pid !== undefined ? { holderPid: attempt.holder.pid } : {}),
|
|
222
|
-
...(attempt.holder?.lumpName !== undefined
|
|
223
|
-
? { holderLumpName: attempt.holder.lumpName }
|
|
224
|
-
: {}),
|
|
225
|
-
});
|
|
271
|
+
}));
|
|
226
272
|
}
|
|
227
|
-
|
|
228
|
-
|
|
273
|
+
const now = Date.now();
|
|
274
|
+
const elapsedMs = now - waitStartedAt;
|
|
275
|
+
if (elapsedMs >= waitTimeoutMs) {
|
|
276
|
+
return core.failure(toBusyError({
|
|
229
277
|
spec,
|
|
230
278
|
workspacePath: normalizedWorkspacePath,
|
|
231
279
|
holder: attempt.holder,
|
|
280
|
+
reason: 'waitTimedOut',
|
|
281
|
+
message: formatWaitTimeoutMessage({
|
|
282
|
+
spec,
|
|
283
|
+
workspacePath: normalizedWorkspacePath,
|
|
284
|
+
holder: attempt.holder,
|
|
285
|
+
waitTimeoutMs,
|
|
286
|
+
}),
|
|
232
287
|
}));
|
|
288
|
+
}
|
|
289
|
+
if (!loggedWait || now - lastWaitLogAt >= waitLogIntervalMs) {
|
|
290
|
+
logger?.info(loggedWait
|
|
291
|
+
? formatWorkspaceFileStillWaitingMessage({
|
|
292
|
+
spec,
|
|
293
|
+
workspacePath: normalizedWorkspacePath,
|
|
294
|
+
holder: attempt.holder,
|
|
295
|
+
elapsedMs,
|
|
296
|
+
})
|
|
297
|
+
: formatWorkspaceFileWaitMessage({
|
|
298
|
+
spec,
|
|
299
|
+
workspacePath: normalizedWorkspacePath,
|
|
300
|
+
holder: attempt.holder,
|
|
301
|
+
}));
|
|
233
302
|
loggedWait = true;
|
|
303
|
+
lastWaitLogAt = now;
|
|
234
304
|
}
|
|
235
|
-
|
|
305
|
+
const remainingMs = waitTimeoutMs - elapsedMs;
|
|
306
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(WAIT_POLL_MS, Math.max(0, remainingMs))));
|
|
236
307
|
}
|
|
237
308
|
}
|
|
238
309
|
|
|
@@ -276,6 +347,8 @@ async function acquireGitCommonDirLock(input) {
|
|
|
276
347
|
mode: input.lockMode,
|
|
277
348
|
projectName: input.projectName,
|
|
278
349
|
logger: input.logger,
|
|
350
|
+
waitTimeoutMs: input.waitTimeoutMs,
|
|
351
|
+
waitLogIntervalMs: input.waitLogIntervalMs,
|
|
279
352
|
});
|
|
280
353
|
}
|
|
281
354
|
/** Acquire, run `fn`, always release. */
|
package/dist/index.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ type LumpJsConfigStep<V extends LumpVariables = LumpVariables, SV extends StepVa
|
|
|
64
64
|
type LumpJsConfigSoloStep<V extends LumpVariables = LumpVariables, SV extends StepVariables = StepVariables> = LumpJsConfigStep<V, SV> | LumpJsConfigStep<V, SV>['promptTemplate'] | LumpJsConfigStep<V, SV>['promptFn'];
|
|
65
65
|
type LumpJsConfig<V extends LumpVariables = LumpVariables, SV extends StepVariables = StepVariables> = MergeObjs<Omit<{
|
|
66
66
|
[K in keyof RunLumpInput<V, SV>]?: NonNullable<RunLumpInput<V, SV>[K]> extends Function ? (RunLumpInput<V, SV>[K] | FilePath) : RunLumpInput<V, SV>[K];
|
|
67
|
-
}, 'gitCommitMessageFn' | 'projectRoot' | 'branchFn' | 'baseBranch' | 'setupWorkspaceFn' | 'teardownWorkspaceFn' | '
|
|
67
|
+
}, 'gitCommitMessageFn' | 'projectRoot' | 'branchFn' | 'baseBranch' | 'setupWorkspaceFn' | 'teardownWorkspaceFn' | 'gitAddCommitFn' | 'gitPushFn' | 'getContextListFn' | 'refreshRemoteTrackingRefsFn'>, {
|
|
68
68
|
/**
|
|
69
69
|
* Execution integration branch. Exact string, `BaseBranchFn`, or FilePath to a module.
|
|
70
70
|
* Omit → concrete effective discovery branch. Pattern strings are rejected at resolve.
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import path__default from 'node:path';
|
|
|
5
5
|
import * as os from 'node:os';
|
|
6
6
|
import os__default from 'node:os';
|
|
7
7
|
import * as crypto from 'node:crypto';
|
|
8
|
+
import * as fsSync from 'node:fs';
|
|
8
9
|
import * as fs from 'node:fs/promises';
|
|
9
10
|
import { load } from 'js-yaml';
|
|
10
11
|
|
|
@@ -77,6 +78,8 @@ function formatJsonFileContent(input) {
|
|
|
77
78
|
return trailingNewline ? `${json}\n` : json;
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
const WORKSPACE_LOCK_WAIT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
82
|
+
const WORKSPACE_LOCK_WAIT_LOG_INTERVAL_MS = 30000;
|
|
80
83
|
const WAIT_POLL_MS = 500;
|
|
81
84
|
function workspaceLocksDirPath(input) {
|
|
82
85
|
return path.join(input.globalConfigFolderPath, input.spec.locksSubdirName);
|
|
@@ -89,26 +92,53 @@ function workspaceLockFilePath(input) {
|
|
|
89
92
|
spec: input.spec,
|
|
90
93
|
}), `${hash}.lock.json`);
|
|
91
94
|
}
|
|
92
|
-
function
|
|
93
|
-
const { spec, workspacePath, holder } = input;
|
|
95
|
+
function formatHolderClause(holder, style) {
|
|
94
96
|
if (holder?.lumpName && holder.pid) {
|
|
95
|
-
return
|
|
96
|
-
`(pid ${holder.pid}, lump "${holder.lumpName}")
|
|
97
|
+
return style === 'busy'
|
|
98
|
+
? ` (pid ${holder.pid}, lump "${holder.lumpName}")`
|
|
99
|
+
: ` (held by lump "${holder.lumpName}" pid ${holder.pid})`;
|
|
97
100
|
}
|
|
98
101
|
if (holder?.pid) {
|
|
99
|
-
return (
|
|
100
|
-
|
|
102
|
+
return style === 'busy' ? ` (pid ${holder.pid})` : ` (held by pid ${holder.pid})`;
|
|
103
|
+
}
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
function formatBusyMessage(input) {
|
|
107
|
+
const { spec, workspacePath, holder } = input;
|
|
108
|
+
const clause = formatHolderClause(holder, 'busy');
|
|
109
|
+
if (clause) {
|
|
110
|
+
return (`${spec.workspaceLabel} "${workspacePath}" is in use by another lumpcode run` +
|
|
111
|
+
`${clause}. Wait for it to finish or stop the daemon before running again.`);
|
|
101
112
|
}
|
|
102
113
|
return (`${spec.workspaceLabel} "${workspacePath}" is in use by another lumpcode run. ` +
|
|
103
114
|
`Wait for it to finish or stop the daemon before running again.`);
|
|
104
115
|
}
|
|
105
116
|
function formatWorkspaceFileWaitMessage(input) {
|
|
106
117
|
const { spec, workspacePath, holder } = input;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
return (`${spec.waitLogNoun} busy at "${workspacePath}"` +
|
|
119
|
+
`${formatHolderClause(holder, 'held')}; waiting…`);
|
|
120
|
+
}
|
|
121
|
+
function formatWorkspaceFileStillWaitingMessage(input) {
|
|
122
|
+
const { spec, workspacePath, holder, elapsedMs } = input;
|
|
123
|
+
const elapsedSec = Math.max(1, Math.round(elapsedMs / 1000));
|
|
124
|
+
return (`${spec.waitLogNoun} busy at "${workspacePath}"` +
|
|
125
|
+
`${formatHolderClause(holder, 'held')}; still waiting (${elapsedSec}s)…`);
|
|
126
|
+
}
|
|
127
|
+
function formatWaitTimeoutMessage(input) {
|
|
128
|
+
const { spec, workspacePath, holder, waitTimeoutMs } = input;
|
|
129
|
+
return (`Waited ${waitTimeoutMs}ms for ${spec.waitLogNoun} "${workspacePath}"` +
|
|
130
|
+
`${formatHolderClause(holder, 'held')}; giving up.`);
|
|
131
|
+
}
|
|
132
|
+
function toBusyError(input) {
|
|
133
|
+
const { spec, workspacePath, holder, message, reason } = input;
|
|
134
|
+
return {
|
|
135
|
+
code: spec.busyCode,
|
|
136
|
+
message,
|
|
137
|
+
[spec.workspacePathField]: workspacePath,
|
|
138
|
+
...(holder?.pid !== undefined ? { holderPid: holder.pid } : {}),
|
|
139
|
+
...(holder?.lumpName !== undefined ? { holderLumpName: holder.lumpName } : {}),
|
|
140
|
+
...(reason !== undefined ? { reason } : {}),
|
|
141
|
+
};
|
|
112
142
|
}
|
|
113
143
|
async function readLockHolder(lockFilePath) {
|
|
114
144
|
const result = await readJsonFile({ filePath: lockFilePath, ifMissing: 'undefined' });
|
|
@@ -151,6 +181,8 @@ async function tryAcquireWorkspaceFileLockOnce(input) {
|
|
|
151
181
|
}
|
|
152
182
|
async function acquireWorkspaceFileLock(input) {
|
|
153
183
|
const { spec, globalConfigFolderPath, workspacePath, lumpName, mode, projectName, logger } = input;
|
|
184
|
+
const waitTimeoutMs = input.waitTimeoutMs ?? WORKSPACE_LOCK_WAIT_TIMEOUT_MS;
|
|
185
|
+
const waitLogIntervalMs = input.waitLogIntervalMs ?? WORKSPACE_LOCK_WAIT_LOG_INTERVAL_MS;
|
|
154
186
|
const normalizedWorkspacePath = path.resolve(workspacePath);
|
|
155
187
|
const locksDir = workspaceLocksDirPath({ globalConfigFolderPath, spec });
|
|
156
188
|
await fs.mkdir(locksDir, { recursive: true });
|
|
@@ -167,10 +199,12 @@ async function acquireWorkspaceFileLock(input) {
|
|
|
167
199
|
...(projectName !== undefined ? { projectName } : {}),
|
|
168
200
|
};
|
|
169
201
|
let loggedWait = false;
|
|
202
|
+
let lastWaitLogAt = 0;
|
|
203
|
+
const waitStartedAt = Date.now();
|
|
170
204
|
for (;;) {
|
|
171
205
|
const attempt = await tryAcquireWorkspaceFileLockOnce({ lockFilePath, payload, spec, logger });
|
|
172
206
|
if (attempt.status === 'acquired') {
|
|
173
|
-
const
|
|
207
|
+
const releaseAsync = async () => {
|
|
174
208
|
try {
|
|
175
209
|
const holder = await readLockHolder(lockFilePath);
|
|
176
210
|
if (holder?.pid === process.pid) {
|
|
@@ -181,6 +215,20 @@ async function acquireWorkspaceFileLock(input) {
|
|
|
181
215
|
// lock already gone
|
|
182
216
|
}
|
|
183
217
|
};
|
|
218
|
+
const release = Object.assign(releaseAsync, {
|
|
219
|
+
sync: () => {
|
|
220
|
+
try {
|
|
221
|
+
const raw = fsSync.readFileSync(lockFilePath, 'utf8');
|
|
222
|
+
const holder = JSON.parse(raw);
|
|
223
|
+
if (holder?.pid === process.pid) {
|
|
224
|
+
fsSync.unlinkSync(lockFilePath);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// lock already gone or unreadable
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
});
|
|
184
232
|
return success(release);
|
|
185
233
|
}
|
|
186
234
|
if (attempt.status === 'stale_removed') {
|
|
@@ -188,29 +236,51 @@ async function acquireWorkspaceFileLock(input) {
|
|
|
188
236
|
continue;
|
|
189
237
|
}
|
|
190
238
|
if (mode === 'fail') {
|
|
191
|
-
return failure({
|
|
192
|
-
|
|
239
|
+
return failure(toBusyError({
|
|
240
|
+
spec,
|
|
241
|
+
workspacePath: normalizedWorkspacePath,
|
|
242
|
+
holder: attempt.holder,
|
|
193
243
|
message: formatBusyMessage({
|
|
194
244
|
spec,
|
|
195
245
|
workspacePath: normalizedWorkspacePath,
|
|
196
246
|
holder: attempt.holder,
|
|
197
247
|
}),
|
|
198
|
-
|
|
199
|
-
...(attempt.holder?.pid !== undefined ? { holderPid: attempt.holder.pid } : {}),
|
|
200
|
-
...(attempt.holder?.lumpName !== undefined
|
|
201
|
-
? { holderLumpName: attempt.holder.lumpName }
|
|
202
|
-
: {}),
|
|
203
|
-
});
|
|
248
|
+
}));
|
|
204
249
|
}
|
|
205
|
-
|
|
206
|
-
|
|
250
|
+
const now = Date.now();
|
|
251
|
+
const elapsedMs = now - waitStartedAt;
|
|
252
|
+
if (elapsedMs >= waitTimeoutMs) {
|
|
253
|
+
return failure(toBusyError({
|
|
207
254
|
spec,
|
|
208
255
|
workspacePath: normalizedWorkspacePath,
|
|
209
256
|
holder: attempt.holder,
|
|
257
|
+
reason: 'waitTimedOut',
|
|
258
|
+
message: formatWaitTimeoutMessage({
|
|
259
|
+
spec,
|
|
260
|
+
workspacePath: normalizedWorkspacePath,
|
|
261
|
+
holder: attempt.holder,
|
|
262
|
+
waitTimeoutMs,
|
|
263
|
+
}),
|
|
210
264
|
}));
|
|
265
|
+
}
|
|
266
|
+
if (!loggedWait || now - lastWaitLogAt >= waitLogIntervalMs) {
|
|
267
|
+
logger?.info(loggedWait
|
|
268
|
+
? formatWorkspaceFileStillWaitingMessage({
|
|
269
|
+
spec,
|
|
270
|
+
workspacePath: normalizedWorkspacePath,
|
|
271
|
+
holder: attempt.holder,
|
|
272
|
+
elapsedMs,
|
|
273
|
+
})
|
|
274
|
+
: formatWorkspaceFileWaitMessage({
|
|
275
|
+
spec,
|
|
276
|
+
workspacePath: normalizedWorkspacePath,
|
|
277
|
+
holder: attempt.holder,
|
|
278
|
+
}));
|
|
211
279
|
loggedWait = true;
|
|
280
|
+
lastWaitLogAt = now;
|
|
212
281
|
}
|
|
213
|
-
|
|
282
|
+
const remainingMs = waitTimeoutMs - elapsedMs;
|
|
283
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(WAIT_POLL_MS, Math.max(0, remainingMs))));
|
|
214
284
|
}
|
|
215
285
|
}
|
|
216
286
|
|
|
@@ -254,6 +324,8 @@ async function acquireGitCommonDirLock(input) {
|
|
|
254
324
|
mode: input.lockMode,
|
|
255
325
|
projectName: input.projectName,
|
|
256
326
|
logger: input.logger,
|
|
327
|
+
waitTimeoutMs: input.waitTimeoutMs,
|
|
328
|
+
waitLogIntervalMs: input.waitLogIntervalMs,
|
|
257
329
|
});
|
|
258
330
|
}
|
|
259
331
|
/** Acquire, run `fn`, always release. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumpcode/cli-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Types, defineX helpers, and runtime utilities for Lumpcode lump configs and recipes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumpcode",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"test": "vitest run"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@lumpcode/cli-types": "^0.
|
|
47
|
-
"@lumpcode/core": "^0.
|
|
46
|
+
"@lumpcode/cli-types": "^0.2.0",
|
|
47
|
+
"@lumpcode/core": "^0.2.0",
|
|
48
48
|
"js-yaml": "^5.0.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|