homegraph 1.5.8 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/extraction/grammars.d.ts +19 -0
- package/dist/extraction/grammars.js +44 -1
- package/dist/extraction/index.js +17 -12
- package/dist/extraction/languages/arkts.d.ts +40 -7
- package/dist/extraction/languages/arkts.js +290 -84
- package/dist/extraction/tree-sitter.js +12 -3
- package/dist/index.js +23 -12
- package/dist/mcp/arkts-evidence-packs.js +1 -0
- package/dist/mcp/daemon.d.ts +21 -3
- package/dist/mcp/daemon.js +60 -6
- package/dist/mcp/engine.d.ts +26 -0
- package/dist/mcp/engine.js +143 -7
- package/dist/mcp/index-availability.d.ts +40 -10
- package/dist/mcp/index-availability.js +89 -23
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/indexable-root.d.ts +22 -0
- package/dist/mcp/indexable-root.js +140 -0
- package/dist/mcp/liveness-watchdog.d.ts +6 -1
- package/dist/mcp/liveness-watchdog.js +17 -5
- package/dist/mcp/locate-contract.d.ts +50 -0
- package/dist/mcp/locate-contract.js +146 -0
- package/dist/mcp/server-instructions.d.ts +2 -6
- package/dist/mcp/server-instructions.js +15 -5
- package/dist/mcp/session.js +15 -0
- package/dist/mcp/tools.d.ts +52 -0
- package/dist/mcp/tools.js +750 -103
- package/dist/project-map/index.d.ts +45 -0
- package/dist/project-map/index.js +373 -45
- package/dist/resolution/callback-synthesizer.js +251 -0
- package/dist/resolution/frameworks/arkts-entry.d.ts +35 -6
- package/dist/resolution/frameworks/arkts-entry.js +513 -30
- package/dist/resolution/index.js +25 -21
- package/dist/runtime-log.d.ts +52 -0
- package/dist/runtime-log.js +199 -0
- package/dist/search/query-plan-provider.js +3 -2
- package/dist/search/query-plan.js +21 -19
- package/dist/search/query-utils.d.ts +13 -0
- package/dist/search/query-utils.js +64 -0
- package/package.json +2 -2
|
@@ -30,4 +30,49 @@ export interface BuiltProjectMap {
|
|
|
30
30
|
*/
|
|
31
31
|
export declare function buildProjectMapScan(projectRoot: string): BuiltProjectMap;
|
|
32
32
|
export declare function parseBuildPhase(raw: string | null | undefined): BuildPhase | null;
|
|
33
|
+
/**
|
|
34
|
+
* Spec 0040 — optional bundleName/name from app.json5 (AppScope or root).
|
|
35
|
+
* Best-effort; returns null on missing/parse failure.
|
|
36
|
+
*/
|
|
37
|
+
export declare function readHarmonyAppBundleName(projectRoot: string): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Spec 0040 — project-relative paths to Spec 0039 route profile JSON under a module root.
|
|
40
|
+
*/
|
|
41
|
+
export declare function listHarmonyRouteProfilesUnderModule(projectRoot: string, moduleRootPath: string, cap?: number): string[];
|
|
42
|
+
/** Spec 0040 — oh-package name at module root, if any. */
|
|
43
|
+
export declare function readModuleOhPackageName(projectRoot: string, moduleRootPath: string): string | null;
|
|
44
|
+
/** Spec 0042 B — path-only Harmony resource inventory (no parse, no edges). */
|
|
45
|
+
export interface HarmonyResourceInventory {
|
|
46
|
+
stringJson: string[];
|
|
47
|
+
rawfiles: string[];
|
|
48
|
+
mediaDirs: Array<{
|
|
49
|
+
dir: string;
|
|
50
|
+
countsByExt: Record<string, number>;
|
|
51
|
+
}>;
|
|
52
|
+
/** Spec 0048 — form_config.json / shortcuts_config.json paths. */
|
|
53
|
+
capabilityProfiles: string[];
|
|
54
|
+
unindexedDirs: Array<{
|
|
55
|
+
path: string;
|
|
56
|
+
reason: string;
|
|
57
|
+
}>;
|
|
58
|
+
truncated: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Bounded walk: whitelist paths under `resources/…` plus package dirs with zero
|
|
62
|
+
* indexed sources. Does not read JSON bodies or media bytes.
|
|
63
|
+
*/
|
|
64
|
+
export declare function scanHarmonyResourceInventory(projectRoot: string, options?: {
|
|
65
|
+
indexedPaths?: Iterable<string>;
|
|
66
|
+
}): HarmonyResourceInventory;
|
|
67
|
+
/** Markdown section for Spec 0042 B (empty string when nothing to show). */
|
|
68
|
+
export declare function formatHarmonyResourceInventory(inv: HarmonyResourceInventory): string;
|
|
69
|
+
/**
|
|
70
|
+
* Spec 0048 §4 — module roster with oh-package name + local file: deps.
|
|
71
|
+
*/
|
|
72
|
+
export declare function formatHarmonyModuleRoster(projectRoot: string, modules: Array<{
|
|
73
|
+
name: string;
|
|
74
|
+
rootPath: string;
|
|
75
|
+
kind: string;
|
|
76
|
+
fileCount: number;
|
|
77
|
+
}>, maxRows?: number): string;
|
|
33
78
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -41,6 +41,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
42
|
exports.buildProjectMapScan = buildProjectMapScan;
|
|
43
43
|
exports.parseBuildPhase = parseBuildPhase;
|
|
44
|
+
exports.readHarmonyAppBundleName = readHarmonyAppBundleName;
|
|
45
|
+
exports.listHarmonyRouteProfilesUnderModule = listHarmonyRouteProfilesUnderModule;
|
|
46
|
+
exports.readModuleOhPackageName = readModuleOhPackageName;
|
|
47
|
+
exports.scanHarmonyResourceInventory = scanHarmonyResourceInventory;
|
|
48
|
+
exports.formatHarmonyResourceInventory = formatHarmonyResourceInventory;
|
|
49
|
+
exports.formatHarmonyModuleRoster = formatHarmonyModuleRoster;
|
|
44
50
|
const fs = __importStar(require("fs"));
|
|
45
51
|
const path = __importStar(require("path"));
|
|
46
52
|
const extraction_1 = require("../extraction");
|
|
@@ -103,7 +109,8 @@ function discoverModules(projectRoot) {
|
|
|
103
109
|
rootPath: '',
|
|
104
110
|
kind: 'root',
|
|
105
111
|
});
|
|
106
|
-
|
|
112
|
+
// Same parser as ArkTS dirty-module mapping (Spec 0034): bare keys + single quotes.
|
|
113
|
+
const harmony = (0, arkts_1.listHarmonyProjectModules)(projectRoot);
|
|
107
114
|
if (harmony.length > 0) {
|
|
108
115
|
for (const m of harmony) {
|
|
109
116
|
ensure({
|
|
@@ -132,47 +139,6 @@ function discoverModules(projectRoot) {
|
|
|
132
139
|
}
|
|
133
140
|
return [...byId.values()];
|
|
134
141
|
}
|
|
135
|
-
function listHarmonyProjectModulesLoose(projectRoot) {
|
|
136
|
-
const strict = (0, arkts_1.listHarmonyProjectModules)(projectRoot);
|
|
137
|
-
if (strict.length > 0)
|
|
138
|
-
return strict;
|
|
139
|
-
// Real DevEco `build-profile.json5` often uses unquoted keys; the strict
|
|
140
|
-
// parser only strips comments/trailing commas. Quote bare keys and retry.
|
|
141
|
-
const profilePath = path.join(projectRoot, 'build-profile.json5');
|
|
142
|
-
if (!fs.existsSync(profilePath))
|
|
143
|
-
return [];
|
|
144
|
-
let raw;
|
|
145
|
-
try {
|
|
146
|
-
const text = fs.readFileSync(profilePath, 'utf-8');
|
|
147
|
-
const stripped = text
|
|
148
|
-
.replace(/\/\/.*$/gm, '')
|
|
149
|
-
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
150
|
-
.replace(/,(\s*[}\]])/g, '$1')
|
|
151
|
-
.replace(/([{,]\s*)([A-Za-z_][\w]*)\s*:/g, '$1"$2":');
|
|
152
|
-
raw = JSON.parse(stripped);
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
return [];
|
|
156
|
-
}
|
|
157
|
-
if (!raw || typeof raw !== 'object')
|
|
158
|
-
return [];
|
|
159
|
-
const modules = raw.modules;
|
|
160
|
-
if (!Array.isArray(modules))
|
|
161
|
-
return [];
|
|
162
|
-
const out = [];
|
|
163
|
-
for (const entry of modules) {
|
|
164
|
-
if (!entry || typeof entry !== 'object')
|
|
165
|
-
continue;
|
|
166
|
-
const rec = entry;
|
|
167
|
-
if (typeof rec.name !== 'string' || typeof rec.srcPath !== 'string')
|
|
168
|
-
continue;
|
|
169
|
-
const srcPath = (0, arkts_1.normalizeHarmonyModuleSrcPath)(rec.srcPath);
|
|
170
|
-
if (!srcPath)
|
|
171
|
-
continue;
|
|
172
|
-
out.push({ name: rec.name, srcPath });
|
|
173
|
-
}
|
|
174
|
-
return out;
|
|
175
|
-
}
|
|
176
142
|
function discoverOhpmPackages(projectRoot) {
|
|
177
143
|
const out = [];
|
|
178
144
|
const queue = [{ rel: '', depth: 0 }];
|
|
@@ -218,9 +184,19 @@ function discoverOhpmPackages(projectRoot) {
|
|
|
218
184
|
}
|
|
219
185
|
function readOhpmName(manifestAbs) {
|
|
220
186
|
try {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
187
|
+
const text = fs.readFileSync(manifestAbs, 'utf-8');
|
|
188
|
+
try {
|
|
189
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
190
|
+
const parsed = require('jsonc-parser').parse(text);
|
|
191
|
+
if (typeof parsed?.name === 'string' && parsed.name.trim())
|
|
192
|
+
return parsed.name.trim();
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
/* regex */
|
|
196
|
+
}
|
|
197
|
+
const m = text.match(/["']name["']\s*:\s*["']([^"']+)["']/) ||
|
|
198
|
+
text.match(/name\s*:\s*["']([^"']+)["']/);
|
|
199
|
+
return m?.[1]?.trim() || null;
|
|
224
200
|
}
|
|
225
201
|
catch {
|
|
226
202
|
return null;
|
|
@@ -257,4 +233,356 @@ function parseBuildPhase(raw) {
|
|
|
257
233
|
}
|
|
258
234
|
return null;
|
|
259
235
|
}
|
|
236
|
+
const SKELETON_SKIP_DIRS = new Set([
|
|
237
|
+
'node_modules',
|
|
238
|
+
'oh_modules',
|
|
239
|
+
'.git',
|
|
240
|
+
'.homegraph',
|
|
241
|
+
'.hvigor',
|
|
242
|
+
'.preview',
|
|
243
|
+
'build',
|
|
244
|
+
'dist',
|
|
245
|
+
'out',
|
|
246
|
+
'.cxx',
|
|
247
|
+
]);
|
|
248
|
+
const ROUTE_PROFILE_WALK_MAX_DEPTH = 8;
|
|
249
|
+
const ROUTE_PROFILE_DIR_BUDGET = 400;
|
|
250
|
+
const ROUTE_PROFILE_CAP_PER_MODULE = 8;
|
|
251
|
+
/**
|
|
252
|
+
* Spec 0040 — optional bundleName/name from app.json5 (AppScope or root).
|
|
253
|
+
* Best-effort; returns null on missing/parse failure.
|
|
254
|
+
*/
|
|
255
|
+
function readHarmonyAppBundleName(projectRoot) {
|
|
256
|
+
for (const rel of ['AppScope/app.json5', 'app.json5']) {
|
|
257
|
+
const abs = path.join(projectRoot, rel);
|
|
258
|
+
if (!fs.existsSync(abs))
|
|
259
|
+
continue;
|
|
260
|
+
try {
|
|
261
|
+
const text = fs.readFileSync(abs, 'utf-8');
|
|
262
|
+
let bundle = null;
|
|
263
|
+
try {
|
|
264
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
265
|
+
const parsed = require('jsonc-parser').parse(text);
|
|
266
|
+
if (parsed?.app && typeof parsed.app.bundleName === 'string' && parsed.app.bundleName.trim()) {
|
|
267
|
+
bundle = parsed.app.bundleName.trim();
|
|
268
|
+
}
|
|
269
|
+
else if (typeof parsed?.bundleName === 'string' && parsed.bundleName.trim()) {
|
|
270
|
+
bundle = parsed.bundleName.trim();
|
|
271
|
+
}
|
|
272
|
+
else if (typeof parsed?.name === 'string' && parsed.name.trim()) {
|
|
273
|
+
bundle = parsed.name.trim();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
/* regex fallback below */
|
|
278
|
+
}
|
|
279
|
+
if (!bundle) {
|
|
280
|
+
const m = text.match(/["']bundleName["']\s*:\s*["']([^"']+)["']/) ||
|
|
281
|
+
text.match(/bundleName\s*:\s*["']([^"']+)["']/);
|
|
282
|
+
if (m?.[1])
|
|
283
|
+
bundle = m[1];
|
|
284
|
+
}
|
|
285
|
+
if (bundle)
|
|
286
|
+
return bundle;
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Spec 0040 — project-relative paths to Spec 0039 route profile JSON under a module root.
|
|
296
|
+
*/
|
|
297
|
+
function listHarmonyRouteProfilesUnderModule(projectRoot, moduleRootPath, cap = ROUTE_PROFILE_CAP_PER_MODULE) {
|
|
298
|
+
const rootAbs = path.resolve(projectRoot);
|
|
299
|
+
const modAbs = moduleRootPath
|
|
300
|
+
? path.resolve(projectRoot, moduleRootPath)
|
|
301
|
+
: rootAbs;
|
|
302
|
+
if (!fs.existsSync(modAbs))
|
|
303
|
+
return [];
|
|
304
|
+
const found = [];
|
|
305
|
+
const queue = [{ abs: modAbs, depth: 0 }];
|
|
306
|
+
let visited = 0;
|
|
307
|
+
while (queue.length > 0 && found.length < cap) {
|
|
308
|
+
const { abs, depth } = queue.shift();
|
|
309
|
+
if (++visited > ROUTE_PROFILE_DIR_BUDGET)
|
|
310
|
+
break;
|
|
311
|
+
let entries;
|
|
312
|
+
try {
|
|
313
|
+
entries = fs.readdirSync(abs, { withFileTypes: true });
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
for (const e of entries) {
|
|
319
|
+
if (found.length >= cap)
|
|
320
|
+
break;
|
|
321
|
+
const child = path.join(abs, e.name);
|
|
322
|
+
if (e.isDirectory()) {
|
|
323
|
+
if (depth >= ROUTE_PROFILE_WALK_MAX_DEPTH)
|
|
324
|
+
continue;
|
|
325
|
+
if (e.name.startsWith('.') || SKELETON_SKIP_DIRS.has(e.name))
|
|
326
|
+
continue;
|
|
327
|
+
queue.push({ abs: child, depth: depth + 1 });
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (!e.isFile())
|
|
331
|
+
continue;
|
|
332
|
+
const rel = path.relative(rootAbs, child).replace(/\\/g, '/');
|
|
333
|
+
if ((0, grammars_1.isHarmonyRouteProfileJson)(rel))
|
|
334
|
+
found.push(rel);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return found.sort();
|
|
338
|
+
}
|
|
339
|
+
/** Spec 0040 — oh-package name at module root, if any. */
|
|
340
|
+
function readModuleOhPackageName(projectRoot, moduleRootPath) {
|
|
341
|
+
const abs = path.join(projectRoot, moduleRootPath || '.', OHPM_MANIFEST);
|
|
342
|
+
return readOhpmName(abs);
|
|
343
|
+
}
|
|
344
|
+
const RESOURCE_WALK_MAX_DEPTH = 14;
|
|
345
|
+
const RESOURCE_DIR_BUDGET = 4000;
|
|
346
|
+
const RESOURCE_STRING_CAP = 24;
|
|
347
|
+
const RESOURCE_RAWFILE_CAP = 40;
|
|
348
|
+
const RESOURCE_MEDIA_DIR_CAP = 24;
|
|
349
|
+
const RESOURCE_UNINDEXED_CAP = 16;
|
|
350
|
+
const RESOURCE_CAPABILITY_CAP = 16;
|
|
351
|
+
const RESOURCE_CONFIG_BASENAMES = new Set([
|
|
352
|
+
'shortcuts_config.json',
|
|
353
|
+
'form_config.json',
|
|
354
|
+
]);
|
|
355
|
+
/**
|
|
356
|
+
* Bounded walk: whitelist paths under `resources/…` plus package dirs with zero
|
|
357
|
+
* indexed sources. Does not read JSON bodies or media bytes.
|
|
358
|
+
*/
|
|
359
|
+
function scanHarmonyResourceInventory(projectRoot, options) {
|
|
360
|
+
const rootAbs = path.resolve(projectRoot);
|
|
361
|
+
const out = {
|
|
362
|
+
stringJson: [],
|
|
363
|
+
rawfiles: [],
|
|
364
|
+
mediaDirs: [],
|
|
365
|
+
capabilityProfiles: [],
|
|
366
|
+
unindexedDirs: [],
|
|
367
|
+
truncated: false,
|
|
368
|
+
};
|
|
369
|
+
if (!fs.existsSync(rootAbs))
|
|
370
|
+
return out;
|
|
371
|
+
const indexed = new Set();
|
|
372
|
+
for (const p of options?.indexedPaths ?? []) {
|
|
373
|
+
indexed.add(p.replace(/\\/g, '/'));
|
|
374
|
+
}
|
|
375
|
+
const mediaCounts = new Map();
|
|
376
|
+
const packageDirs = new Map();
|
|
377
|
+
const queue = [{ abs: rootAbs, depth: 0 }];
|
|
378
|
+
let visited = 0;
|
|
379
|
+
while (queue.length > 0) {
|
|
380
|
+
const { abs, depth } = queue.shift();
|
|
381
|
+
if (++visited > RESOURCE_DIR_BUDGET) {
|
|
382
|
+
out.truncated = true;
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
let entries;
|
|
386
|
+
try {
|
|
387
|
+
entries = fs.readdirSync(abs, { withFileTypes: true });
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
for (const e of entries) {
|
|
393
|
+
const child = path.join(abs, e.name);
|
|
394
|
+
const rel = path.relative(rootAbs, child).replace(/\\/g, '/');
|
|
395
|
+
if (e.isDirectory()) {
|
|
396
|
+
if (depth >= RESOURCE_WALK_MAX_DEPTH)
|
|
397
|
+
continue;
|
|
398
|
+
if (e.name.startsWith('.') || SKELETON_SKIP_DIRS.has(e.name))
|
|
399
|
+
continue;
|
|
400
|
+
queue.push({ abs: child, depth: depth + 1 });
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (!e.isFile() || !rel || rel.startsWith('..'))
|
|
404
|
+
continue;
|
|
405
|
+
if (/(?:^|\/)resources\/(?:[^/]+\/)*element\/string\.json$/i.test(rel)) {
|
|
406
|
+
if (out.stringJson.length < RESOURCE_STRING_CAP)
|
|
407
|
+
out.stringJson.push(rel);
|
|
408
|
+
else
|
|
409
|
+
out.truncated = true;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (/(?:^|\/)resources\/(?:[^/]+\/)*rawfile\//i.test(rel)) {
|
|
413
|
+
if (out.rawfiles.length < RESOURCE_RAWFILE_CAP)
|
|
414
|
+
out.rawfiles.push(rel);
|
|
415
|
+
else
|
|
416
|
+
out.truncated = true;
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const mediaMatch = rel.match(/^(.*?\/resources\/(?:[^/]+\/)*(?:base\/)?media)\/[^/]+$/i);
|
|
420
|
+
if (mediaMatch) {
|
|
421
|
+
const dir = mediaMatch[1].replace(/\\/g, '/');
|
|
422
|
+
const ext = path.extname(rel).slice(1).toLowerCase() || 'bin';
|
|
423
|
+
const counts = mediaCounts.get(dir) ?? {};
|
|
424
|
+
counts[ext] = (counts[ext] ?? 0) + 1;
|
|
425
|
+
mediaCounts.set(dir, counts);
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
const base = rel.split('/').pop()?.toLowerCase() ?? '';
|
|
429
|
+
if (RESOURCE_CONFIG_BASENAMES.has(base)) {
|
|
430
|
+
if (out.capabilityProfiles.length < RESOURCE_CAPABILITY_CAP) {
|
|
431
|
+
if (!out.capabilityProfiles.includes(rel))
|
|
432
|
+
out.capabilityProfiles.push(rel);
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
out.truncated = true;
|
|
436
|
+
}
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (base === 'oh-package.json5' || base === 'module.json5') {
|
|
440
|
+
const dirRel = rel.includes('/') ? rel.slice(0, rel.lastIndexOf('/')) : '';
|
|
441
|
+
const cur = packageDirs.get(dirRel) ?? { hasOhpm: false, hasModule: false };
|
|
442
|
+
if (base === 'oh-package.json5')
|
|
443
|
+
cur.hasOhpm = true;
|
|
444
|
+
else
|
|
445
|
+
cur.hasModule = true;
|
|
446
|
+
packageDirs.set(dirRel, cur);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
for (const [dir, counts] of [...mediaCounts.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
451
|
+
if (out.mediaDirs.length >= RESOURCE_MEDIA_DIR_CAP) {
|
|
452
|
+
out.truncated = true;
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
out.mediaDirs.push({ dir, countsByExt: counts });
|
|
456
|
+
}
|
|
457
|
+
out.stringJson.sort((a, b) => a.localeCompare(b));
|
|
458
|
+
out.rawfiles.sort((a, b) => a.localeCompare(b));
|
|
459
|
+
out.capabilityProfiles.sort((a, b) => a.localeCompare(b));
|
|
460
|
+
// Unindexed package/module dirs: on disk but no indexed source under them.
|
|
461
|
+
for (const [dirRel, flags] of [...packageDirs.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
462
|
+
if (out.unindexedDirs.length >= RESOURCE_UNINDEXED_CAP) {
|
|
463
|
+
out.truncated = true;
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
if (!dirRel)
|
|
467
|
+
continue; // project root always "indexed" via map
|
|
468
|
+
const prefix = `${dirRel}/`;
|
|
469
|
+
const hasIndexed = [...indexed].some((p) => p === dirRel || p.startsWith(prefix));
|
|
470
|
+
if (hasIndexed)
|
|
471
|
+
continue;
|
|
472
|
+
// Skip pure resource-only package markers under resources/ (not a module root).
|
|
473
|
+
if (/(?:^|\/)resources\//i.test(dirRel))
|
|
474
|
+
continue;
|
|
475
|
+
const reason = flags.hasOhpm
|
|
476
|
+
? 'oh-package.json5 present; 0 indexed source files'
|
|
477
|
+
: 'module.json5 present; 0 indexed source files';
|
|
478
|
+
out.unindexedDirs.push({ path: dirRel, reason });
|
|
479
|
+
}
|
|
480
|
+
return out;
|
|
481
|
+
}
|
|
482
|
+
/** Markdown section for Spec 0042 B (empty string when nothing to show). */
|
|
483
|
+
function formatHarmonyResourceInventory(inv) {
|
|
484
|
+
const lines = [];
|
|
485
|
+
const hasResources = inv.stringJson.length > 0
|
|
486
|
+
|| inv.rawfiles.length > 0
|
|
487
|
+
|| inv.mediaDirs.length > 0
|
|
488
|
+
|| inv.capabilityProfiles.length > 0;
|
|
489
|
+
if (hasResources) {
|
|
490
|
+
lines.push('### HarmonyOS resources');
|
|
491
|
+
if (inv.stringJson.length) {
|
|
492
|
+
const shown = inv.stringJson.slice(0, 12);
|
|
493
|
+
lines.push(`- string.json (${inv.stringJson.length}): ${shown.map((p) => `\`${p}\``).join(', ')}`
|
|
494
|
+
+ (inv.stringJson.length > shown.length ? `, +${inv.stringJson.length - shown.length} more` : ''));
|
|
495
|
+
}
|
|
496
|
+
if (inv.capabilityProfiles.length) {
|
|
497
|
+
const shown = inv.capabilityProfiles.slice(0, 12);
|
|
498
|
+
lines.push(`- capability profiles (${inv.capabilityProfiles.length}): ${shown.map((p) => `\`${p}\``).join(', ')}`
|
|
499
|
+
+ (inv.capabilityProfiles.length > shown.length
|
|
500
|
+
? `, +${inv.capabilityProfiles.length - shown.length} more`
|
|
501
|
+
: ''));
|
|
502
|
+
}
|
|
503
|
+
if (inv.rawfiles.length) {
|
|
504
|
+
const shown = inv.rawfiles.slice(0, 12);
|
|
505
|
+
lines.push(`- rawfile (${inv.rawfiles.length}): ${shown.map((p) => `\`${p}\``).join(', ')}`
|
|
506
|
+
+ (inv.rawfiles.length > shown.length ? `, +${inv.rawfiles.length - shown.length} more` : ''));
|
|
507
|
+
}
|
|
508
|
+
for (const m of inv.mediaDirs.slice(0, 8)) {
|
|
509
|
+
const counts = Object.entries(m.countsByExt)
|
|
510
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
511
|
+
.map(([ext, n]) => `${ext}=${n}`)
|
|
512
|
+
.join(', ');
|
|
513
|
+
lines.push(`- media: \`${m.dir}\`${counts ? ` (${counts})` : ''}`);
|
|
514
|
+
}
|
|
515
|
+
if (inv.mediaDirs.length > 8) {
|
|
516
|
+
lines.push(`- media: … +${inv.mediaDirs.length - 8} more dirs`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (inv.unindexedDirs.length) {
|
|
520
|
+
if (lines.length)
|
|
521
|
+
lines.push('');
|
|
522
|
+
lines.push('### On disk, not in graph');
|
|
523
|
+
for (const u of inv.unindexedDirs) {
|
|
524
|
+
lines.push(`- \`${u.path}/\` (${u.reason})`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (inv.truncated) {
|
|
528
|
+
lines.push('');
|
|
529
|
+
lines.push('_resource inventory truncated (bounded walk)_');
|
|
530
|
+
}
|
|
531
|
+
return lines.join('\n');
|
|
532
|
+
}
|
|
533
|
+
function readOhpmLocalDeps(manifestAbs, cap = 8) {
|
|
534
|
+
try {
|
|
535
|
+
const text = fs.readFileSync(manifestAbs, 'utf-8');
|
|
536
|
+
let deps;
|
|
537
|
+
try {
|
|
538
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
539
|
+
const parsed = require('jsonc-parser').parse(text);
|
|
540
|
+
deps = parsed?.dependencies;
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
deps = undefined;
|
|
544
|
+
}
|
|
545
|
+
if (!deps || typeof deps !== 'object')
|
|
546
|
+
return [];
|
|
547
|
+
const out = [];
|
|
548
|
+
for (const [name, ver] of Object.entries(deps)) {
|
|
549
|
+
if (out.length >= cap)
|
|
550
|
+
break;
|
|
551
|
+
if (typeof ver !== 'string')
|
|
552
|
+
continue;
|
|
553
|
+
if (!/^(file:|\.\.?\/)/i.test(ver.trim()))
|
|
554
|
+
continue;
|
|
555
|
+
out.push(`\`${name}\` (${ver.trim()})`);
|
|
556
|
+
}
|
|
557
|
+
return out;
|
|
558
|
+
}
|
|
559
|
+
catch {
|
|
560
|
+
return [];
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Spec 0048 §4 — module roster with oh-package name + local file: deps.
|
|
565
|
+
*/
|
|
566
|
+
function formatHarmonyModuleRoster(projectRoot, modules, maxRows = 24) {
|
|
567
|
+
const rows = [];
|
|
568
|
+
for (const m of modules) {
|
|
569
|
+
if (rows.length >= maxRows)
|
|
570
|
+
break;
|
|
571
|
+
if (m.kind === 'root' && !m.rootPath)
|
|
572
|
+
continue;
|
|
573
|
+
const rootLabel = m.rootPath || '.';
|
|
574
|
+
const ohpmAbs = path.join(projectRoot, m.rootPath || '.', OHPM_MANIFEST);
|
|
575
|
+
const pkg = readOhpmName(ohpmAbs);
|
|
576
|
+
const localDeps = readOhpmLocalDeps(ohpmAbs);
|
|
577
|
+
let line = `- \`${m.name}\` (\`${rootLabel}\`) · ${m.kind} · ${m.fileCount} files`;
|
|
578
|
+
if (pkg)
|
|
579
|
+
line += ` · oh-package \`${pkg}\``;
|
|
580
|
+
if (localDeps.length)
|
|
581
|
+
line += ` · deps: ${localDeps.join(', ')}`;
|
|
582
|
+
rows.push(line);
|
|
583
|
+
}
|
|
584
|
+
if (rows.length === 0)
|
|
585
|
+
return '';
|
|
586
|
+
return ['### Module roster', ...rows].join('\n');
|
|
587
|
+
}
|
|
260
588
|
//# sourceMappingURL=index.js.map
|