@detergent-software/atk 8.6.2-dev.1 → 8.7.0-dev.10
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/CHANGELOG.md +109 -0
- package/build/commands/audit.d.ts.map +1 -1
- package/build/commands/audit.js +34 -11
- package/build/commands/audit.js.map +1 -1
- package/build/commands/browse.d.ts.map +1 -1
- package/build/commands/browse.js +23 -15
- package/build/commands/browse.js.map +1 -1
- package/build/commands/freeze.d.ts.map +1 -1
- package/build/commands/freeze.js +11 -6
- package/build/commands/freeze.js.map +1 -1
- package/build/commands/index.d.ts.map +1 -1
- package/build/commands/index.js +1 -0
- package/build/commands/index.js.map +1 -1
- package/build/commands/install.d.ts.map +1 -1
- package/build/commands/install.js +91 -26
- package/build/commands/install.js.map +1 -1
- package/build/commands/list.d.ts +2 -1
- package/build/commands/list.d.ts.map +1 -1
- package/build/commands/list.js +82 -66
- package/build/commands/list.js.map +1 -1
- package/build/commands/move.d.ts +28 -0
- package/build/commands/move.d.ts.map +1 -0
- package/build/commands/move.js +180 -0
- package/build/commands/move.js.map +1 -0
- package/build/commands/outdated.d.ts +2 -1
- package/build/commands/outdated.d.ts.map +1 -1
- package/build/commands/outdated.js +67 -24
- package/build/commands/outdated.js.map +1 -1
- package/build/commands/pin.d.ts.map +1 -1
- package/build/commands/pin.js +12 -7
- package/build/commands/pin.js.map +1 -1
- package/build/commands/prune.d.ts.map +1 -1
- package/build/commands/prune.js +17 -10
- package/build/commands/prune.js.map +1 -1
- package/build/commands/schema.d.ts +1 -1
- package/build/commands/sync.d.ts.map +1 -1
- package/build/commands/sync.js +22 -13
- package/build/commands/sync.js.map +1 -1
- package/build/commands/thaw.d.ts.map +1 -1
- package/build/commands/thaw.js +11 -6
- package/build/commands/thaw.js.map +1 -1
- package/build/commands/uninstall.d.ts.map +1 -1
- package/build/commands/uninstall.js +20 -15
- package/build/commands/uninstall.js.map +1 -1
- package/build/commands/unpin.d.ts.map +1 -1
- package/build/commands/unpin.js +11 -6
- package/build/commands/unpin.js.map +1 -1
- package/build/commands/update.d.ts.map +1 -1
- package/build/commands/update.js +11 -11
- package/build/commands/update.js.map +1 -1
- package/build/components/CrossScopeWarning.d.ts +12 -0
- package/build/components/CrossScopeWarning.d.ts.map +1 -0
- package/build/components/CrossScopeWarning.js +13 -0
- package/build/components/CrossScopeWarning.js.map +1 -0
- package/build/components/DryRunBanner.d.ts +3 -1
- package/build/components/DryRunBanner.d.ts.map +1 -1
- package/build/components/DryRunBanner.js +2 -2
- package/build/components/DryRunBanner.js.map +1 -1
- package/build/components/InstallConfirmBar.d.ts.map +1 -1
- package/build/components/InstallConfirmBar.js +1 -3
- package/build/components/InstallConfirmBar.js.map +1 -1
- package/build/components/InstallSummary.d.ts +3 -1
- package/build/components/InstallSummary.d.ts.map +1 -1
- package/build/components/InstallSummary.js +2 -2
- package/build/components/InstallSummary.js.map +1 -1
- package/build/components/ListActionBar.d.ts +4 -2
- package/build/components/ListActionBar.d.ts.map +1 -1
- package/build/components/ListActionBar.js +5 -4
- package/build/components/ListActionBar.js.map +1 -1
- package/build/components/ListBrowseList.d.ts +7 -6
- package/build/components/ListBrowseList.d.ts.map +1 -1
- package/build/components/ListBrowseList.js +10 -6
- package/build/components/ListBrowseList.js.map +1 -1
- package/build/components/StatusBadge.d.ts +1 -1
- package/build/components/StatusBadge.d.ts.map +1 -1
- package/build/components/StatusBadge.js +1 -0
- package/build/components/StatusBadge.js.map +1 -1
- package/build/components/SyncAllInSync.d.ts +3 -1
- package/build/components/SyncAllInSync.d.ts.map +1 -1
- package/build/components/SyncAllInSync.js +2 -2
- package/build/components/SyncAllInSync.js.map +1 -1
- package/build/components/SyncDriftedList.d.ts +3 -1
- package/build/components/SyncDriftedList.d.ts.map +1 -1
- package/build/components/SyncDriftedList.js +3 -3
- package/build/components/SyncDriftedList.js.map +1 -1
- package/build/components/SyncNoAssets.d.ts +3 -1
- package/build/components/SyncNoAssets.d.ts.map +1 -1
- package/build/components/SyncNoAssets.js +2 -2
- package/build/components/SyncNoAssets.js.map +1 -1
- package/build/components/index.d.ts +1 -0
- package/build/components/index.d.ts.map +1 -1
- package/build/components/index.js +1 -0
- package/build/components/index.js.map +1 -1
- package/build/hooks/useInitState.d.ts.map +1 -1
- package/build/hooks/useInitState.js +4 -2
- package/build/hooks/useInitState.js.map +1 -1
- package/build/hooks/useListActions.d.ts +19 -6
- package/build/hooks/useListActions.d.ts.map +1 -1
- package/build/hooks/useListActions.js +36 -30
- package/build/hooks/useListActions.js.map +1 -1
- package/build/hooks/useListState.d.ts +29 -12
- package/build/hooks/useListState.d.ts.map +1 -1
- package/build/hooks/useListState.js +22 -6
- package/build/hooks/useListState.js.map +1 -1
- package/build/hooks/usePruneState.d.ts +6 -0
- package/build/hooks/usePruneState.d.ts.map +1 -1
- package/build/hooks/usePruneState.js +6 -1
- package/build/hooks/usePruneState.js.map +1 -1
- package/build/hooks/useResolvedScope.d.ts +21 -0
- package/build/hooks/useResolvedScope.d.ts.map +1 -0
- package/build/hooks/useResolvedScope.js +15 -0
- package/build/hooks/useResolvedScope.js.map +1 -0
- package/build/hooks/useUninstallState.d.ts +6 -0
- package/build/hooks/useUninstallState.d.ts.map +1 -1
- package/build/hooks/useUninstallState.js +6 -1
- package/build/hooks/useUninstallState.js.map +1 -1
- package/build/lib/adapter.d.ts +47 -1
- package/build/lib/adapter.d.ts.map +1 -1
- package/build/lib/adapter.js +153 -10
- package/build/lib/adapter.js.map +1 -1
- package/build/lib/checksum.d.ts.map +1 -1
- package/build/lib/checksum.js +2 -1
- package/build/lib/checksum.js.map +1 -1
- package/build/lib/config-entries.d.ts +5 -3
- package/build/lib/config-entries.d.ts.map +1 -1
- package/build/lib/config-entries.js +7 -4
- package/build/lib/config-entries.js.map +1 -1
- package/build/lib/config-file.d.ts +15 -4
- package/build/lib/config-file.d.ts.map +1 -1
- package/build/lib/config-file.js +35 -10
- package/build/lib/config-file.js.map +1 -1
- package/build/lib/config-lock.d.ts +22 -0
- package/build/lib/config-lock.d.ts.map +1 -0
- package/build/lib/config-lock.js +67 -0
- package/build/lib/config-lock.js.map +1 -0
- package/build/lib/cross-scope.d.ts +50 -0
- package/build/lib/cross-scope.d.ts.map +1 -0
- package/build/lib/cross-scope.js +132 -0
- package/build/lib/cross-scope.js.map +1 -0
- package/build/lib/diagnostics.d.ts +15 -2
- package/build/lib/diagnostics.d.ts.map +1 -1
- package/build/lib/diagnostics.js +152 -10
- package/build/lib/diagnostics.js.map +1 -1
- package/build/lib/diff-command.d.ts.map +1 -1
- package/build/lib/diff-command.js +7 -7
- package/build/lib/diff-command.js.map +1 -1
- package/build/lib/hook-config.d.ts +7 -0
- package/build/lib/hook-config.d.ts.map +1 -1
- package/build/lib/hook-config.js +11 -0
- package/build/lib/hook-config.js.map +1 -1
- package/build/lib/init.d.ts.map +1 -1
- package/build/lib/init.js +2 -1
- package/build/lib/init.js.map +1 -1
- package/build/lib/installed-for-tool.d.ts +2 -1
- package/build/lib/installed-for-tool.d.ts.map +1 -1
- package/build/lib/installed-for-tool.js +7 -4
- package/build/lib/installed-for-tool.js.map +1 -1
- package/build/lib/installer.d.ts +8 -4
- package/build/lib/installer.d.ts.map +1 -1
- package/build/lib/installer.js +153 -57
- package/build/lib/installer.js.map +1 -1
- package/build/lib/lockfile.d.ts +1 -1
- package/build/lib/lockfile.d.ts.map +1 -1
- package/build/lib/lockfile.js +2 -2
- package/build/lib/lockfile.js.map +1 -1
- package/build/lib/mover.d.ts +103 -0
- package/build/lib/mover.d.ts.map +1 -0
- package/build/lib/mover.js +375 -0
- package/build/lib/mover.js.map +1 -0
- package/build/lib/multi-tool-install.js +1 -1
- package/build/lib/multi-tool-install.js.map +1 -1
- package/build/lib/owned-paths.d.ts.map +1 -1
- package/build/lib/owned-paths.js +8 -8
- package/build/lib/owned-paths.js.map +1 -1
- package/build/lib/read-scopes.d.ts +32 -0
- package/build/lib/read-scopes.d.ts.map +1 -0
- package/build/lib/read-scopes.js +55 -0
- package/build/lib/read-scopes.js.map +1 -0
- package/build/lib/root-path.d.ts +7 -0
- package/build/lib/root-path.d.ts.map +1 -0
- package/build/lib/root-path.js +10 -0
- package/build/lib/root-path.js.map +1 -0
- package/build/lib/schemas/adapter.d.ts +80 -1
- package/build/lib/schemas/adapter.d.ts.map +1 -1
- package/build/lib/schemas/adapter.js +48 -0
- package/build/lib/schemas/adapter.js.map +1 -1
- package/build/lib/schemas/lockfile.d.ts +2 -0
- package/build/lib/schemas/lockfile.d.ts.map +1 -1
- package/build/lib/schemas/lockfile.js +8 -2
- package/build/lib/schemas/lockfile.js.map +1 -1
- package/build/lib/scope.d.ts +11 -0
- package/build/lib/scope.d.ts.map +1 -1
- package/build/lib/scope.js +12 -0
- package/build/lib/scope.js.map +1 -1
- package/build/lib/sync.d.ts +4 -3
- package/build/lib/sync.d.ts.map +1 -1
- package/build/lib/sync.js +12 -5
- package/build/lib/sync.js.map +1 -1
- package/build/lib/toml-edit.d.ts +10 -0
- package/build/lib/toml-edit.d.ts.map +1 -0
- package/build/lib/toml-edit.js +311 -0
- package/build/lib/toml-edit.js.map +1 -0
- package/build/lib/tool-resolver.d.ts +5 -0
- package/build/lib/tool-resolver.d.ts.map +1 -1
- package/build/lib/tool-resolver.js +16 -12
- package/build/lib/tool-resolver.js.map +1 -1
- package/build/lib/uninstaller.d.ts +14 -1
- package/build/lib/uninstaller.d.ts.map +1 -1
- package/build/lib/uninstaller.js +73 -28
- package/build/lib/uninstaller.js.map +1 -1
- package/build/lib/updater.d.ts +5 -4
- package/build/lib/updater.d.ts.map +1 -1
- package/build/lib/updater.js +18 -12
- package/build/lib/updater.js.map +1 -1
- package/build/lib/write-file.d.ts +17 -4
- package/build/lib/write-file.d.ts.map +1 -1
- package/build/lib/write-file.js +60 -10
- package/build/lib/write-file.js.map +1 -1
- package/package.json +1 -1
- package/tool-adapters/claude-code.json +44 -0
- package/tool-adapters/codex-cli.json +28 -0
- package/tool-adapters/copilot-cli.json +46 -2
package/build/lib/config-file.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { readFile, unlink } from 'node:fs/promises';
|
|
3
3
|
import { extname } from 'node:path';
|
|
4
4
|
import { parse as parseToml } from 'smol-toml';
|
|
5
|
-
import {
|
|
5
|
+
import { editTomlTables } from './toml-edit.js';
|
|
6
|
+
import { writeJsonFile, writeTextFile, writeTomlFile } from './write-file.js';
|
|
6
7
|
/**
|
|
7
8
|
* Infer the config format from a file path. `.toml` (any case) → `toml`,
|
|
8
9
|
* everything else → `json`. Used where only a lockfile entry (file + key) is
|
|
@@ -38,6 +39,20 @@ export async function readConfigFile(filePath, format) {
|
|
|
38
39
|
const raw = await readFile(filePath, 'utf-8');
|
|
39
40
|
return parseConfig(raw, format);
|
|
40
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Delete a config file that removing ATK's entries left with no keys. A TOML file that still holds
|
|
44
|
+
* the user's comments is kept instead, with only ATK's tables taken out of it.
|
|
45
|
+
*/
|
|
46
|
+
export async function removeEmptiedConfigFile(filePath, format, options = {}) {
|
|
47
|
+
if (format === 'toml') {
|
|
48
|
+
const remaining = editTomlTables(await readFile(filePath, 'utf-8'), {});
|
|
49
|
+
if (remaining !== undefined && remaining.trim() !== '') {
|
|
50
|
+
await writeTextFile(filePath, remaining, options);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
await unlink(filePath);
|
|
55
|
+
}
|
|
41
56
|
/**
|
|
42
57
|
* Resolve the format for a placement's config file: an explicit
|
|
43
58
|
* `configFormat` wins, otherwise the file extension decides.
|
|
@@ -48,22 +63,32 @@ export function resolveConfigFormat(filePath, placement) {
|
|
|
48
63
|
/**
|
|
49
64
|
* Serialize and write a config file in the given format, idempotently.
|
|
50
65
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
66
|
+
* An existing TOML file is edited in place ({@link editTomlTables}): only the tables that changed
|
|
67
|
+
* are rewritten, so the user's comments and formatting stay. When the change can't be made that way
|
|
68
|
+
* (it isn't one of the `section.child` tables ATK writes, or the file defines one inline), the whole
|
|
69
|
+
* document is re-serialized, which drops the comments; a one-line warning is then emitted through
|
|
70
|
+
* `onProgress` so the loss is visible rather than silent.
|
|
71
|
+
*
|
|
72
|
+
* `options.atomic` writes through a temp file and a rename, for a file the tool rewrites while it
|
|
73
|
+
* runs (a placement with `configLockFile`).
|
|
54
74
|
*/
|
|
55
|
-
export async function writeConfigFile(filePath, data, format, onProgress) {
|
|
75
|
+
export async function writeConfigFile(filePath, data, format, onProgress, options = {}) {
|
|
56
76
|
if (format === 'json') {
|
|
57
|
-
await writeJsonFile(filePath, data);
|
|
77
|
+
await writeJsonFile(filePath, data, options);
|
|
58
78
|
return;
|
|
59
79
|
}
|
|
60
|
-
if (
|
|
80
|
+
if (existsSync(filePath)) {
|
|
61
81
|
const existing = await readFile(filePath, 'utf-8');
|
|
82
|
+
const edited = editTomlTables(existing, data);
|
|
83
|
+
if (edited !== undefined) {
|
|
84
|
+
await writeTextFile(filePath, edited, options);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
62
87
|
if (/^\s*#/m.test(existing)) {
|
|
63
|
-
onProgress(`Warning: rewriting ${filePath} — TOML comments and custom formatting are not preserved`);
|
|
88
|
+
onProgress?.(`Warning: rewriting ${filePath} — TOML comments and custom formatting are not preserved`);
|
|
64
89
|
}
|
|
65
90
|
}
|
|
66
|
-
await writeTomlFile(filePath, data);
|
|
91
|
+
await writeTomlFile(filePath, data, options);
|
|
67
92
|
}
|
|
68
93
|
/** Recursively drop null/undefined members from plain objects (arrays are mapped, not filtered). */
|
|
69
94
|
function stripNullish(value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-file.js","sourceRoot":"","sources":["../../src/lib/config-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"config-file.js","sourceRoot":"","sources":["../../src/lib/config-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PlacementConfig } from './schemas/adapter.js';
|
|
2
|
+
/**
|
|
3
|
+
* The lock to hold while writing `file` (relative to `root`): the placement's `configLockFile`,
|
|
4
|
+
* kept where it sits relative to `configFile`, when `file` is a copy of the placement's
|
|
5
|
+
* `configFile` (the same file name). That also covers one recorded before `CLAUDE_CONFIG_DIR`
|
|
6
|
+
* moved the file. Otherwise none.
|
|
7
|
+
*/
|
|
8
|
+
export declare function configLockPath(placement: Pick<PlacementConfig, 'configFile' | 'configLockFile'> | undefined, root: string, file: string): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Run `fn` holding a tool's own config lock (a placement's `configLockFile`, such as Claude
|
|
11
|
+
* Code's `.claude.json.lock`), so a read-merge-write of a file the tool rewrites while it runs
|
|
12
|
+
* doesn't interleave with one of its own writes.
|
|
13
|
+
*
|
|
14
|
+
* The lock is a directory made with `mkdir`, the way the tool's `proper-lockfile` takes it.
|
|
15
|
+
* It is best effort: the tool doesn't wait for it either (ATK-324), so a lock still held after
|
|
16
|
+
* `waitMs` is left alone and `fn` runs without it. Only a lock this call made is removed.
|
|
17
|
+
* Without a `lockPath`, `fn` just runs.
|
|
18
|
+
*/
|
|
19
|
+
export declare function withConfigLock<T>(lockPath: string | undefined, fn: () => Promise<T>, { waitMs }?: {
|
|
20
|
+
waitMs?: number;
|
|
21
|
+
}): Promise<T>;
|
|
22
|
+
//# sourceMappingURL=config-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-lock.d.ts","sourceRoot":"","sources":["../../src/lib/config-lock.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAS5D;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE,YAAY,GAAG,gBAAgB,CAAC,GAAG,SAAS,EAC7E,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAWpB;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,EAAE,MAAqB,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,OAAO,CAAC,CAAC,CAAC,CAaZ"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { mkdir, rmdir } from 'node:fs/promises';
|
|
2
|
+
import { join, posix } from 'node:path';
|
|
3
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
4
|
+
import { normalizeConfigPath } from './config-entries.js';
|
|
5
|
+
import { fromRoot } from './root-path.js';
|
|
6
|
+
/** How long a held lock is waited for before going ahead without it. */
|
|
7
|
+
const LOCK_WAIT_MS = 1000;
|
|
8
|
+
const LOCK_RETRY_MS = 50;
|
|
9
|
+
/**
|
|
10
|
+
* The lock to hold while writing `file` (relative to `root`): the placement's `configLockFile`,
|
|
11
|
+
* kept where it sits relative to `configFile`, when `file` is a copy of the placement's
|
|
12
|
+
* `configFile` (the same file name). That also covers one recorded before `CLAUDE_CONFIG_DIR`
|
|
13
|
+
* moved the file. Otherwise none.
|
|
14
|
+
*/
|
|
15
|
+
export function configLockPath(placement, root, file) {
|
|
16
|
+
if (!placement?.configLockFile || !placement.configFile) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const configFile = normalizeConfigPath(placement.configFile);
|
|
20
|
+
const recorded = normalizeConfigPath(file);
|
|
21
|
+
if (posix.basename(recorded) !== posix.basename(configFile)) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const lockFromConfig = posix.relative(posix.dirname(configFile), normalizeConfigPath(placement.configLockFile));
|
|
25
|
+
return join(fromRoot(root, posix.dirname(recorded)), lockFromConfig);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Run `fn` holding a tool's own config lock (a placement's `configLockFile`, such as Claude
|
|
29
|
+
* Code's `.claude.json.lock`), so a read-merge-write of a file the tool rewrites while it runs
|
|
30
|
+
* doesn't interleave with one of its own writes.
|
|
31
|
+
*
|
|
32
|
+
* The lock is a directory made with `mkdir`, the way the tool's `proper-lockfile` takes it.
|
|
33
|
+
* It is best effort: the tool doesn't wait for it either (ATK-324), so a lock still held after
|
|
34
|
+
* `waitMs` is left alone and `fn` runs without it. Only a lock this call made is removed.
|
|
35
|
+
* Without a `lockPath`, `fn` just runs.
|
|
36
|
+
*/
|
|
37
|
+
export async function withConfigLock(lockPath, fn, { waitMs = LOCK_WAIT_MS } = {}) {
|
|
38
|
+
if (lockPath === undefined) {
|
|
39
|
+
return fn();
|
|
40
|
+
}
|
|
41
|
+
const acquired = await tryLock(lockPath, waitMs);
|
|
42
|
+
try {
|
|
43
|
+
return await fn();
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
if (acquired) {
|
|
47
|
+
await rmdir(lockPath).catch(() => undefined);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Make the lock directory, retrying while another process holds it, until `waitMs` has passed. */
|
|
52
|
+
async function tryLock(lockPath, waitMs) {
|
|
53
|
+
const deadline = Date.now() + waitMs;
|
|
54
|
+
for (;;) {
|
|
55
|
+
try {
|
|
56
|
+
await mkdir(lockPath);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error.code !== 'EEXIST' || Date.now() >= deadline) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
await sleep(LOCK_RETRY_MS);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=config-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-lock.js","sourceRoot":"","sources":["../../src/lib/config-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAI3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,wEAAwE;AACxE,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,aAAa,GAAG,EAAE,CAAC;AAEzB;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,SAA6E,EAC7E,IAAY,EACZ,IAAY;IAEZ,IAAI,CAAC,SAAS,EAAE,cAAc,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC;QACxD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,UAAU,GAAG,mBAAmB,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5D,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,cAAc,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC;IAChH,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;AACvE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAA4B,EAC5B,EAAoB,EACpB,EAAE,MAAM,GAAG,YAAY,KAA0B,EAAE;IAEnD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACjD,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;YAAS,CAAC;QACT,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;AACH,CAAC;AAED,mGAAmG;AACnG,KAAK,UAAU,OAAO,CAAC,QAAgB,EAAE,MAAc;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;IACrC,SAAS,CAAC;QACR,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;gBACjF,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QACD,MAAM,KAAK,CAAC,aAAa,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { AssetType } from './schemas/index.js';
|
|
2
|
+
import type { InstalledAsset } from './schemas/lockfile.js';
|
|
3
|
+
import { type Notice } from './notices.js';
|
|
4
|
+
import { type ScopeClash } from './schemas/adapter.js';
|
|
5
|
+
import { type ResolvedScope, type Scope } from './scope.js';
|
|
6
|
+
/** Which copy one tool uses of an asset installed at both scopes. */
|
|
7
|
+
export interface CrossScopeClash {
|
|
8
|
+
displayName: string;
|
|
9
|
+
tool: string;
|
|
10
|
+
uses: ScopeClash;
|
|
11
|
+
}
|
|
12
|
+
/** An asset being installed at one scope that the other scope already has. */
|
|
13
|
+
export interface CrossScopeInstall {
|
|
14
|
+
/** Each tool that has both copies and a known rule, and the copy it uses. */
|
|
15
|
+
clash: CrossScopeClash[];
|
|
16
|
+
name: string;
|
|
17
|
+
org?: string;
|
|
18
|
+
/** The other scope's root. */
|
|
19
|
+
root: string;
|
|
20
|
+
/** The scope that already has it. */
|
|
21
|
+
scope: Scope;
|
|
22
|
+
type: AssetType;
|
|
23
|
+
/** The version the other scope has. */
|
|
24
|
+
version: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The `CrossScopeInstall` for `installed`, an entry of the other scope's lockfile: its version
|
|
28
|
+
* there, and the clash rule of each of `tools` (the tools that have both copies) that has one.
|
|
29
|
+
*/
|
|
30
|
+
export declare function crossScopeInstall(installed: InstalledAsset, { root, scope }: ResolvedScope, tools: string[]): Promise<CrossScopeInstall>;
|
|
31
|
+
/** The one `cross-scope` notice for an install's duplicates, which all sit at the same other scope. */
|
|
32
|
+
export declare function crossScopeNotice(installs: [CrossScopeInstall, ...CrossScopeInstall[]]): Notice;
|
|
33
|
+
/** The tools' rules for which copy they use, as one clause: `Claude Code uses the user copy; …`. */
|
|
34
|
+
export declare function describeClashes(clash: CrossScopeClash[]): string;
|
|
35
|
+
/** One line on an asset installed at both scopes: where the other copy is, its version, and each tool's rule. */
|
|
36
|
+
export declare function describeCrossScope(install: CrossScopeInstall): string;
|
|
37
|
+
/**
|
|
38
|
+
* The assets among `assets` that the other scope already has: the user scope for a project install,
|
|
39
|
+
* and the cwd's project for a user-scope install (none when the cwd is in no project but the user
|
|
40
|
+
* root). Matched on (type, org, name). `tools` are the install's tools; each result's rules are for
|
|
41
|
+
* those that have both copies. An asset's `excludedTools` (from the lockfile, after an install) and
|
|
42
|
+
* the other entry's leave out the tools a copy isn't placed for, and a copy placed for none of its
|
|
43
|
+
* scope's tools doesn't count. The other scope is optional, so one that is missing or can't be read
|
|
44
|
+
* gives none, never an error.
|
|
45
|
+
*/
|
|
46
|
+
export declare function findCrossScopeInstalls(assets: ReadonlyArray<Pick<InstalledAsset, 'excludedTools' | 'name' | 'org' | 'type'>>, { scope, tools }: {
|
|
47
|
+
scope: Scope;
|
|
48
|
+
tools: string[];
|
|
49
|
+
}): Promise<CrossScopeInstall[]>;
|
|
50
|
+
//# sourceMappingURL=cross-scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cross-scope.d.ts","sourceRoot":"","sources":["../../src/lib/cross-scope.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAK5D,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAGL,KAAK,aAAa,EAElB,KAAK,KAAK,EAEX,MAAM,YAAY,CAAC;AAEpB,qEAAqE;AACrE,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,cAAc,EACzB,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,aAAa,EAC9B,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,iBAAiB,CAAC,CAU5B;AAED,uGAAuG;AACvG,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,CAAC,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,CAAC,GAAG,MAAM,CAY9F;AAED,oGAAoG;AACpG,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,CAMhE;AAED,iHAAiH;AACjH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAGrE;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,cAAc,EAAE,eAAe,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC,EACtF,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAClD,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA6B9B"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { loadAdapter } from './adapter.js';
|
|
3
|
+
import { isExcludedForTool, isExcludedForTools } from './asset-tools.js';
|
|
4
|
+
import { identityKey, lockfileExists, readLockfile } from './lockfile.js';
|
|
5
|
+
import {} from './notices.js';
|
|
6
|
+
import { formatOrgName } from './org.js';
|
|
7
|
+
import { findProjectRoot } from './paths.js';
|
|
8
|
+
import {} from './schemas/adapter.js';
|
|
9
|
+
import { isUserRoot, loadScopeConfig, resolveUserRoot, scopedCommand, } from './scope.js';
|
|
10
|
+
/**
|
|
11
|
+
* The `CrossScopeInstall` for `installed`, an entry of the other scope's lockfile: its version
|
|
12
|
+
* there, and the clash rule of each of `tools` (the tools that have both copies) that has one.
|
|
13
|
+
*/
|
|
14
|
+
export async function crossScopeInstall(installed, { root, scope }, tools) {
|
|
15
|
+
return {
|
|
16
|
+
clash: await clashRules(installed.type, tools),
|
|
17
|
+
name: installed.name,
|
|
18
|
+
...(installed.org ? { org: installed.org } : {}),
|
|
19
|
+
root,
|
|
20
|
+
scope,
|
|
21
|
+
type: installed.type,
|
|
22
|
+
version: installed.version,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** The one `cross-scope` notice for an install's duplicates, which all sit at the same other scope. */
|
|
26
|
+
export function crossScopeNotice(installs) {
|
|
27
|
+
if (installs.length === 1) {
|
|
28
|
+
const [install] = installs;
|
|
29
|
+
return { command: uninstallCommand(install), kind: 'cross-scope', text: describeCrossScope(install) };
|
|
30
|
+
}
|
|
31
|
+
const list = installs.map((i) => `${formatOrgName(i.org, i.name)} (${i.type}) v${i.version}`).join(', ');
|
|
32
|
+
return {
|
|
33
|
+
command: 'atk list',
|
|
34
|
+
kind: 'cross-scope',
|
|
35
|
+
text: `${installs.length} assets are also installed ${scopePhrase(installs[0].scope)}: ${list}. Each tool that has both copies follows its own rule for which one it uses.`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** The tools' rules for which copy they use, as one clause: `Claude Code uses the user copy; …`. */
|
|
39
|
+
export function describeClashes(clash) {
|
|
40
|
+
return clash
|
|
41
|
+
.map(({ displayName, uses }) => uses === 'both' ? `${displayName} loads both copies` : `${displayName} uses the ${uses} copy`)
|
|
42
|
+
.join('; ');
|
|
43
|
+
}
|
|
44
|
+
/** One line on an asset installed at both scopes: where the other copy is, its version, and each tool's rule. */
|
|
45
|
+
export function describeCrossScope(install) {
|
|
46
|
+
const head = `${formatOrgName(install.org, install.name)} (${install.type}) is also installed ${scopePhrase(install.scope)} (v${install.version})`;
|
|
47
|
+
return install.clash.length === 0 ? `${head}.` : `${head}. ${describeClashes(install.clash)}.`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The assets among `assets` that the other scope already has: the user scope for a project install,
|
|
51
|
+
* and the cwd's project for a user-scope install (none when the cwd is in no project but the user
|
|
52
|
+
* root). Matched on (type, org, name). `tools` are the install's tools; each result's rules are for
|
|
53
|
+
* those that have both copies. An asset's `excludedTools` (from the lockfile, after an install) and
|
|
54
|
+
* the other entry's leave out the tools a copy isn't placed for, and a copy placed for none of its
|
|
55
|
+
* scope's tools doesn't count. The other scope is optional, so one that is missing or can't be read
|
|
56
|
+
* gives none, never an error.
|
|
57
|
+
*/
|
|
58
|
+
export async function findCrossScopeInstalls(assets, { scope, tools }) {
|
|
59
|
+
const other = await otherScope(scope);
|
|
60
|
+
if (!other)
|
|
61
|
+
return [];
|
|
62
|
+
let otherAssets;
|
|
63
|
+
let otherTools;
|
|
64
|
+
try {
|
|
65
|
+
otherAssets = (await readLockfile(other.root)).assets ?? [];
|
|
66
|
+
// Without a config, the other scope names no tools, so no rule applies (as in `atk doctor`).
|
|
67
|
+
otherTools = (await loadScopeConfig(other.root))?.tools ?? [];
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const byKey = new Map(otherAssets.map((a) => [identityKey(a), a]));
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
const installs = [];
|
|
75
|
+
for (const asset of assets) {
|
|
76
|
+
const key = identityKey(asset);
|
|
77
|
+
const installed = byKey.get(key);
|
|
78
|
+
if (!installed || seen.has(key))
|
|
79
|
+
continue;
|
|
80
|
+
if (isExcludedForTools(asset, tools) || isExcludedForTools(installed, otherTools))
|
|
81
|
+
continue;
|
|
82
|
+
seen.add(key);
|
|
83
|
+
const shared = tools.filter((t) => otherTools.includes(t) && !isExcludedForTool(asset, t) && !isExcludedForTool(installed, t));
|
|
84
|
+
installs.push(await crossScopeInstall(installed, other, shared));
|
|
85
|
+
}
|
|
86
|
+
return installs;
|
|
87
|
+
}
|
|
88
|
+
/** Each tool's rule for `type`; a tool without one, or whose adapter doesn't load, is left out. */
|
|
89
|
+
async function clashRules(type, tools) {
|
|
90
|
+
const rules = [];
|
|
91
|
+
for (const tool of tools) {
|
|
92
|
+
try {
|
|
93
|
+
const adapter = await loadAdapter(tool);
|
|
94
|
+
const uses = adapter.scopeClash?.[type];
|
|
95
|
+
if (uses) {
|
|
96
|
+
rules.push({ displayName: adapter.displayName, tool, uses });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// The install itself reports an adapter that doesn't load.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return rules;
|
|
104
|
+
}
|
|
105
|
+
/** The scope opposite `scope`, when it exists and has a lockfile. */
|
|
106
|
+
async function otherScope(scope) {
|
|
107
|
+
let root;
|
|
108
|
+
try {
|
|
109
|
+
if (scope === 'project') {
|
|
110
|
+
root = await resolveUserRoot();
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
root = findProjectRoot();
|
|
114
|
+
if (await isUserRoot(root))
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
if (!existsSync(root) || !lockfileExists(root))
|
|
122
|
+
return undefined;
|
|
123
|
+
return { root, scope: scope === 'project' ? 'user' : 'project' };
|
|
124
|
+
}
|
|
125
|
+
function scopePhrase(scope) {
|
|
126
|
+
return scope === 'user' ? 'at the user scope' : 'in the project';
|
|
127
|
+
}
|
|
128
|
+
/** The command that removes the other scope's copy. */
|
|
129
|
+
function uninstallCommand({ name, org, scope, type }) {
|
|
130
|
+
return scopedCommand(`atk uninstall ${name} --type ${type}${org ? ` --org ${org}` : ''}`, scope);
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=cross-scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cross-scope.js","sourceRoot":"","sources":["../../src/lib/cross-scope.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,11 +3,24 @@ import type { ProjectConfig } from './schemas/config.js';
|
|
|
3
3
|
import type { Registry } from './schemas/index.js';
|
|
4
4
|
import type { InstalledAsset } from './schemas/lockfile.js';
|
|
5
5
|
import { type Scope } from './scope.js';
|
|
6
|
+
/**
|
|
7
|
+
* Check for assets installed both in the project and at the user scope, with the version at each and
|
|
8
|
+
* which copy each tool that has both uses. A copy uninstalled for every tool of its scope doesn't
|
|
9
|
+
* count. Returns undefined when there are none.
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkBothScopes(project: {
|
|
12
|
+
assets: InstalledAsset[];
|
|
13
|
+
tools: string[];
|
|
14
|
+
}, user: {
|
|
15
|
+
assets: InstalledAsset[];
|
|
16
|
+
root: string;
|
|
17
|
+
tools: string[];
|
|
18
|
+
}): Promise<DiagnosticCheck | undefined>;
|
|
6
19
|
/**
|
|
7
20
|
* Check that the hook scripts a configured tool runs directly are executable. Returns
|
|
8
21
|
* undefined when there is nothing to check: on Windows, which has no exec bit, or when
|
|
9
|
-
* no installed hook places a script for any configured tool.
|
|
10
|
-
* `scope`.
|
|
22
|
+
* no installed hook places a script for any configured tool. Scripts are looked for where `scope`
|
|
23
|
+
* places them, and the sync command it suggests runs in `scope`.
|
|
11
24
|
*/
|
|
12
25
|
export declare function checkHookScripts(projectDir: string, tools: string[], assets: InstalledAsset[], platform?: NodeJS.Platform, scope?: Scope): Promise<DiagnosticCheck | undefined>;
|
|
13
26
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../src/lib/diagnostics.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../src/lib/diagnostics.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAgB5D,OAAO,EAAkD,KAAK,KAAK,EAAiB,MAAM,YAAY,CAAC;AAEvG;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE;IAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,EACtD,IAAI,EAAE;IAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAChE,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAyBtC;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,EAAE,cAAc,EAAE,EACxB,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,KAAK,CAAC,EAAE,KAAK,GACZ,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CA0CtC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,cAAc,EAAE,EACxB,QAAQ,EAAE,QAAQ,EAClB,KAAK,CAAC,EAAE,KAAK,GACZ,eAAe,GAAG,SAAS,CAmB7B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAqBjE;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACnC,EAAE,WAAW,EAAE,IAAI,EAAE,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GACnE,OAAO,CAAC,eAAe,EAAE,CAAC,CAsM5B"}
|
package/build/lib/diagnostics.js
CHANGED
|
@@ -1,23 +1,53 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import {
|
|
3
|
-
import { loadAdapter } from './adapter.js';
|
|
2
|
+
import { adapterForScope, loadAdapter } from './adapter.js';
|
|
4
3
|
import { resolveApiUrl } from './api-client.js';
|
|
5
|
-
import { isExcludedAtVersion, isExcludedForTool, isUndeclaredForTool } from './asset-tools.js';
|
|
4
|
+
import { isExcludedAtVersion, isExcludedForTool, isExcludedForTools, isUndeclaredForTool } from './asset-tools.js';
|
|
6
5
|
import { getCredential, getLastEntraSession, validateToken } from './auth.js';
|
|
7
6
|
import { isFrozenInConfig, loadConfig } from './config.js';
|
|
7
|
+
import { crossScopeInstall, describeClashes } from './cross-scope.js';
|
|
8
8
|
import { detectTools, getAdapterDiagnostics } from './detector.js';
|
|
9
9
|
import { getEntraCachePath } from './entra.js';
|
|
10
10
|
import { errorMessage } from './errors.js';
|
|
11
11
|
import { hookScriptPath, lacksExecBit } from './hook-script.js';
|
|
12
|
-
import { lockfileExists, readLockfile } from './lockfile.js';
|
|
12
|
+
import { identityKey, lockfileExists, readLockfile } from './lockfile.js';
|
|
13
13
|
import { formatOrgName } from './org.js';
|
|
14
14
|
import { fetchRegistry, versionTools } from './registry.js';
|
|
15
|
-
import {
|
|
15
|
+
import { fromRoot } from './root-path.js';
|
|
16
|
+
import { loadScopeConfig, resolveScope, resolveUserRoot, scopedCommand } from './scope.js';
|
|
17
|
+
/**
|
|
18
|
+
* Check for assets installed both in the project and at the user scope, with the version at each and
|
|
19
|
+
* which copy each tool that has both uses. A copy uninstalled for every tool of its scope doesn't
|
|
20
|
+
* count. Returns undefined when there are none.
|
|
21
|
+
*/
|
|
22
|
+
export async function checkBothScopes(project, user) {
|
|
23
|
+
const projectByKey = new Map(project.assets.map((a) => [identityKey(a), a]));
|
|
24
|
+
const lines = [];
|
|
25
|
+
for (const userAsset of user.assets) {
|
|
26
|
+
const projectAsset = projectByKey.get(identityKey(userAsset));
|
|
27
|
+
if (!projectAsset)
|
|
28
|
+
continue;
|
|
29
|
+
if (isExcludedForTools(projectAsset, project.tools) || isExcludedForTools(userAsset, user.tools))
|
|
30
|
+
continue;
|
|
31
|
+
const tools = project.tools.filter((t) => user.tools.includes(t) && !isExcludedForTool(projectAsset, t) && !isExcludedForTool(userAsset, t));
|
|
32
|
+
const { clash } = await crossScopeInstall(userAsset, { root: user.root, scope: 'user' }, tools);
|
|
33
|
+
const rules = clash.length > 0 ? ` (${describeClashes(clash)})` : '';
|
|
34
|
+
lines.push(`${formatOrgName(userAsset.org, userAsset.name)} (${userAsset.type}): project v${projectAsset.version}, user v${userAsset.version}${rules}`);
|
|
35
|
+
}
|
|
36
|
+
if (lines.length === 0)
|
|
37
|
+
return undefined;
|
|
38
|
+
const one = lines.length === 1;
|
|
39
|
+
return {
|
|
40
|
+
detail: `${lines.join('; ')}. Run \`atk uninstall <name> --type <type>\` (with \`--org <org>\` for an org asset), adding \`--user\` for the user copy, to keep one copy.`,
|
|
41
|
+
message: `${lines.length} asset${one ? ' is' : 's are'} installed at both scopes`,
|
|
42
|
+
name: 'Both scopes',
|
|
43
|
+
status: 'warn',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
16
46
|
/**
|
|
17
47
|
* Check that the hook scripts a configured tool runs directly are executable. Returns
|
|
18
48
|
* undefined when there is nothing to check: on Windows, which has no exec bit, or when
|
|
19
|
-
* no installed hook places a script for any configured tool.
|
|
20
|
-
* `scope`.
|
|
49
|
+
* no installed hook places a script for any configured tool. Scripts are looked for where `scope`
|
|
50
|
+
* places them, and the sync command it suggests runs in `scope`.
|
|
21
51
|
*/
|
|
22
52
|
export async function checkHookScripts(projectDir, tools, assets, platform = process.platform, scope) {
|
|
23
53
|
if (platform === 'win32')
|
|
@@ -26,7 +56,7 @@ export async function checkHookScripts(projectDir, tools, assets, platform = pro
|
|
|
26
56
|
for (const tool of tools) {
|
|
27
57
|
let adapter;
|
|
28
58
|
try {
|
|
29
|
-
adapter = await loadAdapter(tool);
|
|
59
|
+
adapter = adapterForScope(await loadAdapter(tool), scope ?? 'project', projectDir);
|
|
30
60
|
}
|
|
31
61
|
catch {
|
|
32
62
|
// The Adapters check already reports adapters that fail to load.
|
|
@@ -36,7 +66,7 @@ export async function checkHookScripts(projectDir, tools, assets, platform = pro
|
|
|
36
66
|
if (isExcludedForTool(asset, tool))
|
|
37
67
|
continue;
|
|
38
68
|
const scriptPath = hookScriptPath(adapter.placements[asset.type], asset.name);
|
|
39
|
-
if (scriptPath && existsSync(
|
|
69
|
+
if (scriptPath && existsSync(fromRoot(projectDir, scriptPath))) {
|
|
40
70
|
scripts.add(scriptPath);
|
|
41
71
|
}
|
|
42
72
|
}
|
|
@@ -45,7 +75,7 @@ export async function checkHookScripts(projectDir, tools, assets, platform = pro
|
|
|
45
75
|
return undefined;
|
|
46
76
|
const notExecutable = [];
|
|
47
77
|
for (const scriptPath of scripts) {
|
|
48
|
-
if (await lacksExecBit(
|
|
78
|
+
if (await lacksExecBit(fromRoot(projectDir, scriptPath), platform)) {
|
|
49
79
|
notExecutable.push(scriptPath);
|
|
50
80
|
}
|
|
51
81
|
}
|
|
@@ -228,6 +258,12 @@ export async function runDiagnostics(progress, { projectRoot, user } = {}) {
|
|
|
228
258
|
status: 'pass',
|
|
229
259
|
});
|
|
230
260
|
}
|
|
261
|
+
// The user root: the user scope's own root with --user, else checked with the user scope below
|
|
262
|
+
progress('Checking user root...');
|
|
263
|
+
const userRoot = await checkUserRoot(config);
|
|
264
|
+
if (scope === 'user') {
|
|
265
|
+
checks.push(userRoot.check);
|
|
266
|
+
}
|
|
231
267
|
// Check the scope's config: the project's .atkrc.json, or the user scope's tools in the user config
|
|
232
268
|
const configName = scope === 'user' ? 'User scope config' : 'Project config';
|
|
233
269
|
const setup = scopedCommand('atk setup', scope);
|
|
@@ -304,6 +340,112 @@ export async function runDiagnostics(progress, { projectRoot, user } = {}) {
|
|
|
304
340
|
if (liftedHooks) {
|
|
305
341
|
checks.push(liftedHooks);
|
|
306
342
|
}
|
|
343
|
+
// In a project, the user scope's installs sit beside the project's, so check them too
|
|
344
|
+
if (scope === 'project') {
|
|
345
|
+
progress('Checking user scope...');
|
|
346
|
+
checks.push(userRoot.check);
|
|
347
|
+
if (userRoot.root) {
|
|
348
|
+
checks.push(...(await checkUserScope(userRoot.root, registry, { assets: installedAssets, tools: configuredTools })));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return checks;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Report the user root: its path, where it comes from (`ATK_USER_ROOT`, the `userRoot` config key
|
|
355
|
+
* or the home directory), and a warning when it doesn't exist. `root` is set when it can be checked.
|
|
356
|
+
*/
|
|
357
|
+
async function checkUserRoot(config) {
|
|
358
|
+
let root;
|
|
359
|
+
try {
|
|
360
|
+
root = await resolveUserRoot();
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
return { check: { message: errorMessage(err), name: 'User root', status: 'warn' } };
|
|
364
|
+
}
|
|
365
|
+
const source = process.env['ATK_USER_ROOT']
|
|
366
|
+
? 'From ATK_USER_ROOT'
|
|
367
|
+
: config?.userRoot
|
|
368
|
+
? 'From userRoot in config'
|
|
369
|
+
: 'The home directory';
|
|
370
|
+
if (!existsSync(root)) {
|
|
371
|
+
return {
|
|
372
|
+
check: {
|
|
373
|
+
detail: `${source}. Create it, or point ATK_USER_ROOT or the userRoot config key at an existing directory.`,
|
|
374
|
+
message: `${root} does not exist`,
|
|
375
|
+
name: 'User root',
|
|
376
|
+
status: 'warn',
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return { check: { detail: source, message: root, name: 'User root', status: 'pass' }, root };
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* The user scope's checks for a project's doctor run: its config (tools), its lockfile, its hooks,
|
|
384
|
+
* and the assets it shares with `project`. The user scope is optional, so one that isn't set up
|
|
385
|
+
* passes; one that can't be read warns.
|
|
386
|
+
*/
|
|
387
|
+
async function checkUserScope(userRoot, registry, project) {
|
|
388
|
+
const checks = [];
|
|
389
|
+
let userConfig = null;
|
|
390
|
+
try {
|
|
391
|
+
userConfig = await loadScopeConfig(userRoot);
|
|
392
|
+
checks.push(userConfig
|
|
393
|
+
? {
|
|
394
|
+
message: userConfig.tools.length === 0
|
|
395
|
+
? 'Loaded (no tools configured)'
|
|
396
|
+
: `Loaded (tools: ${userConfig.tools.join(', ')})`,
|
|
397
|
+
name: 'User scope config',
|
|
398
|
+
status: 'pass',
|
|
399
|
+
}
|
|
400
|
+
: {
|
|
401
|
+
detail: 'Run `atk setup --user` to install assets at the user scope.',
|
|
402
|
+
message: 'Not set up',
|
|
403
|
+
name: 'User scope config',
|
|
404
|
+
status: 'pass',
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
catch (err) {
|
|
408
|
+
checks.push({
|
|
409
|
+
detail: 'Run `atk setup --user` to initialize user scope config.',
|
|
410
|
+
message: errorMessage(err),
|
|
411
|
+
name: 'User scope config',
|
|
412
|
+
status: 'warn',
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
let userAssets = [];
|
|
416
|
+
if (!lockfileExists(userRoot)) {
|
|
417
|
+
checks.push({ message: 'None (no user installs)', name: 'User lockfile', status: 'pass' });
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
try {
|
|
421
|
+
userAssets = (await readLockfile(userRoot)).assets ?? [];
|
|
422
|
+
checks.push({
|
|
423
|
+
message: `Found .atk-lock.json (${userAssets.length} asset${userAssets.length === 1 ? '' : 's'})`,
|
|
424
|
+
name: 'User lockfile',
|
|
425
|
+
status: 'pass',
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
checks.push({
|
|
430
|
+
detail: 'Run `atk setup --user` to re-initialize',
|
|
431
|
+
message: errorMessage(err),
|
|
432
|
+
name: 'User lockfile',
|
|
433
|
+
status: 'warn',
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const userTools = userConfig?.tools ?? [];
|
|
438
|
+
const hookScripts = await checkHookScripts(userRoot, userTools, userAssets, process.platform, 'user');
|
|
439
|
+
const liftedHooks = registry && userConfig && checkLiftedHooks(userConfig, userAssets, registry, 'user');
|
|
440
|
+
for (const check of [hookScripts, liftedHooks]) {
|
|
441
|
+
if (check) {
|
|
442
|
+
checks.push({ ...check, name: `User ${check.name.toLowerCase()}` });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const bothScopes = await checkBothScopes(project, { assets: userAssets, root: userRoot, tools: userTools });
|
|
446
|
+
if (bothScopes) {
|
|
447
|
+
checks.push(bothScopes);
|
|
448
|
+
}
|
|
307
449
|
return checks;
|
|
308
450
|
}
|
|
309
451
|
const SOURCE_LABELS = {
|