@hbarefoot/engram 1.2.0 → 1.3.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/bin/engram.js +4 -1
- package/dashboard/dist/assets/{index-D0xT6oKC.css → index-CIMIyJGP.css} +1 -1
- package/dashboard/dist/assets/index-CK-bEXRL.js +45 -0
- package/dashboard/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/import/parsers/claude.js +69 -17
- package/src/import/parsers/cursorrules.js +55 -16
- package/src/import/parsers/env.js +52 -11
- package/src/import/parsers/git.js +54 -11
- package/src/import/parsers/obsidian.js +17 -2
- package/src/import/parsers/package.js +55 -8
- package/src/import/parsers/shell.js +60 -30
- package/src/import/parsers/ssh.js +57 -10
- package/src/import/wizard.js +14 -5
- package/src/server/rest.js +39 -5
- package/dashboard/dist/assets/index-D3bysGhj.js +0 -45
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Engram Dashboard</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CK-bEXRL.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CIMIyJGP.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
3
4
|
import { validateContent } from '../../extract/secrets.js';
|
|
4
5
|
|
|
5
6
|
const CLAUDE_LOCATIONS = [
|
|
@@ -11,43 +12,94 @@ const CLAUDE_LOCATIONS = [
|
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Detect if .claude project files exist
|
|
15
|
+
* @param {Object} [options] - Detection options
|
|
16
|
+
* @param {string} [options.cwd] - Working directory to scan
|
|
17
|
+
* @param {string[]} [options.paths] - Additional directories to scan
|
|
18
|
+
* @returns {{ found: boolean, path: string|null, paths: string[] }}
|
|
14
19
|
*/
|
|
15
20
|
export function detect(options = {}) {
|
|
16
21
|
const cwd = options.cwd || process.cwd();
|
|
22
|
+
const foundPaths = [];
|
|
23
|
+
const seen = new Set();
|
|
17
24
|
|
|
25
|
+
// Check cwd
|
|
18
26
|
for (const loc of CLAUDE_LOCATIONS) {
|
|
19
27
|
const fullPath = path.resolve(cwd, loc);
|
|
20
28
|
if (fs.existsSync(fullPath)) {
|
|
21
|
-
|
|
29
|
+
const dir = path.resolve(cwd);
|
|
30
|
+
if (!seen.has(dir)) {
|
|
31
|
+
seen.add(dir);
|
|
32
|
+
foundPaths.push(dir);
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
22
35
|
}
|
|
23
36
|
}
|
|
24
37
|
|
|
25
|
-
|
|
38
|
+
// Always check home directory for user-level .claude
|
|
39
|
+
const homeDir = os.homedir();
|
|
40
|
+
for (const loc of CLAUDE_LOCATIONS) {
|
|
41
|
+
const fullPath = path.resolve(homeDir, loc);
|
|
42
|
+
if (fs.existsSync(fullPath)) {
|
|
43
|
+
const dir = path.resolve(homeDir);
|
|
44
|
+
if (!seen.has(dir)) {
|
|
45
|
+
seen.add(dir);
|
|
46
|
+
foundPaths.push(dir);
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check additional paths
|
|
53
|
+
if (options.paths && Array.isArray(options.paths)) {
|
|
54
|
+
for (const extraDir of options.paths) {
|
|
55
|
+
for (const loc of CLAUDE_LOCATIONS) {
|
|
56
|
+
const fullPath = path.resolve(extraDir, loc);
|
|
57
|
+
if (fs.existsSync(fullPath)) {
|
|
58
|
+
const dir = path.resolve(extraDir);
|
|
59
|
+
if (!seen.has(dir)) {
|
|
60
|
+
seen.add(dir);
|
|
61
|
+
foundPaths.push(dir);
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
found: foundPaths.length > 0,
|
|
71
|
+
path: foundPaths[0] || null,
|
|
72
|
+
paths: foundPaths
|
|
73
|
+
};
|
|
26
74
|
}
|
|
27
75
|
|
|
28
76
|
/**
|
|
29
77
|
* Parse .claude project files into memory candidates
|
|
78
|
+
* Scans cwd, home directory, and any additional paths
|
|
30
79
|
*/
|
|
31
80
|
export async function parse(options = {}) {
|
|
32
81
|
const result = { source: 'claude', memories: [], skipped: [], warnings: [] };
|
|
33
|
-
const
|
|
82
|
+
const detected = detect(options);
|
|
83
|
+
const dirsToScan = detected.paths.length > 0 ? detected.paths : [options.cwd || process.cwd()];
|
|
34
84
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
85
|
+
for (const dir of dirsToScan) {
|
|
86
|
+
// Parse CLAUDE.md
|
|
87
|
+
const claudeMdPath = path.resolve(dir, 'CLAUDE.md');
|
|
88
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
89
|
+
parseClaudeMd(claudeMdPath, result);
|
|
90
|
+
}
|
|
40
91
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
92
|
+
// Parse .claude/settings.json
|
|
93
|
+
const settingsPath = path.resolve(dir, '.claude/settings.json');
|
|
94
|
+
if (fs.existsSync(settingsPath)) {
|
|
95
|
+
parseClaudeSettings(settingsPath, result);
|
|
96
|
+
}
|
|
46
97
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
98
|
+
// Parse .claude/commands directory
|
|
99
|
+
const commandsPath = path.resolve(dir, '.claude/commands');
|
|
100
|
+
if (fs.existsSync(commandsPath) && fs.statSync(commandsPath).isDirectory()) {
|
|
101
|
+
parseClaudeCommands(commandsPath, result);
|
|
102
|
+
}
|
|
51
103
|
}
|
|
52
104
|
|
|
53
105
|
if (result.memories.length === 0) {
|
|
@@ -15,25 +15,48 @@ const CURSORRULES_LOCATIONS = [
|
|
|
15
15
|
* Detect if .cursorrules exists
|
|
16
16
|
* @param {Object} options
|
|
17
17
|
* @param {string} [options.cwd] - Working directory to scan
|
|
18
|
-
* @
|
|
18
|
+
* @param {string[]} [options.paths] - Additional directories to scan
|
|
19
|
+
* @returns {{ found: boolean, path: string|null, paths: string[] }}
|
|
19
20
|
*/
|
|
20
21
|
export function detect(options = {}) {
|
|
21
22
|
const cwd = options.cwd || process.cwd();
|
|
23
|
+
const foundPaths = [];
|
|
24
|
+
const seen = new Set();
|
|
22
25
|
|
|
26
|
+
// Check cwd
|
|
23
27
|
for (const loc of CURSORRULES_LOCATIONS) {
|
|
24
28
|
const fullPath = path.resolve(cwd, loc);
|
|
25
|
-
if (fs.existsSync(fullPath)) {
|
|
26
|
-
|
|
29
|
+
if (!seen.has(fullPath) && fs.existsSync(fullPath)) {
|
|
30
|
+
seen.add(fullPath);
|
|
31
|
+
foundPaths.push(fullPath);
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
//
|
|
31
|
-
const homePath = path.
|
|
32
|
-
if (fs.existsSync(homePath)) {
|
|
33
|
-
|
|
35
|
+
// Check home directory
|
|
36
|
+
const homePath = path.resolve(os.homedir(), '.cursorrules');
|
|
37
|
+
if (!seen.has(homePath) && fs.existsSync(homePath)) {
|
|
38
|
+
seen.add(homePath);
|
|
39
|
+
foundPaths.push(homePath);
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
// Check additional paths
|
|
43
|
+
if (options.paths && Array.isArray(options.paths)) {
|
|
44
|
+
for (const dir of options.paths) {
|
|
45
|
+
for (const loc of CURSORRULES_LOCATIONS) {
|
|
46
|
+
const fullPath = path.resolve(dir, loc);
|
|
47
|
+
if (!seen.has(fullPath) && fs.existsSync(fullPath)) {
|
|
48
|
+
seen.add(fullPath);
|
|
49
|
+
foundPaths.push(fullPath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
found: foundPaths.length > 0,
|
|
57
|
+
path: foundPaths[0] || null,
|
|
58
|
+
paths: foundPaths
|
|
59
|
+
};
|
|
37
60
|
}
|
|
38
61
|
|
|
39
62
|
/**
|
|
@@ -46,20 +69,38 @@ export function detect(options = {}) {
|
|
|
46
69
|
export async function parse(options = {}) {
|
|
47
70
|
const result = { source: 'cursorrules', memories: [], skipped: [], warnings: [] };
|
|
48
71
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
72
|
+
// If explicit filePath, parse just that one (backward compat)
|
|
73
|
+
if (options.filePath) {
|
|
74
|
+
parseOneCursorrules(options.filePath, result);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
53
77
|
|
|
54
|
-
|
|
78
|
+
const detected = detect(options);
|
|
79
|
+
if (!detected.found) {
|
|
55
80
|
result.warnings.push('No .cursorrules file found');
|
|
56
81
|
return result;
|
|
57
82
|
}
|
|
58
83
|
|
|
84
|
+
for (const filePath of detected.paths) {
|
|
85
|
+
parseOneCursorrules(filePath, result);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Parse a single .cursorrules file and accumulate results
|
|
93
|
+
*/
|
|
94
|
+
function parseOneCursorrules(filePath, result) {
|
|
95
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
96
|
+
result.warnings.push('No .cursorrules file found');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
59
100
|
const content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
60
101
|
if (!content) {
|
|
61
102
|
result.warnings.push('.cursorrules file is empty');
|
|
62
|
-
return
|
|
103
|
+
return;
|
|
63
104
|
}
|
|
64
105
|
|
|
65
106
|
const lines = content.split('\n');
|
|
@@ -109,8 +150,6 @@ export async function parse(options = {}) {
|
|
|
109
150
|
if (currentBlock.length > 0) {
|
|
110
151
|
flushBlock(currentBlock, currentSection, result);
|
|
111
152
|
}
|
|
112
|
-
|
|
113
|
-
return result;
|
|
114
153
|
}
|
|
115
154
|
|
|
116
155
|
/**
|
|
@@ -3,19 +3,44 @@ import path from 'path';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Detect if .env.example exists
|
|
6
|
+
* @param {Object} [options] - Detection options
|
|
7
|
+
* @param {string} [options.cwd] - Working directory to scan
|
|
8
|
+
* @param {string[]} [options.paths] - Additional directories to scan
|
|
9
|
+
* @returns {{ found: boolean, path: string|null, paths: string[] }}
|
|
6
10
|
*/
|
|
7
11
|
export function detect(options = {}) {
|
|
8
12
|
const candidates = ['.env.example', '.env.sample', '.env.template'];
|
|
9
13
|
const baseDir = options.cwd || process.cwd();
|
|
14
|
+
const foundPaths = [];
|
|
15
|
+
const seen = new Set();
|
|
10
16
|
|
|
17
|
+
// Check cwd
|
|
11
18
|
for (const name of candidates) {
|
|
12
19
|
const filePath = path.resolve(baseDir, name);
|
|
13
|
-
if (fs.existsSync(filePath)) {
|
|
14
|
-
|
|
20
|
+
if (!seen.has(filePath) && fs.existsSync(filePath)) {
|
|
21
|
+
seen.add(filePath);
|
|
22
|
+
foundPaths.push(filePath);
|
|
15
23
|
}
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
// Check additional paths
|
|
27
|
+
if (options.paths && Array.isArray(options.paths)) {
|
|
28
|
+
for (const dir of options.paths) {
|
|
29
|
+
for (const name of candidates) {
|
|
30
|
+
const filePath = path.resolve(dir, name);
|
|
31
|
+
if (!seen.has(filePath) && fs.existsSync(filePath)) {
|
|
32
|
+
seen.add(filePath);
|
|
33
|
+
foundPaths.push(filePath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
found: foundPaths.length > 0,
|
|
41
|
+
path: foundPaths[0] || null,
|
|
42
|
+
paths: foundPaths
|
|
43
|
+
};
|
|
19
44
|
}
|
|
20
45
|
|
|
21
46
|
/**
|
|
@@ -25,16 +50,34 @@ export function detect(options = {}) {
|
|
|
25
50
|
export async function parse(options = {}) {
|
|
26
51
|
const result = { source: 'env', memories: [], skipped: [], warnings: [] };
|
|
27
52
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
53
|
+
// If explicit filePath, parse just that one (backward compat)
|
|
54
|
+
if (options.filePath) {
|
|
55
|
+
parseOneEnv(options.filePath, result);
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
32
58
|
|
|
33
|
-
|
|
59
|
+
const detected = detect(options);
|
|
60
|
+
if (!detected.found) {
|
|
34
61
|
result.warnings.push('No .env.example file found');
|
|
35
62
|
return result;
|
|
36
63
|
}
|
|
37
64
|
|
|
65
|
+
for (const filePath of detected.paths) {
|
|
66
|
+
parseOneEnv(filePath, result);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parse a single .env.example file and accumulate results
|
|
74
|
+
*/
|
|
75
|
+
function parseOneEnv(filePath, result) {
|
|
76
|
+
if (!fs.existsSync(filePath)) {
|
|
77
|
+
result.warnings.push(`No .env.example file found at ${filePath}`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
38
81
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
39
82
|
const lines = content.split('\n');
|
|
40
83
|
|
|
@@ -68,7 +111,7 @@ export async function parse(options = {}) {
|
|
|
68
111
|
|
|
69
112
|
if (variables.length === 0) {
|
|
70
113
|
result.warnings.push('.env.example has no variables');
|
|
71
|
-
return
|
|
114
|
+
return;
|
|
72
115
|
}
|
|
73
116
|
|
|
74
117
|
// Group variables by prefix for cleaner memories
|
|
@@ -116,8 +159,6 @@ export async function parse(options = {}) {
|
|
|
116
159
|
|
|
117
160
|
// Security warning
|
|
118
161
|
result.warnings.push('Only variable NAMES were extracted — no values or secrets');
|
|
119
|
-
|
|
120
|
-
return result;
|
|
121
162
|
}
|
|
122
163
|
|
|
123
164
|
export const meta = {
|
|
@@ -10,14 +10,41 @@ const GITCONFIG_LOCATIONS = [
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Detect if git config exists
|
|
13
|
+
* @param {Object} [options] - Detection options
|
|
14
|
+
* @param {string[]} [options.paths] - Additional directories to scan for gitconfig
|
|
15
|
+
* @returns {{ found: boolean, path: string|null, paths: string[] }}
|
|
13
16
|
*/
|
|
14
|
-
export function detect() {
|
|
17
|
+
export function detect(options = {}) {
|
|
18
|
+
const foundPaths = [];
|
|
19
|
+
const seen = new Set();
|
|
20
|
+
|
|
15
21
|
for (const loc of GITCONFIG_LOCATIONS) {
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
const resolved = path.resolve(loc);
|
|
23
|
+
if (!seen.has(resolved) && fs.existsSync(resolved)) {
|
|
24
|
+
seen.add(resolved);
|
|
25
|
+
foundPaths.push(resolved);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check additional paths for gitconfig files
|
|
30
|
+
if (options.paths && Array.isArray(options.paths)) {
|
|
31
|
+
for (const dir of options.paths) {
|
|
32
|
+
for (const name of ['.gitconfig', '.config/git/config']) {
|
|
33
|
+
const loc = path.join(dir, name);
|
|
34
|
+
const resolved = path.resolve(loc);
|
|
35
|
+
if (!seen.has(resolved) && fs.existsSync(resolved)) {
|
|
36
|
+
seen.add(resolved);
|
|
37
|
+
foundPaths.push(resolved);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
18
40
|
}
|
|
19
41
|
}
|
|
20
|
-
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
found: foundPaths.length > 0,
|
|
45
|
+
path: foundPaths[0] || null,
|
|
46
|
+
paths: foundPaths
|
|
47
|
+
};
|
|
21
48
|
}
|
|
22
49
|
|
|
23
50
|
/**
|
|
@@ -26,16 +53,34 @@ export function detect() {
|
|
|
26
53
|
export async function parse(options = {}) {
|
|
27
54
|
const result = { source: 'git', memories: [], skipped: [], warnings: [] };
|
|
28
55
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
56
|
+
// If explicit filePath, parse just that one (backward compat)
|
|
57
|
+
if (options.filePath) {
|
|
58
|
+
parseOneGitconfig(options.filePath, result);
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
33
61
|
|
|
34
|
-
|
|
62
|
+
const detected = detect(options);
|
|
63
|
+
if (!detected.found) {
|
|
35
64
|
result.warnings.push('No .gitconfig found');
|
|
36
65
|
return result;
|
|
37
66
|
}
|
|
38
67
|
|
|
68
|
+
for (const filePath of detected.paths) {
|
|
69
|
+
parseOneGitconfig(filePath, result);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Parse a single .gitconfig file and accumulate results
|
|
77
|
+
*/
|
|
78
|
+
function parseOneGitconfig(filePath, result) {
|
|
79
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
80
|
+
result.warnings.push('No .gitconfig found');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
39
84
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
40
85
|
const sections = parseIniFile(content);
|
|
41
86
|
|
|
@@ -146,8 +191,6 @@ export async function parse(options = {}) {
|
|
|
146
191
|
source: 'import:git'
|
|
147
192
|
});
|
|
148
193
|
}
|
|
149
|
-
|
|
150
|
-
return result;
|
|
151
194
|
}
|
|
152
195
|
|
|
153
196
|
/**
|
|
@@ -15,9 +15,24 @@ const VAULT_SEARCH_DIRS = [
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Detect Obsidian vaults by looking for .obsidian directories
|
|
18
|
+
* @param {Object} [options] - Detection options
|
|
19
|
+
* @param {string[]} [options.vaultPaths] - Explicit vault paths to check
|
|
20
|
+
* @param {string[]} [options.paths] - Additional directories to scan for vaults
|
|
21
|
+
* @returns {{ found: boolean, path: string|null, paths: string[], vaults: string[] }}
|
|
18
22
|
*/
|
|
19
23
|
export function detect(options = {}) {
|
|
20
|
-
const searchDirs = options.vaultPaths || VAULT_SEARCH_DIRS;
|
|
24
|
+
const searchDirs = [...(options.vaultPaths || VAULT_SEARCH_DIRS)];
|
|
25
|
+
|
|
26
|
+
// Merge additional paths into search dirs
|
|
27
|
+
if (options.paths && Array.isArray(options.paths)) {
|
|
28
|
+
for (const dir of options.paths) {
|
|
29
|
+
const resolved = path.resolve(dir);
|
|
30
|
+
if (!searchDirs.includes(resolved)) {
|
|
31
|
+
searchDirs.push(resolved);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
const vaults = [];
|
|
22
37
|
|
|
23
38
|
for (const dir of searchDirs) {
|
|
@@ -37,7 +52,7 @@ export function detect(options = {}) {
|
|
|
37
52
|
}
|
|
38
53
|
}
|
|
39
54
|
|
|
40
|
-
return { found: vaults.length > 0, path: vaults[0] || null, vaults };
|
|
55
|
+
return { found: vaults.length > 0, path: vaults[0] || null, paths: vaults, vaults };
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
/**
|
|
@@ -3,11 +3,39 @@ import path from 'path';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Detect if package.json exists
|
|
6
|
+
* @param {Object} [options] - Detection options
|
|
7
|
+
* @param {string} [options.cwd] - Working directory to scan
|
|
8
|
+
* @param {string[]} [options.paths] - Additional directories to scan
|
|
9
|
+
* @returns {{ found: boolean, path: string|null, paths: string[] }}
|
|
6
10
|
*/
|
|
7
11
|
export function detect(options = {}) {
|
|
8
12
|
const cwd = options.cwd || process.cwd();
|
|
13
|
+
const foundPaths = [];
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
|
|
16
|
+
// Check cwd
|
|
9
17
|
const filePath = path.resolve(cwd, 'package.json');
|
|
10
|
-
|
|
18
|
+
if (fs.existsSync(filePath)) {
|
|
19
|
+
seen.add(filePath);
|
|
20
|
+
foundPaths.push(filePath);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check additional paths
|
|
24
|
+
if (options.paths && Array.isArray(options.paths)) {
|
|
25
|
+
for (const dir of options.paths) {
|
|
26
|
+
const p = path.resolve(dir, 'package.json');
|
|
27
|
+
if (!seen.has(p) && fs.existsSync(p)) {
|
|
28
|
+
seen.add(p);
|
|
29
|
+
foundPaths.push(p);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
found: foundPaths.length > 0,
|
|
36
|
+
path: foundPaths[0] || null,
|
|
37
|
+
paths: foundPaths
|
|
38
|
+
};
|
|
11
39
|
}
|
|
12
40
|
|
|
13
41
|
/**
|
|
@@ -15,20 +43,41 @@ export function detect(options = {}) {
|
|
|
15
43
|
*/
|
|
16
44
|
export async function parse(options = {}) {
|
|
17
45
|
const result = { source: 'package', memories: [], skipped: [], warnings: [] };
|
|
18
|
-
const cwd = options.cwd || process.cwd();
|
|
19
|
-
const filePath = options.filePath || path.resolve(cwd, 'package.json');
|
|
20
46
|
|
|
21
|
-
|
|
47
|
+
// If explicit filePath, parse just that one (backward compat)
|
|
48
|
+
if (options.filePath) {
|
|
49
|
+
parseOnePackage(options.filePath, result);
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const detected = detect(options);
|
|
54
|
+
if (!detected.found) {
|
|
22
55
|
result.warnings.push('No package.json found');
|
|
23
56
|
return result;
|
|
24
57
|
}
|
|
25
58
|
|
|
59
|
+
for (const filePath of detected.paths) {
|
|
60
|
+
parseOnePackage(filePath, result);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Parse a single package.json file and accumulate results
|
|
68
|
+
*/
|
|
69
|
+
function parseOnePackage(filePath, result) {
|
|
70
|
+
if (!fs.existsSync(filePath)) {
|
|
71
|
+
result.warnings.push(`No package.json found at ${filePath}`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
26
75
|
let pkg;
|
|
27
76
|
try {
|
|
28
77
|
pkg = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
29
78
|
} catch {
|
|
30
|
-
result.warnings.push(
|
|
31
|
-
return
|
|
79
|
+
result.warnings.push(`Failed to parse ${filePath}`);
|
|
80
|
+
return;
|
|
32
81
|
}
|
|
33
82
|
|
|
34
83
|
// Project name and description
|
|
@@ -164,8 +213,6 @@ export async function parse(options = {}) {
|
|
|
164
213
|
});
|
|
165
214
|
}
|
|
166
215
|
}
|
|
167
|
-
|
|
168
|
-
return result;
|
|
169
216
|
}
|
|
170
217
|
|
|
171
218
|
export const meta = {
|