dsh-gitnexus-plus 0.0.0-stage → 0.1.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 +38 -0
- package/README.md +483 -2
- package/cordis.patch.yml +18 -0
- package/lib/bin/gitnexus-coverage.d.ts +14 -0
- package/lib/bin/gitnexus-coverage.d.ts.map +1 -0
- package/lib/bin/gitnexus-coverage.js +171 -0
- package/lib/bin/gitnexus-coverage.js.map +1 -0
- package/lib/client.js +1642 -0
- package/lib/gitnexus/analyze.d.ts +148 -0
- package/lib/gitnexus/analyze.d.ts.map +1 -0
- package/lib/gitnexus/analyze.js +283 -0
- package/lib/gitnexus/analyze.js.map +1 -0
- package/lib/gitnexus/commands.d.ts +96 -0
- package/lib/gitnexus/commands.d.ts.map +1 -0
- package/lib/gitnexus/commands.js +132 -0
- package/lib/gitnexus/commands.js.map +1 -0
- package/lib/gitnexus/coverage.d.ts +340 -0
- package/lib/gitnexus/coverage.d.ts.map +1 -0
- package/lib/gitnexus/coverage.js +552 -0
- package/lib/gitnexus/coverage.js.map +1 -0
- package/lib/gitnexus/format.d.ts +16 -0
- package/lib/gitnexus/format.d.ts.map +1 -0
- package/lib/gitnexus/format.js +178 -0
- package/lib/gitnexus/format.js.map +1 -0
- package/lib/gitnexus/git-activity.d.ts +136 -0
- package/lib/gitnexus/git-activity.d.ts.map +1 -0
- package/lib/gitnexus/git-activity.js +345 -0
- package/lib/gitnexus/git-activity.js.map +1 -0
- package/lib/gitnexus/git.d.ts +70 -0
- package/lib/gitnexus/git.d.ts.map +1 -0
- package/lib/gitnexus/git.js +121 -0
- package/lib/gitnexus/git.js.map +1 -0
- package/lib/gitnexus/index-meta.d.ts +150 -0
- package/lib/gitnexus/index-meta.d.ts.map +1 -0
- package/lib/gitnexus/index-meta.js +156 -0
- package/lib/gitnexus/index-meta.js.map +1 -0
- package/lib/gitnexus/install.d.ts +97 -0
- package/lib/gitnexus/install.d.ts.map +1 -0
- package/lib/gitnexus/install.js +228 -0
- package/lib/gitnexus/install.js.map +1 -0
- package/lib/gitnexus/languages.d.ts +73 -0
- package/lib/gitnexus/languages.d.ts.map +1 -0
- package/lib/gitnexus/languages.js +217 -0
- package/lib/gitnexus/languages.js.map +1 -0
- package/lib/gitnexus/root.d.ts +68 -0
- package/lib/gitnexus/root.d.ts.map +1 -0
- package/lib/gitnexus/root.js +162 -0
- package/lib/gitnexus/root.js.map +1 -0
- package/lib/gitnexus/scan.d.ts +67 -0
- package/lib/gitnexus/scan.d.ts.map +1 -0
- package/lib/gitnexus/scan.js +247 -0
- package/lib/gitnexus/scan.js.map +1 -0
- package/lib/gitnexus/toolchain.d.ts +136 -0
- package/lib/gitnexus/toolchain.d.ts.map +1 -0
- package/lib/gitnexus/toolchain.js +251 -0
- package/lib/gitnexus/toolchain.js.map +1 -0
- package/lib/index.d.ts +79 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +211 -0
- package/lib/index.js.map +1 -0
- package/lib/routes/analyze.d.ts +35 -0
- package/lib/routes/analyze.d.ts.map +1 -0
- package/lib/routes/analyze.js +71 -0
- package/lib/routes/analyze.js.map +1 -0
- package/lib/routes/coverage.d.ts +44 -0
- package/lib/routes/coverage.d.ts.map +1 -0
- package/lib/routes/coverage.js +68 -0
- package/lib/routes/coverage.js.map +1 -0
- package/lib/routes/guard.d.ts +92 -0
- package/lib/routes/guard.d.ts.map +1 -0
- package/lib/routes/guard.js +187 -0
- package/lib/routes/guard.js.map +1 -0
- package/lib/routes/install.d.ts +27 -0
- package/lib/routes/install.d.ts.map +1 -0
- package/lib/routes/install.js +46 -0
- package/lib/routes/install.js.map +1 -0
- package/package.json +87 -4
- package/scripts/link-runtime.mjs +146 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the on-disk GitNexus index metadata.
|
|
3
|
+
*
|
|
4
|
+
* `.gitnexus/gitnexus.json` is the only fully machine-readable artifact GitNexus
|
|
5
|
+
* writes: the graph itself lives in a single LadybugDB file (`lbug`) and there is
|
|
6
|
+
* no persisted graph JSON/CSV. Everything this plugin shows about an index that
|
|
7
|
+
* is not the graph comes from here or from `~/.gitnexus/registry.json`.
|
|
8
|
+
*/
|
|
9
|
+
/** Directory GitNexus keeps its global state in, honouring `GITNEXUS_HOME`. */
|
|
10
|
+
export declare function globalDir(env?: NodeJS.ProcessEnv): string;
|
|
11
|
+
/** The environment a caller may thread through instead of the real one. */
|
|
12
|
+
export interface RegistryOptions {
|
|
13
|
+
env?: NodeJS.ProcessEnv;
|
|
14
|
+
}
|
|
15
|
+
/** The counters every manifest and registry entry carries. */
|
|
16
|
+
export interface IndexStats {
|
|
17
|
+
files: number;
|
|
18
|
+
nodes: number;
|
|
19
|
+
edges: number;
|
|
20
|
+
communities: number;
|
|
21
|
+
processes: number;
|
|
22
|
+
embeddings: number;
|
|
23
|
+
}
|
|
24
|
+
export interface RegistryEntry {
|
|
25
|
+
name: string;
|
|
26
|
+
path: string;
|
|
27
|
+
storagePath: string;
|
|
28
|
+
indexedAt: string;
|
|
29
|
+
lastCommit: string;
|
|
30
|
+
remoteUrl?: string;
|
|
31
|
+
branch?: string;
|
|
32
|
+
stats: IndexStats;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read the global registry. Never throws: an absent or malformed registry yields
|
|
36
|
+
* an empty list, because "GitNexus has never run here" is a normal state.
|
|
37
|
+
*
|
|
38
|
+
* The registry is written by GitNexus, not by this plugin, so entries are taken
|
|
39
|
+
* as written: the guard checks the one field a lookup needs (`path`) and nothing
|
|
40
|
+
* else, exactly as the untyped revision did.
|
|
41
|
+
*/
|
|
42
|
+
export declare function readRegistry(options?: RegistryOptions): Promise<RegistryEntry[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Normalize a path for comparison.
|
|
45
|
+
*
|
|
46
|
+
* Windows paths in the registry are not case-stable in practice — this machine's
|
|
47
|
+
* registry holds both `D:\Project\...` and `D:\project\...` for the same tree.
|
|
48
|
+
* Comparing raw strings would treat one repository as two.
|
|
49
|
+
*/
|
|
50
|
+
export declare function normalizePathKey(p: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Find the registry entry for a repository path or alias.
|
|
53
|
+
*
|
|
54
|
+
* @param target absolute path or registry alias
|
|
55
|
+
*/
|
|
56
|
+
export declare function findRegistryEntry(target: string, options?: RegistryOptions): Promise<RegistryEntry | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Whether a directory holds a GitNexus index.
|
|
59
|
+
*
|
|
60
|
+
* This is the bound the HTTP route enforces instead of registry membership. The
|
|
61
|
+
* panel shows the project the user is *in*, which need not appear in the global
|
|
62
|
+
* registry at all — and a plugin that refused to describe the user's own working
|
|
63
|
+
* directory because a global file elsewhere had not heard of it would be wrong.
|
|
64
|
+
* Requiring the directory to already be a GitNexus index keeps the route from
|
|
65
|
+
* becoming a filesystem probe: it will describe a path only if GitNexus has
|
|
66
|
+
* already indexed it.
|
|
67
|
+
*/
|
|
68
|
+
export declare function hasIndex(repoPath: string): Promise<boolean>;
|
|
69
|
+
/** `indexCoverage` as the `analyze` that produced the index wrote it. */
|
|
70
|
+
export interface RawIndexCoverage {
|
|
71
|
+
maxFileSizeBytes?: unknown;
|
|
72
|
+
dirtyPaths?: unknown;
|
|
73
|
+
[key: string]: unknown;
|
|
74
|
+
}
|
|
75
|
+
/** `runnerIdentity`, the analyzer-provenance receipt (schema v4). */
|
|
76
|
+
export interface RunnerIdentity {
|
|
77
|
+
cliVersion?: string;
|
|
78
|
+
[key: string]: unknown;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The parsed manifest, as far as this plugin reads it.
|
|
82
|
+
*
|
|
83
|
+
* Named fields are the ones read here or in the report built from them; the index
|
|
84
|
+
* signature carries the rest of a manifest GitNexus is free to extend, so a newer
|
|
85
|
+
* manifest stays readable without a change here. Nothing in this file validates
|
|
86
|
+
* the manifest — a field of the wrong type is reported as absent, not repaired.
|
|
87
|
+
*/
|
|
88
|
+
export interface RawIndexManifest {
|
|
89
|
+
storagePath?: unknown;
|
|
90
|
+
repoPath?: unknown;
|
|
91
|
+
indexedAt?: unknown;
|
|
92
|
+
lastCommit?: unknown;
|
|
93
|
+
branch?: unknown;
|
|
94
|
+
stats?: IndexStats;
|
|
95
|
+
fileHashes?: Record<string, unknown> | null;
|
|
96
|
+
indexCoverage?: RawIndexCoverage | null;
|
|
97
|
+
capabilities?: Record<string, unknown>;
|
|
98
|
+
schemaFingerprint?: unknown;
|
|
99
|
+
runnerIdentity?: RunnerIdentity;
|
|
100
|
+
remoteUrl?: string;
|
|
101
|
+
contentRetention?: string | null;
|
|
102
|
+
ftsProfile?: string | null;
|
|
103
|
+
analysisFeatures?: Record<string, number>;
|
|
104
|
+
crossLanguageProperties?: unknown[];
|
|
105
|
+
unresolvedReceiverMembers?: unknown;
|
|
106
|
+
[key: string]: unknown;
|
|
107
|
+
}
|
|
108
|
+
export interface IndexMeta {
|
|
109
|
+
/** which of the two manifest names the values came from */
|
|
110
|
+
metaFile: string | null;
|
|
111
|
+
storagePath: string;
|
|
112
|
+
repoPath: string;
|
|
113
|
+
indexedAt: string;
|
|
114
|
+
lastCommit: string;
|
|
115
|
+
branch?: string;
|
|
116
|
+
stats: IndexStats;
|
|
117
|
+
/** repo-relative path -> sha256 */
|
|
118
|
+
fileHashes: Record<string, string>;
|
|
119
|
+
/** size cap in force when the index was written */
|
|
120
|
+
coverageMaxFileSizeBytes: number | null;
|
|
121
|
+
/** files that were dirty vs HEAD when indexed */
|
|
122
|
+
dirtyPaths: string[];
|
|
123
|
+
capabilities: Record<string, unknown>;
|
|
124
|
+
schemaFingerprint?: string;
|
|
125
|
+
runnerIdentity?: RunnerIdentity;
|
|
126
|
+
/** the full parsed manifest */
|
|
127
|
+
raw: RawIndexManifest;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Read the index manifest for a repository.
|
|
131
|
+
*
|
|
132
|
+
* Two file names exist in the wild and both must be honoured:
|
|
133
|
+
*
|
|
134
|
+
* `gitnexus.json` — current format, written by the `analyze` that produced the
|
|
135
|
+
* index. Carries `indexCoverage`, `storagePath`, `branch`,
|
|
136
|
+
* `runnerIdentity` on top of the shared fields.
|
|
137
|
+
* `meta.json` — the older name, still present on indexes built by earlier
|
|
138
|
+
* releases. Observed on this machine holding a 28 536-entry
|
|
139
|
+
* `fileHashes` with **no** `indexCoverage` at all.
|
|
140
|
+
*
|
|
141
|
+
* Reading only `gitnexus.json` makes the tool report a perfectly good 28k-file
|
|
142
|
+
* index as "not indexed" — which is how this fallback came to exist. Absent
|
|
143
|
+
* coverage fields are reported as absent (`null`), never defaulted: claiming a cap
|
|
144
|
+
* the index never recorded would misattribute files as "skipped".
|
|
145
|
+
*
|
|
146
|
+
* @param repoPath absolute repository root
|
|
147
|
+
* @returns `null` when the repository is not indexed.
|
|
148
|
+
*/
|
|
149
|
+
export declare function readIndexMeta(repoPath: string): Promise<IndexMeta | null>;
|
|
150
|
+
//# sourceMappingURL=index-meta.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-meta.d.ts","sourceRoot":"","sources":["../../src/gitnexus/index-meta.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the on-disk GitNexus index metadata.
|
|
3
|
+
*
|
|
4
|
+
* `.gitnexus/gitnexus.json` is the only fully machine-readable artifact GitNexus
|
|
5
|
+
* writes: the graph itself lives in a single LadybugDB file (`lbug`) and there is
|
|
6
|
+
* no persisted graph JSON/CSV. Everything this plugin shows about an index that
|
|
7
|
+
* is not the graph comes from here or from `~/.gitnexus/registry.json`.
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
/** Directory GitNexus keeps its global state in, honouring `GITNEXUS_HOME`. */
|
|
13
|
+
export function globalDir(env = process.env) {
|
|
14
|
+
return env.GITNEXUS_HOME && env.GITNEXUS_HOME.trim() !== ''
|
|
15
|
+
? path.resolve(env.GITNEXUS_HOME)
|
|
16
|
+
: path.join(os.homedir(), '.gitnexus');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read the global registry. Never throws: an absent or malformed registry yields
|
|
20
|
+
* an empty list, because "GitNexus has never run here" is a normal state.
|
|
21
|
+
*
|
|
22
|
+
* The registry is written by GitNexus, not by this plugin, so entries are taken
|
|
23
|
+
* as written: the guard checks the one field a lookup needs (`path`) and nothing
|
|
24
|
+
* else, exactly as the untyped revision did.
|
|
25
|
+
*/
|
|
26
|
+
export async function readRegistry(options = {}) {
|
|
27
|
+
const file = path.join(globalDir(options.env ?? process.env), 'registry.json');
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
30
|
+
return Array.isArray(parsed)
|
|
31
|
+
? parsed.filter((entry) => Boolean(entry) && typeof entry.path === 'string')
|
|
32
|
+
: [];
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Normalize a path for comparison.
|
|
40
|
+
*
|
|
41
|
+
* Windows paths in the registry are not case-stable in practice — this machine's
|
|
42
|
+
* registry holds both `D:\Project\...` and `D:\project\...` for the same tree.
|
|
43
|
+
* Comparing raw strings would treat one repository as two.
|
|
44
|
+
*/
|
|
45
|
+
export function normalizePathKey(p) {
|
|
46
|
+
const resolved = path.resolve(p).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
47
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Find the registry entry for a repository path or alias.
|
|
51
|
+
*
|
|
52
|
+
* @param target absolute path or registry alias
|
|
53
|
+
*/
|
|
54
|
+
export async function findRegistryEntry(target, options = {}) {
|
|
55
|
+
const entries = await readRegistry(options);
|
|
56
|
+
const wanted = normalizePathKey(target);
|
|
57
|
+
const byPath = entries.find((e) => {
|
|
58
|
+
try {
|
|
59
|
+
return normalizePathKey(e.path) === wanted;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
if (byPath)
|
|
66
|
+
return byPath;
|
|
67
|
+
return entries.find((e) => e.name === target) ?? null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Whether a directory holds a GitNexus index.
|
|
71
|
+
*
|
|
72
|
+
* This is the bound the HTTP route enforces instead of registry membership. The
|
|
73
|
+
* panel shows the project the user is *in*, which need not appear in the global
|
|
74
|
+
* registry at all — and a plugin that refused to describe the user's own working
|
|
75
|
+
* directory because a global file elsewhere had not heard of it would be wrong.
|
|
76
|
+
* Requiring the directory to already be a GitNexus index keeps the route from
|
|
77
|
+
* becoming a filesystem probe: it will describe a path only if GitNexus has
|
|
78
|
+
* already indexed it.
|
|
79
|
+
*/
|
|
80
|
+
export async function hasIndex(repoPath) {
|
|
81
|
+
const dir = path.join(repoPath, '.gitnexus');
|
|
82
|
+
for (const name of ['gitnexus.json', 'meta.json']) {
|
|
83
|
+
try {
|
|
84
|
+
await fs.access(path.join(dir, name));
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
/* try the next name */
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Read the index manifest for a repository.
|
|
95
|
+
*
|
|
96
|
+
* Two file names exist in the wild and both must be honoured:
|
|
97
|
+
*
|
|
98
|
+
* `gitnexus.json` — current format, written by the `analyze` that produced the
|
|
99
|
+
* index. Carries `indexCoverage`, `storagePath`, `branch`,
|
|
100
|
+
* `runnerIdentity` on top of the shared fields.
|
|
101
|
+
* `meta.json` — the older name, still present on indexes built by earlier
|
|
102
|
+
* releases. Observed on this machine holding a 28 536-entry
|
|
103
|
+
* `fileHashes` with **no** `indexCoverage` at all.
|
|
104
|
+
*
|
|
105
|
+
* Reading only `gitnexus.json` makes the tool report a perfectly good 28k-file
|
|
106
|
+
* index as "not indexed" — which is how this fallback came to exist. Absent
|
|
107
|
+
* coverage fields are reported as absent (`null`), never defaulted: claiming a cap
|
|
108
|
+
* the index never recorded would misattribute files as "skipped".
|
|
109
|
+
*
|
|
110
|
+
* @param repoPath absolute repository root
|
|
111
|
+
* @returns `null` when the repository is not indexed.
|
|
112
|
+
*/
|
|
113
|
+
export async function readIndexMeta(repoPath) {
|
|
114
|
+
const dir = path.join(repoPath, '.gitnexus');
|
|
115
|
+
let raw = null;
|
|
116
|
+
let source = null;
|
|
117
|
+
for (const name of ['gitnexus.json', 'meta.json']) {
|
|
118
|
+
try {
|
|
119
|
+
raw = JSON.parse(await fs.readFile(path.join(dir, name), 'utf8'));
|
|
120
|
+
source = name;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
/* try the next name */
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (raw === null)
|
|
128
|
+
return null;
|
|
129
|
+
const fileHashes = {};
|
|
130
|
+
if (raw.fileHashes && typeof raw.fileHashes === 'object') {
|
|
131
|
+
for (const [key, value] of Object.entries(raw.fileHashes)) {
|
|
132
|
+
if (typeof value === 'string')
|
|
133
|
+
fileHashes[key.replace(/\\/g, '/')] = value;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const coverage = raw.indexCoverage;
|
|
137
|
+
return {
|
|
138
|
+
metaFile: source,
|
|
139
|
+
storagePath: typeof raw.storagePath === 'string' ? raw.storagePath : path.join(repoPath, '.gitnexus'),
|
|
140
|
+
repoPath: typeof raw.repoPath === 'string' ? raw.repoPath : repoPath,
|
|
141
|
+
indexedAt: typeof raw.indexedAt === 'string' ? raw.indexedAt : '',
|
|
142
|
+
lastCommit: typeof raw.lastCommit === 'string' ? raw.lastCommit : '',
|
|
143
|
+
branch: typeof raw.branch === 'string' ? raw.branch : undefined,
|
|
144
|
+
stats: raw.stats ?? { files: 0, nodes: 0, edges: 0, communities: 0, processes: 0, embeddings: 0 },
|
|
145
|
+
fileHashes,
|
|
146
|
+
// Absent on indexes written before this field existed — callers must not
|
|
147
|
+
// invent a cap when it is missing.
|
|
148
|
+
coverageMaxFileSizeBytes: coverage && typeof coverage.maxFileSizeBytes === 'number' ? coverage.maxFileSizeBytes : null,
|
|
149
|
+
dirtyPaths: Array.isArray(coverage?.dirtyPaths) ? coverage.dirtyPaths : [],
|
|
150
|
+
capabilities: raw.capabilities ?? {},
|
|
151
|
+
schemaFingerprint: typeof raw.schemaFingerprint === 'string' ? raw.schemaFingerprint : undefined,
|
|
152
|
+
runnerIdentity: raw.runnerIdentity,
|
|
153
|
+
raw,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=index-meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-meta.js","sourceRoot":"","sources":["../../src/gitnexus/index-meta.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install GitNexus on this machine, on the operator's explicit instruction.
|
|
3
|
+
*
|
|
4
|
+
* WHY THE PLUGIN DOES THIS ITSELF
|
|
5
|
+
* -------------------------------
|
|
6
|
+
* The plugin reads `.gitnexus/gitnexus.json` — a file that only GitNexus can produce,
|
|
7
|
+
* containing every function definition, symbol relationship and execution flow. Without
|
|
8
|
+
* it there is nothing to display, and no amount of scanning the working tree substitutes
|
|
9
|
+
* for it. So "the user has no GitNexus" is not a degraded state to design around; it is
|
|
10
|
+
* a missing prerequisite, and the most useful thing the plugin can do about it is offer
|
|
11
|
+
* to satisfy it.
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT DOES NOT DO
|
|
14
|
+
* -------------------
|
|
15
|
+
* It does not download anything itself, and it does not pick a registry. It runs `npm
|
|
16
|
+
* install` and lets npm use whatever registry the machine is already configured for —
|
|
17
|
+
* a mirror, a corporate feed, a local Verdaccio. Hardcoding one would break exactly the
|
|
18
|
+
* users whose network is the reason they need this path, and the operator's `.npmrc` is
|
|
19
|
+
* the only authority on where their packages come from.
|
|
20
|
+
*
|
|
21
|
+
* It also does not build anything itself. npm runs the dependency install scripts that
|
|
22
|
+
* GitNexus needs (`@ladybugdb/core`, `tree-sitter`, its own grammar build) — which is
|
|
23
|
+
* why this uses npm rather than pnpm, whose ≥10 line refuses dependency build scripts by
|
|
24
|
+
* default and would fail the install in a way the operator could not act on.
|
|
25
|
+
*
|
|
26
|
+
* INSTALL LOCATION
|
|
27
|
+
* ----------------
|
|
28
|
+
* A directory the plugin owns (`$DSH_HOME/gitnexus-plus/gitnexus`), never the global
|
|
29
|
+
* prefix. A global install needs elevation on Windows and would collide with a GitNexus
|
|
30
|
+
* the operator already has. A plugin-owned copy is removable, self-describing, and does
|
|
31
|
+
* not make the plugin responsible for someone else's global state.
|
|
32
|
+
*
|
|
33
|
+
* The cost is real and the confirmation must state it: roughly 500 MB to 1 GB, several
|
|
34
|
+
* minutes, network access, and the package's build scripts running on this machine.
|
|
35
|
+
*/
|
|
36
|
+
import type { ShellLike } from './analyze.js';
|
|
37
|
+
export type InstallState = 'running' | 'done' | 'failed' | 'timed-out' | 'cancelled'
|
|
38
|
+
/** The sandbox refused a write. Reported, never retried wider. */
|
|
39
|
+
| 'denied'
|
|
40
|
+
/** No shell executor in this deployment, so nothing can be installed. */
|
|
41
|
+
| 'unavailable';
|
|
42
|
+
export interface InstallStatus {
|
|
43
|
+
state: InstallState;
|
|
44
|
+
/** The command as run, so a failure can be reproduced by hand. */
|
|
45
|
+
command: string;
|
|
46
|
+
/** Where it installs to. */
|
|
47
|
+
prefix: string;
|
|
48
|
+
startedAt: number;
|
|
49
|
+
finishedAt: number | null;
|
|
50
|
+
exitCode: number | null;
|
|
51
|
+
message: string | null;
|
|
52
|
+
output: string;
|
|
53
|
+
}
|
|
54
|
+
/** The version range this plugin is known to work against. */
|
|
55
|
+
export declare const GITNEXUS_RANGE = "^1.6.12";
|
|
56
|
+
export interface InstallRunnerOptions {
|
|
57
|
+
shell: ShellLike | null;
|
|
58
|
+
/** Install target. Defaults to `$DSH_HOME/gitnexus-plus/gitnexus`. */
|
|
59
|
+
prefix?: string;
|
|
60
|
+
/** Version spec passed to npm. */
|
|
61
|
+
spec?: string;
|
|
62
|
+
timeoutMs?: number;
|
|
63
|
+
outputLimit?: number;
|
|
64
|
+
tailLimit?: number;
|
|
65
|
+
/** How long to wait for the completion notice, for tests. */
|
|
66
|
+
now?: () => number;
|
|
67
|
+
/** Platform whose shell syntax applies. */
|
|
68
|
+
platform?: NodeJS.Platform;
|
|
69
|
+
}
|
|
70
|
+
export interface InstallRunner {
|
|
71
|
+
start(): InstallStatus;
|
|
72
|
+
status(): InstallStatus | null;
|
|
73
|
+
cancel(): boolean;
|
|
74
|
+
/** Where a successful install puts the CLI. */
|
|
75
|
+
cliPath(): string;
|
|
76
|
+
dispose(): void;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The default install target.
|
|
80
|
+
*
|
|
81
|
+
* Under `DSH_HOME` rather than under the plugin, because a profile-installed plugin
|
|
82
|
+
* lives inside `node_modules` and writing a 500 MB tree there would be lost on the next
|
|
83
|
+
* install and would confuse the package manager's own bookkeeping.
|
|
84
|
+
*/
|
|
85
|
+
export declare function defaultInstallPrefix(env?: NodeJS.ProcessEnv): string;
|
|
86
|
+
/**
|
|
87
|
+
* The `npm install` line, as the shell will read it.
|
|
88
|
+
*
|
|
89
|
+
* `--prefix` rather than `cd`: the working directory stays wherever the executor put it,
|
|
90
|
+
* so a failed install cannot leave a half-written tree in the operator's project. No
|
|
91
|
+
* `--registry` on purpose — see the module comment.
|
|
92
|
+
*/
|
|
93
|
+
export declare function installCommandLine(prefix: string, spec: string, options?: {
|
|
94
|
+
platform?: NodeJS.Platform;
|
|
95
|
+
}): string;
|
|
96
|
+
export declare function createInstallRunner(options: InstallRunnerOptions): InstallRunner;
|
|
97
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/gitnexus/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,MAAM,GACN,QAAQ,GACR,WAAW,GACX,WAAW;AACb,kEAAkE;GAChE,QAAQ;AACV,yEAAyE;GACvE,aAAa,CAAC;AAElB,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,8DAA8D;AAC9D,eAAO,MAAM,cAAc,YAAY,CAAC;AAMxC,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,IAAI,aAAa,CAAC;IACvB,MAAM,IAAI,aAAa,GAAG,IAAI,CAAC;IAC/B,MAAM,IAAI,OAAO,CAAC;IAClB,+CAA+C;IAC/C,OAAO,IAAI,MAAM,CAAC;IAClB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAGjF;AAOD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;CAAO,GAC3C,MAAM,CAIR;AAcD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAwJhF"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install GitNexus on this machine, on the operator's explicit instruction.
|
|
3
|
+
*
|
|
4
|
+
* WHY THE PLUGIN DOES THIS ITSELF
|
|
5
|
+
* -------------------------------
|
|
6
|
+
* The plugin reads `.gitnexus/gitnexus.json` — a file that only GitNexus can produce,
|
|
7
|
+
* containing every function definition, symbol relationship and execution flow. Without
|
|
8
|
+
* it there is nothing to display, and no amount of scanning the working tree substitutes
|
|
9
|
+
* for it. So "the user has no GitNexus" is not a degraded state to design around; it is
|
|
10
|
+
* a missing prerequisite, and the most useful thing the plugin can do about it is offer
|
|
11
|
+
* to satisfy it.
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT DOES NOT DO
|
|
14
|
+
* -------------------
|
|
15
|
+
* It does not download anything itself, and it does not pick a registry. It runs `npm
|
|
16
|
+
* install` and lets npm use whatever registry the machine is already configured for —
|
|
17
|
+
* a mirror, a corporate feed, a local Verdaccio. Hardcoding one would break exactly the
|
|
18
|
+
* users whose network is the reason they need this path, and the operator's `.npmrc` is
|
|
19
|
+
* the only authority on where their packages come from.
|
|
20
|
+
*
|
|
21
|
+
* It also does not build anything itself. npm runs the dependency install scripts that
|
|
22
|
+
* GitNexus needs (`@ladybugdb/core`, `tree-sitter`, its own grammar build) — which is
|
|
23
|
+
* why this uses npm rather than pnpm, whose ≥10 line refuses dependency build scripts by
|
|
24
|
+
* default and would fail the install in a way the operator could not act on.
|
|
25
|
+
*
|
|
26
|
+
* INSTALL LOCATION
|
|
27
|
+
* ----------------
|
|
28
|
+
* A directory the plugin owns (`$DSH_HOME/gitnexus-plus/gitnexus`), never the global
|
|
29
|
+
* prefix. A global install needs elevation on Windows and would collide with a GitNexus
|
|
30
|
+
* the operator already has. A plugin-owned copy is removable, self-describing, and does
|
|
31
|
+
* not make the plugin responsible for someone else's global state.
|
|
32
|
+
*
|
|
33
|
+
* The cost is real and the confirmation must state it: roughly 500 MB to 1 GB, several
|
|
34
|
+
* minutes, network access, and the package's build scripts running on this machine.
|
|
35
|
+
*/
|
|
36
|
+
import os from 'node:os';
|
|
37
|
+
import path from 'node:path';
|
|
38
|
+
/** The version range this plugin is known to work against. */
|
|
39
|
+
export const GITNEXUS_RANGE = '^1.6.12';
|
|
40
|
+
const DEFAULT_TIMEOUT_MS = 20 * 60 * 1000;
|
|
41
|
+
const DEFAULT_OUTPUT_LIMIT = 256 * 1024;
|
|
42
|
+
const DEFAULT_TAIL_LIMIT = 6 * 1024;
|
|
43
|
+
/**
|
|
44
|
+
* The default install target.
|
|
45
|
+
*
|
|
46
|
+
* Under `DSH_HOME` rather than under the plugin, because a profile-installed plugin
|
|
47
|
+
* lives inside `node_modules` and writing a 500 MB tree there would be lost on the next
|
|
48
|
+
* install and would confuse the package manager's own bookkeeping.
|
|
49
|
+
*/
|
|
50
|
+
export function defaultInstallPrefix(env = process.env) {
|
|
51
|
+
const home = env.DSH_HOME ?? path.join(os.homedir(), '.dsh');
|
|
52
|
+
return path.join(home, 'gitnexus-plus', 'gitnexus');
|
|
53
|
+
}
|
|
54
|
+
/** Quote one argument for the shell the executor spawns. */
|
|
55
|
+
function quote(value, platform) {
|
|
56
|
+
return platform === 'win32' ? `'${value.replace(/'/g, "''")}'` : `'${value.replace(/'/g, "'\\''")}'`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The `npm install` line, as the shell will read it.
|
|
60
|
+
*
|
|
61
|
+
* `--prefix` rather than `cd`: the working directory stays wherever the executor put it,
|
|
62
|
+
* so a failed install cannot leave a half-written tree in the operator's project. No
|
|
63
|
+
* `--registry` on purpose — see the module comment.
|
|
64
|
+
*/
|
|
65
|
+
export function installCommandLine(prefix, spec, options = {}) {
|
|
66
|
+
const platform = options.platform ?? process.platform;
|
|
67
|
+
const args = ['install', '--prefix', quote(prefix, platform), spec, '--no-audit', '--no-fund'];
|
|
68
|
+
return `npm ${args.join(' ')}`;
|
|
69
|
+
}
|
|
70
|
+
/** Keep the last `limit` characters, on a line boundary when one is available. */
|
|
71
|
+
function tail(text, limit) {
|
|
72
|
+
if (text.length <= limit)
|
|
73
|
+
return text;
|
|
74
|
+
const cut = text.slice(text.length - limit);
|
|
75
|
+
const firstBreak = cut.indexOf('\n');
|
|
76
|
+
return firstBreak === -1 ? cut : cut.slice(firstBreak + 1);
|
|
77
|
+
}
|
|
78
|
+
function looksDenied(text) {
|
|
79
|
+
return /\[sandbox:[^\]]*denied/i.test(text) || /access is denied|EPERM|EACCES/i.test(text);
|
|
80
|
+
}
|
|
81
|
+
export function createInstallRunner(options) {
|
|
82
|
+
const { shell } = options;
|
|
83
|
+
const platform = options.platform ?? process.platform;
|
|
84
|
+
const prefix = options.prefix ?? defaultInstallPrefix();
|
|
85
|
+
const spec = options.spec ?? `gitnexus@${GITNEXUS_RANGE}`;
|
|
86
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
87
|
+
const outputLimit = options.outputLimit ?? DEFAULT_OUTPUT_LIMIT;
|
|
88
|
+
const tailLimit = options.tailLimit ?? DEFAULT_TAIL_LIMIT;
|
|
89
|
+
const now = options.now ?? (() => Date.now());
|
|
90
|
+
let status = null;
|
|
91
|
+
let controller = null;
|
|
92
|
+
const cliPath = () => path.join(prefix, 'node_modules', 'gitnexus', 'dist', 'cli', 'index.js');
|
|
93
|
+
/** The shape a status starts from, before any attempt has touched it. */
|
|
94
|
+
const BLANK = {
|
|
95
|
+
state: 'failed',
|
|
96
|
+
command: '',
|
|
97
|
+
prefix,
|
|
98
|
+
startedAt: 0,
|
|
99
|
+
finishedAt: null,
|
|
100
|
+
exitCode: null,
|
|
101
|
+
message: null,
|
|
102
|
+
output: '',
|
|
103
|
+
};
|
|
104
|
+
const finish = (patch) => {
|
|
105
|
+
status = {
|
|
106
|
+
...BLANK,
|
|
107
|
+
...status,
|
|
108
|
+
...patch,
|
|
109
|
+
// Set last and unconditionally: every path here is finishing, so a caller passing
|
|
110
|
+
// `finishedAt: null` means "not finished", not a silent no-op.
|
|
111
|
+
finishedAt: patch.finishedAt ?? now(),
|
|
112
|
+
};
|
|
113
|
+
controller = null;
|
|
114
|
+
return status;
|
|
115
|
+
};
|
|
116
|
+
const start = () => {
|
|
117
|
+
if (status !== null && status.state === 'running')
|
|
118
|
+
return status;
|
|
119
|
+
if (shell === null) {
|
|
120
|
+
return finish({
|
|
121
|
+
state: 'unavailable',
|
|
122
|
+
command: installCommandLine(prefix, spec, { platform }),
|
|
123
|
+
startedAt: now(),
|
|
124
|
+
message: '当前部署没有提供 shell 执行器,无法安装。请手动执行下面列出的命令。',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const command = installCommandLine(prefix, spec, { platform });
|
|
128
|
+
const abort = new AbortController();
|
|
129
|
+
controller = abort;
|
|
130
|
+
status = {
|
|
131
|
+
state: 'running',
|
|
132
|
+
command,
|
|
133
|
+
prefix,
|
|
134
|
+
startedAt: now(),
|
|
135
|
+
finishedAt: null,
|
|
136
|
+
exitCode: null,
|
|
137
|
+
message: null,
|
|
138
|
+
output: '',
|
|
139
|
+
};
|
|
140
|
+
const spec0 = shell.resolve({
|
|
141
|
+
command,
|
|
142
|
+
timeoutMs,
|
|
143
|
+
onExpiry: 'kill',
|
|
144
|
+
stdoutMaxBytes: outputLimit,
|
|
145
|
+
signal: abort.signal,
|
|
146
|
+
// No sandboxPolicy: the deployment decides. See analyze.ts for why.
|
|
147
|
+
});
|
|
148
|
+
void (async () => {
|
|
149
|
+
let execution;
|
|
150
|
+
try {
|
|
151
|
+
execution = await shell.execute(spec0);
|
|
152
|
+
}
|
|
153
|
+
catch (cause) {
|
|
154
|
+
const aborted = abort.signal.aborted;
|
|
155
|
+
finish({
|
|
156
|
+
state: aborted ? 'cancelled' : 'failed',
|
|
157
|
+
message: aborted ? '已取消。' : `无法启动安装:${String(cause?.message ?? cause)}`,
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
let result;
|
|
162
|
+
try {
|
|
163
|
+
result = await execution.result();
|
|
164
|
+
}
|
|
165
|
+
catch (cause) {
|
|
166
|
+
finish({ state: 'failed', message: `安装失败:${String(cause?.message ?? cause)}` });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const combined = `${result.stdout?.text ?? ''}${result.stderr?.text ?? ''}`;
|
|
170
|
+
const body = tail(combined, tailLimit);
|
|
171
|
+
const exitCode = typeof result.exitCode === 'number' ? result.exitCode : null;
|
|
172
|
+
if (abort.signal.aborted) {
|
|
173
|
+
finish({ state: 'cancelled', exitCode, output: body, message: '已取消。' });
|
|
174
|
+
}
|
|
175
|
+
else if (result.timedOut === true) {
|
|
176
|
+
finish({
|
|
177
|
+
state: 'timed-out',
|
|
178
|
+
exitCode,
|
|
179
|
+
output: body,
|
|
180
|
+
message: `超过 ${Math.round(timeoutMs / 60000)} 分钟上限已终止。网络慢或需要本地编译时,可以延长 analyzeTimeoutMs 后重试。`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
else if (looksDenied(combined)) {
|
|
184
|
+
finish({
|
|
185
|
+
state: 'denied',
|
|
186
|
+
exitCode,
|
|
187
|
+
output: body,
|
|
188
|
+
message: '沙箱拒绝了写入。本插件不会申请扩权 —— 请在终端里手动执行这条命令。',
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
else if (exitCode === 0) {
|
|
192
|
+
finish({
|
|
193
|
+
state: 'done',
|
|
194
|
+
exitCode,
|
|
195
|
+
output: body,
|
|
196
|
+
message: `已安装到 ${prefix}。`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
finish({
|
|
201
|
+
state: 'failed',
|
|
202
|
+
exitCode,
|
|
203
|
+
output: body,
|
|
204
|
+
message: `npm 以退出码 ${exitCode ?? '未知'} 结束。常见原因:` +
|
|
205
|
+
'访问不到你的 npm registry、或者缺少 C++ 编译工具链(这些包需要本地编译)。',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
})();
|
|
209
|
+
return status;
|
|
210
|
+
};
|
|
211
|
+
return {
|
|
212
|
+
start,
|
|
213
|
+
status: () => status,
|
|
214
|
+
cancel: () => {
|
|
215
|
+
if (controller === null)
|
|
216
|
+
return false;
|
|
217
|
+
controller.abort();
|
|
218
|
+
return true;
|
|
219
|
+
},
|
|
220
|
+
cliPath,
|
|
221
|
+
dispose: () => {
|
|
222
|
+
if (controller !== null)
|
|
223
|
+
controller.abort();
|
|
224
|
+
controller = null;
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/gitnexus/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AA2B7B,8DAA8D;AAC9D,MAAM,CAAC,MAAM,cAAc,GAAG,SAAS,CAAC;AAExC,MAAM,kBAAkB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAC1C,MAAM,oBAAoB,GAAG,GAAG,GAAG,IAAI,CAAC;AACxC,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,CAAC;AA0BpC;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACvE,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;IAC7D,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;AACtD,CAAC;AAED,4DAA4D;AAC5D,SAAS,KAAK,CAAC,KAAa,EAAE,QAAyB;IACrD,OAAO,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AACvG,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAc,EACd,IAAY,EACZ,UAA0C,EAAE;IAE5C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;IACtD,MAAM,IAAI,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IAC/F,OAAO,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AACjC,CAAC;AAED,kFAAkF;AAClF,SAAS,IAAI,CAAC,IAAY,EAAE,KAAa;IACvC,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,OAAO,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,gCAAgC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7F,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAA6B;IAC/D,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;IACtD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,oBAAoB,EAAE,CAAC;IACxD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,YAAY,cAAc,EAAE,CAAC;IAC1D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;IAC1D,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,oBAAoB,CAAC;IAChE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;IAC1D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAE9C,IAAI,MAAM,GAAyB,IAAI,CAAC;IACxC,IAAI,UAAU,GAA2B,IAAI,CAAC;IAE9C,MAAM,OAAO,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IAEvG,yEAAyE;IACzE,MAAM,KAAK,GAA+B;QACxC,KAAK,EAAE,QAAQ;QACf,OAAO,EAAE,EAAE;QACX,MAAM;QACN,SAAS,EAAE,CAAC;QACZ,UAAU,EAAE,IAAI;QAChB,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,EAAE;KACX,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,KAA6B,EAAiB,EAAE;QAC9D,MAAM,GAAG;YACP,GAAG,KAAK;YACR,GAAG,MAAM;YACT,GAAG,KAAK;YACR,kFAAkF;YAClF,+DAA+D;YAC/D,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,GAAG,EAAE;SACtC,CAAC;QACF,UAAU,GAAG,IAAI,CAAC;QAClB,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,GAAkB,EAAE;QAChC,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;QAEjE,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,OAAO,MAAM,CAAC;gBACZ,KAAK,EAAE,aAAa;gBACpB,OAAO,EAAE,kBAAkB,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC;gBACvD,SAAS,EAAE,GAAG,EAAE;gBAChB,OAAO,EAAE,uCAAuC;aACjD,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAAG,kBAAkB,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC/D,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAC;QACpC,UAAU,GAAG,KAAK,CAAC;QACnB,MAAM,GAAG;YACP,KAAK,EAAE,SAAS;YAChB,OAAO;YACP,MAAM;YACN,SAAS,EAAE,GAAG,EAAE;YAChB,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,IAAI;YACd,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,EAAE;SACX,CAAC;QAEF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC;YAC1B,OAAO;YACP,SAAS;YACT,QAAQ,EAAE,MAAM;YAChB,cAAc,EAAE,WAAW;YAC3B,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,oEAAoE;SACrE,CAAC,CAAC;QAEH,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,IAAI,SAAS,CAAC;YACd,IAAI,CAAC;gBACH,SAAS,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACzC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;gBACrC,MAAM,CAAC;oBACL,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ;oBACvC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,MAAM,CAAE,KAAe,EAAE,OAAO,IAAI,KAAK,CAAC,EAAE;iBACnF,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,IAAI,MAAM,CAAC;YACX,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,CAAC;YACpC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,MAAM,CAAE,KAAe,EAAE,OAAO,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC3F,OAAO;YACT,CAAC;YAED,MAAM,QAAQ,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC;YAC5E,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YACvC,MAAM,QAAQ,GAAG,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;YAE9E,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACzB,MAAM,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YAC1E,CAAC;iBAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACpC,MAAM,CAAC;oBACL,KAAK,EAAE,WAAW;oBAClB,QAAQ;oBACR,MAAM,EAAE,IAAI;oBACZ,OAAO,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,iDAAiD;iBAC9F,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,MAAM,CAAC;oBACL,KAAK,EAAE,QAAQ;oBACf,QAAQ;oBACR,MAAM,EAAE,IAAI;oBACZ,OAAO,EAAE,qCAAqC;iBAC/C,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;gBAC1B,MAAM,CAAC;oBACL,KAAK,EAAE,MAAM;oBACb,QAAQ;oBACR,MAAM,EAAE,IAAI;oBACZ,OAAO,EAAE,QAAQ,MAAM,GAAG;iBAC3B,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC;oBACL,KAAK,EAAE,QAAQ;oBACf,QAAQ;oBACR,MAAM,EAAE,IAAI;oBACZ,OAAO,EACL,YAAY,QAAQ,IAAI,IAAI,WAAW;wBACvC,gDAAgD;iBACnD,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;QAEL,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,OAAO;QACL,KAAK;QACL,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM;QACpB,MAAM,EAAE,GAAG,EAAE;YACX,IAAI,UAAU,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YACtC,UAAU,CAAC,KAAK,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO;QACP,OAAO,EAAE,GAAG,EAAE;YACZ,IAAI,UAAU,KAAK,IAAI;gBAAE,UAAU,CAAC,KAAK,EAAE,CAAC;YAC5C,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;KACF,CAAC;AACJ,CAAC"}
|