ctxinit 0.1.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/LICENSE +21 -0
- package/README.md +484 -0
- package/bin/ctx.js +3 -0
- package/dist/analysis/index.d.ts +2 -0
- package/dist/analysis/index.d.ts.map +1 -0
- package/dist/analysis/index.js +18 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/static-analysis.d.ts +79 -0
- package/dist/analysis/static-analysis.d.ts.map +1 -0
- package/dist/analysis/static-analysis.js +279 -0
- package/dist/analysis/static-analysis.js.map +1 -0
- package/dist/bootstrap/index.d.ts +8 -0
- package/dist/bootstrap/index.d.ts.map +1 -0
- package/dist/bootstrap/index.js +13 -0
- package/dist/bootstrap/index.js.map +1 -0
- package/dist/bootstrap/orchestrator.d.ts +48 -0
- package/dist/bootstrap/orchestrator.d.ts.map +1 -0
- package/dist/bootstrap/orchestrator.js +363 -0
- package/dist/bootstrap/orchestrator.js.map +1 -0
- package/dist/bootstrap/validator.d.ts +25 -0
- package/dist/bootstrap/validator.d.ts.map +1 -0
- package/dist/bootstrap/validator.js +412 -0
- package/dist/bootstrap/validator.js.map +1 -0
- package/dist/build/atomic.d.ts +74 -0
- package/dist/build/atomic.d.ts.map +1 -0
- package/dist/build/atomic.js +235 -0
- package/dist/build/atomic.js.map +1 -0
- package/dist/build/index.d.ts +10 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +26 -0
- package/dist/build/index.js.map +1 -0
- package/dist/build/lock.d.ts +102 -0
- package/dist/build/lock.d.ts.map +1 -0
- package/dist/build/lock.js +297 -0
- package/dist/build/lock.js.map +1 -0
- package/dist/build/manifest.d.ts +138 -0
- package/dist/build/manifest.d.ts.map +1 -0
- package/dist/build/manifest.js +269 -0
- package/dist/build/manifest.js.map +1 -0
- package/dist/build/orchestrator.d.ts +103 -0
- package/dist/build/orchestrator.d.ts.map +1 -0
- package/dist/build/orchestrator.js +524 -0
- package/dist/build/orchestrator.js.map +1 -0
- package/dist/cli/bootstrap.d.ts +77 -0
- package/dist/cli/bootstrap.d.ts.map +1 -0
- package/dist/cli/bootstrap.js +527 -0
- package/dist/cli/bootstrap.js.map +1 -0
- package/dist/cli/build.d.ts +32 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/build.js +156 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/diff.d.ts +23 -0
- package/dist/cli/diff.d.ts.map +1 -0
- package/dist/cli/diff.js +226 -0
- package/dist/cli/diff.js.map +1 -0
- package/dist/cli/hooks.d.ts +29 -0
- package/dist/cli/hooks.d.ts.map +1 -0
- package/dist/cli/hooks.js +176 -0
- package/dist/cli/hooks.js.map +1 -0
- package/dist/cli/init.d.ts +53 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +254 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/lint.d.ts +46 -0
- package/dist/cli/lint.d.ts.map +1 -0
- package/dist/cli/lint.js +210 -0
- package/dist/cli/lint.js.map +1 -0
- package/dist/cli/migrate.d.ts +28 -0
- package/dist/cli/migrate.d.ts.map +1 -0
- package/dist/cli/migrate.js +350 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli/verify.d.ts +21 -0
- package/dist/cli/verify.d.ts.map +1 -0
- package/dist/cli/verify.js +209 -0
- package/dist/cli/verify.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +262 -0
- package/dist/cli.js.map +1 -0
- package/dist/compiler/agents-compiler.d.ts +24 -0
- package/dist/compiler/agents-compiler.d.ts.map +1 -0
- package/dist/compiler/agents-compiler.js +192 -0
- package/dist/compiler/agents-compiler.js.map +1 -0
- package/dist/compiler/base-compiler.d.ts +152 -0
- package/dist/compiler/base-compiler.d.ts.map +1 -0
- package/dist/compiler/base-compiler.js +180 -0
- package/dist/compiler/base-compiler.js.map +1 -0
- package/dist/compiler/claude-compiler.d.ts +24 -0
- package/dist/compiler/claude-compiler.d.ts.map +1 -0
- package/dist/compiler/claude-compiler.js +182 -0
- package/dist/compiler/claude-compiler.js.map +1 -0
- package/dist/compiler/cursor-compiler.d.ts +33 -0
- package/dist/compiler/cursor-compiler.d.ts.map +1 -0
- package/dist/compiler/cursor-compiler.js +136 -0
- package/dist/compiler/cursor-compiler.js.map +1 -0
- package/dist/compiler/index.d.ts +7 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +24 -0
- package/dist/compiler/index.js.map +1 -0
- package/dist/compiler/rule-selector.d.ts +115 -0
- package/dist/compiler/rule-selector.d.ts.map +1 -0
- package/dist/compiler/rule-selector.js +273 -0
- package/dist/compiler/rule-selector.js.map +1 -0
- package/dist/compiler/token-estimator.d.ts +74 -0
- package/dist/compiler/token-estimator.d.ts.map +1 -0
- package/dist/compiler/token-estimator.js +191 -0
- package/dist/compiler/token-estimator.js.map +1 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +18 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +48 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +175 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/git/gitignore.d.ts +59 -0
- package/dist/git/gitignore.d.ts.map +1 -0
- package/dist/git/gitignore.js +268 -0
- package/dist/git/gitignore.js.map +1 -0
- package/dist/git/hooks.d.ts +34 -0
- package/dist/git/hooks.d.ts.map +1 -0
- package/dist/git/hooks.js +129 -0
- package/dist/git/hooks.js.map +1 -0
- package/dist/git/husky.d.ts +52 -0
- package/dist/git/husky.d.ts.map +1 -0
- package/dist/git/husky.js +219 -0
- package/dist/git/husky.js.map +1 -0
- package/dist/git/index.d.ts +9 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +15 -0
- package/dist/git/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/base-provider.d.ts +43 -0
- package/dist/llm/base-provider.d.ts.map +1 -0
- package/dist/llm/base-provider.js +91 -0
- package/dist/llm/base-provider.js.map +1 -0
- package/dist/llm/index.d.ts +17 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +36 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/prompts/bootstrap-prompt.d.ts +27 -0
- package/dist/llm/prompts/bootstrap-prompt.d.ts.map +1 -0
- package/dist/llm/prompts/bootstrap-prompt.js +278 -0
- package/dist/llm/prompts/bootstrap-prompt.js.map +1 -0
- package/dist/llm/prompts/index.d.ts +5 -0
- package/dist/llm/prompts/index.d.ts.map +1 -0
- package/dist/llm/prompts/index.js +11 -0
- package/dist/llm/prompts/index.js.map +1 -0
- package/dist/llm/provider-factory.d.ts +27 -0
- package/dist/llm/provider-factory.d.ts.map +1 -0
- package/dist/llm/provider-factory.js +213 -0
- package/dist/llm/provider-factory.js.map +1 -0
- package/dist/llm/providers/claude-api.d.ts +21 -0
- package/dist/llm/providers/claude-api.d.ts.map +1 -0
- package/dist/llm/providers/claude-api.js +110 -0
- package/dist/llm/providers/claude-api.js.map +1 -0
- package/dist/llm/providers/claude-code.d.ts +21 -0
- package/dist/llm/providers/claude-code.d.ts.map +1 -0
- package/dist/llm/providers/claude-code.js +120 -0
- package/dist/llm/providers/claude-code.js.map +1 -0
- package/dist/llm/providers/codex-cli.d.ts +25 -0
- package/dist/llm/providers/codex-cli.d.ts.map +1 -0
- package/dist/llm/providers/codex-cli.js +129 -0
- package/dist/llm/providers/codex-cli.js.map +1 -0
- package/dist/llm/providers/cursor-cli.d.ts +24 -0
- package/dist/llm/providers/cursor-cli.d.ts.map +1 -0
- package/dist/llm/providers/cursor-cli.js +106 -0
- package/dist/llm/providers/cursor-cli.js.map +1 -0
- package/dist/llm/providers/gemini-api.d.ts +20 -0
- package/dist/llm/providers/gemini-api.d.ts.map +1 -0
- package/dist/llm/providers/gemini-api.js +121 -0
- package/dist/llm/providers/gemini-api.js.map +1 -0
- package/dist/llm/providers/gemini-cli.d.ts +20 -0
- package/dist/llm/providers/gemini-cli.d.ts.map +1 -0
- package/dist/llm/providers/gemini-cli.js +109 -0
- package/dist/llm/providers/gemini-cli.js.map +1 -0
- package/dist/llm/providers/interactive.d.ts +42 -0
- package/dist/llm/providers/interactive.d.ts.map +1 -0
- package/dist/llm/providers/interactive.js +200 -0
- package/dist/llm/providers/interactive.js.map +1 -0
- package/dist/llm/providers/openai-api.d.ts +21 -0
- package/dist/llm/providers/openai-api.d.ts.map +1 -0
- package/dist/llm/providers/openai-api.js +107 -0
- package/dist/llm/providers/openai-api.js.map +1 -0
- package/dist/llm/types.d.ts +128 -0
- package/dist/llm/types.d.ts.map +1 -0
- package/dist/llm/types.js +8 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/parser/index.d.ts +3 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +19 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/path-security.d.ts +40 -0
- package/dist/parser/path-security.d.ts.map +1 -0
- package/dist/parser/path-security.js +183 -0
- package/dist/parser/path-security.js.map +1 -0
- package/dist/parser/rule-parser.d.ts +50 -0
- package/dist/parser/rule-parser.d.ts.map +1 -0
- package/dist/parser/rule-parser.js +203 -0
- package/dist/parser/rule-parser.js.map +1 -0
- package/dist/schemas/config.d.ts +202 -0
- package/dist/schemas/config.d.ts.map +1 -0
- package/dist/schemas/config.js +96 -0
- package/dist/schemas/config.js.map +1 -0
- package/dist/schemas/index.d.ts +3 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +19 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/rule.d.ts +67 -0
- package/dist/schemas/rule.d.ts.map +1 -0
- package/dist/schemas/rule.js +44 -0
- package/dist/schemas/rule.js.map +1 -0
- package/package.json +69 -0
- package/templates/architecture.md +35 -0
- package/templates/bootstrap-prompt.md +242 -0
- package/templates/config.yaml +25 -0
- package/templates/project.md +44 -0
- package/templates/rules/example.md +36 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Atomic File Operations
|
|
4
|
+
*
|
|
5
|
+
* Provides safe file writing with:
|
|
6
|
+
* - Temp file → atomic rename pattern
|
|
7
|
+
* - Multi-file transactions (all-or-nothing)
|
|
8
|
+
* - Cleanup on error
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.getTempPath = getTempPath;
|
|
45
|
+
exports.atomicWrite = atomicWrite;
|
|
46
|
+
exports.atomicWriteSync = atomicWriteSync;
|
|
47
|
+
exports.transaction = transaction;
|
|
48
|
+
exports.cleanupStaleTempFiles = cleanupStaleTempFiles;
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
/**
|
|
52
|
+
* Generate temp file path for atomic write
|
|
53
|
+
*
|
|
54
|
+
* @param targetPath - Target file path
|
|
55
|
+
* @returns Temp file path with pid suffix
|
|
56
|
+
*/
|
|
57
|
+
function getTempPath(targetPath) {
|
|
58
|
+
const dir = path.dirname(targetPath);
|
|
59
|
+
const base = path.basename(targetPath);
|
|
60
|
+
return path.join(dir, `${base}.tmp.${process.pid}`);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Write file atomically using temp file + rename
|
|
64
|
+
*
|
|
65
|
+
* @param targetPath - Target file path
|
|
66
|
+
* @param content - Content to write
|
|
67
|
+
* @throws Error if write or rename fails
|
|
68
|
+
*/
|
|
69
|
+
async function atomicWrite(targetPath, content) {
|
|
70
|
+
const tempPath = getTempPath(targetPath);
|
|
71
|
+
const dir = path.dirname(targetPath);
|
|
72
|
+
// Ensure directory exists
|
|
73
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
74
|
+
try {
|
|
75
|
+
// Write to temp file
|
|
76
|
+
await fs.promises.writeFile(tempPath, content, 'utf-8');
|
|
77
|
+
// Atomic rename
|
|
78
|
+
await fs.promises.rename(tempPath, targetPath);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// Cleanup temp file on error
|
|
82
|
+
try {
|
|
83
|
+
await fs.promises.unlink(tempPath);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Ignore cleanup errors
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Write file atomically (synchronous version)
|
|
93
|
+
*
|
|
94
|
+
* @param targetPath - Target file path
|
|
95
|
+
* @param content - Content to write
|
|
96
|
+
* @throws Error if write or rename fails
|
|
97
|
+
*/
|
|
98
|
+
function atomicWriteSync(targetPath, content) {
|
|
99
|
+
const tempPath = getTempPath(targetPath);
|
|
100
|
+
const dir = path.dirname(targetPath);
|
|
101
|
+
// Ensure directory exists
|
|
102
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
103
|
+
try {
|
|
104
|
+
// Write to temp file
|
|
105
|
+
fs.writeFileSync(tempPath, content, 'utf-8');
|
|
106
|
+
// Atomic rename
|
|
107
|
+
fs.renameSync(tempPath, targetPath);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
// Cleanup temp file on error
|
|
111
|
+
try {
|
|
112
|
+
fs.unlinkSync(tempPath);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Ignore cleanup errors
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Execute a multi-file transaction (all-or-nothing)
|
|
122
|
+
*
|
|
123
|
+
* If any file fails to write, all temp files are cleaned up
|
|
124
|
+
* and no target files are modified.
|
|
125
|
+
*
|
|
126
|
+
* @param writes - Array of pending writes
|
|
127
|
+
* @returns Transaction result
|
|
128
|
+
*/
|
|
129
|
+
async function transaction(writes) {
|
|
130
|
+
const result = {
|
|
131
|
+
success: false,
|
|
132
|
+
writtenFiles: [],
|
|
133
|
+
errors: [],
|
|
134
|
+
};
|
|
135
|
+
// Track temp files for cleanup
|
|
136
|
+
const tempFiles = [];
|
|
137
|
+
try {
|
|
138
|
+
// Phase 1: Write all temp files
|
|
139
|
+
for (const write of writes) {
|
|
140
|
+
const tempPath = getTempPath(write.targetPath);
|
|
141
|
+
const dir = path.dirname(write.targetPath);
|
|
142
|
+
try {
|
|
143
|
+
// Ensure directory exists
|
|
144
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
145
|
+
// Write to temp file
|
|
146
|
+
await fs.promises.writeFile(tempPath, write.content, 'utf-8');
|
|
147
|
+
write.tempPath = tempPath;
|
|
148
|
+
tempFiles.push(tempPath);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
result.errors.push({
|
|
152
|
+
path: write.targetPath,
|
|
153
|
+
error: error,
|
|
154
|
+
});
|
|
155
|
+
// Cleanup all temp files on any error
|
|
156
|
+
await cleanupTempFiles(tempFiles);
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Phase 2: Atomic rename all files
|
|
161
|
+
for (const write of writes) {
|
|
162
|
+
if (!write.tempPath)
|
|
163
|
+
continue;
|
|
164
|
+
try {
|
|
165
|
+
await fs.promises.rename(write.tempPath, write.targetPath);
|
|
166
|
+
result.writtenFiles.push(write.targetPath);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
result.errors.push({
|
|
170
|
+
path: write.targetPath,
|
|
171
|
+
error: error,
|
|
172
|
+
});
|
|
173
|
+
// Cleanup remaining temp files
|
|
174
|
+
const remaining = tempFiles.filter(tf => !result.writtenFiles.some(wf => getTempPath(wf) === tf));
|
|
175
|
+
await cleanupTempFiles(remaining);
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
result.success = true;
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
// Unexpected error - cleanup all temp files
|
|
184
|
+
await cleanupTempFiles(tempFiles);
|
|
185
|
+
result.errors.push({
|
|
186
|
+
path: 'transaction',
|
|
187
|
+
error: error,
|
|
188
|
+
});
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Cleanup temp files
|
|
194
|
+
*
|
|
195
|
+
* @param tempFiles - Array of temp file paths to cleanup
|
|
196
|
+
*/
|
|
197
|
+
async function cleanupTempFiles(tempFiles) {
|
|
198
|
+
for (const tempPath of tempFiles) {
|
|
199
|
+
try {
|
|
200
|
+
await fs.promises.unlink(tempPath);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Ignore cleanup errors
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Cleanup stale temp files from a previous crashed build
|
|
209
|
+
*
|
|
210
|
+
* @param directory - Directory to scan for temp files
|
|
211
|
+
* @param pattern - Pattern to match temp files (default: *.tmp.*)
|
|
212
|
+
*/
|
|
213
|
+
async function cleanupStaleTempFiles(directory, pattern = /\.tmp\.\d+$/) {
|
|
214
|
+
const cleaned = [];
|
|
215
|
+
try {
|
|
216
|
+
const entries = await fs.promises.readdir(directory, { withFileTypes: true });
|
|
217
|
+
for (const entry of entries) {
|
|
218
|
+
if (entry.isFile() && pattern.test(entry.name)) {
|
|
219
|
+
const filePath = path.join(directory, entry.name);
|
|
220
|
+
try {
|
|
221
|
+
await fs.promises.unlink(filePath);
|
|
222
|
+
cleaned.push(filePath);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// Ignore cleanup errors
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Directory doesn't exist or not accessible
|
|
232
|
+
}
|
|
233
|
+
return cleaned;
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=atomic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic.js","sourceRoot":"","sources":["../../src/build/atomic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/build/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Build System
|
|
4
|
+
*
|
|
5
|
+
* Exports for the build system modules.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
19
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
__exportStar(require("./manifest"), exports);
|
|
23
|
+
__exportStar(require("./atomic"), exports);
|
|
24
|
+
__exportStar(require("./lock"), exports);
|
|
25
|
+
__exportStar(require("./orchestrator"), exports);
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/build/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;;;;;;;;;;;AAEH,6CAA2B;AAC3B,2CAAyB;AACzB,yCAAuB;AACvB,iDAA+B"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build Locking
|
|
3
|
+
*
|
|
4
|
+
* Prevents concurrent builds with:
|
|
5
|
+
* - Lock file with pid, timestamp, hostname
|
|
6
|
+
* - Stale lock detection (>5 minutes old)
|
|
7
|
+
* - Automatic cleanup on build completion
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Lock file content structure
|
|
11
|
+
*/
|
|
12
|
+
export interface LockInfo {
|
|
13
|
+
/** Process ID that holds the lock */
|
|
14
|
+
pid: number;
|
|
15
|
+
/** Timestamp when lock was acquired (Unix ms) */
|
|
16
|
+
timestamp: number;
|
|
17
|
+
/** Hostname of the machine holding the lock */
|
|
18
|
+
hostname: string;
|
|
19
|
+
/** Target being built */
|
|
20
|
+
target: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Lock acquisition result
|
|
24
|
+
*/
|
|
25
|
+
export interface LockResult {
|
|
26
|
+
/** Whether lock was acquired */
|
|
27
|
+
acquired: boolean;
|
|
28
|
+
/** Path to lock file */
|
|
29
|
+
lockPath: string;
|
|
30
|
+
/** If not acquired, info about existing lock */
|
|
31
|
+
existingLock?: LockInfo;
|
|
32
|
+
/** If stale lock was removed */
|
|
33
|
+
staleRemoved?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get lock file path
|
|
37
|
+
*
|
|
38
|
+
* @param projectRoot - Project root directory
|
|
39
|
+
* @returns Lock file path
|
|
40
|
+
*/
|
|
41
|
+
export declare function getLockPath(projectRoot: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Create lock info for current process
|
|
44
|
+
*
|
|
45
|
+
* @param target - Build target
|
|
46
|
+
* @returns Lock info
|
|
47
|
+
*/
|
|
48
|
+
export declare function createLockInfo(target: string): LockInfo;
|
|
49
|
+
/**
|
|
50
|
+
* Read existing lock file
|
|
51
|
+
*
|
|
52
|
+
* @param lockPath - Path to lock file
|
|
53
|
+
* @returns Lock info or null if not found/invalid
|
|
54
|
+
*/
|
|
55
|
+
export declare function readLock(lockPath: string): Promise<LockInfo | null>;
|
|
56
|
+
/**
|
|
57
|
+
* Check if a lock is stale (older than threshold)
|
|
58
|
+
*
|
|
59
|
+
* @param lock - Lock info to check
|
|
60
|
+
* @param thresholdMs - Stale threshold in milliseconds
|
|
61
|
+
* @returns true if lock is stale
|
|
62
|
+
*/
|
|
63
|
+
export declare function isLockStale(lock: LockInfo, thresholdMs?: number): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Check if process with given PID is still running
|
|
66
|
+
*
|
|
67
|
+
* @param pid - Process ID to check
|
|
68
|
+
* @returns true if process is running
|
|
69
|
+
*/
|
|
70
|
+
export declare function isProcessRunning(pid: number): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Acquire build lock
|
|
73
|
+
*
|
|
74
|
+
* @param projectRoot - Project root directory
|
|
75
|
+
* @param target - Build target
|
|
76
|
+
* @param staleThresholdMs - Stale lock threshold in milliseconds
|
|
77
|
+
* @returns Lock acquisition result
|
|
78
|
+
*/
|
|
79
|
+
export declare function acquireLock(projectRoot: string, target: string, staleThresholdMs?: number): Promise<LockResult>;
|
|
80
|
+
/**
|
|
81
|
+
* Release build lock
|
|
82
|
+
*
|
|
83
|
+
* @param projectRoot - Project root directory
|
|
84
|
+
* @returns true if lock was released
|
|
85
|
+
*/
|
|
86
|
+
export declare function releaseLock(projectRoot: string): Promise<boolean>;
|
|
87
|
+
/**
|
|
88
|
+
* Format lock info for display
|
|
89
|
+
*
|
|
90
|
+
* @param lock - Lock info to format
|
|
91
|
+
* @returns Human-readable lock description
|
|
92
|
+
*/
|
|
93
|
+
export declare function formatLockInfo(lock: LockInfo): string;
|
|
94
|
+
/**
|
|
95
|
+
* Create a lock guard for RAII-style lock management
|
|
96
|
+
*
|
|
97
|
+
* @param projectRoot - Project root directory
|
|
98
|
+
* @param target - Build target
|
|
99
|
+
* @returns Lock guard with release method
|
|
100
|
+
*/
|
|
101
|
+
export declare function withLock<T>(projectRoot: string, target: string, fn: () => Promise<T>): Promise<T>;
|
|
102
|
+
//# sourceMappingURL=lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/build/lock.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,gCAAgC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,YAAY,CAAC,EAAE,QAAQ,CAAC;IACxB,gCAAgC;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAYD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAOvD;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAWzE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,GAAE,MAA2B,GAAG,OAAO,CAG7F;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAUrD;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,gBAAgB,GAAE,MAA2B,GAC5C,OAAO,CAAC,UAAU,CAAC,CAiDrB;AAwCD;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA0BvE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAIrD;AAmBD;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAgBZ"}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Build Locking
|
|
4
|
+
*
|
|
5
|
+
* Prevents concurrent builds with:
|
|
6
|
+
* - Lock file with pid, timestamp, hostname
|
|
7
|
+
* - Stale lock detection (>5 minutes old)
|
|
8
|
+
* - Automatic cleanup on build completion
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.getLockPath = getLockPath;
|
|
45
|
+
exports.createLockInfo = createLockInfo;
|
|
46
|
+
exports.readLock = readLock;
|
|
47
|
+
exports.isLockStale = isLockStale;
|
|
48
|
+
exports.isProcessRunning = isProcessRunning;
|
|
49
|
+
exports.acquireLock = acquireLock;
|
|
50
|
+
exports.releaseLock = releaseLock;
|
|
51
|
+
exports.formatLockInfo = formatLockInfo;
|
|
52
|
+
exports.withLock = withLock;
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const os = __importStar(require("os"));
|
|
56
|
+
/**
|
|
57
|
+
* Default stale lock threshold (5 minutes in ms)
|
|
58
|
+
*/
|
|
59
|
+
const STALE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
60
|
+
/**
|
|
61
|
+
* Lock file name
|
|
62
|
+
*/
|
|
63
|
+
const LOCK_FILENAME = '.build.lock';
|
|
64
|
+
/**
|
|
65
|
+
* Get lock file path
|
|
66
|
+
*
|
|
67
|
+
* @param projectRoot - Project root directory
|
|
68
|
+
* @returns Lock file path
|
|
69
|
+
*/
|
|
70
|
+
function getLockPath(projectRoot) {
|
|
71
|
+
return path.join(projectRoot, '.context', LOCK_FILENAME);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create lock info for current process
|
|
75
|
+
*
|
|
76
|
+
* @param target - Build target
|
|
77
|
+
* @returns Lock info
|
|
78
|
+
*/
|
|
79
|
+
function createLockInfo(target) {
|
|
80
|
+
return {
|
|
81
|
+
pid: process.pid,
|
|
82
|
+
timestamp: Date.now(),
|
|
83
|
+
hostname: os.hostname(),
|
|
84
|
+
target,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Read existing lock file
|
|
89
|
+
*
|
|
90
|
+
* @param lockPath - Path to lock file
|
|
91
|
+
* @returns Lock info or null if not found/invalid
|
|
92
|
+
*/
|
|
93
|
+
async function readLock(lockPath) {
|
|
94
|
+
try {
|
|
95
|
+
const content = await fs.promises.readFile(lockPath, 'utf-8');
|
|
96
|
+
return JSON.parse(content);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error.code === 'ENOENT') {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
// Invalid JSON or other error - treat as no lock
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check if a lock is stale (older than threshold)
|
|
108
|
+
*
|
|
109
|
+
* @param lock - Lock info to check
|
|
110
|
+
* @param thresholdMs - Stale threshold in milliseconds
|
|
111
|
+
* @returns true if lock is stale
|
|
112
|
+
*/
|
|
113
|
+
function isLockStale(lock, thresholdMs = STALE_THRESHOLD_MS) {
|
|
114
|
+
const age = Date.now() - lock.timestamp;
|
|
115
|
+
return age > thresholdMs;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check if process with given PID is still running
|
|
119
|
+
*
|
|
120
|
+
* @param pid - Process ID to check
|
|
121
|
+
* @returns true if process is running
|
|
122
|
+
*/
|
|
123
|
+
function isProcessRunning(pid) {
|
|
124
|
+
try {
|
|
125
|
+
// kill with signal 0 checks if process exists without killing it
|
|
126
|
+
process.kill(pid, 0);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
// EPERM means process exists but we don't have permission
|
|
131
|
+
// ESRCH means process doesn't exist
|
|
132
|
+
return error.code === 'EPERM';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Acquire build lock
|
|
137
|
+
*
|
|
138
|
+
* @param projectRoot - Project root directory
|
|
139
|
+
* @param target - Build target
|
|
140
|
+
* @param staleThresholdMs - Stale lock threshold in milliseconds
|
|
141
|
+
* @returns Lock acquisition result
|
|
142
|
+
*/
|
|
143
|
+
async function acquireLock(projectRoot, target, staleThresholdMs = STALE_THRESHOLD_MS) {
|
|
144
|
+
const lockPath = getLockPath(projectRoot);
|
|
145
|
+
const lockDir = path.dirname(lockPath);
|
|
146
|
+
// Ensure directory exists
|
|
147
|
+
await fs.promises.mkdir(lockDir, { recursive: true });
|
|
148
|
+
// Check for existing lock
|
|
149
|
+
const existingLock = await readLock(lockPath);
|
|
150
|
+
if (existingLock) {
|
|
151
|
+
// Check if lock is stale
|
|
152
|
+
if (isLockStale(existingLock, staleThresholdMs)) {
|
|
153
|
+
// Remove stale lock
|
|
154
|
+
try {
|
|
155
|
+
await fs.promises.unlink(lockPath);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Ignore removal errors
|
|
159
|
+
}
|
|
160
|
+
// Try to acquire
|
|
161
|
+
return await tryAcquireLock(lockPath, target, true);
|
|
162
|
+
}
|
|
163
|
+
// Check if holding process is still running (same hostname only)
|
|
164
|
+
if (existingLock.hostname === os.hostname()) {
|
|
165
|
+
if (!isProcessRunning(existingLock.pid)) {
|
|
166
|
+
// Process is dead, remove lock
|
|
167
|
+
try {
|
|
168
|
+
await fs.promises.unlink(lockPath);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Ignore removal errors
|
|
172
|
+
}
|
|
173
|
+
// Try to acquire
|
|
174
|
+
return await tryAcquireLock(lockPath, target, true);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Lock is active - cannot acquire
|
|
178
|
+
return {
|
|
179
|
+
acquired: false,
|
|
180
|
+
lockPath,
|
|
181
|
+
existingLock,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
// No existing lock - try to acquire
|
|
185
|
+
return await tryAcquireLock(lockPath, target, false);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Try to acquire the lock (internal helper)
|
|
189
|
+
*/
|
|
190
|
+
async function tryAcquireLock(lockPath, target, staleRemoved) {
|
|
191
|
+
const lockInfo = createLockInfo(target);
|
|
192
|
+
try {
|
|
193
|
+
// Write lock file with exclusive flag
|
|
194
|
+
const fd = await fs.promises.open(lockPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL);
|
|
195
|
+
try {
|
|
196
|
+
await fd.writeFile(JSON.stringify(lockInfo, null, 2), 'utf-8');
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
await fd.close();
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
acquired: true,
|
|
203
|
+
lockPath,
|
|
204
|
+
staleRemoved,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
if (error.code === 'EEXIST') {
|
|
209
|
+
// Race condition - another process acquired the lock
|
|
210
|
+
const existingLock = await readLock(lockPath);
|
|
211
|
+
return {
|
|
212
|
+
acquired: false,
|
|
213
|
+
lockPath,
|
|
214
|
+
existingLock: existingLock || undefined,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Release build lock
|
|
222
|
+
*
|
|
223
|
+
* @param projectRoot - Project root directory
|
|
224
|
+
* @returns true if lock was released
|
|
225
|
+
*/
|
|
226
|
+
async function releaseLock(projectRoot) {
|
|
227
|
+
const lockPath = getLockPath(projectRoot);
|
|
228
|
+
try {
|
|
229
|
+
// Verify we own the lock before releasing
|
|
230
|
+
const existingLock = await readLock(lockPath);
|
|
231
|
+
if (!existingLock) {
|
|
232
|
+
// Lock doesn't exist - nothing to release
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
// Only release if we own the lock
|
|
236
|
+
if (existingLock.pid !== process.pid || existingLock.hostname !== os.hostname()) {
|
|
237
|
+
// We don't own this lock
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
await fs.promises.unlink(lockPath);
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
if (error.code === 'ENOENT') {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Format lock info for display
|
|
252
|
+
*
|
|
253
|
+
* @param lock - Lock info to format
|
|
254
|
+
* @returns Human-readable lock description
|
|
255
|
+
*/
|
|
256
|
+
function formatLockInfo(lock) {
|
|
257
|
+
const age = Date.now() - lock.timestamp;
|
|
258
|
+
const ageStr = formatDuration(age);
|
|
259
|
+
return `Build in progress by PID ${lock.pid} on ${lock.hostname} (${ageStr} ago) for target: ${lock.target}`;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Format duration in human-readable form
|
|
263
|
+
*/
|
|
264
|
+
function formatDuration(ms) {
|
|
265
|
+
const seconds = Math.floor(ms / 1000);
|
|
266
|
+
const minutes = Math.floor(seconds / 60);
|
|
267
|
+
const hours = Math.floor(minutes / 60);
|
|
268
|
+
if (hours > 0) {
|
|
269
|
+
return `${hours}h ${minutes % 60}m`;
|
|
270
|
+
}
|
|
271
|
+
if (minutes > 0) {
|
|
272
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
273
|
+
}
|
|
274
|
+
return `${seconds}s`;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Create a lock guard for RAII-style lock management
|
|
278
|
+
*
|
|
279
|
+
* @param projectRoot - Project root directory
|
|
280
|
+
* @param target - Build target
|
|
281
|
+
* @returns Lock guard with release method
|
|
282
|
+
*/
|
|
283
|
+
async function withLock(projectRoot, target, fn) {
|
|
284
|
+
const result = await acquireLock(projectRoot, target);
|
|
285
|
+
if (!result.acquired) {
|
|
286
|
+
throw new Error(result.existingLock
|
|
287
|
+
? `Build already in progress: ${formatLockInfo(result.existingLock)}`
|
|
288
|
+
: 'Failed to acquire build lock');
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
return await fn();
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
await releaseLock(projectRoot);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=lock.js.map
|