@alex-dbs/coderuler 0.0.0-stage → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +175 -0
- package/README.md +218 -2
- package/dist/adapter/cli/commands/check.d.ts +3 -0
- package/dist/adapter/cli/commands/check.js +93 -0
- package/dist/adapter/cli/commands/deps.d.ts +3 -0
- package/dist/adapter/cli/commands/deps.js +4 -0
- package/dist/adapter/cli/commands/inspect.d.ts +3 -0
- package/dist/adapter/cli/commands/inspect.js +59 -0
- package/dist/adapter/cli/commands/rules.d.ts +10 -0
- package/dist/adapter/cli/commands/rules.js +17 -0
- package/dist/adapter/cli/commands/skill.d.ts +4 -0
- package/dist/adapter/cli/commands/skill.js +7 -0
- package/dist/adapter/cli/commands/test.d.ts +3 -0
- package/dist/adapter/cli/commands/test.js +78 -0
- package/dist/adapter/cli/commands/update.d.ts +3 -0
- package/dist/adapter/cli/commands/update.js +7 -0
- package/dist/adapter/cli/commands/validate.d.ts +3 -0
- package/dist/adapter/cli/commands/validate.js +12 -0
- package/dist/adapter/cli/exit.d.ts +7 -0
- package/dist/adapter/cli/exit.js +10 -0
- package/dist/adapter/cli/inspect-json.d.ts +9 -0
- package/dist/adapter/cli/inspect-json.js +160 -0
- package/dist/adapter/cli/inspect-usage.d.ts +9 -0
- package/dist/adapter/cli/inspect-usage.js +14 -0
- package/dist/adapter/cli/program.d.ts +4 -0
- package/dist/adapter/cli/program.js +216 -0
- package/dist/adapter/cli/progress.d.ts +22 -0
- package/dist/adapter/cli/progress.js +79 -0
- package/dist/adapter/cli/reports.d.ts +47 -0
- package/dist/adapter/cli/reports.js +735 -0
- package/dist/adapter/cli/self-update.d.ts +1 -0
- package/dist/adapter/cli/self-update.js +89 -0
- package/dist/adapter/cli/services.d.ts +101 -0
- package/dist/adapter/cli/services.js +1 -0
- package/dist/adapter/cli/text.d.ts +5 -0
- package/dist/adapter/cli/text.js +36 -0
- package/dist/adapter/cli/version.d.ts +1 -0
- package/dist/adapter/cli/version.js +3 -0
- package/dist/adapter/decision/jev.d.ts +11 -0
- package/dist/adapter/decision/jev.js +138 -0
- package/dist/adapter/filesystem/dependencies.d.ts +26 -0
- package/dist/adapter/filesystem/dependencies.js +577 -0
- package/dist/adapter/filesystem/dependency-installation.d.ts +11 -0
- package/dist/adapter/filesystem/dependency-installation.js +373 -0
- package/dist/adapter/filesystem/project.d.ts +14 -0
- package/dist/adapter/filesystem/project.js +387 -0
- package/dist/adapter/filesystem/rule-resolution.d.ts +4 -0
- package/dist/adapter/filesystem/rule-resolution.js +516 -0
- package/dist/adapter/filesystem/rule-test-worker.d.ts +17 -0
- package/dist/adapter/filesystem/rule-test-worker.js +47 -0
- package/dist/adapter/filesystem/rule-tests.d.ts +3 -0
- package/dist/adapter/filesystem/rule-tests.js +264 -0
- package/dist/adapter/filesystem/rules.d.ts +13 -0
- package/dist/adapter/filesystem/rules.js +242 -0
- package/dist/adapter/git/changes.d.ts +1 -0
- package/dist/adapter/git/changes.js +54 -0
- package/dist/adapter/git/environment.d.ts +1 -0
- package/dist/adapter/git/environment.js +10 -0
- package/dist/adapter/github/api.d.ts +5 -0
- package/dist/adapter/github/api.js +40 -0
- package/dist/adapter/github/comments.d.ts +3 -0
- package/dist/adapter/github/comments.js +235 -0
- package/dist/adapter/github/pull-request.d.ts +16 -0
- package/dist/adapter/github/pull-request.js +139 -0
- package/dist/adapter/scope/astgrep-languages.d.ts +5 -0
- package/dist/adapter/scope/astgrep-languages.js +119 -0
- package/dist/adapter/scope/astgrep.d.ts +14 -0
- package/dist/adapter/scope/astgrep.js +183 -0
- package/dist/adapter/scope/csv.d.ts +4 -0
- package/dist/adapter/scope/csv.js +158 -0
- package/dist/adapter/scope/file.d.ts +2 -0
- package/dist/adapter/scope/file.js +4 -0
- package/dist/adapter/scope/json.d.ts +7 -0
- package/dist/adapter/scope/json.js +132 -0
- package/dist/adapter/scope/lines.d.ts +10 -0
- package/dist/adapter/scope/lines.js +42 -0
- package/dist/adapter/scope/protobuf-options.d.ts +29 -0
- package/dist/adapter/scope/protobuf-options.js +64 -0
- package/dist/adapter/scope/protobuf-parser.d.ts +30 -0
- package/dist/adapter/scope/protobuf-parser.js +772 -0
- package/dist/adapter/scope/protobuf.d.ts +5 -0
- package/dist/adapter/scope/protobuf.js +53 -0
- package/dist/adapter/scope/structured.d.ts +10 -0
- package/dist/adapter/scope/structured.js +84 -0
- package/dist/adapter/scope/yaml.d.ts +7 -0
- package/dist/adapter/scope/yaml.js +224 -0
- package/dist/adapter/skills/cli.d.ts +10 -0
- package/dist/adapter/skills/cli.js +77 -0
- package/dist/app/concurrency.d.ts +1 -0
- package/dist/app/concurrency.js +44 -0
- package/dist/app/evaluators.d.ts +14 -0
- package/dist/app/evaluators.js +175 -0
- package/dist/app/execute.d.ts +2 -0
- package/dist/app/execute.js +427 -0
- package/dist/app/jsonpath.d.ts +1 -0
- package/dist/app/jsonpath.js +8 -0
- package/dist/app/ports.d.ts +165 -0
- package/dist/app/ports.js +1 -0
- package/dist/app/rule-tests.d.ts +20 -0
- package/dist/app/rule-tests.js +11 -0
- package/dist/app/rule.d.ts +121 -0
- package/dist/app/rule.js +24 -0
- package/dist/app/selection.d.ts +2 -0
- package/dist/app/selection.js +23 -0
- package/dist/app/usage.d.ts +2 -0
- package/dist/app/usage.js +13 -0
- package/dist/app/validate.d.ts +11 -0
- package/dist/app/validate.js +415 -0
- package/dist/app/violation-comments.d.ts +10 -0
- package/dist/app/violation-comments.js +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +11 -0
- package/dist/infra/runtime.d.ts +3 -0
- package/dist/infra/runtime.js +168 -0
- package/dist/infra/settings.d.ts +5 -0
- package/dist/infra/settings.js +91 -0
- package/package.json +86 -4
- package/skills/coderuler-new-rule/SKILL.md +141 -0
- package/skills/coderuler-new-rule/reference.md +447 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { lstat, mkdir, open, readFile, readlink, realpath, rename, rm, symlink, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { createRequire, registerHooks } from 'node:module';
|
|
5
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
6
|
+
import { setTimeout } from 'node:timers/promises';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
+
const sdkName = '@alex-dbs/coderuler';
|
|
9
|
+
const markerName = '.coderuler-install.json';
|
|
10
|
+
function code(error) {
|
|
11
|
+
return error !== null && typeof error === 'object' && 'code' in error ? String(error.code) : undefined;
|
|
12
|
+
}
|
|
13
|
+
function conflict(path) {
|
|
14
|
+
return new Error(`${path}: rule dependency installation conflict; preserve user files and resolve the conflict before running coderuler deps sync`);
|
|
15
|
+
}
|
|
16
|
+
async function stat(path) {
|
|
17
|
+
try {
|
|
18
|
+
return await lstat(path);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (code(error) === 'ENOENT')
|
|
22
|
+
return null;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function regularText(path) {
|
|
27
|
+
const info = await stat(path);
|
|
28
|
+
if (!info)
|
|
29
|
+
return null;
|
|
30
|
+
if (!info.isFile() || info.isSymbolicLink())
|
|
31
|
+
throw conflict(path);
|
|
32
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
33
|
+
try {
|
|
34
|
+
if (!(await handle.stat()).isFile())
|
|
35
|
+
throw conflict(path);
|
|
36
|
+
return await handle.readFile('utf8');
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await handle.close();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function directory(path, create = false) {
|
|
43
|
+
let info = await stat(path);
|
|
44
|
+
if (!info && create) {
|
|
45
|
+
try {
|
|
46
|
+
await mkdir(path);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (code(error) !== 'EEXIST')
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
info = await stat(path);
|
|
53
|
+
}
|
|
54
|
+
if (!info)
|
|
55
|
+
return false;
|
|
56
|
+
if (!info.isDirectory() || info.isSymbolicLink())
|
|
57
|
+
throw conflict(path);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
async function installation(path) {
|
|
61
|
+
if (!await directory(path))
|
|
62
|
+
return null;
|
|
63
|
+
const markerPath = join(path, markerName);
|
|
64
|
+
const text = await regularText(markerPath);
|
|
65
|
+
if (text === null)
|
|
66
|
+
throw conflict(path);
|
|
67
|
+
let marker;
|
|
68
|
+
try {
|
|
69
|
+
marker = JSON.parse(text);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw conflict(markerPath);
|
|
73
|
+
}
|
|
74
|
+
if (!marker || marker.version !== 1 || typeof marker.key !== 'string' || !/^(?:empty|[a-f0-9]{64})$/.test(marker.key)
|
|
75
|
+
|| !Array.isArray(marker.installedLocations) || !marker.installedLocations.every(location => typeof location === 'string'
|
|
76
|
+
&& /^(?:node_modules\/(?:@[^/\\]+\/)?[^/\\]+)(?:\/node_modules\/(?:@[^/\\]+\/)?[^/\\]+)*$/.test(location)
|
|
77
|
+
&& location.split('/').every(segment => segment !== '.' && segment !== '..'))
|
|
78
|
+
|| (marker.sdkTarget !== undefined && (typeof marker.sdkTarget !== 'string' || !isAbsolute(marker.sdkTarget))))
|
|
79
|
+
throw conflict(markerPath);
|
|
80
|
+
const info = await lstat(path, { bigint: true });
|
|
81
|
+
const markerInfo = await lstat(markerPath, { bigint: true });
|
|
82
|
+
return { identity: `${info.dev}:${info.ino}:${markerInfo.dev}:${markerInfo.ino}`, marker, text };
|
|
83
|
+
}
|
|
84
|
+
async function unchanged(path, expected) {
|
|
85
|
+
try {
|
|
86
|
+
const actual = await installation(path);
|
|
87
|
+
return actual?.identity === expected.identity && actual.text === expected.text;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function warn(onWarning, message) {
|
|
94
|
+
// A committed operation must not become a reported failure even if a warning sink throws.
|
|
95
|
+
try {
|
|
96
|
+
onWarning?.(message);
|
|
97
|
+
}
|
|
98
|
+
catch { /* Publication has already committed. */ }
|
|
99
|
+
}
|
|
100
|
+
export async function withRuleDependencyDirectoryLock(ownerDirectory, action, onWarning) {
|
|
101
|
+
ownerDirectory = await realpath(ownerDirectory);
|
|
102
|
+
const mutex = join(ownerDirectory, '.coderuler-deps-write');
|
|
103
|
+
const token = randomUUID();
|
|
104
|
+
const owner = join(mutex, 'owner.json');
|
|
105
|
+
const deadline = Date.now() + 30_000;
|
|
106
|
+
for (;;) {
|
|
107
|
+
try {
|
|
108
|
+
await mkdir(mutex);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (code(error) !== 'EEXIST')
|
|
113
|
+
throw error;
|
|
114
|
+
if (Date.now() >= deadline) {
|
|
115
|
+
let pid;
|
|
116
|
+
try {
|
|
117
|
+
const value = JSON.parse(await regularText(owner) ?? '{}');
|
|
118
|
+
if (value && typeof value === 'object' && 'pid' in value)
|
|
119
|
+
pid = value.pid;
|
|
120
|
+
}
|
|
121
|
+
catch { /* Owner may be publishing its record. */ }
|
|
122
|
+
throw new Error(`${mutex}: dependency directory busy or stale${typeof pid === 'number' ? ` (owner PID ${pid})` : ''}; remove the mutex only after confirming its owner exited, then run coderuler deps sync`);
|
|
123
|
+
}
|
|
124
|
+
await setTimeout(50);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
let succeeded = false;
|
|
128
|
+
try {
|
|
129
|
+
await writeFile(owner, JSON.stringify({ pid: process.pid, token }), { flag: 'wx' });
|
|
130
|
+
const result = await action();
|
|
131
|
+
succeeded = true;
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
let releaseError;
|
|
136
|
+
try {
|
|
137
|
+
const recorded = JSON.parse(await regularText(owner) ?? '{}');
|
|
138
|
+
if (recorded.token !== token)
|
|
139
|
+
throw new Error(`${mutex}: dependency mutex ownership changed; refusing to remove another owner's mutex`);
|
|
140
|
+
await rm(mutex, { recursive: true });
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
releaseError = error;
|
|
144
|
+
}
|
|
145
|
+
if (releaseError !== undefined && succeeded)
|
|
146
|
+
warn(onWarning, `Rule dependencies committed, but could not release ${mutex}: ${String(releaseError)}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function packageRoot(entry) {
|
|
150
|
+
let path = dirname(await realpath(entry));
|
|
151
|
+
for (;;) {
|
|
152
|
+
try {
|
|
153
|
+
const manifest = JSON.parse(await readFile(join(path, 'package.json'), 'utf8'));
|
|
154
|
+
if (manifest.name === sdkName)
|
|
155
|
+
return path;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (code(error) !== 'ENOENT')
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
const above = dirname(path);
|
|
162
|
+
if (above === path)
|
|
163
|
+
throw new Error(`${entry}: cannot locate the CodeRuler SDK package`);
|
|
164
|
+
path = above;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function sdkEntry(ownerDirectory) {
|
|
168
|
+
const probe = `coderuler-installation-sdk:${randomUUID()}`;
|
|
169
|
+
const parentURL = pathToFileURL(join(ownerDirectory, '.coderuler-sdk.js')).href;
|
|
170
|
+
const hook = registerHooks({
|
|
171
|
+
resolve(specifier, context, nextResolve) {
|
|
172
|
+
return specifier === probe ? nextResolve(sdkName, { ...context, parentURL, conditions: ['node', 'import'] }) : nextResolve(specifier, context);
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
try {
|
|
176
|
+
return fileURLToPath(import.meta.resolve(probe));
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
hook.deregister();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function linkTarget(path) {
|
|
183
|
+
if (!(await stat(path))?.isSymbolicLink())
|
|
184
|
+
return undefined;
|
|
185
|
+
return resolve(dirname(path), await readlink(path));
|
|
186
|
+
}
|
|
187
|
+
async function localSdk(ownerDirectory) {
|
|
188
|
+
const search = createRequire(pathToFileURL(join(ownerDirectory, '.coderuler-sdk.js'))).resolve.paths(sdkName) ?? [];
|
|
189
|
+
for (const modules of search) {
|
|
190
|
+
const candidate = join(modules, sdkName);
|
|
191
|
+
if (!await stat(candidate))
|
|
192
|
+
continue;
|
|
193
|
+
const markerText = await regularText(join(modules, markerName));
|
|
194
|
+
if (markerText !== null) {
|
|
195
|
+
let marker;
|
|
196
|
+
try {
|
|
197
|
+
marker = JSON.parse(markerText);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
throw conflict(join(modules, markerName));
|
|
201
|
+
}
|
|
202
|
+
if (marker.version === 1 && marker.sdkTarget !== undefined && await linkTarget(candidate) === marker.sdkTarget)
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const root = await packageRoot(sdkEntry(dirname(modules)));
|
|
207
|
+
if (root === await realpath(candidate))
|
|
208
|
+
return root;
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
if (!['MODULE_NOT_FOUND', 'ERR_MODULE_NOT_FOUND', 'ENOENT'].includes(code(error) ?? ''))
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return packageRoot(fileURLToPath(import.meta.url));
|
|
216
|
+
}
|
|
217
|
+
/** Called under the owner mutex. Only the SDK may be a generated package link. */
|
|
218
|
+
export async function prepareRuleDependencySdk(ownerDirectory, nodeModules) {
|
|
219
|
+
const generated = !await directory(nodeModules);
|
|
220
|
+
if (generated && nodeModules === join(ownerDirectory, 'node_modules')) {
|
|
221
|
+
// Publish SDK-only ownership with its directory, so failed initialization leaves no unowned files.
|
|
222
|
+
const temporary = join(ownerDirectory, `.coderuler-sdk-${randomUUID()}`);
|
|
223
|
+
await mkdir(temporary);
|
|
224
|
+
try {
|
|
225
|
+
const target = await prepareRuleDependencySdk(ownerDirectory, temporary);
|
|
226
|
+
if (target === undefined)
|
|
227
|
+
throw conflict(temporary);
|
|
228
|
+
const marker = { version: 1, key: 'empty', installedLocations: [], sdkTarget: target };
|
|
229
|
+
await writeFile(join(temporary, markerName), `${JSON.stringify(marker, null, 2)}\n`, { flag: 'wx' });
|
|
230
|
+
if (await stat(nodeModules))
|
|
231
|
+
throw conflict(nodeModules);
|
|
232
|
+
await rename(temporary, nodeModules);
|
|
233
|
+
return target;
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
await rm(temporary, { recursive: true, force: true });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (generated)
|
|
240
|
+
await directory(nodeModules, true);
|
|
241
|
+
const markerPath = join(nodeModules, markerName);
|
|
242
|
+
const text = await regularText(markerPath);
|
|
243
|
+
const owned = text === null ? null : await installation(nodeModules);
|
|
244
|
+
const scope = join(nodeModules, '@alex-dbs');
|
|
245
|
+
await directory(scope, true);
|
|
246
|
+
const leaf = join(scope, 'coderuler');
|
|
247
|
+
const existing = await stat(leaf);
|
|
248
|
+
if (existing && (owned?.marker.sdkTarget === undefined || await linkTarget(leaf) !== owned.marker.sdkTarget))
|
|
249
|
+
return undefined;
|
|
250
|
+
const target = await localSdk(ownerDirectory);
|
|
251
|
+
if (nodeModules !== join(ownerDirectory, 'node_modules') && target.startsWith(`${join(ownerDirectory, 'node_modules')}/`)) {
|
|
252
|
+
throw conflict(join(ownerDirectory, 'node_modules', sdkName));
|
|
253
|
+
}
|
|
254
|
+
if (!existing || await linkTarget(leaf) !== target) {
|
|
255
|
+
const temporary = join(scope, `.coderuler-sdk-${randomUUID()}`);
|
|
256
|
+
try {
|
|
257
|
+
await symlink(target, temporary, process.platform === 'win32' ? 'junction' : 'dir');
|
|
258
|
+
if (existing && await linkTarget(leaf) !== owned?.marker.sdkTarget)
|
|
259
|
+
throw conflict(leaf);
|
|
260
|
+
if (!existing && await stat(leaf))
|
|
261
|
+
throw conflict(leaf);
|
|
262
|
+
await rename(temporary, leaf);
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
await rm(temporary, { force: true });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Direct SDK-only sync may operate on an unowned directory. Do not claim its user files.
|
|
269
|
+
if (nodeModules === join(ownerDirectory, 'node_modules') && owned) {
|
|
270
|
+
const marker = { ...owned.marker, sdkTarget: target };
|
|
271
|
+
const temporary = join(nodeModules, `.coderuler-marker-${randomUUID()}`);
|
|
272
|
+
try {
|
|
273
|
+
await writeFile(temporary, `${JSON.stringify(marker, null, 2)}\n`, { flag: 'wx' });
|
|
274
|
+
if (!await unchanged(nodeModules, owned))
|
|
275
|
+
throw conflict(markerPath);
|
|
276
|
+
await rename(temporary, markerPath);
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
const previousTarget = owned.marker.sdkTarget;
|
|
280
|
+
if (previousTarget !== undefined && previousTarget !== target) {
|
|
281
|
+
if (!await unchanged(nodeModules, owned) || await linkTarget(leaf) !== target)
|
|
282
|
+
throw conflict(leaf);
|
|
283
|
+
const restore = join(scope, `.coderuler-sdk-${randomUUID()}`);
|
|
284
|
+
try {
|
|
285
|
+
await symlink(previousTarget, restore, process.platform === 'win32' ? 'junction' : 'dir');
|
|
286
|
+
if (await linkTarget(leaf) !== target)
|
|
287
|
+
throw conflict(leaf);
|
|
288
|
+
await rename(restore, leaf);
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
await rm(restore, { force: true });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
await rm(temporary, { force: true });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return target;
|
|
301
|
+
}
|
|
302
|
+
/** Takes ownership of stage. The caller holds the owner mutex throughout publication. */
|
|
303
|
+
export async function publishRuleDependencyInstallation(options) {
|
|
304
|
+
const { directory: ownerDirectory, stage, previousLock, nextLock, onWarning } = options;
|
|
305
|
+
const modules = join(ownerDirectory, 'node_modules');
|
|
306
|
+
const stagedModules = join(stage, 'node_modules');
|
|
307
|
+
const backup = join(stage, 'previous-node_modules');
|
|
308
|
+
const lock = join(ownerDirectory, 'coderuler.lock');
|
|
309
|
+
const temporaryLock = join(stage, 'next-coderuler.lock');
|
|
310
|
+
let old = null;
|
|
311
|
+
let published = null;
|
|
312
|
+
let movedOld = false;
|
|
313
|
+
if (dirname(stage) !== ownerDirectory || !await directory(stage))
|
|
314
|
+
throw conflict(stage);
|
|
315
|
+
try {
|
|
316
|
+
const next = await installation(stagedModules);
|
|
317
|
+
if (!next)
|
|
318
|
+
throw conflict(stagedModules);
|
|
319
|
+
if (await regularText(lock) !== previousLock)
|
|
320
|
+
throw conflict(lock);
|
|
321
|
+
old = await installation(modules);
|
|
322
|
+
if (await stat(backup))
|
|
323
|
+
throw conflict(backup);
|
|
324
|
+
if (nextLock !== previousLock)
|
|
325
|
+
await writeFile(temporaryLock, nextLock, { flag: 'wx' });
|
|
326
|
+
if (old) {
|
|
327
|
+
if (!await unchanged(modules, old))
|
|
328
|
+
throw conflict(modules);
|
|
329
|
+
await rename(modules, backup);
|
|
330
|
+
movedOld = true;
|
|
331
|
+
}
|
|
332
|
+
if (await stat(modules))
|
|
333
|
+
throw conflict(modules);
|
|
334
|
+
await rename(stagedModules, modules);
|
|
335
|
+
published = next;
|
|
336
|
+
if (!await unchanged(modules, published))
|
|
337
|
+
throw conflict(modules);
|
|
338
|
+
if (await regularText(lock) !== previousLock)
|
|
339
|
+
throw conflict(lock);
|
|
340
|
+
if (nextLock !== previousLock)
|
|
341
|
+
await rename(temporaryLock, lock);
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
try {
|
|
345
|
+
if (published) {
|
|
346
|
+
if (!await unchanged(modules, published))
|
|
347
|
+
throw conflict(modules);
|
|
348
|
+
await rename(modules, stagedModules);
|
|
349
|
+
}
|
|
350
|
+
if (movedOld) {
|
|
351
|
+
if (await stat(modules) || !old || !await unchanged(backup, old))
|
|
352
|
+
throw conflict(modules);
|
|
353
|
+
await rename(backup, modules);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (rollbackError) {
|
|
357
|
+
throw new Error(`${String(error)}; could not restore the previous installation: ${String(rollbackError)}. Preserved recovery files at ${stage}; resolve the conflict and run coderuler deps sync`, { cause: error });
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
await rm(stage, { recursive: true, force: true });
|
|
361
|
+
}
|
|
362
|
+
catch (cleanupError) {
|
|
363
|
+
warn(onWarning, `Could not clean failed rule dependency stage ${stage}: ${String(cleanupError)}`);
|
|
364
|
+
}
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
await rm(stage, { recursive: true, force: true });
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
warn(onWarning, `Rule dependencies committed, but could not clean ${stage}: ${String(error)}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Project } from '../../app/ports.js';
|
|
2
|
+
export declare function isReservedDirectory(name: string, rootLevel: boolean): boolean;
|
|
3
|
+
export interface ProjectOptions {
|
|
4
|
+
readonly excludeFiles?: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export interface ProjectTarget {
|
|
7
|
+
readonly root: string;
|
|
8
|
+
readonly path: string;
|
|
9
|
+
readonly kind: 'file' | 'directory';
|
|
10
|
+
contains(filepath: string): boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function traversablePath(root: string, filepath: string): Promise<string | undefined>;
|
|
13
|
+
export declare function createProject(root: string, options?: ProjectOptions): Project;
|
|
14
|
+
export declare function resolveTarget(root: string, target: string): Promise<ProjectTarget>;
|